Tag Archives: wordpress

Migration: Zeroboard to WordPress (Using Ruby)

오늘 하루 내가 겪은 제로보드에서 워드프레스로의 이동에 대해서 간략히 정리해 놓고자 한다.

  1. 옮길 테이블 정하기
    나의 경우에는 freeboard, letters, link 등 총 7개 게시판을 옮기기로 계획했다.
  2. DB Backup & 로컬 SQL 서버에 Restore
    기존 제로보드 홈페이지에서 DB를 백업 받는다. 나의 경우 호스팅 업체에서 sqldump로 전체 DB를 백업 받을 수 있도록 되어 있었다. 다운로드 받은 sql 파일을 로컬 서버의 mysql 콘솔을 통해서 restore한다.
  3. Ruby Gem Install
    내가 사용했던 gem들은 mysql-ruby와 libxml이었다.

    install gem mysql-ruby
    install gem libxml

    나의 경우에는 처음에 이렇게 설치하고 irb에서 require ‘mysql’ 을 했을 때 “invalid win32 application”이라고 에러가 났었는데, sql server를 64bit 버전으로 설치한 것이 문제였다. sql server를 32bit로 다시 설치했다. 각각 API Doc 주소는 다음과 같다.
    mysql: http://www.tmtm.org/en/mysql/ruby/
    libxml: http://libxml.rubyforge.org/rdoc/index.html

  4. Zeroboard의 field와 WordPress XML 포맷의 element 매칭
    제로보드는 sql server의 DB를 뒤져보면 필드가 쭉 나오는데, 문제는 워드프레스의 경우 DTD도 마땅히 찾기 힘들다는데 문제가 있었다. 이 사이트를 참고해서 WXR에 대해서도 대략 알게 되었다. 내가 정리한 board와 comment 테이블에서의 zeroboard와 wxr의 관계는 다음과 같다.

    • zetyx_[boardname] <=> WXR item
      • no <=> wp:post_id
      • memo <=> content:encoded
      • name <=> dc:creator
      • subject <=> title
      • reg_date <=> pubDate, wp:post_date, wp:post_date_gmt
    • zetyx_comment_[boardname] <=> WXR item
      • no <=> wp:comment_id
      • name <=> wp:comment_author
      • memo <=> wp:comment_content
      • ip <=> wp:comment_author_IP
      • reg_date <=> wp:comment_date, wp:comment_date_gmt
  5. Ruby 코드 짜기
    실제 루비 코드를 짜는데 있어서 크게 두 가지 정도가 방해가 되었는데, 하나는 xml encoding을 utf-8으로 기록하는 것과 CDATA 블럭을 넣는 방법을 몰랐었다. 둘 다 RDoc에 나오니까 “찾기”를 사용해서 찾으면 금방 나올 것이다. (내가 사용했던 코드는 이 글 제일 아래에 붙여 놓았다.)
  6. WordPress에서 import하기
    여기서 결정적으로 난국에 부딪혔던 것이 한글 문제였는데, 처음에 import 했을 때 한글이 다 깨져서 나오길래 왜 그런가 하고 봤더니 ruby에서 저장한 문서는 ANSI로 저장이 되어 있어서 그랬다. 편집기로 열어서 다른 이름으로 저장을 해서 UTF-8으로 저장하고 import하면 깔끔하게 된다. 이어서 또 귀찮았던 것은 import할 때 사람 이름이 user로 등록되어 있지 않으면 어떻게 처리할지 모두 다 물어보는 것이었다. 한글 아이디는 생성이 안 되기 때문에 모든 한글 이름을 그에 해당하는 영문 id를 입력해 주었다. 마지막으로 파일 크기가 너무 크면 한 번에 잘 업로드가 안 되니까 한 50개에서 100개 정도로 끊어서 import하는 것이 (최소한 내 경험 상에서는) 적절한 듯 싶다.

다음은 내가 짰던 코드다.

main.rb

# To change this template, choose Tools | Templates
# and open the template in the editor.

puts "Hello World"

require "mysql"
require "xml"

#####
# Creating basic rss
#####

doc = XML::Document.new()
doc.encoding = XML::Encoding.encoding_to_s(XML::Encoding::UTF8)

doc.root = XML::Node.new('rss')
root = doc.root
root['version'] = '2.0'
root['xmlns:content'] = 'http://purl.org/rss/1.0/modules/content/'
root['xmlns:wfw'] = 'http://wellformedweb.org/CommentAPI/'
root['xmlns:dc'] = 'http://purl.org/dc/elements/1.1/'
root['xmlns:wp'] = 'http://wordpress.org/export/1.0/'

root << channel = XML::Node.new('channel')
channel << title = XML::Node.new('title') << 'oksure.org'
channel << link = XML::Node.new('link') << 'http://oksure.byus.net/09012003'
channel << description = XML::Node.new('description') << 'oksure.org'
channel << pubDate = XML::Node.new('pubDate') << 'Wed, 10 Sep 2003 00:00:00 +0000'
channel << generator = XML::Node.new('generator') << 'http://wordpress.org/?v=MU'
channel << language = XML::Node.new('language') << 'ko'
channel << wp_wxr_version = XML::Node.new('wp:wxr_version') << '1.0'
channel << wp_category = XML::Node.new('wp:category')
wp_category << wp_category_nickname = XML::Node.new('wp:category_nickname') << 'season1'
wp_category << wp_category_parent = XML::Node.new('wp:category_parent')
wp_category << wp_cat_name = XML::Node.new('wp:cat_name') << XML::Node.new_cdata('Season 1')
wp_category << wp_category_description = XML::Node.new('wp:category_description') << XML::Node.new_cdata('Archive from oksure.org [2003-2004] ')
['banner', 'diary', 'freeboard', 'letter', 'link', 'profile', 'poem'].each do |category|
	channel << wp_category = XML::Node.new('wp:category')
	wp_category << wp_category_nickname = XML::Node.new('wp:category_nickname') << category
	wp_category << wp_category_parent = XML::Node.new('wp:category_parent') << 'Season 1'
	wp_category << wp_cat_name = XML::Node.new('wp:cat_name') << XML::Node.new_cdata(category.capitalize)
end

#####
# MySQL part
#####

db = Mysql.real_connect("localhost", "root", "********", "oksurenet_backup_20081120")

zb = "zetyx_"
boards = ["freeboard", "4u_poem", "letters", "profile", "link", "diary", "main_ban"]

boards.each do |board|
	puts board + " is being processed."

	category_name = board unless ["main_ban", "4u_poem", "letters"].include?(board)
	if board == "letters"
		category_name = "letter"
	elsif board == "main_ban"
		category_name = "banner"
	elsif board == "4u_poem"
		category_name = "poem"
	end

	result = db.query("SELECT * FROM " + zb + "board_" + board)
	result.each_hash do |row|
		channel << item = XML::Node.new('item')
		item << title = XML::Node.new('title') << row["subject"]
		item << link = XML::Node.new('link')
		item << pubDate = XML::Node.new('pubDate') << Time.at(row["reg_date"].to_i).strftime("%a, %d %b %Y %H:%M:%S +0000")
		item << dc_creator = XML::Node.new('dc:creator') << XML::Node.new_cdata(row["name"])
		item << category = XML::Node.new('category') << XML::Node.new_cdata(category_name.capitalize)
		item << category = XML::Node.new('category') << XML::Node.new_cdata(category_name.capitalize)
		category['domain'] = 'category'
		category['nicename'] = category_name
		item << guid = XML::Node.new('guid')
		guid['isPermaLink'] = 'false'
		item << description = XML::Node.new('description')
		item << content_encoded = XML::Node.new('content:encoded') << XML::Node.new_cdata(row["memo"])
		item << content_excerpt = XML::Node.new('content:excerpt') << XML::Node.new_cdata('')
		item << wp_post_id = XML::Node.new('wp:post_id') << row["no"]
		item << wp_post_date = XML::Node.new('wp:post_date') << Time.at(row["reg_date"].to_i).strftime("%Y-%m-%d %H:%M:%S")
		item << wp_post_date_gmt = XML::Node.new('wp:post_date_gmt') << (Time.at(row["reg_date"].to_i) - (60 * 60 * 9)).strftime("%Y-%m-%d %H:%M:%S")
		item << wp_comment_status = XML::Node.new('wp:comment_status') << 'open'
		item << wp_ping_status = XML::Node.new('wp:ping_status') << 'open'
		item << wp_post_name = XML::Node.new('wp:post_name')
		item << wp_status = XML::Node.new('wp:status') << 'publish'
		item << wp_post_parent = XML::Node.new('wp:post_parent') << '0'
		item << wp_menu_order = XML::Node.new('wp:menu_order') << '0'
		item << wp_post_type = XML::Node.new('wp:post_type') << 'post'
		item << wp_post_password = XML::Node.new('wp:post_password')

		result_comment = db.query("SELECT * FROM " + zb + "board_comment_" + board + " where parent = " + row["no"])
		result_comment.each_hash do |row_comment|
			item << wp_comment = XML::Node.new('wp:comment')
			wp_comment << wp_comment_id = XML::Node.new('wp:comment_id') << row_comment["no"]
			wp_comment << wp_comment_author = XML::Node.new('wp:comment_author') << XML::Node.new_cdata(row_comment["name"])
			wp_comment << wp_comment_author_email = XML::Node.new('wp:comment_author_email')
			wp_comment << wp_comment_url = XML::Node.new('wp:comment_url')
			wp_comment << wp_comment_IP = XML::Node.new('wp:comment_IP') << row_comment["ip"]
			wp_comment << wp_comment_date = XML::Node.new('wp:comment_date') << Time.at(row_comment["reg_date"].to_i).strftime("%Y-%m-%d %H:%M:%S")
			wp_comment << wp_comment_date_gmt = XML::Node.new('wp:comment_date_gmt') << (Time.at(row_comment["reg_date"].to_i) - (60 * 60 * 9)).strftime("%Y-%m-%d %H:%M:%S")
			wp_comment << wp_comment_content = XML::Node.new('wp:comment_content') << XML::Node.new_cdata(row_comment["memo"])
			wp_comment << wp_comment_approved = XML::Node.new('wp:comment_approved') << '1'
			wp_comment << wp_comment_type = XML::Node.new('wp:comment_type')
			wp_comment << wp_comment_parent = XML::Node.new('wp:comment_parent') << '0'
			wp_comment << wp_comment_user_id = XML::Node.new('wp:comment_user_id') << '0'
		end
	end
	puts "Number of rows returned: #{result.num_rows}"

end

puts "===== Created WXR (WordPress eXtended Rss) file ====="

db.close

#####
# Writing XML
#####

format = true
doc.save('C:\libxml.xml', format)