coradoc-adoc 2.0.21 → 2.0.22
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/parser/admonition.rb +15 -6
- data/lib/coradoc/asciidoc/parser/inline.rb +39 -4
- data/lib/coradoc/asciidoc/parser/list.rb +7 -2
- data/lib/coradoc/asciidoc/transform/element_transformers/admonition_styles.rb +15 -0
- data/lib/coradoc/asciidoc/transform/element_transformers/block_transformer.rb +39 -29
- data/lib/coradoc/asciidoc/transform/element_transformers/document_transformer.rb +11 -0
- data/lib/coradoc/asciidoc/transform/element_transformers/list_transformer.rb +19 -11
- data/lib/coradoc/asciidoc/transform/element_transformers/other_transformer.rb +2 -1
- data/lib/coradoc/asciidoc/transform/from_core_model.rb +2 -0
- data/lib/coradoc/asciidoc/transform/to_core_model_registrations.rb +13 -1
- data/lib/coradoc/asciidoc/transformer/block_rules.rb +5 -2
- data/lib/coradoc/asciidoc/transformer/inline_rules.rb +15 -3
- data/lib/coradoc/asciidoc/transformer/table_cell_builder.rb +13 -1
- data/lib/coradoc/asciidoc/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 42db73896b69e9e5344105625afe528bd2b7b8f57a11822f61881e782be8b974
|
|
4
|
+
data.tar.gz: bc3e5e3073992f0d2b71e12ab62fa07d11cdf6ded25299d673f863522cd6291d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 334c5ae5f1860ccde94bb518e8bc7f91be7dcce338d863d93ec137acbfedce8f742be44336fde3e4cec7ea6709b3f309c484c716ceb8c485da2830312515d59e
|
|
7
|
+
data.tar.gz: 3f387aab6c8b6d8862319593b9c2b8c2304758c4c66f15aed0e5ac57209f2f35b4930d1d95a57b2cf4d940834599db35395546c81b8d7760384062d0a7a48d49
|
|
@@ -4,13 +4,22 @@ module Coradoc
|
|
|
4
4
|
module AsciiDoc
|
|
5
5
|
module Parser
|
|
6
6
|
module Admonition
|
|
7
|
+
# Match a single style name case-insensitively. Each character
|
|
8
|
+
# class `[Xx]` lets the same alternation accept +note+, +Note+,
|
|
9
|
+
# and +NOTE+ without three separate str() branches per style.
|
|
10
|
+
def case_insensitive_str(s)
|
|
11
|
+
s.chars.reduce(nil) do |acc, ch|
|
|
12
|
+
node = match("[#{ch.upcase}#{ch.downcase}]")
|
|
13
|
+
acc.nil? ? node : (acc >> node)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
7
17
|
def admonition_type
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# | str('SAFETY PRECAUTION')
|
|
18
|
+
styles = Coradoc::AsciiDoc::Transform::ElementTransformers::AdmonitionStyles.all_styles
|
|
19
|
+
styles.reduce(nil) do |acc, style|
|
|
20
|
+
matcher = case_insensitive_str(style)
|
|
21
|
+
acc.nil? ? matcher : (acc | matcher)
|
|
22
|
+
end
|
|
14
23
|
end
|
|
15
24
|
|
|
16
25
|
def admonition_line
|
|
@@ -120,20 +120,38 @@ module Coradoc
|
|
|
120
120
|
(str('image:').present? >> str('image:') >>
|
|
121
121
|
str(':').absent? >>
|
|
122
122
|
match('[A-Za-z0-9_.\\-:/&?=+,%#~;]+').repeat(1).as(:path) >>
|
|
123
|
-
(
|
|
123
|
+
attribute_list(:attribute_list).maybe
|
|
124
124
|
).as(:inline_image)
|
|
125
125
|
end
|
|
126
126
|
|
|
127
127
|
# Triple-plus inline passthrough: `+++raw content+++`. The content
|
|
128
128
|
# passes through all substitutions verbatim. Common use is to embed
|
|
129
129
|
# raw HTML in AsciiDoc documents.
|
|
130
|
-
def
|
|
130
|
+
def inline_passthrough_triple_plus
|
|
131
131
|
(str('+++') >>
|
|
132
132
|
(str('+++').absent? >> match('[^\n]')).repeat(1).as(:raw) >>
|
|
133
133
|
str('+++')
|
|
134
134
|
).as(:inline_passthrough)
|
|
135
135
|
end
|
|
136
136
|
|
|
137
|
+
# `pass:[raw]` macro form. Equivalent semantic to triple-plus:
|
|
138
|
+
# the bracket payload survives all substitutions verbatim. Common
|
|
139
|
+
# use is inside monospace spans to keep characters like `<` from
|
|
140
|
+
# being re-interpreted as xref markers. The optional `subs` segment
|
|
141
|
+
# (`pass:quotes[...]`) is consumed but currently ignored — the
|
|
142
|
+
# payload is always passed through raw.
|
|
143
|
+
def inline_passthrough_macro
|
|
144
|
+
(str('pass:').present? >> str('pass:') >>
|
|
145
|
+
match('[a-zA-Z,+]').repeat(0) >>
|
|
146
|
+
str('[') >> (str(']]').absent? >> match('[^\]\n]')).repeat(1).as(:raw) >>
|
|
147
|
+
str(']')
|
|
148
|
+
).as(:inline_passthrough)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def inline_passthrough
|
|
152
|
+
inline_passthrough_triple_plus | inline_passthrough_macro
|
|
153
|
+
end
|
|
154
|
+
|
|
137
155
|
def underline
|
|
138
156
|
(attribute_list >> match('\\[.underline\\]').as(:role) >>
|
|
139
157
|
str('#') >>
|
|
@@ -157,10 +175,26 @@ module Coradoc
|
|
|
157
175
|
str('link:').present? |
|
|
158
176
|
str('image:').present? |
|
|
159
177
|
str('+++').present? |
|
|
178
|
+
str('pass:').present? |
|
|
160
179
|
term_type.present? |
|
|
161
180
|
str('footnote').present? |
|
|
162
181
|
stem_type.present? |
|
|
163
|
-
str('\\<<').present?
|
|
182
|
+
str('\\<<').present? |
|
|
183
|
+
hard_line_break_marker?
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# AsciiDoc hard line break: a space followed by `+` at end of line,
|
|
187
|
+
# or a backslash at end of line. Both forms render as `<br>` inside
|
|
188
|
+
# the enclosing paragraph/verse. Recognised ahead of `text_unformatted`
|
|
189
|
+
# so the marker isn't swallowed as plain text.
|
|
190
|
+
def hard_line_break_marker?
|
|
191
|
+
(str(' +') >> str("\n")).present? |
|
|
192
|
+
(str('\\') >> str("\n")).present?
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def hard_line_break
|
|
196
|
+
((str(' +') >> str("\n")) |
|
|
197
|
+
(str('\\') >> str("\n"))).as(:hard_line_break)
|
|
164
198
|
end
|
|
165
199
|
|
|
166
200
|
def inline
|
|
@@ -187,7 +221,8 @@ module Coradoc
|
|
|
187
221
|
inline_image |
|
|
188
222
|
inline_passthrough |
|
|
189
223
|
underline |
|
|
190
|
-
small
|
|
224
|
+
small |
|
|
225
|
+
hard_line_break
|
|
191
226
|
end
|
|
192
227
|
|
|
193
228
|
def text_unformatted
|
|
@@ -149,8 +149,13 @@ module Coradoc
|
|
|
149
149
|
end
|
|
150
150
|
|
|
151
151
|
def dlist_definition
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
# AsciiDoc convention: the definition body is indented relative
|
|
153
|
+
# to the term. That leading whitespace is structural (marks
|
|
154
|
+
# the line as a continuation of the dlist item), not content.
|
|
155
|
+
# Consume it without capturing so downstream CoreModel text
|
|
156
|
+
# doesn't carry the source indentation into HTML/Markdown.
|
|
157
|
+
(match('[ \t]').repeat(0) >> text.as(:definition)) >>
|
|
158
|
+
line_ending >> empty_line.repeat(0)
|
|
154
159
|
end
|
|
155
160
|
|
|
156
161
|
def dlist_item(_delimiter = nil)
|
|
@@ -26,6 +26,21 @@ module Coradoc
|
|
|
26
26
|
BUILTIN.include?(name) || custom.include?(name)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
# Canonical uppercase form for +style+, or nil if unknown.
|
|
30
|
+
# Single source of truth for the canonical casing used in
|
|
31
|
+
# CoreModel::AnnotationBlock#annotation_type and AsciiDoc
|
|
32
|
+
# round-trip output.
|
|
33
|
+
def canonicalize(style)
|
|
34
|
+
return nil unless admonition?(style)
|
|
35
|
+
|
|
36
|
+
style.to_s.upcase
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# All registered style names (BUILTIN + custom), lowercased.
|
|
40
|
+
def all_styles
|
|
41
|
+
(BUILTIN + custom).freeze
|
|
42
|
+
end
|
|
43
|
+
|
|
29
44
|
# Register an additional admonition style. Open for extension.
|
|
30
45
|
def register(style)
|
|
31
46
|
name = style.to_s.downcase
|
|
@@ -71,16 +71,26 @@ module Coradoc
|
|
|
71
71
|
|
|
72
72
|
# Dispatch entry point for any block whose AsciiDoc model carries
|
|
73
73
|
# an attribute list (delimited blocks + open blocks). Per the
|
|
74
|
-
# AsciiDoc spec, every delimited block accepts admonition
|
|
75
|
-
# in its attribute line.
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
# semantics are stronger than the annotation label.
|
|
74
|
+
# AsciiDoc spec, every delimited block accepts both admonition
|
|
75
|
+
# labels and verbatim/stem casts in its attribute line. The cast
|
|
76
|
+
# is resolved by +block_cast_semantic+ in MECE order:
|
|
77
|
+
# admonition > stem > verbatim > fallback.
|
|
79
78
|
def transform_with_admonition_check(block, native_class)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
semantic = block_cast_semantic(block)
|
|
80
|
+
case semantic
|
|
81
|
+
when :admonition
|
|
82
|
+
transform_admonition_block(block, first_positional_attr(block))
|
|
83
|
+
when :stem
|
|
84
|
+
transform_stem_block(block, first_positional_attr(block))
|
|
85
|
+
when :source
|
|
86
|
+
transform_source_block(block)
|
|
87
|
+
when :listing
|
|
88
|
+
transform_listing_from_open(block)
|
|
89
|
+
when :literal
|
|
90
|
+
transform_literal_from_open(block)
|
|
91
|
+
else
|
|
92
|
+
transform_typed_block(block, native_class)
|
|
93
|
+
end
|
|
84
94
|
end
|
|
85
95
|
|
|
86
96
|
def transform_example_block(block)
|
|
@@ -98,36 +108,32 @@ module Coradoc
|
|
|
98
108
|
# Open blocks (`--`) are generic containers. AsciiDoc allows
|
|
99
109
|
# casting them to a different block type via positional
|
|
100
110
|
# attributes: verbatim types (`[source]`, `[listing]`,
|
|
101
|
-
# `[literal]`)
|
|
111
|
+
# `[literal]`), STEM types (`[stem]`, `[latexmath]`,
|
|
112
|
+
# `[asciimath]`), and admonition labels (`[NOTE]`, `[TIP]`,
|
|
102
113
|
# `[WARNING]`, `[CAUTION]`, `[IMPORTANT]`). When such a cast
|
|
103
114
|
# is present, the block behaves like the corresponding
|
|
104
115
|
# delimited block. Anything else stays an OpenBlock.
|
|
105
116
|
def transform_open_block(block)
|
|
106
|
-
|
|
107
|
-
case semantic
|
|
108
|
-
when :source
|
|
109
|
-
transform_source_block(block)
|
|
110
|
-
when :listing
|
|
111
|
-
transform_listing_from_open(block)
|
|
112
|
-
when :literal
|
|
113
|
-
transform_literal_from_open(block)
|
|
114
|
-
when :admonition
|
|
115
|
-
transform_admonition_block(block, first_positional_attr(block))
|
|
116
|
-
else
|
|
117
|
-
transform_typed_block(block, Coradoc::CoreModel::OpenBlock)
|
|
118
|
-
end
|
|
117
|
+
transform_with_admonition_check(block, Coradoc::CoreModel::OpenBlock)
|
|
119
118
|
end
|
|
120
119
|
|
|
121
120
|
VERBATILE_CAST_STYLES = %w[source listing literal].freeze
|
|
122
121
|
|
|
123
|
-
|
|
122
|
+
# Resolve a delimited block's cast from its first positional
|
|
123
|
+
# attribute. Single source of truth for the cast ladder used
|
|
124
|
+
# by every block-form transformer (example/sidebar/quote/open).
|
|
125
|
+
# Returns one of: :admonition, :stem, :source, :listing,
|
|
126
|
+
# :literal, or nil (no cast — use the block's native class).
|
|
127
|
+
def block_cast_semantic(block)
|
|
124
128
|
style = first_positional_attr(block)
|
|
125
129
|
return nil unless style
|
|
126
130
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
+
style_str = style.to_s.downcase
|
|
132
|
+
return :admonition if AdmonitionStyles.admonition?(style)
|
|
133
|
+
return :stem if STEM_STYLES.key?(style_str)
|
|
134
|
+
return style_str.to_sym if VERBATILE_CAST_STYLES.include?(style_str)
|
|
135
|
+
|
|
136
|
+
nil
|
|
131
137
|
end
|
|
132
138
|
|
|
133
139
|
# Returns the first positional attribute on the block's
|
|
@@ -148,10 +154,14 @@ module Coradoc
|
|
|
148
154
|
# (example / sidebar / quote / pass / open). Builds an
|
|
149
155
|
# AnnotationBlock with annotation_type set from the style and
|
|
150
156
|
# the block's body lines joined into a single content string.
|
|
157
|
+
# Type is canonicalised via AdmonitionStyles so every code path
|
|
158
|
+
# (line admonition, block admonition, attribute-cast admonition)
|
|
159
|
+
# produces the same uppercase key.
|
|
151
160
|
def transform_admonition_block(block, type)
|
|
161
|
+
canonical = AdmonitionStyles.canonicalize(type) || type.to_s
|
|
152
162
|
content_lines = Array(block.lines).map { |line| ToCoreModel.extract_text_content(line) }.join("\n")
|
|
153
163
|
Coradoc::CoreModel::AnnotationBlock.new(
|
|
154
|
-
annotation_type:
|
|
164
|
+
annotation_type: canonical,
|
|
155
165
|
content: content_lines,
|
|
156
166
|
title: ToCoreModel.extract_title_text(block.title)
|
|
157
167
|
)
|
|
@@ -13,6 +13,7 @@ module Coradoc
|
|
|
13
13
|
children = CalloutMerger.call(children)
|
|
14
14
|
children = prepend_frontmatter(children, doc.frontmatter)
|
|
15
15
|
children = insert_title_heading_after_frontmatter(children, title_text)
|
|
16
|
+
children = resolve_attribute_references(children, attributes)
|
|
16
17
|
|
|
17
18
|
Coradoc::CoreModel::DocumentElement.new(
|
|
18
19
|
id: doc.id,
|
|
@@ -58,6 +59,16 @@ module Coradoc
|
|
|
58
59
|
children.insert(frontmatter_count, title_heading)
|
|
59
60
|
end
|
|
60
61
|
|
|
62
|
+
# Resolve `{name}` attribute references in the body against the
|
|
63
|
+
# document's own declared attributes. Single source of truth:
|
|
64
|
+
# the resolver lives in core so other format gems can reuse it
|
|
65
|
+
# when they have equivalent reference macros.
|
|
66
|
+
def resolve_attribute_references(children, attributes)
|
|
67
|
+
return children if attributes.nil? || attributes.keys.empty?
|
|
68
|
+
|
|
69
|
+
Coradoc::CoreModel::AttributeReferenceResolver.call(children, attributes)
|
|
70
|
+
end
|
|
71
|
+
|
|
61
72
|
def transform_section(section, parent_id: nil)
|
|
62
73
|
title_text = ToCoreModel.extract_title_text(section.title)
|
|
63
74
|
section_id = section.id || Coradoc::CoreModel::IdGenerator.generate_from_title(
|
|
@@ -67,20 +67,28 @@ module Coradoc
|
|
|
67
67
|
)
|
|
68
68
|
li.children = children
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
li.children << nested_core
|
|
73
|
-
elsif item.nested.is_a?(Array)
|
|
74
|
-
item.nested.each do |n|
|
|
75
|
-
next unless n.is_a?(Coradoc::AsciiDoc::Model::List::Core)
|
|
76
|
-
|
|
77
|
-
li.children << transform_list(n, list_marker_type(n))
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
|
|
70
|
+
nested_lists = extract_nested_lists(item)
|
|
71
|
+
li.nested_list = nested_lists.first if nested_lists.size == 1
|
|
81
72
|
li
|
|
82
73
|
end
|
|
83
74
|
|
|
75
|
+
# Pull every nested List::Core off the AsciiDoc model item and
|
|
76
|
+
# transform each into a CoreModel::ListBlock. Returns [] when
|
|
77
|
+
# the item has no nested lists. Single source of truth for the
|
|
78
|
+
# nested-list shape so transform_list_item and any future caller
|
|
79
|
+
# share the same extraction logic.
|
|
80
|
+
def extract_nested_lists(item)
|
|
81
|
+
nested = item.nested
|
|
82
|
+
return [] if nested.nil?
|
|
83
|
+
|
|
84
|
+
candidates = nested.is_a?(Array) ? nested : [nested]
|
|
85
|
+
candidates.filter_map do |n|
|
|
86
|
+
next unless n.is_a?(Coradoc::AsciiDoc::Model::List::Core)
|
|
87
|
+
|
|
88
|
+
transform_list(n, list_marker_type(n))
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
84
92
|
def list_marker_type(list)
|
|
85
93
|
case list
|
|
86
94
|
when Coradoc::AsciiDoc::Model::List::Ordered then 'ordered'
|
|
@@ -15,9 +15,10 @@ module Coradoc
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def transform_admonition(admonition)
|
|
18
|
+
canonical = Coradoc::AsciiDoc::Transform::ElementTransformers::AdmonitionStyles.canonicalize(admonition.type) || admonition.type.to_s
|
|
18
19
|
children = ToCoreModel.transform_inline_content(admonition.content)
|
|
19
20
|
block = Coradoc::CoreModel::AnnotationBlock.new(
|
|
20
|
-
annotation_type:
|
|
21
|
+
annotation_type: canonical,
|
|
21
22
|
content: ToCoreModel.extract_text_content(admonition.content)
|
|
22
23
|
)
|
|
23
24
|
block.children = children
|
|
@@ -319,6 +319,8 @@ module Coradoc
|
|
|
319
319
|
)
|
|
320
320
|
when 'raw_inline'
|
|
321
321
|
Coradoc::AsciiDoc::Model::Inline::Passthrough.new(content: inline.content)
|
|
322
|
+
when 'hard_line_break'
|
|
323
|
+
Coradoc::AsciiDoc::Model::Inline::HardLineBreak.new
|
|
322
324
|
else
|
|
323
325
|
Coradoc::AsciiDoc::Model::TextElement.new(content: inline.content)
|
|
324
326
|
end
|
|
@@ -172,9 +172,11 @@ module Coradoc
|
|
|
172
172
|
Registry.register(
|
|
173
173
|
Coradoc::AsciiDoc::Model::Inline::AttributeReference,
|
|
174
174
|
lambda { |model|
|
|
175
|
+
name = model.name.to_s
|
|
175
176
|
Coradoc::CoreModel::InlineElement.new(
|
|
176
177
|
format_type: 'attribute_reference',
|
|
177
|
-
|
|
178
|
+
target: name,
|
|
179
|
+
content: "{#{name}}"
|
|
178
180
|
)
|
|
179
181
|
}
|
|
180
182
|
)
|
|
@@ -246,6 +248,16 @@ module Coradoc
|
|
|
246
248
|
->(model) { Inc.transform_include(model) }
|
|
247
249
|
)
|
|
248
250
|
|
|
251
|
+
# Inline hard line break — distinct from the structural
|
|
252
|
+
# Model::LineBreak (which represents paragraph-separator blank
|
|
253
|
+
# lines and is dropped at the CoreModel boundary). Hard breaks
|
|
254
|
+
# are semantic and round-trip as CoreModel::HardLineBreakElement
|
|
255
|
+
# so HTML/Markdown renderers can map them to <br>.
|
|
256
|
+
Registry.register(
|
|
257
|
+
Coradoc::AsciiDoc::Model::Inline::HardLineBreak,
|
|
258
|
+
->(_model) { Coradoc::CoreModel::HardLineBreakElement.new(content: '') }
|
|
259
|
+
)
|
|
260
|
+
|
|
249
261
|
[
|
|
250
262
|
Coradoc::AsciiDoc::Model::Audio,
|
|
251
263
|
Coradoc::AsciiDoc::Model::Video,
|
|
@@ -37,12 +37,15 @@ module Coradoc
|
|
|
37
37
|
Model::Block::Example.new(title: '', lines: example)
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
# Admonition
|
|
40
|
+
# Admonition. Canonicalise the type to uppercase so round-trips
|
|
41
|
+
# through HTML/Markdown/DocBook all see the same key used by
|
|
42
|
+
# icon and CSS-class lookups.
|
|
41
43
|
rule(
|
|
42
44
|
admonition_type: simple(:admonition_type),
|
|
43
45
|
content: sequence(:content)
|
|
44
46
|
) do
|
|
45
|
-
|
|
47
|
+
canonical = Coradoc::AsciiDoc::Transform::ElementTransformers::AdmonitionStyles.canonicalize(admonition_type.to_s)
|
|
48
|
+
Model::Admonition.new(content: content, type: canonical)
|
|
46
49
|
end
|
|
47
50
|
|
|
48
51
|
# Block image
|
|
@@ -36,14 +36,17 @@ module Coradoc
|
|
|
36
36
|
|
|
37
37
|
# Inline image
|
|
38
38
|
rule(inline_image: subtree(:inline_image)) do
|
|
39
|
+
attrs = AttributeListNormalizer.coerce(inline_image[:attribute_list])
|
|
39
40
|
Model::Image::InlineImage.new(
|
|
40
|
-
title: inline_image[:text],
|
|
41
41
|
src: inline_image[:path],
|
|
42
|
-
attributes:
|
|
42
|
+
attributes: attrs
|
|
43
43
|
)
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
-
# Inline passthrough (`+++raw content+++`)
|
|
46
|
+
# Inline passthrough (`+++raw content+++` or `pass:[raw]`).
|
|
47
|
+
# Both forms carry an opaque payload that survives all
|
|
48
|
+
# substitutions verbatim; `form` records which syntax was
|
|
49
|
+
# used so the AsciiDoc serializer can round-trip faithfully.
|
|
47
50
|
rule(inline_passthrough: subtree(:passthrough)) do
|
|
48
51
|
Model::Inline::Passthrough.new(
|
|
49
52
|
content: passthrough[:raw].to_s,
|
|
@@ -56,6 +59,15 @@ module Coradoc
|
|
|
56
59
|
Model::Inline::AttributeReference.new(name:)
|
|
57
60
|
end
|
|
58
61
|
|
|
62
|
+
# Hard line break (` +\n` or `\\n`). Emitted as a dedicated
|
|
63
|
+
# AsciiDoc model (Inline::HardLineBreak) distinct from
|
|
64
|
+
# Model::LineBreak, which only represents paragraph-separator
|
|
65
|
+
# blank lines. Hard breaks carry semantic meaning: HTML/Markdown
|
|
66
|
+
# renderers map them to <br>.
|
|
67
|
+
rule(hard_line_break: simple(:_)) do
|
|
68
|
+
Model::Inline::HardLineBreak.new
|
|
69
|
+
end
|
|
70
|
+
|
|
59
71
|
# Term
|
|
60
72
|
rule(
|
|
61
73
|
term_type: simple(:term_type),
|
|
@@ -20,12 +20,24 @@ module Coradoc
|
|
|
20
20
|
cell_opts = {}
|
|
21
21
|
style = parse_format(format, cell_opts)
|
|
22
22
|
|
|
23
|
-
unescaped_content = content.
|
|
23
|
+
unescaped_content = cell_content_to_string(content).gsub(/\\([|!,:;])/, '\1')
|
|
24
24
|
cell_opts[:content] = parse_inline_content(unescaped_content, style)
|
|
25
25
|
|
|
26
26
|
Model::TableCell.new(**cell_opts)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
# Coerce the parser-supplied cell content into a plain String.
|
|
30
|
+
# The cell parser emits `text:` as either a single Parslet::Slice
|
|
31
|
+
# or an Array of slices (from `.repeat(0)`). An empty Array must
|
|
32
|
+
# map to "" — Ruby's `[].to_s` returns "[]", which previously
|
|
33
|
+
# leaked into TableCell content as a phantom "[]" cell.
|
|
34
|
+
def cell_content_to_string(content)
|
|
35
|
+
return '' if content.nil?
|
|
36
|
+
return content.map(&:to_s).join if content.is_a?(Array)
|
|
37
|
+
|
|
38
|
+
content.to_s
|
|
39
|
+
end
|
|
40
|
+
|
|
29
41
|
# Coerce a raw parser cell value into a TableCell.
|
|
30
42
|
# Used by TableLayout.group_cells_into_rows when it encounters
|
|
31
43
|
# cells that the parser emitted as Hashes or plain strings.
|