sts 0.6.5 → 0.6.7

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/TODO.roadmap/00-overview.md +80 -0
  3. data/TODO.roadmap/01-lutaml-model-namespace-scope-uri-matching.md +111 -0
  4. data/TODO.roadmap/02-lutaml-model-element-builder-dispatch.md +115 -0
  5. data/TODO.roadmap/03-issue-40-child-bearing-roots.md +57 -0
  6. data/TODO.roadmap/04-issue-40-recursive-roots.md +105 -0
  7. data/TODO.roadmap/05-content-mathml-unification.md +75 -0
  8. data/TODO.roadmap/06-mathml-namespace-deduplication.md +59 -0
  9. data/TODO.roadmap/07-tbxisotml-misclassification-audit.md +82 -0
  10. data/TODO.roadmap/08-register-versioning.md +95 -0
  11. data/TODO.roadmap/09-test-coverage-gaps.md +78 -0
  12. data/TODO.roadmap/10-iso-sts-common-attributes-module.md +115 -0
  13. data/TODO.roadmap/11-docs-and-readme-refresh.md +52 -0
  14. data/TODO.sts-refactor/00-overview.md +3 -3
  15. data/TODO.sts-refactor/03-namespace-coupling.md +24 -5
  16. data/TODO.sts-refactor/09-autoload-cleanup.md +48 -83
  17. data/TODO.sts-refactor/11-duplicate-models.md +32 -2
  18. data/lib/sts/iso_sts/array.rb +1 -1
  19. data/lib/sts/iso_sts/attrib.rb +72 -0
  20. data/lib/sts/iso_sts/boxed_text.rb +2 -0
  21. data/lib/sts/iso_sts/custom_meta.rb +37 -0
  22. data/lib/sts/iso_sts/custom_meta_group.rb +16 -0
  23. data/lib/sts/iso_sts/def_list.rb +1 -1
  24. data/lib/sts/iso_sts/disp_quote.rb +2 -0
  25. data/lib/sts/iso_sts/iso_meta.rb +1 -1
  26. data/lib/sts/iso_sts/license.rb +35 -0
  27. data/lib/sts/iso_sts/license_p.rb +96 -0
  28. data/lib/sts/iso_sts/meta_name.rb +14 -0
  29. data/lib/sts/iso_sts/meta_value.rb +41 -0
  30. data/lib/sts/iso_sts/nat_meta.rb +1 -1
  31. data/lib/sts/iso_sts/permissions.rb +1 -1
  32. data/lib/sts/iso_sts/reg_meta.rb +1 -1
  33. data/lib/sts/iso_sts/table_wrap_foot.rb +1 -1
  34. data/lib/sts/iso_sts/term_head.rb +74 -0
  35. data/lib/sts/iso_sts.rb +8 -3
  36. data/lib/sts/version.rb +1 -1
  37. metadata +22 -4
  38. data/lib/sts/iso_sts/mathml2/math.rb +0 -68
  39. data/lib/sts/iso_sts/mathml2.rb +0 -9
@@ -0,0 +1,82 @@
1
+ # C.07: TbxIsoTml misclassification audit
2
+
3
+ **Status**: deferred. Same pattern as the prior Fn/FnGroup (PR #47) and
4
+ Math (commit 9e977a5) removals — proven approach.
5
+ **Memory**: `architecture-namespace-independence` (TbxIsoTml is a shared
6
+ namespace, but only for elements that are *actually* TBX-ISO-TML).
7
+
8
+ ## Problem
9
+
10
+ `Sts::TbxIsoTml::*` is the namespace for elements defined by ISO 30042
11
+ (TBX-ISO-TML). The standard defines terminology structures: `<termEntry>`,
12
+ `<tig>`, `<term>`, `<descrip>`, `<descripGrp>`, `<note>`, `<definition>`,
13
+ `<source>`, `<langSet>`, `<subjectField>`, `<partOfSpeech>`,
14
+ `<entailedTerm>`, etc.
15
+
16
+ The current `lib/sts/tbx_iso_tml/` also contains elements that are **not**
17
+ defined by TBX-ISO-TML — they're JATS / NISO STS elements borrowed into TBX
18
+ contexts. These are misclassifications: they should live in the host
19
+ namespace (NisoSts or IsoSts), not TbxIsoTml.
20
+
21
+ ## Suspects (likely misclassifications)
22
+
23
+ Each was identified by cross-referencing the TBX-ISO-TML (ISO 30042)
24
+ element list. Anything NOT in TBX-ISO-TML is a misfit.
25
+
26
+ | Class | Actual element source | Should move to |
27
+ |---|---|---|
28
+ | `TbxIsoTml::Xref` | JATS `<xref>` | (already shared via TbxIsoTml per Bold/Italic precedent — but the precedent itself may be wrong) |
29
+ | `TbxIsoTml::TableWrap` | JATS `<table-wrap>` | (same) |
30
+ | `TbxIsoTml::Table`, `Thead`, `Tbody`, `Tfoot`, `Tr`, `Th`, `Td`, `Col`, `Colgroup` | XHTML table model | IsoSts already has equivalents; NisoSts has equivalents |
31
+ | `TbxIsoTml::Caption` | JATS `<caption>` | (same) |
32
+ | `TbxIsoTml::TableWrapFoot` | JATS `<table-wrap-foot>` | (same) |
33
+ | `TbxIsoTml::TableBreak` | JATS extension | (same) |
34
+ | `TbxIsoTml::ExternalGraphic` | JATS `<external-graphic>` | (same) |
35
+ | `TbxIsoTml::ExternalSource` | JATS extension | (same) |
36
+ | `TbxIsoTml::Example` | JATS `<example>` (or NISO STS `<non-normative-example>`) | IsoSts has `NonNormativeExample` |
37
+ | `TbxIsoTml::Fn` ❌ | already removed (PR #47) | — |
38
+ | `TbxIsoTml::FnGroup` ❌ | already removed (PR #47) | — |
39
+ | `TbxIsoTml::Math`, `Mrow`, `Mi`, etc. ❌ | already removed (9e977a5) | — |
40
+ | `TbxIsoTml::Sup`, `TbxIsoTml::Bold`, `TbxIsoTml::Italic` | JATS emphasis group | IsoSts and NisoSts each have equivalents |
41
+
42
+ ## Per-element decision matrix
43
+
44
+ For each suspect:
45
+
46
+ 1. Confirm the element is NOT in TBX-ISO-TML (ISO 30042 spec lookup).
47
+ 2. Identify all call sites (`grep -rn "TbxIsoTml::<Class>" lib/`).
48
+ 3. Decide:
49
+ - **Delete and repoint** — if the host-equivalent class already exists
50
+ and has compatible API (like Fn → IsoSts::Fn + NisoSts::Fn).
51
+ - **Promote** — if no host-equivalent exists, create one in each host
52
+ namespace (parallel class per the ADR 2026-05-07).
53
+ - **Keep** — if the element genuinely belongs in shared TBX (rare; only
54
+ for true TBX-ISO-TML elements like `TermEntry`, `Term`, `Tig`).
55
+
56
+ ## Why this matters
57
+
58
+ Each misclassified class is a potential version-mismatch footgun (same
59
+ trap as the deleted `TbxIsoTml::Fn`). They also blur the architectural
60
+ boundary between TBX (shared, frozen) and STS host namespaces
61
+ (independent, evolving).
62
+
63
+ ## How to apply
64
+
65
+ - Same pattern as PR #47 (delete TbxIsoTml::Fn/FnGroup)
66
+ - One PR per element family (e.g., one PR for the XHTML table model, one
67
+ for emphasis, etc.) — keeps review tractable
68
+ - Each PR must update all call sites and add specs asserting the
69
+ TbxIsoTml class stays deleted (mirror of `spec/fn_namespace_spec.rb`)
70
+
71
+ ## Verification
72
+
73
+ - After each PR: `grep -rn "TbxIsoTml::<Class>" lib/` returns nothing
74
+ - TBX fixture round-trips still pass
75
+ - `bundle exec rake` green
76
+
77
+ ## Open question
78
+
79
+ The shared-namespace role of TbxIsoTml (for elements used in both IsoSts
80
+ and NisoSts hosts without their own version of the element) needs explicit
81
+ documentation. Today the boundary is implicit. Consider an ADR documenting
82
+ which elements are TBX-ISO-TML (shared) vs which are host-specific.
@@ -0,0 +1,95 @@
1
+ # C.08: Register versioning for STS schema versions
2
+
3
+ **Status**: planning. Significant API design.
4
+ **Original doc**: `TODO.sts-refactor/04-register-versioning.md` (HIGH
5
+ priority, untouched).
6
+ **Estimated effort**: large (multi-week).
7
+
8
+ ## Problem
9
+
10
+ sts-ruby today ships a single `Sts::IsoSts` and `Sts::NisoSts` namespace.
11
+ Each maps to one schema version (ISOSTS v1.1, NISO STS 1.0). Real-world
12
+ documents use multiple versions:
13
+
14
+ - ISOSTS v1.1 (2013, frozen)
15
+ - NISO STS 1.0 (2017)
16
+ - NISO STS 1.2 (2022, evolving)
17
+
18
+ When a 1.2-specific element (`<processing-meta>`, `<code>`, `<legend>`) is
19
+ modelled, it lives in `Sts::NisoSts::*` alongside 1.0 elements. There's no
20
+ way to parse a document as "NISO STS 1.0 only" and reject 1.2 elements.
21
+
22
+ ## Goal
23
+
24
+ Use lutaml-model's `Register` system to version the model classes. The
25
+ mml gem already does this:
26
+
27
+ ```ruby
28
+ module Mml
29
+ module V2
30
+ class Math < CommonElements
31
+ def self.lutaml_default_register
32
+ :mml_v2
33
+ end
34
+ end
35
+ end
36
+ end
37
+ ```
38
+
39
+ Each MathML version is a separate module with its own context. sts-ruby
40
+ should follow the same pattern: `Sts::IsoSts::V1_1::*`, `Sts::NisoSts::V1_0::*`,
41
+ `Sts::NisoSts::V1_2::*`.
42
+
43
+ ## Plan (high level)
44
+
45
+ 1. **API design** — decide caller-facing entrypoint:
46
+ - Option A: `Sts.parse(xml, version: :nisosts_1_0)` — explicit
47
+ - Option B: auto-detect from DOCTYPE / `dtd-version` attribute
48
+ - Option C: keep `Sts::NisoSts::*` as the latest alias, add
49
+ `Sts::NisoSts::V1_0::*` etc. for version-specific access
50
+ 2. **Re-namespace** — move every existing `Sts::NisoSts::*` class to
51
+ `Sts::NisoSts::V1_2::*` (or wherever it belongs based on schema
52
+ history). Keep backward-compat aliases.
53
+ 3. **Cross-version testing** — same fixture parsed under different
54
+ versions should accept/reject the right elements.
55
+ 4. **Version-specific elements** — `<processing-meta>` (1.2-only) lives
56
+ only in V1_2; parsing it under V1_0 raises (or warns).
57
+
58
+ ## Risks
59
+
60
+ - **Breaking API change** — current callers use `Sts::NisoSts::Standard`
61
+ without a version. Any re-namespace breaks them.
62
+ - **Cross-version references** — if V1_2::Standard contains V1_0::Paragraph
63
+ (because Paragraph hasn't changed), the type references cross versions.
64
+ Needs careful design.
65
+ - **Migration burden** — large existing model surface.
66
+
67
+ ## Decision needed
68
+
69
+ This is the largest remaining architectural work. Before starting:
70
+
71
+ 1. Confirm the use case (do downstream consumers actually need version
72
+ discrimination?)
73
+ 2. Decide the API (Options A/B/C above)
74
+ 3. Plan migration path for existing callers
75
+
76
+ ## Files affected
77
+
78
+ - Every `lib/sts/niso_sts/*.rb` (~200 classes)
79
+ - Every `lib/sts/iso_sts/*.rb` (~150 classes)
80
+ - All specs that reference classes by current names
81
+
82
+ ## How to apply
83
+
84
+ - Don't start until the design is approved
85
+ - Read mml gem's `lib/mml/versioned_parser.rb` and
86
+ `lib/mml/context_configuration.rb` for the established pattern
87
+ - Consider a small proof-of-concept (version one element family — e.g.,
88
+ `<list>` — across versions) before committing
89
+
90
+ ## Verification
91
+
92
+ - Each version's classes round-trip its corresponding schema's fixtures
93
+ - Cross-version parsing is explicitly tested
94
+ - Existing callers can opt in incrementally (backward compat for at least
95
+ one release)
@@ -0,0 +1,78 @@
1
+ # D.09: Test coverage gaps
2
+
3
+ **Status**: ongoing. Original doc: `TODO.sts-refactor/10-test-coverage.md`.
4
+ **Estimated effort**: medium, distributed across future PRs.
5
+
6
+ ## Current state
7
+
8
+ - **2563 examples, 0 failures**
9
+ - Anti-pattern enforcement: 9 per-file checks in `spec/anti_patterns_spec.rb`
10
+ - Per-model attribute-set assertions: ~50 models covered (mostly IsoSts
11
+ recent additions)
12
+ - Round-trip coverage: `spec/round_trip/reference_docs_spec.rb` exercises
13
+ real fixtures
14
+ - Coverage: 99.8% line coverage (per TODO.sts-refactor/10)
15
+
16
+ ## Gaps
17
+
18
+ ### 1. Schema-validation tests
19
+
20
+ Currently absent. Each model is asserted against an attribute set in
21
+ specs, but no test validates model output against the actual XSDs in
22
+ `reference-docs/`. A schema-validation spec would catch:
23
+
24
+ - Attributes the model invents (not in XSD)
25
+ - Required attributes the model drops
26
+ - Element nesting the XSD forbids
27
+
28
+ Implementation: `Nokogiri::XML::Schema` to validate serialized output
29
+ against `reference-docs/isosts-v1/xsd/ISOSTS.xsd` and
30
+ `reference-docs/NISO-STS-extended-1-MathML3-XSD/`.
31
+
32
+ ### 2. Performance tests
33
+
34
+ Currently absent. The user's TODO.sts-refactor/10 lists these as
35
+ "needed". sts-ruby parses real ISO documents (some 1MB+); parsing time
36
+ and memory pressure should be tracked.
37
+
38
+ Implementation: `rspec-benchmark` or similar. Bench against the largest
39
+ fixture (`spec/fixtures/mn-samples-iso-private/`). Track regressions
40
+ across releases.
41
+
42
+ ### 3. Concurrent-parse tests
43
+
44
+ `Lutaml::Model::TransformationRegistry` uses mutexes for thread safety
45
+ (lib/lutaml/model/transformation_registry.rb:92-101). No spec covers
46
+ concurrent parsing. Worth a thread-spawning test that hammers the
47
+ registry from multiple threads to catch any race.
48
+
49
+ ### 4. Versioning tests (blocked on C.08)
50
+
51
+ Once Register versioning exists, add specs that the same document parses
52
+ differently under different versions, and that version-incompatible
53
+ elements are rejected.
54
+
55
+ ### 5. Cross-namespace round-trip
56
+
57
+ `spec/iso_sts/iso_sts_element_spec.rb` and `spec/sts_spec.rb` cover
58
+ IsoSts and NisoSts separately. No spec exercises a TBX-in-ISOSTS document
59
+ end-to-end (TBX elements inside IsoSts Standard with MathML 2 content).
60
+ A round-trip test for `spec/fixtures/tbx-nisosts-0.2.xml`-style documents
61
+ across all three namespaces would catch integration regressions.
62
+
63
+ ### 6. Profile validation (existing)
64
+
65
+ `spec/profiles/iso_iec_validator_spec.rb` — 11 specs. Covers structural,
66
+ metadata, originator, doc-type validation. Looks healthy.
67
+
68
+ ## How to apply
69
+
70
+ - Add schema-validation spec first (highest value, mechanical to write)
71
+ - Add performance spec next (gives a baseline before C.05/C.08 land)
72
+ - Concurrent-parse spec is small and high-value — fit it in alongside any
73
+ future lutaml-model bump
74
+ - Versioning and cross-namespace specs block on C.08
75
+
76
+ ## Verification
77
+
78
+ Each new spec file passes locally and on CI. Coverage stays at ≥99.5%.
@@ -0,0 +1,115 @@
1
+ # D.10: IsoSts common-attribute module (DRY within namespace)
2
+
3
+ **Status**: proposal. Needs approval before implementing.
4
+ **Estimated effort**: medium (touches ~50 classes).
5
+
6
+ ## Problem
7
+
8
+ Many IsoSts classes share the same attribute set:
9
+
10
+ | Pattern | Attributes | Used by |
11
+ |---|---|---|
12
+ | Date parts | `id content_type specific_use xml_lang` | Day, Month, Season, Etal (partial) |
13
+ | Contact (simple) | `id content_type specific_use content` | Phone, Fax |
14
+ | xlink extension | `xlink_type xlink_href xlink_role xlink_title xlink_show xlink_actuate` | Abbrev, Email, ExtLink, Institution, InlineGraphic, MixedCitation |
15
+ | Citation-common | `id content_type specific_use xml_lang originator` | (various) |
16
+
17
+ This is fine-grained duplication. Each class is ~20-30 lines, mostly
18
+ attribute declarations. A single attribute rename across the schema (e.g.,
19
+ `xml:lang` semantics change) requires updating 30+ files.
20
+
21
+ ## Proposal
22
+
23
+ Within the IsoSts namespace only (NOT cross-namespace — that's forbidden
24
+ by the ADR), define modules:
25
+
26
+ ```ruby
27
+ module Sts
28
+ module IsoSts
29
+ module CommonAttributes
30
+ # XLink attributes — included into classes that wrap XLink-extending elements
31
+ module Xlink
32
+ def self.included(base)
33
+ base.class_eval do
34
+ attribute :xlink_type, :string
35
+ attribute :xlink_href, :string
36
+ attribute :xlink_role, :string
37
+ attribute :xlink_title, :string
38
+ attribute :xlink_show, :string
39
+ attribute :xlink_actuate, :string
40
+ end
41
+ end
42
+ end
43
+
44
+ # Common attrs for simple text-with-id elements
45
+ module TextWithId
46
+ def self.included(base)
47
+ base.class_eval do
48
+ attribute :id, :string
49
+ attribute :content_type, :string
50
+ attribute :specific_use, :string
51
+ attribute :xml_lang, :string
52
+ attribute :content, :string, collection: true
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ ```
60
+
61
+ Then:
62
+
63
+ ```ruby
64
+ class Abbrev < Lutaml::Model::Serializable
65
+ include IsoSts::CommonAttributes::Xlink
66
+ # ... class-specific attrs
67
+ end
68
+ ```
69
+
70
+ ## Trade-off
71
+
72
+ **Pros**:
73
+ - DRY at the attribute-declaration level
74
+ - Single source of truth for common attribute sets
75
+ - Easier to evolve (rename `xml_lang` → `xml_lang_attr` once)
76
+ - Same pattern as `Mml::Base::CommonAttributes` in the mml gem
77
+
78
+ **Cons**:
79
+ - Adds indirection — readers must look at the module to know which attrs a
80
+ class has
81
+ - Mapping (`map_attribute "xml:lang", to: :xml_lang`) still needs to be
82
+ declared per class
83
+ - Risk of "module soup" if too many tiny modules are introduced
84
+
85
+ ## Decision needed
86
+
87
+ This is a judgment call. The duplication today is annoying but not
88
+ critical. The mml gem's precedent suggests the pattern is viable.
89
+
90
+ Recommendation: **defer** until the namespace-coupling work (B.03, B.04)
91
+ is complete. Doing it now would create churn on classes that are about to
92
+ change anyway.
93
+
94
+ ## How to apply (if approved)
95
+
96
+ 1. Start with one module (XLink) — covers 5+ classes, lowest risk
97
+ 2. Convert one class, run tests, repeat
98
+ 3. Each conversion is its own commit (small, reviewable)
99
+ 4. Stop if the indirection starts hurting readability
100
+
101
+ ## Verification
102
+
103
+ - All existing specs still pass
104
+ - No spec needs to change (attribute-set assertions should be unchanged —
105
+ the module just provides the attribute declarations, the class still
106
+ exposes the same keys)
107
+ - Rubocop clean
108
+
109
+ ## Anti-pattern note
110
+
111
+ This proposal does NOT introduce cross-namespace sharing. The modules
112
+ live in `Sts::IsoSts::CommonAttributes::*` and are only included into
113
+ `Sts::IsoSts::*` classes. NisoSts would have its own
114
+ `Sts::NisoSts::CommonAttributes::*` if it wants the same pattern. This
115
+ respects the ADR 2026-05-07.
@@ -0,0 +1,52 @@
1
+ # D.11: Docs and README refresh
2
+
3
+ **Status**: deferred. Low priority but accumulates debt.
4
+ **Estimated effort**: small.
5
+
6
+ ## Problem
7
+
8
+ Several docs reference outdated state:
9
+
10
+ - `README.adoc` — likely doesn't mention 0.6.x features (V2/V3 MathML host
11
+ binding, the namespace-independence principle, the strengthened
12
+ anti-pattern enforcement)
13
+ - `CLAUDE.md` — gives architecture guidance but predates the 2026-05-07
14
+ ADR, the recent unification work, the TODO.finalize/ series, and the
15
+ new TODO.roadmap/
16
+ - `TODO.sts-refactor/` — mostly historical; the overview at
17
+ `00-overview.md` should link to `TODO.roadmap/00-overview.md` as the
18
+ current source of truth
19
+ - Cross-references between TODO docs use stale ref counts (e.g.,
20
+ "63→16" appears in some places even after PRs reduced it to 11)
21
+
22
+ ## Plan
23
+
24
+ 1. **README** — refresh to mention:
25
+ - The IsoSts/NisoSts independence contract
26
+ - How to construct documents (which Fn/DispQuote/BoxedText class to use)
27
+ - MathML host binding (V2 for IsoSts, V3 for NisoSts)
28
+ - Anti-pattern enforcement (link to `spec/anti_patterns_spec.rb`)
29
+ 2. **CLAUDE.md** — refresh architecture section to reflect:
30
+ - The ADR 2026-05-07 (IsoSts/NisoSts independence)
31
+ - Current autoload conventions (no `require_relative`, no internal
32
+ `require`)
33
+ - The anti-pattern rules now enforced by spec
34
+ 3. **TODO.sts-refactor/00-overview.md** — add a header noting that
35
+ `TODO.roadmap/` is the current source of truth; this directory is
36
+ retained for history
37
+ 4. **Cross-reference cleanup** — search and update stale ref counts
38
+ across all TODO docs
39
+
40
+ ## How to apply
41
+
42
+ - One PR with all doc updates
43
+ - Coordinate with any in-flight code PRs to avoid stale doc references
44
+ - Consider an ADR directory (`docs/adr/`) for the architectural decisions;
45
+ currently they're embedded in TODO docs which is fragile
46
+
47
+ ## Verification
48
+
49
+ - All Markdown links resolve
50
+ - Code examples in docs still work (copy-paste runnable)
51
+ - No stale references to deleted classes (TbxIsoTml::Fn, TbxIsoTml::Math,
52
+ Sts::Mathml::Math, etc.)
@@ -19,7 +19,7 @@
19
19
  |---|------|----------|----------|--------------|
20
20
  | 01 | `01-mathml-delegation.md` | Anti-Pattern Fix | DONE | — |
21
21
  | 02 | `02-type-resolution.md` | Anti-Pattern Fix | DONE | — |
22
- | 03 | `03-namespace-coupling.md` | Architecture | IN PROGRESS (63→13 refs) | 01, 02 |
22
+ | 03 | `03-namespace-coupling.md` | Architecture | IN PROGRESS (63→6 refs) | 01, 02 |
23
23
  | 04 | `04-register-versioning.md` | Architecture | HIGH | 01, 02 |
24
24
  | 05 | `05-missing-elements.md` | Feature Gap | MOSTLY DONE | 04 |
25
25
  | 06 | `06-missing-attributes.md` | Feature Gap | DONE | 04 |
@@ -166,7 +166,7 @@ grep -r "method_missing|respond_to_missing|Object.const_get|\.send" lib/
166
166
  - ~~Expand StringName usage (in contrib, element-citation, related-article — NISO STS 1.2)~~ → DONE: added to name-alternatives; already in person-group and mixed-citation
167
167
 
168
168
  ### Architectural Items (High Effort)
169
- - `03-namespace-coupling.md` — IN PROGRESS: 63→13 IsoSts→NisoSts references
169
+ - `03-namespace-coupling.md` — IN PROGRESS: 63→6 IsoSts→NisoSts references
170
170
  remaining (issue #40). ISOSTS.xsd governs content models and all non-`@id`
171
171
  attributes; the `@id` follows the 86948b9 convention.
172
172
  - `04-register-versioning.md` — Version the models via lutaml-model Registers
@@ -193,7 +193,7 @@ the **NISO** XSD and applied the result to IsoSts.
193
193
 
194
194
  ## Next Action
195
195
  Two independent tracks:
196
- 1. Finish `03` — the 13 remaining refs split across five recursive roots
196
+ 1. Finish `03` — the 6 remaining refs split across five recursive roots
197
197
  (`ElementCitation`, `PersonGroup`, `Collab`, `Source`, `TermDisplay`) and
198
198
  five child-bearing roots. `DispQuote` and `BoxedText` themselves are now
199
199
  IsoSts classes. The first 15 high-reuse dependency leaves are modelled,
@@ -3,7 +3,7 @@
3
3
  **Priority**: HIGH
4
4
  **Category**: Architecture
5
5
  **Estimated Effort**: High
6
- **Status**: Partially done — 63 → 13 references (GitHub issue #40)
6
+ **Status**: Partially done — 63 → 6 references (GitHub issue #40)
7
7
 
8
8
  ## Problem
9
9
 
@@ -14,6 +14,11 @@ evolves, so coupling them violates OCP.
14
14
  PR #31 reduced this from 157 to 63 references. Issue #40 reduced it further,
15
15
  from 63 to 16. The `IsoSts::DispQuote` / `IsoSts::BoxedText` addition then
16
16
  reduced it from 16 to 13 (Body#disp_quote, Sec#disp_quote, Sec#boxed_text).
17
+ The `IsoSts::Attrib` addition then reduced it from 13 to 11
18
+ (Array#attrib, TableWrapFoot#attrib) and filled the omission in DispQuote /
19
+ BoxedText from PR #48. The License/TermHead/CustomMetaGroup closure then
20
+ reduced it from 11 to 6 (Permissions#license, DefList#term_head, and
21
+ IsoMeta/RegMeta/NatMeta#custom_meta_group).
17
22
 
18
23
  ## Content models from ISOSTS.xsd; `@id` from the 86948b9 convention
19
24
 
@@ -74,12 +79,24 @@ Non-`@id` attribute lists must be **generated** from the XSD, never hand-read:
74
79
  from NisoSts, which disagrees: `NisoSts::DispQuote` lacks `xml_lang` and
75
80
  `title`; `NisoSts::BoxedText` carries `form_type`/`is_form` that ISOSTS does
76
81
  not define). Children limited to existing IsoSts types; omitted children
77
- (`attrib`, `speech`, `statement`, `verse-group`, `address`, `alternatives`,
82
+ (`speech`, `statement`, `verse-group`, `address`, `alternatives`,
78
83
  `array`, `chem-struct-wrap`, `fig-group`, `media`, `supplementary-material`,
79
84
  `table-wrap`, `table-wrap-group`, `disp-formula-group`, `mml:math`,
80
85
  `related-article`, `related-object`, `glossary`) are tracked below.
81
86
  `BoxedText#sts_object_id` (not `:object_id`) follows the `NisoSts::Graphic`
82
87
  convention to avoid clashing with `Object#object_id`. 3 refs.
88
+ - **`Attrib` added** — mixed-content class modelled from ISOSTS.xsd with the
89
+ 25 inline-element children that have IsoSts types today. Fills the
90
+ `attrib` gap left in `DispQuote` and `BoxedText` from the prior bullet.
91
+ 2 refs (`Array#attrib`, `TableWrapFoot#attrib`). Omitted inline children
92
+ (`inline-supplementary-material`, `related-article`, `related-object`,
93
+ `element-citation`, `overline`, `roman`, `sans-serif`, `alternatives`,
94
+ `private-char`, `chem-struct`, `mml:math`, `target`, `tbx:entailedTerm`)
95
+ tracked below.
96
+ - **License / TermHead / CustomMetaGroup closure added** — `License`,
97
+ `LicenseP`, `TermHead`, `CustomMetaGroup`, `CustomMeta`, `MetaName`, and
98
+ `MetaValue` modelled from ISOSTS.xsd. 5 refs (`Permissions#license`,
99
+ `DefList#term_head`, `IsoMeta/RegMeta/NatMeta#custom_meta_group`).
83
100
 
84
101
  ### Why classes and not plain `:string`
85
102
 
@@ -90,13 +107,15 @@ round-trip. For a scalar, `render_empty: :empty` recovers it; for a collection
90
107
  to `[]`, destroying the information at parse time before any render option
91
108
  applies. Content-only classes round-trip every case.
92
109
 
93
- ## Remaining — 13 refs
110
+ ## Remaining — 6 refs
94
111
 
95
112
  **5 refs to 5 recursive roots**: `ElementCitation`, `PersonGroup`, `Collab`,
96
113
  `Source`, `TermDisplay`. Each reaches the same 78–79-element
97
114
  mutually-recursive core before existing IsoSts boundaries (`sec` → `p` →
98
- `disp-quote` → `p`). `DispQuote` and `BoxedText` themselves are now IsoSts
99
- classes and no longer in this list.
115
+ `disp-quote` → `p`). `DispQuote`, `BoxedText`, `Attrib`, `License`,
116
+ `TermHead`, and `CustomMetaGroup` are now IsoSts classes and no longer in
117
+ this list. The remaining 6 are the recursive roots — see
118
+ `TODO.roadmap/04-issue-40-recursive-roots.md` for the strategy.
100
119
 
101
120
  **8 refs to 5 child-bearing roots**, now measured after the first dependency
102
121
  layer: `attrib` (:1082) reaches 78 not-yet-modelled ISOSTS elements before
@@ -1,88 +1,53 @@
1
1
  # 09: Autoload and Require Cleanup
2
2
 
3
+ **Status**: Refreshed 2026-07-26. Most items done; remaining items noted below.
3
4
  **Priority**: LOW
4
5
  **Category**: Cleanup
5
- **Estimated Effort**: Medium
6
- **Files Affected**: `lib/sts.rb`, `lib/sts/niso_sts.rb`, `lib/sts/iso_sts.rb`
7
-
8
- ## Current Issues
9
-
10
- ### 1. Autoload Alphabetization
11
-
12
- The autoload entries in `lib/sts/niso_sts.rb` should be alphabetically ordered. Check for any out-of-order entries when adding new elements.
13
-
14
- ### 2. Moxml Monkey-Patch
15
-
16
- `lib/sts.rb` contains a monkey-patch on Moxml:
17
-
18
- ```ruby
19
- Moxml::Adapter::Nokogiri.singleton_class.alias_method(:_orig_children, :children)
20
- Moxml::Adapter::Nokogiri.define_singleton_method(:children, &:children)
21
- ```
22
-
23
- This patches an external library's behavior. If Moxml fixes the underlying issue, this will mask it or cause conflicts.
24
-
25
- **Action**: Add a comment explaining WHY this patch exists (the Nokogiri adapter bug it works around) and a link to the upstream issue if tracked.
26
-
27
- ### 3. Orphaned/Unused Files
28
-
29
- After refactoring (TODOs 01-04), check for:
30
- - Files in `lib/sts/niso_sts/` that are no longer referenced
31
- - Files in `lib/sts/iso_sts/` that are no longer referenced
32
- - Unused requires
33
-
34
- ### 4. Require Order Dependencies
35
-
36
- Some files may have implicit load-order dependencies (class A references class B, but B hasn't been loaded yet). The autoload system should handle this, but verify.
37
-
38
- ### 5. Missing Requires for Cross-Namespace Types
39
-
40
- After fixing namespace coupling (TODO 03), ensure all cross-namespace references are replaced with proper requires or base type references.
41
-
42
- ## File Structure After Cleanup
43
-
6
+ **Files Affected**: `lib/sts.rb`, `lib/sts/iso_sts.rb`, `lib/sts/niso_sts.rb`
7
+
8
+ ## Done
9
+
10
+ - **Moxml monkey-patch removed entirely** — upstream fix in moxml aa58888.
11
+ No code lives in `lib/sts.rb` patching `Moxml::Adapter::Nokogiri`.
12
+ - **`lib/sts/mathml.rb` deleted** (commit 9e977a5) — Presentation MathML
13
+ unified on the mml gem (`Mml::V3::Math` for NisoSts, `Mml::V2::Math` for
14
+ IsoSts/TbxIsoTml per the host-binding fix in PR #45).
15
+ - **`lib/sts/iso_sts/mathml2/` deleted** (this change) — dead-code hack file
16
+ that violated multiple anti-pattern rules. `IsoSts::Mathml2::Math` is gone.
17
+ - **`lib/sts/content_groups/` deleted** was dead code (the only caller,
18
+ `IsoSts::ContentGroups::HighlightElements`, was itself dead).
19
+ - **`lib/sts/tbx_iso_tml/fn.rb` and `fn_group.rb` deleted** (PR #47) —
20
+ duplicates of `NisoSts::Fn` / `NisoSts::FnGroup` with conflicting Ruby
21
+ attribute names.
22
+ - **Autoload registry is 1:1 with the directory tree** for both `iso_sts/`
23
+ and `niso_sts/`.
24
+ - **`require_relative` is forbidden in `lib/`** enforced by the
25
+ strengthened `spec/anti_patterns_spec.rb` (this change).
26
+
27
+ ## Current autoload conventions
28
+
29
+ - Top-level: `lib/sts.rb` declares `autoload` for each top-level namespace
30
+ (`IsoSts`, `NisoSts`, `TbxIsoTml`, `Namespaces`, `Profiles`, `VERSION`).
31
+ - Each namespace's `lib/sts/<namespace>.rb` file declares `autoload` for
32
+ every class in that namespace, organised by category with leading
33
+ comments. Alphabetical order within each category block.
34
+ - External gems are eagerly required at the top of `lib/sts.rb`
35
+ (`require "lutaml/model"`, `require "mml"`). Internal code never uses
36
+ `require` or `require_relative`.
37
+
38
+ ## Remaining items
39
+
40
+ - **`lib/sts/iso_sts.rb:3` has `require "mml"`** — redundant with the
41
+ top-level require in `lib/sts.rb:4`. Can be removed once verified no
42
+ load-order issue exists. Low priority.
43
+ - **Verify no implicit load-order deps remain** — the autoload system
44
+ should resolve all references lazily. If a circular dependency emerges,
45
+ it usually indicates a misnamed constant or a missing autoload entry.
46
+
47
+ ## Verification
48
+
49
+ ```bash
50
+ bundle exec rspec spec/anti_patterns_spec.rb # catches require_relative,
51
+ # internal require, etc.
52
+ bundle exec rake # full suite
44
53
  ```
45
- lib/
46
- ├── sts.rb # Top-level: requires, Moxml patch (documented)
47
- ├── sts/
48
- │ ├── namespaces.rb # Namespace constants
49
- │ ├── mathml.rb # MathML wrapper (after TODO 01 fix)
50
- │ ├── mathml/ # MathML-specific types (if needed)
51
- │ ├── base/ # Shared base types (after TODO 03)
52
- │ │ ├── text/ # Bold, Italic, Sub, Sup
53
- │ │ ├── struct/ # Section, Paragraph, List
54
- │ │ ├── meta/ # Title, Label, Caption
55
- │ │ └── refs/ # Xref, BiblioRef
56
- │ ├── iso_sts/ # ISOSTS namespace
57
- │ │ ├── iso_sts.rb # Autoload declarations
58
- │ │ ├── v1_1/ # ISOSTS v1.1 classes
59
- │ │ └── content_groups/ # Content group helpers
60
- │ ├── niso_sts/ # NISO STS namespace
61
- │ │ ├── niso_sts.rb # Autoload declarations
62
- │ │ ├── v1_0/ # NISO STS 1.0 classes
63
- │ │ ├── v1_2/ # NISO STS 1.2 classes
64
- │ │ ├── content_groups/ # Content group helpers
65
- │ │ ├── mml_content/ # MathML content elements
66
- │ │ └── xi.rb, ali.rb # Namespace modules
67
- │ ├── tbx_iso_tml/ # TBX namespace
68
- │ │ ├── tbx_iso_tml.rb # Autoload declarations
69
- │ │ └── ...
70
- │ ├── content_groups/ # Shared content group helpers
71
- │ │ └── helpers.rb # After TODO 02 fix
72
- │ └── profiles/ # ISO/IEC validation layer (after TODO 08)
73
- ```
74
-
75
- ## Dependencies
76
-
77
- - `01-mathml-delegation.md` — MathML wrapper location
78
- - `03-namespace-coupling.md` — base types directory structure
79
- - `04-register-versioning.md` — versioned subdirectories
80
-
81
- ## TODO Checklist
82
-
83
- - [x] Document Moxml monkey-patch with rationale (removed entirely — upstream fix in moxml aa58888)
84
- - [x] Audit autoload entries for alphabetical order
85
- - [x] After refactoring: remove orphaned files (deleted content_groups dead code)
86
- - [ ] After refactoring: verify no implicit load-order deps
87
- - [x] After refactoring: clean up unused requires
88
- - [x] Verify `bundle exec rake` passes cleanly