The project is in a healthy, maintained state
A Ruby library for representing and processing Metanorma document XML, providing a comprehensive model for standards documents with support for various metadata, content blocks, and structured markup.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

~> 0.8.0
 Project Readme

metanorma-document

Ruby library for working with Metanorma document XML.

Installation

gem install metanorma-document

Usage

require 'metanorma/document'
doc = IO.read('spec/fixtures/rice-amd-en.final.xml')
standard = Metanorma::IsoDocument::Root.from_xml(doc)
puts standard.to_xml(pretty: true)

HTML Generation

The Metanorma::Html::Generator produces a complete, self-contained HTML document from a presentation XML document model. It handles body content rendering, table of contents, CSS theming, JavaScript interactivity, and responsive layout.

Quick Start

require "metanorma/document"
require "metanorma/html/generator"

Parse presentation XML into a document model

xml = File.read("spec/fixtures/iso/is/document-en.presentation.xml") doc = Metanorma::IsoDocument::Root.from_xml(xml)

Generate a complete HTML document

html = Metanorma::Html::Generator.generate(doc) File.write("output.html", html)

Renderer Selection

The Generator automatically selects the correct renderer based on the document model class:

Metanorma::Document::Root

BaseRenderer (generic documents)

Metanorma::StandardDocument::Root

StandardRenderer (standards with terms, annexes)

Metanorma::IsoDocument::Root

IsoRenderer (ISO-specific formatting)

For publisher-based dispatch within the same model (e.g., ICC documents published through ISO), use taste registration:

ICC documents are IsoDocument::Root but use IccRenderer

Metanorma::Html::Generator.register_taste( Metanorma::IsoDocument::Root, "ICC", Metanorma::Html::IccRenderer )

The lookup order is: taste match (publisher) → model class (most specific last) → BaseRenderer.

Renderer Architecture

The renderer hierarchy follows the open/closed principle:

BaseRenderer                  # Core HTML rendering, document assembly, CSS/JS pipeline
└── StandardRenderer          # Terms, definitions, annexes, bibliography
    ├── IsoRenderer           # ISO cover page, copyright, title formatting
    │   ├── IccRenderer       # ICC publisher styling
    │   └── PdfaRenderer      # PDF Association publisher styling
    ├── IecRenderer           # IEC-specific formatting
    ├── IeeeRenderer          # IEEE-specific formatting
    ├── IetfRenderer          # IETF-specific formatting
    ├── IhoRenderer           # IHO-specific formatting
    ├── ItuRenderer           # ITU-specific formatting
    ├── OgcRenderer           # OGC-specific formatting
    ├── OimlRenderer          # OIML-specific formatting
    ├── BipmRenderer          # BIPM-specific formatting
    ├── CcRenderer            # CC-specific formatting
    └── RiboseRenderer        # Ribose-specific formatting
Drop Pattern

Block elements (notes, examples, sourcecode, formulas, figures, admonitions) use the Drop pattern:

A Drop captures rendered content via RendererContext, then passes

data to a Liquid template. The renderer never emits HTML directly.

def render_note(note, **_opts) drop = Drops::NoteDrop.from_model(note, renderer: renderer_context) @output << render_liquid("_note.html.liquid", { "block" ⇒ drop }) end

Each Drop class inherits from BlockElementDrop and implements .from_model(model, renderer:) which:

  1. Captures child content via renderer.capture_output { …​ }

  2. Returns a new Drop instance with pre-rendered HTML strings

  3. The Liquid template reads Drop attributes and outputs final HTML

RendererContext is a facade that exposes only the rendering methods Drops need, maintaining encapsulation.

Class Name Ownership

The HTML renderer owns its class names entirely. No XML-originated class names appear in the HTML output.

  • Block-level classes (note-block, formula, figure, etc.) are assigned by the renderer based on what it’s rendering

  • Inline span classes use SPAN_ROLE_CLASSES to map XML span roles to HTML-specific names (e.g., boldtitletitle-text, citesecxref-section)

  • The XML’s class_attr is read as input only to determine semantic role, never emitted directly

Presentation XML

The HTML renderer expects presentation XML (not source XML). Presentation XML contains fmt- display elements (fmt-title, fmt-xref, fmt-link, fmt-concept, fmt-definition, fmt-preferred, etc.) alongside semantic elements. The renderer prioritizes fmt- elements for rendering, falling back to semantic elements when display elements are absent.

Theming

Each renderer has a Theme object controlling colors, typography, and layout. Override theme properties in subclasses:

class MyRenderer < Metanorma::Html::StandardRenderer
  def theme
    @theme ||= begin
      t = Theme.new
      t.primary = "#1a5276"
      t.accent = "#2e86c1"
      t.font_body = '"Charter", serif'
      t
    end
  end
end

Theme properties are emitted as CSS custom properties (--mn-primary, --font-body, etc.) for runtime customization.

Liquid Templates

HTML structure is defined in .liquid templates under lib/metanorma/html/templates/:

document.html.liquid

Full document shell (<html>, <head>, <body>)

_header.html.liquid

Sticky header with publisher logos

_footer.html.liquid

Footer with copyright

_cover.html.liquid

Cover page layout

_iso_cover.html.liquid

ISO-specific cover page

_footnotes.html.liquid

Footnotes section

_doc_title.html.liquid

Document title rendering

_note.html.liquid

Note block

_example.html.liquid

Example block

_sourcecode.html.liquid

Source code block

_formula.html.liquid

Formula block

_figure.html.liquid

Figure block

_admonition.html.liquid

Admonition block

Templates use Liquid::LocalFileSystem for partials (prefixed with _). The render_liquid method handles template caching via TEMPLATE_CACHE.

Asset Pipeline

The AssetPipeline compiles CSS and JavaScript from modular source files:

CSS

data/stylesheets/base/ (reset, typography, layout, dark mode, print) + data/stylesheets/components/ (21 component stylesheets)

JS

data/javascripts/core/ (reader, theme, scroll, navigation, reveal) + data/javascripts/components/ (ToC, search, lightbox, code copy, glossary, etc.)

All assets are compiled into inline <style> and <script> blocks for self-contained output.

CLI Usage

From the command line:

Generate HTML from presentation XML

bundle exec ruby -e ' require "metanorma/document" require "metanorma/html/generator" doc = Metanorma::IsoDocument::Root.from_xml(ARGF.read) puts Metanorma::Html::Generator.generate(doc) ' < document.presentation.xml > output.html

API Reference

Generator.generate(document, **options)

Returns a complete HTML string. Auto-selects the renderer.

Generator.register(model_class, renderer_class)

Register a model-to-renderer mapping.

Generator.register_taste(model_class, publisher_abbrev, renderer_class)

Register a publisher-based override.

Generator.renderer_for(document)

Returns the renderer class that would be used.

Renderer instance methods:

generate_full_document(document)

Full HTML document (body + assembly).

to_html

Returns the rendered body content after generate_full_document.

theme

Returns the Theme instance (override in subclasses).

render_liquid(template_name, assigns)

Renders a Liquid template with caching.

Document Flavors

The gem provides a hierarchy of document flavors:

  • BasicDocument - Basic document model

  • StandardDocument - Standard document model (extends BasicDocument)

  • IsoDocument - ISO standard document model (extends StandardDocument)

  • IecDocument - IEC standard document model

  • IeeeDocument - IEEE standard document model

  • IetfDocument - IETF standard document model

  • IhoDocument - IHO standard document model

  • OimlDocument - OIML standard document model

  • BipmDocument - BIPM standard document model

  • ItuDocument - ITU standard document model

  • OgcDocument - OGC standard document model

  • CcDocument - CC standard document model

  • RiboseDocument - Ribose standard document model

Supported XML Formats

This library targets the modern Metanorma XML format which uses <metanorma> as the root element.

Legacy XML formats that use flavor-specific root elements (e.g. <iso-standard>, <m3d-standard>, <csa-standard>, <un-standard>) are not supported.

Documentation

Detailed architecture documentation is in the docs/ directory: