A Day in the Life

はてな関連の HTML で書かれた記事を markdown にする

このサイトに持ってきたはてな関係の記事、すなわち古くははてなダイアリーから、はてなグループ、そしてはてなブログの記事は MovableType 形式で export したものを使っていて、本文は HTML で出力されている。

いつかは markdown にしたいなぁ、と思っていたのだけど、とりわけ困ってるわけでもなかったのでなかなか重い腰が上がらなかった。のだけど、サイトの構成をもうちょっといろいろしたくなってきて、そのためにも markdown のほうが融通がきくので変換した。

MovableType 形式のファイルの読み込みには mt-parser を使って簡単にできるのだけど、はてなの HTML から markdown が、古の時代(ダイアリーやグループ)の HTML が含まれるため、少し時間がかかってしまった。

具体的にはturndownを使って markdown に変換した。turndown はよく出来ていて、無視したいノード、お好きに変換したいノード、何もしないノード、などを自分の好きな感じにできるし、また TypeScript の型ファイルがあったのでそれも相成って書きやすかった。

ちょっとハマったのが、TurndownService.keep() は一つしか関数を登録できず、keep を複数回書いてしまって関数が上書きされてしまい意図した動作にならず、解決まで時間を食ってしまった。

実際はこんな感じのコードで、意図した感じの markdown に変換ができた。これで統一フォーマットでアレコレできるので、進捗を上げたい。

import TurndownService from "turndown"

const turndownService = new TurndownService({
  bulletListMarker: "-",
  headingStyle: "atx",
  hr: "---",
})

// はてなのキーワードリンクの削除
turndownService.addRule("remove hatena keywords", {
  filter: (node, options) => {
    return (
      node.nodeName === "A" &&
      (node.getAttribute("href")?.includes("/keyword/") ||
        node.getAttribute("href")?.includes(":keyword:"))
    )
  },
  replacement: (content, node, options) => {
    return content
  },
})

turndownService.addRule("pre to ```", {
  filter: (node, options) => {
    return node.nodeName === "PRE"
  },
  replacement: (content, node, options) => {
    return "\n```\n" + node.textContent.trim() + "\n```\n"
  },
})

// HTML のまま残す要素
turndownService.keep((node, options) => {
  if (
    node.nodeName === "IFRAME" &&
    !node.getAttribute("src")?.includes("facebook.com/")
  ) {
    // facebook の iframe 以外は残す
    return true
  }
  // 特定の div の class があるものは残す
  return node.nodeName === "DIV" && node.className?.includes("photos-hatena")
})
記事の一覧 >

関連するかもエントリー

このウェブサイトの実装 2020年版
r7kamura さんや kzys さん に倣って、このウェブサイトの実装を紹介してみる。ホスティングGoogle Firebase Hosting を使って静的ファイルを配信してる。一部動的な実装に関しては、Cloud Functions for Firebase を使っている...
r7kamura さんや kzys さん に倣って、このウェブサイトの実装を紹介してみる。ホスティングGoogle Firebase Hos...
はてなグループ終了に寄せて
はてなグループ終了に寄せて「セカンドライフ、はてなグループのディレクターやらへん?」2006年、鉢山オフィスの会議室に呼び出された。当時のはてなでは、基本パブリックスペースでの会議が主だったので、何なんだろうと思った。会議室では、jkondo 他にも誰か同席していたと思うが、近藤...
はてなグループ終了に寄せて「セカンドライフ、はてなグループのディレクターやらへん?」2006年、鉢山オフィスの会議室に呼び出された。当時のは...