newsmlg2 0.1.1

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 (127) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/docs.yml +63 -0
  3. data/.github/workflows/links.yml +99 -0
  4. data/.github/workflows/performance.yml +25 -0
  5. data/.github/workflows/rake.yml +19 -0
  6. data/.github/workflows/release.yml +34 -0
  7. data/.gitignore +17 -0
  8. data/.rubocop.yml +51 -0
  9. data/CHANGELOG.md +31 -0
  10. data/CLAUDE.md +77 -0
  11. data/Gemfile +15 -0
  12. data/LICENSE +24 -0
  13. data/README.adoc +135 -0
  14. data/Rakefile +15 -0
  15. data/docs/Gemfile +6 -0
  16. data/docs/_config.yml +4 -0
  17. data/docs/index.md +15 -0
  18. data/docs/lychee.toml +4 -0
  19. data/docs/usage.md +36 -0
  20. data/lib/newsmlg2/base/administrative_metadata_group.rb +40 -0
  21. data/lib/newsmlg2/base/arbitrary_value_attributes.rb +13 -0
  22. data/lib/newsmlg2/base/authority_attributes.rb +14 -0
  23. data/lib/newsmlg2/base/common_power_attributes.rb +17 -0
  24. data/lib/newsmlg2/base/concept_definition_group.rb +25 -0
  25. data/lib/newsmlg2/base/concept_relationships_group.rb +22 -0
  26. data/lib/newsmlg2/base/confirmation_status_attributes.rb +13 -0
  27. data/lib/newsmlg2/base/deprecated_link_attributes.rb +14 -0
  28. data/lib/newsmlg2/base/descriptive_metadata_group.rb +54 -0
  29. data/lib/newsmlg2/base/entity_details_group.rb +28 -0
  30. data/lib/newsmlg2/base/flex_attributes.rb +13 -0
  31. data/lib/newsmlg2/base/i18n_attributes.rb +14 -0
  32. data/lib/newsmlg2/base/item_management_group.rb +59 -0
  33. data/lib/newsmlg2/base/media_content_characteristics1.rb +22 -0
  34. data/lib/newsmlg2/base/news_content_attributes.rb +18 -0
  35. data/lib/newsmlg2/base/news_content_characteristics.rb +30 -0
  36. data/lib/newsmlg2/base/news_content_type_attributes.rb +17 -0
  37. data/lib/newsmlg2/base/persistent_edit_attributes.rb +13 -0
  38. data/lib/newsmlg2/base/qualifying_attributes.rb +13 -0
  39. data/lib/newsmlg2/base/quantify_attributes.rb +13 -0
  40. data/lib/newsmlg2/base/ranking_attributes.rb +13 -0
  41. data/lib/newsmlg2/base/recurrence_rule_attributes.rb +18 -0
  42. data/lib/newsmlg2/base/target_resource_attributes.rb +20 -0
  43. data/lib/newsmlg2/base/time_validity_attributes.rb +13 -0
  44. data/lib/newsmlg2/base.rb +35 -0
  45. data/lib/newsmlg2/builder/node.rb +125 -0
  46. data/lib/newsmlg2/builder.rb +69 -0
  47. data/lib/newsmlg2/catalog.rb +38 -0
  48. data/lib/newsmlg2/catalog_cache.rb +49 -0
  49. data/lib/newsmlg2/catalog_ref.rb +13 -0
  50. data/lib/newsmlg2/catalog_store.rb +66 -0
  51. data/lib/newsmlg2/catalogs/catalog.IPTC-G2-Standards_32.xml +620 -0
  52. data/lib/newsmlg2/catalogs/catalog.IPTC-G2-Standards_33.xml +625 -0
  53. data/lib/newsmlg2/catalogs/catalog.IPTC-G2-Standards_34.xml +626 -0
  54. data/lib/newsmlg2/catalogs/catalog.IPTC-G2-Standards_35.xml +635 -0
  55. data/lib/newsmlg2/catalogs/catalog.IPTC-G2-Standards_36.xml +644 -0
  56. data/lib/newsmlg2/catalogs/catalog.IPTC-G2-Standards_37.xml +661 -0
  57. data/lib/newsmlg2/catalogs/catalog.IPTC-G2-Standards_38.xml +674 -0
  58. data/lib/newsmlg2/catalogs/catalog.IPTC-G2-Standards_39.xml +682 -0
  59. data/lib/newsmlg2/catalogs/catalog.IPTC-G2-Standards_40.xml +724 -0
  60. data/lib/newsmlg2/catalogs/catalog.IPTC-G2-Standards_41.xml +729 -0
  61. data/lib/newsmlg2/configuration.rb +61 -0
  62. data/lib/newsmlg2/document.rb +123 -0
  63. data/lib/newsmlg2/errors.rb +18 -0
  64. data/lib/newsmlg2/i18n.rb +20 -0
  65. data/lib/newsmlg2/items/any_item.rb +180 -0
  66. data/lib/newsmlg2/items/catalog_item.rb +24 -0
  67. data/lib/newsmlg2/items/concept_item.rb +23 -0
  68. data/lib/newsmlg2/items/content_meta.rb +73 -0
  69. data/lib/newsmlg2/items/item_ref.rb +13 -0
  70. data/lib/newsmlg2/items/knowledge_item.rb +68 -0
  71. data/lib/newsmlg2/items/news_item.rb +130 -0
  72. data/lib/newsmlg2/items/news_message.rb +142 -0
  73. data/lib/newsmlg2/items/package_item.rb +82 -0
  74. data/lib/newsmlg2/items/part_meta.rb +68 -0
  75. data/lib/newsmlg2/items/planning_item.rb +160 -0
  76. data/lib/newsmlg2/nar_model.rb +76 -0
  77. data/lib/newsmlg2/nar_namespace.rb +10 -0
  78. data/lib/newsmlg2/nitf_namespace.rb +9 -0
  79. data/lib/newsmlg2/same_as_scheme.rb +15 -0
  80. data/lib/newsmlg2/scheme.rb +30 -0
  81. data/lib/newsmlg2/types/approximate_date_time_prop_type.rb +14 -0
  82. data/lib/newsmlg2/types/bag.rb +16 -0
  83. data/lib/newsmlg2/types/block_types.rb +62 -0
  84. data/lib/newsmlg2/types/concept.rb +36 -0
  85. data/lib/newsmlg2/types/concept_id.rb +17 -0
  86. data/lib/newsmlg2/types/concept_name_type.rb +17 -0
  87. data/lib/newsmlg2/types/content_meta_types.rb +152 -0
  88. data/lib/newsmlg2/types/date_opt_time_prop_type.rb +12 -0
  89. data/lib/newsmlg2/types/date_time_or_null_prop_type.rb +12 -0
  90. data/lib/newsmlg2/types/date_time_prop_type.rb +12 -0
  91. data/lib/newsmlg2/types/event_details.rb +213 -0
  92. data/lib/newsmlg2/types/ext_property.rb +21 -0
  93. data/lib/newsmlg2/types/facet.rb +17 -0
  94. data/lib/newsmlg2/types/flex1_prop_type.rb +32 -0
  95. data/lib/newsmlg2/types/flex_location_prop_type.rb +43 -0
  96. data/lib/newsmlg2/types/flex_party_prop_type.rb +49 -0
  97. data/lib/newsmlg2/types/flex_prop2_type.rb +26 -0
  98. data/lib/newsmlg2/types/flex_prop_type.rb +17 -0
  99. data/lib/newsmlg2/types/geo.rb +92 -0
  100. data/lib/newsmlg2/types/hierarchy_info.rb +17 -0
  101. data/lib/newsmlg2/types/ids.rb +28 -0
  102. data/lib/newsmlg2/types/inline_types.rb +56 -0
  103. data/lib/newsmlg2/types/intl_string_type.rb +17 -0
  104. data/lib/newsmlg2/types/intl_string_type2.rb +14 -0
  105. data/lib/newsmlg2/types/item_management.rb +74 -0
  106. data/lib/newsmlg2/types/link.rb +40 -0
  107. data/lib/newsmlg2/types/object_details.rb +40 -0
  108. data/lib/newsmlg2/types/party_details.rb +258 -0
  109. data/lib/newsmlg2/types/poi.rb +79 -0
  110. data/lib/newsmlg2/types/qcode_prop_type.rb +12 -0
  111. data/lib/newsmlg2/types/qual_prop_type.rb +23 -0
  112. data/lib/newsmlg2/types/qual_rel_prop_type.rb +18 -0
  113. data/lib/newsmlg2/types/related_concept.rb +71 -0
  114. data/lib/newsmlg2/types/rights.rb +84 -0
  115. data/lib/newsmlg2/types/ruby_annotation.rb +55 -0
  116. data/lib/newsmlg2/types/same_as.rb +17 -0
  117. data/lib/newsmlg2/types/truncated_date_time_prop_type.rb +13 -0
  118. data/lib/newsmlg2/types/versioned_string_type.rb +10 -0
  119. data/lib/newsmlg2/types/xml_base.rb +12 -0
  120. data/lib/newsmlg2/types/xml_lang.rb +12 -0
  121. data/lib/newsmlg2/types.rb +166 -0
  122. data/lib/newsmlg2/utils.rb +42 -0
  123. data/lib/newsmlg2/version.rb +5 -0
  124. data/lib/newsmlg2.rb +181 -0
  125. data/lib/tasks/performance.rake +72 -0
  126. data/newsmlg2.gemspec +34 -0
  127. metadata +185 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 556c768538613353dfff0fcadf54e6b11a810d7aa6fce89b1182204d6135f471
4
+ data.tar.gz: f8a212eb59e47d8e0118e54472d03589fb6f471df17750d686a258576f1b86e1
5
+ SHA512:
6
+ metadata.gz: 2df4c64419fce1237b3a91228b9bf04f8b8cc612fcee7c3d97a445370ce9287eb00b59f069df15a7eca02940b2bc9b60ff1afaae0038b5b3f258daab8329232a
7
+ data.tar.gz: d02c09c2b4c808bb5bc62769f7a9471da7ef2316a0544f3ed392b2babbd65007453dbefab2934c7eeb132b00eb47350e055696450892ea159e620e3591edcee6
@@ -0,0 +1,63 @@
1
+ name: docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - 'docs/**'
8
+ pull_request:
9
+ paths:
10
+ - 'docs/**'
11
+ repository_dispatch:
12
+ workflow_dispatch:
13
+
14
+ permissions:
15
+ contents: read
16
+ pages: write
17
+ id-token: write
18
+
19
+ concurrency:
20
+ group: ${{ github.workflow }}-${{ github.ref }}
21
+ cancel-in-progress: false
22
+
23
+ jobs:
24
+ build:
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - name: Checkout
28
+ uses: actions/checkout@v6
29
+
30
+ - name: Setup Ruby
31
+ uses: ruby/setup-ruby@v1
32
+ with:
33
+ ruby-version: '3.3'
34
+ bundler-cache: true
35
+ cache-version: 0
36
+ working-directory: docs
37
+
38
+ - name: Setup Pages
39
+ id: pages
40
+ uses: actions/configure-pages@v5
41
+
42
+ - name: Build with Jekyll
43
+ run: bundle exec jekyll build --verbose --trace --baseurl "${{ steps.pages.outputs.base_path }}"
44
+ working-directory: docs
45
+ env:
46
+ JEKYLL_ENV: production
47
+
48
+ - name: Upload artifact
49
+ uses: actions/upload-pages-artifact@v4
50
+ with:
51
+ path: docs/_site
52
+
53
+ deploy:
54
+ environment:
55
+ name: github-pages
56
+ url: ${{ steps.deployment.outputs.page_url }}
57
+ if: ${{ github.ref == 'refs/heads/main' }}
58
+ runs-on: ubuntu-latest
59
+ needs: build
60
+ steps:
61
+ - name: Deploy to GitHub Pages
62
+ id: deployment
63
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,99 @@
1
+ name: links
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ paths:
8
+ - 'docs/**'
9
+ pull_request:
10
+ paths:
11
+ - 'docs/**'
12
+
13
+ # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
14
+ permissions:
15
+ contents: read
16
+ pull-requests: write
17
+
18
+ jobs:
19
+ link_checker:
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v6
23
+
24
+ - uses: ruby/setup-ruby@v1
25
+ with:
26
+ ruby-version: '3.4'
27
+ bundler-cache: true
28
+ working-directory: docs
29
+
30
+ - name: Build site
31
+ env:
32
+ JEKYLL_ENV: production
33
+ run: bundle exec jekyll build --trace
34
+ working-directory: docs
35
+
36
+ - name: Restore lychee cache
37
+ uses: actions/cache@v4
38
+ with:
39
+ path: .lycheecache
40
+ key: cache-lychee-${{ github.sha }}
41
+ restore-keys: cache-lychee-
42
+
43
+ - name: Check if site was built
44
+ run: |
45
+ if [ ! -d "_site" ]; then
46
+ echo "Error: _site directory not created"
47
+ exit 1
48
+ fi
49
+ echo "Site built successfully"
50
+ ls -la _site/
51
+ working-directory: docs
52
+
53
+ - name: Link Checker (Built Site)
54
+ uses: lycheeverse/lychee-action@v2
55
+ with:
56
+ # Check the built HTML site for rendered links
57
+ # Only check https and http schemes, exclude file:// due to path resolution issues
58
+ args: >-
59
+ --verbose
60
+ --no-progress
61
+ --config lychee.toml
62
+ --root-dir "$(pwd)/_site"
63
+ --scheme https,http
64
+ '_site/**/*.html'
65
+ fail: true
66
+ output: link-check-results.md
67
+ format: markdown
68
+ workingDirectory: docs
69
+
70
+ - name: Upload link check results
71
+ if: always()
72
+ uses: actions/upload-artifact@v4
73
+ with:
74
+ name: link-check-results
75
+ path: |
76
+ docs/link-check-results.md
77
+ retention-days: 30
78
+
79
+ - name: Comment PR with results
80
+ if: failure() && github.event_name == 'pull_request'
81
+ uses: actions/github-script@v7
82
+ with:
83
+ script: |
84
+ const fs = require('fs');
85
+ let comment = '## 🔗 Link Check Failed\n\n';
86
+
87
+ if (fs.existsSync('docs/link-check-results.md')) {
88
+ const results = fs.readFileSync('docs/link-check-results.md', 'utf8');
89
+ comment += '### Built Site Results\n\n' + results + '\n\n';
90
+ }
91
+
92
+ comment += '\n---\n\n*Please fix the broken links and push a new commit.*';
93
+
94
+ github.rest.issues.createComment({
95
+ issue_number: context.issue.number,
96
+ owner: context.repo.owner,
97
+ repo: context.repo.repo,
98
+ body: comment
99
+ });
@@ -0,0 +1,25 @@
1
+ name: performance
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main]
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ performance:
13
+ runs-on: ubuntu-latest
14
+ timeout-minutes: 15
15
+ steps:
16
+ - uses: actions/checkout@v6
17
+
18
+ - name: Set up Ruby
19
+ uses: ruby/setup-ruby@v1
20
+ with:
21
+ ruby-version: 3.4
22
+ bundler-cache: true
23
+
24
+ - name: Run performance benchmarks
25
+ run: bundle exec rake performance:compare
@@ -0,0 +1,19 @@
1
+ # Auto-generated by Cimas: Do not edit it manually!
2
+ # See https://github.com/metanorma/cimas
3
+ name: rake
4
+
5
+ permissions:
6
+ contents: write
7
+ packages: write
8
+
9
+ on:
10
+ push:
11
+ branches: [ master, main ]
12
+ tags: [ v* ]
13
+ pull_request:
14
+
15
+ jobs:
16
+ rake:
17
+ uses: metanorma/ci/.github/workflows/generic-rake.yml@main
18
+ secrets:
19
+ pat_token: ${{ secrets.LUTAML_CI_PAT_TOKEN }}
@@ -0,0 +1,34 @@
1
+ # Auto-generated by Cimas: Do not edit it manually!
2
+ # See https://github.com/metanorma/cimas
3
+ name: release
4
+
5
+ permissions:
6
+ contents: write
7
+ packages: write
8
+ id-token: write
9
+
10
+ on:
11
+ workflow_dispatch:
12
+ inputs:
13
+ next_version:
14
+ description: |
15
+ Next release version. Possible values: x.y.z, major, minor, patch (or pre|rc|etc).
16
+ Also, you can pass 'skip' to skip 'git tag' and do 'gem push' for the current version
17
+ required: true
18
+ default: 'skip'
19
+ acknowledge_breaking_in_patch:
20
+ description: 'Override the patch-release breaking-change guard when the trip is a known false-positive'
21
+ required: false
22
+ type: boolean
23
+ default: false
24
+ repository_dispatch:
25
+ types: [ do-release ]
26
+
27
+ jobs:
28
+ release:
29
+ uses: metanorma/ci/.github/workflows/rubygems-release.yml@main
30
+ with:
31
+ next_version: ${{ github.event.inputs.next_version }}
32
+ acknowledge_breaking_in_patch: ${{ inputs.acknowledge_breaking_in_patch }}
33
+ secrets:
34
+ rubygems-api-key: ${{ secrets.LUTAML_CI_RUBYGEMS_API_KEY }}
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /spec/examples.txt
10
+ /tmp/
11
+ .rspec_status
12
+ /docs/_site/
13
+ /docs/.jekyll-cache/
14
+ /docs/.jekyll-metadata
15
+ /docs/Gemfile.lock
16
+ /docs/vendor/
17
+ /TODO.impl/
data/.rubocop.yml ADDED
@@ -0,0 +1,51 @@
1
+ require:
2
+ - rubocop
3
+
4
+ AllCops:
5
+ NewCops: enable
6
+ TargetRubyVersion: 3.3
7
+ Exclude:
8
+ - "pkg/**/*"
9
+ - "vendor/**/*"
10
+ - "coverage/**/*"
11
+ - "reference-docs/**/*"
12
+ - "spec/fixtures/**/*"
13
+ - "data/**/*"
14
+
15
+ Metrics/BlockLength:
16
+ Exclude:
17
+ - "spec/**/*"
18
+ - "*.gemspec"
19
+ - "lib/newsmlg2/base/**/*"
20
+ - "lib/tasks/*.rake"
21
+
22
+ Metrics/MethodLength:
23
+ Exclude:
24
+ - "lib/newsmlg2/base/**/*"
25
+ - "lib/newsmlg2/builder/**/*"
26
+
27
+ Metrics/AbcSize:
28
+ Exclude:
29
+ - "lib/newsmlg2/base/**/*"
30
+ - "lib/newsmlg2/builder/**/*"
31
+
32
+ Metrics/ModuleLength:
33
+ Exclude:
34
+ - "lib/newsmlg2/base/**/*"
35
+ - "lib/newsmlg2.rb"
36
+ - "lib/newsmlg2/types.rb"
37
+ - "lib/newsmlg2/base.rb"
38
+ - "spec/support/**/*"
39
+
40
+ Layout/LineLength:
41
+ Exclude:
42
+ - "lib/newsmlg2/base/**/*"
43
+ - "spec/**/*"
44
+
45
+ Lint/ConstantDefinitionInBlock:
46
+ Exclude:
47
+ - "spec/**/*"
48
+ - "lib/tasks/*.rake"
49
+
50
+ Style/Documentation:
51
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,31 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
+
7
+ ## [0.1.1] - 2026-08-30
8
+
9
+ ### Fixed
10
+
11
+ - `Newsmlg2.parse` now works on a fresh install with no XML adapter
12
+ configured: when the configured adapter cannot be resolved, the
13
+ stdlib REXML adapter is used instead of raising.
14
+ - Renamed `InlineData#encoding` to `#content_encoding` (wire name
15
+ unchanged) to silence a lutaml-model override warning at load time.
16
+
17
+ ## [0.1.0] - 2026-08-30
18
+
19
+ ### Added
20
+
21
+ - Initial release: a lutaml-model-based Ruby object model for IPTC
22
+ NewsML-G2 (NAR) targeting specification version 2.35 (power conformance).
23
+ - Typed parsing and serialization of all seven item types plus
24
+ `newsMessage`, with per-document catalog stores and bundled IPTC
25
+ catalogs (v32–v41) for offline qcode ⇄ URI resolution.
26
+ - Reflection-driven builder DSL (`Newsmlg2.build_news_item`, …) generated
27
+ from the model metadata.
28
+ - Full port of the python-newsmlg2 test suite.
29
+ - Compliance fixtures: the 35 official IPTC example documents and the
30
+ official IPTC XSD validation unit-test suite (161 files across schema
31
+ versions 2.9–2.35).
data/CLAUDE.md ADDED
@@ -0,0 +1,77 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project
6
+
7
+ `newsmlg2` — a lutaml-model-based Ruby object model for IPTC **NewsML-G2** (News Architecture, NAR): parse, manipulate, build (DSL), and serialize NewsML-G2 XML with round-trip fidelity. Targets spec version **2.35**, **power** conformance. Namespace: `Newsmlg2`.
8
+
9
+ Implementation was driven by the ordered plan files in **`TODO.impl/`** (`01-…` through `14-…`; kept locally only — the directory is gitignored). Their completion records document every port deviation and design decision.
10
+
11
+ ## Reference codebases — READ, do not guess
12
+
13
+ Four local checkouts are the authority for this port. Consult them before designing anything:
14
+
15
+ | Path | Role |
16
+ |---|---|
17
+ | `/Users/mulgogi/src/external/python-newsmlg2/` | Reference implementation (Python, lxml). ~317 model classes in `NewsMLG2/*.py`; its `tests/` (11 files, 35 tests, 13 fixtures in `tests/test_files/`) must be **ported fully and pass**. |
18
+ | `/Users/mulgogi/src/external/newsml-g2/` | **Official IPTC spec repo** (git@github.com:iptc/newsml-g2). XSDs in `specification/` and `tests/schema_versions/`; 31 example listings in `examples/`; official unit-test suite: 161 XML files in `tests/unit_test_files/{version}/should_pass|should_fail/` run by `tests/runtests.py`. All are compliance fixtures for our specs. |
19
+ | `/Users/mulgogi/src/lutaml/chemicalml/` | Sibling gem whose **gemspec, GitHub workflows, and architecture we fully adopt** (gemspec, `.github/workflows/rake.yml`+`release.yml`, `Base::*` mixin pattern, canon-based round-trip specs). |
20
+ | `/Users/mulgogi/src/plurimath/mml/` | **Registry-pattern reference**: how models register into a lutaml GlobalContext (`register_model(Klass, id:)`), resolve via `GlobalContext.resolve_type`, and model heterogeneous children with per-element attributes + symbol types (CommonElements). |
21
+ | `/Users/mulgogi/src/mn/metanorma-document/` | lutaml-model usage reference. NOTE: it has **no builder DSL** — the NewsML builder DSL is generated generically from model metadata. |
22
+ | `/Users/mulgogi/src/lutaml/lutaml-model/docs/` | lutaml-model's own documentation (guides/references/tutorials) — the authority on the framework's public API. |
23
+
24
+ Ground truth on NewsML-G2 semantics (item types, itemMeta/contentMeta/partMeta, catalog/scheme/qcode system, conformance levels): the 2.35 XSDs in the official repo (`specification/individual/NewsML-G2_2.35-spec-Framework-Power.xsd` is the core, 5.3k lines).
25
+
26
+ ## Commands
27
+
28
+ ```sh
29
+ bundle install # deps: lutaml-model ~> 0.8, dev: rspec, rubocop, nokogiri, canon, simplecov
30
+ bundle exec rake # default task = rspec
31
+ bundle exec rspec # full suite
32
+ bundle exec rspec spec/newsmlg2/catalog_spec.rb # one file
33
+ bundle exec rspec spec/newsmlg2/catalog_spec.rb:42 # one example by line
34
+ bundle exec rspec -e "round trips" # by description
35
+ bundle exec rubocop # lint (TargetRubyVersion 3.3, NewCops: enable)
36
+ bundle exec rubocop -a # safe autocorrect
37
+ ```
38
+
39
+ RSpec config lives in `spec/spec_helper.rb` (no `.rspec` file): nokogiri XML adapter, canon semantic-XML comparison (`be_xml_equivalent_to`, wrap compared docs in `<r>…</r>`), random order, focus filtering.
40
+
41
+ Never modify vendored fixture files under `spec/fixtures/` (copied from the reference repos); regenerate/copy anew if needed.
42
+
43
+ ## Architecture
44
+
45
+ ### Model layer (chemicalml pattern)
46
+
47
+ One Ruby class per NewsML-G2 complex type, `snake_case` file per class under `lib/newsmlg2/`, all wired with `autoload` from `lib/newsmlg2.rb` (never `require_relative`).
48
+
49
+ - **`Base::*` modules** (`lib/newsmlg2/base/`) — one per XSD *attribute group* (`CommonPowerAttributes`, `I18NAttributes`, `FlexAttributes`, …) and per shared *element group* (`ItemManagementGroup`, `DescriptiveMetadataGroup`, `ConceptRelationshipsGroup`, …). Each declares its `attribute`s and appends mappings via a `self.included(klass)` hook — this is how python-newsmlg2's multiple-inheritance MRO merging translates to Ruby mixins.
50
+ - **Wire classes** — thin: `class NewsItem < Lutaml::Model::Serializable; include Base::…; include …; end` with their `xml do … end` mapping (`root`, `map_element`, `map_attribute`, `map_content`).
51
+ - **Namespaces** — `Newsmlg2::NarNamespace` (`http://iptc.org/std/nar/2006-10-01/`, default/unprefixed) and `Newsmlg2::NitfNamespace`; `xml:lang`/`xml:base` attributes come from `Lutaml::Xml::W3c::XmlNamespace`. Set via `namespace` in the `xml` block.
52
+ - **xs:any / unknown children** — captured with `map_all` (raw round-trip), replacing python-newsmlg2's `ExtensionElement`/`_xs_any_content`.
53
+ - All serialization goes through lutaml-model mappings. **Never** hand-roll `to_h`/`from_h`/`to_xml` on model classes.
54
+
55
+ ### Subsystems
56
+
57
+ - **`Newsmlg2::Configuration`** — the registry: every root model is registered by element id (`register_model(NewsItem, id: :"newsItem")` … in the `lib/newsmlg2.rb` module body) into a lutaml GlobalContext. It is the **single source of truth** for element-name → class resolution.
58
+ - **`Newsmlg2::Document`** — thin entry point: `Newsmlg2.parse(xml)` / `parse_file(path)` resolves the root class **through the registry** and delegates to `klass.from_xml`; `#item`/`#item=`; `#to_xml` with declaration; writes required defaults (`standard="NewsML-G2" standardversion="2.35" conformance="power" version="1"`). `NarModel.lutaml_default_register` binds symbol-typed attributes to the `:newsmlg2` context (chemicalml `Context` pattern).
59
+ - **`ItemSet`** — typed the mml CommonElements way: one collection attribute + `map_element` per carried item type, types as registry symbols (`:"newsItem"`).
60
+ - **Catalog system** — `CatalogStore` (per-document), `Catalog`/`CatalogRef`/`Scheme`, bundled IPTC catalogs (`lib/newsmlg2/catalogs/*.xml`, v32–v41), `Newsmlg2.qcode_to_uri` / `uri_to_qcode`, errors `AliasNotFoundInCatalogs` / `URINotFoundInCatalogs`.
61
+ - **Builder DSL** — `Newsmlg2.build_news_item(guid: …, lang: "en-GB") { |item| … }` (one `build_*` per item type). Node proxies are **generated from the model's lutaml attribute metadata** (snake_case methods, value coercion String→content-model, nested blocks, repeatable calls for collections). No per-class hand-written builder code.
62
+
63
+ ### Test tiers (all must stay green)
64
+
65
+ 1. **Ported python suite** — `spec/newsmlg2/**` mirroring `python-newsmlg2/tests/test_*.py` one-to-one (semantics preserved, Ruby idioms).
66
+ 2. **IPTC examples** — `spec/compliance/examples_spec.rb` over vendored `spec/fixtures/iptc/examples/` (31 LISTING files + extras): parse → typed assertions → semantic round-trip via canon.
67
+ 3. **IPTC official validation suite** — `spec/compliance/xsd_validation_spec.rb` over `spec/fixtures/iptc/unit_test_files/` (151 should-pass / 10 should-fail) × per-version XSDs from `tests/schema_versions/`, using `Lutaml::Xml::XsdValidator`; the file→schema matrix mirrors `tests/runtests.py` exactly.
68
+
69
+ ## Conventions
70
+
71
+ - Specs use **real model instances — never doubles**; assert behavior and output, not interactions.
72
+ - **NEVER reference nokogiri (or any specific XML parser) in `lib/`** — specs only. All raw XML handling goes through lutaml-model's configured adapter (`Lutaml::Model::Config.adapter_for(:xml)`) or, better, `from_xml`/`to_xml` on models.
73
+ - **NEVER hand-roll element-name dispatch** (no name→class hashes, no `node.name` comparisons in code). Element names live only in the `xml do` DSL and the `Configuration` registration block; dispatch goes through the registry / framework mappings.
74
+ - **NEVER reimplement framework mechanics** (parsing, polymorphic dispatch, serialization) — read `~/src/lutaml/lutaml-model/docs/` and follow `~/src/plurimath/mml/`.
75
+ - Gemspec/workflows follow chemicalml verbatim (adapted names): `required_ruby_version >= 3.3.0`, single runtime dep `lutaml-model ~> 0.8.0`, `BSD-2-Clause`, Ribose Inc.
76
+ - Versions, tags, and releases are **the user's decision** — the release workflow is manually dispatched; never pick a version number.
77
+ - All changes go through PRs; never commit/push to main or push tags.
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ gem 'rake', '~> 13.2'
8
+
9
+ group :development do
10
+ gem 'canon', '0.2.12'
11
+ gem 'nokogiri'
12
+ gem 'rspec', '~> 3.13'
13
+ gem 'rubocop', '~> 1.66', require: false
14
+ gem 'simplecov', '~> 0.22', require: false
15
+ end
data/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ BSD 2-Clause License
2
+
3
+ Copyright (c) 2026, Ribose Inc.
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
19
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.adoc ADDED
@@ -0,0 +1,135 @@
1
+ = newsmlg2
2
+
3
+ NewsML-G2 for Ruby, built on link:https://github.com/lutaml/lutaml-model[lutaml-model].
4
+
5
+ Parse, manipulate, build and serialize IPTC
6
+ link:https://iptc.org/standards/newsml-g2/[NewsML-G2] (News Architecture) XML
7
+ with round-trip fidelity. Targets specification version 2.35, power
8
+ conformance. The model layer is a port of
9
+ link:https://github.com/iptc/python-newsmlg2[python-newsmlg2], its full test
10
+ suite is ported, and the official IPTC specification examples and unit-test
11
+ suite are adopted as compliance fixtures.
12
+
13
+ == Installation
14
+
15
+ .....
16
+ gem "newsmlg2"
17
+ .....
18
+
19
+ == Parsing
20
+
21
+ .....
22
+ require "newsmlg2"
23
+
24
+ doc = Newsmlg2.parse_file("newsitem.xml") # or Newsmlg2.parse(xml_string)
25
+ doc.item # => Newsmlg2::NewsItem (typed)
26
+ doc.item.item_meta.item_class.qcode # => "ninat:text"
27
+ doc.item.content_meta.headlines.first.to_s # => "Fed to halt QE ..."
28
+ .....
29
+
30
+ Every root element dispatches to its typed model: +NewsItem+, +PackageItem+,
31
+ +ConceptItem+, +KnowledgeItem+, +CatalogItem+, +PlanningItem+ or
32
+ +NewsMessage+.
33
+
34
+ == QCodes and catalogs
35
+
36
+ A document carries its own catalog store: inline +<catalog>+ elements plus
37
+ +<catalogRef>+ hrefs (the IPTC standard catalogs are bundled, so no network
38
+ access is needed).
39
+
40
+ .....
41
+ doc.catalog_store.get_scheme_for_alias("ninat").uri
42
+ # => "http://cv.iptc.org/newscodes/ninature/"
43
+
44
+ Newsmlg2.qcode_to_uri("ninat:text", doc)
45
+ # => "http://cv.iptc.org/newscodes/ninature/text"
46
+ Newsmlg2.uri_to_qcode("http://cv.iptc.org/newscodes/ninature/text", doc)
47
+ # => "ninat:text"
48
+ .....
49
+
50
+ == Building with the DSL
51
+
52
+ .....
53
+ doc = Newsmlg2.build_news_item(guid: "urn:newsml:acme.com:20260830:00001",
54
+ lang: "en-GB") do |item|
55
+ item.item_meta do |meta|
56
+ meta.item_class qcode: "ninat:text"
57
+ meta.provider qcode: "nprov:acme" do |p|
58
+ p.name "Acme News Agency"
59
+ end
60
+ meta.version_created "2026-08-30T12:00:00+00:00"
61
+ end
62
+ item.content_meta do |cm|
63
+ cm.urgency 2
64
+ cm.headline "Eruption of Icelandic volcano"
65
+ cm.subject qcode: "medtop:20000962" do |s|
66
+ s.name "Volcano"
67
+ s.name "Vulkan", xml_lang: "de"
68
+ end
69
+ end
70
+ end
71
+
72
+ doc.to_xml # => "<?xml version=\"1.0\" ... ?>"
73
+ .....
74
+
75
+ Builder methods are generated from the model metadata: one per attribute
76
+ (collections accept repeated calls and singular aliases — +subject+ appends
77
+ to +subjects+), keyword arguments become child attributes, plain strings
78
+ wrap into content-bearing types, and blocks nest.
79
+
80
+ == Serialization
81
+
82
+ .....
83
+ doc.to_xml # declaration + canonical 2-space-pretty XML
84
+ doc.item.item_meta.to_xml # any subtree
85
+ .....
86
+
87
+ Serializing an item without a +guid+ raises +Newsmlg2::MissingGuidError+.
88
+ The spec's default attributes (+standard+, +standardversion+,
89
+ +conformance+, +version+) are filled in like python-newsmlg2 does.
90
+
91
+ == Architecture
92
+
93
+ * +lib/newsmlg2/nar_model.rb+ — the base model: binds the NAR namespace
94
+ once and provides the declaration DSL (+xml_attributes+, +xml_element+,
95
+ +xml_content+).
96
+ * +lib/newsmlg2/base/+ — XSD attribute groups and element groups as mixins
97
+ (one file per group, ported from python-newsmlg2's attributegroups.py and
98
+ the +*Group+ element lists).
99
+ * +lib/newsmlg2/types/+ — one class per XSD complex type; element names are
100
+ declared once, at their usage site in the owning model.
101
+ * +lib/newsmlg2/items/+ — the seven item types, +newsMessage+, and their
102
+ structural children (+ItemMeta+, +ContentMeta+, +PartMeta+, +GroupSet+ …).
103
+ * +lib/newsmlg2/catalog_store.rb+ — per-document catalog store and qcode
104
+ resolution; IPTC catalogs v32–v41 are bundled under
105
+ +lib/newsmlg2/catalogs/+.
106
+ * +lib/newsmlg2/document.rb+ — the parse/serialize entry point.
107
+ * +lib/newsmlg2/builder.rb+ — the reflection-driven build DSL.
108
+
109
+ == Compliance
110
+
111
+ Three test tiers, all green in CI:
112
+
113
+ 1. **python-newsmlg2 test suite port** — +spec/newsmlg2/*_spec.rb+.
114
+ 2. **IPTC official examples** — all 35 example documents
115
+ (+spec/compliance/examples_spec.rb+).
116
+ 3. **IPTC official validation suite** — 161 should-pass/should-fail files
117
+ across all schema versions, ~2100 assertions
118
+ (+spec/compliance/xsd_validation_spec.rb+).
119
+
120
+ IPTC material (examples, schemas, unit-test files) is vendored under
121
+ +spec/fixtures/iptc/+ under the IPTC's CC-BY 4.0 / MIT terms; see
122
+ +spec/fixtures/iptc/README.md+.
123
+
124
+ == Development
125
+
126
+ .....
127
+ bundle install
128
+ bundle exec rake # full suite
129
+ bundle exec rubocop
130
+ .....
131
+
132
+ == License
133
+
134
+ BSD-2-Clause. NewsML-G2 is a trademark of the IPTC; this library is not
135
+ affiliated with the IPTC.
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+
5
+ Dir[File.expand_path('lib/tasks/*.rake', __dir__)].each { |task| import task }
6
+
7
+ begin
8
+ require 'rspec/core/rake_task'
9
+ RSpec::Core::RakeTask.new(:spec)
10
+ task default: :spec
11
+ rescue LoadError
12
+ task :default do
13
+ warn 'rspec not available — skipping specs'
14
+ end
15
+ end
data/docs/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'jekyll', '~> 4.3'
6
+ gem 'jekyll-theme-minimal'
data/docs/_config.yml ADDED
@@ -0,0 +1,4 @@
1
+ title: newsmlg2
2
+ description: NewsML-G2 for Ruby, built on lutaml-model
3
+ theme: jekyll-theme-minimal
4
+ markdown: kramdown
data/docs/index.md ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ title: newsmlg2 — NewsML-G2 for Ruby
3
+ layout: default
4
+ ---
5
+
6
+ # newsmlg2
7
+
8
+ NewsML-G2 for Ruby, built on [lutaml-model](https://github.com/lutaml/lutaml-model).
9
+
10
+ See the [README](https://github.com/lutaml/newsmlg2-ruby) for usage:
11
+ parsing, qcode/catalog resolution, the builder DSL and serialization.
12
+
13
+ ## Pages
14
+
15
+ - [Usage](usage.md)
data/docs/lychee.toml ADDED
@@ -0,0 +1,4 @@
1
+ # Link checker configuration for the built docs site.
2
+ timeout = 20
3
+ max_retries = 3
4
+ retry_wait_time = 5