isodoc 0.5.8 → 0.5.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.tb.yml +20 -10
  3. data/lib/isodoc/blocks.rb +13 -13
  4. data/lib/isodoc/cleanup.rb +19 -3
  5. data/lib/isodoc/comments.rb +1 -1
  6. data/lib/isodoc/convert.rb +2 -0
  7. data/lib/isodoc/footnotes.rb +4 -2
  8. data/lib/isodoc/html.rb +28 -10
  9. data/lib/isodoc/i18n-en.yaml +9 -0
  10. data/lib/isodoc/i18n-fr.yaml +12 -3
  11. data/lib/isodoc/i18n-zh-Hans.yaml +10 -1
  12. data/lib/isodoc/i18n.rb +9 -2
  13. data/lib/isodoc/inline.rb +5 -6
  14. data/lib/isodoc/iso2wordhtml.rb +9 -6
  15. data/lib/isodoc/lists.rb +7 -5
  16. data/lib/isodoc/metadata.rb +27 -10
  17. data/lib/isodoc/references.rb +12 -6
  18. data/lib/isodoc/section.rb +17 -11
  19. data/lib/isodoc/table.rb +2 -4
  20. data/lib/isodoc/terms.rb +3 -3
  21. data/lib/isodoc/utils.rb +15 -14
  22. data/lib/isodoc/version.rb +1 -1
  23. data/lib/isodoc/wordconvert/comments.rb +1 -1
  24. data/lib/isodoc/wordconvert/wordconvertmodule.rb +51 -12
  25. data/lib/isodoc/xref_gen.rb +30 -26
  26. data/lib/isodoc/xref_sect_gen.rb +31 -13
  27. data/spec/assets/scripts.html +1 -0
  28. data/spec/isodoc/blocks_spec.rb +50 -49
  29. data/spec/isodoc/cleanup_spec.rb +6 -6
  30. data/spec/isodoc/footnotes_spec.rb +10 -2
  31. data/spec/isodoc/i18n_spec.rb +86 -54
  32. data/spec/isodoc/inline_spec.rb +22 -18
  33. data/spec/isodoc/lists_spec.rb +25 -11
  34. data/spec/isodoc/metadata_spec.rb +4 -3
  35. data/spec/isodoc/postproc_spec.rb +108 -42
  36. data/spec/isodoc/ref_spec.rb +9 -7
  37. data/spec/isodoc/section_spec.rb +162 -141
  38. data/spec/isodoc/table_spec.rb +18 -16
  39. data/spec/isodoc/terms_spec.rb +9 -9
  40. data/spec/isodoc/xref_spec.rb +153 -122
  41. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 57ca9a13e94e7c5861ee7694b1c543a304d35e2c
4
- data.tar.gz: 4d1ca9fe799cc66bb3d6e1422585168e09da7c4a
3
+ metadata.gz: 90089f0b90f1392ef99b4d676db3215397bb70fc
4
+ data.tar.gz: 5d82f8fb83a29877f878716df97dc6499a998834
5
5
  SHA512:
6
- metadata.gz: a382164e2ff256407dc864caa9638235970b84bff249291d690588ac880cd832cefd089505336603f4e920dd89053096fc96c5b9d2233fb4f023ac6bf9465d63
7
- data.tar.gz: 54d6a2064d4b1fe1a72a8997ca842b1f9a45094a7f3778a9858af9c560757f72d078d38045cee41782d79043a743ecd6be8dbc2d31b657ab07292d2ac6efa090
6
+ metadata.gz: eb49c04b480775ba01401325d448b9d03181c93f6f456525755ac7a704b8eae5b19a88afd3ac736ff234bb5fc4e7e3a2c0affa0555128efc1e89071eec14f10b
7
+ data.tar.gz: 382f620801c7e6a447f00de270f2d372a81abbc6f7bb878760e7a000527e2c085389ae694483a426f76d3b75fdb48d93d1d40abcd0601d9db68dee5bc6fd3e95
@@ -352,8 +352,18 @@ Style/TrailingCommaInArguments:
352
352
  - no_comma
353
353
  Enabled: true
354
354
 
355
- Style/TrailingCommaInLiteral:
356
- Description: 'Checks for trailing comma in array and hash literals.'
355
+ Style/TrailingCommaInArrayLiteral:
356
+ Description: 'Checks for trailing comma in array literals.'
357
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas'
358
+ EnforcedStyleForMultiline: comma
359
+ SupportedStylesForMultiline:
360
+ - comma
361
+ - consistent_comma
362
+ - no_comma
363
+ Enabled: true
364
+
365
+ Style/TrailingCommaInHashLiteral:
366
+ Description: 'Checks for trailing comma in hash literals.'
357
367
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas'
358
368
  EnforcedStyleForMultiline: comma
359
369
  SupportedStylesForMultiline:
@@ -398,6 +408,13 @@ Layout/AlignParameters:
398
408
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-double-indent'
399
409
  Enabled: false
400
410
 
411
+ Layout/ConditionPosition:
412
+ Description: >-
413
+ Checks for condition placed in a confusing position relative to
414
+ the keyword.
415
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#same-line-condition'
416
+ Enabled: false
417
+
401
418
  Layout/DotPosition:
402
419
  Description: 'Checks the position of the dot in multi-line method calls.'
403
420
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains'
@@ -450,13 +467,6 @@ Lint/CircularArgumentReference:
450
467
  Description: "Don't refer to the keyword argument in the default value."
451
468
  Enabled: false
452
469
 
453
- Lint/ConditionPosition:
454
- Description: >-
455
- Checks for condition placed in a confusing position relative to
456
- the keyword.
457
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#same-line-condition'
458
- Enabled: false
459
-
460
470
  Lint/DeprecatedClassMethods:
461
471
  Description: 'Check for deprecated class method calls.'
462
472
  Enabled: false
@@ -523,7 +533,7 @@ Lint/UnderscorePrefixedVariableName:
523
533
  Description: 'Do not use prefix `_` for a variable that is used.'
524
534
  Enabled: false
525
535
 
526
- Lint/UnneededDisable:
536
+ Lint/UnneededCopDisableDirective:
527
537
  Description: >-
528
538
  Checks for rubocop:disable comments that can be removed.
529
539
  Note: this cop is not disabled when disabling all cops.
@@ -10,7 +10,9 @@ module IsoDoc
10
10
 
11
11
  def note_p_parse(node, div)
12
12
  div.p **{ class: "Note" } do |p|
13
- p << note_label(node)
13
+ p.span **{ class: "note_label" } do |s|
14
+ s << note_label(node)
15
+ end
14
16
  insert_tab(p, 1)
15
17
  node.first_element_child.children.each { |n| parse(n, p) }
16
18
  end
@@ -19,7 +21,9 @@ module IsoDoc
19
21
 
20
22
  def note_parse1(node, div)
21
23
  div.p **{ class: "Note" } do |p|
22
- p << note_label(node)
24
+ p.span **{ class: "note_label" } do |s|
25
+ s << note_label(node)
26
+ end
23
27
  insert_tab(p, 1)
24
28
  end
25
29
  node.children.each { |n| parse(n, div) }
@@ -39,10 +43,8 @@ module IsoDoc
39
43
 
40
44
  def figure_name_parse(node, div, name)
41
45
  div.p **{ class: "FigureTitle", align: "center" } do |p|
42
- p.b do |b|
43
- b << l10n("#{@figure_lbl} #{get_anchors[node['id']][:label]}")
44
- b << "&nbsp;&mdash; #{name.text}" if name
45
- end
46
+ p << l10n("#{@figure_lbl} #{get_anchors[node['id']][:label]}")
47
+ p << "&nbsp;&mdash; #{name.text}" if name
46
48
  end
47
49
  end
48
50
 
@@ -72,7 +74,7 @@ module IsoDoc
72
74
  end
73
75
 
74
76
  EXAMPLE_TBL_ATTR =
75
- { width: "110pt", valign: "top",
77
+ { width: "110pt", valign: "top", class: "example_label",
76
78
  style: "width:82.8pt;padding:.75pt .75pt .75pt .75pt" }.freeze
77
79
 
78
80
  # used if we are boxing examples
@@ -91,7 +93,7 @@ module IsoDoc
91
93
  tr.td **EXAMPLE_TBL_ATTR do |td|
92
94
  td << example_label(node)
93
95
  end
94
- tr.td **{ valign: "top" } do |td|
96
+ tr.td **{ valign: "top", class: "example" } do |td|
95
97
  node.children.each { |n| parse(n, td) }
96
98
  end
97
99
  end
@@ -100,9 +102,7 @@ module IsoDoc
100
102
 
101
103
  def sourcecode_name_parse(_node, div, name)
102
104
  div.p **{ class: "FigureTitle", align: "center" } do |p|
103
- p.b do |b|
104
- b << name.text
105
- end
105
+ p << name.text
106
106
  end
107
107
  end
108
108
 
@@ -124,14 +124,14 @@ module IsoDoc
124
124
  out.span **{ class: "zzMoveToFollowing" } do |s|
125
125
  s << "&lt;#{node.at(ns("//callout[@target='#{node['id']}']")).text}&gt; "
126
126
  end
127
- node.children.each { |n| parse(n, out) }
127
+ node.children.each { |n| parse(n, out) }
128
128
  @annotation = false
129
129
  end
130
130
 
131
131
  def admonition_parse(node, out)
132
132
  name = node["type"]
133
133
  out.div **{ class: "Admonition" } do |t|
134
- t.title { |b| b << name.upcase } if name
134
+ t.title { |b| b << @admonition[name].upcase } if name
135
135
  node.children.each do |n|
136
136
  parse(n, t)
137
137
  end
@@ -12,6 +12,7 @@ module IsoDoc
12
12
  figure_cleanup(docxml)
13
13
  table_cleanup(docxml)
14
14
  symbols_cleanup(docxml)
15
+ example_cleanup(docxml)
15
16
  admonition_cleanup(docxml)
16
17
  end
17
18
 
@@ -24,6 +25,13 @@ module IsoDoc
24
25
  docxml
25
26
  end
26
27
 
28
+ def example_cleanup(docxml)
29
+ docxml.xpath("//table[@class = 'example']//p[not(@class)]").each do |p|
30
+ p["class"] = "example"
31
+ end
32
+ docxml
33
+ end
34
+
27
35
  def figure_get_or_make_dl(t)
28
36
  dl = t.at(".//dl")
29
37
  if dl.nil?
@@ -93,10 +101,15 @@ module IsoDoc
93
101
  t.xpath(".//aside").each do |a|
94
102
  merge_fnref_into_fn_text(a)
95
103
  a.name = "div"
96
- a["class"] = "Note"
104
+ a["class"] = "TableFootnote"
97
105
  t << a.remove
98
106
  end
99
107
  end
108
+ # preempt html2doc putting MsoNormal there
109
+ docxml.xpath("//p[not(self::*[@class])]"\
110
+ "[ancestor::*[@class = 'TableFootnote']]").each do |p|
111
+ p["class"] = "TableFootnote"
112
+ end
100
113
  end
101
114
 
102
115
  def remove_bottom_border(td)
@@ -127,10 +140,13 @@ module IsoDoc
127
140
  end
128
141
 
129
142
  def table_note_cleanup(docxml)
130
- docxml.xpath("//table[div[@class = 'Note']]").each do |t|
143
+ docxml.xpath("//table[div[@class = 'Note' or "\
144
+ "@class = 'TableFootnote']]").each do |t|
131
145
  tfoot = table_get_or_make_tfoot(t)
132
146
  insert_here = new_fullcolspan_row(t, tfoot)
133
- t.xpath("div[@class = 'Note']").each { |d| d.parent = insert_here }
147
+ t.xpath("div[@class = 'Note' or @class = 'TableFootnote']").each do |d|
148
+ d.parent = insert_here
149
+ end
134
150
  end
135
151
  # preempt html2doc putting MsoNormal there
136
152
  docxml.xpath("//p[not(self::*[@class])]"\
@@ -32,7 +32,7 @@ module IsoDoc
32
32
  out.span(**comment_link_attrs(fn, node)) do |s1|
33
33
  s1.span **{ lang: "EN-GB", style: "font-size:9.0pt" } do |s2|
34
34
  s2.a **{ style: "mso-comment-reference:SMC_#{fn};"\
35
- "mso-comment-date:#{node['date']}" }
35
+ "mso-comment-date:#{node['date'].gsub(/[-:Z]/, '')}" }
36
36
  s2.span **{ style: "mso-special-character:comment",
37
37
  target: fn } # do |s|
38
38
  end
@@ -39,6 +39,7 @@ module IsoDoc
39
39
  @wordcoverpage = options[:wordcoverpage]
40
40
  @htmlintropage = options[:htmlintropage]
41
41
  @wordintropage = options[:wordintropage]
42
+ @scripts = options[:scripts]
42
43
  @i18nyaml = options[:i18nyaml]
43
44
  @ulstyle = options[:ulstyle]
44
45
  @olstyle = options[:olstyle]
@@ -61,6 +62,7 @@ module IsoDoc
61
62
  @closemathdelim = "`"
62
63
  @lang = "en"
63
64
  @script = "Latn"
65
+ @files_to_delete = []
64
66
  end
65
67
 
66
68
  def convert1(docxml, filename, dir)
@@ -34,7 +34,7 @@ module IsoDoc
34
34
 
35
35
  def make_generic_footnote_text(node, fnid)
36
36
  noko do |xml|
37
- xml.aside **{ id: "ftn#{fnid}" } do |div|
37
+ xml.aside **{ id: "ftn#{fnid}", class: "footnote" } do |div|
38
38
  node.children.each { |n| parse(n, div) }
39
39
  end
40
40
  end.join("\n")
@@ -53,7 +53,9 @@ module IsoDoc
53
53
  # do not output footnote text if we have already seen it for this table
54
54
  return if @seen_footnote.include?(tid + fn)
55
55
  @in_footnote = true
56
- out.aside { |a| a << make_table_footnote_text(node, tid + fn, fn) }
56
+ out.aside **{ class: "footnote" } do |a|
57
+ a << make_table_footnote_text(node, tid + fn, fn)
58
+ end
57
59
  @in_footnote = false
58
60
  @seen_footnote << (tid + fn)
59
61
  end
@@ -3,10 +3,13 @@ module IsoDoc
3
3
  def postprocess(result, filename, dir)
4
4
  result = from_xhtml(cleanup(to_xhtml(result)))
5
5
  toHTML(result, filename)
6
+ @files_to_delete.each { |f| system "rm #{f}" }
6
7
  end
7
8
 
8
9
  def toHTML(result, filename)
9
- result = from_xhtml(html_cleanup(to_xhtml(result)))
10
+ result = from_xhtml(html_cleanup(to_xhtml(result))).
11
+ gsub(%r{<script><!\[CDATA\[}, "<script>").
12
+ gsub(%r{\]\]></script>}, "</script>")
10
13
  result = populate_template(result, :html)
11
14
  File.open("#{filename}.html", "w") do |f|
12
15
  f.write(result)
@@ -14,8 +17,8 @@ module IsoDoc
14
17
  end
15
18
 
16
19
  def html_cleanup(x)
17
- footnote_backlinks(
18
- html_toc(move_images(html_footnote_filter(html_preface(htmlstyle(x)))))
20
+ footnote_backlinks(html_toc(
21
+ term_header(move_images(html_footnote_filter(html_preface(htmlstyle(x))))))
19
22
  )
20
23
  end
21
24
 
@@ -36,24 +39,38 @@ module IsoDoc
36
39
  MATHJAX.gsub("OPEN", open).gsub("CLOSE", close)
37
40
  end
38
41
 
42
+ def term_header(docxml)
43
+ %w(h1 h2 h3 h4 h5 h6 h7 h8).each do |h|
44
+ docxml.xpath("//p[@class = 'TermNum'][../#{h}]").each do |p|
45
+ p.name = "h#{h[1].to_i + 1}"
46
+ end
47
+ end
48
+ docxml
49
+ end
50
+
39
51
  def html_preface(docxml)
40
52
  html_cover(docxml) if @htmlcoverpage
41
53
  html_intro(docxml) if @htmlintropage
42
- docxml.at("//*[local-name() = 'body']") << mathjax(@openmathdelim,
43
- @closemathdelim)
54
+ docxml.at("//body") << mathjax(@openmathdelim, @closemathdelim)
55
+ if @scripts
56
+ scripts = File.read(@scripts, encoding: "UTF-8")
57
+ docxml.at("//body").add_child scripts #scripts.to_xml(encoding: "US-ASCII")
58
+ end
44
59
  docxml
45
60
  end
46
61
 
47
62
  def html_cover(docxml)
48
- cover = Nokogiri::HTML(File.read(@htmlcoverpage, encoding: "UTF-8"))
63
+ cover = File.read(@htmlcoverpage, encoding: "UTF-8")
64
+ coverxml = to_xhtml_fragment(cover)
49
65
  d = docxml.at('//div[@class="WordSection1"]')
50
- d.children.first.add_previous_sibling cover.to_xml(encoding: "US-ASCII")
66
+ d.children.first.add_previous_sibling coverxml.to_xml(encoding: "US-ASCII")
51
67
  end
52
68
 
53
69
  def html_intro(docxml)
54
- cover = Nokogiri::HTML(File.read(@htmlintropage, encoding: "UTF-8"))
70
+ intro = File.read(@htmlintropage, encoding: "UTF-8")
71
+ introxml = to_xhtml_fragment(intro)
55
72
  d = docxml.at('//div[@class="WordSection2"]')
56
- d.children.first.add_previous_sibling cover.to_xml(encoding: "US-ASCII")
73
+ d.children.first.add_previous_sibling introxml.to_xml(encoding: "US-ASCII")
57
74
  end
58
75
 
59
76
  def htmlstylesheet
@@ -140,7 +157,8 @@ module IsoDoc
140
157
  ret = ""
141
158
  prevname = ""
142
159
  docxml.xpath("//h1 | //h2").each do |h|
143
- ret = html_toc1(h, ret, prevname) unless h["class"] == "toc-contents"
160
+ next if ["toc-contents", "TermNum"].include? h["class"]
161
+ ret = html_toc1(h, ret, prevname)
144
162
  prevname = h.name
145
163
  end
146
164
  ret += "<ul>" if prevname == "h2"
@@ -10,6 +10,7 @@ term_def_boilerplate: |
10
10
  </p> </li> </ul>
11
11
  scope: Scope
12
12
  symbols: Symbols and Abbreviated Terms
13
+ table_of_contents: Table of Contents
13
14
  introduction: Introduction
14
15
  foreword: Foreword
15
16
  termsdef: Terms and Definitions
@@ -18,6 +19,7 @@ normref: Normative References
18
19
  bibliography: Bibliography
19
20
  clause: Clause
20
21
  annex: Annex
22
+ appendix: Appendix
21
23
  no_terms_boilerplate: |
22
24
  <p>No terms and definitions are listed in this document.</p>
23
25
  internal_terms_boilerplate: |
@@ -57,6 +59,13 @@ deprecated: DEPRECATED
57
59
  source: SOURCE
58
60
  and: and
59
61
  all_parts: All Parts
62
+ admonition: {
63
+ danger: Danger,
64
+ warning: Warning,
65
+ caution: Caution,
66
+ important: Important,
67
+ safety precautions: Safety Precautions,
68
+ }
60
69
  locality: {
61
70
  section: Section,
62
71
  clause: Clause,
@@ -10,14 +10,16 @@ term_def_boilerplate: |
10
10
  </p> </li> </ul>
11
11
  scope: Domaine d'application
12
12
  symbols: Symboles et termes abrégés
13
+ table_of_contents: Sommaire
13
14
  introduction: Introduction
14
15
  foreword: Avant-propos
15
16
  termsdef: Terms et définitions
16
17
  termsdefsymbols: Terms, définitions, symboles et termes abrégés
17
18
  normref: Références normatives
18
- bibliography: then Bibliographie
19
- clause: then Article
19
+ bibliography: Bibliographie
20
+ clause: Article
20
21
  annex: Annexe
22
+ appendix: Appendice
21
23
  no_terms_boilerplate: |
22
24
  <p>Aucun terme n'est defini dans le présent document.</p>
23
25
  internal_terms_boilerplate: |
@@ -50,6 +52,13 @@ deprecated: DÉCONSEILLÉ
50
52
  source: SOURCE
51
53
  and: et
52
54
  all_parts: toutes les parties
55
+ admonition: {
56
+ danger: Danger,
57
+ warning: Avertissement,
58
+ caution: Attention,
59
+ important: Important,
60
+ safety precautions: Précautions de Sécurité,
61
+ }
53
62
  locality: {
54
63
  section: Section,
55
64
  clause: Article,
@@ -63,4 +72,4 @@ locality: {
63
72
  example: Exemple,
64
73
  note: Note,
65
74
  formula: Formule
66
- }
75
+ }
@@ -8,6 +8,7 @@ term_def_boilerplate: |
8
8
  </p> </li> </ul>
9
9
  scope: 范围
10
10
  symbols: 符号、代号和缩略语
11
+ table_of_contents: 目次
11
12
  introduction: 引言
12
13
  foreword: 前言
13
14
  termsdef: 术语和定义
@@ -15,7 +16,8 @@ termsdefsymbols_lbl: 术语、定义、符号、代号和缩略语
15
16
  normref: 规范性引用文件
16
17
  bibliography: 参考文献
17
18
  clause: 条
18
- annex: 附录
19
+ annex: 附件
20
+ appendix: 附录
19
21
  no_terms_boilerplate: |
20
22
  <p>本文件不提供术语和定义。</p>
21
23
  internal_terms_boilerplate: |
@@ -50,6 +52,13 @@ deprecated: 被取代
50
52
  source: 定义
51
53
  and: 和
52
54
  all_parts: 所有部分
55
+ admonition: {
56
+ danger: 危险,
57
+ warning: 警告,
58
+ caution: 注意,
59
+ important: 重要,
60
+ safety precautions: 安全须知,
61
+ }
53
62
  locality: {
54
63
  section: 条,
55
64
  clause: 条,
@@ -20,6 +20,7 @@ module IsoDoc
20
20
  @term_def_boilerplate = y["term_def_boilerplate"]
21
21
  @scope_lbl = y["scope"]
22
22
  @symbols_lbl = y["symbols"]
23
+ @table_of_contents_lbl = y["table_of_contents"]
23
24
  @introduction_lbl = y["introduction"]
24
25
  @foreword_lbl = y["foreword"]
25
26
  @termsdef_lbl = y["termsdef"]
@@ -28,6 +29,7 @@ module IsoDoc
28
29
  @bibliography_lbl = y["bibliography"]
29
30
  @clause_lbl = y["clause"]
30
31
  @annex_lbl = y["annex"]
32
+ @appendix_lbl = y["appendix"]
31
33
  @no_terms_boilerplate = y["no_terms_boilerplate"]
32
34
  @internal_terms_boilerplate = y["internal_terms_boilerplate"]
33
35
  @norm_with_refs_pref = y["norm_with_refs_pref"]
@@ -56,12 +58,16 @@ module IsoDoc
56
58
  @and_lbl = y["and"]
57
59
  @all_parts_lbl = y["all_parts"]
58
60
  @locality = y["locality"]
61
+ @admonition = y["admonition"]
62
+ @labels = y
63
+ @labels["language"] = @lang
64
+ @labels["script"] = @script
59
65
  end
60
66
 
61
67
  def eref_localities1_zh(type, from, to)
62
68
  ret = ", 第#{from.text}" if from
63
69
  ret += "&ndash;#{to}" if to
64
- ret += @locality[type]
70
+ ret += (@locality[type] || type.sub(/^locality:/, "").capitalize )
65
71
  ret
66
72
  end
67
73
 
@@ -69,7 +75,8 @@ module IsoDoc
69
75
  subsection = from&.text&.match?(/\./)
70
76
  return l10n(eref_localities1_zh(type, from, to)) if lang == "zh"
71
77
  ret = ","
72
- ret += " #{@locality[type]}" unless subsection && type == "clause"
78
+ loc = @locality[type] || type.sub(/^locality:/, "").capitalize
79
+ ret += " #{loc}" unless subsection && type == "clause"
73
80
  ret += " #{from.text}" if from
74
81
  ret += "&ndash;#{to.text}" if to
75
82
  l10n(ret)