rfcxml 0.3.0 → 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 +86 -20
- data/Gemfile +3 -1
- data/README.adoc +255 -35
- data/Rakefile +55 -0
- data/lib/rfcxml/v3/abstract.rb +2 -1
- data/lib/rfcxml/v3/address.rb +2 -1
- data/lib/rfcxml/v3/annotation.rb +2 -1
- data/lib/rfcxml/v3/area.rb +1 -1
- data/lib/rfcxml/v3/artset.rb +2 -1
- data/lib/rfcxml/v3/artwork.rb +13 -10
- data/lib/rfcxml/v3/aside.rb +2 -1
- data/lib/rfcxml/v3/author.rb +16 -9
- data/lib/rfcxml/v3/back.rb +2 -1
- data/lib/rfcxml/v3/bcp14.rb +1 -1
- data/lib/rfcxml/v3/blockquote.rb +2 -1
- data/lib/rfcxml/v3/boilerplate.rb +2 -1
- data/lib/rfcxml/v3/br.rb +1 -1
- data/lib/rfcxml/v3/c.rb +2 -1
- 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 -1
- data/lib/rfcxml/v3/country.rb +1 -1
- data/lib/rfcxml/v3/cref.rb +2 -1
- data/lib/rfcxml/v3/date.rb +4 -4
- data/lib/rfcxml/v3/dd.rb +2 -1
- data/lib/rfcxml/v3/displayreference.rb +1 -1
- data/lib/rfcxml/v3/dl.rb +2 -1
- data/lib/rfcxml/v3/dt.rb +2 -1
- 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 -11
- data/lib/rfcxml/v3/format.rb +1 -1
- data/lib/rfcxml/v3/front.rb +2 -1
- data/lib/rfcxml/v3/iref.rb +4 -3
- data/lib/rfcxml/v3/keyword.rb +1 -1
- data/lib/rfcxml/v3/li.rb +2 -1
- data/lib/rfcxml/v3/link.rb +1 -1
- data/lib/rfcxml/v3/list.rb +2 -1
- data/lib/rfcxml/v3/middle.rb +2 -1
- data/lib/rfcxml/v3/name.rb +2 -1
- data/lib/rfcxml/v3/note.rb +2 -1
- data/lib/rfcxml/v3/ol.rb +5 -2
- 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 -1
- data/lib/rfcxml/v3/postal_line.rb +1 -1
- data/lib/rfcxml/v3/postamble.rb +2 -1
- data/lib/rfcxml/v3/preamble.rb +2 -1
- data/lib/rfcxml/v3/refcontent.rb +2 -1
- data/lib/rfcxml/v3/reference.rb +7 -4
- data/lib/rfcxml/v3/referencegroup.rb +2 -1
- data/lib/rfcxml/v3/references.rb +2 -1
- data/lib/rfcxml/v3/region.rb +1 -1
- data/lib/rfcxml/v3/relref.rb +1 -1
- data/lib/rfcxml/v3/rfc.rb +60 -12
- data/lib/rfcxml/v3/section.rb +11 -4
- 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 +2 -1
- data/lib/rfcxml/v3/sub.rb +2 -1
- data/lib/rfcxml/v3/sup.rb +2 -1
- data/lib/rfcxml/v3/table.rb +1 -1
- data/lib/rfcxml/v3/tbody.rb +1 -1
- data/lib/rfcxml/v3/td.rb +23 -4
- data/lib/rfcxml/v3/text.rb +2 -1
- data/lib/rfcxml/v3/texttable.rb +12 -6
- data/lib/rfcxml/v3/tfoot.rb +1 -1
- data/lib/rfcxml/v3/th.rb +1 -1
- data/lib/rfcxml/v3/thead.rb +1 -1
- data/lib/rfcxml/v3/title.rb +3 -2
- data/lib/rfcxml/v3/toc.rb +1 -1
- data/lib/rfcxml/v3/tr.rb +3 -1
- data/lib/rfcxml/v3/tt.rb +1 -1
- data/lib/rfcxml/v3/ttcol.rb +4 -2
- data/lib/rfcxml/v3/u.rb +1 -1
- data/lib/rfcxml/v3/ul.rb +10 -4
- 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 -1
- data/lib/rfcxml/version.rb +1 -1
- data/lib/rfcxml.rb +1 -0
- data/scripts/README.md +110 -0
- data/scripts/roundtrip_test.rb +361 -0
- metadata +5 -2
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,42 +1,108 @@
|
|
|
1
1
|
# This configuration was generated by
|
|
2
2
|
# `rubocop --auto-gen-config`
|
|
3
|
-
# on 2026-03-
|
|
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
|
-
|
|
11
|
-
Gemspec/RequireMFA:
|
|
10
|
+
Gemspec/RequiredRubyVersion:
|
|
12
11
|
Exclude:
|
|
13
12
|
- 'rfcxml.gemspec'
|
|
14
13
|
|
|
15
|
-
# Offense count:
|
|
16
|
-
|
|
14
|
+
# Offense count: 22
|
|
15
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
16
|
+
# Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
|
|
17
|
+
# URISchemes: http, https
|
|
18
|
+
Layout/LineLength:
|
|
17
19
|
Exclude:
|
|
18
|
-
- '
|
|
20
|
+
- 'canon_attribute_order_bug.rb'
|
|
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'
|
|
19
62
|
|
|
20
63
|
# Offense count: 1
|
|
21
64
|
# This cop supports safe autocorrection (--autocorrect).
|
|
22
65
|
# Configuration parameters: EnforcedStyle.
|
|
23
|
-
# SupportedStyles:
|
|
24
|
-
|
|
66
|
+
# SupportedStyles: be, be_nil
|
|
67
|
+
RSpec/BeNil:
|
|
25
68
|
Exclude:
|
|
26
|
-
- '
|
|
69
|
+
- 'spec/rfcxml_spec.rb'
|
|
27
70
|
|
|
28
71
|
# Offense count: 1
|
|
29
|
-
#
|
|
30
|
-
|
|
31
|
-
# URISchemes: http, https
|
|
32
|
-
Layout/LineLength:
|
|
72
|
+
# Configuration parameters: IgnoredMetadata.
|
|
73
|
+
RSpec/DescribeClass:
|
|
33
74
|
Exclude:
|
|
34
|
-
- '
|
|
75
|
+
- 'spec/v3/document_creation_spec.rb'
|
|
35
76
|
|
|
36
|
-
# Offense count:
|
|
37
|
-
# This cop supports
|
|
38
|
-
# Configuration parameters: EnforcedStyle,
|
|
39
|
-
# SupportedStyles:
|
|
40
|
-
|
|
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:
|
|
41
82
|
Exclude:
|
|
42
|
-
- '
|
|
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:
|
|
107
|
+
Exclude:
|
|
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 "canon"
|
|
8
|
+
gem "canon", github: "ronaldtse/canon", branch: "fix/empty-attribute-value"
|
|
9
9
|
gem "lutaml-model", github: "lutaml/lutaml-model", ref: "main"
|
|
10
10
|
gem "nokogiri"
|
|
11
11
|
gem "rake", "~> 13.0"
|
|
12
12
|
gem "rspec", "~> 3.0"
|
|
13
13
|
gem "rubocop"
|
|
14
14
|
gem "rubocop-performance"
|
|
15
|
+
gem "rubocop-rake"
|
|
16
|
+
gem "rubocop-rspec"
|
data/README.adoc
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
= RFC XML Ruby library
|
|
2
2
|
|
|
3
3
|
The `rfcxml` library is a parser and generator for RFC XML v3 documents.
|
|
4
|
+
|
|
4
5
|
It provides bidirectional XML serialization using the lutaml-model framework,
|
|
5
6
|
enabling perfect round-trips with namespace preservation.
|
|
6
7
|
|
|
@@ -64,56 +65,153 @@ puts rfc.category # => "std"
|
|
|
64
65
|
puts rfc.submission_type # => "IETF"
|
|
65
66
|
|
|
66
67
|
# Access front matter
|
|
67
|
-
puts rfc.front.title.
|
|
68
|
+
puts rfc.front.title.content
|
|
68
69
|
rfc.front.author.each do |author|
|
|
69
70
|
puts author.fullname
|
|
70
71
|
end
|
|
71
72
|
|
|
72
73
|
# Access sections
|
|
73
74
|
rfc.middle.section.each do |section|
|
|
74
|
-
puts section.name
|
|
75
|
+
puts section.name&.content
|
|
75
76
|
end
|
|
76
77
|
----
|
|
77
78
|
|
|
78
|
-
===
|
|
79
|
+
=== Creating an Internet-Draft
|
|
80
|
+
|
|
81
|
+
Internet-Drafts have a `doc_name` attribute but no `number`:
|
|
79
82
|
|
|
80
83
|
[source,ruby]
|
|
81
84
|
----
|
|
82
85
|
require 'rfcxml'
|
|
83
86
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
+
)
|
|
89
121
|
)
|
|
90
122
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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`:
|
|
129
|
+
|
|
130
|
+
[source,ruby]
|
|
131
|
+
----
|
|
132
|
+
require 'rfcxml'
|
|
101
133
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
+
)
|
|
112
167
|
)
|
|
113
168
|
|
|
114
|
-
# Serialize to XML
|
|
115
169
|
xml = rfc.to_xml(pretty: true, declaration: true, encoding: "utf-8")
|
|
116
|
-
|
|
170
|
+
----
|
|
171
|
+
|
|
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:
|
|
176
|
+
|
|
177
|
+
[source,ruby]
|
|
178
|
+
----
|
|
179
|
+
require 'rfcxml'
|
|
180
|
+
|
|
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)
|
|
117
215
|
----
|
|
118
216
|
|
|
119
217
|
=== Round-trip preservation
|
|
@@ -152,14 +250,22 @@ The RFC XML v3 structure maps to these Ruby classes:
|
|
|
152
250
|
| `Rfcxml::V3::Title` | `<title>`
|
|
153
251
|
| `Rfcxml::V3::Author` | `<author>`
|
|
154
252
|
| `Rfcxml::V3::Section` | `<section>`
|
|
155
|
-
| `Rfcxml::V3::
|
|
156
|
-
| `Rfcxml::V3::List` | `<ul>`, `<ol>`, `<dl>`
|
|
157
|
-
| `Rfcxml::V3::Figure` | `<figure>`
|
|
158
|
-
| `Rfcxml::V3::Table` | `<table>`
|
|
159
|
-
| `Rfcxml::V3::References` | `<references>`
|
|
253
|
+
| `Rfcxml::V3::Text` | `<t>` (paragraph)
|
|
160
254
|
| `Rfcxml::V3::Reference` | `<reference>`
|
|
255
|
+
| `Rfcxml::V3::References` | `<references>`
|
|
161
256
|
|===
|
|
162
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
|
+
|
|
163
269
|
== Serialization Options
|
|
164
270
|
|
|
165
271
|
The `to_xml` method supports these options:
|
|
@@ -185,6 +291,54 @@ xml = rfc.to_xml(
|
|
|
185
291
|
)
|
|
186
292
|
----
|
|
187
293
|
|
|
294
|
+
== Enumeration Validation
|
|
295
|
+
|
|
296
|
+
The library provides built-in validation for all RFC XML v3 enumeration
|
|
297
|
+
attributes.
|
|
298
|
+
|
|
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
|
+
|
|
188
342
|
== Development
|
|
189
343
|
|
|
190
344
|
After checking out the repo, run:
|
|
@@ -202,6 +356,72 @@ This runs tests + linting. To run tests only:
|
|
|
202
356
|
$ bundle exec rake spec
|
|
203
357
|
----
|
|
204
358
|
|
|
359
|
+
=== Round-Trip Testing
|
|
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.
|
|
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
|
+
|
|
205
425
|
== Architecture
|
|
206
426
|
|
|
207
427
|
The library uses lutaml-model for XML serialization:
|
data/Rakefile
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
require "bundler/gem_tasks"
|
|
4
4
|
require "rspec/core/rake_task"
|
|
5
|
+
require "open-uri"
|
|
6
|
+
require "rubygems/package"
|
|
7
|
+
require "zlib"
|
|
8
|
+
require "fileutils"
|
|
5
9
|
|
|
6
10
|
RSpec::Core::RakeTask.new(:spec)
|
|
7
11
|
|
|
@@ -10,3 +14,54 @@ require "rubocop/rake_task"
|
|
|
10
14
|
RuboCop::RakeTask.new
|
|
11
15
|
|
|
12
16
|
task default: %i[spec rubocop]
|
|
17
|
+
|
|
18
|
+
RFC_XML_URL = "https://www.rfc-editor.org/in-notes/tar/xmlsource-all.tar.gz"
|
|
19
|
+
RFC_XML_DIR = File.expand_path("spec/fixtures/xmlsource", __dir__)
|
|
20
|
+
RFC_XML_TAR = File.expand_path("tmp/xmlsource-all.tar.gz", __dir__)
|
|
21
|
+
|
|
22
|
+
namespace :rfc do
|
|
23
|
+
desc "Download and extract RFC XML files from rfc-editor.org"
|
|
24
|
+
task :download do
|
|
25
|
+
FileUtils.mkdir_p(RFC_XML_DIR)
|
|
26
|
+
FileUtils.mkdir_p(File.dirname(RFC_XML_TAR))
|
|
27
|
+
|
|
28
|
+
# Download tarball if not exists
|
|
29
|
+
unless File.exist?(RFC_XML_TAR)
|
|
30
|
+
puts "Downloading RFC XML tarball from #{RFC_XML_URL}..."
|
|
31
|
+
URI.open(RFC_XML_URL) do |remote|
|
|
32
|
+
File.binwrite(RFC_XML_TAR, remote.read)
|
|
33
|
+
end
|
|
34
|
+
puts "Downloaded to #{RFC_XML_TAR}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Extract tarball
|
|
38
|
+
puts "Extracting to #{RFC_XML_DIR}..."
|
|
39
|
+
tar_extract = Gem::Package::TarReader.new(Zlib::GzipReader.open(RFC_XML_TAR))
|
|
40
|
+
tar_extract.rewind
|
|
41
|
+
tar_extract.each do |entry|
|
|
42
|
+
next unless entry.file? && entry.full_name.end_with?(".xml")
|
|
43
|
+
|
|
44
|
+
# Extract only the filename, flatten directory structure
|
|
45
|
+
filename = File.basename(entry.full_name)
|
|
46
|
+
target_path = File.join(RFC_XML_DIR, filename)
|
|
47
|
+
|
|
48
|
+
File.binwrite(target_path, entry.read)
|
|
49
|
+
end
|
|
50
|
+
tar_extract.close
|
|
51
|
+
|
|
52
|
+
xml_count = Dir.glob(File.join(RFC_XML_DIR, "*.xml")).count
|
|
53
|
+
puts "Done! Extracted #{xml_count} XML files to #{RFC_XML_DIR}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
desc "Clean downloaded RFC XML files"
|
|
57
|
+
task :clean do
|
|
58
|
+
puts "Removing #{RFC_XML_DIR}..."
|
|
59
|
+
FileUtils.rm_rf(RFC_XML_DIR)
|
|
60
|
+
puts "Removing #{RFC_XML_TAR}..."
|
|
61
|
+
FileUtils.rm_f(RFC_XML_TAR)
|
|
62
|
+
puts "Done!"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
desc "Re-download RFC XML files (clean + download)"
|
|
66
|
+
task redownload: %i[clean download]
|
|
67
|
+
end
|