Supported formats reference
manni meta reads metadata from a document using a per-format extractor. The
extractor is chosen from the file extension, or forced with
--as <format>. Every extractor below is
implemented and counts toward directory and glob walks.
Formats
Section titled “Formats”Format (--as name) |
Extensions | Metadata source | fill can write |
|---|---|---|---|
markdown |
.md, .markdown |
Leading fenced frontmatter: YAML (--- … ---), TOML (+++ … +++), or JSON (;;; … ;;;). |
Yes |
mdx |
.mdx |
Leading fenced frontmatter: YAML, TOML, or JSON. | Yes |
asciidoc |
.adoc, .asciidoc |
Fenced frontmatter, or the native header: = Title plus :key: value attributes. |
Only into an existing fenced block |
rst |
.rst |
Fenced frontmatter, or the native section title plus :key: value docinfo fields. |
Only into an existing fenced block |
xml |
.xml, .dita, .ditamap |
Root-element attributes, plus element metadata; for DITA, also <othermeta> and the typed <prolog>/<topicmeta> elements. |
Yes |
html |
.html, .htm |
<title> plus <meta name="…" content="…"> tags, plus element metadata from <head>. |
Yes |
Why writing is conditional
Section titled “Why writing is conditional”Writing metadata back is a harder problem than reading it. fill will only write
a format it can round-trip without disturbing the rest of the document.
Fenced frontmatter round-trips cleanly. Only the characters between the fences are replaced. The body, the fence style, and (for YAML) comments and quoting survive untouched.
The native syntaxes do not. AsciiDoc attributes and reStructuredText docinfo
fields are read through a YAML scalar parse that loses the original spelling. An
rst title is synthesized from the section heading rather than stored as a
field. Writing it back would mean rewriting the underline adornment or adding a
:title: field that shadows the heading. Nor can manni meta simply add a fenced
block to those files. A bare --- is a transition in reStructuredText and an
open-block delimiter in AsciiDoc. Inventing one would change how the page
renders.
HTML and XML are both written the same way, by replacing one character range and leaving every other byte alone. For HTML the parser hands those ranges over directly. For XML it does not. The parser reports only where each attribute starts, as a line and column, so manni meta rebuilds the range from that position.
DITA is written differently from plain XML, and not for a mechanical reason. Its
metadata belongs in a <prolog> element, so adding it to the root element would
produce a topic that its DTD rejects. The prolog is written instead.
fill writes HTML and XML the same way it writes fenced frontmatter, by
replacing one range and leaving every other byte alone. Entity spelling,
attribute quoting, void elements and the doctype all survive. For XML that
matters most with the DTD-declared entities common in DITA-adjacent content:
re-serializing would turn into &nbsp;, and splicing cannot. A new
<meta> tag is inserted just inside <head>, indented to match its neighbours.
A new XML attribute is added to the root element’s start tag. A page with no
<head> of its own is refused rather than guessed at.
Run manni meta schemas to see the current writability of each format. A file
manni meta cannot write is reported as a per-file error, and the rest of the run
continues. Examples are an .rst with no fenced block, or an HTML page with no
<head>.
Some formats keep metadata in more than one place. HTML can carry a title in
both <title> and a <meta name="title">. fill writes back to whichever one
manni meta read the value from. That matters because fill also corrects values
that are present but invalid. Writing the correction anywhere else would leave
the wrong value in the tag the page actually uses. The report would still call
the file fixed.
The --as name is the extractor name in the first column. The extension match
is case-insensitive.
Frontmatter flavors
Section titled “Frontmatter flavors”Wherever manni meta reads frontmatter, it accepts three interchangeable flavors.
That covers Markdown, MDX, and the fenced-block path of AsciiDoc and
reStructuredText. Vale uses the
same convention. The flavor is auto-detected from the opening fence, so no flag
or --as name is needed:
| Flavor | Fence | Inner syntax |
|---|---|---|
| YAML | --- … --- (or ... close) |
YAML |
| TOML | +++ … +++ |
TOML |
| JSON | ;;; … ;;; |
a JSON object |
YAML additionally accepts the conventional ... document-end marker as a closing
fence; TOML and JSON close on a repeat of their own opening fence.
All three are fenced blocks at the very top of the file. The opening fence sits
on its own line, the content beneath, and a matching closing fence below that. A
fence that never closes is not treated as frontmatter. For AsciiDoc and rst
the native header is read instead. For Markdown and MDX, which have no native
fallback, the metadata is reported as absent rather than as an error. A
malformed block, whether invalid YAML, TOML, or JSON, is a per-file parse
failure with exit code 1. The rest of the run continues. See Output & exit
codes.
Error annotations point at the offending field’s own source line. For TOML this
is best-effort. Bare and simply-quoted top-level keys (key = …, "key" = …)
map to their line. Dotted keys, keys nested under a [table], and quoted keys
containing escape sequences fall back to the block’s opening fence line.
How each format is read
Section titled “How each format is read”Markdown and MDX
Section titled “Markdown and MDX”Both read a leading fenced frontmatter block in any of the three
flavors. MDX uses the same logic as Markdown;
export const meta = {…} is not read. A file with no frontmatter reports its
metadata as not present.
The three blocks below are equivalent:
---type: guidetitle: Getting startedtags: [setup, onboarding]---+++type = "guide"title = "Getting started"tags = ["setup", "onboarding"]+++;;;{ "type": "guide", "title": "Getting started", "tags": ["setup", "onboarding"]};;;AsciiDoc
Section titled “AsciiDoc”AsciiDoc accepts two metadata styles. If the file opens with a complete fenced frontmatter block (YAML, TOML, or JSON), that block is used. Otherwise manni meta reads the native document header: the lines from the top of the file down to the first blank line:
- A leading
= Titleline becomestitle. - Each
:name: valueline becomes anamekey. A:name:with no value istrue; an unset attribute (:!name:or:name!:) isfalse. - Other header lines (such as author or revision lines) are ignored.
= Getting started:type: guide:draft: falsereStructuredText
Section titled “reStructuredText”reStructuredText also accepts two styles. A complete leading fenced frontmatter block (YAML, TOML, or JSON) is used when present (as some MyST setups produce it). Otherwise manni meta reads the native page metadata:
- A leading section title (a line underlined, and optionally overlined, with
punctuation) becomes
title. - The docinfo field list that follows, a run of
:name: valuefields, becomes the remaining keys. A:name:with no value istrue. An explicit:title:field takes precedence over the heading.
Getting started===============
:type: guide:tags: [setup, onboarding]XML metadata comes from the attributes of the root element. Namespace
declarations (xmlns and xmlns:*) are dropped as transport noise.
<document type="concept" version="2" />This yields type: "concept" and version: 2. Malformed XML is reported as a
per-file parse error. An entity manni meta cannot resolve is not malformed XML. No
external DTD is ever fetched. A reference such as is left as written,
and the file is still read.
DITA topics (.dita) and maps (.ditamap) are read by the same extractor, so
their root-element attributes are the metadata:
<concept id="metadata-overview" type="concept" xml:lang="en-us">This yields id, type, and xml:lang; the DOCTYPE declaration is skipped.
The DTD-declared entities common in DITA content ( , —) do not
fail the file. See above.
Elements are read too, not just attributes. The root’s direct text-bearing
children become <root>.<child> keys, so <article><byline> is
article.byline. An elements: path
reaches deeper. HTML gains head.title beside the flat title it has always
produced. See Element metadata for the
naming rule and what a write will and will not do.
DITA also carries metadata in the place the standard sets aside for it, and manni meta reads that too:
<prolog> <metadata> <othermeta name="audience" content="developer"/> </metadata></prolog>A map uses <topicmeta> in place of <prolog><metadata>. Where a key appears
both as a root attribute and as an <othermeta>, the <othermeta> wins. It is
the explicit metadata channel, while root attributes are largely structural.
manni meta also reads the typed prolog elements <author>, <critdates>,
<audience> and <permissions>, as prolog.author, critdates.created and so
on. Those and the <othermeta> keys are separate keys, so a topic carrying the
same fact in both channels has both validated. The DITA
schema describes them.
fill writes DITA into that same channel. A key already present keeps its
place. A root attribute is corrected as a root attribute, and an <othermeta>
as an <othermeta>. Only a key that appears in neither is added, as a new
<othermeta>. <metadata> and <prolog> are created if the topic lacks them.
A new <prolog> is placed where the content model requires: after the title,
titlealts, shortdesc and abstract, and before the body.
Keeping an existing key in its own tag is what makes the write safe. fill
corrects values that are present but invalid, not only missing ones. Adding an
<othermeta> beside a stale root attribute would leave a stale value in place.
That is the value a DITA processor actually reads, and the report would still
call the file fixed. It is also why the write cannot break DTD validity:
updating an attribute that is already there cannot make it un-declared.
A file counts as DITA in three cases. Its DOCTYPE names a DITA DTD. Its root
element carries a DITA class or DITAArchVersion attribute. Or a .dita /
.ditamap extension is paired with a DITA root element name. A root element
merely named map or task in a .xml file is not enough.
HTML metadata comes from the document head:
<title>…</title>becomestitle(the first<title>wins; its text is kept verbatim).<meta name="X" content="Y">becomesX: Y.property="X"is accepted in place ofnamefor OpenGraph-style tags.<meta>tags with neithernamenorproperty(such ascharsetorhttp-equiv) carry no metadata and are skipped. For duplicate keys, the last tag wins.- A
<meta name="title">outranks the<title>element, whichever comes first in the document. This matters when filling:fillwritestitleback into the<meta>tag when the page has one, because that is the value manni meta reads.
<title>Getting started</title><meta name="type" content="guide"><meta name="draft" content="false">HTML parsing recovers from malformed markup, so extraction does not throw a parse error.
fill can write HTML. An existing tag has only its value replaced, so the
surrounding quoting and spelling are preserved. A key with no tag yet gets a new
<meta> just inside <head>, indented to match its neighbours. A document
whose <head> is implied rather than written out, an HTML fragment for
instance, is refused. There is no head to write into.
Type coercion
Section titled “Type coercion”Each value on the native metadata paths is parsed as a YAML scalar. Those
paths are the AsciiDoc header, the reStructuredText docinfo field list, XML
attributes, and HTML <meta>/<title>. This means string-looking inputs are
coerced to their natural types:
| Raw value | Becomes | Type |
|---|---|---|
2 |
2 |
number |
true |
true |
boolean |
[a, b] |
["a", "b"] |
array |
An explicitly empty value (title="" in XML, content="" in HTML) stays the
empty string rather than becoming null.
Fenced frontmatter carries its own native types instead, with no per-value
scalar re-parsing is applied. YAML types come from normal YAML rules, and TOML
and JSON from their own type systems. So version = 2 and "version": 2 are
both the number 2, and a TOML/JSON array is a list.
Because the value’s type is decided by how it’s written, quoting is a real
authoring choice in every flavor. A quoted "2", or YAML version: "2", is the
string "2". It will not satisfy a schema field typed integer.