coradoc-adoc 2.0.23 → 2.0.24
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 +4 -4
- data/lib/coradoc/asciidoc/model/base.rb +7 -0
- data/lib/coradoc/asciidoc/transform/element_transformers/admonition_styles.rb +6 -1
- data/lib/coradoc/asciidoc/transform/element_transformers/block_transformer.rb +39 -16
- data/lib/coradoc/asciidoc/transform/element_transformers/document_transformer.rb +2 -27
- data/lib/coradoc/asciidoc/transform/element_transformers/include_transformer.rb +2 -1
- data/lib/coradoc/asciidoc/transform/element_transformers/inline_transformer.rb +12 -6
- data/lib/coradoc/asciidoc/transform/element_transformers/list_transformer.rb +10 -4
- data/lib/coradoc/asciidoc/transform/element_transformers/other_transformer.rb +10 -5
- data/lib/coradoc/asciidoc/transform/element_transformers/table_transformer.rb +6 -3
- data/lib/coradoc/asciidoc/transform/from_core_model.rb +1 -28
- data/lib/coradoc/asciidoc/transform/inline_transform_visitor.rb +23 -5
- data/lib/coradoc/asciidoc/transform/to_core_model.rb +30 -6
- data/lib/coradoc/asciidoc/transformer/block_rules.rb +13 -3
- data/lib/coradoc/asciidoc/transformer/header_rules.rb +12 -3
- data/lib/coradoc/asciidoc/transformer/inline_rules.rb +63 -19
- data/lib/coradoc/asciidoc/transformer/list_rules.rb +20 -5
- data/lib/coradoc/asciidoc/transformer/misc_rules.rb +31 -12
- data/lib/coradoc/asciidoc/transformer/source_line_extractor.rb +67 -0
- data/lib/coradoc/asciidoc/transformer/structural_rules.rb +7 -3
- data/lib/coradoc/asciidoc/transformer/text_rules.rb +33 -9
- data/lib/coradoc/asciidoc/transformer.rb +6 -1
- data/lib/coradoc/asciidoc/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d135f788f7205a03a242f7d1e9242b84fdc76994f6eff372579db29c9c420d51
|
|
4
|
+
data.tar.gz: 6eafdb8eb6a101650e48e6a29d5e719f729d8c7888c506a5e67a33511640db2f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cfe57d959c7d69cf47aadb30526cd826ac85720d1f3f4426c4f6fc4d769c2618ad2c31cd713e3b86b4480f79fdf41d473457623c145746eb7fc25155c47b2dfc
|
|
7
|
+
data.tar.gz: 07dcf6099269006d16c34a21f5ca9457227fe9e7fcd597607716511ddecf7a3a1b7d794d5f32a29c6542a4b4f93ced0f61b37ae7e06553d775a0068ad4f7f363
|
|
@@ -35,6 +35,13 @@ module Coradoc
|
|
|
35
35
|
|
|
36
36
|
attribute :id, :string
|
|
37
37
|
|
|
38
|
+
# 1-indexed source line where this element begins, when known.
|
|
39
|
+
# Populated by the Parslet transformer from the matched Slice's
|
|
40
|
+
# line_and_column. nil for programmatically constructed models.
|
|
41
|
+
# Single source of truth for source-position propagation through
|
|
42
|
+
# AsciiDoc::Model → CoreModel (Issue 1, STATUS-2026-06-28).
|
|
43
|
+
attribute :source_line, :integer
|
|
44
|
+
|
|
38
45
|
# Element classification for spacing and serialization decisions.
|
|
39
46
|
# Subclasses override these to declare their level.
|
|
40
47
|
def block_level?
|
|
@@ -13,7 +13,12 @@ module Coradoc
|
|
|
13
13
|
# block-form transformer that needs to decide between an admonition
|
|
14
14
|
# and the block's native type.
|
|
15
15
|
module AdmonitionStyles
|
|
16
|
-
|
|
16
|
+
# Generic `[admonition]` (capitalized as ADMONITION by
|
|
17
|
+
# +canonicalize+) is the spec-defined generic admonition style.
|
|
18
|
+
# It is rarely used directly but exists in the AsciiDoc spec;
|
|
19
|
+
# treating it as a real admonition prevents it from collapsing
|
|
20
|
+
# to an ExampleBlock when applied to a delimited block.
|
|
21
|
+
BUILTIN = %w[note tip warning caution important editor todo admonition].freeze
|
|
17
22
|
|
|
18
23
|
@custom = []
|
|
19
24
|
|
|
@@ -8,10 +8,13 @@ module Coradoc
|
|
|
8
8
|
class << self
|
|
9
9
|
def transform_paragraph(para)
|
|
10
10
|
children = ToCoreModel.transform_inline_content(para.content)
|
|
11
|
+
source_lines = ToCoreModel.extract_source_lines(para.content)
|
|
11
12
|
|
|
12
13
|
Coradoc::CoreModel::ParagraphBlock.new(
|
|
13
14
|
id: para.id,
|
|
14
15
|
content: ToCoreModel.extract_text_content(para.content),
|
|
16
|
+
lines: source_lines,
|
|
17
|
+
source_line: para.source_line,
|
|
15
18
|
children: children
|
|
16
19
|
)
|
|
17
20
|
end
|
|
@@ -22,19 +25,15 @@ module Coradoc
|
|
|
22
25
|
# lines are preserved. Treating these as paragraphs would collapse
|
|
23
26
|
# whitespace and join consecutive lines into a single flowing text.
|
|
24
27
|
def transform_verbatim_block(block, klass, language_override: nil)
|
|
25
|
-
|
|
26
|
-
line.is_a?(Coradoc::AsciiDoc::Model::LineBreak) ||
|
|
27
|
-
line.is_a?(Coradoc::AsciiDoc::Model::Break::PageBreak)
|
|
28
|
-
end
|
|
29
|
-
content_lines = non_break_lines.map do |line|
|
|
30
|
-
ToCoreModel.extract_text_content(line)
|
|
31
|
-
end.join("\n")
|
|
28
|
+
source_lines = ToCoreModel.extract_source_lines(block.lines)
|
|
32
29
|
|
|
33
30
|
klass.new(
|
|
34
31
|
id: block.id,
|
|
35
32
|
title: ToCoreModel.extract_title_text(block.title),
|
|
36
|
-
content:
|
|
37
|
-
|
|
33
|
+
content: source_lines.join("\n"),
|
|
34
|
+
lines: source_lines,
|
|
35
|
+
language: language_override || ToCoreModel.extract_block_language(block),
|
|
36
|
+
source_line: block.source_line
|
|
38
37
|
)
|
|
39
38
|
end
|
|
40
39
|
|
|
@@ -74,7 +73,7 @@ module Coradoc
|
|
|
74
73
|
# AsciiDoc spec, every delimited block accepts both admonition
|
|
75
74
|
# labels and verbatim/stem casts in its attribute line. The cast
|
|
76
75
|
# is resolved by +block_cast_semantic+ in MECE order:
|
|
77
|
-
# admonition > stem > verbatim > fallback.
|
|
76
|
+
# admonition > stem > verbatim > typed-cast > fallback.
|
|
78
77
|
def transform_with_admonition_check(block, native_class)
|
|
79
78
|
semantic = block_cast_semantic(block)
|
|
80
79
|
case semantic
|
|
@@ -88,6 +87,9 @@ module Coradoc
|
|
|
88
87
|
transform_listing_from_open(block)
|
|
89
88
|
when :literal
|
|
90
89
|
transform_literal_from_open(block)
|
|
90
|
+
when :typed_cast
|
|
91
|
+
style = first_positional_attr(block).to_s.downcase
|
|
92
|
+
transform_typed_block(block, TYPED_BLOCK_CAST_STYLES.fetch(style))
|
|
91
93
|
else
|
|
92
94
|
transform_typed_block(block, native_class)
|
|
93
95
|
end
|
|
@@ -119,11 +121,25 @@ module Coradoc
|
|
|
119
121
|
|
|
120
122
|
VERBATILE_CAST_STYLES = %w[source listing literal].freeze
|
|
121
123
|
|
|
124
|
+
# Positional style → typed CoreModel class for blocks whose
|
|
125
|
+
# attribute line casts an open block into a richer semantic
|
|
126
|
+
# type (e.g. `[sidebar]\n--\n...\n--`). Single source of
|
|
127
|
+
# truth: adding a new style cast is one line here, no edits
|
|
128
|
+
# to dispatch (OCP).
|
|
129
|
+
TYPED_BLOCK_CAST_STYLES = {
|
|
130
|
+
'sidebar' => Coradoc::CoreModel::SidebarBlock,
|
|
131
|
+
'example' => Coradoc::CoreModel::ExampleBlock,
|
|
132
|
+
'quote' => Coradoc::CoreModel::QuoteBlock,
|
|
133
|
+
'abstract' => Coradoc::CoreModel::AbstractBlock,
|
|
134
|
+
'partintro' => Coradoc::CoreModel::PartintroBlock
|
|
135
|
+
}.freeze
|
|
136
|
+
|
|
122
137
|
# Resolve a delimited block's cast from its first positional
|
|
123
138
|
# attribute. Single source of truth for the cast ladder used
|
|
124
139
|
# by every block-form transformer (example/sidebar/quote/open).
|
|
125
140
|
# Returns one of: :admonition, :stem, :source, :listing,
|
|
126
|
-
# :literal, or nil (no cast — use the block's
|
|
141
|
+
# :literal, :typed_cast, or nil (no cast — use the block's
|
|
142
|
+
# native class).
|
|
127
143
|
def block_cast_semantic(block)
|
|
128
144
|
style = first_positional_attr(block)
|
|
129
145
|
return nil unless style
|
|
@@ -131,6 +147,7 @@ module Coradoc
|
|
|
131
147
|
style_str = style.to_s.downcase
|
|
132
148
|
return :admonition if AdmonitionStyles.admonition?(style)
|
|
133
149
|
return :stem if STEM_STYLES.key?(style_str)
|
|
150
|
+
return :typed_cast if TYPED_BLOCK_CAST_STYLES.key?(style_str)
|
|
134
151
|
return style_str.to_sym if VERBATILE_CAST_STYLES.include?(style_str)
|
|
135
152
|
|
|
136
153
|
nil
|
|
@@ -163,7 +180,8 @@ module Coradoc
|
|
|
163
180
|
Coradoc::CoreModel::AnnotationBlock.new(
|
|
164
181
|
annotation_type: canonical,
|
|
165
182
|
content: content_lines,
|
|
166
|
-
title: ToCoreModel.extract_title_text(block.title)
|
|
183
|
+
title: ToCoreModel.extract_title_text(block.title),
|
|
184
|
+
source_line: block.source_line
|
|
167
185
|
)
|
|
168
186
|
end
|
|
169
187
|
|
|
@@ -176,7 +194,7 @@ module Coradoc
|
|
|
176
194
|
end
|
|
177
195
|
|
|
178
196
|
def transform_block(block, semantic_type_or_delimiter)
|
|
179
|
-
|
|
197
|
+
source_lines = ToCoreModel.extract_source_lines(block.lines)
|
|
180
198
|
semantic_type = if semantic_type_or_delimiter.is_a?(Symbol)
|
|
181
199
|
semantic_type_or_delimiter
|
|
182
200
|
else
|
|
@@ -188,8 +206,10 @@ module Coradoc
|
|
|
188
206
|
delimiter_type: semantic_type_or_delimiter.is_a?(String) ? semantic_type_or_delimiter : nil,
|
|
189
207
|
id: block.id,
|
|
190
208
|
title: ToCoreModel.extract_title_text(block.title),
|
|
191
|
-
content:
|
|
192
|
-
|
|
209
|
+
content: source_lines.join("\n"),
|
|
210
|
+
lines: source_lines,
|
|
211
|
+
language: ToCoreModel.extract_block_language(block),
|
|
212
|
+
source_line: block.source_line
|
|
193
213
|
)
|
|
194
214
|
end
|
|
195
215
|
|
|
@@ -216,6 +236,7 @@ module Coradoc
|
|
|
216
236
|
title: ToCoreModel.extract_title_text(block.title),
|
|
217
237
|
children: children,
|
|
218
238
|
language: ToCoreModel.extract_block_language(block),
|
|
239
|
+
source_line: block.source_line,
|
|
219
240
|
**extra_attrs
|
|
220
241
|
)
|
|
221
242
|
else
|
|
@@ -230,7 +251,8 @@ module Coradoc
|
|
|
230
251
|
inline = [Coradoc::CoreModel::TextContent.new(text: '')] if inline.empty?
|
|
231
252
|
Coradoc::CoreModel::ParagraphBlock.new(
|
|
232
253
|
content: ToCoreModel.extract_text_content(group),
|
|
233
|
-
children: Array(inline)
|
|
254
|
+
children: Array(inline),
|
|
255
|
+
source_line: ToCoreModel.extract_source_line(group)
|
|
234
256
|
)
|
|
235
257
|
end
|
|
236
258
|
|
|
@@ -240,6 +262,7 @@ module Coradoc
|
|
|
240
262
|
content: content_lines,
|
|
241
263
|
children: children,
|
|
242
264
|
language: ToCoreModel.extract_block_language(block),
|
|
265
|
+
source_line: block.source_line,
|
|
243
266
|
**extra_attrs
|
|
244
267
|
)
|
|
245
268
|
end
|
|
@@ -12,7 +12,6 @@ module Coradoc
|
|
|
12
12
|
children = ToCoreModel.transform(doc.sections || doc.contents || [])
|
|
13
13
|
children = CalloutMerger.call(children)
|
|
14
14
|
children = prepend_frontmatter(children, doc.frontmatter)
|
|
15
|
-
children = insert_title_heading_after_frontmatter(children, title_text)
|
|
16
15
|
children = resolve_attribute_references(children, attributes)
|
|
17
16
|
|
|
18
17
|
Coradoc::CoreModel::DocumentElement.new(
|
|
@@ -34,31 +33,6 @@ module Coradoc
|
|
|
34
33
|
[block, *children]
|
|
35
34
|
end
|
|
36
35
|
|
|
37
|
-
# Per the AsciiDoc spec, the document title (`= Title`) is the
|
|
38
|
-
# document's level-0 heading. Asciidoctor renders it as an
|
|
39
|
-
# <h1> at the top of the body by default. Emit a HeaderElement
|
|
40
|
-
# at level 0 so consumers that walk the body's children see the
|
|
41
|
-
# title (the standard ProseMirror/HTML rendering pattern)
|
|
42
|
-
# instead of having to read Document.title separately.
|
|
43
|
-
#
|
|
44
|
-
# The title attribute on DocumentElement is preserved for
|
|
45
|
-
# consumers that read it directly. The title heading is placed
|
|
46
|
-
# after any FrontmatterBlock (frontmatter is metadata that
|
|
47
|
-
# precedes the body).
|
|
48
|
-
def insert_title_heading_after_frontmatter(children, title_text)
|
|
49
|
-
return children if title_text.nil? || title_text.strip.empty?
|
|
50
|
-
|
|
51
|
-
title_heading = Coradoc::CoreModel::HeaderElement.new(
|
|
52
|
-
level: 0,
|
|
53
|
-
title: title_text,
|
|
54
|
-
content: title_text
|
|
55
|
-
)
|
|
56
|
-
frontmatter_count = children.count do |child|
|
|
57
|
-
child.is_a?(Coradoc::CoreModel::FrontmatterBlock)
|
|
58
|
-
end
|
|
59
|
-
children.insert(frontmatter_count, title_heading)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
36
|
# Resolve `{name}` attribute references in the body against the
|
|
63
37
|
# document's own declared attributes. Single source of truth:
|
|
64
38
|
# the resolver lives in core so other format gems can reuse it
|
|
@@ -86,7 +60,8 @@ module Coradoc
|
|
|
86
60
|
level: section.level,
|
|
87
61
|
title: title_text,
|
|
88
62
|
children: content_children + nested_sections,
|
|
89
|
-
attributes: section_metadata_from(section)
|
|
63
|
+
attributes: section_metadata_from(section),
|
|
64
|
+
source_line: section.source_line
|
|
90
65
|
)
|
|
91
66
|
end
|
|
92
67
|
|
|
@@ -9,14 +9,16 @@ module Coradoc
|
|
|
9
9
|
def transform_inline(inline, format_type)
|
|
10
10
|
klass = Coradoc::CoreModel::InlineElement.format_type_class(format_type)
|
|
11
11
|
klass.new(
|
|
12
|
-
content: ToCoreModel.extract_text_content(inline.content)
|
|
12
|
+
content: ToCoreModel.extract_text_content(inline.content),
|
|
13
|
+
source_line: inline.source_line
|
|
13
14
|
)
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def transform_inline_text(inline, format_type)
|
|
17
18
|
klass = Coradoc::CoreModel::InlineElement.format_type_class(format_type)
|
|
18
19
|
klass.new(
|
|
19
|
-
content: inline.text.to_s
|
|
20
|
+
content: inline.text.to_s,
|
|
21
|
+
source_line: inline.source_line
|
|
20
22
|
)
|
|
21
23
|
end
|
|
22
24
|
|
|
@@ -24,28 +26,32 @@ module Coradoc
|
|
|
24
26
|
parsed_content = ToCoreModel.parse_and_transform_inline(footnote.text.to_s)
|
|
25
27
|
Coradoc::CoreModel::FootnoteElement.new(
|
|
26
28
|
target: footnote.id,
|
|
27
|
-
content: parsed_content
|
|
29
|
+
content: parsed_content,
|
|
30
|
+
source_line: footnote.source_line
|
|
28
31
|
)
|
|
29
32
|
end
|
|
30
33
|
|
|
31
34
|
def transform_link(link)
|
|
32
35
|
Coradoc::CoreModel::LinkElement.new(
|
|
33
36
|
target: link.path,
|
|
34
|
-
content: link.name || link.path
|
|
37
|
+
content: link.name || link.path,
|
|
38
|
+
source_line: link.source_line
|
|
35
39
|
)
|
|
36
40
|
end
|
|
37
41
|
|
|
38
42
|
def transform_cross_reference(xref)
|
|
39
43
|
Coradoc::CoreModel::CrossReferenceElement.new(
|
|
40
44
|
target: xref.href,
|
|
41
|
-
content: xref.args&.first || xref.href
|
|
45
|
+
content: xref.args&.first || xref.href,
|
|
46
|
+
source_line: xref.source_line
|
|
42
47
|
)
|
|
43
48
|
end
|
|
44
49
|
|
|
45
50
|
def transform_stem(stem)
|
|
46
51
|
Coradoc::CoreModel::StemElement.new(
|
|
47
52
|
content: stem.content,
|
|
48
|
-
stem_type: stem.type || 'stem'
|
|
53
|
+
stem_type: stem.type || 'stem',
|
|
54
|
+
source_line: stem.source_line
|
|
49
55
|
)
|
|
50
56
|
end
|
|
51
57
|
end
|
|
@@ -16,11 +16,15 @@ module Coradoc
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
if marker_type == 'definition'
|
|
19
|
-
Coradoc::CoreModel::DefinitionList.new(
|
|
19
|
+
Coradoc::CoreModel::DefinitionList.new(
|
|
20
|
+
items: items,
|
|
21
|
+
source_line: list.source_line
|
|
22
|
+
)
|
|
20
23
|
else
|
|
21
24
|
Coradoc::CoreModel::ListBlock.new(
|
|
22
25
|
marker_type: marker_type,
|
|
23
|
-
items: items
|
|
26
|
+
items: items,
|
|
27
|
+
source_line: list.source_line
|
|
24
28
|
)
|
|
25
29
|
end
|
|
26
30
|
end
|
|
@@ -45,7 +49,8 @@ module Coradoc
|
|
|
45
49
|
term: ToCoreModel.extract_text_content(term_children),
|
|
46
50
|
definitions: [ToCoreModel.extract_text_content(def_children)],
|
|
47
51
|
term_children: term_children,
|
|
48
|
-
definition_children: def_children
|
|
52
|
+
definition_children: def_children,
|
|
53
|
+
source_line: item.source_line
|
|
49
54
|
)
|
|
50
55
|
di.id = item.id if item.id
|
|
51
56
|
|
|
@@ -63,7 +68,8 @@ module Coradoc
|
|
|
63
68
|
|
|
64
69
|
li = Coradoc::CoreModel::ListItem.new(
|
|
65
70
|
content: ToCoreModel.extract_text_content(content_val),
|
|
66
|
-
marker: item.marker
|
|
71
|
+
marker: item.marker,
|
|
72
|
+
source_line: item.source_line
|
|
67
73
|
)
|
|
68
74
|
li.children = children
|
|
69
75
|
|
|
@@ -10,7 +10,8 @@ module Coradoc
|
|
|
10
10
|
Coradoc::CoreModel::Term.new(
|
|
11
11
|
text: term.term.to_s,
|
|
12
12
|
type: term.type&.to_s || 'preferred',
|
|
13
|
-
lang: term.lang&.to_s || 'en'
|
|
13
|
+
lang: term.lang&.to_s || 'en',
|
|
14
|
+
source_line: term.source_line
|
|
14
15
|
)
|
|
15
16
|
end
|
|
16
17
|
|
|
@@ -19,7 +20,8 @@ module Coradoc
|
|
|
19
20
|
children = ToCoreModel.transform_inline_content(admonition.content)
|
|
20
21
|
block = Coradoc::CoreModel::AnnotationBlock.new(
|
|
21
22
|
annotation_type: canonical,
|
|
22
|
-
content: ToCoreModel.extract_text_content(admonition.content)
|
|
23
|
+
content: ToCoreModel.extract_text_content(admonition.content),
|
|
24
|
+
source_line: admonition.source_line
|
|
23
25
|
)
|
|
24
26
|
block.children = children
|
|
25
27
|
block
|
|
@@ -35,7 +37,8 @@ module Coradoc
|
|
|
35
37
|
alt: image.alt, title: image.title, caption: image.caption,
|
|
36
38
|
width: image.width, height: image.height,
|
|
37
39
|
link: image.link, role: image.role,
|
|
38
|
-
inline: image.is_a?(Coradoc::AsciiDoc::Model::Image::InlineImage)
|
|
40
|
+
inline: image.is_a?(Coradoc::AsciiDoc::Model::Image::InlineImage),
|
|
41
|
+
source_line: image.source_line
|
|
39
42
|
}
|
|
40
43
|
end
|
|
41
44
|
|
|
@@ -53,7 +56,8 @@ module Coradoc
|
|
|
53
56
|
id: bib.id,
|
|
54
57
|
title: bib.title.to_s,
|
|
55
58
|
level: nil,
|
|
56
|
-
entries: entries
|
|
59
|
+
entries: entries,
|
|
60
|
+
source_line: bib.source_line
|
|
57
61
|
)
|
|
58
62
|
end
|
|
59
63
|
|
|
@@ -61,7 +65,8 @@ module Coradoc
|
|
|
61
65
|
Coradoc::CoreModel::BibliographyEntry.new(
|
|
62
66
|
anchor_name: entry.anchor_name,
|
|
63
67
|
document_id: entry.document_id,
|
|
64
|
-
ref_text: entry.ref_text.to_s
|
|
68
|
+
ref_text: entry.ref_text.to_s,
|
|
69
|
+
source_line: entry.source_line
|
|
65
70
|
)
|
|
66
71
|
end
|
|
67
72
|
end
|
|
@@ -14,7 +14,8 @@ module Coradoc
|
|
|
14
14
|
Coradoc::CoreModel::Table.new(
|
|
15
15
|
id: table.id,
|
|
16
16
|
title: table.title&.to_s,
|
|
17
|
-
rows: rows
|
|
17
|
+
rows: rows,
|
|
18
|
+
source_line: table.source_line
|
|
18
19
|
)
|
|
19
20
|
end
|
|
20
21
|
|
|
@@ -24,7 +25,8 @@ module Coradoc
|
|
|
24
25
|
end
|
|
25
26
|
Coradoc::CoreModel::TableRow.new(
|
|
26
27
|
cells: cells,
|
|
27
|
-
header: row.header
|
|
28
|
+
header: row.header,
|
|
29
|
+
source_line: row.source_line
|
|
28
30
|
)
|
|
29
31
|
end
|
|
30
32
|
|
|
@@ -38,7 +40,8 @@ module Coradoc
|
|
|
38
40
|
colspan: cell.colspan,
|
|
39
41
|
rowspan: cell.rowspan,
|
|
40
42
|
style: cell.style_name,
|
|
41
|
-
children: children
|
|
43
|
+
children: children,
|
|
44
|
+
source_line: cell.source_line
|
|
42
45
|
)
|
|
43
46
|
end
|
|
44
47
|
end
|
|
@@ -38,18 +38,12 @@ module Coradoc
|
|
|
38
38
|
Coradoc::AsciiDoc::Model::Header.new(title: '')
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
# Pull FrontmatterBlock out first so strip_title_heading sees
|
|
42
|
-
# the body children in their natural order (frontmatter →
|
|
43
|
-
# title heading → body). The reverse-direction strip then
|
|
44
|
-
# matches on a level-0 HeaderElement wherever it sits among
|
|
45
|
-
# the remaining children.
|
|
46
41
|
without_frontmatter, frontmatter = extract_frontmatter(Array(element.children))
|
|
47
|
-
without_title = strip_title_heading(without_frontmatter, element.title)
|
|
48
42
|
|
|
49
43
|
Coradoc::AsciiDoc::Model::Document.new(
|
|
50
44
|
id: element.id,
|
|
51
45
|
header: header,
|
|
52
|
-
sections: flatten_children(
|
|
46
|
+
sections: flatten_children(without_frontmatter),
|
|
53
47
|
frontmatter: frontmatter
|
|
54
48
|
)
|
|
55
49
|
when CoreModel::SectionElement
|
|
@@ -68,27 +62,6 @@ module Coradoc
|
|
|
68
62
|
end
|
|
69
63
|
end
|
|
70
64
|
|
|
71
|
-
# The forward direction (DocumentTransformer#insert_title_heading_after_frontmatter)
|
|
72
|
-
# emits a level-0 HeaderElement after any FrontmatterBlock so
|
|
73
|
-
# consumers that walk the children see the title. On the reverse
|
|
74
|
-
# path, that HeaderElement would round-trip back as a separate
|
|
75
|
-
# body element and the title would be emitted twice (once via
|
|
76
|
-
# the document header, once via the heading child). Drop it
|
|
77
|
-
# before serialization when it carries the same text as the
|
|
78
|
-
# document title.
|
|
79
|
-
def strip_title_heading(children, document_title)
|
|
80
|
-
return children unless document_title && !document_title.strip.empty?
|
|
81
|
-
|
|
82
|
-
index = children.find_index do |child|
|
|
83
|
-
child.is_a?(CoreModel::HeaderElement) &&
|
|
84
|
-
child.level.to_i.zero? &&
|
|
85
|
-
child.title.to_s == document_title.to_s
|
|
86
|
-
end
|
|
87
|
-
return children unless index
|
|
88
|
-
|
|
89
|
-
children.reject.with_index { |_, i| i == index }
|
|
90
|
-
end
|
|
91
|
-
|
|
92
65
|
# Transforms each CoreModel child and flattens one level so a
|
|
93
66
|
# transform that returns multiple siblings (e.g. a source block
|
|
94
67
|
# followed by its re-expanded callout paragraphs) stays in
|
|
@@ -35,21 +35,39 @@ module Coradoc
|
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
# Folds soft source line breaks into a single text run: when two
|
|
39
|
+
# +Model::TextElement+s sit adjacent in the content array (the parser
|
|
40
|
+
# emits one per source line of a paragraph), a space is inserted
|
|
41
|
+
# between them so wrapped text renders as flowing prose rather than
|
|
42
|
+
# concatenated words.
|
|
43
|
+
#
|
|
44
|
+
# The previous item must ALSO be a TextElement — if a non-text
|
|
45
|
+
# inline (HardLineBreak, Passthrough, Image, etc.) sits between two
|
|
46
|
+
# TextElements, the source did not have a soft break there and no
|
|
47
|
+
# space should be synthesised. Without this guard, `foo +\nbar`
|
|
48
|
+
# would emit `["foo", hard_break, " ", "bar"]` (stray space) and
|
|
49
|
+
# `Before +pass:[RAW]+ after` would emit a double space around the
|
|
50
|
+
# passthrough.
|
|
38
51
|
def visit_array(items)
|
|
39
52
|
result = []
|
|
40
|
-
|
|
53
|
+
previous = nil
|
|
54
|
+
items.each do |item|
|
|
41
55
|
transformed = visit_content(item)
|
|
42
56
|
next if transformed.empty?
|
|
43
57
|
|
|
44
|
-
|
|
45
|
-
item.is_a?(Model::TextElement) &&
|
|
46
|
-
item.line_break != '+'
|
|
47
|
-
result << CoreModel::TextContent.new(text: ' ') if needs_space
|
|
58
|
+
result << CoreModel::TextContent.new(text: ' ') if soft_break_before?(previous, item)
|
|
48
59
|
result.concat(transformed)
|
|
60
|
+
previous = item
|
|
49
61
|
end
|
|
50
62
|
result
|
|
51
63
|
end
|
|
52
64
|
|
|
65
|
+
def soft_break_before?(previous, current)
|
|
66
|
+
previous.is_a?(Model::TextElement) &&
|
|
67
|
+
current.is_a?(Model::TextElement) &&
|
|
68
|
+
current.line_break != '+'
|
|
69
|
+
end
|
|
70
|
+
|
|
53
71
|
def visit_model(model)
|
|
54
72
|
@to_core_model.transform(model)
|
|
55
73
|
end
|
|
@@ -23,14 +23,38 @@ module Coradoc
|
|
|
23
23
|
transformer ? transformer.call(model) : model
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
# Returns the source-line view of an AsciiDoc content array as
|
|
27
|
+
# an Array<String>. Single source of truth for source-line
|
|
28
|
+
# extraction — every transformer that needs to populate
|
|
29
|
+
# CoreModel::Block#lines goes through here so the filtering
|
|
30
|
+
# rules (skip Model::LineBreak and Model::Break::PageBreak,
|
|
31
|
+
# which carry no renderable text) apply consistently.
|
|
32
|
+
def extract_source_lines(content)
|
|
33
|
+
Array(content).filter_map do |item|
|
|
34
|
+
next if item.is_a?(Coradoc::AsciiDoc::Model::LineBreak) ||
|
|
35
|
+
item.is_a?(Coradoc::AsciiDoc::Model::Break::PageBreak)
|
|
36
|
+
|
|
37
|
+
extract_text_content(item).to_s
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
26
41
|
def extract_block_lines(block)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
42
|
+
extract_source_lines(block.lines).join("\n")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Extracts the source_line of the first AsciiDoc::Model::Base in
|
|
46
|
+
# +content+. Single source of truth for source-line propagation
|
|
47
|
+
# from AsciiDoc::Model trees into CoreModel objects built from
|
|
48
|
+
# grouped content (e.g., paragraphs inside typed blocks).
|
|
49
|
+
# Returns nil when no model carries a source_line.
|
|
50
|
+
def extract_source_line(content)
|
|
51
|
+
Array(content).each do |item|
|
|
52
|
+
next unless item.is_a?(Coradoc::AsciiDoc::Model::Base)
|
|
53
|
+
|
|
54
|
+
line = item.source_line
|
|
55
|
+
return line if line
|
|
30
56
|
end
|
|
31
|
-
|
|
32
|
-
extract_text_content(line)
|
|
33
|
-
end.join("\n")
|
|
57
|
+
nil
|
|
34
58
|
end
|
|
35
59
|
|
|
36
60
|
def extract_title_text(title)
|
|
@@ -23,7 +23,8 @@ module Coradoc
|
|
|
23
23
|
title: title,
|
|
24
24
|
delimiter_len: delimiter.size,
|
|
25
25
|
lines: lines,
|
|
26
|
-
ordering: ordering
|
|
26
|
+
ordering: ordering,
|
|
27
|
+
source_line: SourceLineExtractor.extract(block)
|
|
27
28
|
}
|
|
28
29
|
# Markdown fences carry the language tag inline (```ruby);
|
|
29
30
|
# pass it through so the SourceCode classifier entry can set
|
|
@@ -34,7 +35,11 @@ module Coradoc
|
|
|
34
35
|
|
|
35
36
|
# Example
|
|
36
37
|
rule(example: sequence(:example)) do
|
|
37
|
-
Model::Block::Example.new(
|
|
38
|
+
Model::Block::Example.new(
|
|
39
|
+
title: '',
|
|
40
|
+
lines: example,
|
|
41
|
+
source_line: SourceLineExtractor.extract(example)
|
|
42
|
+
)
|
|
38
43
|
end
|
|
39
44
|
|
|
40
45
|
# Admonition. Canonicalise the type to uppercase so round-trips
|
|
@@ -45,7 +50,11 @@ module Coradoc
|
|
|
45
50
|
content: sequence(:content)
|
|
46
51
|
) do
|
|
47
52
|
canonical = Coradoc::AsciiDoc::Transform::ElementTransformers::AdmonitionStyles.canonicalize(admonition_type.to_s)
|
|
48
|
-
Model::Admonition.new(
|
|
53
|
+
Model::Admonition.new(
|
|
54
|
+
content: content,
|
|
55
|
+
type: canonical,
|
|
56
|
+
source_line: SourceLineExtractor.extract(admonition_type)
|
|
57
|
+
)
|
|
49
58
|
end
|
|
50
59
|
|
|
51
60
|
# Block image
|
|
@@ -63,6 +72,7 @@ module Coradoc
|
|
|
63
72
|
src: path,
|
|
64
73
|
attributes: residual,
|
|
65
74
|
line_break: "\n",
|
|
75
|
+
source_line: SourceLineExtractor.extract(block_image),
|
|
66
76
|
**promoted
|
|
67
77
|
)
|
|
68
78
|
end
|
|
@@ -21,7 +21,10 @@ module Coradoc
|
|
|
21
21
|
id = id.to_s unless id.nil?
|
|
22
22
|
id = nil if id && id.empty?
|
|
23
23
|
|
|
24
|
-
Model::Header.new(
|
|
24
|
+
Model::Header.new(
|
|
25
|
+
id:, title:, author:, revision:,
|
|
26
|
+
source_line: SourceLineExtractor.extract(header)
|
|
27
|
+
)
|
|
25
28
|
end
|
|
26
29
|
|
|
27
30
|
rule(header: simple(:header)) do
|
|
@@ -34,7 +37,10 @@ module Coradoc
|
|
|
34
37
|
last_name: simple(:last_name),
|
|
35
38
|
email: simple(:email)
|
|
36
39
|
) do
|
|
37
|
-
Model::Author.new(
|
|
40
|
+
Model::Author.new(
|
|
41
|
+
first_name:, last_name:, email:, middle_name: nil,
|
|
42
|
+
source_line: SourceLineExtractor.extract(first_name)
|
|
43
|
+
)
|
|
38
44
|
end
|
|
39
45
|
|
|
40
46
|
# Revision
|
|
@@ -43,7 +49,10 @@ module Coradoc
|
|
|
43
49
|
date: simple(:date),
|
|
44
50
|
remark: simple(:remark)
|
|
45
51
|
) do
|
|
46
|
-
Model::Revision.new(
|
|
52
|
+
Model::Revision.new(
|
|
53
|
+
number:, date:, remark:,
|
|
54
|
+
source_line: SourceLineExtractor.extract(number)
|
|
55
|
+
)
|
|
47
56
|
end
|
|
48
57
|
end
|
|
49
58
|
end
|
|
@@ -22,7 +22,8 @@ module Coradoc
|
|
|
22
22
|
rule(link: subtree(:link)) do
|
|
23
23
|
Model::Inline::Link.new(
|
|
24
24
|
path: link[:path].to_s,
|
|
25
|
-
name: link[:text]&.to_s
|
|
25
|
+
name: link[:text]&.to_s,
|
|
26
|
+
source_line: SourceLineExtractor.extract(link)
|
|
26
27
|
)
|
|
27
28
|
end
|
|
28
29
|
|
|
@@ -31,7 +32,10 @@ module Coradoc
|
|
|
31
32
|
href = xref[:href].to_s
|
|
32
33
|
text = xref[:text]
|
|
33
34
|
args = text ? [text.to_s] : []
|
|
34
|
-
Model::Inline::CrossReference.new(
|
|
35
|
+
Model::Inline::CrossReference.new(
|
|
36
|
+
href:, args:,
|
|
37
|
+
source_line: SourceLineExtractor.extract(xref)
|
|
38
|
+
)
|
|
35
39
|
end
|
|
36
40
|
|
|
37
41
|
# Inline image
|
|
@@ -43,6 +47,7 @@ module Coradoc
|
|
|
43
47
|
Model::Image::InlineImage.new(
|
|
44
48
|
src: inline_image[:path],
|
|
45
49
|
attributes: residual,
|
|
50
|
+
source_line: SourceLineExtractor.extract(inline_image),
|
|
46
51
|
**promoted
|
|
47
52
|
)
|
|
48
53
|
end
|
|
@@ -54,13 +59,17 @@ module Coradoc
|
|
|
54
59
|
rule(inline_passthrough: subtree(:passthrough)) do
|
|
55
60
|
Model::Inline::Passthrough.new(
|
|
56
61
|
content: passthrough[:raw].to_s,
|
|
57
|
-
form: 'triple'
|
|
62
|
+
form: 'triple',
|
|
63
|
+
source_line: SourceLineExtractor.extract(passthrough)
|
|
58
64
|
)
|
|
59
65
|
end
|
|
60
66
|
|
|
61
67
|
# Attribute reference
|
|
62
68
|
rule(attribute_reference: simple(:name)) do
|
|
63
|
-
Model::Inline::AttributeReference.new(
|
|
69
|
+
Model::Inline::AttributeReference.new(
|
|
70
|
+
name:,
|
|
71
|
+
source_line: SourceLineExtractor.extract(name)
|
|
72
|
+
)
|
|
64
73
|
end
|
|
65
74
|
|
|
66
75
|
# Hard line break (` +\n` or `\\n`). Emitted as a dedicated
|
|
@@ -68,8 +77,10 @@ module Coradoc
|
|
|
68
77
|
# Model::LineBreak, which only represents paragraph-separator
|
|
69
78
|
# blank lines. Hard breaks carry semantic meaning: HTML/Markdown
|
|
70
79
|
# renderers map them to <br>.
|
|
71
|
-
rule(hard_line_break: simple(:
|
|
72
|
-
Model::Inline::HardLineBreak.new
|
|
80
|
+
rule(hard_line_break: simple(:hard_line_break)) do
|
|
81
|
+
Model::Inline::HardLineBreak.new(
|
|
82
|
+
source_line: SourceLineExtractor.extract(hard_line_break)
|
|
83
|
+
)
|
|
73
84
|
end
|
|
74
85
|
|
|
75
86
|
# Term
|
|
@@ -77,30 +88,45 @@ module Coradoc
|
|
|
77
88
|
term_type: simple(:term_type),
|
|
78
89
|
term: simple(:term)
|
|
79
90
|
) do
|
|
80
|
-
Coradoc::AsciiDoc::Model::Term.new(
|
|
91
|
+
Coradoc::AsciiDoc::Model::Term.new(
|
|
92
|
+
term:, type: term_type, lang: :en,
|
|
93
|
+
source_line: SourceLineExtractor.extract(term_type)
|
|
94
|
+
)
|
|
81
95
|
end
|
|
82
96
|
|
|
83
97
|
# Footnote
|
|
84
98
|
rule(footnote: simple(:footnote)) do
|
|
85
99
|
text_str = footnote.to_s
|
|
86
|
-
Coradoc::AsciiDoc::Model::Inline::Footnote.new(
|
|
100
|
+
Coradoc::AsciiDoc::Model::Inline::Footnote.new(
|
|
101
|
+
text: text_str,
|
|
102
|
+
source_line: SourceLineExtractor.extract(footnote)
|
|
103
|
+
)
|
|
87
104
|
end
|
|
88
105
|
|
|
89
106
|
rule(footnote: simple(:footnote), id: simple(:id)) do
|
|
90
107
|
text_str = footnote.to_s
|
|
91
|
-
Coradoc::AsciiDoc::Model::Inline::Footnote.new(
|
|
108
|
+
Coradoc::AsciiDoc::Model::Inline::Footnote.new(
|
|
109
|
+
text: text_str, id: id.to_s,
|
|
110
|
+
source_line: SourceLineExtractor.extract(footnote)
|
|
111
|
+
)
|
|
92
112
|
end
|
|
93
113
|
|
|
94
114
|
# Footnote with empty content (reference to named footnote)
|
|
95
115
|
rule(footnote: sequence(:footnote), id: simple(:id)) do
|
|
96
116
|
text_str = footnote.map(&:to_s).join
|
|
97
|
-
Coradoc::AsciiDoc::Model::Inline::Footnote.new(
|
|
117
|
+
Coradoc::AsciiDoc::Model::Inline::Footnote.new(
|
|
118
|
+
text: text_str, id: id.to_s,
|
|
119
|
+
source_line: SourceLineExtractor.extract(footnote)
|
|
120
|
+
)
|
|
98
121
|
end
|
|
99
122
|
|
|
100
123
|
# Footnote with empty content and no id
|
|
101
124
|
rule(footnote: sequence(:footnote)) do
|
|
102
125
|
text_str = footnote.map(&:to_s).join
|
|
103
|
-
Coradoc::AsciiDoc::Model::Inline::Footnote.new(
|
|
126
|
+
Coradoc::AsciiDoc::Model::Inline::Footnote.new(
|
|
127
|
+
text: text_str,
|
|
128
|
+
source_line: SourceLineExtractor.extract(footnote)
|
|
129
|
+
)
|
|
104
130
|
end
|
|
105
131
|
|
|
106
132
|
# Inline formatting rules generated from a single registry.
|
|
@@ -114,11 +140,17 @@ module Coradoc
|
|
|
114
140
|
|
|
115
141
|
rule(constrained_key => subtree(:subtree)) do
|
|
116
142
|
content = Transformer.extract_inline_content(subtree)
|
|
117
|
-
klass.new(
|
|
143
|
+
klass.new(
|
|
144
|
+
content: content, unconstrained: false,
|
|
145
|
+
source_line: SourceLineExtractor.extract(subtree)
|
|
146
|
+
)
|
|
118
147
|
end
|
|
119
148
|
rule(unconstrained_key => subtree(:subtree)) do
|
|
120
149
|
content = Transformer.extract_inline_content(subtree)
|
|
121
|
-
klass.new(
|
|
150
|
+
klass.new(
|
|
151
|
+
content: content, unconstrained: true,
|
|
152
|
+
source_line: SourceLineExtractor.extract(subtree)
|
|
153
|
+
)
|
|
122
154
|
end
|
|
123
155
|
end
|
|
124
156
|
|
|
@@ -127,7 +159,8 @@ module Coradoc
|
|
|
127
159
|
Model::Inline::Span.new(
|
|
128
160
|
text: span_constrained[:text],
|
|
129
161
|
unconstrained: false,
|
|
130
|
-
attributes: span_constrained[:attribute_list]
|
|
162
|
+
attributes: span_constrained[:attribute_list],
|
|
163
|
+
source_line: SourceLineExtractor.extract(span_constrained)
|
|
131
164
|
)
|
|
132
165
|
end
|
|
133
166
|
|
|
@@ -136,31 +169,42 @@ module Coradoc
|
|
|
136
169
|
Model::Inline::Span.new(
|
|
137
170
|
text: span_unconstrained[:text],
|
|
138
171
|
unconstrained: true,
|
|
139
|
-
attributes: span_unconstrained[:attribute_list]
|
|
172
|
+
attributes: span_unconstrained[:attribute_list],
|
|
173
|
+
source_line: SourceLineExtractor.extract(span_unconstrained)
|
|
140
174
|
)
|
|
141
175
|
end
|
|
142
176
|
|
|
143
177
|
# Superscript
|
|
144
178
|
rule(superscript: subtree(:superscript)) do
|
|
145
179
|
content = Transformer.extract_simple_inline_content(superscript)
|
|
146
|
-
Model::Inline::Superscript.new(
|
|
180
|
+
Model::Inline::Superscript.new(
|
|
181
|
+
content:,
|
|
182
|
+
source_line: SourceLineExtractor.extract(superscript)
|
|
183
|
+
)
|
|
147
184
|
end
|
|
148
185
|
|
|
149
186
|
# Subscript
|
|
150
187
|
rule(subscript: subtree(:subscript)) do
|
|
151
188
|
content = Transformer.extract_simple_inline_content(subscript)
|
|
152
|
-
Model::Inline::Subscript.new(
|
|
189
|
+
Model::Inline::Subscript.new(
|
|
190
|
+
content:,
|
|
191
|
+
source_line: SourceLineExtractor.extract(subscript)
|
|
192
|
+
)
|
|
153
193
|
end
|
|
154
194
|
|
|
155
195
|
# Highlight (simple)
|
|
156
196
|
rule(highlight: simple(:text)) do
|
|
157
|
-
Model::Highlight.new(
|
|
197
|
+
Model::Highlight.new(
|
|
198
|
+
content: text,
|
|
199
|
+
source_line: SourceLineExtractor.extract(text)
|
|
200
|
+
)
|
|
158
201
|
end
|
|
159
202
|
# Stem
|
|
160
203
|
rule(stem: subtree(:stem)) do
|
|
161
204
|
Coradoc::AsciiDoc::Model::Inline::Stem.new(
|
|
162
205
|
type: stem[:stem_type],
|
|
163
|
-
content: stem[:content]
|
|
206
|
+
content: stem[:content],
|
|
207
|
+
source_line: SourceLineExtractor.extract(stem)
|
|
164
208
|
)
|
|
165
209
|
end
|
|
166
210
|
end
|
|
@@ -74,7 +74,8 @@ module Coradoc
|
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
Model::List::Item.new(
|
|
77
|
-
content: content, id:, marker:, attached:, nested:, line_break
|
|
77
|
+
content: content, id:, marker:, attached:, nested:, line_break:,
|
|
78
|
+
source_line: SourceLineExtractor.extract(list_item)
|
|
78
79
|
)
|
|
79
80
|
end
|
|
80
81
|
|
|
@@ -85,26 +86,40 @@ module Coradoc
|
|
|
85
86
|
|
|
86
87
|
# Unordered list
|
|
87
88
|
rule(unordered: sequence(:list_items)) do
|
|
88
|
-
Model::List::Unordered.new(
|
|
89
|
+
Model::List::Unordered.new(
|
|
90
|
+
items: list_items,
|
|
91
|
+
source_line: SourceLineExtractor.extract(list_items)
|
|
92
|
+
)
|
|
89
93
|
end
|
|
90
94
|
|
|
91
95
|
rule(
|
|
92
96
|
attribute_list: simple(:attribute_list),
|
|
93
97
|
unordered: sequence(:list_items)
|
|
94
98
|
) do
|
|
95
|
-
Model::List::Unordered.new(
|
|
99
|
+
Model::List::Unordered.new(
|
|
100
|
+
items: list_items,
|
|
101
|
+
attrs: attribute_list,
|
|
102
|
+
source_line: SourceLineExtractor.extract(attribute_list)
|
|
103
|
+
)
|
|
96
104
|
end
|
|
97
105
|
|
|
98
106
|
# Ordered list
|
|
99
107
|
rule(ordered: sequence(:list_items)) do
|
|
100
|
-
Model::List::Ordered.new(
|
|
108
|
+
Model::List::Ordered.new(
|
|
109
|
+
items: list_items,
|
|
110
|
+
source_line: SourceLineExtractor.extract(list_items)
|
|
111
|
+
)
|
|
101
112
|
end
|
|
102
113
|
|
|
103
114
|
rule(
|
|
104
115
|
attribute_list: simple(:attribute_list),
|
|
105
116
|
ordered: sequence(:list_items)
|
|
106
117
|
) do
|
|
107
|
-
Model::List::Ordered.new(
|
|
118
|
+
Model::List::Ordered.new(
|
|
119
|
+
items: list_items,
|
|
120
|
+
attrs: attribute_list,
|
|
121
|
+
source_line: SourceLineExtractor.extract(attribute_list)
|
|
122
|
+
)
|
|
108
123
|
end
|
|
109
124
|
|
|
110
125
|
# Definition list term (with optional anchor)
|
|
@@ -12,16 +12,25 @@ module Coradoc
|
|
|
12
12
|
comment_text: simple(:comment_text),
|
|
13
13
|
line_break: simple(:line_break)
|
|
14
14
|
}) do
|
|
15
|
-
Model::CommentLine.new(
|
|
15
|
+
Model::CommentLine.new(
|
|
16
|
+
text: comment_text,
|
|
17
|
+
line_break: line_break,
|
|
18
|
+
source_line: SourceLineExtractor.extract(comment_text)
|
|
19
|
+
)
|
|
16
20
|
end
|
|
17
21
|
|
|
18
22
|
rule(comment_block: { comment_text: simple(:comment_text) }) do
|
|
19
|
-
Model::CommentBlock.new(
|
|
23
|
+
Model::CommentBlock.new(
|
|
24
|
+
text: comment_text,
|
|
25
|
+
source_line: SourceLineExtractor.extract(comment_text)
|
|
26
|
+
)
|
|
20
27
|
end
|
|
21
28
|
|
|
22
29
|
# Page break
|
|
23
30
|
rule(page_break: simple(:page_break)) do
|
|
24
|
-
Model::Break::PageBreak.new
|
|
31
|
+
Model::Break::PageBreak.new(
|
|
32
|
+
source_line: SourceLineExtractor.extract(page_break)
|
|
33
|
+
)
|
|
25
34
|
end
|
|
26
35
|
|
|
27
36
|
# Tag
|
|
@@ -30,7 +39,8 @@ module Coradoc
|
|
|
30
39
|
name: tag[:name],
|
|
31
40
|
attrs: tag[:attribute_list],
|
|
32
41
|
line_break: tag[:line_break],
|
|
33
|
-
prefix: tag[:prefix]
|
|
42
|
+
prefix: tag[:prefix],
|
|
43
|
+
source_line: SourceLineExtractor.extract(tag)
|
|
34
44
|
)
|
|
35
45
|
end
|
|
36
46
|
|
|
@@ -45,7 +55,7 @@ module Coradoc
|
|
|
45
55
|
end
|
|
46
56
|
|
|
47
57
|
rule(positional: simple(:positional)) do
|
|
48
|
-
positional
|
|
58
|
+
positional
|
|
49
59
|
end
|
|
50
60
|
|
|
51
61
|
rule(attribute_array: nil) do
|
|
@@ -53,11 +63,16 @@ module Coradoc
|
|
|
53
63
|
end
|
|
54
64
|
|
|
55
65
|
rule(attribute_array: sequence(:attributes)) do
|
|
56
|
-
attr_list = Model::AttributeList.new
|
|
66
|
+
attr_list = Model::AttributeList.new(
|
|
67
|
+
source_line: SourceLineExtractor.extract(attributes)
|
|
68
|
+
)
|
|
57
69
|
attributes.each do |a|
|
|
58
|
-
|
|
70
|
+
case a
|
|
71
|
+
when Parslet::Slice
|
|
72
|
+
attr_list.add_positional(a.to_s)
|
|
73
|
+
when String
|
|
59
74
|
attr_list.add_positional(a)
|
|
60
|
-
|
|
75
|
+
when Model::Attribute
|
|
61
76
|
attr_list.add_named(a.key, a.value)
|
|
62
77
|
end
|
|
63
78
|
end
|
|
@@ -104,7 +119,8 @@ module Coradoc
|
|
|
104
119
|
Model::Include.new(
|
|
105
120
|
path: path.to_s,
|
|
106
121
|
attributes: attribute_list,
|
|
107
|
-
line_break: line_break
|
|
122
|
+
line_break: line_break,
|
|
123
|
+
source_line: SourceLineExtractor.extract(path)
|
|
108
124
|
)
|
|
109
125
|
end
|
|
110
126
|
|
|
@@ -119,7 +135,8 @@ module Coradoc
|
|
|
119
135
|
Model::Audio.new(
|
|
120
136
|
src: path.to_s,
|
|
121
137
|
attributes: attribute_list,
|
|
122
|
-
line_break: line_break
|
|
138
|
+
line_break: line_break,
|
|
139
|
+
source_line: SourceLineExtractor.extract(path)
|
|
123
140
|
)
|
|
124
141
|
end
|
|
125
142
|
|
|
@@ -134,7 +151,8 @@ module Coradoc
|
|
|
134
151
|
Model::Video.new(
|
|
135
152
|
src: path.to_s,
|
|
136
153
|
attributes: attribute_list,
|
|
137
|
-
line_break: line_break
|
|
154
|
+
line_break: line_break,
|
|
155
|
+
source_line: SourceLineExtractor.extract(path)
|
|
138
156
|
)
|
|
139
157
|
end
|
|
140
158
|
|
|
@@ -162,7 +180,8 @@ module Coradoc
|
|
|
162
180
|
date: attrs[:date],
|
|
163
181
|
from: attrs[:from],
|
|
164
182
|
to: attrs[:to],
|
|
165
|
-
content: Transformer.lines_to_text_elements(lines)
|
|
183
|
+
content: Transformer.lines_to_text_elements(lines),
|
|
184
|
+
source_line: SourceLineExtractor.extract(reviewer_note)
|
|
166
185
|
)
|
|
167
186
|
end
|
|
168
187
|
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'parslet'
|
|
4
|
+
|
|
5
|
+
module Coradoc
|
|
6
|
+
module AsciiDoc
|
|
7
|
+
class Transformer < Parslet::Transform
|
|
8
|
+
# Walks a Parslet AST subtree (Hash/Array/Slice/String/Model) and
|
|
9
|
+
# returns the 1-indexed source line of the first +Parslet::Slice+
|
|
10
|
+
# or Model::Base#source_line it finds.
|
|
11
|
+
#
|
|
12
|
+
# Parslet preserves byte offsets on every matched slice
|
|
13
|
+
# (+slice.line_and_column+ returns +[line, column]+); the
|
|
14
|
+
# transformer receives these slices in the AST but most rules
|
|
15
|
+
# discard the position when building Model objects. This helper
|
|
16
|
+
# recovers the start line of any subtree so transformer rules
|
|
17
|
+
# can populate +Model::Base#source_line+ without changing the
|
|
18
|
+
# parser.
|
|
19
|
+
#
|
|
20
|
+
# Handles two distinct shapes:
|
|
21
|
+
# * Pre-transform AST (Hash/Array of Parslet::Slice) — used by
|
|
22
|
+
# rules that bind raw slices via +simple(:x)+.
|
|
23
|
+
# * Post-transform Model tree (Model::Base instances whose
|
|
24
|
+
# +source_line+ was populated by an earlier rule) — used by
|
|
25
|
+
# rules that bind via +subtree(:x)+ and receive already-
|
|
26
|
+
# transformed content.
|
|
27
|
+
#
|
|
28
|
+
# Returns nil when no position is found (programmatic input,
|
|
29
|
+
# already-stripped ASTs, etc.) — callers should treat nil as
|
|
30
|
+
# "source position unavailable".
|
|
31
|
+
module SourceLineExtractor
|
|
32
|
+
module_function
|
|
33
|
+
|
|
34
|
+
def extract(node)
|
|
35
|
+
case node
|
|
36
|
+
when Parslet::Slice then line_of(node)
|
|
37
|
+
when Coradoc::AsciiDoc::Model::Base then node.source_line
|
|
38
|
+
when Hash then extract_from_hash(node)
|
|
39
|
+
when Array then extract_from_array(node)
|
|
40
|
+
else nil
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def line_of(slice)
|
|
45
|
+
line, _column = slice.line_and_column
|
|
46
|
+
line
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def extract_from_hash(hash)
|
|
50
|
+
hash.each_value do |value|
|
|
51
|
+
line = extract(value)
|
|
52
|
+
return line if line
|
|
53
|
+
end
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def extract_from_array(array)
|
|
58
|
+
array.each do |value|
|
|
59
|
+
line = extract(value)
|
|
60
|
+
return line if line
|
|
61
|
+
end
|
|
62
|
+
nil
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -72,6 +72,7 @@ module Coradoc
|
|
|
72
72
|
opts = { rows: Transformer.regroup_table_rows(rows, attrs), attrs: attrs }
|
|
73
73
|
opts[:id] = id if id
|
|
74
74
|
opts[:title] = title unless title.nil? || title.empty?
|
|
75
|
+
opts[:source_line] = SourceLineExtractor.extract(table)
|
|
75
76
|
Model::Table.new(**opts)
|
|
76
77
|
end
|
|
77
78
|
|
|
@@ -84,7 +85,8 @@ module Coradoc
|
|
|
84
85
|
Model::Title.new(
|
|
85
86
|
content: text,
|
|
86
87
|
level_int: level.size - 1,
|
|
87
|
-
line_break: line_break
|
|
88
|
+
line_break: line_break,
|
|
89
|
+
source_line: SourceLineExtractor.extract(level)
|
|
88
90
|
)
|
|
89
91
|
end
|
|
90
92
|
|
|
@@ -98,7 +100,8 @@ module Coradoc
|
|
|
98
100
|
content: text,
|
|
99
101
|
level_int: level.size - 1,
|
|
100
102
|
line_break: line_break,
|
|
101
|
-
id: name
|
|
103
|
+
id: name,
|
|
104
|
+
source_line: SourceLineExtractor.extract(name)
|
|
102
105
|
)
|
|
103
106
|
end
|
|
104
107
|
|
|
@@ -117,7 +120,8 @@ module Coradoc
|
|
|
117
120
|
id: id,
|
|
118
121
|
attribute_list: attribute_list,
|
|
119
122
|
contents: contents,
|
|
120
|
-
sections: sections
|
|
123
|
+
sections: sections,
|
|
124
|
+
source_line: SourceLineExtractor.extract(section)
|
|
121
125
|
)
|
|
122
126
|
end
|
|
123
127
|
|
|
@@ -9,7 +9,10 @@ module Coradoc
|
|
|
9
9
|
transformer_class.class_eval do
|
|
10
10
|
# Text Model
|
|
11
11
|
rule(text: simple(:text)) do
|
|
12
|
-
Model::TextElement.new(
|
|
12
|
+
Model::TextElement.new(
|
|
13
|
+
content: text.to_s,
|
|
14
|
+
source_line: Transformer::SourceLineExtractor.extract(text)
|
|
15
|
+
)
|
|
13
16
|
end
|
|
14
17
|
|
|
15
18
|
rule(text_string: subtree(:text_string)) do
|
|
@@ -17,19 +20,34 @@ module Coradoc
|
|
|
17
20
|
end
|
|
18
21
|
|
|
19
22
|
rule(text: simple(:text), line_break: simple(:line_break)) do
|
|
20
|
-
Model::TextElement.new(
|
|
23
|
+
Model::TextElement.new(
|
|
24
|
+
content: text.to_s,
|
|
25
|
+
line_break: line_break,
|
|
26
|
+
source_line: Transformer::SourceLineExtractor.extract(text)
|
|
27
|
+
)
|
|
21
28
|
end
|
|
22
29
|
|
|
23
30
|
rule(text: sequence(:text), line_break: simple(:line_break)) do
|
|
24
|
-
Model::TextElement.new(
|
|
31
|
+
Model::TextElement.new(
|
|
32
|
+
content: text,
|
|
33
|
+
line_break: line_break,
|
|
34
|
+
source_line: Transformer::SourceLineExtractor.extract(text)
|
|
35
|
+
)
|
|
25
36
|
end
|
|
26
37
|
|
|
27
38
|
rule(id: simple(:id), text: simple(:text)) do
|
|
28
|
-
Model::TextElement.new(
|
|
39
|
+
Model::TextElement.new(
|
|
40
|
+
content: text.to_s,
|
|
41
|
+
id: id.to_s,
|
|
42
|
+
source_line: Transformer::SourceLineExtractor.extract(id)
|
|
43
|
+
)
|
|
29
44
|
end
|
|
30
45
|
|
|
31
46
|
rule(text: sequence(:text)) do
|
|
32
|
-
Model::TextElement.new(
|
|
47
|
+
Model::TextElement.new(
|
|
48
|
+
content: text,
|
|
49
|
+
source_line: Transformer::SourceLineExtractor.extract(text)
|
|
50
|
+
)
|
|
33
51
|
end
|
|
34
52
|
|
|
35
53
|
rule(
|
|
@@ -40,7 +58,8 @@ module Coradoc
|
|
|
40
58
|
Model::TextElement.new(
|
|
41
59
|
content: text.to_s,
|
|
42
60
|
id: id.to_s,
|
|
43
|
-
line_break: line_break
|
|
61
|
+
line_break: line_break,
|
|
62
|
+
source_line: Transformer::SourceLineExtractor.extract(id)
|
|
44
63
|
)
|
|
45
64
|
end
|
|
46
65
|
|
|
@@ -52,13 +71,17 @@ module Coradoc
|
|
|
52
71
|
Model::TextElement.new(
|
|
53
72
|
content: text,
|
|
54
73
|
id: id.to_s,
|
|
55
|
-
line_break: line_break
|
|
74
|
+
line_break: line_break,
|
|
75
|
+
source_line: Transformer::SourceLineExtractor.extract(id)
|
|
56
76
|
)
|
|
57
77
|
end
|
|
58
78
|
|
|
59
79
|
# Line break
|
|
60
80
|
rule(line_break: simple(:line_break)) do
|
|
61
|
-
Model::LineBreak.new(
|
|
81
|
+
Model::LineBreak.new(
|
|
82
|
+
line_break:,
|
|
83
|
+
source_line: Transformer::SourceLineExtractor.extract(line_break)
|
|
84
|
+
)
|
|
62
85
|
end
|
|
63
86
|
|
|
64
87
|
# Unparsed text
|
|
@@ -72,7 +95,8 @@ module Coradoc
|
|
|
72
95
|
content: Transformer.lines_to_text_elements(paragraph[:lines]),
|
|
73
96
|
id: paragraph[:id],
|
|
74
97
|
attributes: paragraph[:attribute_list],
|
|
75
|
-
title: paragraph[:title]
|
|
98
|
+
title: paragraph[:title],
|
|
99
|
+
source_line: Transformer::SourceLineExtractor.extract(paragraph)
|
|
76
100
|
)
|
|
77
101
|
end
|
|
78
102
|
end
|
|
@@ -33,6 +33,7 @@ module Coradoc
|
|
|
33
33
|
autoload :BlockTypeClassifier, "#{__dir__}/transformer/block_type_classifier"
|
|
34
34
|
autoload :TableLayout, "#{__dir__}/transformer/table_layout"
|
|
35
35
|
autoload :TableCellBuilder, "#{__dir__}/transformer/table_cell_builder"
|
|
36
|
+
autoload :SourceLineExtractor, "#{__dir__}/transformer/source_line_extractor"
|
|
36
37
|
|
|
37
38
|
# Apply all rule modules (triggers autoload)
|
|
38
39
|
HeaderRules.apply(self)
|
|
@@ -139,7 +140,11 @@ module Coradoc
|
|
|
139
140
|
text_content
|
|
140
141
|
end
|
|
141
142
|
|
|
142
|
-
Model::TextElement.new(
|
|
143
|
+
Model::TextElement.new(
|
|
144
|
+
content: transformed,
|
|
145
|
+
line_break: line[:line_break],
|
|
146
|
+
source_line: SourceLineExtractor.extract(line)
|
|
147
|
+
)
|
|
143
148
|
end
|
|
144
149
|
end
|
|
145
150
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: coradoc-adoc
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.
|
|
4
|
+
version: 2.0.24
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -343,6 +343,7 @@ files:
|
|
|
343
343
|
- lib/coradoc/asciidoc/transformer/inline_rules.rb
|
|
344
344
|
- lib/coradoc/asciidoc/transformer/list_rules.rb
|
|
345
345
|
- lib/coradoc/asciidoc/transformer/misc_rules.rb
|
|
346
|
+
- lib/coradoc/asciidoc/transformer/source_line_extractor.rb
|
|
346
347
|
- lib/coradoc/asciidoc/transformer/structural_rules.rb
|
|
347
348
|
- lib/coradoc/asciidoc/transformer/table_cell_builder.rb
|
|
348
349
|
- lib/coradoc/asciidoc/transformer/table_layout.rb
|