Skip to content


Migration: Zeroboard to Wordpress (Using Ruby)

  • Twitter
  • E-mail this story to a friend!
  • Turn this article into a PDF!
  • Facebook
  • Google Bookmarks
  • del.icio.us

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

  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 &lt;&lt; channel = XML::Node.new('channel')
channel &lt;&lt; title = XML::Node.new('title') &lt;&lt; 'oksure.org'
channel &lt;&lt; link = XML::Node.new('link') &lt;&lt; 'http://oksure.byus.net/09012003'
channel &lt;&lt; description = XML::Node.new('description') &lt;&lt; 'oksure.org'
channel &lt;&lt; pubDate = XML::Node.new('pubDate') &lt;&lt; 'Wed, 10 Sep 2003 00:00:00 +0000'
channel &lt;&lt; generator = XML::Node.new('generator') &lt;&lt; 'http://wordpress.org/?v=MU'
channel &lt;&lt; language = XML::Node.new('language') &lt;&lt; 'ko'
channel &lt;&lt; wp_wxr_version = XML::Node.new('wp:wxr_version') &lt;&lt; '1.0'
channel &lt;&lt; wp_category = XML::Node.new('wp:category')
wp_category &lt;&lt; wp_category_nickname = XML::Node.new('wp:category_nickname') &lt;&lt; 'season1'
wp_category &lt;&lt; wp_category_parent = XML::Node.new('wp:category_parent')
wp_category &lt;&lt; wp_cat_name = XML::Node.new('wp:cat_name') &lt;&lt; XML::Node.new_cdata('Season 1')
wp_category &lt;&lt; wp_category_description = XML::Node.new('wp:category_description') &lt;&lt; XML::Node.new_cdata('Archive from oksure.org [2003-2004] ')
['banner', 'diary', 'freeboard', 'letter', 'link', 'profile', 'poem'].each do |category|
	channel &lt;&lt; wp_category = XML::Node.new('wp:category')
	wp_category &lt;&lt; wp_category_nickname = XML::Node.new('wp:category_nickname') &lt;&lt; category
	wp_category &lt;&lt; wp_category_parent = XML::Node.new('wp:category_parent') &lt;&lt; 'Season 1'
	wp_category &lt;&lt; wp_cat_name = XML::Node.new('wp:cat_name') &lt;&lt; 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 &lt;&lt; item = XML::Node.new('item')
		item &lt;&lt; title = XML::Node.new('title') &lt;&lt; row["subject"]
		item &lt;&lt; link = XML::Node.new('link')
		item &lt;&lt; pubDate = XML::Node.new('pubDate') &lt;&lt; Time.at(row["reg_date"].to_i).strftime("%a, %d %b %Y %H:%M:%S +0000")
		item &lt;&lt; dc_creator = XML::Node.new('dc:creator') &lt;&lt; XML::Node.new_cdata(row["name"])
		item &lt;&lt; category = XML::Node.new('category') &lt;&lt; XML::Node.new_cdata(category_name.capitalize)
		item &lt;&lt; category = XML::Node.new('category') &lt;&lt; XML::Node.new_cdata(category_name.capitalize)
		category['domain'] = 'category'
		category['nicename'] = category_name
		item &lt;&lt; guid = XML::Node.new('guid')
		guid['isPermaLink'] = 'false'
		item &lt;&lt; description = XML::Node.new('description')
		item &lt;&lt; content_encoded = XML::Node.new('content:encoded') &lt;&lt; XML::Node.new_cdata(row["memo"])
		item &lt;&lt; content_excerpt = XML::Node.new('content:excerpt') &lt;&lt; XML::Node.new_cdata('')
		item &lt;&lt; wp_post_id = XML::Node.new('wp:post_id') &lt;&lt; row["no"]
		item &lt;&lt; wp_post_date = XML::Node.new('wp:post_date') &lt;&lt; Time.at(row["reg_date"].to_i).strftime("%Y-%m-%d %H:%M:%S")
		item &lt;&lt; wp_post_date_gmt = XML::Node.new('wp:post_date_gmt') &lt;&lt; (Time.at(row["reg_date"].to_i) - (60 * 60 * 9)).strftime("%Y-%m-%d %H:%M:%S")
		item &lt;&lt; wp_comment_status = XML::Node.new('wp:comment_status') &lt;&lt; 'open'
		item &lt;&lt; wp_ping_status = XML::Node.new('wp:ping_status') &lt;&lt; 'open'
		item &lt;&lt; wp_post_name = XML::Node.new('wp:post_name')
		item &lt;&lt; wp_status = XML::Node.new('wp:status') &lt;&lt; 'publish'
		item &lt;&lt; wp_post_parent = XML::Node.new('wp:post_parent') &lt;&lt; '0'
		item &lt;&lt; wp_menu_order = XML::Node.new('wp:menu_order') &lt;&lt; '0'
		item &lt;&lt; wp_post_type = XML::Node.new('wp:post_type') &lt;&lt; 'post'
		item &lt;&lt; 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 &lt;&lt; wp_comment = XML::Node.new('wp:comment')
			wp_comment &lt;&lt; wp_comment_id = XML::Node.new('wp:comment_id') &lt;&lt; row_comment["no"]
			wp_comment &lt;&lt; wp_comment_author = XML::Node.new('wp:comment_author') &lt;&lt; XML::Node.new_cdata(row_comment["name"])
			wp_comment &lt;&lt; wp_comment_author_email = XML::Node.new('wp:comment_author_email')
			wp_comment &lt;&lt; wp_comment_url = XML::Node.new('wp:comment_url')
			wp_comment &lt;&lt; wp_comment_IP = XML::Node.new('wp:comment_IP') &lt;&lt; row_comment["ip"]
			wp_comment &lt;&lt; wp_comment_date = XML::Node.new('wp:comment_date') &lt;&lt; Time.at(row_comment["reg_date"].to_i).strftime("%Y-%m-%d %H:%M:%S")
			wp_comment &lt;&lt; wp_comment_date_gmt = XML::Node.new('wp:comment_date_gmt') &lt;&lt; (Time.at(row_comment["reg_date"].to_i) - (60 * 60 * 9)).strftime("%Y-%m-%d %H:%M:%S")
			wp_comment &lt;&lt; wp_comment_content = XML::Node.new('wp:comment_content') &lt;&lt; XML::Node.new_cdata(row_comment["memo"])
			wp_comment &lt;&lt; wp_comment_approved = XML::Node.new('wp:comment_approved') &lt;&lt; '1'
			wp_comment &lt;&lt; wp_comment_type = XML::Node.new('wp:comment_type')
			wp_comment &lt;&lt; wp_comment_parent = XML::Node.new('wp:comment_parent') &lt;&lt; '0'
			wp_comment &lt;&lt; wp_comment_user_id = XML::Node.new('wp:comment_user_id') &lt;&lt; '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)

플래시를 지원하지 않는 브라우저입니다.

관련 글 (링크에 마우스를 올리면 요약 내용이 보입니다.)

  1. 워드프레스 호스팅 업체 옮기기 (연관성: 5.75821점)
  2. 오~ (연관성: 4.65088점)
  3. 다우버(Dauver) – 아고라에 썼던 글 (1) (연관성: 4.27948점)
  4. Delicious+Chrome: 자바스크립트 즐겨찾기 버튼 (연관성: 4.26108점)
  5. 저작권법 개정: 한국 블로거 분들 그냥 워드프레스로 망명하세요 (연관성: 4.1275점)

Posted in 컴퓨터. Tagged with , , , , .

0 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

Some HTML is OK

(never shared)

or, reply to this post via trackback.


Leave a verified comment using