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.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/roundtrip.yml +79 -0
  3. data/.gitignore +3 -0
  4. data/.rubocop.yml +9 -3
  5. data/.rubocop_todo.yml +87 -17
  6. data/Gemfile +4 -2
  7. data/README.adoc +386 -18
  8. data/Rakefile +55 -0
  9. data/lib/rfcxml/v3/abstract.rb +2 -6
  10. data/lib/rfcxml/v3/address.rb +2 -7
  11. data/lib/rfcxml/v3/annotation.rb +2 -15
  12. data/lib/rfcxml/v3/area.rb +1 -1
  13. data/lib/rfcxml/v3/artset.rb +2 -3
  14. data/lib/rfcxml/v3/artwork.rb +13 -10
  15. data/lib/rfcxml/v3/aside.rb +2 -12
  16. data/lib/rfcxml/v3/author.rb +16 -12
  17. data/lib/rfcxml/v3/back.rb +2 -5
  18. data/lib/rfcxml/v3/bcp14.rb +1 -1
  19. data/lib/rfcxml/v3/blockquote.rb +2 -26
  20. data/lib/rfcxml/v3/boilerplate.rb +2 -3
  21. data/lib/rfcxml/v3/br.rb +1 -1
  22. data/lib/rfcxml/v3/c.rb +2 -7
  23. data/lib/rfcxml/v3/city.rb +1 -1
  24. data/lib/rfcxml/v3/cityarea.rb +1 -1
  25. data/lib/rfcxml/v3/code.rb +1 -1
  26. data/lib/rfcxml/v3/contact.rb +2 -4
  27. data/lib/rfcxml/v3/country.rb +1 -1
  28. data/lib/rfcxml/v3/cref.rb +2 -11
  29. data/lib/rfcxml/v3/date.rb +4 -4
  30. data/lib/rfcxml/v3/dd.rb +2 -25
  31. data/lib/rfcxml/v3/displayreference.rb +1 -1
  32. data/lib/rfcxml/v3/dl.rb +2 -7
  33. data/lib/rfcxml/v3/dt.rb +2 -14
  34. data/lib/rfcxml/v3/email.rb +1 -1
  35. data/lib/rfcxml/v3/eref.rb +1 -1
  36. data/lib/rfcxml/v3/extaddr.rb +1 -1
  37. data/lib/rfcxml/v3/facsimile.rb +1 -1
  38. data/lib/rfcxml/v3/figure.rb +17 -19
  39. data/lib/rfcxml/v3/format.rb +1 -1
  40. data/lib/rfcxml/v3/front.rb +2 -13
  41. data/lib/rfcxml/v3/iref.rb +4 -3
  42. data/lib/rfcxml/v3/keyword.rb +1 -1
  43. data/lib/rfcxml/v3/li.rb +2 -28
  44. data/lib/rfcxml/v3/link.rb +1 -1
  45. data/lib/rfcxml/v3/list.rb +3 -4
  46. data/lib/rfcxml/v3/middle.rb +2 -3
  47. data/lib/rfcxml/v3/name.rb +2 -14
  48. data/lib/rfcxml/v3/note.rb +2 -7
  49. data/lib/rfcxml/v3/ol.rb +5 -4
  50. data/lib/rfcxml/v3/organization.rb +10 -5
  51. data/lib/rfcxml/v3/phone.rb +1 -1
  52. data/lib/rfcxml/v3/pobox.rb +1 -1
  53. data/lib/rfcxml/v3/postal.rb +2 -12
  54. data/lib/rfcxml/v3/postal_line.rb +1 -1
  55. data/lib/rfcxml/v3/postamble.rb +2 -7
  56. data/lib/rfcxml/v3/preamble.rb +2 -15
  57. data/lib/rfcxml/v3/refcontent.rb +2 -4
  58. data/lib/rfcxml/v3/reference.rb +7 -10
  59. data/lib/rfcxml/v3/referencegroup.rb +2 -3
  60. data/lib/rfcxml/v3/references.rb +2 -5
  61. data/lib/rfcxml/v3/region.rb +1 -1
  62. data/lib/rfcxml/v3/relref.rb +1 -1
  63. data/lib/rfcxml/v3/rfc.rb +62 -14
  64. data/lib/rfcxml/v3/section.rb +11 -21
  65. data/lib/rfcxml/v3/series_info.rb +5 -4
  66. data/lib/rfcxml/v3/sortingcode.rb +1 -1
  67. data/lib/rfcxml/v3/sourcecode.rb +13 -9
  68. data/lib/rfcxml/v3/spanx.rb +1 -1
  69. data/lib/rfcxml/v3/street.rb +1 -1
  70. data/lib/rfcxml/v3/strong.rb +6 -8
  71. data/lib/rfcxml/v3/sub.rb +6 -15
  72. data/lib/rfcxml/v3/sup.rb +2 -3
  73. data/lib/rfcxml/v3/table.rb +1 -7
  74. data/lib/rfcxml/v3/tbody.rb +1 -3
  75. data/lib/rfcxml/v3/td.rb +23 -26
  76. data/lib/rfcxml/v3/text.rb +2 -20
  77. data/lib/rfcxml/v3/texttable.rb +12 -12
  78. data/lib/rfcxml/v3/tfoot.rb +1 -3
  79. data/lib/rfcxml/v3/th.rb +1 -3
  80. data/lib/rfcxml/v3/thead.rb +1 -3
  81. data/lib/rfcxml/v3/title.rb +3 -4
  82. data/lib/rfcxml/v3/toc.rb +1 -3
  83. data/lib/rfcxml/v3/tr.rb +3 -4
  84. data/lib/rfcxml/v3/tt.rb +5 -19
  85. data/lib/rfcxml/v3/ttcol.rb +4 -7
  86. data/lib/rfcxml/v3/u.rb +1 -1
  87. data/lib/rfcxml/v3/ul.rb +10 -6
  88. data/lib/rfcxml/v3/uri.rb +1 -1
  89. data/lib/rfcxml/v3/vspace.rb +1 -1
  90. data/lib/rfcxml/v3/workgroup.rb +1 -1
  91. data/lib/rfcxml/v3/xref.rb +2 -3
  92. data/lib/rfcxml/v3/xref_text.rb +7 -16
  93. data/lib/rfcxml/v3.rb +100 -2
  94. data/lib/rfcxml/version.rb +1 -1
  95. data/lib/rfcxml.rb +3 -4
  96. data/rfcxml.gemspec +3 -2
  97. data/scripts/README.md +110 -0
  98. data/scripts/roundtrip_test.rb +361 -0
  99. metadata +9 -6
  100. data/lib/rfcxml/v3/em.rb +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5b11cbfa392f124f60b2e407491746679edd9adce04e41efc4a93e57dc00b958
4
- data.tar.gz: a613b687d3808dc83d7b75840129b2e594041e7a8c811adda836aa0b547af501
3
+ metadata.gz: d78bc97fa8b73803d80d88b9e3d69d3ab98ea34b481d45b4844b685943cb518e
4
+ data.tar.gz: 144d9c7c64dc113af0764099683f33bc8eb9f3f3e6ae70077ae51e43d2e2891b
5
5
  SHA512:
6
- metadata.gz: 5f499a434f14334a0a4a85c036426e4ff4444007393fbe5815a07c7f73e0e504df84d43ade669bebd7aa8dd89922c8177fd0fd1c985dec9a05c031648533c16c
7
- data.tar.gz: 2e36d85702ec42c68e4c5b4fd0d9393e59b0ad6baef6952e299b0b53bb934b5f647242a2baefce7b3f3380b7cdbef073cca6740b0f4fda2acb9387c45ab5515b
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
@@ -7,5 +7,8 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
 
10
+ # Downloaded RFC XML files
11
+ /spec/fixtures/xmlsource/
12
+
10
13
  # rspec failure tracking
11
14
  .rspec_status
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
- - https://raw.githubusercontent.com/riboseinc/oss-guides/master/ci/rubocop.yml
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 2025-01-11 09:55:13 UTC using RuboCop version 1.70.0.
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: 1
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, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
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
- - 'lib/rfcxml/v3/dt.rb'
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: AllowInHeredoc.
36
- Layout/TrailingWhitespace:
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
- - 'lib/rfcxml/v3/dt.rb'
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 "equivalent-xml"
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 "xml-c14n"
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. It is
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
- == Usage
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
- To parse an RFC XML document:
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::Document.new(File.read('rfc.xml'))
41
- puts rfc.title
42
- puts rfc.abstract
43
- puts rfc.sections
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
- To generate an RFC XML document:
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
- rfc = Rfcxml::Document.new
53
- rfc.title = 'An RFC Title'
54
- rfc.abstract = 'An RFC Abstract'
55
- rfc.sections = [
56
- Rfcxml::Section.new('Section 1', 'This is the first section'),
57
- Rfcxml::Section.new('Section 2', 'This is the second section')
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
- puts rfc.to_xml
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.