coradoc-mirror 0.1.1
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 +7 -0
- data/lib/coradoc/mirror/core_model_to_mirror.rb +181 -0
- data/lib/coradoc/mirror/handler_registry.rb +105 -0
- data/lib/coradoc/mirror/handlers/admonition.rb +29 -0
- data/lib/coradoc/mirror/handlers/bibliography.rb +43 -0
- data/lib/coradoc/mirror/handlers/blockquote.rb +19 -0
- data/lib/coradoc/mirror/handlers/code_block.rb +69 -0
- data/lib/coradoc/mirror/handlers/comment.rb +14 -0
- data/lib/coradoc/mirror/handlers/definition_list.rb +69 -0
- data/lib/coradoc/mirror/handlers/example.rb +19 -0
- data/lib/coradoc/mirror/handlers/footnote.rb +18 -0
- data/lib/coradoc/mirror/handlers/frontmatter.rb +71 -0
- data/lib/coradoc/mirror/handlers/generic_block.rb +24 -0
- data/lib/coradoc/mirror/handlers/horizontal_rule.rb +14 -0
- data/lib/coradoc/mirror/handlers/image.rb +58 -0
- data/lib/coradoc/mirror/handlers/inline.rb +213 -0
- data/lib/coradoc/mirror/handlers/list.rb +80 -0
- data/lib/coradoc/mirror/handlers/open_block.rb +16 -0
- data/lib/coradoc/mirror/handlers/paragraph.rb +16 -0
- data/lib/coradoc/mirror/handlers/reviewer.rb +14 -0
- data/lib/coradoc/mirror/handlers/sidebar.rb +19 -0
- data/lib/coradoc/mirror/handlers/structural.rb +84 -0
- data/lib/coradoc/mirror/handlers/table.rb +82 -0
- data/lib/coradoc/mirror/handlers/toc.rb +48 -0
- data/lib/coradoc/mirror/handlers/verse.rb +22 -0
- data/lib/coradoc/mirror/handlers.rb +38 -0
- data/lib/coradoc/mirror/mark.rb +181 -0
- data/lib/coradoc/mirror/mark_reverse_builder.rb +142 -0
- data/lib/coradoc/mirror/mirror_json_format.rb +42 -0
- data/lib/coradoc/mirror/mirror_to_core_model.rb +73 -0
- data/lib/coradoc/mirror/mirror_yaml_format.rb +41 -0
- data/lib/coradoc/mirror/node.rb +856 -0
- data/lib/coradoc/mirror/output.rb +62 -0
- data/lib/coradoc/mirror/partitioner.rb +62 -0
- data/lib/coradoc/mirror/reverse_builder.rb +600 -0
- data/lib/coradoc/mirror/transformer.rb +41 -0
- data/lib/coradoc/mirror/version.rb +7 -0
- data/lib/coradoc/mirror.rb +161 -0
- data/lib/coradoc-mirror.rb +14 -0
- metadata +140 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Mirror
|
|
5
|
+
module Handlers
|
|
6
|
+
# Image handler.
|
|
7
|
+
#
|
|
8
|
+
# Two emission shapes:
|
|
9
|
+
# - Ruby legacy (default): bare `image` node, title/caption in attrs.
|
|
10
|
+
# - JS @metanorma/mirror (`partition_structural: true`): when the
|
|
11
|
+
# source image has a title, wrap it in a `figure` node with the
|
|
12
|
+
# image plus a `caption` child, matching the JS schema.
|
|
13
|
+
module Image
|
|
14
|
+
def self.call(element, context:)
|
|
15
|
+
image_node = build_image_node(element)
|
|
16
|
+
|
|
17
|
+
return image_node unless context.partition_structural
|
|
18
|
+
return image_node unless caption_text?(element)
|
|
19
|
+
|
|
20
|
+
Node::Figure.new(
|
|
21
|
+
attrs: Node::Figure::Attrs.new(id: element.id, title: caption_value(element)),
|
|
22
|
+
content: [image_node, Node::Caption.new(content: caption_text_nodes(element, context))]
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class << self
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def build_image_node(element)
|
|
30
|
+
Node::Image.new(
|
|
31
|
+
attrs: Node::Image::Attrs.new(
|
|
32
|
+
src: element.src,
|
|
33
|
+
alt: element.alt,
|
|
34
|
+
title: element.title,
|
|
35
|
+
caption: element.caption,
|
|
36
|
+
width: element.width,
|
|
37
|
+
height: element.height,
|
|
38
|
+
inline: element.inline || nil
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def caption_text?(element)
|
|
44
|
+
!caption_value(element).to_s.empty?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def caption_value(element)
|
|
48
|
+
element.caption || element.title
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def caption_text_nodes(element, context)
|
|
52
|
+
[context.text_node(caption_value(element).to_s)]
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Mirror
|
|
5
|
+
module Handlers
|
|
6
|
+
module Inline
|
|
7
|
+
# Classification of inline handlers.
|
|
8
|
+
SIMPLE_MARK_TYPES = {
|
|
9
|
+
CoreModel::BoldElement => Mark::Bold,
|
|
10
|
+
CoreModel::ItalicElement => Mark::Italic,
|
|
11
|
+
CoreModel::MonospaceElement => Mark::Monospace,
|
|
12
|
+
CoreModel::UnderlineElement => Mark::Underline,
|
|
13
|
+
CoreModel::StrikethroughElement => Mark::Strikethrough,
|
|
14
|
+
CoreModel::SubscriptElement => Mark::Subscript,
|
|
15
|
+
CoreModel::SuperscriptElement => Mark::Superscript,
|
|
16
|
+
CoreModel::HighlightElement => Mark::Highlight,
|
|
17
|
+
CoreModel::TermElement => Mark::Bold
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
def self.process(element, context:)
|
|
21
|
+
return [] unless element
|
|
22
|
+
|
|
23
|
+
children = inline_children_for(element)
|
|
24
|
+
|
|
25
|
+
children.flat_map do |child|
|
|
26
|
+
process_child(child, context)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.process_child(child, context)
|
|
31
|
+
case child
|
|
32
|
+
when CoreModel::TextContent
|
|
33
|
+
return [] if child.text.nil? || child.text.empty?
|
|
34
|
+
|
|
35
|
+
[context.text_node(child.text)]
|
|
36
|
+
when CoreModel::InlineElement
|
|
37
|
+
[dispatch_inline(child, context)].compact
|
|
38
|
+
when CoreModel::FootnoteReference
|
|
39
|
+
[context.resolve_footnote_reference(child)]
|
|
40
|
+
when CoreModel::Block, CoreModel::StructuralElement
|
|
41
|
+
result = context.registry.handle(child, context: context)
|
|
42
|
+
return [] unless result
|
|
43
|
+
|
|
44
|
+
value, concat = result
|
|
45
|
+
return [] unless value
|
|
46
|
+
|
|
47
|
+
if concat
|
|
48
|
+
Array(value)
|
|
49
|
+
else
|
|
50
|
+
[value].compact
|
|
51
|
+
end
|
|
52
|
+
when CoreModel::Image
|
|
53
|
+
[Handlers::Image.call(child, context: context)]
|
|
54
|
+
else
|
|
55
|
+
[]
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.call(element, context:)
|
|
60
|
+
dispatch_inline(element, context)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.text_content(element, context:)
|
|
64
|
+
return nil if element.text.nil? || element.text.empty?
|
|
65
|
+
|
|
66
|
+
context.text_node(element.text)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
class << self
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def inline_children_for(element)
|
|
73
|
+
if element.is_a?(CoreModel::InlineElement) ||
|
|
74
|
+
element.is_a?(CoreModel::Block) ||
|
|
75
|
+
element.is_a?(CoreModel::TableCell) ||
|
|
76
|
+
element.is_a?(CoreModel::StructuralElement)
|
|
77
|
+
children = element.children
|
|
78
|
+
return children if children && !children.empty?
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
if element.is_a?(CoreModel::InlineElement) ||
|
|
82
|
+
element.is_a?(CoreModel::Block)
|
|
83
|
+
content = element.content
|
|
84
|
+
return [CoreModel::TextContent.new(text: content.to_s)] if content && !content.to_s.empty?
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
[]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def dispatch_inline(element, context)
|
|
91
|
+
mark_class = SIMPLE_MARK_TYPES[element.class]
|
|
92
|
+
return build_simple_mark(element, context, mark_class) if mark_class
|
|
93
|
+
|
|
94
|
+
case element
|
|
95
|
+
when CoreModel::LinkElement
|
|
96
|
+
build_link_mark(element, context)
|
|
97
|
+
when CoreModel::CrossReferenceElement
|
|
98
|
+
build_xref_mark(element, context)
|
|
99
|
+
when CoreModel::StemElement
|
|
100
|
+
build_stem_mark(element, context)
|
|
101
|
+
when CoreModel::SpanElement
|
|
102
|
+
build_span_mark(element, context)
|
|
103
|
+
when CoreModel::FootnoteElement
|
|
104
|
+
build_footnote_node(element, context)
|
|
105
|
+
when CoreModel::HardLineBreakElement, CoreModel::LineBreakElement
|
|
106
|
+
Node::SoftBreak.new
|
|
107
|
+
when CoreModel::TextElement
|
|
108
|
+
build_text_only(element, context)
|
|
109
|
+
when CoreModel::InlineElement
|
|
110
|
+
handle_generic_inline(element, context)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def handle_generic_inline(element, context)
|
|
115
|
+
text = element.content.to_s
|
|
116
|
+
return nil if text.empty?
|
|
117
|
+
|
|
118
|
+
context.text_node(text)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def build_simple_mark(element, context, mark_class)
|
|
122
|
+
text = extract_inline_text(element)
|
|
123
|
+
return nil if text.empty?
|
|
124
|
+
|
|
125
|
+
context.text_node(text, marks: [mark_class.new])
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def build_link_mark(element, context)
|
|
129
|
+
text = extract_inline_text(element)
|
|
130
|
+
text = element.target.to_s if text.empty? && element.target
|
|
131
|
+
return nil if text.empty?
|
|
132
|
+
|
|
133
|
+
context.text_node(text, marks: [
|
|
134
|
+
Mark::Link.new(attrs: Mark::Link::Attrs.new(href: element.target))
|
|
135
|
+
])
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def build_xref_mark(element, context)
|
|
139
|
+
text = extract_inline_text(element)
|
|
140
|
+
target = element.target
|
|
141
|
+
|
|
142
|
+
display_text = text.empty? ? (target || '') : text
|
|
143
|
+
return nil if display_text.empty?
|
|
144
|
+
|
|
145
|
+
context.text_node(display_text, marks: [Mark::CrossReference.new(
|
|
146
|
+
attrs: Mark::CrossReference::Attrs.new(
|
|
147
|
+
target: target,
|
|
148
|
+
resolved: text.empty? ? nil : text
|
|
149
|
+
)
|
|
150
|
+
)])
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def build_stem_mark(element, context)
|
|
154
|
+
text = extract_inline_text(element)
|
|
155
|
+
return nil if text.empty?
|
|
156
|
+
|
|
157
|
+
context.text_node(text, marks: [
|
|
158
|
+
Mark::Stem.new(attrs: Mark::Stem::Attrs.new(stem_type: element.stem_type))
|
|
159
|
+
])
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def build_span_mark(element, context)
|
|
163
|
+
text = extract_inline_text(element)
|
|
164
|
+
return nil if text.empty?
|
|
165
|
+
|
|
166
|
+
role = element.attr('role')
|
|
167
|
+
context.text_node(text, marks: [
|
|
168
|
+
Mark::Span.new(attrs: Mark::Span::Attrs.new(role: role))
|
|
169
|
+
])
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def build_footnote_node(element, context)
|
|
173
|
+
footnote = nil
|
|
174
|
+
if element.is_a?(CoreModel::InlineElement) && element.content
|
|
175
|
+
fn_id = element.attr('id')
|
|
176
|
+
footnote = CoreModel::Footnote.new(
|
|
177
|
+
id: fn_id,
|
|
178
|
+
content: element.content.to_s
|
|
179
|
+
)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
context.register_footnote(footnote)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def build_text_only(element, context)
|
|
186
|
+
text = extract_inline_text(element)
|
|
187
|
+
return nil if text.empty?
|
|
188
|
+
|
|
189
|
+
context.text_node(text)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def extract_inline_text(element)
|
|
193
|
+
return element.content.to_s if element.content && !element.content.to_s.empty?
|
|
194
|
+
|
|
195
|
+
return element.nested_elements.map { |nested| extract_inline_text(nested) }.join if element.is_a?(CoreModel::InlineElement) && element.nested_elements
|
|
196
|
+
|
|
197
|
+
if (element.is_a?(CoreModel::InlineElement) || element.is_a?(CoreModel::Block)) && element.children && !element.children.empty?
|
|
198
|
+
return element.children.map do |child|
|
|
199
|
+
case child
|
|
200
|
+
when CoreModel::TextContent then child.text.to_s
|
|
201
|
+
when CoreModel::InlineElement then extract_inline_text(child)
|
|
202
|
+
else ''
|
|
203
|
+
end
|
|
204
|
+
end.join
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
''
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Mirror
|
|
5
|
+
module Handlers
|
|
6
|
+
module List
|
|
7
|
+
def self.call(element, context:)
|
|
8
|
+
items = Array(element.items).filter_map do |item|
|
|
9
|
+
list_item(item, context: context)
|
|
10
|
+
end
|
|
11
|
+
return nil if items.empty?
|
|
12
|
+
|
|
13
|
+
node_class = ordered?(element) ? Node::OrderedList : Node::BulletList
|
|
14
|
+
node_class.new(
|
|
15
|
+
attrs: node_class::Attrs.new(
|
|
16
|
+
id: element.id,
|
|
17
|
+
start: element.is_a?(CoreModel::ListBlock) ? element.start : nil
|
|
18
|
+
),
|
|
19
|
+
content: items
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def list_item(item, context:)
|
|
27
|
+
content = build_item_content(item, context)
|
|
28
|
+
return nil if content.empty?
|
|
29
|
+
|
|
30
|
+
Node::ListItem.new(
|
|
31
|
+
attrs: Node::ListItem::Attrs.new(id: item.id),
|
|
32
|
+
content: content
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def build_item_content(item, context)
|
|
37
|
+
content = []
|
|
38
|
+
|
|
39
|
+
has_children = item.is_a?(CoreModel::ListItem) &&
|
|
40
|
+
item.children && !item.children.empty?
|
|
41
|
+
|
|
42
|
+
if has_children
|
|
43
|
+
item.children.each do |child|
|
|
44
|
+
node = dispatch_child(child, context)
|
|
45
|
+
content << node if node
|
|
46
|
+
end
|
|
47
|
+
elsif item.content && !item.content.to_s.empty?
|
|
48
|
+
content << context.text_node(item.content.to_s)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
if item.is_a?(CoreModel::ListItem) && item.nested_list
|
|
52
|
+
nested = Handlers::List.call(item.nested_list, context: context)
|
|
53
|
+
content << nested if nested
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
content
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def dispatch_child(child, context)
|
|
60
|
+
case child
|
|
61
|
+
when CoreModel::TextContent
|
|
62
|
+
return nil if child.text.nil? || child.text.empty?
|
|
63
|
+
|
|
64
|
+
context.text_node(child.text)
|
|
65
|
+
when CoreModel::InlineElement
|
|
66
|
+
Handlers::Inline.call(child, context: context)
|
|
67
|
+
when CoreModel::Block, CoreModel::StructuralElement
|
|
68
|
+
result = context.registry.handle(child, context: context)
|
|
69
|
+
result&.first
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def ordered?(element)
|
|
74
|
+
element.marker_type == 'ordered'
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Mirror
|
|
5
|
+
module Handlers
|
|
6
|
+
module OpenBlock
|
|
7
|
+
def self.call(element, context:)
|
|
8
|
+
content = context.extract_content(element)
|
|
9
|
+
return nil if content.empty?
|
|
10
|
+
|
|
11
|
+
Node::OpenBlock.new(content: content)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Mirror
|
|
5
|
+
module Handlers
|
|
6
|
+
module Paragraph
|
|
7
|
+
def self.call(element, context:)
|
|
8
|
+
content = context.process_inline_content(element)
|
|
9
|
+
return nil if content.empty?
|
|
10
|
+
|
|
11
|
+
Node::Paragraph.new(content: content)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Mirror
|
|
5
|
+
module Handlers
|
|
6
|
+
module Sidebar
|
|
7
|
+
def self.call(element, context:)
|
|
8
|
+
content = context.extract_content(element)
|
|
9
|
+
return nil if content.empty?
|
|
10
|
+
|
|
11
|
+
Node::Sidebar.new(
|
|
12
|
+
attrs: Node::Sidebar::Attrs.new(id: element.id, title: element.title),
|
|
13
|
+
content: content
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Mirror
|
|
5
|
+
module Handlers
|
|
6
|
+
module Structural
|
|
7
|
+
# Top-level document handler. Stays flat; structural partitioning
|
|
8
|
+
# (preface/sections/bibliography) is opted into via the
|
|
9
|
+
# partition_structural: kwarg on CoreModelToMirror#call, which
|
|
10
|
+
# delegates to Mirror::Partitioner.
|
|
11
|
+
def self.document(element, context:)
|
|
12
|
+
content = context.extract_content(element)
|
|
13
|
+
Node::Document.new(
|
|
14
|
+
attrs: Node::Document::Attrs.new(title: element.title, id: element.id),
|
|
15
|
+
content: content
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Map CoreModel section style/title hints to JS SECTION_TYPES.
|
|
20
|
+
# When coradoc propagates AsciiDoc style attributes ([appendix],
|
|
21
|
+
# [bibliography], etc.) into SectionElement.attributes, this table
|
|
22
|
+
# is used to pick the right JS section type. Default fallback is
|
|
23
|
+
# `clause` (the JS generic section type).
|
|
24
|
+
SECTION_STYLE_TO_JS_TYPE = {
|
|
25
|
+
'appendix' => 'annex',
|
|
26
|
+
'annex' => 'annex',
|
|
27
|
+
'bibliography' => 'references',
|
|
28
|
+
'references' => 'references',
|
|
29
|
+
'abstract' => 'abstract',
|
|
30
|
+
'foreword' => 'foreword',
|
|
31
|
+
'introduction' => 'introduction',
|
|
32
|
+
'acknowledgements' => 'acknowledgements',
|
|
33
|
+
'terms' => 'terms',
|
|
34
|
+
'definitions' => 'definitions'
|
|
35
|
+
}.freeze
|
|
36
|
+
|
|
37
|
+
def self.section(element, context:)
|
|
38
|
+
content = context.extract_content(element)
|
|
39
|
+
type = context.partition_structural ? section_type_for(element) : 'section'
|
|
40
|
+
|
|
41
|
+
Node::Section.new(
|
|
42
|
+
type: type,
|
|
43
|
+
attrs: Node::Section::Attrs.new(
|
|
44
|
+
id: element.id,
|
|
45
|
+
title: element.title,
|
|
46
|
+
level: element.heading_level
|
|
47
|
+
),
|
|
48
|
+
content: content
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.section_type_for(element)
|
|
53
|
+
style = section_style(element)
|
|
54
|
+
SECTION_STYLE_TO_JS_TYPE[style] || 'clause'
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Reads `style` then `role` from SectionElement#attributes via the
|
|
58
|
+
# Metadata#[] accessor — no intermediate hash allocation per call.
|
|
59
|
+
def self.section_style(element)
|
|
60
|
+
attrs = element.attributes
|
|
61
|
+
return nil unless attrs.is_a?(Coradoc::CoreModel::Metadata)
|
|
62
|
+
|
|
63
|
+
attrs['style'] || attrs['role']
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.preamble(element, context:)
|
|
67
|
+
content = context.extract_content(element)
|
|
68
|
+
Node::Preamble.new(content: content)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.header(element, context:)
|
|
72
|
+
content = context.extract_content(element)
|
|
73
|
+
Node::Header.new(
|
|
74
|
+
attrs: Node::Header::Attrs.new(
|
|
75
|
+
title: element.title,
|
|
76
|
+
level: element.heading_level
|
|
77
|
+
),
|
|
78
|
+
content: content
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Mirror
|
|
5
|
+
module Handlers
|
|
6
|
+
module Table
|
|
7
|
+
def self.call(element, context:)
|
|
8
|
+
rows = Array(element.rows)
|
|
9
|
+
return nil if rows.empty?
|
|
10
|
+
|
|
11
|
+
head_rows, body_rows = partition_rows(rows)
|
|
12
|
+
|
|
13
|
+
content = []
|
|
14
|
+
content << build_table_head(head_rows, context) unless head_rows.empty?
|
|
15
|
+
content << build_table_body(body_rows, context) unless body_rows.empty?
|
|
16
|
+
|
|
17
|
+
return nil if content.empty?
|
|
18
|
+
|
|
19
|
+
Node::Table.new(
|
|
20
|
+
attrs: Node::Table::Attrs.new(
|
|
21
|
+
id: element.id,
|
|
22
|
+
title: element.title,
|
|
23
|
+
width: element.width
|
|
24
|
+
),
|
|
25
|
+
content: content
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class << self
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def partition_rows(rows)
|
|
33
|
+
head = rows.select { |r| r.is_a?(CoreModel::TableRow) && r.header }
|
|
34
|
+
body = rows.reject { |r| r.is_a?(CoreModel::TableRow) && r.header }
|
|
35
|
+
[head, body]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def build_table_head(rows, context)
|
|
39
|
+
content = rows.map { |r| build_table_row(r, context) }
|
|
40
|
+
Node::TableHead.new(content: content)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def build_table_body(rows, context)
|
|
44
|
+
content = rows.map { |r| build_table_row(r, context) }
|
|
45
|
+
Node::TableBody.new(content: content)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def build_table_row(row, context)
|
|
49
|
+
cells = Array(row.cells).map { |c| build_table_cell(c, context) }
|
|
50
|
+
Node::TableRow.new(content: cells)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def build_table_cell(cell, context)
|
|
54
|
+
content = build_cell_content(cell, context)
|
|
55
|
+
Node::TableCell.new(
|
|
56
|
+
attrs: Node::TableCell::Attrs.new(
|
|
57
|
+
colspan: cell.colspan,
|
|
58
|
+
rowspan: cell.rowspan,
|
|
59
|
+
alignment: cell.alignment,
|
|
60
|
+
header: cell.header || nil
|
|
61
|
+
),
|
|
62
|
+
content: content
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def build_cell_content(cell, context)
|
|
67
|
+
if cell.is_a?(CoreModel::TableCell) && cell.children && !cell.children.empty?
|
|
68
|
+
return cell.children.flat_map do |child|
|
|
69
|
+
Handlers::Inline.process_child(child, context)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
text = cell.content
|
|
74
|
+
return [] if text.nil? || text.to_s.empty?
|
|
75
|
+
|
|
76
|
+
[context.text_node(text.to_s)]
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Mirror
|
|
5
|
+
module Handlers
|
|
6
|
+
module Toc
|
|
7
|
+
def self.call(element, context:)
|
|
8
|
+
entries = if element.is_a?(CoreModel::Toc) && element.entries
|
|
9
|
+
Array(element.entries).filter_map do |entry|
|
|
10
|
+
build_entry(entry, context)
|
|
11
|
+
end
|
|
12
|
+
else
|
|
13
|
+
[]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
Node::Toc.new(
|
|
17
|
+
attrs: Node::Toc::Attrs.new(title: element.title),
|
|
18
|
+
content: entries
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class << self
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def build_entry(entry, context)
|
|
26
|
+
children = if entry.is_a?(CoreModel::TocEntry) && entry.children
|
|
27
|
+
entry.children.filter_map { |c| build_entry(c, context) }
|
|
28
|
+
else
|
|
29
|
+
[]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
content = [context.text_node(entry.title.to_s)] unless children.any?
|
|
33
|
+
content ||= children
|
|
34
|
+
|
|
35
|
+
Node::TocEntry.new(
|
|
36
|
+
attrs: Node::TocEntry::Attrs.new(
|
|
37
|
+
id: entry.is_a?(CoreModel::TocEntry) ? entry.id : nil,
|
|
38
|
+
title: entry.is_a?(CoreModel::TocEntry) ? entry.title : nil,
|
|
39
|
+
level: entry.is_a?(CoreModel::TocEntry) ? entry.level : nil
|
|
40
|
+
),
|
|
41
|
+
content: content
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module Mirror
|
|
5
|
+
module Handlers
|
|
6
|
+
module Verse
|
|
7
|
+
def self.call(element, context:)
|
|
8
|
+
text = if element.content && !element.content.to_s.empty?
|
|
9
|
+
element.flat_text || element.content.to_s
|
|
10
|
+
else
|
|
11
|
+
''
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
Node::Verse.new(
|
|
15
|
+
attrs: Node::Verse::Attrs.new(attribution: element.attribution),
|
|
16
|
+
content: [context.text_node(text)]
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|