isodoc 1.2.8 → 1.4.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake.yml +15 -1
  3. data/isodoc.gemspec +1 -1
  4. data/lib/isodoc-yaml/i18n-en.yaml +55 -0
  5. data/lib/isodoc-yaml/i18n-fr.yaml +56 -0
  6. data/lib/isodoc/base_style/blocks.scss +2 -2
  7. data/lib/isodoc/base_style/typography.scss +1 -1
  8. data/lib/isodoc/convert.rb +13 -85
  9. data/lib/isodoc/css.rb +95 -0
  10. data/lib/isodoc/function/inline_simple.rb +10 -1
  11. data/lib/isodoc/function/lists.rb +2 -1
  12. data/lib/isodoc/function/references.rb +7 -12
  13. data/lib/isodoc/function/section.rb +1 -1
  14. data/lib/isodoc/function/table.rb +10 -0
  15. data/lib/isodoc/function/to_word_html.rb +4 -2
  16. data/lib/isodoc/function/utils.rb +2 -2
  17. data/lib/isodoc/gem_tasks.rb +4 -0
  18. data/lib/isodoc/html_function/html.rb +7 -0
  19. data/lib/isodoc/html_function/mathvariant_to_plain.rb +82 -0
  20. data/lib/isodoc/html_function/postprocess.rb +43 -22
  21. data/lib/isodoc/metadata_contributor.rb +4 -3
  22. data/lib/isodoc/presentation_function/bibdata.rb +3 -3
  23. data/lib/isodoc/presentation_function/inline.rb +5 -3
  24. data/lib/isodoc/presentation_function/section.rb +9 -0
  25. data/lib/isodoc/presentation_xml_convert.rb +2 -0
  26. data/lib/isodoc/version.rb +1 -1
  27. data/lib/isodoc/word_convert.rb +0 -20
  28. data/lib/isodoc/word_function/inline.rb +2 -2
  29. data/lib/isodoc/word_function/postprocess.rb +38 -80
  30. data/lib/isodoc/word_function/postprocess_cover.rb +55 -0
  31. data/lib/isodoc/word_function/table.rb +10 -0
  32. data/lib/isodoc/xref.rb +1 -0
  33. data/lib/isodoc/xref/xref_counter.rb +63 -20
  34. data/lib/isodoc/xref/xref_gen.rb +20 -2
  35. data/lib/isodoc/xref/xref_sect_gen.rb +34 -27
  36. data/spec/assets/html.scss +14 -0
  37. data/spec/isodoc/blocks_spec.rb +26 -73
  38. data/spec/isodoc/cleanup_spec.rb +0 -1
  39. data/spec/isodoc/inline_spec.rb +14 -14
  40. data/spec/isodoc/lists_spec.rb +1 -1
  41. data/spec/isodoc/metadata_spec.rb +3 -1
  42. data/spec/isodoc/postproc_spec.rb +473 -11
  43. data/spec/isodoc/presentation_xml_spec.rb +2 -2
  44. data/spec/isodoc/ref_spec.rb +323 -5
  45. data/spec/isodoc/table_spec.rb +28 -0
  46. data/spec/isodoc/xref_spec.rb +472 -19
  47. metadata +6 -4
@@ -38,7 +38,8 @@ module IsoDoc::Function
38
38
  end
39
39
 
40
40
  def ol_attrs(node)
41
- { type: ol_depth(node), id: node["id"], style: keep_style(node) }
41
+ { type: node["type"] ? ol_style(node["type"].to_sym) : ol_depth(node),
42
+ id: node["id"], style: keep_style(node) }
42
43
  end
43
44
 
44
45
  def ol_parse(node, out)
@@ -80,7 +80,8 @@ module IsoDoc::Function
80
80
  end
81
81
 
82
82
  def docid_prefix(prefix, docid)
83
- docid = "#{prefix} #{docid}" if prefix && !omit_docid_prefix(prefix)
83
+ docid = "#{prefix} #{docid}" if prefix && !omit_docid_prefix(prefix) &&
84
+ !/^#{prefix}\b/.match(docid)
84
85
  docid_l10n(docid)
85
86
  end
86
87
 
@@ -111,7 +112,7 @@ module IsoDoc::Function
111
112
  # reference not to be rendered because it is deemed implicit
112
113
  # in the standards environment
113
114
  def implicit_reference(b)
114
- false
115
+ b["hidden"] == "true"
115
116
  end
116
117
 
117
118
  def prefix_bracketed_ref(ref, text)
@@ -160,7 +161,7 @@ module IsoDoc::Function
160
161
  end
161
162
 
162
163
  def norm_ref(isoxml, out, num)
163
- f = isoxml.at(ns(norm_ref_xpath)) or return num
164
+ f = isoxml.at(ns(norm_ref_xpath)) and f["hidden"] != "true" or return num
164
165
  out.div do |div|
165
166
  num = num + 1
166
167
  clause_name(num, f.at(ns("./title")), div, nil)
@@ -180,10 +181,9 @@ module IsoDoc::Function
180
181
  end
181
182
 
182
183
  def bibliography(isoxml, out)
183
- f = isoxml.at(ns(bibliography_xpath)) || return
184
+ f = isoxml.at(ns(bibliography_xpath)) and f["hidden"] != "true" or return
184
185
  page_break(out)
185
186
  out.div do |div|
186
- #div.h1 @bibliography_lbl, **{ class: "Section3" }
187
187
  div.h1 **{class: "Section3"} do |h1|
188
188
  f&.at(ns("./title"))&.children&.each { |c2| parse(c2, h1) }
189
189
  end
@@ -192,6 +192,7 @@ module IsoDoc::Function
192
192
  end
193
193
 
194
194
  def bibliography_parse(node, out)
195
+ node["hidden"] != true or return
195
196
  title = node&.at(ns("./title"))&.text || ""
196
197
  out.div do |div|
197
198
  clause_parse_title(node, div, node.at(ns("./title")), out,
@@ -202,15 +203,9 @@ module IsoDoc::Function
202
203
 
203
204
  def format_ref(ref, prefix, isopub, date, allparts)
204
205
  ref = docid_prefix(prefix, ref)
205
- return "[#{ref}]" if /^\d+$/.match(ref) && !prefix &&
206
+ return "[#{ref}]" if ref && /^\d+$/.match(ref) && !prefix &&
206
207
  !/^\[.*\]$/.match(ref)
207
208
  ref
208
209
  end
209
-
210
- # def ref_names(ref)
211
- # linkend = ref.text
212
- # linkend.gsub!(/[\[\]]/, "") unless /^\[\d+\]$/.match linkend
213
- # @anchors[ref["id"]] = { xref: linkend }
214
- # end
215
210
  end
216
211
  end
@@ -56,7 +56,7 @@ module IsoDoc::Function
56
56
  end
57
57
 
58
58
  def clause(isoxml, out)
59
- isoxml.xpath(ns(middle_clause)).each do |c|
59
+ isoxml.xpath(ns(middle_clause(isoxml))).each do |c|
60
60
  out.div **attr_code(clause_attrs(c)) do |s|
61
61
  clause_name(nil, c&.at(ns("./title")), s, nil)
62
62
  c.elements.reject { |c1| c1.name == "title" }.each do |c1|
@@ -58,11 +58,21 @@ module IsoDoc::Function
58
58
  end
59
59
  end
60
60
 
61
+ def colgroup(node, t)
62
+ colgroup = node.at(ns("./colgroup")) or return
63
+ t.colgroup do |cg|
64
+ colgroup.xpath(ns("./col")).each do |c|
65
+ cg.col **{ style: "width: #{c['width']};" }
66
+ end
67
+ end
68
+ end
69
+
61
70
  def table_parse(node, out)
62
71
  @in_table = true
63
72
  table_title_parse(node, out)
64
73
  out.table **table_attrs(node) do |t|
65
74
  tcaption(node, t)
75
+ colgroup(node, t)
66
76
  thead_parse(node, t)
67
77
  tbody_parse(node, t)
68
78
  tfoot_parse(node, t)
@@ -109,7 +109,7 @@ module IsoDoc::Function
109
109
  @meta.get
110
110
  end
111
111
 
112
- def middle_title(out)
112
+ def middle_title(_isoxml, out)
113
113
  out.p(**{ class: "zzSTDTitle1" }) { |p| p << @meta.get[:doctitle] }
114
114
  end
115
115
 
@@ -120,7 +120,7 @@ module IsoDoc::Function
120
120
  end
121
121
 
122
122
  def middle(isoxml, out)
123
- middle_title(out)
123
+ middle_title(isoxml, out)
124
124
  middle_admonitions(isoxml, out)
125
125
  i = scope isoxml, out, 0
126
126
  i = norm_ref isoxml, out, i
@@ -157,6 +157,7 @@ module IsoDoc::Function
157
157
  when "sub" then sub_parse(node, out)
158
158
  when "tt" then tt_parse(node, out)
159
159
  when "strike" then strike_parse(node, out)
160
+ when "underline" then underline_parse(node, out)
160
161
  when "keyword" then keyword_parse(node, out)
161
162
  when "smallcap" then smallcap_parse(node, out)
162
163
  when "br" then br_parse(node, out)
@@ -215,6 +216,7 @@ module IsoDoc::Function
215
216
  when "verification" then requirement_component_parse(node, out)
216
217
  when "import" then requirement_component_parse(node, out)
217
218
  when "index" then index_parse(node, out)
219
+ when "index-xref" then index_xref_parse(node, out)
218
220
  when "termref" then termrefelem_parse(node, out)
219
221
  when "copyright-statement" then copyright_parse(node, out)
220
222
  when "license-statement" then license_parse(node, out)
@@ -161,7 +161,7 @@ module IsoDoc::Function
161
161
  end
162
162
 
163
163
  def save_dataimage(uri, _relative_dir = true)
164
- %r{^data:image/(?<imgtype>[^;]+);base64,(?<imgdata>.+)$} =~ uri
164
+ %r{^data:(image|application)/(?<imgtype>[^;]+);base64,(?<imgdata>.+)$} =~ uri
165
165
  imgtype.sub!(/\+[a-z0-9]+$/, '') # svg+xml
166
166
  imgtype = 'png' unless /^[a-z0-9]+$/.match imgtype
167
167
  Tempfile.open(['image', ".#{imgtype}"]) do |f|
@@ -173,7 +173,7 @@ module IsoDoc::Function
173
173
  end
174
174
 
175
175
  def image_localfile(i)
176
- if /^data:image/.match? i['src']
176
+ if /^data:/.match? i['src']
177
177
  save_dataimage(i['src'], false)
178
178
  elsif %r{^([A-Z]:)?/}.match? i['src']
179
179
  i['src']
@@ -97,6 +97,10 @@ module IsoDoc
97
97
  $bodyfont: '{{bodyfont}}';
98
98
  $headerfont: '{{headerfont}}';
99
99
  $monospacefont: '{{monospacefont}}';
100
+ $normalfontsize: '{{normalfontsize}}';
101
+ $smallerfontsize: '{{smallerfontsize}}';
102
+ $footnotefontsize: '{{footnotefontsize}}';
103
+ $monospacefontsize: '{{monospacefontsize}}';
100
104
  TEXT
101
105
  end
102
106
 
@@ -60,6 +60,7 @@ module IsoDoc::HtmlFunction
60
60
  <script type="text/javascript">#{toclevel}</script>
61
61
 
62
62
  <!--Google fonts-->
63
+ <link rel="preconnect" href="https://fonts.gstatic.com">
63
64
  #{googlefonts}
64
65
  <!--Font awesome import for the link icon-->
65
66
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.8/css/solid.css" integrity="sha384-v2Tw72dyUXeU3y4aM2Y0tBJQkGfplr39mxZqlTBDUZAb9BGoC40+rdFCG0m10lXk" crossorigin="anonymous">
@@ -110,6 +111,12 @@ module IsoDoc::HtmlFunction
110
111
  sourcecode_name_parse(node, out, name)
111
112
  end
112
113
 
114
+ def underline_parse(node, out)
115
+ out.span **{ style: "text-decoration: underline;" } do |e|
116
+ node.children.each { |n| parse(n, e) }
117
+ end
118
+ end
119
+
113
120
  def table_long_strings_cleanup(docxml)
114
121
  end
115
122
  end
@@ -0,0 +1,82 @@
1
+ module IsoDoc
2
+ module HtmlFunction
3
+ # Class for recursively converting mathvariant text into plain text symbols
4
+ class MathvariantToPlain
5
+ MATHML = { "m" => "http://www.w3.org/1998/Math/MathML" }.freeze
6
+ MATHVARIANT_SPECIAL_CASE_MAPPINGS_1 = %w[bold italic sans-serif]
7
+ .permutation
8
+ .each_with_object(:sansbolditalic)
9
+ .map { |n, y| [n, y] }
10
+ .to_h
11
+ .freeze
12
+ MATHVARIANT_SPECIAL_CASE_MAPPINGS_2 = {
13
+ %w[bold fraktur] => :frakturbold,
14
+ %w[bold script] => :scriptbold,
15
+ %w[sans-serif bold] => :sansbold,
16
+ %w[sans-serif italic] => :sansitalic,
17
+ %w[sans-serif bold-italic] => :sansbolditalic,
18
+ %w[bold-sans-serif italic] => :sansbolditalic,
19
+ %w[sans-serif-italic bold] => :sansbolditalic,
20
+ }.freeze
21
+ MATHVARIANT_TO_PLANE_MAPPINGS = {
22
+ %w[double-struck] => :doublestruck,
23
+ %w[bold-fraktur] => :frakturbold,
24
+ %w[script] => :script,
25
+ %w[bold-script] => :scriptbold,
26
+ %w[fraktur] => :fraktur,
27
+ %w[sans-serif] => :sans,
28
+ %w[bold-sans-serif] => :sansbold,
29
+ %w[sans-serif-italic] => :sansitalic,
30
+ %w[sans-serif-bold-italic] => :sansbolditalic,
31
+ %w[monospace] => :monospace,
32
+ }.freeze
33
+
34
+ attr_reader :docxml
35
+
36
+ # @param [Nokogiri::Document] docxml
37
+ def initialize(docxml)
38
+ @docxml = docxml
39
+ end
40
+
41
+ def convert
42
+ docxml.xpath("//m:math", MATHML).each do |elem|
43
+ next if nothing_to_style(elem)
44
+ mathml1(elem)
45
+ end
46
+ docxml
47
+ end
48
+
49
+ private
50
+
51
+ def nothing_to_style(elem)
52
+ !elem.at("./*[@mathvariant][not(@mathvariant = 'normal')][not(@mathvariant = 'italic')]")
53
+ end
54
+
55
+ def mathml1(base_elem)
56
+ MATHVARIANT_SPECIAL_CASE_MAPPINGS_1
57
+ .merge(MATHVARIANT_SPECIAL_CASE_MAPPINGS_2)
58
+ .merge(MATHVARIANT_TO_PLANE_MAPPINGS)
59
+ .each_pair do |mathvariant_list, plain_font|
60
+ base_elem.xpath(mathvariant_xpath(mathvariant_list)).each do |elem|
61
+ toPlane(elem, plain_font)
62
+ end
63
+ end
64
+ end
65
+
66
+ def mathvariant_xpath(list)
67
+ list
68
+ .map { |variant| "//*[@mathvariant = '#{variant}']" }
69
+ .join
70
+ end
71
+
72
+ def toPlane(elem, font)
73
+ elem.traverse do |n|
74
+ next unless n.text?
75
+
76
+ n.replace(Plane1Converter.conv(HTMLEntities.new.decode(n.text), font))
77
+ end
78
+ elem
79
+ end
80
+ end
81
+ end
82
+ end
@@ -1,35 +1,47 @@
1
+ require "isodoc/html_function/mathvariant_to_plain"
2
+
1
3
  module IsoDoc::HtmlFunction
2
4
  module Html
3
- def postprocess(result, filename, dir)
5
+ def postprocess(result, filename, _dir)
4
6
  result = from_xhtml(cleanup(to_xhtml(textcleanup(result))))
5
7
  toHTML(result, filename)
6
8
  @files_to_delete.each { |f| FileUtils.rm_rf f }
7
9
  end
8
10
 
9
11
  def script_cdata(result)
10
- result.gsub(%r{<script([^>]*)>\s*<!\[CDATA\[}m, "<script\\1>").
11
- gsub(%r{\]\]>\s*</script>}, "</script>").
12
- gsub(%r{<!\[CDATA\[\s*<script([^>]*)>}m, "<script\\1>").
13
- gsub(%r{</script>\s*\]\]>}, "</script>")
12
+ result.gsub(%r{<script([^>]*)>\s*<!\[CDATA\[}m, "<script\\1>")
13
+ .gsub(%r{\]\]>\s*</script>}, "</script>")
14
+ .gsub(%r{<!\[CDATA\[\s*<script([^>]*)>}m, "<script\\1>")
15
+ .gsub(%r{</script>\s*\]\]>}, "</script>")
14
16
  end
15
17
 
16
18
  def toHTML(result, filename)
17
- result = (from_xhtml(html_cleanup(to_xhtml(result))))
18
- #result = populate_template(result, :html)
19
+ result = from_xhtml(html_cleanup(to_xhtml(result)))
20
+ # result = populate_template(result, :html)
19
21
  result = from_xhtml(move_images(to_xhtml(result)))
20
22
  result = html5(script_cdata(inject_script(result)))
21
23
  File.open(filename, "w:UTF-8") { |f| f.write(result) }
22
24
  end
23
25
 
24
26
  def html5(doc)
25
- doc.sub(%r{<!DOCTYPE html [^>]+>}, "<!DOCTYPE html>").
26
- sub(%r{<\?xml[^>]+>}, "")
27
+ doc.sub(%r{<!DOCTYPE html [^>]+>}, "<!DOCTYPE html>")
28
+ .sub(%r{<\?xml[^>]+>}, "")
27
29
  end
28
30
 
29
31
  def html_cleanup(x)
30
- footnote_format(footnote_backlinks(html_toc(
31
- term_header((html_footnote_filter(html_preface(htmlstyle(x))))))
32
- ))
32
+ mathml(
33
+ footnote_format(
34
+ footnote_backlinks(
35
+ html_toc(
36
+ term_header(html_footnote_filter(html_preface(htmlstyle(x))))
37
+ )
38
+ )
39
+ )
40
+ )
41
+ end
42
+
43
+ def mathml(docxml)
44
+ IsoDoc::HtmlFunction::MathvariantToPlain.new(docxml).convert
33
45
  end
34
46
 
35
47
  def htmlstylesheet
@@ -76,14 +88,14 @@ module IsoDoc::HtmlFunction
76
88
  def html_cover(docxml)
77
89
  doc = to_xhtml_fragment(File.read(@htmlcoverpage, encoding: "UTF-8"))
78
90
  d = docxml.at('//div[@class="title-section"]')
79
- #d.children.first.add_previous_sibling doc.to_xml(encoding: "US-ASCII")
91
+ # d.children.first.add_previous_sibling doc.to_xml(encoding: "US-ASCII")
80
92
  d.children.first.add_previous_sibling populate_template(doc.to_xml(encoding: "US-ASCII"), :html)
81
93
  end
82
94
 
83
95
  def html_intro(docxml)
84
96
  doc = to_xhtml_fragment(File.read(@htmlintropage, encoding: "UTF-8"))
85
97
  d = docxml.at('//div[@class="prefatory-section"]')
86
- #d.children.first.add_previous_sibling doc.to_xml(encoding: "US-ASCII")
98
+ # d.children.first.add_previous_sibling doc.to_xml(encoding: "US-ASCII")
87
99
  d.children.first.add_previous_sibling populate_template(doc.to_xml(encoding: "US-ASCII"), :html)
88
100
  end
89
101
 
@@ -93,19 +105,19 @@ module IsoDoc::HtmlFunction
93
105
  end
94
106
 
95
107
  def toclevel_classes
96
- (1..@htmlToClevels).inject([]) { |m, i| m << "h#{i}" }
108
+ (1..@htmlToClevels).reduce([]) { |m, i| m << "h#{i}" }
97
109
  end
98
110
 
99
111
  def toclevel
100
112
  ret = toclevel_classes.map { |l| "#{l}:not(:empty):not(.TermNum):not(.noTOC)" }
101
113
  <<~HEAD.freeze
102
- function toclevel() { return "#{ret.join(',')}";}
114
+ function toclevel() { return "#{ret.join(',')}";}
103
115
  HEAD
104
116
  end
105
117
 
106
118
  # needs to be same output as toclevel
107
119
  def html_toc(docxml)
108
- idx = docxml.at("//div[@id = 'toc']") or return docxml
120
+ (idx = docxml.at("//div[@id = 'toc']")) or (return docxml)
109
121
  toc = "<ul>"
110
122
  path = toclevel_classes.map do |l|
111
123
  "//main//#{l}[not(@class = 'TermNum')][not(@class = 'noTOC')][text()]"
@@ -125,7 +137,7 @@ module IsoDoc::HtmlFunction
125
137
  docxml.xpath("//*[local-name() = 'img']").each do |i|
126
138
  i["width"], i["height"] = Html2Doc.image_resize(i, image_localfile(i),
127
139
  @maxheight, @maxwidth)
128
- next if /^data:image/.match i["src"]
140
+ next if /^data:/.match i["src"]
129
141
  @datauriimage ? datauri(i) : move_image1(i)
130
142
  end
131
143
  docxml
@@ -133,9 +145,10 @@ module IsoDoc::HtmlFunction
133
145
 
134
146
  def datauri(i)
135
147
  type = i["src"].split(".")[-1]
148
+ supertype = type == "xml" ? "application" : "image"
136
149
  bin = IO.binread(image_localfile(i))
137
150
  data = Base64.strict_encode64(bin)
138
- i["src"] = "data:image/#{type};base64,#{data}"
151
+ i["src"] = "data:#{supertype}/#{type};base64,#{data}"
139
152
  end
140
153
 
141
154
  def image_suffix(i)
@@ -186,14 +199,22 @@ module IsoDoc::HtmlFunction
186
199
  docxml
187
200
  end
188
201
 
202
+ def footnote_backlinks1(x, fn)
203
+ xdup = x.dup
204
+ xdup.remove["id"]
205
+ if fn.elements.empty?
206
+ fn.children.first.previous = xdup
207
+ else
208
+ fn.elements.first.children.first.previous = xdup
209
+ end
210
+ end
211
+
189
212
  def footnote_backlinks(docxml)
190
213
  seen = {}
191
214
  docxml.xpath('//a[@class = "FootnoteRef"]').each_with_index do |x, i|
192
215
  seen[x["href"]] and next or seen[x["href"]] = true
193
216
  fn = docxml.at(%<//*[@id = '#{x['href'].sub(/^#/, '')}']>) || next
194
- xdup = x.dup
195
- xdup.remove["id"]
196
- fn.elements.first.children.first.previous = xdup
217
+ footnote_backlinks1(x, fn)
197
218
  x["id"] ||= "fnref:#{i + 1}"
198
219
  fn.add_child "<a href='##{x['id']}'>&#x21A9;</a>"
199
220
  end
@@ -17,9 +17,10 @@ module IsoDoc
17
17
  def extract_person_affiliations(authors)
18
18
  authors.reduce([]) do |m, a|
19
19
  name = a&.at(ns('./affiliation/organization/name'))&.text
20
- location = a&.at(ns('./affiliation/organization/address/'\
21
- 'formattedAddress'))&.text
22
- m << (!name.nil? && !location.nil? ? "#{name}, #{location}" :
20
+ subdivs = a&.xpath(ns('./affiliation/organization/subdivision'))&.map(&:text)&.join(", ")
21
+ name and subdivs and !subdivs.empty? and name = l10n("#{name}, #{subdivs}", @lang, @script)
22
+ location = a&.at(ns('./affiliation/organization/address/formattedAddress'))&.text
23
+ m << (!name.nil? && !location.nil? ? l10n("#{name}, #{location}", @lang, @script) :
23
24
  (name || location || ''))
24
25
  m
25
26
  end
@@ -25,13 +25,13 @@ module IsoDoc
25
25
  hash_translate(b, @i18n.get["substage_dict"], "./status/substage")
26
26
  end
27
27
 
28
- def hash_translate(bibdata, hash, xpath)
28
+ def hash_translate(bibdata, hash, xpath, lang = @lang)
29
29
  x = bibdata.at(ns(xpath)) or return
30
30
  x["language"] = ""
31
31
  hash.is_a? Hash or return
32
32
  hash[x.text] or return
33
33
  x.next = x.dup
34
- x.next["language"] = @lang
34
+ x.next["language"] = lang
35
35
  x.next.children = hash[x.text]
36
36
  end
37
37
 
@@ -40,7 +40,7 @@ module IsoDoc
40
40
  end
41
41
 
42
42
  def i18n_safe(k)
43
- k.gsub(/\s|\./, "_")
43
+ k.to_s.gsub(/\s|\./, "_")
44
44
  end
45
45
 
46
46
  def i8n_name(h, pref)