relaton-render 0.4.2 → 3.0.0.pre.alpha.26

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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake.yml +6 -1
  3. data/.github/workflows/release.yml +24 -0
  4. data/.gitignore +14 -0
  5. data/.rubocop.yml +2 -2
  6. data/Gemfile +17 -1
  7. data/README.adoc +13 -258
  8. data/docs/citation-style-pack.adoc +370 -0
  9. data/lib/relaton/render/general.rb +305 -0
  10. data/lib/relaton/render/iso690/element.rb +153 -0
  11. data/lib/relaton/render/iso690/elements/access.rb +44 -0
  12. data/lib/relaton/render/iso690/elements/authorizer.rb +30 -0
  13. data/lib/relaton/render/iso690/elements/citeid.rb +39 -0
  14. data/lib/relaton/render/iso690/elements/component_part.rb +99 -0
  15. data/lib/relaton/render/iso690/elements/creator.rb +258 -0
  16. data/lib/relaton/render/iso690/elements/edition.rb +92 -0
  17. data/lib/relaton/render/iso690/elements/extent.rb +71 -0
  18. data/lib/relaton/render/iso690/elements/identifier.rb +46 -0
  19. data/lib/relaton/render/iso690/elements/location.rb +73 -0
  20. data/lib/relaton/render/iso690/elements/medium.rb +32 -0
  21. data/lib/relaton/render/iso690/elements/numeration.rb +26 -0
  22. data/lib/relaton/render/iso690/elements/production.rb +92 -0
  23. data/lib/relaton/render/iso690/elements/pub_date.rb +56 -0
  24. data/lib/relaton/render/iso690/elements/series.rb +66 -0
  25. data/lib/relaton/render/iso690/elements/size.rb +41 -0
  26. data/lib/relaton/render/iso690/elements/status.rb +30 -0
  27. data/lib/relaton/render/iso690/elements/stddoc.rb +40 -0
  28. data/lib/relaton/render/iso690/elements/title.rb +56 -0
  29. data/lib/relaton/render/iso690/elements/updated.rb +38 -0
  30. data/lib/relaton/render/iso690/elements.rb +67 -0
  31. data/lib/relaton/render/iso690/fields.rb +142 -0
  32. data/lib/relaton/render/iso690/i18n/en.yml +47 -0
  33. data/lib/relaton/render/iso690/i18n/fr.yml +14 -0
  34. data/lib/relaton/render/iso690/i18n/ja.yml +38 -0
  35. data/lib/relaton/render/iso690/i18n.rb +83 -0
  36. data/lib/relaton/render/iso690/identifier_kinds.rb +29 -0
  37. data/lib/relaton/render/iso690/index_renderer.rb +57 -0
  38. data/lib/relaton/render/iso690/kinds.rb +53 -0
  39. data/lib/relaton/render/iso690/renderer.rb +148 -0
  40. data/lib/relaton/render/iso690/rules.rb +36 -0
  41. data/lib/relaton/render/iso690/style/locale.rb +53 -0
  42. data/lib/relaton/render/iso690/style/name_form.rb +87 -0
  43. data/lib/relaton/render/iso690/style/scheme.rb +33 -0
  44. data/lib/relaton/render/iso690/style/sort_rule.rb +25 -0
  45. data/lib/relaton/render/iso690/style/template_map.rb +40 -0
  46. data/lib/relaton/render/iso690/style/type_template.rb +37 -0
  47. data/lib/relaton/render/iso690/style.rb +222 -0
  48. data/lib/relaton/render/iso690/styles/apa.yml +45 -0
  49. data/lib/relaton/render/iso690/styles/author-date.yml +31 -0
  50. data/lib/relaton/render/iso690/styles/chicago.yml +48 -0
  51. data/lib/relaton/render/iso690/styles/ieee-sa.yml +45 -0
  52. data/lib/relaton/render/iso690/styles/lncs.yml +54 -0
  53. data/lib/relaton/render/iso690/template.rb +248 -0
  54. data/lib/relaton/render/iso690.rb +27 -0
  55. data/lib/relaton/render/version.rb +1 -1
  56. data/lib/relaton-render.rb +11 -5
  57. data/relaton-render.gemspec +6 -13
  58. metadata +61 -116
  59. data/lib/isodoc/i18n.rb +0 -33
  60. data/lib/isodoc-yaml/i18n-ar.yaml +0 -44
  61. data/lib/isodoc-yaml/i18n-de.yaml +0 -43
  62. data/lib/isodoc-yaml/i18n-en.yaml +0 -40
  63. data/lib/isodoc-yaml/i18n-es.yaml +0 -47
  64. data/lib/isodoc-yaml/i18n-fr.yaml +0 -48
  65. data/lib/isodoc-yaml/i18n-ru.yaml +0 -51
  66. data/lib/isodoc-yaml/i18n-zh-Hans.yaml +0 -40
  67. data/lib/relaton/render/fields/date.rb +0 -45
  68. data/lib/relaton/render/fields/fields.rb +0 -245
  69. data/lib/relaton/render/general/citations.rb +0 -96
  70. data/lib/relaton/render/general/config.yml +0 -79
  71. data/lib/relaton/render/general/render.rb +0 -185
  72. data/lib/relaton/render/general/render_classes.rb +0 -129
  73. data/lib/relaton/render/parse/parse.rb +0 -64
  74. data/lib/relaton/render/parse/parse_contributors.rb +0 -132
  75. data/lib/relaton/render/parse/parse_extract.rb +0 -180
  76. data/lib/relaton/render/template/liquid.rb +0 -15
  77. data/lib/relaton/render/template/template.rb +0 -188
  78. data/lib/relaton/render/utils/utils.rb +0 -27
@@ -0,0 +1,370 @@
1
+ = Defining a Citation Style: the Pack Format
2
+
3
+ This document is the authoritative authoring specification for a
4
+ CitationStyle pack: the one file that fully describes a citation style.
5
+ It is the contract between three parties — style authors, the rendering
6
+ engine, and the relaton-models citation schema.
7
+
8
+ Status markers: **[impl]** shipped on this engine line; **[spec]** the
9
+ committed design this engine line converges to (tracked in the
10
+ promotion backlog, section 12).
11
+
12
+ == 1. The laws
13
+
14
+ 1. **Models are ISO 690.** The relaton-models citation module
15
+ (`Relaton::Bib::Item` and friends) is the single data substrate for
16
+ every style. There are no LNCS models, no Chicago models. A
17
+ bibliographic item is parsed, stored and exchanged once.
18
+ 2. **A citation style is one encapsulated data pack.** Everything a
19
+ style knows lives in the pack: the scheme (name forms, citation
20
+ system), the locale (labels, punctuation), the presentation
21
+ templates (citation, reference, per-type patterns), and the
22
+ presentation-of-models (how each 690 model element presents).
23
+ Nothing lives in flavor Ruby.
24
+ 3. **The pack's field vocabulary is a superset of CSL-JSON.** Every
25
+ CSL-JSON term has a home; the standards-specific extensions CSL
26
+ lacks (designations/docids, typed stages, host relations, ordinal
27
+ editions, bilingual and CJK forms) extend it. The same is true of
28
+ the logic vocabulary (section 8): it supersets CSL's condition
29
+ grammar, not a general template engine.
30
+ 4. **Engines hold zero styles.** The engine is a generic evaluator of
31
+ packs. Canonical packs (the ISO 690 default, LNCS, Chicago,
32
+ IEEE-SA) ship beside the engine while the vocabulary co-evolves and
33
+ extract to a data repository once it stabilises — the CSL precedent
34
+ being a separate styles distribution consumed by many processors.
35
+ 5. **No Liquid.** The template language is the chunk grammar
36
+ (section 8): declarative, schema-validated, sandboxed, identical in
37
+ Ruby and relaton-ts. A fenced `procedural:` escape may exist for
38
+ experimental packs; canonical styles never reference it.
39
+
40
+ == 2. The pack file
41
+
42
+ One YAML document. **[impl]** Loaded and validated by the
43
+ CitationStyle model (`Relaton::Render::Iso690::Style.load` accepts a
44
+ path or a hash; unknown keys are rejected). **[spec]** The model
45
+ itself is owned by relaton-models' citation module; this engine loads
46
+ it, and relaton-ts loads its mirror.
47
+
48
+ Skeleton — every key, in order:
49
+
50
+ ....
51
+ name: "ISO 690 (metanorma-iso flavour)"
52
+ family: iso690 # [spec] iso690 | lncs | chicago | ieee-sa
53
+ extends: null # [spec] name of a base pack this deltas
54
+ requires: # [spec] capability gates this pack needs
55
+ - extent_group_separator
56
+
57
+ scheme:
58
+ system: name-date # name-date | numeric | designation
59
+ shortFromReference: true
60
+ homeDocidType: [ISO, IEC] # docid types that mark a "home" standard
61
+ defaultKind: "" # kind for types the taxonomy leaves unmapped
62
+ nameForm: { ... } # section 7
63
+ locale: { ... } # section 6
64
+
65
+ templates: # style-wide templates, section 5
66
+ titleOpen: "_"
67
+ titleClose: "_"
68
+ citation: "{{surname}}, {{date}}"
69
+ reference: "{{creator}}. {{title}}. ..."
70
+ name: "{{surname}}, {{givenNames}}"
71
+ series: ""
72
+ creators: ""
73
+ uri: "<fmt-link target='{{uri}}'>{{uri}}</fmt-link>"
74
+ uriShort: ""
75
+ identifierMode: all # all | first
76
+
77
+ types: # [spec] section 4: the resource-type taxonomy
78
+ article-journal: { alias: continuing }
79
+ standard: { alias: report }
80
+
81
+ perType: # section 5: the presentation patterns
82
+ - type: report
83
+ home: true
84
+ template: "{{title}}"
85
+ title: "<span class='stddocTitle'>{{title}}</span>"
86
+ short: "{{title}}, {{extent}}"
87
+ - type: monograph
88
+ noPlace: true
89
+ fallbacks: { date: "n.d" }
90
+ template: "{{creator}}{{role}}. {{title}}. ..."
91
+
92
+ sortKey: # index sorting
93
+ - attribute: creator
94
+ - attribute: date
95
+
96
+ rules: # [spec] section 9: presentation-of-models
97
+ identifier: itu # named engine rule selections
98
+ creator: nist_series_publisher
99
+
100
+ postProcess: # [spec] ordered gsub rule chain
101
+ - pattern: "。+"
102
+ replace: "。"
103
+ ....
104
+
105
+ == 3. Lifecycle
106
+
107
+ * **Adopt** — a flavor points its `bibrenderer` at a named canonical
108
+ pack. Zero code, zero YAML. The target state for new and generic
109
+ flavors.
110
+ * **Inherit-and-override** — **[spec]** `extends: <pack>` plus a delta.
111
+ The merge semantics are part of this specification (section 11):
112
+ `perType`/`types` merge per type key (a delta re-declaring `article`
113
+ never drops the base's `monograph`); `labels`, `punct`, `fallbacks`
114
+ and rule selections merge per key; scheme scalars replace. The
115
+ merge is deterministic and identical in Ruby and TS.
116
+ * **Own pack** — a fully self-contained instance. Last resort; the
117
+ existing flavor YAMLs are this, and most should collapse into deltas
118
+ over a canonical base.
119
+
120
+ **[spec] `requires:`** names engine capabilities, so a resolver fails
121
+ loudly (`pack requires extent_group_separator`) instead of silently
122
+ mis-rendering. Capability names are frozen per engine minor line.
123
+
124
+ == 4. Info-resource-types
125
+
126
+ Different information resource types present with different patterns
127
+ in nearly every citation style. The pack's type taxonomy is the axis
128
+ those patterns hang on.
129
+
130
+ **[impl]** The engine normalises a bibliographic item's wire `type`
131
+ value to an ISO 690 clause-8 resource *kind* through the Kinds table;
132
+ perType entries select on kinds. **[spec]** The taxonomy becomes pack
133
+ data (`types:`), aligned name-for-name with the CSL resource types —
134
+ the same superset law as the fields — with the standards extensions
135
+ CSL lacks.
136
+
137
+ .Kind taxonomy (ISO 690 clause 8) and its wire-type synonyms [impl]
138
+ [cols="1,3"]
139
+ |===
140
+ |Kind |Wire types
141
+
142
+ |`monograph` |book, booklet, manual, proceedings, thesis
143
+ |`report` |standard, techreport, report *(the clause 8.11 fallback)*
144
+ |`patent` |patent
145
+ |`continuing` |journal, serial, article-journal, article-magazine
146
+ |`component_part` |inbook, incollection, inproceedings
147
+ |`serial_part` |article
148
+ |`online` |software, unpublished, dataset, webpage, online
149
+ |`webdoc` |website, webresource
150
+ |===
151
+
152
+ **[spec]** The full recommended taxonomy carries every CSL type
153
+ (`article-journal`, `article-magazine`, `article-newspaper`, `bill`,
154
+ `book`, `broadcast`, `chapter`, `classic`, `collection`, `dataset`,
155
+ `document`, `entry`, `entry-dictionary`, `entry-encyclopedia`,
156
+ `event`, `figure`, `graphic`, `hearing`, `interview`, `legal_case`,
157
+ `legislation`, `manuscript`, `map`, `motion_picture`, `musical_score`,
158
+ `pamphlet`, `paper-conference`, `patent`, `performance`, `periodical`,
159
+ `personal_communication`, `post`, `post-weblog`, `regulation`,
160
+ `report`, `review`, `review-book`, `software`, `song`, `speech`,
161
+ `standard`, `statute`, `thesis`, `treaty`, `webpage`) plus the
162
+ relaton extensions (`techreport`, `inbook`, `incollection`,
163
+ `inproceedings`, `webresource`, `webdoc`, `online`). Rules of the
164
+ taxonomy:
165
+
166
+ * **Complete, explicit coverage.** Every type resolves to a declared
167
+ pattern — through an entry, an `alias`, or the declared `fallthrough`
168
+ (`report`). Nothing inherits silently.
169
+ * **Aliasing is declared**, never engine-hardcoded: `booklet: book`.
170
+ * **Home/external splits sit on top** — for standards-producing
171
+ bodies, the *same* type carries two patterns, selected by whether
172
+ the item bears one of `scheme.homeDocidType`'s designations.
173
+ IEEE-SA's `standard` pattern is *only* designation + title; a
174
+ Chicago `standard` is cited differently again — same type, different
175
+ packs, different patterns.
176
+
177
+ == 5. Presentation patterns (`perType`)
178
+
179
+ The pattern — not the template — is the unit. A journal-article
180
+ pattern is not a reordered book template; it is its own arrangement
181
+ (quoted title form, italic container, vol/no/pp placement,
182
+ month-abbreviated dates) *plus* its own short cite, fallbacks and
183
+ terminator behaviour. The perType entry carries all of it:
184
+
185
+ [cols="1,3"]
186
+ |===
187
+ |Field |Meaning
188
+
189
+ |`type` |the kind (or **[spec]** taxonomy type) this pattern serves
190
+ |`template` |the reference (bibliography entry) arrangement
191
+ |`title`, `series` |per-slot form overrides for this pattern
192
+ |`short` |the short-cite template (falls back to the reference in short mode)
193
+ |`home` |`true` marks the pattern serving items bearing a home docid; two entries of one type split on it
194
+ |`fallbacks` |absent-slot replacement texts, per slot (`date: "n.d"`, `dategroup: "(n.d.)"`)
195
+ |`noPlace` |render the locale's `no_place` label when production lacks a place
196
+ |===
197
+
198
+ Multiple entries of one `type` resolve: home-flagged variants split by
199
+ the home test; otherwise the first match wins; unmatched kinds fall to
200
+ `templates.reference`.
201
+
202
+ == 6. Locale, labels and punctuation
203
+
204
+ **[impl]** Packs overlay the engine language packs (`i18n/<lang>.yml`)
205
+ per language; the pack wins. Adding a language is a new language pack,
206
+ never code.
207
+
208
+ * `locale.and|others|noDate|noAuthor|in|at|availableAt` — connective
209
+ strings.
210
+ * `locale.labels` — arbitrary label overrides merged verbatim:
211
+ `and`, `oxford_comma`, `list_separator`, `series_no`, `no_place`,
212
+ `available_from`, `ed`/`eds`, `edition_ordinal`/`edition_cardinal`/
213
+ `version_cardinal` (locale templates, `{{ var1 }}` with the
214
+ `ordinal_word` filter), extent unit templates (`volume: "vol. %"`,
215
+ `page: "%頁"` — the `%` carries the value, so a CJK locale places
216
+ its unit after the number), `extent_group_separator`, month names.
217
+ * `locale.punct` — typographic punctuation: `production_sep`,
218
+ `production_order` (`publisher_first` for LNCS-style orders),
219
+ `open-paren`/`close-paren` (ASCII vs fullwidth), `cjk-latin-separator`,
220
+ `size-join`, `marker-space`.
221
+ * **[spec] `postProcess:`** — the CJK spacing rules that flavors
222
+ carry as Ruby today become ordered `pattern/replace` rule chains in
223
+ the pack, so bilingual typography travels with the style.
224
+
225
+ == 7. Name forms (`scheme.nameForm`) [impl]
226
+
227
+ Every knob of creator presentation:
228
+
229
+ [cols="1,3"]
230
+ |===
231
+ |Knob |Effect
232
+
233
+ |`initials`, `initialsSeparator`, `initialsPeriod` |initials vs given names, "A. B." vs "AB"
234
+ |`invertedAll`, `givenNameFirst` |surname-first for all names / direct order
235
+ |`surnameUpcase`, `subsequentSurnameUpcase`, `subsequentInitials` |caps and second-name forms
236
+ |`completenameUpcase`, `orgUpcase` |verbatim names and organisations
237
+ |`editorsMarked`, `loneEditorMarked`, `rolePlacement` |"(eds.)" markers and their placement
238
+ |`listStyle`, `creatorFallback` |join style; fallback source when no personal creator (OGC: `publisher_abbrev`)
239
+ |`etalCount`, `etalDisplay` |bibliography et-al truncation
240
+ |`inTextEtalCount`, `inTextEtalDisplay` |in-text et-al truncation
241
+ |===
242
+
243
+ == 8. The template language
244
+
245
+ **[impl]** Templates are chunk strings: `{{slot}}` fields and literal
246
+ text. The evaluator carries the segment-join semantics — a literal
247
+ renders when it separates two present fields, so absent elements
248
+ swallow their separators; leading and trailing literals drop with
249
+ their fields; parenthetical literals (`({{series}})`, `({{series}})`)
250
+ pair as groups that drop whole when the field is absent, balanced on
251
+ the template's literal parens alone; stacked separators collapse;
252
+ trailing terminators apply per style. The short-cite evaluator splits
253
+ after the leading field run at the first-biblio delimiter, which the
254
+ presentation layer turns into the cross-reference capsule.
255
+
256
+ Slots resolve to the 690 model presentations: `creator`, `title`,
257
+ `edition`, `medium`, `series`, `production`, `date`, `numeration`,
258
+ `component_part`, `identifier`, `location`, `size`, `extent`,
259
+ `access`, `stddoc`, `status`, `citeid`, `authorizer`, `updated`;
260
+ derived fields `role`, `dategroup` (the parenthesised date group),
261
+ `disambiguateddate` (the batch-suffixed date, "2022a"); and the name
262
+ fields (`surname`, `givenNames`, ...).
263
+
264
+ **[spec]** The logic vocabulary supersets CSL's condition grammar:
265
+
266
+ * **Conditionals** — `choose`/`if`/`else-if` over `type`, field
267
+ presence, `home`, and `disambiguated` ( CSL `position`-adjacent ),
268
+ as declarative blocks inside `perType` entries and templates.
269
+ * **Fallback chains** — `creator: [authors, publisher_abbrev]`:
270
+ first present source wins, replacing today's single `creatorFallback`
271
+ knob and OGC's hack.
272
+ * No loops, no arbitrary filters, no string mutation — those are
273
+ `postProcess` rules or named rules, never template logic.
274
+
275
+ == 9. Model presentation templates (`rules`) — presentation-of-models
276
+
277
+ Each 690 model element has a declarative presentation vocabulary; the
278
+ pack selects per-slot rule implementations by name. **[impl]** the
279
+ declarative surface (extent unit `%`-templates, production orders,
280
+ group separators, locale templates, fallbacks); **[spec]** the
281
+ registry: engine implements each named rule once, conformance-tested
282
+ per rule, mirrored in relaton-ts, and addressable as pack data —
283
+ `identifier: itu`, `creator: nist_series_publisher`.
284
+
285
+ The promotion backlog — every flavor element override still standing
286
+ is a rule to promote:
287
+
288
+ [cols="2,2"]
289
+ |===
290
+ |Flavor override |Becomes rule
291
+
292
+ |`ItuIdentifier` |`identifier: itu`
293
+ |`NistIdentifier`, `NistCreator`, `NistDate`, `SerialDate`, `NistSeries`, `NistComponentPart`, `NistPublisher`, `Draft` |the NIST author-year rule set
294
+ |`IeeeIdentifier`, `IeeeComponentPart`, `IeeeAccess`, `IeeeMedium` |the IEEE-SA rule set (designation-first standards)
295
+ |`BipmCreator`, `BipmComponentPart`, `BipmProduction`, `BipmExtent`, `BipmMedium`, `BipmEdition`, `BipmVolume` |the BIPM rule set
296
+ |`IhoEdition`, `IhoCreator` |affiliation-composite creators
297
+ |`JisComponentPart`, `JisSeries`, `JisExtent` |the JIS host-citation rules
298
+ |`BsiStatus` |status presentation
299
+ |===
300
+
301
+ == 10. Citation forms
302
+
303
+ **[impl]** The renderings capsule every pack produces (the isodoc
304
+ contract): `author`, `date`, `citation{full, default, short,
305
+ author_date, author_date_br, author, date, reference_tag, title,
306
+ title_reference_tag}`, `formattedref`. `templates.citation` is the
307
+ in-text form; `shortFromReference` takes the reference form with the
308
+ first-biblio marker; batch disambiguation suffixes derive from
309
+ author+date collisions; `identifierMode` governs `default` (first vs
310
+ all designations). **[spec]** `system: numeric | designation` selects
311
+ numeric `[1]`/`[B4]` and designation-led citation systems — the
312
+ IEEE-SA and JIS/BSI homes — as first-class engines, not author-date
313
+ approximations.
314
+
315
+ == 11. Deltas and the flavor triangle
316
+
317
+ **[spec]** `extends` resolves base-then-delta at load, on model
318
+ instances (never ad-hoc hash merges):
319
+
320
+ * `perType`, `types`: merge **per type key** — a delta's `article`
321
+ entry overlays the base's; the base's other entries survive.
322
+ * `labels`, `punct`, `fallbacks`: per-key merge.
323
+ * `nameForm` knobs, `templates.*`, rule selections: replace — with
324
+ the caveat that a knob left at its model default counts as unset and
325
+ inherits (a Lutaml model cannot distinguish an explicit default from
326
+ an absent key).
327
+ * `sortKey`, `requires`: union.
328
+
329
+ The forcing test: PLATEAU's four near-copy styles (document-language
330
+ x item-language quadrants) collapse to `extends: jis` plus two
331
+ punct/label deltas. If the merge cannot express that, the merge is
332
+ wrong.
333
+
334
+ == 12. Validation, versioning, conformance
335
+
336
+ * **Schema** — the CitationStyle model validates every pack; unknown
337
+ keys fail at load, not at render. [impl]
338
+ * **`requires:`** — capability gating, frozen names per engine minor
339
+ line. [spec]
340
+ * **Conformance corpora** — every canonical pack ships a corpus of
341
+ examples with expected renderings, in the iso690-test tradition:
342
+ LNCS from Springer's authors' instructions; Chicago from CMOS;
343
+ IEEE-SA from the Standards Style Manual clause 17 (`[B1] ASME
344
+ BPVC-I-2004, Title.` designation-only standards; Chicago-derived
345
+ books/articles with only the first name inverted). CI renders the
346
+ matrix: every declared type, every pattern, both engine ports.
347
+ * **Portability & safety** — packs are data: they validate, sandbox
348
+ and render identically server-side (the CF Workers API) and in
349
+ relaton-ts. No interpreters, no Liquid, no flavor Ruby.
350
+
351
+ == 13. Authoring walkthrough (LNCS, abridged)
352
+
353
+ . Declare the pack: `family: lncs`, `extends: null` (canonical),
354
+ `requires: [production_order, extent_units]`.
355
+ . Name forms: initials without periods, `invertedAll`, direct-order
356
+ subsequent names, `creatorFallback: [publisher_abbrev]` for
357
+ standards with no personal authors.
358
+ . Locale: `production_order: publisher_first`, `production_sep: ", "`,
359
+ year trailing in parentheses (the `dategroup` form).
360
+ . Types and patterns: `paper-conference` ("`{{creator}}: {{title}}.
361
+ In: {{componentpart}} {{extent}} {{production}} {{dategroup}}.`"),
362
+ `chapter`, `article-journal`, `book`, `thesis`, `webpage` — one
363
+ entry each, own `title`/`series` forms, own `short` cites.
364
+ . Rules: `identifier: lncs-series` (LNCS vol./pp. forms).
365
+ . Conformance: the Springer instruction examples become the corpus;
366
+ the pack merges only when the matrix is green.
367
+
368
+ The same walk with CMOS examples yields Chicago; with the IEEE-SA
369
+ manual, `family: ieee-sa` — whose `standard` pattern is two fields
370
+ long.
@@ -0,0 +1,305 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Render
5
+ # Compatibility entry point (relaton-render#90).
6
+ #
7
+ # The v2 engine replaced the liquid/config/i18n machinery with the
8
+ # template evaluator over CitationStyle instances
9
+ # (Relaton::Render::Iso690). isodoc main's render-isodoc subclasses
10
+ # this constant, so it must exist for all-git-main stacks to boot.
11
+ # This facade delegates rendering to the v2 engine; the retired
12
+ # options (config:, template overrides in liquid syntax) are ignored
13
+ # with one warning — migrate to a CitationStyle instance, which
14
+ # expresses everything the old config.yml did (see the citation
15
+ # module in relaton-models).
16
+ class General
17
+ # The eref `style=` vocabulary metanorma-standoc validates against
18
+ # (cleanup/xref.rb reads citetemplate.template_raw's keys) — the
19
+ # 1.x default config's top-level citation styles.
20
+ CITE_STYLES = %i[
21
+ author_date author_date_br author date reference_tag title
22
+ title_reference_tag short
23
+ ].freeze
24
+
25
+ CiteTemplate = Struct.new(:template_raw)
26
+
27
+ def initialize(options = {})
28
+ options = deep_symbolize(options)
29
+ @lang = options[:language] || options[:lang] || "en"
30
+ renderer_opts = { lang: @lang, script: options[:script] || "Latn",
31
+ labels: options[:i18nhash] || {} }
32
+ # A flavor names its own CitationStyle instance (name or YAML path)
33
+ options[:style] and renderer_opts[:style] = options[:style]
34
+ @renderer = Iso690::Renderer.new(**renderer_opts)
35
+ @renderer_opts = renderer_opts
36
+ @renderers_by_lang = {}
37
+ warn_general_config if options[:config]
38
+ end
39
+
40
+ # A collection's items may each declare their own language; the
41
+ # renderer localizes by language, so a reference renders in its
42
+ # own language within a document of another (the 1.x plateau
43
+ # per-reference behaviour)
44
+ def renderer_for(lang)
45
+ return @renderer if lang.nil? || lang == @lang
46
+
47
+ @renderers_by_lang[lang] ||= Iso690::Renderer.new(
48
+ **@renderer_opts.merge(lang: lang,
49
+ script: %w(ja ko zh).include?(lang) ? nil : "Latn")
50
+ )
51
+ end
52
+
53
+ def render(model, embedded: false, **opts)
54
+ model = to_model(model)
55
+ text =
56
+ if model.respond_to?(:formattedref) && model.formattedref
57
+ model.formattedref.content
58
+ else
59
+ @renderer.render(model, **opts)
60
+ end
61
+ embedded ? text : "<formattedref>#{text}</formattedref>"
62
+ end
63
+
64
+ def citation(model, **opts)
65
+ @renderer.citation(to_model(model), **opts)
66
+ end
67
+
68
+ # isodoc's references rendering: renderings[id][:formattedref]
69
+ # feeds the <formattedref> of every bibitem (isodoc
70
+ # presentation_function/refs.rb).
71
+ def render_all(bib, type: "author-date")
72
+ items = facade_bibitems(bib) or return nil
73
+ disambiguators = date_disambiguators(items)
74
+ items.each_with_object({}).with_index do |(item, m), i|
75
+ renderer = renderer_for(Array(item.language).first)
76
+ ref = begin
77
+ renderer.render(item, disambiguator: disambiguators[item.id])
78
+ rescue ::Relaton::Render::Unrenderable
79
+ next
80
+ end
81
+ m[item.id] = {
82
+ author: renderer.in_text_author(item),
83
+ date: renderer.disambiguated_date(item,
84
+ disambiguator:
85
+ disambiguators[item.id]),
86
+ citation: citation_renderings(item, ref, renderer,
87
+ type: type,
88
+ disambiguator:
89
+ disambiguators[item.id]),
90
+ formattedref: terminate_reference(ref, item),
91
+ }
92
+ end
93
+ end
94
+
95
+ # isodoc's styled citations consume these keys: the default is the
96
+ # authoritative identifier; the short cite is the style's citation,
97
+ # falling back to the reference rendering (the 1.x short-cite)
98
+ FIRST_DELIM = '<span class="fmt-first-biblio-delim"/>'
99
+
100
+ # A creator list alone (flavors' document-history name forms)
101
+ def creator_names(item)
102
+ @renderer.render_creators(to_model(item))
103
+ end
104
+
105
+ # render_all feeds the bibliography list: a reference not ending
106
+ # in the biblio terminator takes one (the 1.x render1 behaviour;
107
+ # single-item render stays verbatim)
108
+ # The batch disambiguation suffixes ("2022a"): items sharing a
109
+ # principal-creator surname and a date take alphabetical
110
+ # suffixes in bibliography order (the 1.x
111
+ # disambig_author_date_citations)
112
+ def date_disambiguators(items)
113
+ keys = items.filter_map do |item|
114
+ begin
115
+ [item.id, @renderer.author_key(item),
116
+ @renderer.citation(item)]
117
+ rescue ::Relaton::Render::Unrenderable
118
+ nil
119
+ end
120
+ end
121
+ groups = keys.group_by { |_, author, cite| [author, cite] }
122
+ suffixes = {}
123
+ groups.each_value do |group|
124
+ next if group.size < 2
125
+
126
+ group.each_with_index do |(id, _author, _cite), i|
127
+ suffixes[id] = ("a".ord + i).chr
128
+ end
129
+ end
130
+ suffixes
131
+ end
132
+
133
+ # The bibliography terminator; flavors override to suppress it
134
+ # (1.x use_terminator?), receiving the item it terminates
135
+ def terminate_reference(ref, _item = nil)
136
+ return ref if ref.empty? || ref.rstrip.end_with?(".")
137
+
138
+ "#{ref}."
139
+ end
140
+
141
+ # The style-keyed capsule ("citation" => { author_date: "..." })
142
+ # is what isodoc's styled erefs consume (their style attribute
143
+ # names the key)
144
+ def citation_renderings(item, ref, renderer, type: "author-date",
145
+ disambiguator: nil)
146
+ # A style declaring shortFromReference takes the reference form
147
+ # with the first-biblio marker (the 1.x citetemplate short)
148
+ short = if renderer.short_from_reference?
149
+ renderer.render_short(item, FIRST_DELIM)
150
+ else
151
+ short = renderer.citation(item)
152
+ short.empty? ? renderer.render_short(item, FIRST_DELIM) :
153
+ short
154
+ end
155
+
156
+ title = renderer.title_citation(item)
157
+
158
+ {
159
+ full: ref,
160
+ default: item.docidentifier.first&.content.to_s,
161
+ short: short,
162
+ author_date: renderer.citation(item,
163
+ disambiguator: disambiguator),
164
+ author_date_br: renderer.bracketed_date_citation(
165
+ item, disambiguator: disambiguator,
166
+ ),
167
+ author: renderer.in_text_author(item),
168
+ date: renderer.disambiguated_date(item),
169
+ # reference_tag: no rendering — isodoc falls back to the
170
+ # biblio-tag anchor (the 1.x nil)
171
+ title: title,
172
+ title_reference_tag: title,
173
+ }
174
+ end
175
+
176
+ # isodoc's pref_ref_code_parse reads
177
+ # data[:authoritative_identifier] to build biblio-tag identifiers
178
+ # (presentation_function/docid.rb). Returns [data, self] as the
179
+ # 1.x signature did.
180
+ def parse(doc)
181
+ [{ authoritative_identifier: facade_docids(doc) }, self]
182
+ end
183
+
184
+ def citetemplate
185
+ CiteTemplate.new(CITE_STYLES.to_h { |s| [s, ""] })
186
+ end
187
+
188
+ private
189
+
190
+ # 1.x accepted a <references> XML string (isodoc strips namespaces
191
+ # before building it) or a ready array of models.
192
+ def facade_bibitems(bib)
193
+ case bib
194
+ when Array then bib
195
+ when String
196
+ require "moxml"
197
+ root = Moxml.parse(bib).root
198
+ return nil unless root&.name == "references"
199
+
200
+ klass = facade_bibitem_class
201
+ root.xpath("./bibitem").filter_map { |b| klass.from_xml(b.to_xml) }
202
+ end
203
+ end
204
+
205
+ def facade_bibitem_class
206
+ Relaton::Bib::Bibitem
207
+ rescue NameError
208
+ Relaton::Bib::Item
209
+ end
210
+
211
+ # 1.x Parse#authoritative_identifier semantics: the
212
+ # primary/language cascade, the excluded types (URN, DOI, ISBN...)
213
+ # dropped, and scoped ids (biblio-tag duplicates, IEEE trademark)
214
+ # demoted within their type group. isodoc hands the live bibitem
215
+ # node with the document's default namespace still attached, so the
216
+ # element queries must be namespace-agnostic.
217
+ EXCLUDED_ID_TYPES = %w(METANORMA METANORMA-ORDINAL AUTHOR-DATE TITLE
218
+ URN ISO-REFERENCE ISSN ISBN DOI).freeze
219
+
220
+ # Flavors extend the authoritative-identifier exclusions by
221
+ # overriding this reader on their facade subclass
222
+ def excluded_id_types
223
+ EXCLUDED_ID_TYPES
224
+ end
225
+
226
+ def facade_docids(doc)
227
+ node = doc
228
+ unless node.respond_to?(:xpath)
229
+ require "moxml"
230
+ node = Moxml.parse(doc.to_s).root
231
+ end
232
+ return [] unless node
233
+
234
+ ids = node.xpath("./*[local-name() = 'docidentifier']")
235
+ ids = facade_scope_filter(ids)
236
+ out = nil
237
+ [
238
+ ->(x) { x["language"] == @lang && x["primary"] },
239
+ ->(x) { x["primary"] },
240
+ ->(x) { x["language"] == @lang },
241
+ ->(_x) { true },
242
+ ].each do |p|
243
+ out = ids.select do |x|
244
+ p.call(x) && !excluded_id_types.include?(facade_id_type(x))
245
+ end
246
+ out.empty? or break
247
+ end
248
+ # esc-wrapped: isodoc's l10n skips the tags, leaving a standard
249
+ # identifier's punctuation verbatim (the 1.x Parse behaviour)
250
+ out.map { |x| "<esc>#{x.text.strip}</esc>" }
251
+ end
252
+
253
+ def facade_id_type(id)
254
+ t = id["type"] or return nil
255
+ m = /\A(ISBN|ISSN)\..*/i.match(t) or return t.upcase
256
+ m[1].upcase
257
+ end
258
+
259
+ def facade_scope_filter(ids)
260
+ ids.detect { |i| i["scope"] } or return ids
261
+ ids.group_by { |i| i["type"] }.flat_map do |type, group|
262
+ grouped = group.group_by { |i| i["scope"] }
263
+ if type == "IEEE" then grouped["trademark"] || grouped[nil] || []
264
+ else grouped[nil] || []
265
+ end
266
+ end
267
+ end
268
+
269
+ # 1.x accepted string- and symbol-keyed options alike (the old
270
+ # engine deep-symbolized via metanorma-utils); isodoc's bibrenderer
271
+ # and every flavor still pass string keys.
272
+ def deep_symbolize(value)
273
+ case value
274
+ # isodoc i18n hashes carry nil keys (an unlabeled punct slot)
275
+ when Hash then value.to_h { |k, v| [k&.to_sym, deep_symbolize(v)] }
276
+ when Array then value.map { |v| deep_symbolize(v) }
277
+ else value
278
+ end
279
+ end
280
+
281
+ # 1.x render/citation accepted a bibitem XML string; the Iso690
282
+ # engine consumes model instances.
283
+ def to_model(model)
284
+ return model unless model.is_a?(String)
285
+
286
+ klass = facade_bibitem_class
287
+ klass.from_xml(model)
288
+ end
289
+
290
+ def warn_general_config
291
+ return if General.config_warned
292
+
293
+ General.config_warned = true
294
+ warn "relaton-render: Relaton::Render::General is a compatibility " \
295
+ "facade over the v2 engine; the liquid config option is " \
296
+ "retired and ignored. Author a CitationStyle instance " \
297
+ "(relaton-models citation module) for custom rendering."
298
+ end
299
+
300
+ class << self
301
+ attr_accessor :config_warned
302
+ end
303
+ end
304
+ end
305
+ end