通过Ruby完成从MoveableType到.Text的迁移

 
介绍
今年,为了保持对新技术的追踪,我开始学习Ruby和Rails。我的第一个重要的Ruby项目是去写一个脚本用于将MoveableType的日志文件导入到.Text中去。这篇文章介绍了详细的经过以及结果。
旧日志信息
将旧的日志导出的最好方式就是使用XML格式,日志内容已经有一部分可以通过RSS获取了,所以我只是需要通过MovableType模板将我所有的日志都导出到RSS中,而不仅仅知识最新发布的一些Blog。我使用RSS2.0来完成这件事情,最终的XML格式如下:
<rss>
   <channel>
      ...irrelevant stuff describing the blog...
   </channel>
   <item>
      <title>Welcome!</title>
      <link>http://dotnetguy.techieswithcats.com/archives/001082.shtml</link>
      <description><![CDATA[...post content...]]></description>
      <category>This Blog</category>
      <pubDate>2001-11-30T10:29:00-08:00</pubDate>
   </item>
   ...many more items...
</rss>
比较麻烦的是这些日志需要有很多的冗余和错误信息需要进行清理,例如:
l        有一些指向已存在Blog的链接
l        有一些指向先前站点的失效链接
l        HTML 内容需要进行清理,有过多的冗余内容
新的Blog
新的日志运行在.Text系统之上,主要的远程管理API是metaWebLog。最困难的部分在于需要通过阅读协议和猜测来弄清楚究竟怎样发布Blog到.Text上。最终,我封装了如下的一个模块。
require 'xmlrpc/client'
 
module MetaWebLogAPI
 class Client
    def initialize(server, urlPath, blogid, username, password)
      @client = XMLRPC::Client.new(server, urlPath)
      @blogid = blogid
      @username = username
      @password = password
    end
  
    def newPost(content, publish)
      @client.call('metaWeblog.newPost', @blogid, @username,
          @password, content, publish)
    end
 
    def getPost(postid)
      @client.call('metaWeblog.getPost', postid, @username,
          @password)
    end
 
    def editPost(postid, content, publish)
      @client.call('metaWeblog.editPost', postid, @username,
          @password, content, publish)
    end
 end
end
你可以看到通过XML-RPC库,编写这样一个封装变得非常的简单。XML-RPC系统将结构表示为Hash对象,所以完成一个Post也变得非常的简单了。
client = MetaWebLogAPI::Client.new('1.2.3.4', '/path/to/weblogapi',
    'blogid', 'username', 'password')
 
blogpost = {
 'title' => 'New post!',
 'description' => 'This is the body of my new post',
 'pubDate' => Time.gm(2005,05,31,15,0,0,0) # May 31, 2005 at 3:00 PM
}
 
client.newPost(blogpost, true)
下面的例子说明了我们发布一篇Blog到.Text上面是多么的容易,代码如下:
require 'metaweblogapi'
 
class MetaWebLogImport
 def initialize
    # MetaWebLogAPI configuration items
    metaBlogServer = 'www.agileprogrammer.com'
    metaBlogApi = '/dotnetguy/services/metablogapi.aspx'
    metaBlogId = 'dotnetguy'
    metaBlogUser = 'myuser'
    metaBlogPassword = 'mypassword'
 
    @metaBlogClient = MetaWebLogAPI::Client.new(metaBlogServer,
        metaBlogApi, metaBlogId, metaBlogUser, metaBlogPassword)
 end
 
 def run
 end
end
 
MetaWebLogImport.new.run
剩下来的事情很明显:完成run函数
分析
RSS文件中的已有内容需要分析才能够使用。很幸运的,有现成的RSS库可以使用,能够让我们轻松的做到读取RSS文件。
require 'rss/2.0'
 
class MetaWebLogImport
 def read_original_rss
    File.open('export.xml') do |file|
      @originalRss = RSS::Parser.parse(file.read, false)
    end
 end
 
 def run
    read_original_rss
 end
end
The RSS::Parser class returns an object that has attributes for all the members of an RSS feed (notably, we'll take advantage of link, title, description, category, and pubDate). 
RSS::Parser 类返回一个对象包含了RSS feed的所有属性(特别地,我们将要使用到link,title,description,category和pubDate)
发布
现在所有的旧日志已经可以访问。不过,我们知道有一些链接是指向他自身的,然而我们没有办法知道新的链接地址是什么。旧日志中最有效的是现有链接地址,我们可以使用这个信息来完成一些工作。
 
我们需要使用一些hashtables来记录这些信息。第一个是@newPostIdsByOldLink,用于记录Blog中所有的post IDs。第二个是@linksRedirect,用于记录新旧两种链接的对应关系。我们将所有的posts都放到hash表中去,然后我们就可以遍历旧日志内容并修复所有的链接。这样做的好处就是你可以将大部分需要手工修改的链接全部修复(例如图片和二进制文件等),另外还可以将所有无法自动修复的打印出来以便后续处理。
 
下面就是一些用于完成这项工作的代码:
class MetaWebLogImport
 def make_post_content(item, description = "[place-holder content]")
    return {
      "title" => item.title,
      "description" => description,
      "dateCreated" => item.pubDate,
      "categories" => [ item.category.content ]
    }
 end
 
 def post_id_to_permalink(postId)
    @metaBlogClient.getPost(postId).link
 end
 
 def post_items
    @originalRss.items.each do |item|
      newPostId = @metaBlogClient.newPost(make_post_content(item), false)
      @newPostIdsByOldLink[item.link] = newPostId
      @linksRedirects[item.link] = post_id_to_permalink(newPostId)
    end
 end
 
 def run
    read_original_rss
    post_items
 end
end
清除与重新Post
我在前面已经提到过了Blog的内容需要进行清理。在我写这个Importer的时候,我并不清除Ruby里面有一个Tidy库。我在老的实现里面使用了shell来调用Tidy工具来实现清理的工作。Tidy用于将我们的HTML文件转变成为XHTML,这样我就可以很轻松的通过REXML来分析了。
 
这里面使用了不少的技术,在代码后面我会一一进行解释的。
require 'xml/document'
 
class MetaWebLogImport
 def tidy_to_xhtml(input)
    open("|tidy -q -b -c -asxml -f /dev/null", "w+") do |cmd|
      cmd.puts input
      cmd.close_write
      cmd.read
    end
 end
 
 def cleansed_content(element)
    content = "";
    element.elements.each { |e| content += e.to_s }
    return content
 end
 
 def replace_links(xml, xpath, attributeName, oldLink)
    xml.elements.each(xpath) do |e|
      href = e.attributes[attributeName]
      newlink = @linksRedirects[href]
 
      if newlink
        e.attributes[attributeName] = newlink
      else
        if @oldSiteUrlRegex.match(href) then
          (@unmatchedLinks[href] ||= []) << oldLink
        end
      end
    end
 end
 
 def scan_content_for_links
    @originalRss.items.each do |item|
      xml = REXML::Document.new(tidy_to_xtml(item.description))
      replace_links(xml, '//a', 'href', item.link)
      replace_links(xml, '//img', 'src', item.link)
      post = make_post_content(item, cleansed_content(xml.elements["//body"]))
      @metaBlogClient.editPost(@newPostIdsByOldLink[item.link], post, true)
    end
 end
 
 def run
    read_original_rss
    post_items
    scan_content_for_links
 end
end
在scan_content_for_links之后,你可以看到我们仅仅做了很少的一些事情。对于每个Post,我们通过运行Tidy将他们转换成为了XML。然后我们通过XPath查询来查找所有的需要清理的链接信息。然后我们将清理之后的内容重新发布到服务器上面去。
函数tidy_to_xhtml通过shell运行tidy,然后将所有的标准输出的内容捕获,Ruby让这样的事情变得非常的简单。
cleansed_content 函数用于将<body> Tag中的信息提取出来。看起来好像这个函数存在一些问题,不过由于我也是刚刚结束Ruby,所以并不清楚是否存在更加简洁的方式来做这件事情。
最后,replace_links,由于这个函数看起来非常的长,给人一可能做了很多事情的感觉,但是我不打算重构它。这个函数查找XML里面的所有link,使用@linksRedirect去将旧的链接替换成为新的。此外,我跳过了所有的本地链接,因为他们不需要替换。
Apache Redirects
我的旧站点运行在Unix服务器上面,通过Apache提供服务。所以我将所有的重定向信息记录到文件中。
 
class MetaWebLogImport
 def write_htaccess
    open('.htaccess', 'w+') do |file|
      @linksRedirects.each do |key, value|
        match = @oldBlogUrlRegex.match(key)
        file.puts 'RedirectPermanent ' + match.post_match + ' ' + value if match
      end
    end
 end
 
 def run
    read_original_rss
    post_items
    scan_content_for_links
    write_htaccess
 end
end
看看有没有什么遗漏
第一次运行这个脚本的时候,你可以发现还是有不少的链接是断开着的。一个Blog经常会包含一些上传的图片以及一些二进制的文件。如果可以将这些断开的链接记录下来,对于我们的维护是有着很多的好处的。还记得我们有一个叫做@unmatchLinks的哈希表吗?就是用来做这件事情的。
class MetaWebLogImport
 def write_unresolved_links_list
    open('unresolved_links.txt', 'w+') do |file|
    @unmatchedLinks.each do |link, references|
      file.puts link + ":"
      references.each { |ref| file.puts "    " + ref }
      file.puts
    end
 end
 
 def run
    read_original_rss
    post_items
    scan_content_for_links
    write_htaccess
    write_unresolved_links_list
 end
end
结束
作为一个Ruby新手,完成这个工作我花了不少功夫来学习,当初为了完成这个程序我用了8个小时的时间(在今天,我可以在2个小时之内重写它)。还有不少的额外时间用于学习XML-RPC是如何工作的,以及如何向.Text添加内容。
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值