最近弄っているOSSのヘルプがWinHelpで編集が厳しいので各ページをMarkdownに変換してみた。
WinHelpの構造もよく分かっていないし、特に汎用的な変換ツールにしたりはしていないのだけれど、こんな考え方で変換できるのでは? という事例になるかもしれないので備忘しておく。
前処理 hlp -> chm 変換
WihHelpは *.hlp
からHTML形式(*.chm
)に変換、加えてHTMLファイルの文字コードはUTF-8
にしておく。
この辺りの手順は日本語対応も含めて丁寧な記事がある。
*.chm
ファイルは使わず、変換したHTMLファイルを使う。ので厳密にはchmではなくてchmプロジェクトへの変換かもしれない。
元のヘルプには画像をクリックすると説明が表示されるクリッカブルマップのような指定があったっぽいのけれど、それはこの変換の段階で失われていた。どのみちマークダウンでは表現できないので忘れることにした。
HTML -> Markdown 変換
HTMLができたなら、今回はこんな感じで変換してみた。
- 各ページのHTMLファイルを
{titleタグ値}.md
にリネーム- タイトルをファイル名にしておけばWikiに移行したいとなった時にもページ名に使えるだろう
- あるファイルの内容が直感的にわかりやすいだろう
- リネーム内容を覚えておいてリンクをリネーム先のものに置き換え
- これは単純にリンクを維持するための手続き
- HTMLをマークダウンに変換
- HTMLをマークダウンに変換するライブラリは各言語で色々あるので適当に変換すればよい
これをC#で使い捨てのコードで行った。ライブラリにはダウンロード数を見てReverseMarkdownを使ってみた。
この変換が汎用的に適用できるのかさっぱり分かっていないのでちゃんとした実行ファイルを作ったりはしていないのだけれど、他の例も眺めてみてもう少し整備してもいいかもしれない。どうせやるなら文字コード変換*1も含めて処理してしまいたい。
SRC/SRC.Sharp/HelpWork/HelpConverter at f0e68e1d92a9e089a7d40953c6813f0169863fad · 7474/SRC · GitHub
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; namespace HelpConverter { class FileConverter { static readonly Regex nameRegex = new Regex("<TITLE>(.+)</TITLE>", RegexOptions.IgnoreCase | RegexOptions.Multiline); static readonly Regex anchorRegex = new Regex("<A HREF=\"(.+?.htm?)\">(.+?)</A>", RegexOptions.IgnoreCase); static readonly Regex tagRegex = new Regex("<[^>]+>", RegexOptions.IgnoreCase); static readonly ReverseMarkdown.Converter converter = new ReverseMarkdown.Converter(new ReverseMarkdown.Config { // Objectタグを落とす UnknownTags = ReverseMarkdown.Config.UnknownTagsOption.Drop, // GitHubかそのWikiターゲットなので GithubFlavored = true, }); public string SourcePath { get; private set; } public string DestPath { get; private set; } public string SourceContent { get; private set; } public string ConvertedContent { get; private set; } public FileConverter(string sourcePath) { SourcePath = sourcePath; SourceContent = File.ReadAllText(SourcePath); } public void ResolvePath() { DestPath = "md/" + ( nameRegex.Match(SourceContent).Groups.Values.Skip(1).FirstOrDefault()?.Value ?? Path.GetFileName(SourcePath) ) + ".md"; } public void ConvertContent(IDictionary<string, string> fileNameMap) { var tmpContent = anchorRegex.Replace(SourceContent, (m) => { var href = m.Groups[1].Value; var text = m.Groups[2].Value; var name = tagRegex.Replace(text, ""); string newHref; if (!fileNameMap.TryGetValue(href.ToLower(), out newHref)) { newHref = name + ".md"; } return $"<a href=\"{newHref}\">{text}</a>"; }); ConvertedContent = converter.Convert(tmpContent).TrimStart(); } } class Program { static void Main(string[] args) { Directory.CreateDirectory("md"); var converters = args .Where(x => Directory.Exists(x)) .SelectMany(x => Directory.EnumerateFiles(x, "*.html", SearchOption.AllDirectories) .Concat(Directory.EnumerateFiles(x, "*.htm", SearchOption.AllDirectories))) .Concat(args.Where(x => File.Exists(x))) .Select(x => new FileConverter(x)) .ToList(); converters.ForEach(x => x.ResolvePath()); var fileNameMap = converters.ToDictionary(x => Path.GetFileName(x.SourcePath).ToLower(), x => Path.GetFileName(x.DestPath)); converters.ForEach(x => { x.ConvertContent(fileNameMap); Console.Out.WriteLine($"{x.SourcePath} -> {x.DestPath}"); File.WriteAllText(x.DestPath, x.ConvertedContent); }); } } }
*1:変換しなくても読み込めればいいだけだが