2020/05/19

Movable TypeからBloggerへの移行

ふと思い立ち、Movable TypeからBloggerにデータを移行させてみた。

Google Codeにあったツールを使えば簡単に移行できるが、ハマったところがあったので、コードを少し改変した。
google-blog-converters-appengine
https://code.google.com/archive/p/google-blog-converters-appengine/
オリジナルのコードは、2010年5月28日にリリースされたのを最後にちょうど10年(正確には9日足りないが)間更新されていないが、うまく動作する。

Movable TypeのWeb画面に入り、[記事]-[エクスポート]から「子サイトの記事をエクスポート」画面に入り、「子サイトをエクスポート」ボタンを押下し、記事をダウンロードする。
サイトの作りによっては、[記事]-[エクスポート]にあるのは[サイトの記事をエクスポート]画面で、「サイトをエクスポート」ボタンになっているかもしれない。


その後は、google-blog-convertersを呼び出せば、Bloggerにインポートできるデータが出来上がる。(Pythonが必要)

$ python --version
Python 2.7.16
$ tar xzvf google-blog-converters-r89.tar.gz
$ google-blog-converters-r89/bin/movabletype2blogger.sh <MT_file> > converted.xml

しかし、以下のような問題がある。
MovableTypeからダウンロードしたファイルの特徴のため、記事の中に半角ハイフン「-」がちょうど5個もしくは8個だけの行があると、それ以降の行が無視されてしまう。(次の記事が始まると、その新しい記事から読み込みは再開される。)
  • -が8個はエントリの終わりを表す
  • -が5個はエントリのタグ(本文(BODY)、続き(EXTENDED BODY)、概要(EXCERPT)、キーワード(KEYWORDS))の始まり、または終わりを表す。

記事の「本文」「続き」を分けるタグが設定されていないため、Bloggerに移行後は、全てが本文になってしまう。

MovableTypeにあってBloggerにない概要(EXCERPT)とトラックバック(PING)の情報は、Bloggerに移行することでなくなってしまう。

そこで、これらを解決できるようにコードを変更した。
「-」がちょうど5個もしくは8個だけの行があっても、それが境目なのか、記事の一部なのかの判断をするロジックを追加(完璧ではないが、多少はマシになる)。追加した場合は、その旨の警告を表示
「本文」「続き」を分けるタグを挿入させる
概要、トラックバックがあると、Bloggerには移行されない旨を表示
その他、処理できない行があるとエラーを表示

差分(パッチ)は後述のとおりで、パッチの適用は以下のようにするとできる。
$ patch -i patch.diff -p0
patching file google-blog-converters-r89_old/src/movabletype2blogger/mt2b.py

実行はオリジナルと変わらないが、標準エラー出力に警告・エラーを表示させるので、行がたくさん出る場合はファイルにリダイレクトすると見やすい。
$ google-blog-converters-r89/bin/movabletype2blogger.sh <MT_file> > converted.xml 2> error.log 

また、出力されるファイルは、全エントリが1行になっているため、内容を確認しようとすると見にくい。
1行に1エントリにするためには、以下を実行する。
$ sed -e 's/\(\)/\n\1/g' converted.xml > converted2.xml

Bloggerへのインポートは、[設定]-[その他]から「インポートとバックアップ」のセクションで「コンテンツをインポート」すればよい。
1日になんどもインポートすると、
Entry 2, id='post-519', Failed to process entry. Insufficient storage quota
というエラーや
インポート数が上限を超えました。しばらくしてからもう一度お試しください。
というエラーがでてインポートができない事がある。
この場合は、24時間待ってから試す必要がある。


コード改変の差分(パッチ)
diff --text -Naur google-blog-converters-r89_old/src/movabletype2blogger/mt2b.py google-blog-converters-r89_new/src/movabletype2blogger/mt2b.py
--- google-blog-converters-r89_old/src/movabletype2blogger/mt2b.py 2010-05-28 12:21:06.000000000 +0900
+++ google-blog-converters-r89_new/src/movabletype2blogger/mt2b.py 2020-05-19 21:41:42.883506200 +0900
@@ -114,15 +114,30 @@
     last_entry = None    # The previous post atom.Entry if exists
     tag_name = None      # The current name of multi-line values
     tag_contents = ''    # The contents of multi-line values
+    linenum = 0          # Number of line being processed
+    tag_separator = ''   # keep the line with '-' only because it might be 
+                         # treated as a tag separator ('-' * 8 or '-' * 5) wrongly
+    tag_name_back = tag_name  # In case of wrong treatment of tag separator, copy tag_name
+    #_EXTENDED_BODY_SEPARATOR_ = '
'  
+                         # blogger uses "" as the separator and it is conveted to  
+                         # the above when exported. But it will be imported as 
+                         # "", instead
+                         # of original separator - "". 
+    _EXTENDED_BODY_SEPARATOR_ = ''
+    extended_body_separator = _EXTENDED_BODY_SEPARATOR_ 
+                         # Also used as a flag indicating separator of extended body from 
+                         # main body is added into output or not. 
 
     # Loop through the text lines looking for key/value pairs
     for line in infile:
+      linenum+=1
 
       # Remove whitespace
       line = line.strip().lstrip(codecs.BOM_UTF8)
 
       # Check for the post ending token
       if line == '-' * 8:
+        tag_separator = line
         if post_entry:
           # If the body tag is still being read, add what has been read.
           if tag_name == 'BODY':
@@ -137,11 +152,12 @@
         post_entry = None
         comment_entry = None
         tag_name = None
-        tag_contents = ''
+        tag_contents = '\n'
         continue
 
       # Check for the tag ending separator
       elif line == '-' * 5:
+        tag_separator = line
         # Get the contents of the body and set the entry contents
         if tag_name == 'BODY':
           post_entry.content = atom.Content(
@@ -162,9 +178,10 @@
         # entry contents
         elif tag_name == 'EXTENDED BODY':
           if post_entry:
-            post_entry.content.text += '
' + self._TranslateContents(tag_contents)
+            post_entry.content.text += extended_body_separator + self._TranslateContents(tag_contents)
           elif last_entry and last_entry.content:
-            last_entry.content.text += '
' + self._TranslateContents(tag_contents)
+            last_entry.content.text += extended_body_separator + self._TranslateContents(tag_contents)
+          extended_body_separator = '
'
 
         # Convert any keywords (comma separated values) into Blogger labels
         elif tag_name == 'KEYWORDS':
@@ -175,6 +192,7 @@
                   atom.Category(scheme=CATEGORY_NS, term=keyword))
 
         # Reset the current tag and its contents
+        tag_name_back = tag_name
         tag_name = None
         tag_contents = ''
         continue
@@ -261,20 +279,50 @@
       # on following lines
       elif key in ('COMMENT', 'BODY', 'EXTENDED BODY', 'EXCERPT', 'KEYWORDS', 'PING'):
         tag_name = key
+        tag_separator = ''
+        extended_body_separator = _EXTENDED_BODY_SEPARATOR_ 
 
       # These lines can be safely ignored
       elif key in ('BASENAME', 'ALLOW COMMENTS', 'CONVERT BREAKS',
                    'ALLOW PINGS', 'PRIMARY CATEGORY', 'IP', 'URL', 'EMAIL'):
+        tag_separator = ''
+        extended_body_separator = _EXTENDED_BODY_SEPARATOR_ 
         continue
 
-      # If the line is empty and we're processing the body, add an HTML line
-      # break
-      elif tag_name == 'BODY' and len(line) == 0:
-        tag_contents += '
'
+      # If we're processing the body, extended body, or comment, add the line 
+      # because it would be a part of concent. 
+      # Note: Excerpt would have the same situation, but blogger does not have excerpt field and this program will ignore it. 
+      elif tag_name in ('BODY', 'EXTENDED BODY', 'COMMENT', 'KEYWORDS'):
+        #sys.stderr.write('Normal: ' + str(linenum) + ': assumed the line is part of "' + tag_name+ '" content: "' + line + '"\n')
+        if tag_name in ('BODY', 'EXTENDED BODY', 'COMMENT'):
+          tag_contents += line + '
'
+        else:
+          if tag_contents != "":
+            tag_contents += ","
+          tag_contents += line
+
+      # EXCERPT and PING (=Trackback) are ignored because they are not supported by Blogger. 
+      elif tag_name in ('EXCERPT', 'PING') and len(line)!=0:
+        sys.stderr.write('Warn: ' + str(linenum) + ': "' +tag_name+ '" is ignored by blogger: "' + line + '"\n')
 
       # This would be a line of content beyond a key/value pair
-      elif len(key) != 0:
-        tag_contents += line + '\n'
+      # Add '-----' or '--------' because they located at 1 line above this line would be 
+      # treated as a tag seprator and omitted unexpectedly. 
+      elif len(key) != 0: #and not ( tag_name in ('EXCERPT', 'PING') ):
+        if (tag_separator != ''): 
+          if ( not tag_name ): 
+            tag_name = tag_name_back
+            sys.stderr.write('Warn: ' + str(linenum) + ': need recover tag_name to "' + tag_name +'": "' + line + '"\n')
+          tag_contents += tag_separator + '
'
+          sys.stderr.write('Warn: ' + str(linenum) + ': "' + tag_separator + '" was added back to previous line: "' + line + '"\n')
+          tag_separator = ''
+        sys.stderr.write('Warn: ' + str(linenum) + ': assumed the line is part of "' + tag_name+ '" content: "' + line + '"\n')
+        tag_contents += line + '
'
+
+      # Lines unable to process
+      elif line != '': 
+      #else: 
+        sys.stderr.write('Error: ' + str(linenum) + ': cannot process line: "' + line + '"\n')
 
 
     # Update the feed with the last updated time

0 件のコメント: