metanorma-iso 1.7.2 → 1.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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