coradoc-adoc 2.0.23 → 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 +4 -4
- data/lib/coradoc/asciidoc/builder/detection.rb +4 -4
- data/lib/coradoc/asciidoc/model/attribute_list/matchers.rb +4 -4
- data/lib/coradoc/asciidoc/model/base.rb +7 -0
- data/lib/coradoc/asciidoc/model/block/core.rb +1 -1
- data/lib/coradoc/asciidoc/model/content_list.rb +8 -8
- data/lib/coradoc/asciidoc/model/inline/bold.rb +1 -1
- data/lib/coradoc/asciidoc/model/inline/highlight.rb +1 -1
- data/lib/coradoc/asciidoc/model/inline/italic.rb +1 -1
- data/lib/coradoc/asciidoc/model/inline/monospace.rb +1 -1
- data/lib/coradoc/asciidoc/model/inline/strikethrough.rb +2 -2
- data/lib/coradoc/asciidoc/model/inline/subscript.rb +1 -1
- data/lib/coradoc/asciidoc/model/inline/superscript.rb +1 -1
- data/lib/coradoc/asciidoc/model/serialization/asciidoc_mapping.rb +3 -4
- data/lib/coradoc/asciidoc/model/text_element.rb +22 -0
- data/lib/coradoc/asciidoc/parser/block.rb +3 -3
- 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 +6 -30
- data/lib/coradoc/asciidoc/transform/inline_transform_visitor.rb +28 -5
- data/lib/coradoc/asciidoc/transform/to_core_model.rb +30 -6
- data/lib/coradoc/asciidoc/transformer/block_rules.rb +8 -2
- data/lib/coradoc/asciidoc/transformer/block_type_classifier.rb +10 -10
- data/lib/coradoc/asciidoc/transformer/header_rules.rb +9 -3
- data/lib/coradoc/asciidoc/transformer/inline_rules.rb +39 -15
- data/lib/coradoc/asciidoc/transformer/list_rules.rb +14 -4
- data/lib/coradoc/asciidoc/transformer/misc_rules.rb +14 -5
- data/lib/coradoc/asciidoc/transformer/source_line_extractor.rb +66 -0
- data/lib/coradoc/asciidoc/transformer/table_layout.rb +1 -3
- data/lib/coradoc/asciidoc/transformer/text_rules.rb +21 -6
- data/lib/coradoc/asciidoc/transformer.rb +30 -1
- data/lib/coradoc/asciidoc/version.rb +1 -1
- metadata +2 -1
|
@@ -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
|
|
@@ -327,8 +300,11 @@ module Coradoc
|
|
|
327
300
|
end
|
|
328
301
|
|
|
329
302
|
def transform_image(image)
|
|
330
|
-
target_class = image.inline
|
|
331
|
-
|
|
303
|
+
target_class = if image.inline
|
|
304
|
+
Coradoc::AsciiDoc::Model::Image::InlineImage
|
|
305
|
+
else
|
|
306
|
+
Coradoc::AsciiDoc::Model::Image::BlockImage
|
|
307
|
+
end
|
|
332
308
|
target_class.new(**image_attributes(image))
|
|
333
309
|
end
|
|
334
310
|
|
|
@@ -35,21 +35,44 @@ 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
|
+
# Two adjacent TextElements in the content array indicate the
|
|
66
|
+
# parser split a paragraph across source lines. Join them with a
|
|
67
|
+
# space (soft break) unless the second one carries a hard-break
|
|
68
|
+
# marker — the model owns that predicate, so this method does
|
|
69
|
+
# not reach into parser-specific line_break string values.
|
|
70
|
+
def soft_break_before?(previous, current)
|
|
71
|
+
previous.is_a?(Model::TextElement) &&
|
|
72
|
+
current.is_a?(Model::TextElement) &&
|
|
73
|
+
!current.hard_break?
|
|
74
|
+
end
|
|
75
|
+
|
|
53
76
|
def visit_model(model)
|
|
54
77
|
@to_core_model.transform(model)
|
|
55
78
|
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)
|
|
@@ -34,7 +34,10 @@ module Coradoc
|
|
|
34
34
|
|
|
35
35
|
# Example
|
|
36
36
|
rule(example: sequence(:example)) do
|
|
37
|
-
Model::Block::Example.new(
|
|
37
|
+
Model::Block::Example.new(
|
|
38
|
+
title: '',
|
|
39
|
+
lines: example
|
|
40
|
+
)
|
|
38
41
|
end
|
|
39
42
|
|
|
40
43
|
# Admonition. Canonicalise the type to uppercase so round-trips
|
|
@@ -45,7 +48,10 @@ module Coradoc
|
|
|
45
48
|
content: sequence(:content)
|
|
46
49
|
) do
|
|
47
50
|
canonical = Coradoc::AsciiDoc::Transform::ElementTransformers::AdmonitionStyles.canonicalize(admonition_type.to_s)
|
|
48
|
-
Model::Admonition.new(
|
|
51
|
+
Model::Admonition.new(
|
|
52
|
+
content: content,
|
|
53
|
+
type: canonical
|
|
54
|
+
)
|
|
49
55
|
end
|
|
50
56
|
|
|
51
57
|
# Block image
|
|
@@ -16,23 +16,23 @@ module Coradoc
|
|
|
16
16
|
# returning a Model::Block::* instance. `max_length` nil means
|
|
17
17
|
# unbounded.
|
|
18
18
|
DELIMITER_CLASSIFICATIONS = [
|
|
19
|
-
['*', 4, nil,
|
|
19
|
+
['*', 4, nil, lambda { |opts, attrs|
|
|
20
20
|
if attrs && attrs.positional == [] && attrs.named.first&.name == 'reviewer'
|
|
21
|
-
Model::Block::ReviewerComment.new(**opts
|
|
21
|
+
Model::Block::ReviewerComment.new(**opts, attributes: attrs)
|
|
22
22
|
else
|
|
23
|
-
Model::Block::Side.new(**opts
|
|
23
|
+
Model::Block::Side.new(**opts, attributes: attrs)
|
|
24
24
|
end
|
|
25
25
|
}],
|
|
26
|
-
['=', 4, nil, ->(opts, attrs) { Model::Block::Example.new(**opts
|
|
27
|
-
['+', 4, nil, ->(opts, attrs) { Model::Block::Pass.new(**opts
|
|
28
|
-
['.', 4, nil, ->(opts, attrs) { Model::Block::Literal.new(**opts
|
|
29
|
-
['_', 4, nil, ->(opts, attrs) { Model::Block::Quote.new(**opts
|
|
30
|
-
['-', 4, nil, ->(opts, attrs) { Model::Block::SourceCode.new(**opts
|
|
31
|
-
['-', 2, 2,
|
|
26
|
+
['=', 4, nil, ->(opts, attrs) { Model::Block::Example.new(**opts, attributes: attrs) }],
|
|
27
|
+
['+', 4, nil, ->(opts, attrs) { Model::Block::Pass.new(**opts, attributes: attrs) }],
|
|
28
|
+
['.', 4, nil, ->(opts, attrs) { Model::Block::Literal.new(**opts, attributes: attrs) }],
|
|
29
|
+
['_', 4, nil, ->(opts, attrs) { Model::Block::Quote.new(**opts, attributes: attrs) }],
|
|
30
|
+
['-', 4, nil, ->(opts, attrs) { Model::Block::SourceCode.new(**opts, attributes: attrs) }],
|
|
31
|
+
['-', 2, 2, ->(opts, attrs) { Model::Block::Open.new(**opts, attributes: attrs) }],
|
|
32
32
|
# Markdown-style triple-backtick fence: behaves as a SourceCode
|
|
33
33
|
# block. The language tag parsed from the opening fence is passed
|
|
34
34
|
# through opts[:lang]; extract_block_language prefers block.lang.
|
|
35
|
-
['`', 3, nil,
|
|
35
|
+
['`', 3, nil, lambda { |opts, attrs|
|
|
36
36
|
model_opts = opts.merge(attributes: attrs, delimiter_char: '`')
|
|
37
37
|
model_opts[:lang] = opts[:lang] if opts.key?(:lang)
|
|
38
38
|
Model::Block::SourceCode.new(**model_opts)
|
|
@@ -21,7 +21,9 @@ 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
|
+
)
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
rule(header: simple(:header)) do
|
|
@@ -34,7 +36,9 @@ module Coradoc
|
|
|
34
36
|
last_name: simple(:last_name),
|
|
35
37
|
email: simple(:email)
|
|
36
38
|
) do
|
|
37
|
-
Model::Author.new(
|
|
39
|
+
Model::Author.new(
|
|
40
|
+
first_name:, last_name:, email:, middle_name: nil
|
|
41
|
+
)
|
|
38
42
|
end
|
|
39
43
|
|
|
40
44
|
# Revision
|
|
@@ -43,7 +47,9 @@ module Coradoc
|
|
|
43
47
|
date: simple(:date),
|
|
44
48
|
remark: simple(:remark)
|
|
45
49
|
) do
|
|
46
|
-
Model::Revision.new(
|
|
50
|
+
Model::Revision.new(
|
|
51
|
+
number:, date:, remark:
|
|
52
|
+
)
|
|
47
53
|
end
|
|
48
54
|
end
|
|
49
55
|
end
|
|
@@ -10,8 +10,8 @@ module Coradoc
|
|
|
10
10
|
# `span` is excluded because it carries `text:` + `attributes:`
|
|
11
11
|
# rather than `content:`, so it gets its own pair of rules.
|
|
12
12
|
FORMATTING_VARIANTS = [
|
|
13
|
-
%i[bold
|
|
14
|
-
%i[italic
|
|
13
|
+
%i[bold Bold],
|
|
14
|
+
%i[italic Italic],
|
|
15
15
|
%i[highlight Highlight],
|
|
16
16
|
%i[monospace Monospace]
|
|
17
17
|
].freeze
|
|
@@ -31,7 +31,9 @@ module Coradoc
|
|
|
31
31
|
href = xref[:href].to_s
|
|
32
32
|
text = xref[:text]
|
|
33
33
|
args = text ? [text.to_s] : []
|
|
34
|
-
Model::Inline::CrossReference.new(
|
|
34
|
+
Model::Inline::CrossReference.new(
|
|
35
|
+
href:, args:
|
|
36
|
+
)
|
|
35
37
|
end
|
|
36
38
|
|
|
37
39
|
# Inline image
|
|
@@ -60,7 +62,9 @@ module Coradoc
|
|
|
60
62
|
|
|
61
63
|
# Attribute reference
|
|
62
64
|
rule(attribute_reference: simple(:name)) do
|
|
63
|
-
Model::Inline::AttributeReference.new(
|
|
65
|
+
Model::Inline::AttributeReference.new(
|
|
66
|
+
name:
|
|
67
|
+
)
|
|
64
68
|
end
|
|
65
69
|
|
|
66
70
|
# Hard line break (` +\n` or `\\n`). Emitted as a dedicated
|
|
@@ -68,7 +72,7 @@ module Coradoc
|
|
|
68
72
|
# Model::LineBreak, which only represents paragraph-separator
|
|
69
73
|
# blank lines. Hard breaks carry semantic meaning: HTML/Markdown
|
|
70
74
|
# renderers map them to <br>.
|
|
71
|
-
rule(hard_line_break: simple(:
|
|
75
|
+
rule(hard_line_break: simple(:hard_line_break)) do
|
|
72
76
|
Model::Inline::HardLineBreak.new
|
|
73
77
|
end
|
|
74
78
|
|
|
@@ -77,30 +81,40 @@ module Coradoc
|
|
|
77
81
|
term_type: simple(:term_type),
|
|
78
82
|
term: simple(:term)
|
|
79
83
|
) do
|
|
80
|
-
Coradoc::AsciiDoc::Model::Term.new(
|
|
84
|
+
Coradoc::AsciiDoc::Model::Term.new(
|
|
85
|
+
term:, type: term_type, lang: :en
|
|
86
|
+
)
|
|
81
87
|
end
|
|
82
88
|
|
|
83
89
|
# Footnote
|
|
84
90
|
rule(footnote: simple(:footnote)) do
|
|
85
91
|
text_str = footnote.to_s
|
|
86
|
-
Coradoc::AsciiDoc::Model::Inline::Footnote.new(
|
|
92
|
+
Coradoc::AsciiDoc::Model::Inline::Footnote.new(
|
|
93
|
+
text: text_str
|
|
94
|
+
)
|
|
87
95
|
end
|
|
88
96
|
|
|
89
97
|
rule(footnote: simple(:footnote), id: simple(:id)) do
|
|
90
98
|
text_str = footnote.to_s
|
|
91
|
-
Coradoc::AsciiDoc::Model::Inline::Footnote.new(
|
|
99
|
+
Coradoc::AsciiDoc::Model::Inline::Footnote.new(
|
|
100
|
+
text: text_str, id: id.to_s
|
|
101
|
+
)
|
|
92
102
|
end
|
|
93
103
|
|
|
94
104
|
# Footnote with empty content (reference to named footnote)
|
|
95
105
|
rule(footnote: sequence(:footnote), id: simple(:id)) do
|
|
96
106
|
text_str = footnote.map(&:to_s).join
|
|
97
|
-
Coradoc::AsciiDoc::Model::Inline::Footnote.new(
|
|
107
|
+
Coradoc::AsciiDoc::Model::Inline::Footnote.new(
|
|
108
|
+
text: text_str, id: id.to_s
|
|
109
|
+
)
|
|
98
110
|
end
|
|
99
111
|
|
|
100
112
|
# Footnote with empty content and no id
|
|
101
113
|
rule(footnote: sequence(:footnote)) do
|
|
102
114
|
text_str = footnote.map(&:to_s).join
|
|
103
|
-
Coradoc::AsciiDoc::Model::Inline::Footnote.new(
|
|
115
|
+
Coradoc::AsciiDoc::Model::Inline::Footnote.new(
|
|
116
|
+
text: text_str
|
|
117
|
+
)
|
|
104
118
|
end
|
|
105
119
|
|
|
106
120
|
# Inline formatting rules generated from a single registry.
|
|
@@ -114,11 +128,15 @@ module Coradoc
|
|
|
114
128
|
|
|
115
129
|
rule(constrained_key => subtree(:subtree)) do
|
|
116
130
|
content = Transformer.extract_inline_content(subtree)
|
|
117
|
-
klass.new(
|
|
131
|
+
klass.new(
|
|
132
|
+
content: content, unconstrained: false
|
|
133
|
+
)
|
|
118
134
|
end
|
|
119
135
|
rule(unconstrained_key => subtree(:subtree)) do
|
|
120
136
|
content = Transformer.extract_inline_content(subtree)
|
|
121
|
-
klass.new(
|
|
137
|
+
klass.new(
|
|
138
|
+
content: content, unconstrained: true
|
|
139
|
+
)
|
|
122
140
|
end
|
|
123
141
|
end
|
|
124
142
|
|
|
@@ -143,18 +161,24 @@ module Coradoc
|
|
|
143
161
|
# Superscript
|
|
144
162
|
rule(superscript: subtree(:superscript)) do
|
|
145
163
|
content = Transformer.extract_simple_inline_content(superscript)
|
|
146
|
-
Model::Inline::Superscript.new(
|
|
164
|
+
Model::Inline::Superscript.new(
|
|
165
|
+
content:
|
|
166
|
+
)
|
|
147
167
|
end
|
|
148
168
|
|
|
149
169
|
# Subscript
|
|
150
170
|
rule(subscript: subtree(:subscript)) do
|
|
151
171
|
content = Transformer.extract_simple_inline_content(subscript)
|
|
152
|
-
Model::Inline::Subscript.new(
|
|
172
|
+
Model::Inline::Subscript.new(
|
|
173
|
+
content:
|
|
174
|
+
)
|
|
153
175
|
end
|
|
154
176
|
|
|
155
177
|
# Highlight (simple)
|
|
156
178
|
rule(highlight: simple(:text)) do
|
|
157
|
-
Model::Highlight.new(
|
|
179
|
+
Model::Highlight.new(
|
|
180
|
+
content: text
|
|
181
|
+
)
|
|
158
182
|
end
|
|
159
183
|
# Stem
|
|
160
184
|
rule(stem: subtree(:stem)) do
|
|
@@ -85,26 +85,36 @@ module Coradoc
|
|
|
85
85
|
|
|
86
86
|
# Unordered list
|
|
87
87
|
rule(unordered: sequence(:list_items)) do
|
|
88
|
-
Model::List::Unordered.new(
|
|
88
|
+
Model::List::Unordered.new(
|
|
89
|
+
items: list_items
|
|
90
|
+
)
|
|
89
91
|
end
|
|
90
92
|
|
|
91
93
|
rule(
|
|
92
94
|
attribute_list: simple(:attribute_list),
|
|
93
95
|
unordered: sequence(:list_items)
|
|
94
96
|
) do
|
|
95
|
-
Model::List::Unordered.new(
|
|
97
|
+
Model::List::Unordered.new(
|
|
98
|
+
items: list_items,
|
|
99
|
+
attrs: attribute_list
|
|
100
|
+
)
|
|
96
101
|
end
|
|
97
102
|
|
|
98
103
|
# Ordered list
|
|
99
104
|
rule(ordered: sequence(:list_items)) do
|
|
100
|
-
Model::List::Ordered.new(
|
|
105
|
+
Model::List::Ordered.new(
|
|
106
|
+
items: list_items
|
|
107
|
+
)
|
|
101
108
|
end
|
|
102
109
|
|
|
103
110
|
rule(
|
|
104
111
|
attribute_list: simple(:attribute_list),
|
|
105
112
|
ordered: sequence(:list_items)
|
|
106
113
|
) do
|
|
107
|
-
Model::List::Ordered.new(
|
|
114
|
+
Model::List::Ordered.new(
|
|
115
|
+
items: list_items,
|
|
116
|
+
attrs: attribute_list
|
|
117
|
+
)
|
|
108
118
|
end
|
|
109
119
|
|
|
110
120
|
# Definition list term (with optional anchor)
|
|
@@ -12,11 +12,16 @@ 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
|
+
)
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
rule(comment_block: { comment_text: simple(:comment_text) }) do
|
|
19
|
-
Model::CommentBlock.new(
|
|
22
|
+
Model::CommentBlock.new(
|
|
23
|
+
text: comment_text
|
|
24
|
+
)
|
|
20
25
|
end
|
|
21
26
|
|
|
22
27
|
# Page break
|
|
@@ -45,7 +50,7 @@ module Coradoc
|
|
|
45
50
|
end
|
|
46
51
|
|
|
47
52
|
rule(positional: simple(:positional)) do
|
|
48
|
-
positional
|
|
53
|
+
positional
|
|
49
54
|
end
|
|
50
55
|
|
|
51
56
|
rule(attribute_array: nil) do
|
|
@@ -54,10 +59,14 @@ module Coradoc
|
|
|
54
59
|
|
|
55
60
|
rule(attribute_array: sequence(:attributes)) do
|
|
56
61
|
attr_list = Model::AttributeList.new
|
|
62
|
+
|
|
57
63
|
attributes.each do |a|
|
|
58
|
-
|
|
64
|
+
case a
|
|
65
|
+
when Parslet::Slice
|
|
66
|
+
attr_list.add_positional(a.to_s)
|
|
67
|
+
when String
|
|
59
68
|
attr_list.add_positional(a)
|
|
60
|
-
|
|
69
|
+
when Model::Attribute
|
|
61
70
|
attr_list.add_named(a.key, a.value)
|
|
62
71
|
end
|
|
63
72
|
end
|
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def line_of(slice)
|
|
44
|
+
line, _column = slice.line_and_column
|
|
45
|
+
line
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def extract_from_hash(hash)
|
|
49
|
+
hash.each_value do |value|
|
|
50
|
+
line = extract(value)
|
|
51
|
+
return line if line
|
|
52
|
+
end
|
|
53
|
+
nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def extract_from_array(array)
|
|
57
|
+
array.each do |value|
|
|
58
|
+
line = extract(value)
|
|
59
|
+
return line if line
|
|
60
|
+
end
|
|
61
|
+
nil
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -46,9 +46,7 @@ module Coradoc
|
|
|
46
46
|
normalized_cells = cells.map { |cell| TableCellBuilder.normalize_cell(cell) }
|
|
47
47
|
|
|
48
48
|
col_count = explicit_col_count
|
|
49
|
-
if col_count.nil? || col_count.zero?
|
|
50
|
-
col_count = infer_column_count(normalized_cells)
|
|
51
|
-
end
|
|
49
|
+
col_count = infer_column_count(normalized_cells) if col_count.nil? || col_count.zero?
|
|
52
50
|
col_count = normalized_cells.size if col_count.nil? || col_count.zero?
|
|
53
51
|
|
|
54
52
|
rows = []
|
|
@@ -9,7 +9,9 @@ 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
|
+
)
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
rule(text_string: subtree(:text_string)) do
|
|
@@ -17,19 +19,30 @@ module Coradoc
|
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
rule(text: simple(:text), line_break: simple(:line_break)) do
|
|
20
|
-
Model::TextElement.new(
|
|
22
|
+
Model::TextElement.new(
|
|
23
|
+
content: text.to_s,
|
|
24
|
+
line_break: line_break
|
|
25
|
+
)
|
|
21
26
|
end
|
|
22
27
|
|
|
23
28
|
rule(text: sequence(:text), line_break: simple(:line_break)) do
|
|
24
|
-
Model::TextElement.new(
|
|
29
|
+
Model::TextElement.new(
|
|
30
|
+
content: text,
|
|
31
|
+
line_break: line_break
|
|
32
|
+
)
|
|
25
33
|
end
|
|
26
34
|
|
|
27
35
|
rule(id: simple(:id), text: simple(:text)) do
|
|
28
|
-
Model::TextElement.new(
|
|
36
|
+
Model::TextElement.new(
|
|
37
|
+
content: text.to_s,
|
|
38
|
+
id: id.to_s
|
|
39
|
+
)
|
|
29
40
|
end
|
|
30
41
|
|
|
31
42
|
rule(text: sequence(:text)) do
|
|
32
|
-
Model::TextElement.new(
|
|
43
|
+
Model::TextElement.new(
|
|
44
|
+
content: text
|
|
45
|
+
)
|
|
33
46
|
end
|
|
34
47
|
|
|
35
48
|
rule(
|
|
@@ -58,7 +71,9 @@ module Coradoc
|
|
|
58
71
|
|
|
59
72
|
# Line break
|
|
60
73
|
rule(line_break: simple(:line_break)) do
|
|
61
|
-
Model::LineBreak.new(
|
|
74
|
+
Model::LineBreak.new(
|
|
75
|
+
line_break:
|
|
76
|
+
)
|
|
62
77
|
end
|
|
63
78
|
|
|
64
79
|
# Unparsed text
|