relaton-render 3.0.0.pre.alpha.24 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1f8e1bb4347d4eab41a00189b7040c378c6eaa59631eb52fa0e62de36cce9861
4
- data.tar.gz: b6c530258baf6fdf30c70c1f56d17a5c4333fc6f68b6c466e72a18e7cbd7c06d
3
+ metadata.gz: e2d52f05f42c17071799e1cc19e71d0b84f84a0550430d287e4d8cf4dee389a6
4
+ data.tar.gz: 4170319d650b7d3de48553d5fcddb15857b24939e9a6e008d7f74c095f849f60
5
5
  SHA512:
6
- metadata.gz: 45ef9145e49a975775ded3f52bdea26bd429f669adf3a1130524d9b84c60555114f4301a0aac4cb4944bf1126f81b36aa622d2268958e9a6fec357c8b82d4882
7
- data.tar.gz: 7d34cfe2fc402a565bb07d530db022cd9e700a6081a96a8bf271fa19193755e8b1f1eaa5deef5767514ffb8c501b801a7da5c7cd128f005ca3e659a5ca7d7ce8
6
+ metadata.gz: 021e4859ac3ba1f2dda762db881d1322677578359dc0f9a7c319435d7692a36fb6dcc1b96b9b068ca3ed65f9a621648da698962e49d2d2b132adbf5ea2ee66ff
7
+ data.tar.gz: ad7e119b111cac93e1c70fd0af76431a33dc9e95a5508ca866ab556c7b7e3484e7ae31c1981778f2a86ad6c35ba5ecfdfee9799b2a8c1e21480184f81915bc98
data/README.adoc CHANGED
@@ -100,3 +100,6 @@ Liquid templates described below for rendering: it is the hash of bibliographic
100
100
 
101
101
  For the configuation YAML used in relaton-render, refer to https://relaton.org/specs/relaton-render
102
102
 
103
+ == Documentation
104
+
105
+ * link:docs/citation-style-pack.adoc[Defining a Citation Style: the pack format] — the authoritative spec for CitationStyle YAML packs: resource types, presentation patterns, model presentation templates, locale/punctuation, inheritance and conformance.
@@ -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.
@@ -24,8 +24,8 @@ module Relaton
24
24
 
25
25
  def initialize(style: DEFAULT_STYLE, lang: "en", script: "Latn",
26
26
  labels: {}, elements: {})
27
- @elements = elements
28
27
  @style = Style.load(style)
28
+ @elements = Rules.resolve(@style.rules, elements)
29
29
  @i18n = I18n.load(lang)
30
30
  @i18n.overlay_hash!(labels) unless labels.empty?
31
31
  # The style instance is the citation-style authority: its
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Render
5
+ module Iso690
6
+ # The named-rule registry: presentation-of-models implementations
7
+ # the engine ships once, addressable from pack data as
8
+ # rules: { slot: rule_name }. Flavors migrate their ELEMENTS maps
9
+ # onto registered rules; nothing references a flavor class.
10
+ module Rules
11
+ REGISTRY = {}
12
+
13
+ class << self
14
+ def register(name, klass)
15
+ REGISTRY[name.to_s] = klass
16
+ end
17
+
18
+ # A pack's rule selections become the renderer's element map;
19
+ # caller-supplied elements win over the pack's selections
20
+ def resolve(rules, elements = {})
21
+ Array(rules).to_h { |slot, name| [slot.to_sym, fetch(name)] }
22
+ .merge(elements || {})
23
+ end
24
+
25
+ private
26
+
27
+ def fetch(name)
28
+ REGISTRY.fetch(name.to_s) do
29
+ raise ArgumentError, "unknown citation style rule #{name}"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -19,6 +19,21 @@ module Relaton
19
19
  autoload :TypeTemplate, "relaton/render/iso690/style/type_template"
20
20
 
21
21
  attribute :name, :string
22
+ # The citation-style family the pack belongs to (iso690 | lncs |
23
+ # chicago | ieee-sa | apa): selects the presentation-of-models
24
+ # conventions the templates address
25
+ attribute :family, :string, default: "iso690"
26
+ # A named pack this one deltas over; resolved at load with the
27
+ # documented merge semantics (#merge_delta)
28
+ attribute :extends, :string
29
+ # Engine capabilities the pack addresses; a resolver that lacks
30
+ # one fails loudly instead of mis-rendering
31
+ attribute :requires, :string, collection: true, default: []
32
+ # Pack-declared taxonomy: wire type -> kind (aliasing or
33
+ # fallthrough), consulted before the engine's Kinds table
34
+ attribute :types, :hash, default: -> { {} }
35
+ # Presentation-of-models selections: slot -> named engine rule
36
+ attribute :rules, :hash, default: -> { {} }
22
37
  attribute :scheme, Scheme, default: -> { Scheme.new }
23
38
  attribute :templates, TemplateMap, default: -> { TemplateMap.new }
24
39
  attribute :per_type, TypeTemplate, collection: true, default: []
@@ -26,6 +41,11 @@ module Relaton
26
41
 
27
42
  key_value do
28
43
  map "name", to: :name
44
+ map "family", to: :family
45
+ map "extends", to: :extends
46
+ map "requires", to: :requires
47
+ map "types", to: :types
48
+ map "rules", to: :rules
29
49
  map "scheme", to: :scheme
30
50
  map "templates", to: :templates
31
51
  map "perType", to: :per_type
@@ -44,6 +64,12 @@ module Relaton
44
64
  # the style's declared kind for types the vocabulary does not
45
65
  # map (a flavor whose untyped items are not reports)
46
66
  def kind_for(type)
67
+ declared_type = types[type.to_s]
68
+ unless declared_type.to_s.empty?
69
+ return Kinds.mapped?(declared_type) ? Kinds.kind_for(declared_type) :
70
+ declared_type
71
+ end
72
+
47
73
  return Kinds.kind_for(type) if Kinds.mapped?(type)
48
74
 
49
75
  declared = scheme.default_kind.to_s
@@ -97,11 +123,88 @@ module Relaton
97
123
  )
98
124
  end
99
125
 
126
+ # The documented delta merge: perType entries merge per type
127
+ # key (a delta re-declaring `article` never drops the base's
128
+ # `monograph`); hash sections merge per key; scalars, templates
129
+ # and rule selections replace; sortKey and requires union.
130
+ # Deterministic and engine-portable.
131
+ def merge_delta(base)
132
+ merged_per_type = base.per_type.dup
133
+ per_type.each do |delta|
134
+ index = merged_per_type.find_index do |b|
135
+ b.type == delta.type && (b.home || false) == (delta.home || false)
136
+ end
137
+ index ? merged_per_type[index] = delta : merged_per_type << delta
138
+ end
139
+ self.per_type = merged_per_type
140
+ self.types = base.types.merge(types)
141
+ self.rules = base.rules.merge(rules)
142
+ merge_scheme(base)
143
+ merge_templates(base)
144
+ self.sort_key = (base.sort_key + sort_key).uniq
145
+ self.requires = (base.requires + requires).uniq
146
+ self
147
+ end
148
+
149
+ private
150
+
151
+ def merge_scheme(base)
152
+ fresh = NameForm.new
153
+ NameForm.attributes.each_key do |attr|
154
+ val = scheme.name_form.send(attr)
155
+ next unless val == fresh.send(attr)
156
+
157
+ base_val = base.scheme.name_form.send(attr)
158
+ scheme.name_form.send("#{attr}=", base_val) if
159
+ base_val != fresh.send(attr)
160
+ end
161
+
162
+ scheme.system = base.scheme.system if scheme.system.to_s.empty?
163
+ scheme.short_from_reference = base.scheme.short_from_reference unless
164
+ scheme.short_from_reference
165
+ scheme.home_docid_type = base.scheme.home_docid_type if
166
+ Array(scheme.home_docid_type).empty?
167
+ scheme.default_kind = base.scheme.default_kind if
168
+ scheme.default_kind.to_s.empty?
169
+
170
+ locale = scheme.locale
171
+ base_locale = base.scheme.locale
172
+ locale.conj ||= base_locale.conj
173
+ locale.others ||= base_locale.others
174
+ locale.no_date ||= base_locale.no_date
175
+ locale.no_author ||= base_locale.no_author
176
+ locale.in_str ||= base_locale.in_str
177
+ locale.at ||= base_locale.at
178
+ locale.available_at ||= base_locale.available_at
179
+ locale.labels.merge!(base_locale.label_map) { |_k, _b, d| d }
180
+ locale.punct = (base_locale.punct || {}).merge(locale.punct || {})
181
+ end
182
+
183
+ def merge_templates(base)
184
+ fresh = TemplateMap.new
185
+ TemplateMap.attributes.each_key do |attr|
186
+ val = templates.send(attr)
187
+ base_val = base.templates.send(attr)
188
+ next unless val == fresh.send(attr) && base_val != fresh.send(attr)
189
+
190
+ templates.send("#{attr}=", base_val)
191
+ end
192
+ end
193
+
100
194
  class << self
101
- def load(name_or_path)
195
+ def load(name_or_path, seen = [])
102
196
  path = style_path(name_or_path) or
103
197
  raise ArgumentError, "unknown style #{name_or_path}"
104
- from_yaml(File.read(path))
198
+ style = from_yaml(File.read(path))
199
+ return style if style.extends.to_s.empty?
200
+
201
+ style.extends.split(/\s+/).each do |base_name|
202
+ raise ArgumentError,
203
+ "circular extends #{base_name}" if seen.include?(base_name)
204
+
205
+ style.merge_delta(load(base_name, seen + [base_name]))
206
+ end
207
+ style
105
208
  end
106
209
 
107
210
  private
@@ -0,0 +1,45 @@
1
+ # American Psychological Association (7th ed.) reference forms: all
2
+ # author names invert with initials ("Author, A. B."), the date
3
+ # follows the authors in parentheses, article titles take no marks
4
+ # (sentence case is the author's), and only book titles and journal
5
+ # names carry italics. The reference list joins with a comma and ampersand.
6
+ name: "APA (7th edition) references"
7
+ family: apa
8
+ scheme:
9
+ system: name-date
10
+ nameForm:
11
+ initials: true
12
+ initialsSeparator: " "
13
+ initialsPeriod: true
14
+ invertedAll: true
15
+ givenNameFirst: false
16
+ editorsMarked: false
17
+ surnameUpcase: false
18
+ subsequentSurnameUpcase: false
19
+ locale:
20
+ "in": "In"
21
+ labels:
22
+ "and": "&"
23
+ oxford_comma: ","
24
+ oxford_comma: ","
25
+ templates:
26
+ titleOpen: ""
27
+ titleClose: ""
28
+ citation: "{{surname}}, {{date}}"
29
+ reference: "{{creator}} ({{date}}). {{title}}. {{series}}. {{production}}. {{extent}}"
30
+ name: "{{surname}}, {{givenNames}}"
31
+ identifierMode: all
32
+ perType:
33
+ - type: report
34
+ home: true
35
+ template: "{{title}}"
36
+ - type: report
37
+ template: "{{identifier}}. {{title}}. {{production}}."
38
+ - type: monograph
39
+ title: "<em>{{title}}</em>"
40
+ template: "{{creator}} ({{date}}). {{title}}. {{production}}."
41
+ - type: component_part
42
+ template: "{{creator}} ({{date}}). {{title}}. {{componentpart}} ({{extent}}). {{production}}."
43
+ - type: continuing
44
+ series: "<em>{{seriestitle}}, {{serienumber}}</em>"
45
+ template: "{{creator}} ({{date}}). {{title}}. {{series}} {{extent}}. {{location}}"
@@ -0,0 +1,48 @@
1
+ # Chicago Manual of Style (17th ed.), bibliography forms: the notes
2
+ # system's reference list. Only the first author name inverts ("Peck,
3
+ # R. B., W. E. Hanson, and T. H. Thornburn"), article titles carry
4
+ # quotation marks, book titles are set in italics, and production runs
5
+ # "Place: Publisher".
6
+ name: "Chicago Manual of Style (bibliography)"
7
+ family: chicago
8
+ requires:
9
+ - extent_units
10
+ scheme:
11
+ system: name-date
12
+ nameForm:
13
+ initials: false
14
+ initialsSeparator: " "
15
+ initialsPeriod: true
16
+ invertedAll: false
17
+ givenNameFirst: false
18
+ editorsMarked: true
19
+ loneEditorMarked: true
20
+ surnameUpcase: false
21
+ subsequentSurnameUpcase: false
22
+ locale:
23
+ "in": "in"
24
+ labels:
25
+ "and": "and"
26
+ oxford_comma: ","
27
+ series_no: "no."
28
+ templates:
29
+ titleOpen: "\""
30
+ titleClose: "\""
31
+ citation: "{{surname}}, {{date}}"
32
+ reference: "{{creator}}. {{title}}. {{edition}}. {{series}}. {{production}}, {{date}}. {{extent}}."
33
+ name: "{{surname}}, {{givenNames}}"
34
+ identifierMode: all
35
+ perType:
36
+ - type: report
37
+ home: true
38
+ template: "{{title}}"
39
+ - type: report
40
+ template: "{{creator}}. {{title}}. {{edition}}. {{production}}, {{date}}."
41
+ - type: monograph
42
+ title: "<em>{{title}}</em>"
43
+ template: "{{creator}}. {{title}}. {{edition}}. {{production}}, {{date}}. {{extent}}."
44
+ - type: component_part
45
+ template: "{{creator}}. \"{{title}}.\" {{componentpart}}. {{production}}, {{date}}. {{extent}}."
46
+ - type: continuing
47
+ series: "<em>{{seriestitle}}</em>"
48
+ template: "{{creator}}. \"{{title}}.\" {{series}} {{extent}} ({{date}}). {{location}}"