fix: NisoSts metadata repeatable-element cardinality - #55
Merged
Conversation
Per NISO STS v1.2 XSD (reference-docs/NISO-STS-extended-1-MathML3-XSD/),
most children of <iso-meta>, <reg-meta>, <nat-meta>, <std-meta> are
maxOccurs="unbounded". sts-ruby was declaring these as singular Ruby
attributes, silently dropping all but the last value when a document
had multiple of the same element.
The downstream impact (filed as BUGREPORT.metadata-iso-collection-types.md
in the project root) was that callers like
`meta.std_ref = [sr1, sr2]` silently lost sr1: the second assignment
overwrote the first, and lutaml-model's element_order recorded the same
object twice, so the serialised output had two identical `<std-ref>`
entries pointing at sr2.
## Changes
For each affected NisoSts metadata class, added `collection: true` to
every repeatable-per-schema attribute:
- lib/sts/niso_sts/metadata_iso.rb: 9 attributes made collections
(content_language, custom_meta_group, ics, meta_date, permissions,
release_date, std_ref, std_xref, title_wrap)
- lib/sts/niso_sts/metadata_std.rb: 3 attributes made collections
(content_language, std_org_group, self_uri)
- lib/sts/niso_sts/reg_meta.rb: 2 attributes made collections
(release_date, ics)
- lib/sts/niso_sts/nat_meta.rb: 2 attributes made collections
(release_date, ics)
## Lutaml-model limitation discovered
Two repeatable elements (<comm-ref> and <secretariat>) commonly appear as
empty elements in real fixtures (e.g.,
spec/fixtures/ISO_13849-1_2008-12_en_TBX.xml). lutaml-model 0.8.19 drops
empty elements from `:string`-typed collections on round-trip — the
collection ends up empty `[]` instead of `[""]`, and the empty element
is lost on serialise.
To preserve the empty elements, <comm-ref> and <secretariat> stay
singular with `value_map: { to: { empty: :empty } }`. This is a
deliberate trade-off: schema-conformance is sacrificed for those two
attributes so round-trip fidelity is preserved. Filed as
`BUGREPORT.collection-empty-element-dropped.md` in
~/src/lutaml/lutaml-model/.
## Regression specs
spec/niso_sts/metadata_cardinality_spec.rb (30 examples):
- Each collection: true attribute asserted as a collection
- Each singular attribute asserted as singular with documented reason
(empty-element preservation vs schema maxOccurs=1)
- Reproduction of the original BUGREPORT: `meta.std_ref = [sr1, sr2]`
now serialises two distinct `<std-ref>` entries
## Backwards compatibility
Callers that read these attributes as single values (e.g.,
`meta.std_ref.value`) must update to `meta.std_ref.first.value` or
similar. The model is pre-1.0; document in CHANGELOG.
5198 examples, 0 failures. Rubocop clean.
4 tasks
ronaldtse
added a commit
that referenced
this pull request
Jul 27, 2026
… classes PR #55 fixed the cardinality bug in NisoSts metadata classes (MetadataIso, MetadataStd, RegMeta, NatMeta) but applied the value_map workaround inconsistently: - MetadataIso: comm-ref had value_map, secretariat did NOT (regression) - MetadataStd: both had value_map (correct) - RegMeta: neither had value_map (regression) - NatMeta: neither had value_map (regression) The cardinality work in PR #55 kept comm_ref and secretariat as singular attributes (sacrificing schema-conformance) to preserve empty elements in real fixtures via value_map: { to: { empty: :empty } }. The trade-off was only fully applied to MetadataStd; three of four metadata classes silently dropped empty <comm-ref/> and <secretariat/> elements on round-trip. This restores the value_map on all four classes uniformly: - lib/sts/niso_sts/metadata_iso.rb: secretariat (added) - lib/sts/niso_sts/reg_meta.rb: comm-ref, secretariat (both added) - lib/sts/niso_sts/nat_meta.rb: comm-ref, secretariat (both added) Adds a regression spec covering all four classes — discovered by the round-trip audit when validating BUGREPORT.metadata-iso-collection-types.md. Verification: with all four classes preserving both empty elements, the following round-trip works: <iso-meta><comm-ref/><secretariat/></iso-meta> -> MetadataIso.from_xml(...).to_xml -> <iso-meta><comm-ref/><secretariat/></iso-meta> 5202 examples, 0 failures (was 5198; +4 new cross-class regression specs). Rubocop clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the cardinality bug documented in
BUGREPORT.metadata-iso-collection-types.md. Per NISO STS v1.2 XSD, most children of<iso-meta>,<reg-meta>,<nat-meta>,<std-meta>aremaxOccurs="unbounded". sts-ruby was declaring these as singular Ruby attributes, silently dropping all but the last value when a document had multiple of the same element.Changes
For each affected NisoSts metadata class, added
collection: trueto every repeatable-per-schema attribute:lib/sts/niso_sts/metadata_iso.rblib/sts/niso_sts/metadata_std.rblib/sts/niso_sts/reg_meta.rblib/sts/niso_sts/nat_meta.rb16 cardinality fixes total.
Lutaml-model limitation discovered
Two repeatable elements (
<comm-ref>and<secretariat>) commonly appear as empty elements in real fixtures (e.g.,spec/fixtures/ISO_13849-1_2008-12_en_TBX.xml). lutaml-model 0.8.19 drops empty elements from:string-typed collections on round-trip — the collection ends up[]instead of[""], and the empty element is lost on serialise.To preserve empty elements,
<comm-ref>and<secretariat>stay singular withvalue_map: { to: { empty: :empty } }. This is a deliberate trade-off: schema-conformance sacrificed for those two attributes so round-trip fidelity is preserved. Filed asBUGREPORT.collection-empty-element-dropped.mdin~/src/lutaml/lutaml-model/.Regression specs
spec/niso_sts/metadata_cardinality_spec.rb(30 examples):collection: trueattribute asserted as a collectionmeta.std_ref = [sr1, sr2]now serialises two distinct<std-ref>entriesBackwards compatibility
Callers that read these attributes as single values (e.g.,
meta.std_ref.value) must update tometa.std_ref.first.valueor similar. The model is pre-1.0; CHANGELOG entry recommended.Test plan
bundle exec rspec— 5198 examples, 0 failures (was 5168; +30 cardinality specs)bundle exec rubocop— clean (537 files)<comm-ref/>and<secretariat/>are preserved)