metanorma-standoc 1.9.4 → 1.10.0

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 (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