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:langsupport)
The lutaml-model 0.8.0 dependency provides:
-
Automatic namespace declaration preservation (
xmlns:*attributes) -
Built-in
Lutaml::Xml::W3c::XmlLangTypeforxml:langattribute handling -
Perfect XML round-trip support
Installation
Add this line to your application’s Gemfile:
gem 'rfcxml'And then execute:
$ bundle installOr install it yourself as:
$ gem install rfcxmlUsage
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
endCreating 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 contentDocument Structure
The RFC XML v3 structure maps to these Ruby classes:
| Ruby Class | XML Element |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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-print XML with indentation |
|
|
Include XML declaration |
|
|
Set encoding in XML declaration |
|
|
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 |
|
|
- |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Section Attributes
Attribute |
Valid Values |
Default |
|
|
|
|
|
|
|
|
|
Author Attributes
Attribute |
Valid Values |
Default |
|
|
- |
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::ValidationErrorDevelopment
After checking out the repo, run:
$ bundle install
$ bundle exec rakeThis runs tests + linting. To run tests only:
$ bundle exec rake specRound-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:downloadThis 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:redownloadTo clean downloaded fixtures:
$ bundle exec rake rfc:cleanRunning Round-Trip Tests
Run round-trip tests for all downloaded RFC XML files:
$ bundle exec ruby scripts/roundtrip_test.rbThe test script will:
-
Parse each RFC XML file using
Rfcxml::V3::Rfc.from_xml -
Serialize back to XML using
to_xml -
Compare original and serialized XML using Canon with
spec_friendlyprofile -
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.xmlRun round-trip tests with custom thread count:
$ bundle exec ruby scripts/roundtrip_test.rb -- --threads 4Output 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
attributeclass method -
XML mapping is defined in
xml do … endblock -
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.
Copyright and license
The gem is available as open source under the terms of the BSD 2-clause license.
All rights reserved. Ribose.