edo1z blog

プログラミングなどに関するブログです

Wordpressからはてなブログへの引っ越しの際に、日本語URLがおかしいのと、シンタックスハイライトが反映されないのを解決した

Wordpressからはてなブログに引っ越しました

この度、はてなブログにお引っ越しをしました。めっちゃ便利だし、安いし、軽いしいい感じだと思いました。 引っ越し方法も基本は簡単で、Wordpressの管理画面の標準機能に、エクスポート機能があるので、それを使って、XMLファイルをエクスポートします。そして、はてなブログにも、そのXMLファイルをインポートする機能があるので、それを使うと一瞬で完了しました。さらに記事中の画像も勝手にインポートしてくれます。

2点だけ問題がありました

でも、下記2点だけ、問題として残りました。

  • 日本語URLが、なんかおかしい(wordpressのときのURLと微妙に異なる)
  • ソースコードのシンタックスハイライトが反映されなくなった

日本語URLが、なんかおかしい

URL内の日本語は、URLエンコードされますが、そのURLエンコードされている部分の、%という文字が、%25という3文字に変換されていました。それ以外の部分はwordpressのURLと同じでした。なんでだろうなあと思いつつも、はてなブログのAPIがあることを知りまして、API経由で、全記事のURL修正コードを作成しました。コードは下記にあります。

github.com

全記事の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タグがなかったり、タグが&gtになってたりなってなかったりみたいな感じでカオスでした。

そこで、これもAPIで全記事修正することにしました。さすがに全てのソースコード表示箇所は、preタグでは囲まれていたので、記事中のpreを探して、内部の&gtを>に直して、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 = /&gt;/g
  const reLt = /&lt;/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
    }
  }
}