coradoc-adoc 2.0.27 → 2.0.28
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/inline.rb +24 -1
- data/lib/coradoc/asciidoc/parser/list.rb +12 -5
- data/lib/coradoc/asciidoc/transform/element_transformers/document_transformer.rb +36 -0
- data/lib/coradoc/asciidoc/transform/from_core_model.rb +33 -12
- data/lib/coradoc/asciidoc/transformer/inline_rules.rb +13 -0
- 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: 4af9421f3be892d3f963f70789a03201faf12f95ba487207c3affb9f0dd94ba0
|
|
4
|
+
data.tar.gz: d1dfcfe239edf6ad09a67bf1dd9a39ac7992466e9aa979475d376b525e0472b5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eb5d3978b3777a0b04fa51cf736fcee2cf4ce6de38bca4353eff5bd4b8cbf7dcce849f84fb60c05c2979f339fa6b182ff74662e491825a939ca29ea5424152c3
|
|
7
|
+
data.tar.gz: 0fc692a6bd815a8ee102f485c2724b1941fe90bffb09cfdcc066ab6ddf90479386f848fe4ad0ea63f66f8d75b12f40a53ed946a4e0343b3f9d201e860f3d042c
|
|
@@ -4,6 +4,27 @@ module Coradoc
|
|
|
4
4
|
module AsciiDoc
|
|
5
5
|
module Parser
|
|
6
6
|
module Inline
|
|
7
|
+
# AsciiDoc typographic quote syntax: a 2-char pattern that
|
|
8
|
+
# Asciidoctor substitutes with the corresponding Unicode curly
|
|
9
|
+
# quote. Single source of truth for the pattern → Unicode char
|
|
10
|
+
# mapping; the transformer reads this table.
|
|
11
|
+
#
|
|
12
|
+
# The patterns MUST be recognised before +monospace_constrained+
|
|
13
|
+
# in the +inline+ alternation, otherwise the lone backtick in
|
|
14
|
+
# +`"`+ or +`"``+ fires monospace and the surrounding quote
|
|
15
|
+
# collapses to straight ASCII quotes wrapped around a spurious
|
|
16
|
+
# code span.
|
|
17
|
+
TYPOGRAPHIC_QUOTE_PATTERNS = {
|
|
18
|
+
'"`' => "“", # U+201C left double
|
|
19
|
+
'`"' => "”", # U+201D right double
|
|
20
|
+
"'`" => "‘", # U+2018 left single
|
|
21
|
+
"`'" => "’" # U+2019 right single
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
def typographic_quote
|
|
25
|
+
(str('"`') | str('`"') | str("'`") | str("`'")).as(:typographic_quote)
|
|
26
|
+
end
|
|
27
|
+
|
|
7
28
|
def attribute_reference
|
|
8
29
|
str('{').present? >> str('{') >>
|
|
9
30
|
match('[a-zA-Z0-9_-]').repeat(1).as(:attribute_reference) >>
|
|
@@ -170,6 +191,7 @@ module Coradoc
|
|
|
170
191
|
|
|
171
192
|
def inline_chars?
|
|
172
193
|
match('[\[*#_{<^~`]').present? |
|
|
194
|
+
typographic_quote.present? |
|
|
173
195
|
str('http').present? |
|
|
174
196
|
str('https').present? |
|
|
175
197
|
str('link:').present? |
|
|
@@ -198,7 +220,8 @@ module Coradoc
|
|
|
198
220
|
end
|
|
199
221
|
|
|
200
222
|
def inline
|
|
201
|
-
|
|
223
|
+
typographic_quote |
|
|
224
|
+
bold_unconstrained |
|
|
202
225
|
bold_constrained |
|
|
203
226
|
span_unconstrained |
|
|
204
227
|
span_constrained |
|
|
@@ -20,13 +20,13 @@ module Coradoc
|
|
|
20
20
|
def ordered_list(nesting_level = 1)
|
|
21
21
|
attrs = (attribute_list >> newline).maybe
|
|
22
22
|
r = olist_item(nesting_level)
|
|
23
|
-
attrs >>
|
|
23
|
+
attrs >> (empty_line.repeat(0) >> r).repeat(1).as(:ordered)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def unordered_list(nesting_level = 1)
|
|
27
27
|
attrs = (attribute_list >> newline).maybe
|
|
28
28
|
r = ulist_item(nesting_level)
|
|
29
|
-
attrs >> r.repeat(1).as(:unordered)
|
|
29
|
+
attrs >> (empty_line.repeat(0) >> r).repeat(1).as(:unordered)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def definition_list(_delimiter = nil)
|
|
@@ -53,7 +53,7 @@ module Coradoc
|
|
|
53
53
|
def olist_item(nesting_level = 1)
|
|
54
54
|
item = olist_marker(nesting_level).as(:marker) >>
|
|
55
55
|
match("\n").absent? >> space >>
|
|
56
|
-
(text_line(
|
|
56
|
+
(text_line(false, unguarded: true) >>
|
|
57
57
|
list_item_continuation_lines).as(:lines)
|
|
58
58
|
|
|
59
59
|
att = (list_continuation.present? >>
|
|
@@ -105,7 +105,7 @@ module Coradoc
|
|
|
105
105
|
item = ulist_marker(nesting_level).as(:marker) >>
|
|
106
106
|
str(' [[[').absent? >>
|
|
107
107
|
match("\n").absent? >> space >>
|
|
108
|
-
(text_line(
|
|
108
|
+
(text_line(false, unguarded: true) >>
|
|
109
109
|
list_item_continuation_lines).as(:lines)
|
|
110
110
|
|
|
111
111
|
att = (list_continuation.present? >>
|
|
@@ -127,8 +127,15 @@ module Coradoc
|
|
|
127
127
|
# list marker, block delimiter, attribute list, section, element
|
|
128
128
|
# id, table boundary, list continuation, or list prefix).
|
|
129
129
|
# `line_not_text?` (from Paragraph) is that exact lookahead.
|
|
130
|
+
#
|
|
131
|
+
# Each iteration matches exactly one source line via
|
|
132
|
+
# `text_line(false, ...)` (single-newline termination). The
|
|
133
|
+
# `.repeat(0)` walks multiple continuation lines one at a time.
|
|
134
|
+
# Using `text_line(true, ...)` here would let `line_ending.repeat(1)`
|
|
135
|
+
# greedy-match across blank lines, silently absorbing the
|
|
136
|
+
# follow-up paragraph into the last list item's content.
|
|
130
137
|
def list_item_continuation_lines
|
|
131
|
-
(line_not_text? >> text_line(
|
|
138
|
+
(line_not_text? >> text_line(false, unguarded: true)).repeat(0)
|
|
132
139
|
end
|
|
133
140
|
|
|
134
141
|
def dlist_delimiter
|
|
@@ -12,6 +12,7 @@ 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, doc.id)
|
|
15
16
|
children = resolve_attribute_references(children, attributes)
|
|
16
17
|
|
|
17
18
|
Coradoc::CoreModel::DocumentElement.new(
|
|
@@ -33,6 +34,41 @@ module Coradoc
|
|
|
33
34
|
[block, *children]
|
|
34
35
|
end
|
|
35
36
|
|
|
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 as the first body child (after any FrontmatterBlock)
|
|
41
|
+
# so consumers that walk the body's children see the title —
|
|
42
|
+
# the standard ProseMirror/HTML rendering pattern — instead of
|
|
43
|
+
# having to read DocumentElement.title separately.
|
|
44
|
+
#
|
|
45
|
+
# The title attribute on DocumentElement is preserved for
|
|
46
|
+
# consumers that read it directly. The FromCoreModel Document
|
|
47
|
+
# transformer consumes the HeaderElement when constructing
|
|
48
|
+
# Model::Header so AsciiDoc round-trip does not double-render.
|
|
49
|
+
def insert_title_heading_after_frontmatter(children, title_text, doc_id)
|
|
50
|
+
return children if title_text.nil? || title_text.strip.empty?
|
|
51
|
+
return children if children.any? { |c| title_heading?(c) }
|
|
52
|
+
|
|
53
|
+
title_id = doc_id || Coradoc::CoreModel::IdGenerator
|
|
54
|
+
.generate_from_title(title_text)
|
|
55
|
+
title_heading = Coradoc::CoreModel::HeaderElement.new(
|
|
56
|
+
level: 0,
|
|
57
|
+
title: title_text,
|
|
58
|
+
content: title_text,
|
|
59
|
+
id: title_id
|
|
60
|
+
)
|
|
61
|
+
frontmatter_count = children.count do |child|
|
|
62
|
+
child.is_a?(Coradoc::CoreModel::FrontmatterBlock)
|
|
63
|
+
end
|
|
64
|
+
children.insert(frontmatter_count, title_heading)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def title_heading?(child)
|
|
68
|
+
child.is_a?(Coradoc::CoreModel::HeaderElement) &&
|
|
69
|
+
child.level.to_i.zero?
|
|
70
|
+
end
|
|
71
|
+
|
|
36
72
|
# Resolve `{name}` attribute references in the body against the
|
|
37
73
|
# document's own declared attributes. Single source of truth:
|
|
38
74
|
# the resolver lives in core so other format gems can reuse it
|
|
@@ -27,23 +27,24 @@ module Coradoc
|
|
|
27
27
|
def transform_structural_element(element)
|
|
28
28
|
case element
|
|
29
29
|
when CoreModel::DocumentElement
|
|
30
|
-
header = if element.title
|
|
31
|
-
Coradoc::AsciiDoc::Model::Header.new(
|
|
32
|
-
title: Coradoc::AsciiDoc::Model::Title.new(
|
|
33
|
-
content: element.title,
|
|
34
|
-
level_int: 0
|
|
35
|
-
)
|
|
36
|
-
)
|
|
37
|
-
else
|
|
38
|
-
Coradoc::AsciiDoc::Model::Header.new(title: '')
|
|
39
|
-
end
|
|
40
|
-
|
|
41
30
|
without_frontmatter, frontmatter = extract_frontmatter(Array(element.children))
|
|
31
|
+
without_title_heading, title_text = extract_title_heading(without_frontmatter, element.title)
|
|
32
|
+
|
|
33
|
+
header = if title_text
|
|
34
|
+
Coradoc::AsciiDoc::Model::Header.new(
|
|
35
|
+
title: Coradoc::AsciiDoc::Model::Title.new(
|
|
36
|
+
content: title_text,
|
|
37
|
+
level_int: 0
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
else
|
|
41
|
+
Coradoc::AsciiDoc::Model::Header.new(title: '')
|
|
42
|
+
end
|
|
42
43
|
|
|
43
44
|
Coradoc::AsciiDoc::Model::Document.new(
|
|
44
45
|
id: element.id,
|
|
45
46
|
header: header,
|
|
46
|
-
sections: flatten_children(
|
|
47
|
+
sections: flatten_children(without_title_heading),
|
|
47
48
|
frontmatter: frontmatter
|
|
48
49
|
)
|
|
49
50
|
when CoreModel::SectionElement
|
|
@@ -456,6 +457,26 @@ module Coradoc
|
|
|
456
457
|
[children.drop(1), yaml.nil? || yaml.empty? ? nil : yaml]
|
|
457
458
|
end
|
|
458
459
|
|
|
460
|
+
# Pull the level-0 HeaderElement (the document title) out of the
|
|
461
|
+
# body so the AsciiDoc serializer can render it as `= Title`
|
|
462
|
+
# via Model::Header instead of double-rendering it as a body
|
|
463
|
+
# section. Returns [remaining_children, title_text]. Falls
|
|
464
|
+
# back to +fallback_title+ when no HeaderElement is present
|
|
465
|
+
# (preserves backward compatibility with callers that build
|
|
466
|
+
# DocumentElement programmatically and put the title only on
|
|
467
|
+
# DocumentElement#title rather than in the children list).
|
|
468
|
+
def extract_title_heading(children, fallback_title)
|
|
469
|
+
title_idx = children.index do |c|
|
|
470
|
+
c.is_a?(CoreModel::HeaderElement) && c.level.to_i.zero?
|
|
471
|
+
end
|
|
472
|
+
return [children, fallback_title] unless title_idx
|
|
473
|
+
|
|
474
|
+
heading = children[title_idx]
|
|
475
|
+
title_text = heading.title || fallback_title
|
|
476
|
+
remaining = children[0...title_idx] + children[(title_idx + 1)..]
|
|
477
|
+
[remaining, title_text]
|
|
478
|
+
end
|
|
479
|
+
|
|
459
480
|
def resolve_semantic_type(block)
|
|
460
481
|
semantic = block.resolve_semantic_type
|
|
461
482
|
return semantic if semantic
|
|
@@ -67,6 +67,19 @@ module Coradoc
|
|
|
67
67
|
)
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
+
# Typographic quotes — Asciidoctor's curly-quote substitution.
|
|
71
|
+
# Each 2-char pattern collapses to a single Unicode character
|
|
72
|
+
# at parse time so downstream consumers (HTML, Markdown) see
|
|
73
|
+
# the literal typographic char without any post-processing.
|
|
74
|
+
# The mapping is the single source of truth declared on the
|
|
75
|
+
# parser side (Parser::Inline::TYPOGRAPHIC_QUOTE_PATTERNS).
|
|
76
|
+
rule(typographic_quote: simple(:pattern)) do
|
|
77
|
+
char = Coradoc::AsciiDoc::Parser::Inline::TYPOGRAPHIC_QUOTE_PATTERNS.fetch(
|
|
78
|
+
pattern.to_s, pattern.to_s
|
|
79
|
+
)
|
|
80
|
+
Model::TextElement.new(content: char)
|
|
81
|
+
end
|
|
82
|
+
|
|
70
83
|
# Hard line break (` +\n` or `\\n`). Emitted as a dedicated
|
|
71
84
|
# AsciiDoc model (Inline::HardLineBreak) distinct from
|
|
72
85
|
# Model::LineBreak, which only represents paragraph-separator
|