relaton-render 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 900cfb8e591fbbd83ee48c2dfa762a317a15c590fb89844b3ce7a759125d4f94
4
+ data.tar.gz: 29c15ba8cfe1e2dcfafc8d6a4d9985ed298c4515d4c734e3505dfcca9cb11189
5
+ SHA512:
6
+ metadata.gz: ebd7be4d34144997dcee8593266b75eefca1781341b4f081df290a9df64cbb7d665985c1bd0807c1036b190f68d6877b2bf822281bdc6e7c3a332fce349ac612
7
+ data.tar.gz: 8392d3b4d8c836b74cbf7a05435f55b274f24ae1004b247151481a34d86c104818926f3452c68f776a0adb521e9404b908f0d0fde25494014958bfd2c28005ac
@@ -0,0 +1,15 @@
1
+ # Auto-generated by Cimas: Do not edit it manually!
2
+ # See https://github.com/metanorma/cimas
3
+ name: rake
4
+
5
+ on:
6
+ push:
7
+ branches: [ master, main ]
8
+ tags: [ v* ]
9
+ pull_request:
10
+
11
+ jobs:
12
+ rake:
13
+ uses: metanorma/metanorma-build-scripts/.github/workflows/generic-rake.yml@main
14
+ secrets:
15
+ pat_token: ${{ secrets.METANORMA_CI_PAT_TOKEN }}
data/.hound.yml ADDED
@@ -0,0 +1,5 @@
1
+ # Auto-generated by Cimas: Do not edit it manually!
2
+ # See https://github.com/metanorma/cimas
3
+ ruby:
4
+ enabled: true
5
+ config_file: .rubocop.yml
data/.rubocop.yml ADDED
@@ -0,0 +1,10 @@
1
+ # Auto-generated by Cimas: Do not edit it manually!
2
+ # See https://github.com/metanorma/cimas
3
+ inherit_from:
4
+ - https://raw.githubusercontent.com/riboseinc/oss-guides/master/ci/rubocop.yml
5
+
6
+ # local repo-specific modifications
7
+ # ...
8
+
9
+ AllCops:
10
+ TargetRubyVersion: 2.5
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ Encoding.default_external = Encoding::UTF_8
2
+ Encoding.default_internal = Encoding::UTF_8
3
+
4
+ source "https://rubygems.org"
5
+
6
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
7
+
8
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,25 @@
1
+ BSD 2-Clause License
2
+
3
+ Copyright (c) 2018, Ribose
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ * Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.adoc ADDED
@@ -0,0 +1,241 @@
1
+ = Relaton Render
2
+
3
+ image:https://img.shields.io/gem/v/iso690render.svg["Gem Version", link="https://rubygems.org/gems/iso690render"]
4
+ image:https://github.com/metanorma/iso690render/workflows/rake/badge.svg["Build Status", link="https://github.com/metanorma/iso690render/actions?workflow=rake"]
5
+ image:https://codeclimate.com/github/metanorma/iso690render/badges/gpa.svg["Code Climate", link="https://codeclimate.com/github/metanorma/iso690render"]
6
+
7
+ Gem that takes a https://github.com/relaton/relaton[Relaton] bibliographic description and
8
+ a configuration, and generates a https://www.metanorma.org[Metanorma] XML rendering of that description.
9
+
10
+ == Calling
11
+
12
+ [source,ruby]
13
+ ----
14
+ input = "<bibitem type='...'>....</bibitem>"
15
+
16
+ Relaton::Render::General.new(template: ..., nametemplate: ..., seriestemplate: ..., language: "en", script: "Latn").render(input)
17
+ ----
18
+
19
+ The gem is intended to be inherited from by Metanorma flavours, which may specialise it with their own
20
+ code. The templates are however intended to determine much of the rendering.
21
+
22
+ Gems can provide their configurations in YAML files, and parse them before passing them to the call to Iso690Render.
23
+ The built-in values for Iso690Render are given in `/lib/iso690render/config.yml`, and can be overridden by
24
+ the parameters of initialising Iso690Render.
25
+
26
+ The parameters are:
27
+
28
+ `language`:: in ISO-639
29
+ `script`:: in ISO-15124
30
+ `template`:: templates for rendering different bibliographic types
31
+ `nametemplate`:: templates for rendering personal names
32
+ `seriestemplate`:: template for rendering series
33
+ `journaltemplate`:: template for rendering journals in article citations
34
+ `extenttemplate`:: templates for rendering extents
35
+ `edition_number`:: override formatting of edition number
36
+ `edition`:: override formatting of edition
37
+ `date`:: default date format (from Twitter CLDR)
38
+
39
+ == Configuration
40
+
41
+ === Templates
42
+
43
+ There is one template provided for each of the bibliographic types recognised by Relaton (`/bibitem/@type`), and a default template:
44
+
45
+ Currently supported:
46
+
47
+ * article
48
+ * book
49
+ * booklet
50
+ * manual
51
+ * proceedings
52
+ * presentation
53
+ * thesis
54
+ * techreport
55
+ * standard
56
+ * unpublished
57
+ * electronic resource
58
+ * inbook
59
+ * incollection
60
+ * inproceedings
61
+ * journal
62
+ * website
63
+ * webresource
64
+ * dataset
65
+
66
+ Not currently supported:
67
+
68
+ * map
69
+ * audiovisual
70
+ * film
71
+ * video
72
+ * broadcast
73
+ * software
74
+ * graphic_work
75
+ * music
76
+ * performance
77
+ * patent
78
+ * archival
79
+ * social_media
80
+ * alert
81
+ * message
82
+ * conversation
83
+ * misc (default)
84
+
85
+
86
+ In Metanorma, not all types are used, but there are exemplars for all of these given on this site, following
87
+ the human-readable style used in ISO 690. These can be overridden by supplying corresponding paramerers in the call
88
+ to initialise Iso690Render.
89
+
90
+ Each `template` is a string marked up with https://shopify.github.io/liquid/[Liquid Markup], with the following fields
91
+ drawn from the bibliographic item:
92
+
93
+ |===
94
+ | Field | Relaton XPath | Multiple | Can come from host | Note
95
+
96
+ | title | ./title | | |
97
+ | edition | ./edition | | Y | If numeric value, is given internationalised rendering of "nth edition", as set in edition_numbering. Otherwise, the textual content of the tag is given.
98
+ | edition_raw | ./edition | | Y | The strict textual content of the tag is given.
99
+ | medium | ./medium | | Y |
100
+ | place | ./place | | Y |
101
+ | publisher | ./contributor[role/@type = 'publisher']/organization/name | | Y |
102
+ | distributor | ./contributor[role/@type = 'distributor']/organization/name | | Y |
103
+ | standardidentifier | ./docidentifier[not(@type = 'metanorma' or @type = 'ordinal')] | Y | |
104
+ | status | ./status | | | Rendering varies by flavour
105
+ | uri | ./uri[@type = 'doi' or @type = 'uri' or @type = 'src' or true] | | |
106
+ | access_location | ./accessLocation | | Y |
107
+ | extent | ./extent | Y | | Render with standard abbreviations for pp, vols, with n-dash, with delimiting of multiple locations
108
+ | creatornames | ./contributor[role/@type = 'author'] \| ./contributor[role/@type = 'performer'] \| ./contributor[role/@type = 'adapter'] \| ./contributor[role/@type = 'translator'] \| ./contributor[role/@type = 'editor'] \| ./contributor[role/@type = 'publisher'] \| ./contributor[role/@type = 'distributor'] \| ./contributor | Y | | <<nametemplate,`nametemplate`>> applied to each name; joining template from internationalisation applied to multiple names
109
+ | role | ./contributor[role/description] \| ./contributor[role/@type] | | |
110
+ | date | ./date[@type = 'issued'] \| ./date[@type = 'circulated'] \| ./date | | Y |
111
+ | date_updated | ./date[@type = 'updated'] | | Y |
112
+ | date_accessed | ./date[@type = 'accessed'] | | Y |
113
+ | series | ./series[@type = 'main' or not(@type) or true] | | Y | <<seriestemplate,`seriestemplate`>> applies to series
114
+ | host_creatornames | ./relation[@type = 'includedIn']/ bibitem/contributor[role/@type = 'author'] | | Y | Follows options for `creatornames`
115
+ | host_title | ./relation[@type = 'includedIn']/ bibitem/title | Y | Y | Follows options for `creatornames`
116
+ | host_role | ./relation[@type = 'includedIn']/ bibitem/contributor[role/description] \| ./relation[@type = 'includedIn']/ bibitem/contributor[role/@type] | | Y |
117
+ | type | ./@type | |
118
+ | labels | | | text to be looked up in internationalisation configuration files: "edition", "In", "At", "Vol", "Vols", "p.", "pp"
119
+ |===
120
+
121
+ Many fields are populated either by the description of the bibliographic item itself, or by the description of the item containing it (the _host_ item: `./relation[@type = 'includedIn']/bibitem`). For example, in a paper included in an edited volume, the edition will typically be given for the editor volume, rather than for the paper. Those fields are indicated by "Can come from host" in the table.
122
+
123
+ The Liquid template surrounds each field by preceding and following punctuation.
124
+
125
+ * Fields are space-delimited. So `<em>{{ title }}</em> [{{medium}}]` are two separate fields.
126
+ * If fields are not space-delimited, this is indicated by inserting `|`. So `{{ title }}|{{ medium}}` is two fields, rendered with no space separation.
127
+ * If the field is empty, its surrounding markup is also removed. So if there is no medium, then `[{{medium}}]` is not rendered, and the brackets will be stripped.
128
+ * Underscore is treated as space, attaching to the preceding or following field. So `,_{{ edition }}_{{ labels['edition'] }}` is treated as the one field.
129
+ * If punctuation is space delimited, it is inserted regardless of preceding content. So `{{ creatornames }} ({{date}}) .` will insert the full stop whether or not the date is present.
130
+ * Space between punctuation and before punctuation is automatically removed.
131
+
132
+ For example:
133
+
134
+ ....
135
+ "{{ creatornames }} ({{date}}) . <em>{{ title }}</em> [{{medium}}] ,_{{ edition }}_{{ labels['edition'] }} ."
136
+ ....
137
+
138
+ If a type uses another type's template, the type is mapped to the other type's name; e.g.
139
+
140
+ ....
141
+ template:
142
+ book: ...
143
+ booklet: book
144
+ ....
145
+
146
+ [[nametemplate]]
147
+ === Name templates
148
+
149
+ The `nametemplate` is a hash of Liquid templates for the formatting of contributor names in particular positions. It
150
+ draws on the following fields drawn from the bibliographic item:
151
+
152
+ |===
153
+ | Field | Relaton XPath | Multiple | Note
154
+
155
+ | surname[0] | ./contributor[1]/person/name/surname \| ./contributor[1]/person/name/completename \| ./contributor[1]/organization/name | | i.e. surname is the name default
156
+ | surname[1] | ./contributor[2]/name/surname | |
157
+ | surname[2] | ./contributor[3]/name/surname | |
158
+ | initials[0] | ./contributor[1]/name/initial | | If not supplied, the first letter of each given name is used instead
159
+ | initials[1] | ./contributor[2]/name/initial | |
160
+ | given[0] | ./contributor[1]/name/forename[1] | | If not supplied, initials are used instead
161
+ | given[1] | ./contributor[2]/name/forename[1] | |
162
+ | middle[0] | ./contributor[1]/name/forename[not(first())] | Y |
163
+ | middle[1] | ./contributor[2]/name/forename[not(first())] | Y |
164
+ |===
165
+
166
+ There are at least three distinct `nametemplate` instances that need to be provided, one for a single contributor (`one:`), one for two contributors (`two:`), one for three or more (`more:`), and optionally one for "et al." (`etal:`). The number of contributors for which "et al." starts being used is indicated by `etal_count`.
167
+
168
+ For example:
169
+ ....
170
+ {
171
+ one: "{{ surname[0] }}, {{ given[0] }} {{ middle[0] | slice : 0 }}",
172
+ two: "{{ surname[0] }}, {{ given[0] }} {{ middle[0] | slice : 0 }} &amp; {{ given[1] }} {{ middle[1] | slice : 0 }} {{ surname[1] }}",
173
+ more: "{{ surname[0] }}, {{ given[0] }} {{ middle[0] | slice : 0 }}, {{ given[1] }} {{ middle[1] | slice : 0 }} {{ surname[1] }} &amp; {{ given[2] }} {{ middle[2] | slice : 0 }} {{ surname[2] }}",
174
+ etal: "{{ surname[0] }}, {{ given[0] }} {{ middle[0] | slice : 0 }}, {{ given[1] }} {{ middle[1] | slice : 0 }} {{ surname[1] }} <em>et al.</em>",
175
+ etal_count: 6
176
+ }
177
+ ....
178
+
179
+ In the case of `more`, the `(name)[1]` entries are repeated for all additional authors above 2 and before the final author.
180
+
181
+ [[seriestemplate]]
182
+ === Series template
183
+
184
+ The `seriestemplate` is a template for the rendering of series information. It draws on the following fields drawn from the bibliographic item:
185
+
186
+ |===
187
+ | Field | Relaton XPath | Multiple | Can come from host | Note
188
+
189
+ | series_title | ./series[@type = 'main' or not(@type) or true]/name | | Y |
190
+ | series_abbr | ./series[@type = 'main' or not(@type) or true]/abbreviation | | Y |
191
+ | series_num | ./series[@type = 'main' or not(@type) or true]/number | | Y |
192
+ | series_partnumber | ./series[@type = 'main' or not(@type) or true]/partnumber | | Y |
193
+ | series_run | ./series[@type = 'main' or not(@type) or true]/run | | Y |
194
+ |===
195
+
196
+ For example: `{% if series_abbr %}{{series_abbr}}{% else %}{{series_title}}{% endif %} ,_({{series_run}}) {{series_num}}|({{series_partnumber}})`
197
+
198
+ === Journal template
199
+
200
+ The `journaltemplate` is a template for the rendering of series information, when they relate to articles in a journal. The template is distinct because of longstanding practice of rendering journal information differently from monograph series information. The template draws on the same fields as the `seriestemplate`, but because the journal title is typically italicised and the numeration is not, any italicisation needs to occur within the template.
201
+
202
+ For example, the recommended practice in the current edition of ISO 690 is to give explicit volume labels:
203
+
204
+ `<em>{% if series_abbr %}{{series_abbr}}{% else %}{{series_title}}{% endif %}</em> {{ labels['volume'] }}_{{series_num}} {{ labels['part'] }}_{{series_partnumber}}`
205
+
206
+ A common template that drops those labels is:
207
+
208
+ `<em>{% if series_abbr %}{{series_abbr}}{% else %}{{series_title}}{% endif %}</em> {{series_num}}|({{series_partnumber}})`
209
+
210
+ === Extent template
211
+
212
+ The extent of a reference can be expressed differently depending on the type of bibliographic item. For example, the extent of a book is its page count, and is typically expressed as something like _59 pp._ On the other hand, the extent of an article is expressed as _pp. 9–20_, or just _9–20_.
213
+
214
+ To capture this, a separate template is supplied under `extenttemplate` for each bibliographic item type. For those types where none is supplied, the template given for `misc` is used as the default.
215
+
216
+ The template draws on the defined types of locality of extents; the most common of these is `page` and `volume`. Locality types are the fields used in the Liquid templates; for example:
217
+
218
+ ....
219
+ {
220
+ book: "{{ volume }}_{{ label['extent']['volume'] }}, {{ page }}_{{ label['extent']['page'] }}"
221
+ inbook: "{{ volume }}: {{ page }}"
222
+ misc: "{{ label['extent']['volume'] }}_{{ volume }}, {{ label['extent']['page'] }}_{{ page }}"
223
+ }
224
+ ....
225
+
226
+ The internationalisation files define a singular and a plural version of the locality types, under `labels['extent']`
227
+
228
+ * The plural is always used if the extent is a range (with a `<from>` and `<to>`).
229
+ * In a host type of bibliographic item, the extent is singular only if the value is `1`, else it is plural (_1 p._, _2 pp._)
230
+ * In an included item (`inbook`, `incollection`, `inproceedings`, `article`, or any item with an `includedIn` relation),
231
+ the singular is used if the extent is not a range (_pp. 2–4_ vs. _p. 3_)..
232
+
233
+ === Other
234
+
235
+ In addition, the configuration includes different configuration options for rendering:
236
+
237
+ The internationalisation file sets the following variables, which can be overridden in configuration parameters:
238
+ `edition_number`:: has following values corresponding to the rule-based number rules defined in https://github.com/twitter/twitter-cldr-rb[Twitter CLDR]
239
+ for a language. For example, English _4th_ is defined as `["OrdinalRules", "digits-ordinal"]`, because under twitter-cldr, `4th` is generated as `4.localize(:en).to_rbnf_s("OrdinalRules", "digits-ordinal")`.
240
+ `edition`:: is the localised expression for edition, with the edition number given as %. So _4th ed.` is generated with `edition` as `% ed.`.
241
+ `date`:: date format default, taken from https://github.com/twitter/twitter-cldr-rb[Twitter CLDR]: `to_full_s`, `to_long_s`, `to_medium_s`, `to_short_s`, or one of the `to_additional_s` formats. One value is given for each of "month_year", "day_month_year", and "date_time"; e.g. `{ month_year: to_long_s, day_month_year: to_long_s, date_time: to_long_s }`.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "cnccs"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/rspec ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'rspec' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require "pathname"
10
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path(
11
+ "../../Gemfile", Pathname.new(__FILE__).realpath
12
+ )
13
+
14
+ require "rubygems"
15
+ require "bundler/setup"
16
+
17
+ load Gem.bin_path("rspec-core", "rspec")
18
+
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,24 @@
1
+ require "yaml"
2
+ require "isodoc-i18n"
3
+
4
+ module IsoDoc
5
+ class I18n
6
+ def load_yaml1(lang, script)
7
+ case lang
8
+ when "en", "fr", "ru", "de", "es", "ar"
9
+ load_yaml2(lang)
10
+ when "zh"
11
+ if script == "Hans" then load_yaml2("zh-Hans")
12
+ else load_yaml2("en")
13
+ end
14
+ else
15
+ load_yaml2("en")
16
+ end
17
+ end
18
+
19
+ def load_yaml2(str)
20
+ YAML.load_file(File.join(File.dirname(__FILE__),
21
+ "../isodoc-yaml/i18n-#{str}.yaml"))
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,2 @@
1
+ in: في
2
+ at: في
@@ -0,0 +1,2 @@
1
+ in: in
2
+ at: bei
@@ -0,0 +1,22 @@
1
+ in: in
2
+ at: at
3
+ and: and
4
+ updated: updated
5
+ viewed: viewed
6
+ version: version
7
+ date:
8
+ month_year: to_long_s
9
+ day_month_year: to_long_s
10
+ date_time: to_long_s
11
+ edition_number: ["OrdinalRules", "digits-ordinal"]
12
+ edition: "% edition"
13
+ editor:
14
+ sg: ed.
15
+ pl: eds.
16
+ extent:
17
+ page:
18
+ sg: p.
19
+ pl: pp.
20
+ volume:
21
+ sg: vol.
22
+ pl: vols.
@@ -0,0 +1,2 @@
1
+ in: en
2
+ at: en
@@ -0,0 +1,2 @@
1
+ in: dans
2
+ at: à
@@ -0,0 +1,23 @@
1
+ in: в
2
+ at: в
3
+ and: и
4
+ updated: обновлен
5
+ viewed: просмотрено
6
+ version: версия
7
+ date:
8
+ month_year: to_long_s
9
+ day_month_year: to_long_s
10
+ date_time: to_long_s
11
+ edition_number: ["SpelloutRules", "spellout-ordinal-neuter"]
12
+ edition: "% издание"
13
+ editor:
14
+ sg: изд.
15
+ pl: изд.
16
+ extent:
17
+ page:
18
+ sg: стр.
19
+ pl: стр.
20
+ volume:
21
+ sg: т.
22
+ pl: тт.
23
+
@@ -0,0 +1,2 @@
1
+ in: 在
2
+ at: 在
@@ -0,0 +1,54 @@
1
+ require "nokogiri"
2
+ require "twitter_cldr"
3
+ require_relative "parse_contributors"
4
+ require_relative "parse_extract"
5
+
6
+ class Iso690Parse
7
+ def initialize; end
8
+
9
+ def extract(doc)
10
+ host = doc.at("./relation[@type = 'includedIn']/bibitem")
11
+ simple_xml2hash(doc).merge(simple_or_host_xml2hash(doc, host))
12
+ .merge(host_xml2hash(host))
13
+ .merge(series_xml2hash(doc, host))
14
+ end
15
+
16
+ def simple_xml2hash(doc)
17
+ creators, role = creatornames(doc)
18
+ { type: type(doc), title: title(doc), extent_raw: extent(doc),
19
+ standardidentifier: standardidentifier(doc), uri: uri(doc),
20
+ status: status(doc), creators: creators, role_raw: role }
21
+ end
22
+
23
+ def simple_or_host_xml2hash(doc, host)
24
+ { edition_raw: edition(doc, host), medium: medium(doc, host),
25
+ place: place(doc, host), publisher: publisher(doc, host),
26
+ distributor: distributor(doc, host),
27
+ access_location: access_location(doc, host),
28
+ date: date(doc, host), date_updated: date_updated(doc, host),
29
+ date_accessed: date_accessed(doc, host) }
30
+ end
31
+
32
+ def host_xml2hash(host)
33
+ creators, role = creatornames(host)
34
+ { host_creators: creators, host_role_raw: role, host_title: title(host) }
35
+ end
36
+
37
+ def series_xml2hash(doc, host)
38
+ series = doc.at("./series[@type = 'main']") ||
39
+ doc.at("./series[not(@type)]") || doc.at("./series")
40
+ host and series ||=
41
+ host.at("./series[@type = 'main']") ||
42
+ host.at("./series[not(@type)]") || host.at("./series")
43
+
44
+ series_xml2hash1(series)
45
+ end
46
+
47
+ def series_xml2hash1(series)
48
+ return {} unless series
49
+
50
+ { series_title: series_title(series), series_abbr: series_abbr(series),
51
+ series_run: series_run(series), series_num: series_num(series),
52
+ series_partnumber: series_partnumber(series) }
53
+ end
54
+ end
@@ -0,0 +1,96 @@
1
+ class Iso690Parse
2
+ def extract_orgname(org)
3
+ name = org.at("./name")
4
+ name&.text
5
+ end
6
+
7
+ def extract_personname(person)
8
+ surname = person.at("./name/surname") || person.at("./name/completename")
9
+ given, middle, initials = given_and_middle_name(person)
10
+ { surname: surname&.text,
11
+ given: given,
12
+ middle: middle,
13
+ initials: initials }
14
+ end
15
+
16
+ def given_and_middle_name(person)
17
+ forenames = person.xpath("./name/forename")&.map(&:text)
18
+ initials = person.xpath("./name/initial")&.map(&:text)
19
+ forenames.empty? and initials.empty? and return [nil, nil, nil]
20
+ forenames.empty? and forenames = initials.dup
21
+ initials.empty? and initials = forenames.map { |x| x[0] }
22
+ [forenames.first, forenames[1..-1], initials]
23
+ end
24
+
25
+ def extractname(contributor)
26
+ org = contributor.at("./organization")
27
+ person = contributor.at("./person")
28
+ return { surname: extract_orgname(org) } if org
29
+ return extract_personname(person) if person
30
+
31
+ nil
32
+ end
33
+
34
+ def contributor_role(contributors)
35
+ return nil unless contributors.length.positive?
36
+
37
+ desc = contributors[0].at("role/description")&.text
38
+ type = contributors[0].at("role/@type")&.text
39
+ return nil if %w(author publisher).include?(type) && desc.nil?
40
+
41
+ type
42
+ end
43
+
44
+ def creatornames(doc)
45
+ cr = creatornames1(doc)
46
+ cr.empty? and return [nil, nil]
47
+ [cr.map { |x| extractname(x) }, contributor_role(cr)]
48
+ end
49
+
50
+ def creatornames1(doc)
51
+ cr = []
52
+ return cr if doc.nil?
53
+
54
+ %w(author performer adapter translator editor publisher distributor)
55
+ .each do |r|
56
+ add = doc.xpath("./contributor[role/@type = '#{r}']")
57
+ next if add.empty?
58
+
59
+ cr = add and break
60
+ end
61
+ cr.empty? and cr = doc.xpath("./contributor")
62
+ cr
63
+ end
64
+
65
+ def date1(date)
66
+ on = date.at("./on")
67
+ from = date.at("./from")
68
+ to = date.at("./to")
69
+ return { on: on.text } if on
70
+ return { from: from.text, to: to&.text } if from
71
+
72
+ nil
73
+ end
74
+
75
+ def date(doc, host)
76
+ x = doc.at("./date[@type = 'issued']") ||
77
+ doc.at("./date[@type = 'circulated']") ||
78
+ doc.at("./date") ||
79
+ host&.at("./date[@type = 'issued']") ||
80
+ host&.at("./date[@type = 'circulated']") ||
81
+ host&.at("./date") or return nil
82
+ date1(x)
83
+ end
84
+
85
+ def date_updated(doc, host)
86
+ x = doc.at("./date[@type = 'updated']") ||
87
+ host&.at("./date[@type = 'updated']") or return nil
88
+ date1(x)
89
+ end
90
+
91
+ def date_accessed(doc, host)
92
+ x = doc.at("./date[@type = 'accessed']") ||
93
+ host&.at("./date[@type = 'accessed']") or return nil
94
+ date1(x)
95
+ end
96
+ end