One Model, One Markdown Dialect

Once anydoc has chosen a frontend and resolved the source document’s meanings, a second question appears: how can one renderer give different formats the same output behavior without flattening them into plain text? This chapter follows the shared model into the Markdown serializer. The practical result is a way to predict what anydoc preserves, what it normalizes, and where GitHub-Flavored Markdown forces a deliberate compromise.

The shared model carries meaning before it carries text

A common model is sometimes described as a lowest common denominator: reduce every source to the smallest set of features that all formats happen to share. That would make conversion easy to explain but poor to use. anydoc instead carries a structured vocabulary broad enough to preserve relationships that matter to reading and downstream processing.

At the block level, the model distinguishes headings, paragraphs, lists, tables, quotations, code blocks, horizontal rules, and formulas. At the inline level, it can carry styled text, links, images, anchors, note references, line breaks, inline formulas, and checkboxes. These are not cosmetic labels. A heading affects navigation; a list carries ordering; a link has a destination; a note reference points to content elsewhere; a table preserves a relationship among values. If all of them became one string, the renderer could not recover those distinctions reliably.

The model also receives resolved values rather than source-specific instructions. Character styles have definite states such as bold, italic, strike-through, or code. Lists carry their marker family and starting position, with literal marker text when a source numbering scheme is too composite to recreate from the item’s position. This means the renderer can ask what a list means without understanding how a particular office format stored its numbering definitions.

The same separation applies to relationships. An internal target is represented as an internal target, whether the original source called it a bookmark or used another document-local reference. External and relative links remain distinct. Note references carry document-scoped identifiers. Images can refer to an external URL, a retained embedded asset, or an unavailable source. The frontend resolves the source vocabulary; the model carries the result in a form shared by every consumer.

The Document itself is more than a list of paragraphs. It contains body blocks, note bodies, and embedded assets. Asset bytes, media types, and origin information stay attached to the model, so a consumer can make a decision after the source package has been closed. This is an important distinction between the model and the default Markdown output: a value can remain available to a program even when the target text format has no direct way to display it.

That separation gives anydoc a stable interface between parsing and rendering. Frontends can concentrate on the peculiarities of Word, ODF, RTF, presentations, spreadsheets, or EPUBs. The serializer can concentrate on the rules of GFM. A bug in source interpretation still belongs to a frontend; a bug in escaping or note layout can be corrected once for every input that reaches the shared route.

The interface is valuable because it gives each side a clear failure responsibility. A frontend should not hand the renderer unresolved style inheritance and ask it to guess. The renderer should not reinterpret a source-specific numbering definition or search the original archive for a missing relationship. If a source feature cannot be resolved into the model, it reaches the boundary as a loss or a conversion problem. If it is present in the model but awkward in Markdown, the renderer can make a consistent target-format decision. This prevents a consumer from receiving several subtly different interpretations of the same semantic feature simply because the input arrived through different parsers.

It also makes the model useful for more than immediate string conversion. A caller can stop at the document representation, inspect retained assets, or build another output path without reparsing the source package. That possibility is especially important for mixed workflows: the Markdown may serve search and language processing, while notes, links, or binary assets remain structured data for a second stage. The common model is therefore both a normalization boundary and a point at which downstream applications can choose how much information to keep.

classDiagram class Document class Block class Inline class Table class Note class Asset class Style class List class MarkdownRenderer class GFM Document o-- Block : contains Document o-- Note : defines Document o-- Asset : retains Block --> Inline : contains Block --> Table : may contain Inline --> Style : uses resolved style Block --> List : carries resolved numbering Document --> MarkdownRenderer : rendered by MarkdownRenderer --> GFM : emits

The model is therefore not a detour before the “real” conversion. It is the place where anydoc decides what can be made stable across formats. That decision makes the serializer more predictable, but it does not make Markdown infinitely expressive. The next layer has to map a richer structure onto a narrower syntax without allowing source text to break that syntax.

One serializer applies one output policy

The serializer’s consistency begins with the document-level structures that are easy to overlook. It assigns note numbers according to first reference, resolves document anchors, renders blocks in reading order, and appends note definitions. That policy means a note does not receive a different treatment merely because the paragraph containing it came from one source format rather than another. An internal link that has no resolvable target can degrade to its visible content instead of becoming a misleading link.

First-reference numbering is a small but useful example of a policy that belongs in the shared layer. The source may define notes in an order that differs from the order in which a reader encounters them. The serializer can number the references as they appear in the rendered document, then place the corresponding definitions in a stable order. An empty note does not create a body marker, while a nonempty unreferenced note can be placed after referenced notes in the definition order. The result is easier to read and independent of the storage order chosen by the original format.

Anchors create a related boundary. An internal link is useful only if its target exists in the normalized document and can be mapped to a fragment in the output. When that relationship cannot be resolved, preserving the label as ordinary text is safer than emitting a link that appears navigable but leads nowhere. This is a general rule for normalization: when a relationship is missing, degrade its presentation without inventing a destination.

Lists require a similar translation. GFM supports decimal ordered lists and bullets, but source documents can use alphabetic, Roman, or composite markers. Where the source marker can be represented, the serializer uses the source’s ordering information. Where it cannot, the marker remains visible as literal text carried by a bullet rather than being silently converted into a new numbering scheme. The output may not reproduce the source’s typography, but it avoids claiming that a different order is equivalent.

Formulas, code, and links also have their own target syntax. Equations are represented as LaTeX math spans or blocks. Code uses fences long enough to avoid colliding with backticks in the source. External URLs are formatted as Markdown destinations, while internal targets are mapped to generated fragments. These decisions are small individually, but centralizing them matters: an escaping or delimiter fix is not left to each parser to rediscover.

Escaping is where the serializer’s job becomes more than formatting. Markdown characters are not always syntax; their meaning depends on where they occur and what surrounds them. A hash at the beginning of a line can start a heading. A hyphen can begin a list or a thematic break. A pipe can split a table row. A bracket can close a link label. A backtick can open or close a code span. An unescaped source character can therefore alter the structure of the output even when the source document treated it as ordinary text.

anydoc’s renderer tracks context such as ordinary block text, headings, and table cells. It also considers whether a run is inside emphasis, a link label, or a code span, and whether later text could pair with a delimiter. This is not an attempt to escape every punctuation mark. Excessive escaping would make the output noisy and could itself change how readers or Markdown consumers interpret it. The aim is to escape characters when they could become active syntax in the current context and leave ordinary content readable elsewhere.

That is why context-sensitive escaping belongs to semantic correctness. A heading that accidentally becomes two headings, a pipe that shifts every subsequent table column, or a bracket that truncates a link is not merely ugly output. It changes the document’s relationships. The shared serializer is the one place where those target-language risks can be handled consistently for all model-backed inputs.

The same policy applies to empty and unresolved content. Empty runs can be removed without creating visible gaps; adjacent runs with the same style can be merged; a link with no usable destination can retain its label as plain text. These choices keep the output readable while avoiding a false claim that an absent relationship is still valid. The renderer is not only printing what the model contains; it is deciding how to degrade it without inventing meaning.

Where GFM stops, anydoc makes the loss visible

Tables are the clearest example of the difference between preserving structure and reproducing presentation. The model stores a canonical grid in which each logical position appears once. A cell that spans rows or columns has an origin, and covered positions refer back to it. The builder can account for sources that explicitly list covered cells as well as sources that omit them, and it can clamp overlapping spans so the stored grid remains internally consistent.

That invariant matters because table packages do not all describe the same geometry in the same way. One source may write a marker for every position covered by a merge; another may omit the covered positions and expect the next real cell to be placed after the span. The builder records pending covered positions, skips them when a later cell arrives, and materializes them when the grid is finished. If two spans claim the same position, the later span is constrained rather than allowed to create a contradictory grid. The renderer can then treat every slot predictably instead of repairing the table while it is already emitting Markdown.

GitHub-Flavored Markdown has no general syntax for row spans or column spans. The renderer therefore emits covered positions as blank cells. This keeps values in their correct columns and avoids duplicating a merged cell across multiple positions, but it cannot reproduce the visual block created by the original application. The compromise is honest: the source table’s coordinates survive better than its visual geometry.

Cell content is also flattened into a single Markdown table line. Paragraphs can become line breaks within a cell, lists can be represented with visible markers, and nested tables can be reduced to a readable inline arrangement. That is useful for text search and downstream language processing, but it is not the same as preserving arbitrary layout. A consumer that needs exact spreadsheet presentation should not infer that a valid GFM table is a visual equivalent.

Embedded images reveal the value of keeping the model available after rendering. If an image has an external URL, the serializer can emit a normal Markdown image. If the image is embedded in the document, Markdown output uses its alternative text because the bytes cannot be placed into the text stream by the renderer. The raw asset remains on the model with its media type and origin. A pipeline can therefore index the readable Markdown and handle assets separately, but only if it retains access to the model rather than discarding it after producing a string.

The same pattern appears in list markers and heading levels. A source may contain outline depth beyond what the target conventionally renders, so the renderer maps it into the supported heading range. A source may carry a marker family that GFM cannot reproduce exactly, so the serializer keeps the marker’s visible meaning rather than pretending the target has the same numbering semantics. These are controlled transformations, not claims of losslessness.

This makes “one output for every format” a precise but limited promise. It means that formats using the shared model encounter one Markdown policy for structure, links, notes, math, tables, images, and escaping. It does not mean that every source property has a one-to-one Markdown representation. The model preserves what can remain useful; the serializer chooses a readable and structurally safe expression; the target format sets the final boundary.

That boundary is also why the model-backed path should not be judged only by whether the generated text looks plausible. A good result preserves the relationships a downstream task depends on and makes unavoidable losses legible. A table with blank covered cells can still be useful if its columns remain aligned. Alt text can still be useful if the application separately retains the image bytes. A visible source marker can be preferable to a neat but incorrect ordered list.

The shared renderer thus turns normalization into a system-wide policy. It gives different frontends a common destination without erasing the distinctions that make documents meaningful. But a consistent output policy is trustworthy only when the system is equally explicit about damaged input, missing content, and resource pressure. The next chapter moves from what the renderer emits to the conditions under which anydoc must recover, limit, or refuse a conversion.

References