metanorma-standoc 1.9.4 → 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake.yml +1 -1
  3. data/.rubocop.yml +1 -1
  4. data/lib/asciidoctor/standoc/cleanup_inline.rb +117 -77
  5. data/lib/asciidoctor/standoc/cleanup_ref.rb +7 -0
  6. data/lib/asciidoctor/standoc/cleanup_terms.rb +19 -18
  7. data/lib/asciidoctor/standoc/inline.rb +20 -17
  8. data/lib/asciidoctor/standoc/isodoc.rng +18 -1
  9. data/lib/asciidoctor/standoc/macros_plantuml.rb +19 -21
  10. data/lib/asciidoctor/standoc/macros_terms.rb +33 -23
  11. data/lib/asciidoctor/standoc/term_lookup_cleanup.rb +10 -12
  12. data/lib/asciidoctor/standoc/terms.rb +1 -1
  13. data/lib/asciidoctor/standoc/validate.rb +21 -8
  14. data/lib/metanorma/standoc/version.rb +1 -1
  15. data/metanorma-standoc.gemspec +2 -2
  16. data/spec/asciidoctor/blocks_spec.rb +6 -6
  17. data/spec/asciidoctor/cleanup_spec.rb +37 -6
  18. data/spec/asciidoctor/isobib_cache_spec.rb +4 -6
  19. data/spec/asciidoctor/lists_spec.rb +147 -135
  20. data/spec/asciidoctor/macros_spec.rb +505 -181
  21. data/spec/asciidoctor/refs_spec.rb +12 -12
  22. data/spec/asciidoctor/validate_spec.rb +66 -20
  23. data/spec/vcr_cassettes/dated_iso_ref_joint_iso_iec.yml +42 -42
  24. data/spec/vcr_cassettes/isobib_get_123.yml +12 -12
  25. data/spec/vcr_cassettes/isobib_get_123_1.yml +26 -26
  26. data/spec/vcr_cassettes/isobib_get_123_1_fr.yml +35 -35
  27. data/spec/vcr_cassettes/isobib_get_123_2001.yml +13 -13
  28. data/spec/vcr_cassettes/isobib_get_124.yml +12 -12
  29. data/spec/vcr_cassettes/rfcbib_get_rfc8341.yml +13 -13
  30. data/spec/vcr_cassettes/separates_iev_citations_by_top_level_clause.yml +51 -61
  31. metadata +5 -5
@@ -955,7 +955,24 @@
955
955
  <define name="concept">
956
956
  <element name="concept">
957
957
  <optional>
958
- <attribute name="term"/>
958
+ <element name="refterm">
959
+ <zeroOrMore>
960
+ <choice>
961
+ <ref name="PureTextElement"/>
962
+ <ref name="stem"/>
963
+ </choice>
964
+ </zeroOrMore>
965
+ </element>
966
+ </optional>
967
+ <optional>
968
+ <element name="renderterm">
969
+ <zeroOrMore>
970
+ <choice>
971
+ <ref name="PureTextElement"/>
972
+ <ref name="stem"/>
973
+ </choice>
974
+ </zeroOrMore>
975
+ </element>
959
976
  </optional>
960
977
  <choice>
961
978
  <ref name="eref"/>
@@ -4,8 +4,8 @@ module Asciidoctor
4
4
  # https://stackoverflow.com/questions/2108727/which-in-ruby-checking-if-program-exists-in-path-from-ruby
5
5
  def self.plantuml_installed?
6
6
  cmd = "plantuml"
7
- exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
8
- ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
7
+ exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""]
8
+ ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
9
9
  exts.each do |ext|
10
10
  exe = File.join(path, "#{cmd}#{ext}")
11
11
  return exe if File.executable?(exe) && !File.directory?(exe)
@@ -15,7 +15,7 @@ module Asciidoctor
15
15
  nil
16
16
  end
17
17
 
18
- def self.run umlfile, outfile
18
+ def self.run(umlfile, outfile)
19
19
  system "plantuml #{umlfile.path}" or (warn $? and return false)
20
20
  i = 0
21
21
  until !Gem.win_platform? || File.exist?(outfile) || i == 15
@@ -29,9 +29,9 @@ module Asciidoctor
29
29
  # sleep need for windows because dot works in separate process and
30
30
  # plantuml process may finish earlier then dot, as result png file
31
31
  # maybe not created yet after plantuml finish
32
- def self.generate_file parent, reader
32
+ def self.generate_file(parent, reader)
33
33
  localdir = Metanorma::Utils::localdir(parent.document)
34
- imagesdir = parent.document.attr('imagesdir')
34
+ imagesdir = parent.document.attr("imagesdir")
35
35
  umlfile, outfile = save_plantuml parent, reader, localdir
36
36
  run(umlfile, outfile) or raise "No image output from PlantUML (#{umlfile}, #{outfile})!"
37
37
  umlfile.unlink
@@ -40,7 +40,7 @@ module Asciidoctor
40
40
  File.writable?(localdir) or raise "Destination path #{path} not writable for PlantUML!"
41
41
  path.mkpath
42
42
  File.writable?(path) or raise "Destination path #{path} not writable for PlantUML!"
43
- #File.exist?(path) or raise "Destination path #{path} already exists for PlantUML!"
43
+ # File.exist?(path) or raise "Destination path #{path} already exists for PlantUML!"
44
44
 
45
45
  # Warning: metanorma/metanorma-standoc#187
46
46
  # Windows Ruby 2.4 will crash if a Tempfile is "mv"ed.
@@ -51,21 +51,21 @@ module Asciidoctor
51
51
  imagesdir ? filename : File.join(path, filename)
52
52
  end
53
53
 
54
- def self.save_plantuml parent, reader, localdir
54
+ def self.save_plantuml(_parent, reader, _localdir)
55
55
  src = reader.source
56
56
  reader.lines.first.sub(/\s+$/, "").match /^@startuml($| )/ or
57
57
  src = "@startuml\n#{src}\n@enduml\n"
58
58
  /^@startuml (?<fn>[^\n]+)\n/ =~ src
59
- Tempfile.open(["plantuml", ".pml"], :encoding => "utf-8") do |f|
59
+ Tempfile.open(["plantuml", ".pml"], encoding: "utf-8") do |f|
60
60
  f.write(src)
61
61
  [f, File.join(File.dirname(f.path),
62
62
  (fn || File.basename(f.path, ".pml")) + ".png")]
63
63
  end
64
64
  end
65
65
 
66
- def self.generate_attrs attrs
67
- through_attrs = %w(id align float title role width height alt).
68
- inject({}) do |memo, key|
66
+ def self.generate_attrs(attrs)
67
+ %w(id align float title role width height alt)
68
+ .inject({}) do |memo, key|
69
69
  memo[key] = attrs[key] if attrs.has_key? key
70
70
  memo
71
71
  end
@@ -81,19 +81,17 @@ module Asciidoctor
81
81
  def abort(parent, reader, attrs, msg)
82
82
  warn msg
83
83
  attrs["language"] = "plantuml"
84
- create_listing_block parent, reader.source, attrs.reject { |k, v| k == 1 }
84
+ create_listing_block parent, reader.source, attrs.reject { |k, _v| k == 1 }
85
85
  end
86
86
 
87
87
  def process(parent, reader, attrs)
88
- begin
89
- PlantUMLBlockMacroBackend.plantuml_installed?
90
- filename = PlantUMLBlockMacroBackend.generate_file(parent, reader)
91
- through_attrs = PlantUMLBlockMacroBackend.generate_attrs attrs
92
- through_attrs["target"] = filename
93
- create_image_block parent, through_attrs
94
- rescue StandardError => e
95
- abort(parent, reader, attrs, e.message)
96
- end
88
+ PlantUMLBlockMacroBackend.plantuml_installed?
89
+ filename = PlantUMLBlockMacroBackend.generate_file(parent, reader)
90
+ through_attrs = PlantUMLBlockMacroBackend.generate_attrs attrs
91
+ through_attrs["target"] = filename
92
+ create_image_block parent, through_attrs
93
+ rescue StandardError => e
94
+ abort(parent, reader, attrs, e.message)
97
95
  end
98
96
  end
99
97
  end
@@ -1,3 +1,5 @@
1
+ require "csv"
2
+
1
3
  module Asciidoctor
2
4
  module Standoc
3
5
  class AltTermInlineMacro < Asciidoctor::Extensions::InlineMacroProcessor
@@ -36,46 +38,54 @@ module Asciidoctor
36
38
  end
37
39
  end
38
40
 
39
- # Macro to transform `term[X,Y]` into em, termxref xml
40
41
  class TermRefInlineMacro < Asciidoctor::Extensions::InlineMacroProcessor
41
42
  use_dsl
42
43
  named :term
43
- name_positional_attributes 'name', 'termxref'
44
+ name_positional_attributes "name", "termxref"
44
45
  using_format :short
45
46
 
46
47
  def process(_parent, _target, attrs)
47
- termref = attrs['termxref'] || attrs['name']
48
- "<em>#{attrs['name']}</em> (<termxref>#{termref}</termxref>)"
48
+ termref = attrs["termxref"] || attrs["name"]
49
+ "<concept><termxref>#{attrs['name']}</termxref>"\
50
+ "<renderterm>#{termref}</renderterm><xrefrender/></concept>"
49
51
  end
50
52
  end
51
53
 
54
+ # Possibilities:
55
+ # {{<<id>>, term}}
56
+ # {{<<id>>, term, text}}
57
+ # {{<<termbase:id>>, term}}
58
+ # {{<<termbase:id>>, term, text}}
59
+ # {{term}} equivalent to term:[term]
60
+ # {{term, text}} equivalent to term:[term, text]
61
+ # text may optionally be followed by crossreference-rendering, options=""
52
62
  class ConceptInlineMacro < Asciidoctor::Extensions::InlineMacroProcessor
53
63
  use_dsl
54
64
  named :concept
55
- name_positional_attributes "id", "word", "term"
56
- # match %r{concept:(?<target>[^\[]*)\[(?<content>|.*?[^\\])\]$}
57
65
  match /\{\{(?<content>|.*?[^\\])\}\}/
58
66
  using_format :short
59
67
 
60
- # deal with locality attrs and their disruption of positional attrs
61
- def preprocess_attrs(attrs)
62
- attrs.delete("term") if attrs["term"] && !attrs["word"]
63
- attrs.delete(3) if attrs[3] == attrs["term"]
64
- a = attrs.keys.reject { |k| k.is_a?(String) || [1, 2].include?(k) }
65
- attrs["word"] ||= attrs[a[0]] if !a.empty?
66
- attrs["term"] ||= attrs[a[1]] if a.length > 1
67
- attrs
68
+ def preprocess_attrs(target)
69
+ m = /^(?<id>&lt;&lt;.+?&gt;&gt;)?(?<rest>.*)$/.match(target)
70
+ ret = { id: m[:id]&.sub(/^&lt;&lt;/, "")&.sub(/&gt;&gt;$/, "") }
71
+ m2 = /^(?<rest>.*)(?<opt>,option=.+)?$/.match(m[:rest].sub(/^,/, ""))
72
+ ret[:opt] = m2[:opt]&.sub(/^,option=/, "")
73
+ attrs = CSV.parse_line(m2[:rest]) || []
74
+ ret.merge(term: attrs[0], word: attrs[1] || attrs[0],
75
+ xrefrender: attrs[2])
68
76
  end
69
77
 
70
- def process(parent, _target, attr)
71
- attr = preprocess_attrs(attr)
72
- localities = attr.keys.reject { |k| %w(id word term).include? k }.
73
- reject { |k| k.is_a? Numeric }.
74
- map { |k| "#{k}=#{attr[k]}" }.join(",")
75
- text = [localities, attr["word"]].reject{ |k| k.nil? || k.empty? }.
76
- join(",")
77
- out = Asciidoctor::Inline.new(parent, :quoted, text).convert
78
- %{<concept key="#{attr['id']}" term="#{attr['term']}">#{out}</concept>}
78
+ def process(parent, target, _attrs)
79
+ attrs = preprocess_attrs(target)
80
+ termout = Asciidoctor::Inline.new(parent, :quoted, attrs[:term]).convert
81
+ wordout = Asciidoctor::Inline.new(parent, :quoted, attrs[:word]).convert
82
+ xrefout = Asciidoctor::Inline.new(parent, :quoted,
83
+ attrs[:xrefrender]).convert
84
+ attrs[:id] and return "<concept key='#{attrs[:id]}'><refterm>"\
85
+ "#{termout}</refterm><renderterm>#{wordout}</renderterm>"\
86
+ "<xrefrender>#{xrefout}</xrefrender></concept>"
87
+ "<concept><termxref>#{termout}</termxref><renderterm>#{wordout}"\
88
+ "</renderterm><xrefrender>#{xrefout}</xrefrender></concept>"
79
89
  end
80
90
  end
81
91
  end
@@ -3,8 +3,6 @@
3
3
  module Asciidoctor
4
4
  module Standoc
5
5
  # Intelligent term lookup xml modifier
6
- # Lookup all `term` and `calause` tags and replace `termxref` tags with
7
- # `xref`:target tag
8
6
  class TermLookupCleanup
9
7
  AUTOMATIC_GENERATED_ID_REGEXP = /\A_/.freeze
10
8
  EXISTING_TERM_REGEXP = /\Aterm-/.freeze
@@ -41,7 +39,9 @@ module Asciidoctor
41
39
  remove_missing_ref(node, target)
42
40
  next
43
41
  end
44
- modify_ref_node(node, target)
42
+ x = node.at("../xrefrender")
43
+ modify_ref_node(x, target)
44
+ node.name = "refterm"
45
45
  end
46
46
  end
47
47
 
@@ -49,20 +49,18 @@ module Asciidoctor
49
49
  log.add("AsciiDoc Input", node,
50
50
  %(Error: Term reference in `term[#{target}]` missing: \
51
51
  "#{target}" is not defined in document))
52
- term_name_node = node.previous.previous
53
- term_name_node.remove
54
- term_name_node.name = "strong"
55
- term_name_node.children.first.content =
56
- %(term "#{term_name_node.text}" not resolved)
57
- node.add_previous_sibling(term_name_node)
58
- node.remove
52
+ node.name = "strong"
53
+ node.at("../xrefrender").remove
54
+ display = node&.at("../renderterm")&.remove&.children
55
+ display = [] if display.nil? || display&.to_xml == node.text
56
+ d = display.empty? ? "" : ", display <tt>#{display.to_xml}</tt>"
57
+ node.children = "term <tt>#{node.text}</tt>#{d} "\
58
+ "not resolved via ID <tt>#{target}</tt>"
59
59
  end
60
60
 
61
61
  def modify_ref_node(node, target)
62
62
  node.name = "xref"
63
63
  node["target"] = termlookup[target]
64
- node.children.remove
65
- node.remove_attribute("defaultref")
66
64
  end
67
65
 
68
66
  def replace_automatic_generated_ids_terms
@@ -105,7 +105,7 @@ module Asciidoctor
105
105
  end
106
106
 
107
107
  TERM_REFERENCE_RE_STR = <<~REGEXP.freeze
108
- ^(?<xref><(xref|concept)[^>]+>([^<]*</(xref|concept)>)?)
108
+ ^(?<xref><(xref|concept)[^>]+>(.*?</(xref|concept)>)?)
109
109
  (,\s(?<text>.*))?
110
110
  $
111
111
  REGEXP
@@ -19,10 +19,10 @@ module Asciidoctor
19
19
  end
20
20
 
21
21
  def iev_validate(xmldoc)
22
+ @iev = init_iev or return
22
23
  xmldoc.xpath("//term").each do |t|
23
24
  /^IEC 60050-/.match(t&.at("./termsource/origin/@citeas")&.text) &&
24
25
  loc = t.xpath(SOURCELOCALITY)&.text or next
25
- @iev = init_iev or return
26
26
  iev = @iev.fetch(loc, xmldoc&.at("//language")&.text || "en") or next
27
27
  pref = t.xpath("./preferred").inject([]) do |m, x|
28
28
  m << x&.text&.downcase
@@ -38,6 +38,7 @@ module Asciidoctor
38
38
  norm_ref_validate(doc)
39
39
  repeat_id_validate(doc.root)
40
40
  iev_validate(doc.root)
41
+ concept_validate(doc)
41
42
  end
42
43
 
43
44
  def norm_ref_validate(doc)
@@ -54,6 +55,20 @@ module Asciidoctor
54
55
  clean_abort("Numeric reference in normative references", doc.to_xml)
55
56
  end
56
57
 
58
+ def concept_validate(doc)
59
+ found = false
60
+ doc.xpath("//concept/xref").each do |x|
61
+ next if doc.at("//term[@id = '#{x['target']}']")
62
+
63
+ ref = x&.at("../refterm")&.text
64
+ @log.add("Anchors", x, "Concept #{ref} is pointing to "\
65
+ "#{x['target']}, which is not a term")
66
+ found = true
67
+ end
68
+ found and
69
+ clean_abort("Concept not cross-referencing term", doc.to_xml)
70
+ end
71
+
57
72
  def repeat_id_validate1(ids, elem)
58
73
  if ids[elem["id"]]
59
74
  @log.add("Anchors", elem, "Anchor #{elem['id']} has already been "\
@@ -78,13 +93,11 @@ module Asciidoctor
78
93
 
79
94
  def schema_validate(doc, schema)
80
95
  Tempfile.open(["tmp", ".xml"], encoding: "UTF-8") do |f|
81
- begin
82
- schema_validate1(f, doc, schema)
83
- rescue Jing::Error => e
84
- clean_abort("Jing failed with error: #{e}", doc.to_xml)
85
- ensure
86
- f.close!
87
- end
96
+ schema_validate1(f, doc, schema)
97
+ rescue Jing::Error => e
98
+ clean_abort("Jing failed with error: #{e}", doc.to_xml)
99
+ ensure
100
+ f.close!
88
101
  end
89
102
  end
90
103
 
@@ -19,6 +19,6 @@ module Metanorma
19
19
  end
20
20
 
21
21
  module Standoc
22
- VERSION = "1.9.4".freeze
22
+ VERSION = "1.10.0".freeze
23
23
  end
24
24
  end
@@ -24,11 +24,11 @@ Gem::Specification.new do |spec|
24
24
  spec.require_paths = ["lib"]
25
25
  spec.files = `git ls-files`.split("\n")
26
26
  spec.test_files = `git ls-files -- {spec}/*`.split("\n")
27
- spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
27
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
28
28
 
29
29
  spec.add_dependency "asciidoctor", "~> 2.0.0"
30
30
  spec.add_dependency "iev", "~> 0.2.1"
31
- spec.add_dependency "isodoc", "~> 1.6.2"
31
+ spec.add_dependency "isodoc", "~> 1.7.0"
32
32
  spec.add_dependency "metanorma-plugin-datastruct"
33
33
  spec.add_dependency "metanorma-plugin-lutaml"
34
34
  spec.add_dependency "ruby-jing"
@@ -1051,13 +1051,13 @@ RSpec.describe Asciidoctor::Standoc do
1051
1051
  Definition
1052
1052
 
1053
1053
  [.source]
1054
- {{IEV:xyz}}
1054
+ {{<<IEV:xyz>>}}
1055
1055
 
1056
1056
  [.source]
1057
- {{IEV:xyz,t1}}
1057
+ {{<<IEV:xyz>>,t1}}
1058
1058
 
1059
1059
  [.source]
1060
- {{IEV:xyz,t1,t2}}
1060
+ {{<<IEV:xyz>>,t1,t2}}
1061
1061
  INPUT
1062
1062
  output = <<~OUTPUT
1063
1063
  #{BLANK_HDR}
@@ -1094,12 +1094,12 @@ RSpec.describe Asciidoctor::Standoc do
1094
1094
  </termsource>
1095
1095
  <termsource status='identical'>
1096
1096
  <origin citeas=''>
1097
- <termref base='IEV' target='xyz'>t1</termref>
1097
+ <termref base='IEV' target='xyz'/>
1098
1098
  </origin>
1099
1099
  </termsource>
1100
1100
  <termsource status='identical'>
1101
1101
  <origin citeas=''>
1102
- <termref base='IEV' target='xyz'>t1</termref>
1102
+ <termref base='IEV' target='xyz'/>
1103
1103
  </origin>
1104
1104
  </termsource>
1105
1105
  </term>
@@ -1126,7 +1126,7 @@ RSpec.describe Asciidoctor::Standoc do
1126
1126
  Definition
1127
1127
 
1128
1128
  [.source]
1129
- {{IEV:xyz}}, with adjustments
1129
+ {{<<IEV:xyz>>}}, with adjustments
1130
1130
  INPUT
1131
1131
  output = <<~OUTPUT
1132
1132
  #{BLANK_HDR}
@@ -1618,11 +1618,42 @@ RSpec.describe Asciidoctor::Standoc do
1618
1618
  </standard-document>
1619
1619
  INPUT
1620
1620
  output = <<~OUTPUT
1621
- #{BLANK_HDR}
1622
- <sections>
1623
- <stem type="MathML"><math xmlns="http://www.w3.org/1998/Math/MathML"><mfrac><mn>1</mn><mi>r</mi></mfrac></math></stem>
1621
+ #{BLANK_HDR}
1622
+ <sections>
1623
+ <stem type="MathML"><math xmlns="http://www.w3.org/1998/Math/MathML"><mfrac><mn>1</mn><mi>r</mi></mfrac></math></stem>
1624
1624
  </sections>
1625
- </standard-document>
1625
+ </standard-document>
1626
+ OUTPUT
1627
+ expect(Asciidoctor::Standoc::Converter.new(nil, *OPTIONS)
1628
+ .cleanup(Nokogiri::XML(input)).to_xml)
1629
+ .to be_equivalent_to xmlpp(output)
1630
+ end
1631
+
1632
+ it "removes nested bibitem IDs" do
1633
+ input = <<~INPUT
1634
+ #{BLANK_HDR}
1635
+ <bibliography>
1636
+ <references normative="true"><title>Normative</title>
1637
+ <bibitem id="A">
1638
+ <relation type="includes">
1639
+ <bibitem id="B"/>
1640
+ </relation>
1641
+ </bibitem>
1642
+ </bibliography>
1643
+ </standard-document>
1644
+ INPUT
1645
+ output = <<~OUTPUT
1646
+ #{BLANK_HDR}
1647
+ <bibliography>
1648
+ <references normative="true"><title>Normative</title>
1649
+ <bibitem id="A">
1650
+ <relation type="includes">
1651
+ <bibitem id="B"/>
1652
+ </relation>
1653
+ </bibitem>
1654
+ </references>
1655
+ </bibliography>
1656
+ </standard-document>
1626
1657
  OUTPUT
1627
1658
  expect(Asciidoctor::Standoc::Converter.new(nil, *OPTIONS)
1628
1659
  .cleanup(Nokogiri::XML(input)).to_xml)
@@ -1879,7 +1910,7 @@ RSpec.describe Asciidoctor::Standoc do
1879
1910
  .to be_equivalent_to xmlpp(output)
1880
1911
  end
1881
1912
 
1882
- it "sorts symbols lists" do
1913
+ it "sorts symbols lists #1" do
1883
1914
  input = <<~INPUT
1884
1915
  #{ASCIIDOC_BLANK_HDR}
1885
1916
 
@@ -1943,7 +1974,7 @@ RSpec.describe Asciidoctor::Standoc do
1943
1974
  .to be_equivalent_to xmlpp(output)
1944
1975
  end
1945
1976
 
1946
- it "sorts symbols lists" do
1977
+ it "sorts symbols lists #2" do
1947
1978
  input = <<~INPUT
1948
1979
  #{ASCIIDOC_BLANK_HDR}
1949
1980