Wordpressからはてなブログに引っ越しました
この度、はてなブログにお引っ越しをしました。めっちゃ便利だし、安いし、軽いしいい感じだと思いました。 引っ越し方法も基本は簡単で、Wordpressの管理画面の標準機能に、エクスポート機能があるので、それを使って、XMLファイルをエクスポートします。そして、はてなブログにも、そのXMLファイルをインポートする機能があるので、それを使うと一瞬で完了しました。さらに記事中の画像も勝手にインポートしてくれます。
2点だけ問題がありました
でも、下記2点だけ、問題として残りました。
- 日本語URLが、なんかおかしい(wordpressのときのURLと微妙に異なる)
- ソースコードのシンタックスハイライトが反映されなくなった
日本語URLが、なんかおかしい
URL内の日本語は、URLエンコードされますが、そのURLエンコードされている部分の、%という文字が、%25という3文字に変換されていました。それ以外の部分はwordpressのURLと同じでした。なんでだろうなあと思いつつも、はてなブログのAPIがあることを知りまして、API経由で、全記事のURL修正コードを作成しました。コードは下記にあります。
全記事のURLは、修正できたのですが、その修正が何時まで経っても反映されませんでした。管理画面上では修正できてることになっているのですが、実際のURLが2日経っても変わりませんでした。
そこで、今度は、WordpressからエクスポートしたXMLファイル内のURLを全部修正することにしました。というのも、URLエンコード済みの文字列をさらにURLエンコードしようとすると、%が%25になる場合があるのが分かったからです。XMLファイルのURLエンコード済みのURLを、全部デコードしました。そのコードも、上記のgithubにおきました。これでやっと、日本語URLも含めて、完全にWordpressと一致しました。
const fs = require('fs') const {Transform} = require('stream') class UrlDecodeStream extends Transform { constructor(options) { super(options) this.str = '' } write (data) { let str = data.toString() const idx = str.indexOf("\n") if(idx > -1) { let line = this.str + str.slice(0, idx) line = this.decodeLink(line) this.str = str.slice(idx) this.emit('data', line) } else { this.str += str } } end () { this.emit('data', this.str) } decodeLink (str) { const re = /(<link>)(.+?)(<\/link>)/gm if(!str.match(re)) return str return str.replace(re, (match, p1, p2, p3) => { return p1 + decodeURI(p2) + p3 }) } } const reader = fs.createReadStream('wp.xml') const writer = fs.createWriteStream('new.xml') const decoder = new UrlDecodeStream() reader.pipe(decoder).pipe(writer)
ソースコードのシンタックスハイライトが反映されなくなった
今まで、blogger、wordpressのプラグインを何度か変更、からのはてなブログにお引っ越しだったので、シンタックスハイライト用のpre, codeタグに関するclassの付与の仕方や、そもそもcodeタグがなかったり、タグが>になってたりなってなかったりみたいな感じでカオスでした。
そこで、これもAPIで全記事修正することにしました。さすがに全てのソースコード表示箇所は、preタグでは囲まれていたので、記事中のpreを探して、内部の>を>に直して、preとcodeタグを削除して、Markdownのコード表示で囲むようにしました。その際に指定するコードの種類は、記事が登録されているカテゴリから決定するようにしました。これも、上記githubにおきました。これで全記事のソースコードでシンタックスハイライトが反映されました。
const {get, post, put, endpoint, baseUrl} = require('./hatena.api') const xml2js = require('xml2js') const parse = xml2js.parseString const builder = new xml2js.Builder() fixCodeLoop() async function fixCodeLoop (nextPage = null) { return new Promise( async (resolve, reject) => { const url = nextPage ? nextPage : endpoint + '/entry' const xml = await get(url).catch(err => reject(err)) parse(xml, async (err, res) => { if (err) reject(err) nextPage = getNextPage(res.feed.link) const blogPosts = res.feed.entry for(let i = 0; i < blogPosts.length; i++) { await updateCode(blogPosts[i]).catch(err => reject(err)) } if (nextPage) return fixCodeLoop(nextPage) resolve('fin') }) }) } const getNextPage = links => { for(let i = 0; i < links.length; i++) { if (links[i].$.rel === 'next') { return links[i].$.href } } return null } const updateCode = async (blogPost) => { return new Promise((resolve, reject) => { let content = blogPost.content[0]._ const categories = blogPost.category if(content) { content = searchAndFixCode(content, categories) blogPost.content[0]._ = content update(blogPost) .then(() => resolve()) .catch(err => reject(err)) } else { resolve() } }) } const getLanguage = (categories) => { const javascript = ['javascript', 'node.js', 'vue.js', 'webpack', 'react', 'cordova'] const php = ['php', 'cakephp', 'fuelphp', 'laravel'] const nginx = ['nginx'] const go = ['go'] const python = ['python', 'tensorflow'] const ruby = ['ruby', 'rails', 'capistrano'] const zsh = ['shell', 'zsh', 'ubuntu', 'linux'] const java = ['java', 'android'] const swift = ['swift', 'ios'] for(let i = 0; i < categories.length; i++) { let category = categories[i].$.term.toLowerCase() if(python.indexOf(category) !== -1) return 'python' if(go.indexOf(category) !== -1) return 'go' if(java.indexOf(category) !== -1) return 'java' if(swift.indexOf(category) !== -1) return 'swift' if(javascript.indexOf(category) !== -1) return 'javascript' if(php.indexOf(category) !== -1) return 'php' if(nginx.indexOf(category) !== -1) return 'nginx' if(ruby.indexOf(category) !== -1) return 'ruby' if(zsh.indexOf(category) !== -1) return 'zsh' return 'javascript' } } const searchAndFixCode = (content, categories) => { const rePre = /(<pre[\s\S]*?>([\s\S]+?)<\/pre>)/gm const res = content.match(rePre); if(!res) return content const lang = getLanguage(categories) const codeStartStr = "\n" + '```' + lang + "\n" const codeEndStr = "\n" + '```' + "\n" const reCode = /(<code[\s\S]*?>([\s\S]+?)<\/code>)/gm const reGt = />/g const reLt = /</g return content.replace(rePre, (match, p1, p2) => { p2 = p2.replace(reCode, '$2') p2 = p2.replace(reGt, '>') p2 = p2.replace(reLt, '<') return codeStartStr + p2 + codeEndStr }) } const update = async (blogPost) => { const editLink = getEditUrl(blogPost.link) return new Promise((resolve, reject) => { let obj = { 'entry': blogPost } obj.entry.$ = { 'xmlns': 'http://www.w3.org/2005/Atom', 'xmlns:app': 'http://www.w3.org/2007/app' } delete(obj.entry.published) delete(obj.entry['app:edited']) delete(obj.entry.summary) delete(obj.entry.id) delete(obj.entry.link) put(editLink, builder.buildObject(obj)) .then(() => resolve()) .catch(err => reject(err)) }) } const getEditUrl = links => { for(let i = 0; i < links.length; i++) { if(links[i].$.rel === 'edit') { return links[i].$.href } } }