前回の続き
前回はMarkdownの内容をASTに変換してAST内のコンテンツを追加編集するものだったが、今回はMarkdown内のFrontmatterに対して追加や修正を行う
ケースとしてはぼちぼちあると思うので残しておく
frontmatterの判定
micromark-extension-frontmatter
,mdast-util-frontmatter
を読み込んでfrontmatterをパースできるようにする
ほぼREADME通り
自分のケースではtoml
は不要だったので除いた
この拡張を含めてパースするとNodeタイプがtype: yaml
で判断されるようになる
あと書き戻した場合にもしっかりfrontmatterの体をなした状態で書き出せる
この状態でNodeがfrontmatterか?というのは判断できるようになったが、value
はただの文字列のまま
Markdown内での記述とfrontmatterNodeの中身
- Markdown内での記述
hoge: 1 fuga: 2
- frontmatter Nodeの中身
{ type: 'yaml', value: 'hoge: 1\nfuga: 2', position: { start: { line: 1, column: 1, offset: 0 }, end: { line: 4, column: 4, offset: 23 } } },
形式はyamlになっているので今度はjs-yaml
で文字列をパースしてデータの中身をいじれるようにする
frontmatterNodeの読み込みと値の書き換え
- 実行前
--- hoge: 1 fuga: 2 ---
次のようなfrontmatterのMarkdownファイルに対して何かしら追加してみる
一部抜粋
import yaml from 'js-yaml'; const frontmatterIndex = ast.children.findIndex(node => node.type === "yaml"); const frontmatterNode = ast.children[frontmatterIndex]; const metadata = yaml.load(frontmatterNode.value); const updatedMetadata = {...metadata, ...{added: 'AddedValue!!'}}; const frontmatterAst = {...frontMatterNode, ...{value: yaml.dump(newFrontmatter)}}; const children = [ ...ast.children.slice(0,frontmatterIndex), frontmatterAst, ...ast.children.slice(frontmatterIndex + 1) ]; const afterAst = { ...ast, ...{children}};
frontmatterのNodeを特定し、valueの中身をyaml.load
でパースして中身を編集してyaml.dump
で文字列に戻してあげる
このサンプルコードはfrontmatterが既存のMarkdownに存在する前提
- 実行後
--- hoge: 1 fuga: 2 added: AddedValue!! ---
しっかり追加される
編集は既存にあるキーを指定してデータをセットしてあげるだけ
フォーマットのコントロール
前提として、Markdownの修正というからには既存のフォーマットにできるだけ合わせたい
自分の使っている書き方だとデフォルトの挙動からいくつか修正が必要だったので調べた
nodeca/js-yaml: JavaScript YAML parser and dumper. Very fast.
js-yamlのREADMEに色々と使い方やオプションの説明が載っているので基本的にはそこ読みながらで進められる
frontmatter修正の流れ
おさらいとしてざっくり処理の流れ
- mdastでMarkdownファイルのASTを生成
- AST内のfrontmatterのNodeの値をjs-yamlでパースしオブジェクトに変換
- オブジェクトの中身を修正もしくは追加
- 修正したオブジェクトをyaml.dumpし、値をfrontmatterのNodeの値(node.value)に設定
- 修正したNodeをASTに含める
- mdastでMarkdownファイルへ書き出し
以降は主に2,4番目の話
日付にクオートをつけない
デフォルトの挙動で素直に書き戻すと次のような差分が出てしまう
- date: 2023-08-01 + date: '2023-08-01'
optionのschema
でいくつか指定できるがJSON_SCHEMA
を指定することで回避した
const metadata = yaml.load(frontmatterNode.value, { schema: yaml.JSON_SCHEMA });
これはYAMLフォーマットから変換するときに特定のフォーマットの文字列は内部的にdate
や他のデータ型で扱うというルールがあるよう
今回はdate
のフォーマットだった、なので出力時にクオートをつけるという仕様になっているみたい
JSON_SCHEMAはJSONのstringify,parseなどと同じ仕様で読み書きするというschemaのよう
日付のフォーマットだったとしても文字列としてそのまま読み書きするためクオートがつかなくなる
変換時の規則を指定するためのものなので今回のようにパースして書き戻すような場合は読み(load
)書き(dump
)両方で指定する必要がある
試しにload時指定しなかった場合、次のようなエラーになってしまった
file:///home/user/gh/self/markdown-importer/node_modules/js-yaml/dist/js-yaml.mjs:3689 throw new exception('unacceptable kind of an object to dump ' + type); ^ YAMLException: unacceptable kind of an object to dump [object Date] at writeNode (file:///home/user/gh/self/markdown-importer/node_modules/js-yaml/dist/js-yaml.mjs:3689:13) at writeBlockMapping (file:///home/user/gh/self/markdown-importer/node_modules/js-yaml/dist/js-yaml.mjs:3552:10) at writeNode (file:///home/user/gh/self/markdown-importer/node_modules/js-yaml/dist/js-yaml.mjs:3655:9) at Object.dump$1 [as dump] (file:///home/user/gh/self/markdown-importer/node_modules/js-yaml/dist/js-yaml.mjs:3781:7) at file:///home/user/gh/self/markdown-importer/sample_yaml.js:43:38 at Array.map (<anonymous>) at file:///home/user/gh/self/markdown-importer/sample_yaml.js:33:31 at ModuleJob.run (node:internal/modules/esm/module_job:194:25) { reason: 'unacceptable kind of an object to dump [object Date]', mark: undefined }
load
時はDateオブジェクトだねって解釈したがdump
時はそのまま出力っていわれて「えええっ!!!」っていわれている
よく考えたら逆のパターンはもしかしたらエラーにならないかもしれない、試してないけど
空文字にクオートをつけない
Markdownファイル上では特に何も記入しなかった場合
デフォルトの挙動で素直に書き戻すと次のような差分が出てしまう
- hoge: + hoge: ''
こちらはdump
時のstyles
オプションで対応する
nodeca/js-yaml: JavaScript YAML parser and dumper. Very fast.
書き出し(dump
)時にstyles
オプションで特定のケースに対してどのような処理を行うかを選択する
- frontmatterのNodeAST生成時の処理(抜粋)
const frontmatterAst = { type: "yaml", value: yaml.dump(updatedMetadata, { schema: yaml.JSON_SCHEMA, styles: { '!!null': 'empty' }, }), }
現状の設定だと空文字は内部的にnull
と判断されているためnull
の場合はempty
で出力するという指定をする
empty
はクオートつけないというスタイルのよう
- 結果
hoge:
となり、勝手にクオートがつかなくなった
READMEにnullの他のパターン、null以外のint,bool,floatのパターンも載っている
nodeca/js-yaml: JavaScript YAML parser and dumper. Very fast.
まとめ
これで今回のユースケースでは満たせそう
- js-yamlでfrontmatterの中身をパースしてキーの追加や上書きをできるようにした
- フォーマットをコントロールしたい場合はjs-yamlのオプションでコントロールする
- stylesで指定
- schemaで指定
ちょうど下記でMarkdown内のfrontmatter部分だけ修正したいケースが発生し、スクリプトを書いたので参考程度にはなるはず
markdown-importer/modify_daily_note_frontmatter.js at main · swfz/markdown-importer