rfcxml 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/roundtrip.yml +79 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +9 -3
- data/.rubocop_todo.yml +87 -17
- data/Gemfile +4 -2
- data/README.adoc +386 -18
- data/Rakefile +55 -0
- data/lib/rfcxml/v3/abstract.rb +2 -6
- data/lib/rfcxml/v3/address.rb +2 -7
- data/lib/rfcxml/v3/annotation.rb +2 -15
- data/lib/rfcxml/v3/area.rb +1 -1
- data/lib/rfcxml/v3/artset.rb +2 -3
- data/lib/rfcxml/v3/artwork.rb +13 -10
- data/lib/rfcxml/v3/aside.rb +2 -12
- data/lib/rfcxml/v3/author.rb +16 -12
- data/lib/rfcxml/v3/back.rb +2 -5
- data/lib/rfcxml/v3/bcp14.rb +1 -1
- data/lib/rfcxml/v3/blockquote.rb +2 -26
- data/lib/rfcxml/v3/boilerplate.rb +2 -3
- data/lib/rfcxml/v3/br.rb +1 -1
- data/lib/rfcxml/v3/c.rb +2 -7
- data/lib/rfcxml/v3/city.rb +1 -1
- data/lib/rfcxml/v3/cityarea.rb +1 -1
- data/lib/rfcxml/v3/code.rb +1 -1
- data/lib/rfcxml/v3/contact.rb +2 -4
- data/lib/rfcxml/v3/country.rb +1 -1
- data/lib/rfcxml/v3/cref.rb +2 -11
- data/lib/rfcxml/v3/date.rb +4 -4
- data/lib/rfcxml/v3/dd.rb +2 -25
- data/lib/rfcxml/v3/displayreference.rb +1 -1
- data/lib/rfcxml/v3/dl.rb +2 -7
- data/lib/rfcxml/v3/dt.rb +2 -14
- data/lib/rfcxml/v3/email.rb +1 -1
- data/lib/rfcxml/v3/eref.rb +1 -1
- data/lib/rfcxml/v3/extaddr.rb +1 -1
- data/lib/rfcxml/v3/facsimile.rb +1 -1
- data/lib/rfcxml/v3/figure.rb +17 -19
- data/lib/rfcxml/v3/format.rb +1 -1
- data/lib/rfcxml/v3/front.rb +2 -13
- data/lib/rfcxml/v3/iref.rb +4 -3
- data/lib/rfcxml/v3/keyword.rb +1 -1
- data/lib/rfcxml/v3/li.rb +2 -28
- data/lib/rfcxml/v3/link.rb +1 -1
- data/lib/rfcxml/v3/list.rb +3 -4
- data/lib/rfcxml/v3/middle.rb +2 -3
- data/lib/rfcxml/v3/name.rb +2 -14
- data/lib/rfcxml/v3/note.rb +2 -7
- data/lib/rfcxml/v3/ol.rb +5 -4
- data/lib/rfcxml/v3/organization.rb +10 -5
- data/lib/rfcxml/v3/phone.rb +1 -1
- data/lib/rfcxml/v3/pobox.rb +1 -1
- data/lib/rfcxml/v3/postal.rb +2 -12
- data/lib/rfcxml/v3/postal_line.rb +1 -1
- data/lib/rfcxml/v3/postamble.rb +2 -7
- data/lib/rfcxml/v3/preamble.rb +2 -15
- data/lib/rfcxml/v3/refcontent.rb +2 -4
- data/lib/rfcxml/v3/reference.rb +7 -10
- data/lib/rfcxml/v3/referencegroup.rb +2 -3
- data/lib/rfcxml/v3/references.rb +2 -5
- data/lib/rfcxml/v3/region.rb +1 -1
- data/lib/rfcxml/v3/relref.rb +1 -1
- data/lib/rfcxml/v3/rfc.rb +62 -14
- data/lib/rfcxml/v3/section.rb +11 -21
- data/lib/rfcxml/v3/series_info.rb +5 -4
- data/lib/rfcxml/v3/sortingcode.rb +1 -1
- data/lib/rfcxml/v3/sourcecode.rb +13 -9
- data/lib/rfcxml/v3/spanx.rb +1 -1
- data/lib/rfcxml/v3/street.rb +1 -1
- data/lib/rfcxml/v3/strong.rb +6 -8
- data/lib/rfcxml/v3/sub.rb +6 -15
- data/lib/rfcxml/v3/sup.rb +2 -3
- data/lib/rfcxml/v3/table.rb +1 -7
- data/lib/rfcxml/v3/tbody.rb +1 -3
- data/lib/rfcxml/v3/td.rb +23 -26
- data/lib/rfcxml/v3/text.rb +2 -20
- data/lib/rfcxml/v3/texttable.rb +12 -12
- data/lib/rfcxml/v3/tfoot.rb +1 -3
- data/lib/rfcxml/v3/th.rb +1 -3
- data/lib/rfcxml/v3/thead.rb +1 -3
- data/lib/rfcxml/v3/title.rb +3 -4
- data/lib/rfcxml/v3/toc.rb +1 -3
- data/lib/rfcxml/v3/tr.rb +3 -4
- data/lib/rfcxml/v3/tt.rb +5 -19
- data/lib/rfcxml/v3/ttcol.rb +4 -7
- data/lib/rfcxml/v3/u.rb +1 -1
- data/lib/rfcxml/v3/ul.rb +10 -6
- data/lib/rfcxml/v3/uri.rb +1 -1
- data/lib/rfcxml/v3/vspace.rb +1 -1
- data/lib/rfcxml/v3/workgroup.rb +1 -1
- data/lib/rfcxml/v3/xref.rb +2 -3
- data/lib/rfcxml/v3/xref_text.rb +7 -16
- data/lib/rfcxml/v3.rb +100 -2
- data/lib/rfcxml/version.rb +1 -1
- data/lib/rfcxml.rb +3 -4
- data/rfcxml.gemspec +3 -2
- data/scripts/README.md +110 -0
- data/scripts/roundtrip_test.rb +361 -0
- metadata +9 -6
- data/lib/rfcxml/v3/em.rb +0 -14
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d78bc97fa8b73803d80d88b9e3d69d3ab98ea34b481d45b4844b685943cb518e
|
|
4
|
+
data.tar.gz: 144d9c7c64dc113af0764099683f33bc8eb9f3f3e6ae70077ae51e43d2e2891b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 284650ce2accd2ff0a7d75089dd773dce3126e20dcb3a7c02d600612ee4f7bce134b16662e001c2642dedf2d949018550c6d7db6f3781524747ca8aa87b387b9
|
|
7
|
+
data.tar.gz: cee43215ce256f935a1e7986ca1fdfd30de4ac3f44bdd47caf8a5d34ed7fdc7166bb51a3f7db4d993132868e72aeafe255f2c013491c2ccee29c604abed80afc
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# GitHub Actions workflow for RFC XML round-trip testing
|
|
2
|
+
# Tests all RFC XML v3 files from rfc-editor.org
|
|
3
|
+
#
|
|
4
|
+
# Issue: https://github.com/metanorma/rfcxml/issues/4
|
|
5
|
+
|
|
6
|
+
name: RFC Round-Trip Tests
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
# Manual trigger
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
# Weekly run to catch new RFCs (Sundays at 00:00 UTC)
|
|
12
|
+
schedule:
|
|
13
|
+
- cron: '0 0 * * 0'
|
|
14
|
+
# Also run on pushes to main for early detection of issues
|
|
15
|
+
push:
|
|
16
|
+
branches: [ main ]
|
|
17
|
+
paths:
|
|
18
|
+
- 'lib/**'
|
|
19
|
+
- 'spec/**'
|
|
20
|
+
- 'scripts/roundtrip_test.rb'
|
|
21
|
+
pull_request:
|
|
22
|
+
paths:
|
|
23
|
+
- 'lib/**'
|
|
24
|
+
- 'spec/**'
|
|
25
|
+
- 'scripts/roundtrip_test.rb'
|
|
26
|
+
|
|
27
|
+
permissions:
|
|
28
|
+
contents: read
|
|
29
|
+
|
|
30
|
+
jobs:
|
|
31
|
+
roundtrip:
|
|
32
|
+
name: Test RFC XML Round-Trips
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
|
|
35
|
+
steps:
|
|
36
|
+
- name: Checkout repository
|
|
37
|
+
uses: actions/checkout@v4
|
|
38
|
+
|
|
39
|
+
- name: Set up Ruby
|
|
40
|
+
uses: ruby/setup-ruby@v1
|
|
41
|
+
with:
|
|
42
|
+
ruby-version: '3.2'
|
|
43
|
+
bundler-cache: true
|
|
44
|
+
|
|
45
|
+
- name: Download RFC XML files
|
|
46
|
+
run: |
|
|
47
|
+
mkdir -p rfc-xml-source
|
|
48
|
+
echo "Downloading RFC XML tarball..."
|
|
49
|
+
curl -L -o xmlsource.tar.gz "https://www.rfc-editor.org/in-notes/tar/xmlsource-all.tar.gz"
|
|
50
|
+
echo "Extracting..."
|
|
51
|
+
tar -xzf xmlsource.tar.gz -C rfc-xml-source
|
|
52
|
+
# The tarball extracts to a subdirectory, find it
|
|
53
|
+
XML_DIR=$(find rfc-xml-source -name "*.xml" -type f | head -1 | xargs dirname)
|
|
54
|
+
echo "XML_DIR=$XML_DIR" >> $GITHUB_ENV
|
|
55
|
+
FILE_COUNT=$(find "$XML_DIR" -name "*.xml" -type f | wc -l)
|
|
56
|
+
echo "Found $FILE_COUNT XML files in $XML_DIR"
|
|
57
|
+
|
|
58
|
+
- name: Run round-trip tests
|
|
59
|
+
run: |
|
|
60
|
+
echo "Testing all RFC XML files..."
|
|
61
|
+
ruby scripts/roundtrip_test.rb "$XML_DIR"
|
|
62
|
+
continue-on-error: true
|
|
63
|
+
id: roundtrip
|
|
64
|
+
|
|
65
|
+
- name: Upload test results
|
|
66
|
+
if: always()
|
|
67
|
+
uses: actions/upload-artifact@v4
|
|
68
|
+
with:
|
|
69
|
+
name: roundtrip-results
|
|
70
|
+
path: |
|
|
71
|
+
*.log
|
|
72
|
+
retention-days: 30
|
|
73
|
+
if-no-files-found: ignore
|
|
74
|
+
|
|
75
|
+
- name: Check results
|
|
76
|
+
if: steps.roundtrip.outcome == 'failure'
|
|
77
|
+
run: |
|
|
78
|
+
echo "::error::Some RFC XML files failed round-trip testing"
|
|
79
|
+
exit 1
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
# Auto-generated by Cimas: Do not edit it manually!
|
|
2
2
|
# See https://github.com/metanorma/cimas
|
|
3
3
|
inherit_from:
|
|
4
|
+
- https://raw.githubusercontent.com/riboseinc/oss-guides/main/ci/rubocop.yml
|
|
4
5
|
- .rubocop_todo.yml
|
|
5
|
-
|
|
6
|
+
|
|
7
|
+
inherit_mode:
|
|
8
|
+
merge:
|
|
9
|
+
- Exclude
|
|
6
10
|
|
|
7
11
|
# local repo-specific modifications
|
|
8
12
|
# ...
|
|
13
|
+
plugins:
|
|
14
|
+
- rubocop-rspec
|
|
15
|
+
- rubocop-performance
|
|
16
|
+
- rubocop-rake
|
|
9
17
|
|
|
10
|
-
AllCops:
|
|
11
|
-
TargetRubyVersion: 3.4
|
data/.rubocop_todo.yml
CHANGED
|
@@ -1,38 +1,108 @@
|
|
|
1
1
|
# This configuration was generated by
|
|
2
2
|
# `rubocop --auto-gen-config`
|
|
3
|
-
# on
|
|
3
|
+
# on 2026-03-19 12:31:01 UTC using RuboCop version 1.85.1.
|
|
4
4
|
# The point is for the user to remove these configuration records
|
|
5
5
|
# one by one as the offenses are removed from the code base.
|
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
|
7
7
|
# versions of RuboCop, may require this file to be generated again.
|
|
8
8
|
|
|
9
9
|
# Offense count: 1
|
|
10
|
-
# Configuration parameters: Severity, Include.
|
|
11
|
-
# Include: **/*.gemspec
|
|
12
10
|
Gemspec/RequiredRubyVersion:
|
|
13
11
|
Exclude:
|
|
14
12
|
- 'rfcxml.gemspec'
|
|
15
13
|
|
|
16
|
-
# Offense count:
|
|
17
|
-
# This cop supports safe autocorrection (--autocorrect).
|
|
18
|
-
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
|
19
|
-
# SupportedStyles: with_first_element, with_fixed_indentation
|
|
20
|
-
Layout/ArrayAlignment:
|
|
21
|
-
Exclude:
|
|
22
|
-
- 'lib/rfcxml/v3/dt.rb'
|
|
23
|
-
|
|
24
|
-
# Offense count: 2
|
|
14
|
+
# Offense count: 22
|
|
25
15
|
# This cop supports safe autocorrection (--autocorrect).
|
|
26
|
-
# Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes,
|
|
16
|
+
# Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
|
|
27
17
|
# URISchemes: http, https
|
|
28
18
|
Layout/LineLength:
|
|
29
19
|
Exclude:
|
|
30
|
-
- '
|
|
20
|
+
- 'canon_attribute_order_bug.rb'
|
|
31
21
|
- 'lib/rfcxml/v3/li.rb'
|
|
22
|
+
- 'lib/rfcxml/v3/rfc.rb'
|
|
23
|
+
- 'scripts/roundtrip_test.rb'
|
|
24
|
+
- 'spec/v3/document_creation_spec.rb'
|
|
25
|
+
|
|
26
|
+
# Offense count: 4
|
|
27
|
+
Lint/NoReturnInBeginEndBlocks:
|
|
28
|
+
Exclude:
|
|
29
|
+
- 'scripts/roundtrip_test.rb'
|
|
30
|
+
|
|
31
|
+
# Offense count: 6
|
|
32
|
+
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
|
|
33
|
+
Metrics/AbcSize:
|
|
34
|
+
Exclude:
|
|
35
|
+
- 'lib/rfcxml/v3/rfc.rb'
|
|
36
|
+
- 'scripts/roundtrip_test.rb'
|
|
37
|
+
|
|
38
|
+
# Offense count: 3
|
|
39
|
+
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
|
|
40
|
+
# AllowedMethods: refine
|
|
41
|
+
Metrics/BlockLength:
|
|
42
|
+
Max: 37
|
|
43
|
+
|
|
44
|
+
# Offense count: 3
|
|
45
|
+
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
|
|
46
|
+
Metrics/CyclomaticComplexity:
|
|
47
|
+
Exclude:
|
|
48
|
+
- 'lib/rfcxml/v3/rfc.rb'
|
|
49
|
+
- 'scripts/roundtrip_test.rb'
|
|
50
|
+
|
|
51
|
+
# Offense count: 9
|
|
52
|
+
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
|
53
|
+
Metrics/MethodLength:
|
|
54
|
+
Max: 50
|
|
55
|
+
|
|
56
|
+
# Offense count: 3
|
|
57
|
+
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
|
|
58
|
+
Metrics/PerceivedComplexity:
|
|
59
|
+
Exclude:
|
|
60
|
+
- 'lib/rfcxml/v3/rfc.rb'
|
|
61
|
+
- 'scripts/roundtrip_test.rb'
|
|
32
62
|
|
|
33
63
|
# Offense count: 1
|
|
34
64
|
# This cop supports safe autocorrection (--autocorrect).
|
|
35
|
-
# Configuration parameters:
|
|
36
|
-
|
|
65
|
+
# Configuration parameters: EnforcedStyle.
|
|
66
|
+
# SupportedStyles: be, be_nil
|
|
67
|
+
RSpec/BeNil:
|
|
68
|
+
Exclude:
|
|
69
|
+
- 'spec/rfcxml_spec.rb'
|
|
70
|
+
|
|
71
|
+
# Offense count: 1
|
|
72
|
+
# Configuration parameters: IgnoredMetadata.
|
|
73
|
+
RSpec/DescribeClass:
|
|
74
|
+
Exclude:
|
|
75
|
+
- 'spec/v3/document_creation_spec.rb'
|
|
76
|
+
|
|
77
|
+
# Offense count: 19
|
|
78
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
79
|
+
# Configuration parameters: SkipBlocks, EnforcedStyle, OnlyStaticConstants.
|
|
80
|
+
# SupportedStyles: described_class, explicit
|
|
81
|
+
RSpec/DescribedClass:
|
|
82
|
+
Exclude:
|
|
83
|
+
- 'spec/v3/author_spec.rb'
|
|
84
|
+
- 'spec/v3/rfc_spec.rb'
|
|
85
|
+
- 'spec/v3/section_spec.rb'
|
|
86
|
+
|
|
87
|
+
# Offense count: 11
|
|
88
|
+
# Configuration parameters: CountAsOne.
|
|
89
|
+
RSpec/ExampleLength:
|
|
90
|
+
Max: 29
|
|
91
|
+
|
|
92
|
+
# Offense count: 11
|
|
93
|
+
RSpec/MultipleExpectations:
|
|
94
|
+
Max: 9
|
|
95
|
+
|
|
96
|
+
# Offense count: 3
|
|
97
|
+
# Configuration parameters: CustomTransform, IgnoreMethods, IgnoreMetadata, InflectorPath, EnforcedInflector.
|
|
98
|
+
# SupportedInflectors: default, active_support
|
|
99
|
+
RSpec/SpecFilePathFormat:
|
|
100
|
+
Exclude:
|
|
101
|
+
- 'spec/v3/author_spec.rb'
|
|
102
|
+
- 'spec/v3/rfc_spec.rb'
|
|
103
|
+
- 'spec/v3/section_spec.rb'
|
|
104
|
+
|
|
105
|
+
# Offense count: 1
|
|
106
|
+
Security/Open:
|
|
37
107
|
Exclude:
|
|
38
|
-
- '
|
|
108
|
+
- 'Rakefile'
|
data/Gemfile
CHANGED
|
@@ -5,10 +5,12 @@ source "https://rubygems.org"
|
|
|
5
5
|
# Specify your gem's dependencies in genericode.gemspec
|
|
6
6
|
gemspec
|
|
7
7
|
|
|
8
|
-
gem "
|
|
8
|
+
gem "canon", github: "ronaldtse/canon", branch: "fix/empty-attribute-value"
|
|
9
|
+
gem "lutaml-model", github: "lutaml/lutaml-model", ref: "main"
|
|
9
10
|
gem "nokogiri"
|
|
10
11
|
gem "rake", "~> 13.0"
|
|
11
12
|
gem "rspec", "~> 3.0"
|
|
12
13
|
gem "rubocop"
|
|
13
14
|
gem "rubocop-performance"
|
|
14
|
-
gem "
|
|
15
|
+
gem "rubocop-rake"
|
|
16
|
+
gem "rubocop-rspec"
|
data/README.adoc
CHANGED
|
@@ -1,8 +1,28 @@
|
|
|
1
1
|
= RFC XML Ruby library
|
|
2
2
|
|
|
3
|
-
The `rfcxml` library is a parser and generator for RFC XML documents.
|
|
4
|
-
intended to be used to parse and generate RFC XML v3 documents.
|
|
3
|
+
The `rfcxml` library is a parser and generator for RFC XML v3 documents.
|
|
5
4
|
|
|
5
|
+
It provides bidirectional XML serialization using the lutaml-model framework,
|
|
6
|
+
enabling perfect round-trips with namespace preservation.
|
|
7
|
+
|
|
8
|
+
== Features
|
|
9
|
+
|
|
10
|
+
- Parse RFC XML v3 documents into Ruby objects
|
|
11
|
+
- Generate RFC XML v3 documents from Ruby objects
|
|
12
|
+
- Perfect round-trip preservation (namespaces, `xml:lang`, etc.)
|
|
13
|
+
- Full support for all RFC XML v3 elements
|
|
14
|
+
- Built on lutaml-model for declarative XML mapping
|
|
15
|
+
|
|
16
|
+
== Requirements
|
|
17
|
+
|
|
18
|
+
- Ruby >= 3.2.0
|
|
19
|
+
- lutaml-model ~> 0.8.0 (required for namespace preservation and `xml:lang` support)
|
|
20
|
+
|
|
21
|
+
The lutaml-model 0.8.0 dependency provides:
|
|
22
|
+
|
|
23
|
+
- Automatic namespace declaration preservation (`xmlns:*` attributes)
|
|
24
|
+
- Built-in `Lutaml::Xml::W3c::XmlLangType` for `xml:lang` attribute handling
|
|
25
|
+
- Perfect XML round-trip support
|
|
6
26
|
|
|
7
27
|
== Installation
|
|
8
28
|
|
|
@@ -27,47 +47,395 @@ Or install it yourself as:
|
|
|
27
47
|
$ gem install rfcxml
|
|
28
48
|
----
|
|
29
49
|
|
|
50
|
+
== Usage
|
|
30
51
|
|
|
52
|
+
=== Parsing an RFC XML document
|
|
31
53
|
|
|
32
|
-
|
|
54
|
+
[source,ruby]
|
|
55
|
+
----
|
|
56
|
+
require 'rfcxml'
|
|
57
|
+
|
|
58
|
+
# Parse from file
|
|
59
|
+
xml = File.read('rfc8650.xml')
|
|
60
|
+
rfc = Rfcxml::V3::Rfc.from_xml(xml)
|
|
61
|
+
|
|
62
|
+
# Access document attributes
|
|
63
|
+
puts rfc.number # => "8650"
|
|
64
|
+
puts rfc.category # => "std"
|
|
65
|
+
puts rfc.submission_type # => "IETF"
|
|
66
|
+
|
|
67
|
+
# Access front matter
|
|
68
|
+
puts rfc.front.title.content
|
|
69
|
+
rfc.front.author.each do |author|
|
|
70
|
+
puts author.fullname
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Access sections
|
|
74
|
+
rfc.middle.section.each do |section|
|
|
75
|
+
puts section.name&.content
|
|
76
|
+
end
|
|
77
|
+
----
|
|
33
78
|
|
|
34
|
-
|
|
79
|
+
=== Creating an Internet-Draft
|
|
80
|
+
|
|
81
|
+
Internet-Drafts have a `doc_name` attribute but no `number`:
|
|
82
|
+
|
|
83
|
+
[source,ruby]
|
|
84
|
+
----
|
|
85
|
+
require 'rfcxml'
|
|
86
|
+
|
|
87
|
+
draft = Rfcxml::V3::Rfc.new(
|
|
88
|
+
doc_name: "draft-ietf-example-protocol-01",
|
|
89
|
+
submission_type: "IETF",
|
|
90
|
+
ipr: "trust200902",
|
|
91
|
+
category: "std",
|
|
92
|
+
version: "3",
|
|
93
|
+
|
|
94
|
+
front: Rfcxml::V3::Front.new(
|
|
95
|
+
title: Rfcxml::V3::Title.new(
|
|
96
|
+
content: "Example Protocol for Testing",
|
|
97
|
+
abbrev: "Example Protocol"
|
|
98
|
+
),
|
|
99
|
+
author: [
|
|
100
|
+
Rfcxml::V3::Author.new(
|
|
101
|
+
fullname: "Jane Doe",
|
|
102
|
+
initials: "J.",
|
|
103
|
+
surname: "Doe",
|
|
104
|
+
role: "editor"
|
|
105
|
+
)
|
|
106
|
+
],
|
|
107
|
+
date: Rfcxml::V3::Date.new(year: "2025", month: "January")
|
|
108
|
+
),
|
|
109
|
+
|
|
110
|
+
middle: Rfcxml::V3::Middle.new(
|
|
111
|
+
section: [
|
|
112
|
+
Rfcxml::V3::Section.new(
|
|
113
|
+
anchor: "introduction",
|
|
114
|
+
name: Rfcxml::V3::Name.new(content: "Introduction"),
|
|
115
|
+
t: [
|
|
116
|
+
Rfcxml::V3::Text.new(content: "This is an example Internet-Draft.")
|
|
117
|
+
]
|
|
118
|
+
)
|
|
119
|
+
]
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
xml = draft.to_xml(pretty: true, declaration: true, encoding: "utf-8")
|
|
124
|
+
----
|
|
125
|
+
|
|
126
|
+
=== Creating a Published RFC
|
|
127
|
+
|
|
128
|
+
Published RFCs have a `number` attribute and may have `obsoletes`/`updates`:
|
|
35
129
|
|
|
36
130
|
[source,ruby]
|
|
37
131
|
----
|
|
38
132
|
require 'rfcxml'
|
|
39
133
|
|
|
40
|
-
rfc = Rfcxml::
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
134
|
+
rfc = Rfcxml::V3::Rfc.new(
|
|
135
|
+
number: "9999",
|
|
136
|
+
category: "std",
|
|
137
|
+
obsoletes: "9998",
|
|
138
|
+
updates: "9997,9996",
|
|
139
|
+
consensus: "true",
|
|
140
|
+
ipr: "trust200902",
|
|
141
|
+
version: "3",
|
|
142
|
+
|
|
143
|
+
front: Rfcxml::V3::Front.new(
|
|
144
|
+
title: Rfcxml::V3::Title.new(
|
|
145
|
+
content: "A Standard Protocol for Example Purposes",
|
|
146
|
+
abbrev: "Example Standard"
|
|
147
|
+
),
|
|
148
|
+
series_info: [
|
|
149
|
+
Rfcxml::V3::SeriesInfo.new(name: "RFC", value: "9999", stream: "IETF")
|
|
150
|
+
],
|
|
151
|
+
author: [
|
|
152
|
+
Rfcxml::V3::Author.new(fullname: "John Smith", surname: "Smith")
|
|
153
|
+
],
|
|
154
|
+
date: Rfcxml::V3::Date.new(year: "2025", month: "March")
|
|
155
|
+
),
|
|
156
|
+
|
|
157
|
+
middle: Rfcxml::V3::Middle.new(
|
|
158
|
+
section: [
|
|
159
|
+
Rfcxml::V3::Section.new(
|
|
160
|
+
name: Rfcxml::V3::Name.new(content: "Overview"),
|
|
161
|
+
t: [
|
|
162
|
+
Rfcxml::V3::Text.new(content: "This document specifies a standard protocol.")
|
|
163
|
+
]
|
|
164
|
+
)
|
|
165
|
+
]
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
xml = rfc.to_xml(pretty: true, declaration: true, encoding: "utf-8")
|
|
44
170
|
----
|
|
45
171
|
|
|
46
|
-
|
|
172
|
+
=== Bibliography-Only Usage
|
|
173
|
+
|
|
174
|
+
If you only need to parse or generate bibliography references (the `<reference>` element),
|
|
175
|
+
you can use `Rfcxml::V3::Reference` directly:
|
|
47
176
|
|
|
48
177
|
[source,ruby]
|
|
49
178
|
----
|
|
50
179
|
require 'rfcxml'
|
|
51
180
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
181
|
+
# Parse a reference from XML
|
|
182
|
+
ref_xml = <<~XML
|
|
183
|
+
<reference anchor="RFC2119">
|
|
184
|
+
<front>
|
|
185
|
+
<title>Key words for use in RFCs to Indicate Requirement Levels</title>
|
|
186
|
+
<author fullname="S. Bradner" surname="Bradner"/>
|
|
187
|
+
<date month="March" year="1997"/>
|
|
188
|
+
</front>
|
|
189
|
+
<seriesInfo name="RFC" value="2119"/>
|
|
190
|
+
</reference>
|
|
191
|
+
XML
|
|
192
|
+
|
|
193
|
+
ref = Rfcxml::V3::Reference.from_xml(ref_xml)
|
|
194
|
+
puts ref.anchor # => "RFC2119"
|
|
195
|
+
puts ref.front.title.content
|
|
196
|
+
|
|
197
|
+
# Create a reference programmatically
|
|
198
|
+
ref = Rfcxml::V3::Reference.new(
|
|
199
|
+
anchor: "RFC8650",
|
|
200
|
+
front: Rfcxml::V3::Front.new(
|
|
201
|
+
title: Rfcxml::V3::Title.new(
|
|
202
|
+
content: "Dynamic Subscription to YANG Events and Datastores over RESTCONF"
|
|
203
|
+
),
|
|
204
|
+
author: [
|
|
205
|
+
Rfcxml::V3::Author.new(fullname: "E. Voit", surname: "Voit")
|
|
206
|
+
],
|
|
207
|
+
date: Rfcxml::V3::Date.new(year: "2019", month: "September"),
|
|
208
|
+
series_info: [
|
|
209
|
+
Rfcxml::V3::SeriesInfo.new(name: "RFC", value: "8650")
|
|
210
|
+
]
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
xml = ref.to_xml(pretty: true)
|
|
215
|
+
----
|
|
216
|
+
|
|
217
|
+
=== Round-trip preservation
|
|
218
|
+
|
|
219
|
+
The library preserves all XML structure during round-trips:
|
|
220
|
+
|
|
221
|
+
[source,ruby]
|
|
222
|
+
----
|
|
223
|
+
require 'rfcxml'
|
|
224
|
+
|
|
225
|
+
# Parse
|
|
226
|
+
input = File.read('rfc.xml')
|
|
227
|
+
rfc = Rfcxml::V3::Rfc.from_xml(input)
|
|
228
|
+
|
|
229
|
+
# Serialize back
|
|
230
|
+
output = rfc.to_xml(pretty: true, declaration: true, encoding: "utf-8")
|
|
231
|
+
|
|
232
|
+
# The output preserves:
|
|
233
|
+
# - xml:lang attribute
|
|
234
|
+
# - xmlns:* namespace declarations (e.g., xmlns:xi for XInclude)
|
|
235
|
+
# - All element structure and content
|
|
236
|
+
----
|
|
237
|
+
|
|
238
|
+
== Document Structure
|
|
239
|
+
|
|
240
|
+
The RFC XML v3 structure maps to these Ruby classes:
|
|
241
|
+
|
|
242
|
+
[cols="2,3"]
|
|
243
|
+
|===
|
|
244
|
+
| Ruby Class | XML Element
|
|
245
|
+
|
|
246
|
+
| `Rfcxml::V3::Rfc` | `<rfc>` (root)
|
|
247
|
+
| `Rfcxml::V3::Front` | `<front>`
|
|
248
|
+
| `Rfcxml::V3::Middle` | `<middle>`
|
|
249
|
+
| `Rfcxml::V3::Back` | `<back>`
|
|
250
|
+
| `Rfcxml::V3::Title` | `<title>`
|
|
251
|
+
| `Rfcxml::V3::Author` | `<author>`
|
|
252
|
+
| `Rfcxml::V3::Section` | `<section>`
|
|
253
|
+
| `Rfcxml::V3::Text` | `<t>` (paragraph)
|
|
254
|
+
| `Rfcxml::V3::Reference` | `<reference>`
|
|
255
|
+
| `Rfcxml::V3::References` | `<references>`
|
|
256
|
+
|===
|
|
257
|
+
|
|
258
|
+
=== Key Differences: Internet-Draft vs Published RFC
|
|
259
|
+
|
|
260
|
+
| Attribute | Internet-Draft | Published RFC |
|
|
261
|
+
|-----------|----------------|---------------|
|
|
262
|
+
| `doc_name` | Required (e.g., `draft-ietf-wg-topic-01`) | Optional |
|
|
263
|
+
| `number` | Not used | Required (e.g., `"8650"`) |
|
|
264
|
+
| `expires_date` | Optional | Not used |
|
|
265
|
+
| `obsoletes` | Not used | Optional |
|
|
266
|
+
| `updates` | Not used | Optional |
|
|
267
|
+
| `series_info` | Optional | Usually present |
|
|
268
|
+
|
|
269
|
+
== Serialization Options
|
|
270
|
+
|
|
271
|
+
The `to_xml` method supports these options:
|
|
272
|
+
|
|
273
|
+
[cols="1,1,3"]
|
|
274
|
+
|===
|
|
275
|
+
| Option | Default | Description
|
|
276
|
+
|
|
277
|
+
| `:pretty` | `false` | Pretty-print XML with indentation
|
|
278
|
+
| `:declaration` | `false` | Include XML declaration
|
|
279
|
+
| `:encoding` | `"utf-8"` | Set encoding in XML declaration
|
|
280
|
+
| `:omit_doctype` | `false` | Omit DOCTYPE declaration
|
|
281
|
+
|===
|
|
282
|
+
|
|
283
|
+
Example:
|
|
284
|
+
|
|
285
|
+
[source,ruby]
|
|
286
|
+
----
|
|
287
|
+
xml = rfc.to_xml(
|
|
288
|
+
pretty: true,
|
|
289
|
+
declaration: true,
|
|
290
|
+
encoding: "utf-8"
|
|
291
|
+
)
|
|
292
|
+
----
|
|
293
|
+
|
|
294
|
+
== Enumeration Validation
|
|
295
|
+
|
|
296
|
+
The library provides built-in validation for all RFC XML v3 enumeration
|
|
297
|
+
attributes.
|
|
59
298
|
|
|
60
|
-
|
|
299
|
+
These are defined in the v3.rnc schema and implemented using lutaml-model's
|
|
300
|
+
`values:` constraint.
|
|
301
|
+
|
|
302
|
+
=== Core Document Attributes
|
|
303
|
+
|
|
304
|
+
|===
|
|
305
|
+
| Attribute | Valid Values | Default
|
|
306
|
+
| `category` | `std`, `bcp`, `exp`, `info`, `historic` | -
|
|
307
|
+
| `consensus` | `no`, `yes`, `false`, `true` | `false`
|
|
308
|
+
| `submissionType` | `IETF`, `IAB`, `IRTF`, `independent`, `editorial` | `IETF`
|
|
309
|
+
| `sortRefs` | `true`, `false` | `false`
|
|
310
|
+
| `symRefs` | `true`, `false` | `true`
|
|
311
|
+
| `tocInclude` | `true`, `false` | `true`
|
|
312
|
+
| `indexInclude` | `true`, `false` | `true`
|
|
313
|
+
|===
|
|
314
|
+
|
|
315
|
+
=== Section Attributes
|
|
316
|
+
|
|
317
|
+
|===
|
|
318
|
+
| Attribute | Valid Values | Default
|
|
319
|
+
| `numbered` | `true`, `false` | `true`
|
|
320
|
+
| `toc` | `include`, `exclude`, `default` | `default`
|
|
321
|
+
| `removeInRFC` | `true`, `false` | `false`
|
|
322
|
+
|===
|
|
323
|
+
|
|
324
|
+
=== Author Attributes
|
|
325
|
+
|
|
326
|
+
|===
|
|
327
|
+
| Attribute | Valid Values | Default
|
|
328
|
+
| `role` | `editor` | -
|
|
329
|
+
|===
|
|
330
|
+
|
|
331
|
+
=== Validation
|
|
332
|
+
|
|
333
|
+
Validation requires an explicit call to `validate` or `validate!`:
|
|
334
|
+
|
|
335
|
+
[source,ruby]
|
|
336
|
+
----
|
|
337
|
+
rfc = Rfcxml::V3::Rfc.new(category: "invalid")
|
|
338
|
+
errors = rfc.validate # => array of validation errors
|
|
339
|
+
rfc.validate! # => raises Lutaml::Model::ValidationError
|
|
340
|
+
----
|
|
341
|
+
|
|
342
|
+
== Development
|
|
343
|
+
|
|
344
|
+
After checking out the repo, run:
|
|
345
|
+
|
|
346
|
+
[source]
|
|
61
347
|
----
|
|
348
|
+
$ bundle install
|
|
349
|
+
$ bundle exec rake
|
|
350
|
+
----
|
|
351
|
+
|
|
352
|
+
This runs tests + linting. To run tests only:
|
|
353
|
+
|
|
354
|
+
[source]
|
|
355
|
+
----
|
|
356
|
+
$ bundle exec rake spec
|
|
357
|
+
----
|
|
358
|
+
|
|
359
|
+
=== Round-Trip Testing
|
|
62
360
|
|
|
361
|
+
The library includes comprehensive round-trip tests that verify XML parsing and
|
|
362
|
+
serialization preserve all document structure. These tests use the
|
|
363
|
+
https://github.com/lutaml/canon[Canon] gem for semantic XML comparison.
|
|
63
364
|
|
|
365
|
+
==== Downloading RFC XML Fixtures
|
|
366
|
+
|
|
367
|
+
To run round-trip tests, you need to download RFC XML fixtures from
|
|
368
|
+
rfc-editor.org:
|
|
369
|
+
|
|
370
|
+
[source]
|
|
371
|
+
----
|
|
372
|
+
$ bundle exec rake rfc:download
|
|
373
|
+
----
|
|
374
|
+
|
|
375
|
+
This downloads and extracts RFC XML files to `spec/fixtures/xmlsource/`. The
|
|
376
|
+
download is automatically excluded from version control via `.gitignore`.
|
|
377
|
+
|
|
378
|
+
To re-download fresh fixtures:
|
|
379
|
+
|
|
380
|
+
[source]
|
|
381
|
+
----
|
|
382
|
+
$ bundle exec rake rfc:redownload
|
|
383
|
+
----
|
|
384
|
+
|
|
385
|
+
To clean downloaded fixtures:
|
|
386
|
+
|
|
387
|
+
[source]
|
|
388
|
+
----
|
|
389
|
+
$ bundle exec rake rfc:clean
|
|
390
|
+
----
|
|
391
|
+
|
|
392
|
+
==== Running Round-Trip Tests
|
|
393
|
+
|
|
394
|
+
Run round-trip tests for all downloaded RFC XML files:
|
|
395
|
+
|
|
396
|
+
[source]
|
|
397
|
+
----
|
|
398
|
+
$ bundle exec ruby scripts/roundtrip_test.rb
|
|
399
|
+
----
|
|
400
|
+
|
|
401
|
+
The test script will:
|
|
402
|
+
|
|
403
|
+
1. Parse each RFC XML file using `Rfcxml::V3::Rfc.from_xml`
|
|
404
|
+
2. Serialize back to XML using `to_xml`
|
|
405
|
+
3. Compare original and serialized XML using Canon with `spec_friendly` profile
|
|
406
|
+
4. Report pass/fail statistics
|
|
407
|
+
|
|
408
|
+
Run round-trip tests for specific files:
|
|
409
|
+
|
|
410
|
+
[source]
|
|
411
|
+
----
|
|
412
|
+
$ bundle exec ruby scripts/roundtrip_test.rb spec/fixtures/xmlsource/rfc8650.xml
|
|
413
|
+
$ bundle exec ruby scripts/roundtrip_test.rb spec/fixtures/xmlsource/rfc8650.xml spec/fixtures/xmlsource/rfc8651.xml
|
|
414
|
+
----
|
|
415
|
+
|
|
416
|
+
Run round-trip tests with custom thread count:
|
|
417
|
+
|
|
418
|
+
[source]
|
|
419
|
+
----
|
|
420
|
+
$ bundle exec ruby scripts/roundtrip_test.rb -- --threads 4
|
|
421
|
+
----
|
|
422
|
+
|
|
423
|
+
Output is saved to `tmp/roundtrip-results-{timestamp}/` with detailed failure reports.
|
|
424
|
+
|
|
425
|
+
== Architecture
|
|
426
|
+
|
|
427
|
+
The library uses lutaml-model for XML serialization:
|
|
428
|
+
|
|
429
|
+
- All elements inherit from `Lutaml::Model::Serializable`
|
|
430
|
+
- Attributes are declared with `attribute` class method
|
|
431
|
+
- XML mapping is defined in `xml do ... end` block
|
|
432
|
+
- Circular references use string class names (e.g., `"Rfcxml::V3::Section"`)
|
|
64
433
|
|
|
65
434
|
== Contributing
|
|
66
435
|
|
|
67
436
|
Bug reports and pull requests are welcome on GitHub at
|
|
68
437
|
https://github.com/metanorma/rfcxml.
|
|
69
438
|
|
|
70
|
-
|
|
71
439
|
== Copyright and license
|
|
72
440
|
|
|
73
441
|
The gem is available as open source under the terms of the BSD 2-clause license.
|