Keď sa pokúšam vytvoriť blogové príspevky z Markdown dokumentov, zvyčajne potrebujem metadáta niekam umiestniť. Keďže nie je prítomná žiadna databáza, jedným miestom, kam ich možno vložiť, je priamo do dokumentu.

Metadáta sú údaje o údajoch. V kontexte blogových príspevkov sú metadáta zvyčajne kategória, tagy, slug, čas alebo názov. Bohužiaľ, Markdown natívne nepodporuje syntax pre metadáta. Je tiež dôležité poznamenať, že tieto informácie by sa nemali vykresľovať vo výslednom dokumente, ale malo by byť možné ich spracovať počas parsovania.

Markdown komentáre #

Najjednoduchší spôsob, ako to dosiahnuť, je použiť Markdown komentár, napríklad na označenie kategórie takto:

[//]: # "programming"

Tento prístup však nie je ideálny. Keďže existuje viacero spôsobov ako reprezentovať komentár v Markdown dokumente, parser vykonávajúci Markdown parsovanie musí rozumieť všetkým použitým syntax komentárov. To nie je veľký problém, keď blog píše len jedna osoba, ale mohlo by sa to stať problémom v budúcnosti, ak by sa k tímu pridali noví členovia.

Druhý problém s prístupom komentárov je, že metadáta by mali byť ďalej parsované. Ak sa používajú len jednoduché údaje, napríklad len kategória, ako v príklade vyššie, napísanie veľmi naivného parsera by nemalo zabrať viac ako niekoľko riadkov.

V momente, keď chceme vložiť viacero rôznych dátových štruktúr, môže sa oplatit jednoducho delegovať túto úlohu na dedikovaný parser. Zvážte nasledujúci príklad:

[//]: # "programming"
[//]: # "yaml, markdown, metadata"

Stále relatívne jednoduché na vytvorenie parsera.

  1. Získať všetko na prvom riadku za dvojbodkou ako kategóriu
  2. Získať všetko na druhom riadku za dvojbodkou a rozdeliť to podľa čiarky ako tagy

Ale príliš rýchlo začína byť dosť komplikované. Teraz je tretí riadok názov. Musíme pamätať na to, aby sme rozdeľovali podľa čiarky len tagy, nie názov:

[//]: # "programming"
[//]: # "yaml, markdown, metadata"
[//]: # "A simple, yet powerful parser"

Ak by sme chceli, aby poradie metadát nebolo dôležité, mohli by sme to skomplikovať ešte viac:

[//]: # "Title: A simple, yet powerful parser"
[//]: # "Category: programming"
[//]: # "Tags: yaml, markdown, metadata"

Ale teraz musíme zabezpečiť, aby nedochádzalo k preklepy. Title vyzerá podobne ako Title, ale teraz by bol parser zmätený. Mohol by vyhodiť výnimku, ak by všetky metadáta boli povinné a niektoré by chýbali, takže preklepy by sa dali stále pomerne dobre zvládnuť. Ale čo ak by niektoré metadáta boli voliteľné? Alebo čo horšie, čo keby sme chceli vlastné metadáta? Ako by sme označili, či ide o reťazec, ako v prípade názvu, alebo pole, ako v prípade tagov? Čo s bezpečnostnými obavami ohľadom XSS? Čo s testovaním tohto všetkého? A tak ďalej.

YAML Ain’t Markup Language #

V rekurzívnych akronymoch je skrytý vtip. Jedným zo známych, ktorý patrí do tejto kategórie, je GNU, rekurzívny akronym pre GNU’s Not UNIX. Ako vidíte, akronym pre YAML je tiež rekurzívny. YAML nie je značkovací jazyk ako XML alebo HTML, práve preto, že sa považuje za jazyk data-serialization.

Využitie YAML parsera nás zbavuje vyššie spomínaných problémov. Je zdokumentovaný, otestovaný, jeho výhody a nevýhody sú známe a jeho bezpečnostné aspekty sú dostupné na čítanie. Použitie YAML v Markdowne na označenie metadát nie je nový koncept — je známy ako Front Matter. V priestore Markdown blogov ho používa napríklad Jekyll alebo dokonca Hugo, okrem iných. Tieto projekty však nepoužívajú javascript.

Prepísanie posledného príkladu do YAML by vyzeralo takto:

Title: "A simple, yet powerful parser"
Category: "programming"
Tags: ["yaml", "markdown", "metadata"]

Aby sme z toho urobili Front Matter YAML v Markdowne, musíme to obaliť do ---:

---
title: "A simple, yet powerful parser"
cathegory: "programming"
taxonomies:
  tags: ["markdown", "yaml", "metadata"]
---

Na správne a jednoduché parsovanie tohto druhu dokumentu v Javascripte som zvolil remark-frontmatter z ekosystému Unified a balíček js-yaml. Celý kód by vyzeral takto:

const fs = require("fs")
const yaml = require("js-yaml")
const unified = require("unified")
const parse = require("remark-parse")
const stringify = require("remark-stringify")
const frontmatter = require("remark-frontmatter")
const select = require("unist-util-select").select

let tree

unified()
  .use(parse)
  .use(stringify)
  .use(frontmatter, ["yaml"])
  .use(() => t => (tree = t))
  .process(fs.readFileSync("example.md"))

const yamlNode = select("yaml", tree)
const parsedYaml = yaml.safeLoad(yamlNode.value)

module.exports = parsedYaml

Zdrojové kódy sú dostupné v repozitári