metanorma-iso 1.7.2 → 1.8.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake.yml +11 -41
  3. data/.gitignore +2 -0
  4. data/.rubocop.yml +7 -1
  5. data/lib/asciidoctor/iso/base.rb +14 -11
  6. data/lib/asciidoctor/iso/biblio.rng +4 -6
  7. data/lib/asciidoctor/iso/cleanup.rb +40 -24
  8. data/lib/asciidoctor/iso/front.rb +29 -17
  9. data/lib/asciidoctor/iso/front_id.rb +81 -60
  10. data/lib/asciidoctor/iso/isodoc.rng +327 -2
  11. data/lib/asciidoctor/iso/isostandard.rng +12 -97
  12. data/lib/asciidoctor/iso/section.rb +2 -1
  13. data/lib/asciidoctor/iso/validate.rb +22 -109
  14. data/lib/asciidoctor/iso/validate_image.rb +97 -0
  15. data/lib/asciidoctor/iso/validate_requirements.rb +26 -20
  16. data/lib/asciidoctor/iso/validate_section.rb +39 -20
  17. data/lib/asciidoctor/iso/validate_style.rb +36 -24
  18. data/lib/asciidoctor/iso/validate_title.rb +23 -17
  19. data/lib/isodoc/iso/base_convert.rb +19 -2
  20. data/lib/isodoc/iso/html/style-human.css +7 -0
  21. data/lib/isodoc/iso/html/style-iso.css +7 -0
  22. data/lib/isodoc/iso/html_convert.rb +0 -1
  23. data/lib/isodoc/iso/i18n-en.yaml +4 -0
  24. data/lib/isodoc/iso/i18n-fr.yaml +4 -0
  25. data/lib/isodoc/iso/i18n-zh-Hans.yaml +4 -0
  26. data/lib/isodoc/iso/index.rb +140 -0
  27. data/lib/isodoc/iso/iso.amendment.xsl +1157 -208
  28. data/lib/isodoc/iso/iso.international-standard.xsl +1157 -208
  29. data/lib/isodoc/iso/metadata.rb +1 -0
  30. data/lib/isodoc/iso/presentation_xml_convert.rb +45 -37
  31. data/lib/isodoc/iso/sts_convert.rb +10 -13
  32. data/lib/isodoc/iso/word_convert.rb +0 -1
  33. data/lib/isodoc/iso/xref.rb +46 -25
  34. data/lib/metanorma/iso/processor.rb +1 -0
  35. data/lib/metanorma/iso/version.rb +1 -1
  36. data/metanorma-iso.gemspec +8 -8
  37. data/spec/{asciidoctor-iso → asciidoctor}/amd_spec.rb +5 -5
  38. data/spec/asciidoctor/base_spec.rb +825 -0
  39. data/spec/{asciidoctor-iso → asciidoctor}/blocks_spec.rb +0 -0
  40. data/spec/{asciidoctor-iso → asciidoctor}/cleanup_spec.rb +383 -25
  41. data/spec/{asciidoctor-iso → asciidoctor}/inline_spec.rb +0 -0
  42. data/spec/{asciidoctor-iso → asciidoctor}/lists_spec.rb +0 -0
  43. data/spec/{asciidoctor-iso → asciidoctor}/refs_spec.rb +0 -0
  44. data/spec/{asciidoctor-iso → asciidoctor}/section_spec.rb +0 -14
  45. data/spec/{asciidoctor-iso → asciidoctor}/table_spec.rb +0 -0
  46. data/spec/{asciidoctor-iso → asciidoctor}/validate_spec.rb +188 -83
  47. data/spec/isodoc/postproc_spec.rb +481 -438
  48. data/spec/isodoc/section_spec.rb +219 -0
  49. data/spec/spec_helper.rb +2 -0
  50. metadata +65 -64
  51. data/lib/isodoc/iso/html/scripts.html +0 -178
  52. data/spec/asciidoctor-iso/base_spec.rb +0 -704
@@ -7,28 +7,32 @@ module Asciidoctor
7
7
  class Converter < Standoc::Converter
8
8
  def extract_text(node)
9
9
  return "" if node.nil?
10
+
10
11
  node1 = Nokogiri::XML.fragment(node.to_s)
11
12
  node1.xpath("//link | //locality | //localityStack").each(&:remove)
12
13
  ret = ""
13
14
  node1.traverse { |x| ret += x.text if x.text? }
14
- ret
15
+ HTMLEntities.new.decode(ret)
15
16
  end
16
17
 
17
18
  # ISO/IEC DIR 2, 12.2
18
19
  def foreword_style(node)
19
20
  return if @novalid
21
+
20
22
  style_no_guidance(node, extract_text(node), "Foreword")
21
23
  end
22
24
 
23
25
  # ISO/IEC DIR 2, 14.2
24
26
  def scope_style(node)
25
27
  return if @novalid
28
+
26
29
  style_no_guidance(node, extract_text(node), "Scope")
27
30
  end
28
31
 
29
32
  # ISO/IEC DIR 2, 13.2
30
33
  def introduction_style(node)
31
34
  return if @novalid
35
+
32
36
  r = requirement_check(extract_text(node))
33
37
  style_warning(node, "Introduction may contain requirement", r) if r
34
38
  end
@@ -36,6 +40,7 @@ module Asciidoctor
36
40
  # ISO/IEC DIR 2, 16.5.6
37
41
  def definition_style(node)
38
42
  return if @novalid
43
+
39
44
  r = requirement_check(extract_text(node))
40
45
  style_warning(node, "Definition may contain requirement", r) if r
41
46
  end
@@ -44,6 +49,7 @@ module Asciidoctor
44
49
  # ISO/IEC DIR 2, 25.5
45
50
  def example_style(node)
46
51
  return if @novalid
52
+
47
53
  style_no_guidance(node, extract_text(node), "Example")
48
54
  style(node, extract_text(node))
49
55
  end
@@ -51,6 +57,7 @@ module Asciidoctor
51
57
  # ISO/IEC DIR 2, 24.5
52
58
  def note_style(node)
53
59
  return if @novalid
60
+
54
61
  style_no_guidance(node, extract_text(node), "Note")
55
62
  style(node, extract_text(node))
56
63
  end
@@ -58,6 +65,7 @@ module Asciidoctor
58
65
  # ISO/IEC DIR 2, 26.5
59
66
  def footnote_style(node)
60
67
  return if @novalid
68
+
61
69
  style_no_guidance(node, extract_text(node), "Footnote")
62
70
  style(node, extract_text(node))
63
71
  end
@@ -70,6 +78,7 @@ module Asciidoctor
70
78
  # and a negative match on its preceding token
71
79
  def style_two_regex_not_prev(n, text, re, re_prev, warning)
72
80
  return if text.nil?
81
+
73
82
  arr = Tokenizer::WhitespaceTokenizer.new.tokenize(text)
74
83
  arr.each_index do |i|
75
84
  m = re.match arr[i]
@@ -80,43 +89,45 @@ module Asciidoctor
80
89
  end
81
90
  end
82
91
 
83
- def style(n, t)
92
+ def style(node, text)
84
93
  return if @novalid
85
- style_number(n, t)
86
- style_percent(n, t)
87
- style_abbrev(n, t)
88
- style_units(n, t)
94
+
95
+ style_number(node, text)
96
+ style_percent(node, text)
97
+ style_abbrev(node, text)
98
+ style_units(node, text)
89
99
  end
90
100
 
91
101
  # ISO/IEC DIR 2, 9.1
92
102
  # ISO/IEC DIR 2, Table B.1
93
- def style_number(n, t)
103
+ def style_number(node, text)
94
104
  style_two_regex_not_prev(
95
- n, t, /^(?<num>-?[0-9]{4,}[,0-9]*)$/,
105
+ node, text, /^(?<num>-?[0-9]{4,}[,0-9]*)$/,
96
106
  %r{\b(ISO|IEC|IEEE/|(in|January|February|March|April|May|June|August|September|October|November|December)\b)$},
97
- "number not broken up in threes")
107
+ "number not broken up in threes"
108
+ )
98
109
  style_regex(/\b(?<num>[0-9]+\.[0-9]+)/i,
99
- "possible decimal point", n, t)
100
- style_regex(/\b(?<num>billion[s]?)\b/i,
101
- "ambiguous number", n, t)
110
+ "possible decimal point", node, text)
111
+ style_regex(/\b(?<num>billions?)\b/i,
112
+ "ambiguous number", node, text)
102
113
  end
103
114
 
104
115
  # ISO/IEC DIR 2, 9.2.1
105
- def style_percent(n, t)
116
+ def style_percent(node, text)
106
117
  style_regex(/\b(?<num>[0-9.,]+%)/,
107
- "no space before percent sign", n, t)
118
+ "no space before percent sign", node, text)
108
119
  style_regex(/\b(?<num>[0-9.,]+ \u00b1 [0-9,.]+ %)/,
109
- "unbracketed tolerance before percent sign", n, t)
120
+ "unbracketed tolerance before percent sign", node, text)
110
121
  end
111
122
 
112
123
  # ISO/IEC DIR 2, 8.4
113
124
  # ISO/IEC DIR 2, 9.3
114
- def style_abbrev(n, t)
125
+ def style_abbrev(node, text)
115
126
  style_regex(/(^|\s)(?!e\.g\.|i\.e\.)
116
127
  (?<num>[a-z]{1,2}\.([a-z]{1,2}|\.))\b/ix,
117
- "no dots in abbreviations", n, t)
128
+ "no dots in abbreviations", node, text)
118
129
  style_regex(/\b(?<num>ppm)\b/i,
119
- "language-specific abbreviation", n, t)
130
+ "language-specific abbreviation", node, text)
120
131
  end
121
132
 
122
133
  # leaving out as problematic: N J K C S T H h d B o E
@@ -125,12 +136,13 @@ module Asciidoctor
125
136
  "bit|kB|MB|Hart|nat|Sh|var)".freeze
126
137
 
127
138
  # ISO/IEC DIR 2, 9.3
128
- def style_units(n, t)
139
+ def style_units(node, text)
129
140
  style_regex(/\b(?<num>[0-9][0-9,]*\s+[\u00b0\u2032\u2033])/,
130
- "space between number and degrees/minutes/seconds", n, t)
141
+ "space between number and degrees/minutes/seconds",
142
+ node, text)
131
143
  style_regex(/\b(?<num>[0-9][0-9,]*#{SI_UNIT})\b/,
132
- "no space between number and SI unit", n, t)
133
- style_non_std_units(n, t)
144
+ "no space between number and SI unit", node, text)
145
+ style_non_std_units(node, text)
134
146
  end
135
147
 
136
148
  NONSTD_UNITS = {
@@ -139,10 +151,10 @@ module Asciidoctor
139
151
  }.freeze
140
152
 
141
153
  # ISO/IEC DIR 2, 9.3
142
- def style_non_std_units(n, t)
154
+ def style_non_std_units(node, text)
143
155
  NONSTD_UNITS.each do |k, v|
144
156
  style_regex(/\b(?<num>[0-9][0-9,]*\s+#{k})\b/,
145
- "non-standard unit (should be #{v})", n, t)
157
+ "non-standard unit (should be #{v})", node, text)
146
158
  end
147
159
  end
148
160
  end
@@ -3,9 +3,13 @@ require "metanorma-standoc"
3
3
  module Asciidoctor
4
4
  module ISO
5
5
  class Converter < Standoc::Converter
6
+ def title_lang_part(doc, part, lang)
7
+ doc.at("//bibdata/title[@type='title-#{part}' and @language='#{lang}']")
8
+ end
9
+
6
10
  def title_intro_validate(root)
7
- title_intro_en = root.at("//title[@type='title-intro' and @language='en']")
8
- title_intro_fr = root.at("//title[@type='title-intro' and @language='fr']")
11
+ title_intro_en = title_lang_part(root, "intro", "en")
12
+ title_intro_fr = title_lang_part(root, "intro", "fr")
9
13
  if title_intro_en.nil? && !title_intro_fr.nil?
10
14
  @log.add("Style", title_intro_fr, "No English Title Intro!")
11
15
  end
@@ -15,8 +19,8 @@ module Asciidoctor
15
19
  end
16
20
 
17
21
  def title_main_validate(root)
18
- title_main_en = root.at("//title[@type='title-main' and @language='en']")
19
- title_main_fr = root.at("//title[@type='title-main' and @language='fr']")
22
+ title_main_en = title_lang_part(root, "main", "en")
23
+ title_main_fr = title_lang_part(root, "main", "fr")
20
24
  if title_main_en.nil? && !title_main_fr.nil?
21
25
  @log.add("Style", title_main_fr, "No English Title!")
22
26
  end
@@ -26,8 +30,8 @@ module Asciidoctor
26
30
  end
27
31
 
28
32
  def title_part_validate(root)
29
- title_part_en = root.at("//title[@type='title-part' and @language='en']")
30
- title_part_fr = root.at("//title[@type='title-part' and @language='fr']")
33
+ title_part_en = title_lang_part(root, "part", "en")
34
+ title_part_fr = title_lang_part(root, "part", "fr")
31
35
  (title_part_en.nil? && !title_part_fr.nil?) &&
32
36
  @log.add("Style", title_part_fr, "No English Title Part!")
33
37
  (!title_part_en.nil? && title_part_fr.nil?) &&
@@ -41,21 +45,21 @@ module Asciidoctor
41
45
  iec = root.at("//bibdata/contributor[role/@type = 'publisher']/"\
42
46
  "organization[abbreviation = 'IEC' or "\
43
47
  "name = 'International Electrotechnical Commission']")
44
- @log.add("Style", docid, "Subpart defined on non-IEC document!") if subpart && !iec
48
+ subpart && !iec and
49
+ @log.add("Style", docid, "Subpart defined on non-IEC document!")
45
50
  end
46
51
 
47
52
  # ISO/IEC DIR 2, 11.5.2
48
53
  def title_names_type_validate(root)
49
54
  doctypes = /International\sStandard | Technical\sSpecification |
50
55
  Publicly\sAvailable\sSpecification | Technical\sReport | Guide /xi
51
- title_main_en = root.at("//title[@type='title-main' and @language='en']")
52
- if !title_main_en.nil? && doctypes.match(title_main_en.text)
56
+ title_main_en = title_lang_part(root, "main", "en")
57
+ !title_main_en.nil? && doctypes.match(title_main_en.text) and
53
58
  @log.add("Style", title_main_en, "Main Title may name document type")
54
- end
55
- title_intro_en = root.at("//title[@type='title-intro' and @language='en']")
56
- if !title_intro_en.nil? && doctypes.match(title_intro_en.text)
57
- @log.add("Style", title_intro_en, "Title Intro may name document type")
58
- end
59
+ title_intro_en = title_lang_part(root, "intro", "en")
60
+ !title_intro_en.nil? && doctypes.match(title_intro_en.text) and
61
+ @log.add("Style", title_intro_en,
62
+ "Title Intro may name document type")
59
63
  end
60
64
 
61
65
  # ISO/IEC DIR 2, 22.2
@@ -64,8 +68,9 @@ module Asciidoctor
64
68
  title = s&.at("./title")&.text || s.name
65
69
  s.xpath("./clause | ./terms | ./references").each do |ss|
66
70
  subtitle = ss.at("./title")
67
- !subtitle.nil? && !subtitle&.text&.empty? ||
68
- @log.add("Style", ss, "#{title}: each first-level subclause must have a title")
71
+ !subtitle.nil? && !subtitle&.text&.empty? or
72
+ @log.add("Style", ss,
73
+ "#{title}: each first-level subclause must have a title")
69
74
  end
70
75
  end
71
76
  end
@@ -82,7 +87,8 @@ module Asciidoctor
82
87
  withtitle = withtitle || (subtitle && !subtitle.text.empty?)
83
88
  end
84
89
  notitle && withtitle &&
85
- @log.add("Style", nil, "#{label}: all subclauses must have a title, or none")
90
+ @log.add("Style", nil,
91
+ "#{label}: all subclauses must have a title, or none")
86
92
  end
87
93
 
88
94
  def title_validate(root)
@@ -15,6 +15,7 @@ module IsoDoc
15
15
 
16
16
  def implicit_reference(b)
17
17
  return true if b&.at(ns("./docidentifier"))&.text == "IEV"
18
+
18
19
  super
19
20
  end
20
21
 
@@ -27,8 +28,9 @@ module IsoDoc
27
28
  end
28
29
  end
29
30
 
30
- def example_span_label(node, div, name)
31
+ def example_span_label(_node, div, name)
31
32
  return if name.nil?
33
+
32
34
  div.span **{ class: "example_label" } do |p|
33
35
  name.children.each { |n| parse(n, div) }
34
36
  end
@@ -56,6 +58,7 @@ module IsoDoc
56
58
  node.elements.each do |e|
57
59
  next if e.name == "name"
58
60
  return true if e.name == "p"
61
+
59
62
  return false
60
63
  end
61
64
  false
@@ -74,6 +77,7 @@ module IsoDoc
74
77
  def insertall_after_here(node, insert, name)
75
78
  node.children.each do |n|
76
79
  next unless n.name == name
80
+
77
81
  insert.next = n.remove
78
82
  insert = n
79
83
  end
@@ -83,6 +87,7 @@ module IsoDoc
83
87
  def termexamples_before_termnotes(node)
84
88
  return unless node.at(ns("./termnote")) && node.at(ns("./termexample"))
85
89
  return unless insert = node.at(ns("./definition"))
90
+
86
91
  insert = insertall_after_here(node, insert, "termexample")
87
92
  insert = insertall_after_here(node, insert, "termnote")
88
93
  end
@@ -110,6 +115,7 @@ module IsoDoc
110
115
  return super unless (dl&.xpath(ns("./dt"))&.size == 1 &&
111
116
  dl&.at(ns("./dd"))&.elements&.size == 1 &&
112
117
  dl&.at(ns("./dd/p")))
118
+
113
119
  out.span **{ class: "zzMoveToFollowing" } do |s|
114
120
  s << "#{@i18n.where} "
115
121
  dl.at(ns("./dt")).children.each { |n| parse(n, s) }
@@ -149,7 +155,18 @@ module IsoDoc
149
155
 
150
156
  def figure_name_parse(node, div, name)
151
157
  div.p **{ class: "FigureTitle", style: "text-align:center;" } do |p|
152
- name and name.children.each { |n| parse(n, div) }
158
+ name&.children&.each { |n| parse(n, div) }
159
+ end
160
+ end
161
+
162
+ def middle(isoxml, out)
163
+ super
164
+ indexsect isoxml, out
165
+ end
166
+
167
+ def indexsect(isoxml, out)
168
+ isoxml.xpath(ns("//indexsect")).each do |i|
169
+ clause_parse(i, out)
153
170
  end
154
171
  end
155
172
  end
@@ -115,6 +115,13 @@ a.FootnoteRef + a.FootnoteRef:before {
115
115
  content: ", ";
116
116
  vertical-align: super; }
117
117
 
118
+ .addition {
119
+ color: blue; }
120
+
121
+ .deletion {
122
+ color: red;
123
+ text-decoration: line-through; }
124
+
118
125
  #standard-band {
119
126
  background-color: #0AC442; }
120
127
 
@@ -115,6 +115,13 @@ a.FootnoteRef + a.FootnoteRef:before {
115
115
  content: ", ";
116
116
  vertical-align: super; }
117
117
 
118
+ .addition {
119
+ color: blue; }
120
+
121
+ .deletion {
122
+ color: red;
123
+ text-decoration: line-through; }
124
+
118
125
  #standard-band {
119
126
  background-color: #0AC442; }
120
127
 
@@ -38,7 +38,6 @@ module IsoDoc
38
38
  html_doc_path("style-iso.scss")),
39
39
  htmlcoverpage: html_doc_path("html_iso_titlepage.html"),
40
40
  htmlintropage: html_doc_path("html_iso_intro.html"),
41
- scripts: html_doc_path("scripts.html"),
42
41
  }
43
42
  end
44
43
 
@@ -14,3 +14,7 @@ price_based_on: Price based on % pages
14
14
  under_preparation: Under preparation. (Stage at the time of publication %).
15
15
  table_of_contents: Contents
16
16
  date: Date
17
+ index: Index
18
+ see: see
19
+ see_also: see also
20
+
@@ -13,3 +13,7 @@ reference_number: Numéro de référence
13
13
  price_based_on: Prix basé sur % pages
14
14
  under_preparation: En cours d'élaboration. (Stade au moment de la publication %).
15
15
  date: Date
16
+ index: Index
17
+ see: voir
18
+ see_also: voir aussi
19
+
@@ -11,3 +11,7 @@ reference_number: 参考编号
11
11
  price_based_on: 价格基于%页
12
12
  under_preparation: 制定中(出版时最新状态为%)
13
13
  date: 日期
14
+ index: 索引
15
+ see: 见
16
+ see_also: 另见
17
+
@@ -0,0 +1,140 @@
1
+ module IsoDoc
2
+ module Iso
3
+ class PresentationXMLConvert < IsoDoc::PresentationXMLConvert
4
+ def add_id
5
+ %(id="_#{UUIDTools::UUID.random_create}")
6
+ end
7
+
8
+ def index(docxml)
9
+ unless docxml.at(ns("//index"))
10
+ docxml.xpath(ns("//indexsect")).each { |i| i.remove }
11
+ return
12
+ end
13
+ i = docxml.at(ns("//indexsect")) ||
14
+ docxml.root.add_child("<indexsect #{add_id}><title>#{@i18n.index}</title></indexsect>").first
15
+ index = sort_indexterms(docxml.xpath(ns("//index")), docxml.xpath(ns("//index-xref[@also = 'false']")),
16
+ docxml.xpath(ns("//index-xref[@also = 'true']")))
17
+ index1(docxml, i, index)
18
+ end
19
+
20
+ def index1(docxml, i, index)
21
+ c = i.add_child("<ul></ul>").first
22
+ index.keys.sort.each do |k|
23
+ #c = i.add_child "<clause #{add_id}><title>#{k}</title><ul></ul></clause>"
24
+ words = index[k].keys.each_with_object({}) { |w, v| v[sortable(w).downcase] = w }
25
+ words.keys.localize(@lang.to_sym).sort.to_a.each do |w|
26
+ #c.first.at(ns("./ul")).add_child index_entries(words, index[k], w)
27
+ c.add_child index_entries(words, index[k], w)
28
+ end
29
+ end
30
+ docxml.xpath(ns("//indexsect//xref")).each { |x| x.children.remove }
31
+ @xrefs.bookmark_anchor_names(docxml)
32
+ end
33
+
34
+ def sortable(s)
35
+ HTMLEntities.new.decode(Nokogiri::XML.fragment(s).text)
36
+ end
37
+
38
+ def index_entries_opt
39
+ { xref_lbl: ", ", see_lbl: ", #{see_lbl}", also_lbl: ", #{also_lbl}" }
40
+ end
41
+
42
+ def index_entries(words, index, primary)
43
+ ret = index_entries_head(words[primary], index.dig(words[primary], nil, nil), index_entries_opt)
44
+ words2 = index[words[primary]]&.keys&.reject { |k| k.nil?}&.each_with_object({}) { |w, v| v[w.downcase] = w }
45
+ unless words2.empty?
46
+ ret += "<ul>"
47
+ words2.keys.localize(@lang.to_sym).sort.to_a.each do |w|
48
+ ret += index_entries2(words2, index[words[primary]], w)
49
+ end
50
+ ret += "</ul>"
51
+ end
52
+ ret + "</li>"
53
+ end
54
+
55
+ def index_entries2(words, index, secondary)
56
+ ret = index_entries_head(words[secondary], index.dig(words[secondary], nil), index_entries_opt)
57
+ words3 = index[words[secondary]]&.keys&.reject { |k| k.nil?}&.each_with_object({}) { |w, v| v[w.downcase] = w }
58
+ unless words3.empty?
59
+ ret += "<ul>"
60
+ words3.keys.localize(@lang.to_sym).sort.to_a.each do |w|
61
+ ret += (index_entries_head(words3[w], index[words[secondary]][words3[w]], index_entries_opt) + "</li>")
62
+ end
63
+ ret += "</ul>"
64
+ end
65
+ ret + "</li>"
66
+ end
67
+
68
+ def index_entries_head(head, entries, opt)
69
+ ret = "<li>#{head}"
70
+ xref = entries&.dig(:xref)&.join(", ")
71
+ see_sort = entries&.dig(:see)&.each_with_object({}) { |w, v| v[sortable(w).downcase] = w }
72
+ see = see_sort&.keys&.localize(@lang.to_sym)&.sort&.to_a&.map { |k| see_sort[k] }&.join(", ")
73
+ also_sort = entries&.dig(:also)&.each_with_object({}) { |w, v| v[sortable(w).downcase] = w }
74
+ also = also_sort&.keys&.localize(@lang.to_sym)&.sort&.to_a&.map { |k| also_sort[k] }&.join(", ")
75
+ ret += "#{opt[:xref_lbl]} #{xref}" if xref
76
+ ret += "#{opt[:see_lbl]} #{see}" if see
77
+ ret += "#{opt[:also_lbl]} #{also}" if also
78
+ ret
79
+ end
80
+
81
+ def see_lbl
82
+ @lang == "en" ? @i18n.see : "<em>#{@i18n.see}</em>"
83
+ end
84
+
85
+ def also_lbl
86
+ @lang == "en" ? @i18n.see_also : "<em>#{@i18n.see_also}</em>"
87
+ end
88
+
89
+ def sort_indexterms(terms, see, also)
90
+ index = extract_indexterms(terms)
91
+ index = extract_indexsee(index, see, :see)
92
+ index = extract_indexsee(index, also, :also)
93
+ index.keys.sort.each_with_object({}) do |k, v|
94
+ v[sortable(k)[0].upcase.transliterate] ||= {}
95
+ v[sortable(k)[0].upcase.transliterate][k] = index[k]
96
+ end
97
+ end
98
+
99
+ def extract_indexsee(v, terms, label)
100
+ terms.each_with_object(v) do |t, v|
101
+ term = t&.at(ns("./primary"))&.children&.to_xml
102
+ term2 = t&.at(ns("./secondary"))&.children&.to_xml
103
+ term3 = t&.at(ns("./tertiary"))&.children&.to_xml
104
+ v[term] ||= {}
105
+ v[term][term2] ||= {}
106
+ v[term][term2][term3] ||= {}
107
+ v[term][term2][term3][label] ||= []
108
+ v[term][term2][term3][label] << t&.at(ns("./target"))&.children&.to_xml
109
+ t.remove
110
+ end
111
+ end
112
+
113
+ def xml_encode_attr(s)
114
+ HTMLEntities.new.encode(s, :basic, :hexadecimal).gsub(/\&#x([^;]+);/) { |x| "&#x#{$1.upcase};" }
115
+ end
116
+
117
+ # attributes are decoded into UTF-8, elements in extract_indexsee are still in entities
118
+ def extract_indexterms(terms)
119
+ terms.each_with_object({}) do |t, v|
120
+ term = t&.at(ns("./primary"))&.children&.to_xml
121
+ term2 = t&.at(ns("./secondary"))&.children&.to_xml
122
+ term3 = t&.at(ns("./tertiary"))&.children&.to_xml
123
+ index2bookmark(t)
124
+ v[term] ||= {}
125
+ v[term][term2] ||= {}
126
+ v[term][term2][term3] ||= {}
127
+ v[term][term2][term3][:xref] ||= []
128
+ to = t["to"] ? "to='#{t['to']}' " : ""
129
+ v[term][term2][term3][:xref] << "<xref target='#{t['id']}' #{to}pagenumber='true'/>"
130
+ end
131
+ end
132
+
133
+ def index2bookmark(t)
134
+ t.name = "bookmark"
135
+ t.children.each { |x| x.remove }
136
+ t["id"] = "_#{UUIDTools::UUID.random_create}"
137
+ end
138
+ end
139
+ end
140
+ end