Project

rfcxml

0.0
The project is in a healthy, maintained state
IETF RFC XML parser and generator
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

 Project Readme

RFC XML Ruby library

The rfcxml library is a parser and generator for RFC XML v3 documents.

It provides bidirectional XML serialization using the lutaml-model framework, enabling perfect round-trips with namespace preservation.

Features

  • Parse RFC XML v3 documents into Ruby objects

  • Generate RFC XML v3 documents from Ruby objects

  • Perfect round-trip preservation (namespaces, xml:lang, etc.)

  • Full support for all RFC XML v3 elements

  • Built on lutaml-model for declarative XML mapping

Requirements

  • Ruby >= 3.2.0

  • lutaml-model ~> 0.8.0 (required for namespace preservation and xml:lang support)

The lutaml-model 0.8.0 dependency provides:

  • Automatic namespace declaration preservation (xmlns:* attributes)

  • Built-in Lutaml::Xml::W3c::XmlLangType for xml:lang attribute handling

  • Perfect XML round-trip support

Installation

Add this line to your application’s Gemfile:

gem 'rfcxml'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install rfcxml

Usage

Parsing an RFC XML document

require 'rfcxml'

# Parse from file
xml = File.read('rfc8650.xml')
rfc = Rfcxml::V3::Rfc.from_xml(xml)

# Access document attributes
puts rfc.number           # => "8650"
puts rfc.category        # => "std"
puts rfc.submission_type # => "IETF"

# Access front matter
puts rfc.front.title.content
rfc.front.author.each do |author|
  puts author.fullname
end

# Access sections
rfc.middle.section.each do |section|
  puts section.name&.content
end

Creating an Internet-Draft

Internet-Drafts have a doc_name attribute but no number:

require 'rfcxml'

draft = Rfcxml::V3::Rfc.new(
  doc_name: "draft-ietf-example-protocol-01",
  submission_type: "IETF",
  ipr: "trust200902",
  category: "std",
  version: "3",

  front: Rfcxml::V3::Front.new(
    title: Rfcxml::V3::Title.new(
      content: "Example Protocol for Testing",
      abbrev: "Example Protocol"
    ),
    author: [
      Rfcxml::V3::Author.new(
        fullname: "Jane Doe",
        initials: "J.",
        surname: "Doe",
        role: "editor"
      )
    ],
    date: Rfcxml::V3::Date.new(year: "2025", month: "January")
  ),

  middle: Rfcxml::V3::Middle.new(
    section: [
      Rfcxml::V3::Section.new(
        anchor: "introduction",
        name: Rfcxml::V3::Name.new(content: "Introduction"),
        t: [
          Rfcxml::V3::Text.new(content: "This is an example Internet-Draft.")
        ]
      )
    ]
  )
)

xml = draft.to_xml(pretty: true, declaration: true, encoding: "utf-8")

Creating a Published RFC

Published RFCs have a number attribute and may have obsoletes/updates:

require 'rfcxml'

rfc = Rfcxml::V3::Rfc.new(
  number: "9999",
  category: "std",
  obsoletes: "9998",
  updates: "9997,9996",
  consensus: "true",
  ipr: "trust200902",
  version: "3",

  front: Rfcxml::V3::Front.new(
    title: Rfcxml::V3::Title.new(
      content: "A Standard Protocol for Example Purposes",
      abbrev: "Example Standard"
    ),
    series_info: [
      Rfcxml::V3::SeriesInfo.new(name: "RFC", value: "9999", stream: "IETF")
    ],
    author: [
      Rfcxml::V3::Author.new(fullname: "John Smith", surname: "Smith")
    ],
    date: Rfcxml::V3::Date.new(year: "2025", month: "March")
  ),

  middle: Rfcxml::V3::Middle.new(
    section: [
      Rfcxml::V3::Section.new(
        name: Rfcxml::V3::Name.new(content: "Overview"),
        t: [
          Rfcxml::V3::Text.new(content: "This document specifies a standard protocol.")
        ]
      )
    ]
  )
)

xml = rfc.to_xml(pretty: true, declaration: true, encoding: "utf-8")

Bibliography-Only Usage

If you only need to parse or generate bibliography references (the <reference> element), you can use Rfcxml::V3::Reference directly:

require 'rfcxml'

# Parse a reference from XML
ref_xml = <<~XML
  <reference anchor="RFC2119">
    <front>
      <title>Key words for use in RFCs to Indicate Requirement Levels</title>
      <author fullname="S. Bradner" surname="Bradner"/>
      <date month="March" year="1997"/>
    </front>
    <seriesInfo name="RFC" value="2119"/>
  </reference>
XML

ref = Rfcxml::V3::Reference.from_xml(ref_xml)
puts ref.anchor  # => "RFC2119"
puts ref.front.title.content

# Create a reference programmatically
ref = Rfcxml::V3::Reference.new(
  anchor: "RFC8650",
  front: Rfcxml::V3::Front.new(
    title: Rfcxml::V3::Title.new(
      content: "Dynamic Subscription to YANG Events and Datastores over RESTCONF"
    ),
    author: [
      Rfcxml::V3::Author.new(fullname: "E. Voit", surname: "Voit")
    ],
    date: Rfcxml::V3::Date.new(year: "2019", month: "September"),
    series_info: [
      Rfcxml::V3::SeriesInfo.new(name: "RFC", value: "8650")
    ]
  )
)

xml = ref.to_xml(pretty: true)

Round-trip preservation

The library preserves all XML structure during round-trips:

require 'rfcxml'

# Parse
input = File.read('rfc.xml')
rfc = Rfcxml::V3::Rfc.from_xml(input)

# Serialize back
output = rfc.to_xml(pretty: true, declaration: true, encoding: "utf-8")

# The output preserves:
# - xml:lang attribute
# - xmlns:* namespace declarations (e.g., xmlns:xi for XInclude)
# - All element structure and content

Document Structure

The RFC XML v3 structure maps to these Ruby classes:

Ruby Class XML Element

Rfcxml::V3::Rfc

<rfc> (root)

Rfcxml::V3::Front

<front>

Rfcxml::V3::Middle

<middle>

Rfcxml::V3::Back

<back>

Rfcxml::V3::Title

<title>

Rfcxml::V3::Author

<author>

Rfcxml::V3::Section

<section>

Rfcxml::V3::Text

<t> (paragraph)

Rfcxml::V3::Reference

<reference>

Rfcxml::V3::References

<references>

Key Differences: Internet-Draft vs Published RFC

| Attribute | Internet-Draft | Published RFC | |-----------|----------------|---------------| | doc_name | Required (e.g., draft-ietf-wg-topic-01) | Optional | | number | Not used | Required (e.g., "8650") | | expires_date | Optional | Not used | | obsoletes | Not used | Optional | | updates | Not used | Optional | | series_info | Optional | Usually present |

Serialization Options

The to_xml method supports these options:

Option Default Description

:pretty

false

Pretty-print XML with indentation

:declaration

false

Include XML declaration

:encoding

"utf-8"

Set encoding in XML declaration

:omit_doctype

false

Omit DOCTYPE declaration

Example:

xml = rfc.to_xml(
  pretty: true,
  declaration: true,
  encoding: "utf-8"
)

Enumeration Validation

The library provides built-in validation for all RFC XML v3 enumeration attributes.

These are defined in the v3.rnc schema and implemented using lutaml-model’s values: constraint.

Core Document Attributes

Attribute

Valid Values

Default

category

std, bcp, exp, info, historic

-

consensus

no, yes, false, true

false

submissionType

IETF, IAB, IRTF, independent, editorial

IETF

sortRefs

true, false

false

symRefs

true, false

true

tocInclude

true, false

true

indexInclude

true, false

true

Section Attributes

Attribute

Valid Values

Default

numbered

true, false

true

toc

include, exclude, default

default

removeInRFC

true, false

false

Author Attributes

Attribute

Valid Values

Default

role

editor

-

Validation

Validation requires an explicit call to validate or validate!:

rfc = Rfcxml::V3::Rfc.new(category: "invalid")
errors = rfc.validate  # => array of validation errors
rfc.validate!          # => raises Lutaml::Model::ValidationError

Development

After checking out the repo, run:

$ bundle install
$ bundle exec rake

This runs tests + linting. To run tests only:

$ bundle exec rake spec

Round-Trip Testing

The library includes comprehensive round-trip tests that verify XML parsing and serialization preserve all document structure. These tests use the Canon gem for semantic XML comparison.

Downloading RFC XML Fixtures

To run round-trip tests, you need to download RFC XML fixtures from rfc-editor.org:

$ bundle exec rake rfc:download

This downloads and extracts RFC XML files to spec/fixtures/xmlsource/. The download is automatically excluded from version control via .gitignore.

To re-download fresh fixtures:

$ bundle exec rake rfc:redownload

To clean downloaded fixtures:

$ bundle exec rake rfc:clean

Running Round-Trip Tests

Run round-trip tests for all downloaded RFC XML files:

$ bundle exec ruby scripts/roundtrip_test.rb

The test script will:

  1. Parse each RFC XML file using Rfcxml::V3::Rfc.from_xml

  2. Serialize back to XML using to_xml

  3. Compare original and serialized XML using Canon with spec_friendly profile

  4. Report pass/fail statistics

Run round-trip tests for specific files:

$ bundle exec ruby scripts/roundtrip_test.rb spec/fixtures/xmlsource/rfc8650.xml
$ bundle exec ruby scripts/roundtrip_test.rb spec/fixtures/xmlsource/rfc8650.xml spec/fixtures/xmlsource/rfc8651.xml

Run round-trip tests with custom thread count:

$ bundle exec ruby scripts/roundtrip_test.rb -- --threads 4

Output is saved to tmp/roundtrip-results-{timestamp}/ with detailed failure reports.

Architecture

The library uses lutaml-model for XML serialization:

  • All elements inherit from Lutaml::Model::Serializable

  • Attributes are declared with attribute class method

  • XML mapping is defined in xml do …​ end block

  • Circular references use string class names (e.g., "Rfcxml::V3::Section")

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/metanorma/rfcxml.

The gem is available as open source under the terms of the BSD 2-clause license.

All rights reserved. Ribose.