coradoc 2.0.24 → 2.0.25

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bcce76c634ae51a857397a301fc2638950cb25e1a97048589ee5510f8a7aee40
4
- data.tar.gz: 6cd3b391ae83e360ed282b40f778dad1c4290202b386b457b881e264ddca464e
3
+ metadata.gz: 89b8c2a91f2cc76dbd1aa2694eb7fa06b689c46e92fe8d240e2bad3206a39da3
4
+ data.tar.gz: 9645f7e85a7ac1f8ada41f535219b72238b63c503380c79642d04eb2fe1b6589
5
5
  SHA512:
6
- metadata.gz: f424dfb3267d2497dc46b9e764fab868083add0ef5cd82163aa89a35b884e75b81c7426a47d9c0b81abd15659dcc15b49f6f309b786848cba7c0aed42557cf3a
7
- data.tar.gz: 07fe25f772cf02b7673f25f4bb41e9abb65e25631898af071407eb68961bba9c2a74c19c7d20d85544c722a4ad07404c4846d389278fb989d2a186caab36bdbb
6
+ metadata.gz: fa10ea51a7a4ac5146d28cd7a681d5722998b7937ec05169c2376a80af592a65b8a430773e99cf874aca769c2a7dcc61b065ce3ae8e4da283685f77ae3bd22c3
7
+ data.tar.gz: a063ca46ab3f5a8f166044311b118fe82a96185d77f12de607e426ba5ad6ad330cbfb58ee5323dae5a3a7f6b6fa90f12f4fc422730878b219058f318f72eda6a
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module CoreModel
5
+ # Walks a CoreModel tree and resolves InlineElement nodes whose
6
+ # format_type is 'attribute_reference' against a Metadata store.
7
+ #
8
+ # AsciiDoc `{foo}` references round-trip as InlineElement with
9
+ # format_type: 'attribute_reference' and target: 'foo'. After the
10
+ # document is parsed and its attributes are known, this visitor
11
+ # rewrites those nodes in place to TextContent carrying the value.
12
+ # Unresolved references are left untouched so they survive
13
+ # serialisation back to the source format.
14
+ #
15
+ # The visitor never mutates its input tree; it returns a new tree
16
+ # sharing unchanged subtrees. InlineElement#with_content already
17
+ # produces non-mutating copies for inline content; the same pattern
18
+ # is used here at the block level via `replace_children`.
19
+ class AttributeReferenceResolver
20
+ attr_reader :attributes
21
+
22
+ def self.call(root, attributes)
23
+ new(attributes).visit(root)
24
+ end
25
+
26
+ def initialize(attributes)
27
+ @attributes = attributes || Metadata.new
28
+ end
29
+
30
+ def visit(node)
31
+ return node.map { |child| visit(child) } if node.is_a?(Array)
32
+
33
+ return node unless node.is_a?(Base)
34
+
35
+ visit_typed(node)
36
+ end
37
+
38
+ private
39
+
40
+ def visit_typed(node)
41
+ case node
42
+ when DocumentElement, SectionElement
43
+ rebuild_children(node)
44
+ when Table
45
+ rebuild_collection(node, :rows)
46
+ when TableRow
47
+ rebuild_collection(node, :cells)
48
+ when Block, ParagraphBlock, ListItem, TableCell, Term,
49
+ QuoteBlock, ExampleBlock, SidebarBlock, OpenBlock,
50
+ AnnotationBlock, ReviewerBlock, VerseBlock, LiteralBlock,
51
+ PassBlock, StemBlock, ListingBlock, SourceBlock,
52
+ CommentBlock, FrontmatterBlock
53
+ rebuild_block(node)
54
+ when DefinitionList, DefinitionItem
55
+ rebuild_children(node)
56
+ when InlineElement, RawInlineElement
57
+ resolve_inline(node)
58
+ else
59
+ node
60
+ end
61
+ end
62
+
63
+ def rebuild_children(node)
64
+ return node unless node.respond_to?(:children)
65
+
66
+ original = node.children
67
+ return node if original.nil?
68
+
69
+ updated = original.is_a?(Array) ? original.map { |c| visit(c) } : visit(original)
70
+ return node if updated == original
71
+
72
+ node.dup.tap { |copy| copy.children = updated }
73
+ end
74
+
75
+ # For nodes whose child collection lives on a non-`children`
76
+ # attribute (Table#rows, TableRow#cells). Walks the collection,
77
+ # rebuilds the parent only when at least one child changed.
78
+ def rebuild_collection(node, attr_name)
79
+ original = node.public_send(attr_name)
80
+ return node if original.nil?
81
+
82
+ updated = original.is_a?(Array) ?
83
+ original.map { |c| visit(c) } :
84
+ visit(original)
85
+ return node if updated == original
86
+
87
+ node.dup.tap { |copy| copy.public_send("#{attr_name}=", updated) }
88
+ end
89
+
90
+ def rebuild_block(node)
91
+ return node unless node.respond_to?(:children)
92
+
93
+ original_children = node.children
94
+ return node if original_children.nil?
95
+
96
+ updated_children = original_children.is_a?(Array) ?
97
+ original_children.map { |c| visit(c) } :
98
+ visit(original_children)
99
+
100
+ if inline_resolvable?(node)
101
+ original_content = node.content
102
+ updated_content = resolve_content(original_content)
103
+ else
104
+ updated_content = node.content
105
+ end
106
+
107
+ return node if updated_children == original_children && updated_content == node.content
108
+
109
+ node.dup.tap do |copy|
110
+ copy.children = updated_children
111
+ copy.content = updated_content if inline_resolvable?(node)
112
+ end
113
+ end
114
+
115
+ def inline_resolvable?(node)
116
+ node.respond_to?(:content) && node.content.is_a?(String)
117
+ end
118
+
119
+ # Resolve attribute references inside a flat content string.
120
+ # Only replaces `{name}` when +name+ exists in +attributes+;
121
+ # unknown references are preserved verbatim for round-trip.
122
+ def resolve_content(content)
123
+ return content unless content.is_a?(String)
124
+
125
+ content.gsub(/\{([a-zA-Z0-9_-]+)\}/) do |match|
126
+ name = Regexp.last_match(1)
127
+ attributes.key?(name) ? attributes[name].to_s : match
128
+ end
129
+ end
130
+
131
+ def resolve_inline(node)
132
+ return node unless node.is_a?(InlineElement)
133
+ return node unless node.resolve_format_type == 'attribute_reference'
134
+
135
+ target = node.target
136
+ return node if target.nil? || target.empty?
137
+ return node unless attributes.key?(target)
138
+
139
+ TextContent.new(text: attributes[target].to_s)
140
+ end
141
+ end
142
+ end
143
+ end
@@ -81,6 +81,7 @@ module Coradoc
81
81
  autoload :CommentLine, "#{__dir__}/core_model/comment_line"
82
82
  autoload :HorizontalRuleBlock, "#{__dir__}/core_model/horizontal_rule_block"
83
83
  autoload :IdGenerator, "#{__dir__}/core_model/id_generator"
84
+ autoload :AttributeReferenceResolver, "#{__dir__}/core_model/attribute_reference_resolver"
84
85
  autoload :Include, "#{__dir__}/core_model/include"
85
86
  autoload :IncludeOptions, "#{__dir__}/core_model/include_options"
86
87
  autoload :IncludeLevelOffset, "#{__dir__}/core_model/include_level_offset"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Coradoc
4
- VERSION = '2.0.24'
4
+ VERSION = '2.0.25'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: coradoc
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.24
4
+ version: 2.0.25
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -56,6 +56,7 @@ files:
56
56
  - lib/coradoc/coradoc.rb
57
57
  - lib/coradoc/core_model.rb
58
58
  - lib/coradoc/core_model/annotation_block.rb
59
+ - lib/coradoc/core_model/attribute_reference_resolver.rb
59
60
  - lib/coradoc/core_model/base.rb
60
61
  - lib/coradoc/core_model/bibliography.rb
61
62
  - lib/coradoc/core_model/bibliography_entry.rb