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 +4 -4
- data/.rubocop.yml +22 -9
- data/.rubocop_todo.yml +108 -1
- data/CLAUDE.md +13 -6
- data/README.adoc +132 -0
- data/lib/obp/access/element_registry.rb +3 -1
- data/lib/obp/access/elements/base.rb +8 -3
- data/lib/obp/access/elements/disp_formula.rb +53 -0
- data/lib/obp/access/elements/footnotes.rb +54 -0
- data/lib/obp/access/elements/non_normative_example.rb +28 -0
- data/lib/obp/access/elements/protected_content_note.rb +25 -0
- data/lib/obp/access/elements/root.rb +12 -20
- data/lib/obp/access/elements/table_wrap.rb +17 -3
- data/lib/obp/access/fetcher.rb +4 -1
- data/lib/obp/access/inline_renderer.rb +4 -1
- data/lib/obp/access/parser.rb +1 -1
- data/lib/obp/access/raw_html_parser.rb +41 -0
- data/lib/obp/access/renderer.rb +23 -11
- data/lib/obp/access/table_mapper.rb +117 -0
- data/lib/obp/access/table_term_extractor.rb +248 -0
- data/lib/obp/access/urn.rb +55 -0
- data/lib/obp/access/version.rb +1 -1
- data/lib/obp/access.rb +49 -2
- data/obp-access.gemspec +33 -0
- metadata +10 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b11820d71fdd40bfeeddd92447062fc728a2642a3bdd4b572fcb3162bd86230a
|
|
4
|
+
data.tar.gz: 4c061e02a1ead75b4db4828d7646d69052a44ed0369bb905c9a0aafb8f064a99
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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-
|
|
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. **`
|
|
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. **`
|
|
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
|
-
|
|
24
|
+
(self.class.classes - node.classes).empty?
|
|
22
25
|
end
|
|
23
26
|
|
|
24
27
|
def render(target:)
|
|
25
|
-
|
|
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")
|
|
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",
|
|
60
|
-
xml.edition
|
|
61
|
-
xml.version
|
|
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
|
-
|
|
88
|
+
caption&.split(/[[:space:]]/)&.first || urn.originator.upcase
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def ref
|
|
92
|
-
|
|
92
|
+
caption || urn.to_s
|
|
93
93
|
end
|
|
94
94
|
|
|
95
95
|
def ref_dated
|
|
96
|
-
|
|
96
|
+
caption&.gsub(/\([^()]*\)/, "") || ref
|
|
97
97
|
end
|
|
98
98
|
|
|
99
99
|
def ref_undated
|
|
100
|
-
@ref_undated ||=
|
|
100
|
+
@ref_undated ||= caption&.split(":")&.first || urn.doc_number
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
def copyright_year
|
|
104
|
-
|
|
104
|
+
caption&.[](/:(\d{4})/, 1)
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
-
def
|
|
108
|
-
|
|
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
|