obp-access 0.1.4 → 0.1.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b3fda155513c49b50f81b401262456e78f63e55fb0ced23f8a54bf4577ee0af
4
- data.tar.gz: 75070c8db35ae812f69f19bb55e536c9c9b3da5ec29a5846bd9d9cd53e6d5f2a
3
+ metadata.gz: b11820d71fdd40bfeeddd92447062fc728a2642a3bdd4b572fcb3162bd86230a
4
+ data.tar.gz: 4c061e02a1ead75b4db4828d7646d69052a44ed0369bb905c9a0aafb8f064a99
5
5
  SHA512:
6
- metadata.gz: fcf54d0abc492eae8542e14e3313199be54bce5eb45fd454996a3c69dc8d5c7641a8a4bd58bbde701320c142d051ab186441b4ce47d39f47dba1774d6b329562
7
- data.tar.gz: c19c3f486740f899e6e0ecad8479f78f34a7c9cf3cad8638c2eb5344408a2047e3f6f8e7222e31ea57ac7e1c79047709466c23795872c6bf238e8b076a31e77f
6
+ metadata.gz: 819be7f79f169115d5e0c486f09e8b061ac68fb85040e764832f0280e63094893c9d2da2428395f32179fff03625906f841ed4dea4020c7aa484873170603d49
7
+ data.tar.gz: ae43acfe12065482d2bfb872e59b0a221416664c43415a49078b206957e5190744b3ebafd60c7f78e5bb15821f04693f876f7de5127238cdabab29af95c125d4
data/.rubocop.yml CHANGED
@@ -1,18 +1,31 @@
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
5
+ # .rubocop_todo.yml MUST be the last entry. inherit_from is last-wins:
6
+ # if listed before oss-guides, the shared config's stricter Metrics/
7
+ # (MethodLength, BlockLength, etc.) rules override the todo's per-file
8
+ # grandfathering, and the todo becomes inert on those cops. Empirically
9
+ # verified on suma 2026-07-06: with todo listed first, 34 Metrics/* offenses
10
+ # remained; moved last, cleared. Every gem in the metanorma-org fleet
11
+ # already ships a `.rubocop_todo.yml` on its live tree (audited 2026-07-06,
12
+ # 54/54 have it), so this reference is safe to emit unconditionally.
4
13
  - .rubocop_todo.yml
5
- - https://raw.githubusercontent.com/riboseinc/oss-guides/master/ci/rubocop.yml
14
+
15
+ # Rubocop plugins enabled centrally so every metanorma-org gem picks them up
16
+ # on cimas sync — best practice belongs at the shared-template layer, not
17
+ # per-repo. Per ronaldtse feedback on metanorma/ci#332.
18
+ plugins:
19
+ - rubocop-rspec
20
+ - rubocop-performance
21
+ - rubocop-rake
6
22
 
7
23
  # local repo-specific modifications
8
24
  # ...
9
25
 
10
26
  AllCops:
11
- NewCops: enable
12
- SuggestExtensions: false
13
-
14
- Metrics/MethodLength:
15
- Max: 30
16
-
17
- Layout/LineLength:
18
- Max: 120
27
+ # 3.3 matches the org-wide minimum being pushed via #274 (Raise minimum
28
+ # Ruby version to 3.3 due to EOL of 3.2 on 2026-03-31). Was 3.4 before
29
+ # this commit — 3.4 was above the org's stated minimum and would have
30
+ # applied Rubocop rules that fail-close on gems still targeting 3.3.
31
+ TargetRubyVersion: 3.3
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2026-06-15 08:31:30 UTC using RuboCop version 1.86.1.
3
+ # on 2026-07-18 01:14:30 UTC using RuboCop version 1.88.2.
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
@@ -11,9 +11,116 @@ Gemspec/RequiredRubyVersion:
11
11
  Exclude:
12
12
  - 'obp-access.gemspec'
13
13
 
14
+ # Offense count: 139
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:
19
+ Exclude:
20
+ - 'fetch_en_html.rb'
21
+ - 'fetch_html.rb'
22
+ - 'lib/obp/access/catalog.rb'
23
+ - 'lib/obp/access/cli.rb'
24
+ - 'lib/obp/access/domain_extractor.rb'
25
+ - 'lib/obp/access/elements/base.rb'
26
+ - 'lib/obp/access/elements/bibliography.rb'
27
+ - 'lib/obp/access/elements/figure_group.rb'
28
+ - 'lib/obp/access/elements/index.rb'
29
+ - 'lib/obp/access/elements/root.rb'
30
+ - 'lib/obp/access/elements/terminology/definition.rb'
31
+ - 'lib/obp/access/elements/terminology/example.rb'
32
+ - 'lib/obp/access/elements/terminology/tig.rb'
33
+ - 'lib/obp/access/elements/terminology/tig_deprecated.rb'
34
+ - 'lib/obp/access/grammar_parser.rb'
35
+ - 'lib/obp/access/inline_renderer.rb'
36
+ - 'lib/obp/access/parser.rb'
37
+ - 'lib/obp/access/retriever.rb'
38
+ - 'obp-access.gemspec'
39
+ - 'spec/obp/access/access_spec.rb'
40
+ - 'spec/obp/access/cli_spec.rb'
41
+ - 'spec/obp/access/deliverable_spec.rb'
42
+ - 'spec/obp/access/elements/bib_ref_spec.rb'
43
+ - 'spec/obp/access/elements/disp_formula_spec.rb'
44
+ - 'spec/obp/access/elements/figure_group_spec.rb'
45
+ - 'spec/obp/access/elements/figure_spec.rb'
46
+ - 'spec/obp/access/elements/footnotes_spec.rb'
47
+ - 'spec/obp/access/elements/index_spec.rb'
48
+ - 'spec/obp/access/elements/non_normative_example_spec.rb'
49
+ - 'spec/obp/access/elements/protected_content_note_spec.rb'
50
+ - 'spec/obp/access/elements/section_title_spec.rb'
51
+ - 'spec/obp/access/elements/table_wrap_spec.rb'
52
+ - 'spec/obp/access/elements/terminology/definition_spec.rb'
53
+ - 'spec/obp/access/elements/terminology/tig_spec.rb'
54
+ - 'spec/obp/access/grammar_parser_spec.rb'
55
+ - 'spec/obp/access/inline_renderer_spec.rb'
56
+ - 'spec/obp/access/retriever_spec.rb'
57
+
58
+ # Offense count: 1
59
+ # This cop supports safe autocorrection (--autocorrect).
60
+ Lint/SafeNavigationWithEmpty:
61
+ Exclude:
62
+ - 'lib/obp/access/elements/disp_formula.rb'
63
+
14
64
  # Offense count: 3
15
65
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
16
66
  Metrics/AbcSize:
17
67
  Exclude:
18
68
  - 'lib/obp/access/elements/bibliography/bib_ref.rb'
19
69
  - 'lib/obp/access/elements/root.rb'
70
+
71
+ # Offense count: 11
72
+ # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
73
+ Metrics/MethodLength:
74
+ Max: 16
75
+
76
+ # Offense count: 1
77
+ # Configuration parameters: IgnoredMetadata.
78
+ RSpec/DescribeClass:
79
+ Exclude:
80
+ - '**/spec/features/**/*'
81
+ - '**/spec/requests/**/*'
82
+ - '**/spec/routing/**/*'
83
+ - '**/spec/system/**/*'
84
+ - '**/spec/views/**/*'
85
+ - 'spec/obp/access/elements/terminology/tig_spec.rb'
86
+
87
+ # Offense count: 44
88
+ # Configuration parameters: CountAsOne.
89
+ RSpec/ExampleLength:
90
+ Max: 26
91
+
92
+ # Offense count: 1
93
+ # This cop supports safe autocorrection (--autocorrect).
94
+ RSpec/IteratedExpectation:
95
+ Exclude:
96
+ - 'spec/obp/access/catalog_spec.rb'
97
+
98
+ # Offense count: 1
99
+ RSpec/MultipleDescribes:
100
+ Exclude:
101
+ - 'spec/obp/access/elements/terminology/tig_spec.rb'
102
+
103
+ # Offense count: 65
104
+ RSpec/MultipleExpectations:
105
+ Max: 11
106
+
107
+ # Offense count: 14
108
+ # Configuration parameters: CustomTransform, IgnoreMethods, IgnoreMetadata, InflectorPath, EnforcedInflector.
109
+ # SupportedInflectors: default, active_support
110
+ RSpec/SpecFilePathFormat:
111
+ Exclude:
112
+ - '**/spec/routing/**/*'
113
+ - 'spec/obp/access/access_spec.rb'
114
+ - 'spec/obp/access/elements/bib_ref_spec.rb'
115
+ - 'spec/obp/access/elements/disp_formula_spec.rb'
116
+ - 'spec/obp/access/elements/figure_group_spec.rb'
117
+ - 'spec/obp/access/elements/figure_spec.rb'
118
+ - 'spec/obp/access/elements/footnotes_spec.rb'
119
+ - 'spec/obp/access/elements/index_spec.rb'
120
+ - 'spec/obp/access/elements/non_normative_example_spec.rb'
121
+ - 'spec/obp/access/elements/protected_content_note_spec.rb'
122
+ - 'spec/obp/access/elements/root_spec.rb'
123
+ - 'spec/obp/access/elements/section_title_spec.rb'
124
+ - 'spec/obp/access/elements/section_type_spec.rb'
125
+ - 'spec/obp/access/elements/table_wrap_spec.rb'
126
+ - 'spec/obp/access/elements/terminology/definition_spec.rb'
data/CLAUDE.md CHANGED
@@ -9,10 +9,11 @@ Ruby gem (`obp-access`) that fetches informative content (introduction, scope, t
9
9
  ## Commands
10
10
 
11
11
  - **Install dependencies:** `bundle install`
12
- - **Run tests:** `bundle exec rspec`
12
+ - **Run tests:** `bundle exec rspec` (note: retriever/access-fetch specs hit the live OBP API)
13
13
  - **Lint:** `bundle exec rubocop`
14
14
  - **Run a single test:** `bundle exec rspec spec/obp/access/grammar_parser_spec.rb`
15
15
  - **Console:** `bin/console` (loads the gem for interactive use)
16
+ - **CLI:** `bundle exec exe/obp-access` (fetch a document or all languages from OBP)
16
17
 
17
18
  ## Critical rules
18
19
 
@@ -25,13 +26,15 @@ Ruby gem (`obp-access`) that fetches informative content (introduction, scope, t
25
26
 
26
27
  Data flows through these classes in `lib/obp/access/`:
27
28
 
28
- 1. **`Access`** (entry point) — orchestrates the pipeline. `Obp::Access.fetch(urn)` returns an instance per language. `Access.fetch_all(urn, languages:)` returns separate instances per language.
29
+ 1. **`Access`** (entry point) — orchestrates the pipeline. `Obp::Access.fetch(urn)` returns an instance per language. `Access.fetch_all(urn, languages:)` returns separate instances per language. `Access.from_html(urn:, html:, caption:, titles:)` converts an already-downloaded OBP HTML fragment with no network access (the offline path used by fixture-based tests).
29
30
 
30
- 2. **`Parser`** — fetches content from the ISO OBP API (`https://www.iso.org/obp/ui`) via HTTP POST with a URN payload. Parses the JSON response to extract HTML content, titles, and images.
31
+ 2. **`Parser`** — fetches content from the ISO OBP API (`https://www.iso.org/obp/ui`) via HTTP POST with a URN payload. Parses the JSON response to extract HTML content, titles, and images. **`RawHtmlParser`** subclasses it and duck-types the API for pre-captured HTML.
31
32
 
32
- 3. **`Converter`** — wraps the HTML source, normalizes whitespace, parses it with Nokogiri, and passes DOM nodes to the Renderer.
33
+ 3. **`Urn`** — parses URN identity metadata (`originator`, `doc_number` incl. part numbers like `80000:-12` → "80000-12", `doc_type` IS/TS/TR/PAS/Guide/IWA, `edition`, `version`). `Elements::Root` builds `<std-ident>` from these never parse URN segments positionally elsewhere.
33
34
 
34
- 4. **`Renderer`** — recursively walks DOM nodes and dispatches them to element classes registered in `ElementRegistry`. Each element class matches against CSS classes and builds NISO STS XML.
35
+ 4. **`Converter`** — wraps the HTML source, normalizes whitespace, parses it with Nokogiri, and passes DOM nodes to the Renderer.
36
+
37
+ 5. **`Renderer`** — recursively walks DOM nodes and dispatches them to element classes registered in `ElementRegistry`. Each element class matches against CSS classes and builds NISO STS XML.
35
38
 
36
39
  ### Element system
37
40
 
@@ -43,11 +46,15 @@ Data flows through these classes in `lib/obp/access/`:
43
46
 
44
47
  ### Supporting classes
45
48
 
46
- - **`GrammarParser`** — extracts part-of-speech and gender from bold term markup.
49
+ - **`GrammarParser`** — extracts part-of-speech and gender from bold term markup (POS markers `adj.`/`verb`, gender markers `m`/`f`/`n`, 〈bracketed〉 subject fields). Gender/POS only occur in non-English term entries (fr/de/ru); examples live in `spec/obp/access/grammar_parser_spec.rb`.
47
50
  - **`DomainExtractor`** — extracts subject-field domains from definition text.
48
51
  - **`InlineRenderer`** — renders inline HTML elements (links, xrefs, italic, bold, entailed terms) to STS XML.
49
52
  - **`Imager`** — downloads images from OBP. Uses `Parallel` for concurrent downloads.
50
53
 
54
+ ### Testing conventions
55
+
56
+ - Captured OBP HTML fixtures live in `obp-output/` at the repo root (gitignored — download them via the CLI or a browser). Conversion specs read them through `Access.from_html` and **skip** when the fixture is absent, so the offline suite never touches the network.
57
+
51
58
  ## Key dependencies
52
59
 
53
60
  - **sts** — NISO STS gem for generating STS objects from XML
data/README.adoc CHANGED
@@ -54,6 +54,27 @@ obp = Obp::Access.fetch("iso:std:iso:5598:ed-3:v1:en", languages: :all)
54
54
  obp.to_xml(pretty: true) # => NISO STS XML with all available language langSets
55
55
  ----
56
56
 
57
+ ==== Already-downloaded HTML
58
+
59
+ When the OBP page was fetched out of band (e.g. through a browser or
60
+ waffle-punch), build an Access instance from the raw HTML fragment and
61
+ convert it without any further network calls:
62
+
63
+ [source,ruby]
64
+ ----
65
+ obp = Obp::Access.from_html(
66
+ urn: "iso:std:iso:80000-12:ed-2:v2:en",
67
+ html: File.read("iso-80000-12-en.html"),
68
+ caption: "ISO 80000-12:2019",
69
+ titles: { "en" => "Quantities and units — Part 12: Solid state physics" },
70
+ )
71
+ obp.to_xml(pretty: true) # => NISO STS XML
72
+ obp.to_terms # => YAML string of table-carried terms
73
+ ----
74
+
75
+ `caption` is used to build the STS metadata (std-ref, copyright year); it
76
+ should match the document reference. `titles` is optional.
77
+
57
78
  ==== CLI
58
79
 
59
80
  Fetch a single document (English only):
@@ -86,6 +107,117 @@ The output is NISO STS XML with TBX-Basic terminology markup:
86
107
  * Domains are extracted as `<tbx:subjectField>`
87
108
  * Deprecated terms use `<tbx:normativeAuthorization value="deprecatedTerm"/>`
88
109
 
110
+ === Tables and terms as YAML
111
+
112
+ Besides STS XML, the same preview HTML can be read as YAML table data.
113
+ ISO standards that carry terminology in tables rather than TBX term
114
+ sections (the ISO 80000 quantity tables, "list of equivalent terms"
115
+ tables) become machine-readable through two views:
116
+
117
+ [source,ruby]
118
+ ----
119
+ obp = Obp::Access.fetch("iso:std:iso:80000-12:ed-2:v2:en")
120
+ obp.to_tables # => YAML string: every table in the document
121
+ obp.to_terms # => YAML string: term entries from term-carrying tables
122
+ ----
123
+
124
+ Both reuse the already-fetched preview HTML, so no extra OBP request is
125
+ made. A document without tables yields an empty YAML array (`--- []`).
126
+
127
+ ==== Generic tables (`to_tables`)
128
+
129
+ `Obp::Access::TableMapper` maps every table-carrying node —
130
+ `div.sts-table-wrap` and `div.sts-array`, in document order — to plain
131
+ hashes, arrays and strings:
132
+
133
+ [source,yaml]
134
+ ----
135
+ - id: iso_std_iso_80000-12_ed-2_v2_en_tab_1 # wrapper id, if present
136
+ section: '3' # containing clause ("index"/"bibl"/... stay raw)
137
+ label: Table 1 # .sts-caption-label, if present
138
+ caption: Quantities and units used in condensed matter physics
139
+ header:
140
+ - - text: Item No.
141
+ - text: Quantity
142
+ colspan: 3 # colspan/rowspan only when greater than 1
143
+ - text: Unit
144
+ - text: Remarks
145
+ rows:
146
+ - - text: 12-1.1
147
+ - text: lattice vector
148
+ - text: R
149
+ - text: translation vector that maps the crystal lattice on itself
150
+ - text: m
151
+ - text: The non-SI unit ångström (Å) is widely used ...
152
+ ----
153
+
154
+ Cell text is plain stripped text: OBP `sts-unknown-element` placeholder
155
+ spans are removed, and `<br>` / `div.sts-p` boundaries become spaces so
156
+ multi-paragraph cells do not glue words together.
157
+
158
+ ==== Term entries (`to_terms`)
159
+
160
+ `Obp::Access::TableTermExtractor` consumes the generic table model (not
161
+ raw HTML) and extracts entries from term-carrying tables. Recognition
162
+ rules, first match wins per table:
163
+
164
+ . *Quantity/item tables* (ISO 80000 family): at least 4 columns, the
165
+ table sits in a numbered clause, and a majority of body rows start
166
+ with an item number (`12-1.1`, `4-1`). Column roles are resolved from
167
+ the joined header text — this handles the two-row colspan headers in
168
+ both English (Item No. / Quantity / Unit / Remarks) and French
169
+ (N° / Grandeur / Unité / Remarques) — falling back to the ISO 80000
170
+ column order item, designation, symbol, definition, unit, remarks.
171
+ . *Equivalent-terms tables*: a header row naming languages in at least
172
+ 2 columns. One entry per row, with `designations` keyed by downcased
173
+ language name; rows with a single designation (letter dividers) are
174
+ skipped.
175
+ . Anything else is not a term table and yields no entries.
176
+
177
+ [source,yaml]
178
+ ----
179
+ - id: 12-1.1
180
+ designation: lattice vector
181
+ definition: translation vector that maps the crystal lattice on itself
182
+ symbol: R
183
+ unit: m
184
+ remarks: The non-SI unit ångström (Å) is widely used ...
185
+ source:
186
+ table: iso_std_iso_80000-12_ed-2_v2_en_tab_1
187
+ section: '3'
188
+ - id: '2'
189
+ designations:
190
+ english: absorption, atmospheric
191
+ francais: absorption atmosphérique
192
+ deutsch: atmosphärische Absorption
193
+ source:
194
+ section: '1.1'
195
+ ----
196
+
197
+ Only non-empty fields are emitted (`id` and `designation` are always
198
+ present); duplicate item numbers within one table keep the first row.
199
+ For equivalent-terms tables the entry `id` is the row's first cell when
200
+ it looks like an item number, else the 1-based row index.
201
+
202
+ ==== Known limitations
203
+
204
+ * Column mapping is header-text and position based; exotic header
205
+ layouts (rowspans in `thead`, role columns in unusual order) are not
206
+ resolved.
207
+ * Quantity tables are recognized only in numbered clauses and only when
208
+ a strict majority of rows start with an item number; continuation
209
+ rows (blank item number) are skipped rather than merged into the
210
+ previous entry.
211
+ * Item numbers must match `\d+-\d+(\.\d+)?` (U+2011 non-breaking hyphen
212
+ is normalized to `-`); rows printed differently in the source, such
213
+ as the `3.10.2` typo in ISO 80000-3, are skipped.
214
+ * Cell text is flattened: subscripts/superscripts, inline formulas and
215
+ images lose their markup (`a1` for a₁), and French gender markers stay
216
+ glued to designations (`vecteur du réseau, m`).
217
+ * Equivalent-terms designations are keyed by the downcased header text
218
+ as printed (`francais` in ISO 5843-6); mojibake headers (e.g.
219
+ `PyCCKMM` for Russian) are not recognized as languages.
220
+
89
221
  == Credits
90
222
 
91
223
  This gem is developed, maintained and funded by
@@ -11,8 +11,10 @@ module Obp
11
11
  @elements ||= []
12
12
  end
13
13
 
14
+ # Flattened unique class names across all registered elements, so
15
+ # callers can test membership against individual node classes.
14
16
  def css_classes
15
- @css_classes ||= elements.filter_map(&:classes).uniq
17
+ @css_classes ||= elements.filter_map(&:classes).flatten.uniq
16
18
  end
17
19
  end
18
20
  end
@@ -17,12 +17,17 @@ module Obp
17
17
  nil
18
18
  end
19
19
 
20
+ # Subset match: OBP varies class order and adds marker classes
21
+ # (e.g. "commentable"), so a node matches when it carries at
22
+ # least the registered classes, regardless of order or extras.
20
23
  def match_node?
21
- node.classes == self.class.classes
24
+ (self.class.classes - node.classes).empty?
22
25
  end
23
26
 
24
27
  def render(target:)
25
- effective_target = insertion_target || target
28
+ # Top-level nodes without their own insertion target (e.g. a
29
+ # floating sts-p between sections) default to the body.
30
+ effective_target = insertion_target || target || "body"
26
31
  effective_target = "#{effective_target}#{path_suffix}" if path_suffix
27
32
  document.at(effective_target).public_send(insert_method, to_xml)
28
33
  end
@@ -42,7 +47,7 @@ module Obp
42
47
  end
43
48
 
44
49
  def id
45
- @id ||= node.attr("id").split("_").last
50
+ @id ||= node.attr("id")&.split("_")&.last
46
51
  end
47
52
 
48
53
  def to_xml
@@ -0,0 +1,53 @@
1
+ module Obp
2
+ class Access
3
+ class Renderer
4
+ class Elements
5
+ class DispFormula < Base
6
+ def self.classes
7
+ %w[sts-disp-formula-panel]
8
+ end
9
+
10
+ private
11
+
12
+ def content
13
+ Nokogiri::XML::Builder.new do |xml|
14
+ xml.public_send(:"disp-formula") do
15
+ xml.label label if label
16
+ if math
17
+ xml << math.to_xml
18
+ elsif graphic_path
19
+ xml.graphic("xlink:href": graphic_path)
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ def label
26
+ text = node.at_css(".sts-disp-formula-label")&.text&.strip
27
+ text unless text&.empty?
28
+ end
29
+
30
+ def math
31
+ @math ||= node.at_css("math")
32
+ end
33
+
34
+ # OBP serves formula images from its own graphics store, which
35
+ # Imager does not download; fall back to the original src.
36
+ def graphic_path
37
+ @graphic_path ||= begin
38
+ img = node.at_css("img")
39
+ img && (stored_image_path(img) || img.attr("src"))
40
+ end
41
+ end
42
+
43
+ def stored_image_path(img)
44
+ images = metas["images"]
45
+ images[img.attr("src")] if images.is_a?(Hash)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ Obp::Access::ElementRegistry.register(Obp::Access::Renderer::Elements::DispFormula)
@@ -0,0 +1,54 @@
1
+ module Obp
2
+ class Access
3
+ class Renderer
4
+ class Elements
5
+ # OBP emits page footnotes as a trailing "sts-footnotes" container
6
+ # holding one "sts-fn" div per note, each text prefixed with the
7
+ # note number.
8
+ class Footnotes < Base
9
+ def self.classes
10
+ %w[sts-footnotes]
11
+ end
12
+
13
+ private
14
+
15
+ def insertion_target
16
+ "back"
17
+ end
18
+
19
+ def content
20
+ Nokogiri::XML::Builder.new do |xml|
21
+ xml.public_send(:"fn-group") do
22
+ node.css("div.sts-fn").each { |note| render_fn(xml, note) }
23
+ end
24
+ end
25
+ end
26
+
27
+ def render_fn(xml, note)
28
+ id = fn_id(note)
29
+ xml.fn(id: "fn_#{id}") do
30
+ xml.label id
31
+ xml.p fn_text(note)
32
+ end
33
+ end
34
+
35
+ def fn_id(note)
36
+ note.attr("id")&.split("_")&.last || note.object_id.to_s
37
+ end
38
+
39
+ def fn_text(note)
40
+ div = note.at_css("div")
41
+ if div
42
+ div.text.strip.sub(/\A\d+\s*/,
43
+ "")
44
+ else
45
+ note.text.strip.sub(/\A\d+\s*/, "")
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ Obp::Access::ElementRegistry.register(Obp::Access::Renderer::Elements::Footnotes)
@@ -0,0 +1,28 @@
1
+ module Obp
2
+ class Access
3
+ class Renderer
4
+ class Elements
5
+ # Renders the example label; the content div.sts-p children are
6
+ # rendered by Paragraph when the renderer recurses into them.
7
+ class NonNormativeExample < Base
8
+ def self.classes
9
+ %w[sts-non-normative-example]
10
+ end
11
+
12
+ private
13
+
14
+ def content
15
+ Nokogiri::XML::Builder.new do |xml|
16
+ xml.public_send(:"non-normative-example") do
17
+ label_node = node.at_css(".sts-non-normative-example-label")
18
+ xml.label label_node.text if label_node
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ Obp::Access::ElementRegistry.register(Obp::Access::Renderer::Elements::NonNormativeExample)
@@ -0,0 +1,25 @@
1
+ module Obp
2
+ class Access
3
+ class Renderer
4
+ class Elements
5
+ # OBP replaces paywalled sections with this placeholder ("Only
6
+ # informative sections of standards are publicly available...").
7
+ # Skip it explicitly: render nothing and halt recursion, so the
8
+ # placeholder text never leaks into the output.
9
+ class ProtectedContentNote < Base
10
+ def self.classes
11
+ %w[sts-protected-content-note]
12
+ end
13
+
14
+ # Accepts (and ignores) the render target; the nil return tells
15
+ # the renderer to halt recursion into this subtree.
16
+ def render(*)
17
+ nil
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ Obp::Access::ElementRegistry.register(Obp::Access::Renderer::Elements::ProtectedContentNote)
@@ -42,7 +42,7 @@ module Obp
42
42
  def std_meta_content(xml)
43
43
  render_titles(xml)
44
44
  xml.public_send(:"proj-id", ref_undated)
45
- xml.public_send(:"release-version", doc_type)
45
+ xml.public_send(:"release-version", urn.doc_type)
46
46
  render_std_ident(xml)
47
47
  xml.public_send(:"content-language", metas["language"])
48
48
  xml.public_send(:"std-ref", ref_dated, type: "dated")
@@ -55,10 +55,10 @@ module Obp
55
55
  def render_std_ident(xml)
56
56
  xml.public_send(:"std-ident") do
57
57
  xml.originator holder
58
- xml.public_send(:"doc-type", doc_type)
59
- xml.public_send(:"doc-number", urn_parts[3])
60
- xml.edition urn_parts[4].delete_prefix("ed-")
61
- xml.version urn_parts[5].delete_prefix("v")
58
+ xml.public_send(:"doc-type", urn.doc_type)
59
+ xml.public_send(:"doc-number", urn.doc_number)
60
+ xml.edition urn.edition
61
+ xml.version urn.version
62
62
  end
63
63
  end
64
64
 
@@ -85,35 +85,27 @@ module Obp
85
85
  end
86
86
 
87
87
  def holder
88
- metas["caption"].split.first
88
+ caption&.split(/[[:space:]]/)&.first || urn.originator.upcase
89
89
  end
90
90
 
91
91
  def ref
92
- metas["caption"]
92
+ caption || urn.to_s
93
93
  end
94
94
 
95
95
  def ref_dated
96
- metas["caption"].gsub(/\(.*?\)/, "")
96
+ caption&.gsub(/\([^()]*\)/, "") || ref
97
97
  end
98
98
 
99
99
  def ref_undated
100
- @ref_undated ||= metas["caption"].split(":").first
100
+ @ref_undated ||= caption&.split(":")&.first || urn.doc_number
101
101
  end
102
102
 
103
103
  def copyright_year
104
- metas["caption"][/:(\d{4})/, 1]
104
+ caption&.[](/:(\d{4})/, 1)
105
105
  end
106
106
 
107
- def doc_type
108
- case urn_parts[3]
109
- when "ts" then "TS"
110
- when "tr" then "TR"
111
- else "IS"
112
- end
113
- end
114
-
115
- def urn_parts
116
- @urn_parts ||= urn.raw.split(":")
107
+ def caption
108
+ metas["caption"]
117
109
  end
118
110
  end
119
111
  end