Module: Metanorma::Standoc::Refs

Included in:
Converter
Defined in:
lib/metanorma/standoc/ref.rb,
lib/metanorma/standoc/ref_utility.rb

Constant Summary collapse

ISO_REF =
%r{^<ref\sid="(?<anchor>[^"]+)">
\[(?<usrlbl>\([^)]+\))?(?<code>(?:ISO|IEC)[^0-9]*\s[0-9-]+|IEV)
(?::(?<year>[0-9][0-9-]+))?\]</ref>,?\s*(?<text>.*)$}xm.freeze
ISO_REF_NO_YEAR =
%r{^<ref\sid="(?<anchor>[^"]+)">
      \[(?<usrlbl>\([^)]+\))?(?<code>(?:ISO|IEC)[^0-9]*\s[0-9-]+):
      (?:--|&\#821[12];)\]</ref>,?\s*
        (?:<fn[^>]*>\s*<p>(?<fn>[^\]]+)</p>\s*</fn>)?,?\s?(?<text>.*)$}xm
.freeze
ISO_REF_ALL_PARTS =
%r{^<ref\sid="(?<anchor>[^"]+)">
      \[(?<usrlbl>\([^)]+\))?(?<code>(?:ISO|IEC)[^0-9]*\s[0-9]+)
      (?::(?<year>--|&\#821[12];|[0-9][0-9-]+))?\s
      \(all\sparts\)\]</ref>,?\s*
(?:<fn[^>]*>\s*<p>(?<fn>[^\]]+)</p>\s*</fn>,?\s?)?(?<text>.*)$}xm.freeze
NON_ISO_REF =
%r{^<ref\sid="(?<anchor>[^"]+)">
      \[(?<usrlbl>\([^)]+\))?(?<code>.+?)\]</ref>,?\s*(?<text>.*)$}xm
.freeze
NON_ISO_REF1 =
%r{^<ref\sid="(?<anchor>[^"]+)">
      (?<usrlbl>\([^)]+\))?(?<code>.+?)</ref>,?\s*(?<text>.*)$}xm
.freeze
MALFORMED_REF =
<<~REF.freeze
  no anchor on reference, markup may be malformed: see
  https://www.metanorma.com/author/topics/document-format/bibliography/ ,
  https://www.metanorma.com/author/iso/topics/markup/#bibliographies
REF

Instance Method Summary collapse

Instance Method Details

#analyse_ref_code(code) ⇒ Object



109
110
111
112
113
114
# File 'lib/metanorma/standoc/ref_utility.rb', line 109

def analyse_ref_code(code)
  ret = { id: code }
  code.nil? || code.empty? and return ret
  analyse_ref_code_csv(ret) ||
    analyse_ref_code_nested(ret)
end

#analyse_ref_code_csv(ret) ⇒ Object



116
117
118
119
120
121
122
123
# File 'lib/metanorma/standoc/ref_utility.rb', line 116

def analyse_ref_code_csv(ret)
  ret[:id].include?("=") or return nil
  line = CSV.parse_line(ret[:id], liberal_parsing: true) or return nil
  a = analyse_ref_code_csv_breakup(line)
  analyse_ref_code_csv_map(a)
rescue StandardError
  nil
end

#analyse_ref_code_csv_breakup(line) ⇒ Object



125
126
127
128
129
130
131
132
# File 'lib/metanorma/standoc/ref_utility.rb', line 125

def analyse_ref_code_csv_breakup(line)
  line.each_with_object({}) do |x, m|
    kv = x.split("=", 2)
    kv.size == 1 and kv = ["code", kv.first]
    m[kv[0].to_sym] = kv[1].delete_prefix('"').delete_suffix('"')
      .delete_prefix("'").delete_suffix("'")
  end
end

#analyse_ref_code_csv_map(source) ⇒ Object



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/metanorma/standoc/ref_utility.rb', line 134

def analyse_ref_code_csv_map(source)
  source.each_with_object({}) do |(k, v), ret|
    case k
    when :dropid, :hidden, :nofetch
      ret[k] = v == "true"
    when :repo, :path
      ret[:type] = k.to_s
      ret[:key] = v
      ret[:nofetch] = true
      source[:code] or
        ret[:id] = v.sub(%r{^[^/]+/}, "")
    when :"local-file"
      ret[:localfile] = v
    when :number
      if source[:code] then ret[:usrlabel] = "(#{v})"
      else ret[:numeric] = true
      end
    when :usrlabel
      ret[:usrlabel] = "(#{v})"
    when :code then ret[:id] = v
    end
  end
end

#analyse_ref_code_nested(ret) ⇒ Object

ref id = (usrlbl)codeyear code = [? number ]? | ident | nofetch(code) | hidden(code) | dropid(code) | # (repo|path):(key,code) | local-file(source,? key)



161
162
163
164
165
166
167
168
169
170
171
# File 'lib/metanorma/standoc/ref_utility.rb', line 161

def analyse_ref_code_nested(ret)
  analyse_ref_numeric(
    analyse_ref_repo_path(
      analyse_ref_dropid(
        analyse_ref_hidden(
          analyse_ref_nofetch(analyse_ref_localfile(ret)),
        ),
      ),
    ),
  )
end

#analyse_ref_dropid(ret) ⇒ Object



88
89
90
91
92
# File 'lib/metanorma/standoc/ref_utility.rb', line 88

def analyse_ref_dropid(ret)
  return ret unless m = /^dropid\((?<id>.+)\)$/.match(ret[:id])

  ret.merge(id: m[:id], dropid: true)
end

#analyse_ref_hidden(ret) ⇒ Object



82
83
84
85
86
# File 'lib/metanorma/standoc/ref_utility.rb', line 82

def analyse_ref_hidden(ret)
  return ret unless m = /^hidden\((?<id>.+)\)$/.match(ret[:id])

  ret.merge(id: m[:id], hidden: true)
end

#analyse_ref_localfile(ret) ⇒ Object



69
70
71
72
73
74
# File 'lib/metanorma/standoc/ref_utility.rb', line 69

def analyse_ref_localfile(ret)
  m = /^local-file\((?:(?<source>[^,]+),\s*)?(?<id>.+)\)$/.match(ret[:id])
  m or return ret

  ret.merge(id: m[:id], localfile: (m[:source] || "default"))
end

#analyse_ref_nofetch(ret) ⇒ Object



76
77
78
79
80
# File 'lib/metanorma/standoc/ref_utility.rb', line 76

def analyse_ref_nofetch(ret)
  return ret unless m = /^nofetch\((?<id>.+)\)$/.match(ret[:id])

  ret.merge(id: m[:id], nofetch: true)
end

#analyse_ref_numeric(ret) ⇒ Object



103
104
105
106
107
# File 'lib/metanorma/standoc/ref_utility.rb', line 103

def analyse_ref_numeric(ret)
  return ret unless /^\d+$/.match?(ret[:id])

  ret.merge(numeric: true)
end

#analyse_ref_repo_path(ret) ⇒ Object



94
95
96
97
98
99
100
101
# File 'lib/metanorma/standoc/ref_utility.rb', line 94

def analyse_ref_repo_path(ret)
  return ret unless m =
                      /^(?<type>repo|path):\((?<key>[^,]+),?(?<id>.*)\)$/
                        .match(ret[:id])

  id = m[:id].empty? ? m[:key].sub(%r{^[^/]+/}, "") : m[:id]
  ret.merge(id: id, type: m[:type], key: m[:key], nofetch: true)
end

#conditional_date(bib, match, noyr) ⇒ Object



26
27
28
29
30
31
32
# File 'lib/metanorma/standoc/ref_utility.rb', line 26

def conditional_date(bib, match, noyr)
  if match.names.include?("year") && !match[:year].nil?
    bib.date(type: "published") do |d|
      (noyr and d.on "--") or set_date_range(d, norm_year(match[:year]))
    end
  end
end

#docid(bib, code) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
# File 'lib/metanorma/standoc/ref_utility.rb', line 42

def docid(bib, code)
  type, code1 = if /^\[\d+\]$|^\([^)]+\).*$/.match?(code)
                  ["metanorma", mn_code(code)]
                else
                  @bibdb&.docid_type(code) || [nil, code]
                end
  code1.sub!(/^nofetch\((.+)\)$/, "\\1")
  bib.docidentifier **attr_code(type: type) do |d|
    d << code1
  end
end

#docnumber(bib, code) ⇒ Object



54
55
56
57
58
59
# File 'lib/metanorma/standoc/ref_utility.rb', line 54

def docnumber(bib, code)
  code or return
  bib.docnumber do |d|
    d << @c.decode(code).sub(/^[^\d]*/, "")
  end
end

#id_and_year(id, year) ⇒ Object



16
17
18
# File 'lib/metanorma/standoc/ref_utility.rb', line 16

def id_and_year(id, year)
  year ? "#{id}:#{year}" : id
end

#iso_publisher(bib, code) ⇒ Object



6
7
8
9
10
11
12
13
14
15
# File 'lib/metanorma/standoc/ref.rb', line 6

def iso_publisher(bib, code)
  code.sub(/ .*$/, "").split("/").each do |abbrev|
    bib.contributor do |c|
      c.role type: "publisher"
      c.organization do |org|
        organization(org, abbrev, true)
      end
    end
  end
end

#isorefmatches2_1(xml, match, code) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/metanorma/standoc/ref.rb', line 62

def isorefmatches2_1(xml, match, code)
  xml.bibitem **attr_code(ref_attributes(match)) do |t|
    isorefrender1(t, match, code, "--")
    t.date type: "published" do |d|
      d.on "--"
    end
    iso_publisher(t, match[:code])
    unless match[:fn].nil?
      t.note(**plaintxt.merge(type: "Unpublished-Status")) do |p|
        p << (match[:fn]).to_s
      end
    end
  end
end

#isorefmatches2code(match, _item) ⇒ Object



47
48
49
50
51
52
53
# File 'lib/metanorma/standoc/ref.rb', line 47

def isorefmatches2code(match, _item)
  code = analyse_ref_code(match[:code])
  { code: match[:code], no_year: true, lang: (@lang || :all),
    note: match[:fn], year: nil, match: match,
    analyse_code: code,
    title: match[:text], usrlbl: match[:usrlbl] || code[:usrlabel] }
end

#isorefmatches2out(item, xml) ⇒ Object



55
56
57
58
59
60
# File 'lib/metanorma/standoc/ref.rb', line 55

def isorefmatches2out(item, xml)
  if item[:doc] then use_retrieved_relaton(item, xml)
  else isorefmatches2_1(xml, item[:ref][:match],
                        item[:ref][:analyse_code])
  end
end

#isorefmatches3_1(xml, match, code, year, _hasyr, _ref) ⇒ Object



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/metanorma/standoc/ref.rb', line 98

def isorefmatches3_1(xml, match, code, year, _hasyr, _ref)
  xml.bibitem(**attr_code(ref_attributes(match))) do |t|
    isorefrender1(t, match, code, year, " (all parts)")
    conditional_date(t, match, year == "--")
    iso_publisher(t, match[:code])
    if match.names.include?("fn") && match[:fn]
      t.note(**plaintxt.merge(type: "Unpublished-Status")) do |p|
        p << (match[:fn]).to_s
      end
    end
    t.extent type: "part" do |e|
      e.referenceFrom "all"
    end
  end
end

#isorefmatches3code(match, _item) ⇒ Object



77
78
79
80
81
82
83
84
85
86
# File 'lib/metanorma/standoc/ref.rb', line 77

def isorefmatches3code(match, _item)
  code = analyse_ref_code(match[:code])
  yr = norm_year(match[:year])
  hasyr = !yr.nil? && yr != "--"
  { code: match[:code], match: match, yr: yr, hasyr: hasyr,
    year: hasyr ? yr : nil,
    all_parts: true, no_year: yr == "--",
    title: match[:text], usrlbl: match[:usrlbl] || code[:usrlabel],
    lang: (@lang || :all) }
end

#isorefmatches3out(item, xml) ⇒ Object



88
89
90
91
92
93
94
95
96
# File 'lib/metanorma/standoc/ref.rb', line 88

def isorefmatches3out(item, xml)
  if item[:doc] then use_retrieved_relaton(item, xml)
  else
    isorefmatches3_1(xml, item[:ref][:match],
                     item[:ref][:analyse_code],
                     item[:ref][:yr],
                     item[:ref][:hasyr], item[:doc])
  end
end

#isorefmatchescode(match, _item) ⇒ Object



25
26
27
28
29
30
31
# File 'lib/metanorma/standoc/ref.rb', line 25

def isorefmatchescode(match, _item)
  code = analyse_ref_code(match[:code])
  yr = norm_year(match[:year])
  { code: match[:code], year: yr, match: match,
    title: match[:text], usrlbl: match[:usrlbl] || code[:usrlabel],
    analyse_code: code, lang: (@lang || :all) }
end

#isorefmatchesout(item, xml) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/metanorma/standoc/ref.rb', line 33

def isorefmatchesout(item, xml)
  if item[:doc] then use_retrieved_relaton(item, xml)
  else
    xml.bibitem **attr_code(ref_attributes(item[:ref][:match])) do |t|
      isorefrender1(t, item[:ref][:match], item[:ref][:analyse_code],
                    item[:ref][:year])
      item[:ref][:year] and t.date type: "published" do |d|
        set_date_range(d, item[:ref][:year])
      end
      iso_publisher(t, item[:ref][:match][:code])
    end
  end
end

#isorefrender1(bib, match, code, year, allp = "") ⇒ Object



17
18
19
20
21
22
23
# File 'lib/metanorma/standoc/ref.rb', line 17

def isorefrender1(bib, match, code, year, allp = "")
  bib.title(**plaintxt) { |i| i << ref_normalise(match[:text]) }
  docid(bib, match[:usrlbl]) if match[:usrlbl]
  docid(bib, code[:usrlabel]) if code && code[:usrlabel]
  docid(bib, id_and_year(match[:code], year) + allp)
  docnumber(bib, match[:code])
end

#mn_code(code) ⇒ Object



61
62
63
64
65
66
67
# File 'lib/metanorma/standoc/ref_utility.rb', line 61

def mn_code(code)
  code.sub(/^\(/, "[").sub(/\).*$/, "]")
    .sub(/^dropid\((.+)\)$/, "\\1")
    .sub(/^hidden\((.+)\)$/, "\\1")
    .sub(/^nofetch\((.+)\)$/, "\\1")
    .sub(/^local-file\((.+)\)$/, "\\1")
end

#no_year_generic_ref(code) ⇒ Object

if no year is supplied, interpret as no_year reference



174
175
176
# File 'lib/metanorma/standoc/ref_utility.rb', line 174

def no_year_generic_ref(code)
  /^(BSI|BS)\b/.match?(code)
end

#norm_year(year) ⇒ Object



20
21
22
23
24
# File 'lib/metanorma/standoc/ref_utility.rb', line 20

def norm_year(year)
  /^&\#821[12];$/.match(year) and return "--"
  /^\d\d\d\d-\d\d\d\d$/.match(year) and return year
  year&.sub(/(?<=[0-9])-.*$/, "")
end

#plaintxtObject



178
179
180
# File 'lib/metanorma/standoc/ref_utility.rb', line 178

def plaintxt
  { format: "text/plain" }
end

#ref_attributes(match) ⇒ Object



182
183
184
185
186
187
# File 'lib/metanorma/standoc/ref_utility.rb', line 182

def ref_attributes(match)
  code = analyse_ref_code(match[:code])

  { id: match[:anchor], type: "standard",
    suppress_identifier: code[:dropid] || nil }
end

#ref_normalise(ref) ⇒ Object



195
196
197
# File 'lib/metanorma/standoc/ref_utility.rb', line 195

def ref_normalise(ref)
  ref.gsub(/&amp;amp;/, "&amp;").gsub(%r{^<em>(.*)</em>}, "\\1")
end

#ref_normalise_no_format(ref) ⇒ Object



199
200
201
202
# File 'lib/metanorma/standoc/ref_utility.rb', line 199

def ref_normalise_no_format(ref)
  ref.gsub(/&amp;amp;/, "&amp;")
    .gsub(/>\n/, "> \n")
end

#reference(node) ⇒ Object



233
234
235
236
237
238
# File 'lib/metanorma/standoc/ref.rb', line 233

def reference(node)
  refs = node.items.each_with_object([]) do |b, m|
    m << reference1code(b.text, node)
  end
  reference_populate(reference_normalise(refs))
end

#reference1_matches(item) ⇒ Object



203
204
205
206
207
208
# File 'lib/metanorma/standoc/ref.rb', line 203

def reference1_matches(item)
  matched = ISO_REF.match item
  matched2 = ISO_REF_NO_YEAR.match item
  matched3 = ISO_REF_ALL_PARTS.match item
  [matched, matched2, matched3]
end

#reference1code(item, node) ⇒ Object



210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/metanorma/standoc/ref.rb', line 210

def reference1code(item, node)
  matched, matched2, matched3 = reference1_matches(item)
  if matched3.nil? && matched2.nil? && matched.nil?
    refitemcode(item, node).merge(process: 0)
  elsif !matched.nil? then isorefmatchescode(matched,
                                             item).merge(process: 1)
  elsif !matched2.nil? then isorefmatches2code(matched2,
                                               item).merge(process: 2)
  elsif !matched3.nil? then isorefmatches3code(matched3,
                                               item).merge(process: 3)
  end
end

#reference1out(item, xml) ⇒ Object



223
224
225
226
227
228
229
230
231
# File 'lib/metanorma/standoc/ref.rb', line 223

def reference1out(item, xml)
  item[:ref][:analyse_code] ||= analyse_ref_code(item[:ref][:code])
  case item[:ref][:process]
  when 0 then refitemout(item, xml)
  when 1 then isorefmatchesout(item, xml)
  when 2 then isorefmatches2out(item, xml)
  when 3 then isorefmatches3out(item, xml)
  end
end

#reference_normalise(refs) ⇒ Object



240
241
242
243
244
245
# File 'lib/metanorma/standoc/ref.rb', line 240

def reference_normalise(refs)
  refs.each do |r|
    r[:code] = @c.decode(r[:code])
      .gsub(/\u2009\u2014\u2009/, " -- ").strip
  end
end

#reference_populate(refs) ⇒ Object



247
248
249
250
251
252
253
254
255
# File 'lib/metanorma/standoc/ref.rb', line 247

def reference_populate(refs)
  results = refs.each_with_index.with_object(Queue.new) do |(ref, i), res|
    fetch_ref_async(ref.merge(ord: i), i, res)
  end
  ret = reference_queue(refs, results)
  noko do |xml|
    ret.each { |b| reference1out(b, xml) }
  end.join
end

#reference_queue(refs, results) ⇒ Object



257
258
259
260
261
262
263
264
265
266
267
# File 'lib/metanorma/standoc/ref.rb', line 257

def reference_queue(refs, results)
  refs.each.with_object([]) do |_, m|
    ref, i, doc = results.pop
    m[i.to_i] = { ref: ref }
    if doc.is_a?(RelatonBib::RequestError)
      @log.add("Bibliography", nil, "Could not retrieve #{ref[:code]}: " \
                                    "no access to online site")
    else m[i.to_i][:doc] = doc
    end
  end
end

#refitem1code(_item, match) ⇒ Object



151
152
153
154
155
156
157
158
159
160
# File 'lib/metanorma/standoc/ref.rb', line 151

def refitem1code(_item, match)
  code = analyse_ref_code(match[:code])
  ((code[:id] && code[:numeric]) || code[:nofetch]) and
    return { code: nil, match: match, analyse_code: code,
             hidden: code[:hidden] }
  { code: code[:id], analyse_code: code, localfile: code[:localfile],
    year: (m = refitem1yr(code[:id])) ? m[:year] : nil,
    title: match[:text], match: match, hidden: code[:hidden],
    usrlbl: match[:usrlbl] || code[:usrlabel], lang: (@lang || :all) }
end

#refitem1yr(code) ⇒ Object



162
163
164
165
166
167
# File 'lib/metanorma/standoc/ref.rb', line 162

def refitem1yr(code)
  yr_match = /[:-](?<year>(?:19|20)[0-9][0-9])$/.match(code)
  /[:-](?:19|20)[0-9][0-9].*?[:-](?:19|20)[0-9][0-9]$/.match(code) and
    yr_match = nil
  yr_match
end

#refitem_render(xml, match, code) ⇒ Object



128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/metanorma/standoc/ref.rb', line 128

def refitem_render(xml, match, code)
  xml.bibitem **attr_code(id: match[:anchor],
                          suppress_identifier: code[:dropid],
                          hidden: code[:hidden]) do |t|
    t.formattedref format: "application/x-isodoc+xml" do |i|
      i << ref_normalise_no_format(match[:text])
    end
    yr_match = refitem1yr(code[:id])
    refitem_render1(match, code, t)
    /^\d+$|^\(.+\)$/.match?(code[:id]) or
      docnumber(t, code[:id]&.sub(/[:-](19|20)[0-9][0-9]$/, ""))
    conditional_date(t, yr_match || match, false)
  end
end

#refitem_render1(match, code, bib) ⇒ Object



114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/metanorma/standoc/ref.rb', line 114

def refitem_render1(match, code, bib)
  if code[:type] == "path"
    bib.uri code[:key].sub(/\.[a-zA-Z0-9]+$/, ""), type: "URI"
    bib.uri code[:key].sub(/\.[a-zA-Z0-9]+$/, ""), type: "citation"
  end
  # code[:id].sub!(/[:-](19|20)[0-9][0-9]$/, "")
  docid(bib, match[:usrlbl]) if match[:usrlbl]
  docid(bib, code[:usrlabel]) if code[:usrlabel]
  code[:id] and
    docid(bib, /^\d+$/.match?(code[:id]) ? "[#{code[:id]}]" : code[:id])
  code[:type] == "repo" and
    bib.docidentifier code[:key], type: "repository"
end

#refitemcode(item, node) ⇒ Object

TODO: alternative where only title is available



144
145
146
147
148
149
# File 'lib/metanorma/standoc/ref.rb', line 144

def refitemcode(item, node)
  m = NON_ISO_REF.match(item) and return refitem1code(item, m).compact
  m = NON_ISO_REF1.match(item) and return refitem1code(item, m).compact
  @log.add("AsciiDoc Input", node, "#{MALFORMED_REF}: #{item}")
  {}
end

#refitemout(item, xml) ⇒ Object



169
170
171
172
173
174
# File 'lib/metanorma/standoc/ref.rb', line 169

def refitemout(item, xml)
  item[:ref][:match].nil? and return nil
  item[:doc] or return refitem_render(xml, item[:ref][:match],
                                      item[:ref][:analyse_code])
  use_retrieved_relaton(item, xml)
end

#set_date_range(date, text) ⇒ Object



4
5
6
7
8
9
10
11
12
13
14
# File 'lib/metanorma/standoc/ref_utility.rb', line 4

def set_date_range(date, text)
  matched = /^(?<from>[0-9]+)(?:-+(?<to>[0-9]+))?$/.match text
  return unless matched[:from]

  if matched[:to]
    date.from matched[:from]
    date.to matched[:to]
  else
    date.on matched[:from]
  end
end

#skip_docidObject



204
205
206
207
208
# File 'lib/metanorma/standoc/ref_utility.rb', line 204

def skip_docid
  <<~XPATH.strip.freeze
    @type = 'DOI' or @type = 'doi' or @type = 'ISSN' or @type = 'issn' or @type = 'ISBN' or @type = 'isbn' or starts-with(@type, 'ISSN.') or starts-with(@type, 'ISBN.') or starts-with(@type, 'issn.') or starts-with(@type, 'isbn.')
  XPATH
end

#use_my_anchor(ref, id, opt) ⇒ Object



34
35
36
37
38
39
40
# File 'lib/metanorma/standoc/ref_utility.rb', line 34

def use_my_anchor(ref, id, opt)
  ref.parent.elements.last["id"] = id
  opt[:hidden] and ref.parent.elements.last["hidden"] = opt[:hidden]
  opt[:dropid] and
    ref.parent.elements.last["suppress_identifier"] = opt[:dropid]
  ref
end