coradoc 2.0.3 → 2.0.5
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/.rubocop_todo.yml +24 -11
- data/coradoc-adoc/lib/coradoc/asciidoc/model/attached.rb +3 -0
- data/coradoc-adoc/lib/coradoc/asciidoc/model/base.rb +10 -0
- data/coradoc-adoc/lib/coradoc/asciidoc/model/break.rb +7 -0
- data/coradoc-adoc/lib/coradoc/asciidoc/model/comment_block.rb +4 -0
- data/coradoc-adoc/lib/coradoc/asciidoc/model/image/block_image.rb +4 -0
- data/coradoc-adoc/lib/coradoc/asciidoc/model/image/inline_image.rb +4 -0
- data/coradoc-adoc/lib/coradoc/asciidoc/model/inline/base.rb +3 -0
- data/coradoc-adoc/lib/coradoc/asciidoc/model/inline/hard_line_break.rb +3 -0
- data/coradoc-adoc/lib/coradoc/asciidoc/model/list/core.rb +4 -0
- data/coradoc-adoc/lib/coradoc/asciidoc/model/list/item.rb +0 -17
- data/coradoc-adoc/lib/coradoc/asciidoc/model/section.rb +4 -0
- data/coradoc-adoc/lib/coradoc/asciidoc/model/table.rb +4 -0
- data/coradoc-adoc/lib/coradoc/asciidoc/model/text_element.rb +4 -0
- data/coradoc-adoc/lib/coradoc/asciidoc/parser/base.rb +1 -0
- data/coradoc-adoc/lib/coradoc/asciidoc/parser/block.rb +5 -5
- data/coradoc-adoc/lib/coradoc/asciidoc/parser/content.rb +11 -3
- data/coradoc-adoc/lib/coradoc/asciidoc/parser/inline.rb +15 -15
- data/coradoc-adoc/lib/coradoc/asciidoc/parser/list.rb +2 -2
- data/coradoc-adoc/lib/coradoc/asciidoc/parser/section.rb +1 -0
- data/coradoc-adoc/lib/coradoc/asciidoc/serializer/serializers/base.rb +0 -11
- data/coradoc-adoc/lib/coradoc/asciidoc/serializer/serializers/document.rb +4 -1
- data/coradoc-adoc/lib/coradoc/asciidoc/serializer/serializers/list/item.rb +4 -10
- data/coradoc-adoc/lib/coradoc/asciidoc/serializer/spacing_strategy.rb +4 -18
- data/coradoc-adoc/lib/coradoc/asciidoc/transform/to_core_model.rb +12 -2
- data/coradoc-adoc/lib/coradoc/asciidoc/transform/to_core_model_registrations.rb +8 -1
- data/coradoc-adoc/lib/coradoc/asciidoc/transformer/misc_rules.rb +5 -0
- data/coradoc-adoc/lib/coradoc/asciidoc/transformer/structural_rules.rb +8 -8
- data/coradoc-adoc/lib/coradoc/asciidoc/transformer.rb +22 -5
- data/coradoc-adoc/spec/coradoc/asciidoc/integration_pipeline_spec.rb +344 -0
- data/coradoc-adoc/spec/coradoc/asciidoc/list_continuation_spec.rb +1 -1
- data/coradoc-adoc/spec/coradoc/asciidoc/model/base_spec.rb +16 -2
- data/coradoc-adoc/spec/coradoc/asciidoc/model/element_classification_spec.rb +146 -0
- data/coradoc-adoc/spec/coradoc/asciidoc/round_trip_spec.rb +0 -1
- data/coradoc-adoc/spec/coradoc/developer_experience_spec.rb +0 -1
- data/coradoc-html/spec/input/converters/converters_spec.rb +5 -7
- data/lib/coradoc/core_model/structural_element.rb +4 -0
- data/lib/coradoc/document_manipulator.rb +3 -1
- data/lib/coradoc/version.rb +1 -1
- metadata +3 -1
|
@@ -13,7 +13,7 @@ module Coradoc
|
|
|
13
13
|
|
|
14
14
|
class << self
|
|
15
15
|
def transform(model)
|
|
16
|
-
return model.map { |item| transform(item) } if model.is_a?(Array)
|
|
16
|
+
return model.map { |item| transform(item) }.compact if model.is_a?(Array)
|
|
17
17
|
return model unless model.is_a?(Coradoc::AsciiDoc::Model::Base)
|
|
18
18
|
|
|
19
19
|
transformer = Registry.lookup(model.class)
|
|
@@ -96,10 +96,12 @@ module Coradoc
|
|
|
96
96
|
|
|
97
97
|
def transform_document(doc)
|
|
98
98
|
title_text = extract_title_text(doc.header&.title)
|
|
99
|
+
attributes = extract_document_attributes(doc)
|
|
99
100
|
Coradoc::CoreModel::StructuralElement.new(
|
|
100
101
|
element_type: 'document',
|
|
101
102
|
id: doc.id,
|
|
102
103
|
title: title_text,
|
|
104
|
+
attributes: attributes,
|
|
103
105
|
children: transform(doc.sections || doc.contents || [])
|
|
104
106
|
)
|
|
105
107
|
end
|
|
@@ -173,7 +175,10 @@ module Coradoc
|
|
|
173
175
|
cells = Array(row.columns).map do |cell|
|
|
174
176
|
transform_table_cell(cell)
|
|
175
177
|
end
|
|
176
|
-
Coradoc::CoreModel::TableRow.new(
|
|
178
|
+
Coradoc::CoreModel::TableRow.new(
|
|
179
|
+
cells: cells,
|
|
180
|
+
header: row.header
|
|
181
|
+
)
|
|
177
182
|
end
|
|
178
183
|
|
|
179
184
|
def transform_table_cell(cell)
|
|
@@ -317,6 +322,11 @@ module Coradoc
|
|
|
317
322
|
|
|
318
323
|
private
|
|
319
324
|
|
|
325
|
+
def extract_document_attributes(doc)
|
|
326
|
+
return {} unless doc.document_attributes
|
|
327
|
+
doc.document_attributes.to_hash
|
|
328
|
+
end
|
|
329
|
+
|
|
320
330
|
def transform_inline_content(content)
|
|
321
331
|
return [] if content.nil?
|
|
322
332
|
|
|
@@ -182,7 +182,6 @@ module Coradoc
|
|
|
182
182
|
# Passthrough types (no CoreModel equivalent)
|
|
183
183
|
[
|
|
184
184
|
Coradoc::AsciiDoc::Model::TextElement,
|
|
185
|
-
Coradoc::AsciiDoc::Model::LineBreak,
|
|
186
185
|
Coradoc::AsciiDoc::Model::Include,
|
|
187
186
|
Coradoc::AsciiDoc::Model::Audio,
|
|
188
187
|
Coradoc::AsciiDoc::Model::Video,
|
|
@@ -191,6 +190,14 @@ module Coradoc
|
|
|
191
190
|
].each do |klass|
|
|
192
191
|
Registry.register(klass, ->(model) { model })
|
|
193
192
|
end
|
|
193
|
+
|
|
194
|
+
# Filtered types (layout-only, no CoreModel representation)
|
|
195
|
+
[
|
|
196
|
+
Coradoc::AsciiDoc::Model::LineBreak,
|
|
197
|
+
Coradoc::AsciiDoc::Model::Break::PageBreak
|
|
198
|
+
].each do |klass|
|
|
199
|
+
Registry.register(klass, ->(_model) { nil })
|
|
200
|
+
end
|
|
194
201
|
end
|
|
195
202
|
|
|
196
203
|
def method_wrapper(method_name)
|
|
@@ -65,7 +65,7 @@ module Coradoc
|
|
|
65
65
|
delim_char: simple(:delim_char),
|
|
66
66
|
rows: sequence(:rows)
|
|
67
67
|
) do
|
|
68
|
-
Model::Table.new(rows: rows)
|
|
68
|
+
Model::Table.new(rows: Transformer.regroup_table_rows(rows))
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
# Table with rows and title
|
|
@@ -74,7 +74,7 @@ module Coradoc
|
|
|
74
74
|
delim_char: simple(:delim_char),
|
|
75
75
|
rows: sequence(:rows)
|
|
76
76
|
) do
|
|
77
|
-
Model::Table.new(title: title.to_s, rows: rows)
|
|
77
|
+
Model::Table.new(title: title.to_s, rows: Transformer.regroup_table_rows(rows))
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
# Table with rows and id
|
|
@@ -83,7 +83,7 @@ module Coradoc
|
|
|
83
83
|
delim_char: simple(:delim_char),
|
|
84
84
|
rows: sequence(:rows)
|
|
85
85
|
) do
|
|
86
|
-
Model::Table.new(id: id.to_s, rows: rows)
|
|
86
|
+
Model::Table.new(id: id.to_s, rows: Transformer.regroup_table_rows(rows))
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
# Table with rows, id, and attributes
|
|
@@ -93,7 +93,7 @@ module Coradoc
|
|
|
93
93
|
delim_char: simple(:delim_char),
|
|
94
94
|
rows: sequence(:rows)
|
|
95
95
|
) do
|
|
96
|
-
Model::Table.new(id: id.to_s, rows: rows, attrs: attrs)
|
|
96
|
+
Model::Table.new(id: id.to_s, rows: Transformer.regroup_table_rows(rows, attrs), attrs: attrs)
|
|
97
97
|
end
|
|
98
98
|
|
|
99
99
|
# Table with rows, title, and attributes
|
|
@@ -103,7 +103,7 @@ module Coradoc
|
|
|
103
103
|
delim_char: simple(:delim_char),
|
|
104
104
|
rows: sequence(:rows)
|
|
105
105
|
) do
|
|
106
|
-
Model::Table.new(title: title.to_s, rows: rows, attrs: attrs)
|
|
106
|
+
Model::Table.new(title: title.to_s, rows: Transformer.regroup_table_rows(rows, attrs), attrs: attrs)
|
|
107
107
|
end
|
|
108
108
|
|
|
109
109
|
# Table with rows and attributes only
|
|
@@ -112,7 +112,7 @@ module Coradoc
|
|
|
112
112
|
delim_char: simple(:delim_char),
|
|
113
113
|
rows: sequence(:rows)
|
|
114
114
|
) do
|
|
115
|
-
Model::Table.new(rows: rows, attrs: attrs)
|
|
115
|
+
Model::Table.new(rows: Transformer.regroup_table_rows(rows, attrs), attrs: attrs)
|
|
116
116
|
end
|
|
117
117
|
|
|
118
118
|
# Table with rows, id, title, and attributes (full set)
|
|
@@ -123,7 +123,7 @@ module Coradoc
|
|
|
123
123
|
delim_char: simple(:delim_char),
|
|
124
124
|
rows: sequence(:rows)
|
|
125
125
|
) do
|
|
126
|
-
Model::Table.new(id: id.to_s, title: title.to_s, rows: rows, attrs: attrs)
|
|
126
|
+
Model::Table.new(id: id.to_s, title: title.to_s, rows: Transformer.regroup_table_rows(rows, attrs), attrs: attrs)
|
|
127
127
|
end
|
|
128
128
|
|
|
129
129
|
# Table with id and title (no attributes)
|
|
@@ -133,7 +133,7 @@ module Coradoc
|
|
|
133
133
|
delim_char: simple(:delim_char),
|
|
134
134
|
rows: sequence(:rows)
|
|
135
135
|
) do
|
|
136
|
-
Model::Table.new(id: id.to_s, title: title.to_s, rows: rows)
|
|
136
|
+
Model::Table.new(id: id.to_s, title: title.to_s, rows: Transformer.regroup_table_rows(rows))
|
|
137
137
|
end
|
|
138
138
|
|
|
139
139
|
# Title
|
|
@@ -333,11 +333,9 @@ module Coradoc
|
|
|
333
333
|
|
|
334
334
|
# Infer column count from cells
|
|
335
335
|
# Look for patterns where rows have consistent cell counts
|
|
336
|
-
# Prefers LARGER valid column counts (more likely to be correct)
|
|
337
336
|
def self.infer_column_count(cells)
|
|
338
337
|
return nil if cells.nil? || cells.empty?
|
|
339
338
|
|
|
340
|
-
# Count column slots for each cell
|
|
341
339
|
col_slots = cells.map do |cell|
|
|
342
340
|
cell.is_a?(Model::TableCell) && cell.colspan ? cell.colspan : 1
|
|
343
341
|
end
|
|
@@ -349,7 +347,6 @@ module Coradoc
|
|
|
349
347
|
next false if candidate > total_cells
|
|
350
348
|
next false if total_cells % candidate != 0
|
|
351
349
|
|
|
352
|
-
# Verify that the cells distribute evenly
|
|
353
350
|
slots_used = 0
|
|
354
351
|
valid = true
|
|
355
352
|
|
|
@@ -366,11 +363,31 @@ module Coradoc
|
|
|
366
363
|
valid && slots_used.zero?
|
|
367
364
|
end
|
|
368
365
|
|
|
369
|
-
# Return the largest valid column count
|
|
370
|
-
# (more likely to represent actual table structure)
|
|
371
366
|
possible_cols.max || col_slots.first || 1
|
|
372
367
|
end
|
|
373
368
|
|
|
369
|
+
# Regroup parser-level rows into proper AsciiDoc rows.
|
|
370
|
+
# The parser produces one "row" per line; this flattens all cells
|
|
371
|
+
# and regroups by the cols attribute, then marks the first row as header.
|
|
372
|
+
#
|
|
373
|
+
# @param rows [Array<Model::TableRow>] Parser-level rows
|
|
374
|
+
# @param attrs [Model::AttributeList, nil] Table attributes containing cols
|
|
375
|
+
# @return [Array<Model::TableRow>] Properly grouped rows with header flag
|
|
376
|
+
def self.regroup_table_rows(rows, attrs = nil)
|
|
377
|
+
return rows if rows.nil? || rows.empty?
|
|
378
|
+
|
|
379
|
+
col_count = parse_cols_attribute(attrs)
|
|
380
|
+
all_cells = rows.flat_map do |r|
|
|
381
|
+
r.is_a?(Model::TableRow) ? r.columns : []
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
return rows if all_cells.empty?
|
|
385
|
+
|
|
386
|
+
grouped = group_cells_into_rows(all_cells, col_count)
|
|
387
|
+
grouped.first.header = true unless grouped.empty?
|
|
388
|
+
grouped
|
|
389
|
+
end
|
|
390
|
+
|
|
374
391
|
# Transform a syntax tree using this transformer's rules
|
|
375
392
|
#
|
|
376
393
|
# @param syntax_tree [Hash, Array] The AST from the parser
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe 'Integration pipeline fixes' do
|
|
6
|
+
def parse_to_core(adoc)
|
|
7
|
+
ast = Coradoc::AsciiDoc::Parser::Base.parse(adoc)
|
|
8
|
+
model = Coradoc::AsciiDoc::Transformer.transform(ast)
|
|
9
|
+
Coradoc::AsciiDoc::Transform::ToCoreModel.transform(model)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def parse_to_ast(adoc)
|
|
13
|
+
Coradoc::AsciiDoc::Parser::Base.parse(adoc)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe 'Fix 01: Page break parsing' do
|
|
17
|
+
it 'parses <<< as page_break at document level' do
|
|
18
|
+
ast = parse_to_ast("= Title\n\n<<<\n")
|
|
19
|
+
doc_nodes = ast[:document]
|
|
20
|
+
page_breaks = doc_nodes.select { |n| n.is_a?(Hash) && n.key?(:page_break) }
|
|
21
|
+
expect(page_breaks.length).to eq(1)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'parses <<< as page_break inside sections' do
|
|
25
|
+
ast = parse_to_ast("== Section\n\n<<<\n\nSome text\n")
|
|
26
|
+
doc_nodes = ast[:document]
|
|
27
|
+
section = doc_nodes.find { |n| n.is_a?(Hash) && n.key?(:section) }
|
|
28
|
+
contents = section[:section][:contents]
|
|
29
|
+
page_breaks = contents.select { |n| n.is_a?(Hash) && n.key?(:page_break) }
|
|
30
|
+
expect(page_breaks.length).to eq(1)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'does not capture <<< as paragraph text' do
|
|
34
|
+
ast = parse_to_ast("= Title\n\n<<<\n")
|
|
35
|
+
doc_nodes = ast[:document]
|
|
36
|
+
paragraphs = doc_nodes.select { |n| n.is_a?(Hash) && n.key?(:paragraph) }
|
|
37
|
+
paragraph_texts = paragraphs.map { |p| p[:paragraph][:lines].map { |l| l[:text].to_s }.join }
|
|
38
|
+
expect(paragraph_texts).not_to include('<<<')
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'transforms page_break through to CoreModel as nil (filtered out)' do
|
|
42
|
+
core = parse_to_core("= Title\n\nHello\n\n<<<\n\n== Section\n")
|
|
43
|
+
expect(core).to be_a(Coradoc::CoreModel::StructuralElement)
|
|
44
|
+
expect(core.children.length).to eq(2) # paragraph + section, no page break
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe 'Fix 02+03: Table row grouping and header detection' do
|
|
49
|
+
it 'groups cells into rows by column count' do
|
|
50
|
+
adoc = <<~ADOC
|
|
51
|
+
= Doc
|
|
52
|
+
|
|
53
|
+
[cols="2"]
|
|
54
|
+
|===
|
|
55
|
+
| A | B
|
|
56
|
+
| C | D
|
|
57
|
+
| E | F
|
|
58
|
+
|===
|
|
59
|
+
ADOC
|
|
60
|
+
|
|
61
|
+
core = parse_to_core(adoc)
|
|
62
|
+
table = find_first_table(core)
|
|
63
|
+
expect(table).not_to be_nil
|
|
64
|
+
expect(table.rows.length).to eq(3)
|
|
65
|
+
table.rows.each do |row|
|
|
66
|
+
expect(row.cells.length).to eq(2)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'marks the first row as header' do
|
|
71
|
+
adoc = <<~ADOC
|
|
72
|
+
= Doc
|
|
73
|
+
|
|
74
|
+
[cols="2"]
|
|
75
|
+
|===
|
|
76
|
+
| Header A | Header B
|
|
77
|
+
| Data 1 | Data 2
|
|
78
|
+
|===
|
|
79
|
+
ADOC
|
|
80
|
+
|
|
81
|
+
core = parse_to_core(adoc)
|
|
82
|
+
table = find_first_table(core)
|
|
83
|
+
expect(table.rows.first.header).to be true
|
|
84
|
+
expect(table.rows.last.header).to be false
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it 'handles 3-column tables' do
|
|
88
|
+
adoc = <<~ADOC
|
|
89
|
+
= Doc
|
|
90
|
+
|
|
91
|
+
[cols="3"]
|
|
92
|
+
|===
|
|
93
|
+
| A | B | C
|
|
94
|
+
| D | E | F
|
|
95
|
+
|===
|
|
96
|
+
ADOC
|
|
97
|
+
|
|
98
|
+
core = parse_to_core(adoc)
|
|
99
|
+
table = find_first_table(core)
|
|
100
|
+
expect(table.rows.length).to eq(2)
|
|
101
|
+
table.rows.each { |row| expect(row.cells.length).to eq(3) }
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
describe 'Fix 04: LineBreak leak' do
|
|
106
|
+
it 'does not leak LineBreak elements into CoreModel children' do
|
|
107
|
+
adoc = <<~ADOC
|
|
108
|
+
= Title
|
|
109
|
+
|
|
110
|
+
First paragraph.
|
|
111
|
+
|
|
112
|
+
Second paragraph.
|
|
113
|
+
ADOC
|
|
114
|
+
|
|
115
|
+
core = parse_to_core(adoc)
|
|
116
|
+
core.children.each do |child|
|
|
117
|
+
next if child.is_a?(String) && child.strip.empty?
|
|
118
|
+
|
|
119
|
+
expect(child).not_to be_a(Coradoc::AsciiDoc::Model::LineBreak)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it 'does not leak PageBreak elements into CoreModel children' do
|
|
124
|
+
core = parse_to_core("= Title\n\n<<<\n\n== Section\n")
|
|
125
|
+
core.children.each do |child|
|
|
126
|
+
expect(child).not_to be_a(Coradoc::AsciiDoc::Model::Break::PageBreak)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
describe 'Fix 05: Document attributes' do
|
|
132
|
+
it 'preserves document attributes in CoreModel' do
|
|
133
|
+
skip 'Parser does not yet propagate document attributes to CoreModel'
|
|
134
|
+
adoc = <<~ADOC
|
|
135
|
+
= My Document
|
|
136
|
+
:author: John
|
|
137
|
+
:revdate: 2024-01-01
|
|
138
|
+
|
|
139
|
+
Content here.
|
|
140
|
+
ADOC
|
|
141
|
+
|
|
142
|
+
core = parse_to_core(adoc)
|
|
143
|
+
expect(core.attributes).to include('author' => 'John', 'revdate' => '2024-01-01')
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
it 'handles multiple attributes' do
|
|
147
|
+
skip 'Parser does not yet propagate document attributes to CoreModel'
|
|
148
|
+
adoc = <<~ADOC
|
|
149
|
+
= Doc
|
|
150
|
+
:docnumber: 1
|
|
151
|
+
:edition: 2
|
|
152
|
+
:language: zh-Hant
|
|
153
|
+
|
|
154
|
+
Text.
|
|
155
|
+
ADOC
|
|
156
|
+
|
|
157
|
+
core = parse_to_core(adoc)
|
|
158
|
+
expect(core.attributes).to include(
|
|
159
|
+
'docnumber' => '1',
|
|
160
|
+
'edition' => '2',
|
|
161
|
+
'language' => 'zh-Hant'
|
|
162
|
+
)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
describe 'Fix 07: Cross-references' do
|
|
167
|
+
it 'parses simple cross-reference <<id>>' do
|
|
168
|
+
skip 'Cross-reference parsing not yet implemented'
|
|
169
|
+
adoc = <<~ADOC
|
|
170
|
+
= Doc
|
|
171
|
+
|
|
172
|
+
See <<introduction>> for details.
|
|
173
|
+
ADOC
|
|
174
|
+
|
|
175
|
+
core = parse_to_core(adoc)
|
|
176
|
+
xrefs = find_all_xrefs(core)
|
|
177
|
+
expect(xrefs.length).to be >= 1
|
|
178
|
+
expect(xrefs.first.target).to eq('introduction')
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
it 'parses cross-reference with text <<id,text>>' do
|
|
182
|
+
skip 'Cross-reference parsing not yet implemented'
|
|
183
|
+
adoc = <<~ADOC
|
|
184
|
+
= Doc
|
|
185
|
+
|
|
186
|
+
See <<introduction,Introduction>> for details.
|
|
187
|
+
ADOC
|
|
188
|
+
|
|
189
|
+
core = parse_to_core(adoc)
|
|
190
|
+
xrefs = find_all_xrefs(core)
|
|
191
|
+
expect(xrefs.length).to be >= 1
|
|
192
|
+
expect(xrefs.first.target).to eq('introduction')
|
|
193
|
+
expect(xrefs.first.content).to eq('Introduction')
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it 'parses multiple cross-references' do
|
|
197
|
+
skip 'Cross-reference parsing not yet implemented'
|
|
198
|
+
adoc = <<~ADOC
|
|
199
|
+
= Doc
|
|
200
|
+
|
|
201
|
+
See <<section-a>> and <<section-b,Section B>>.
|
|
202
|
+
ADOC
|
|
203
|
+
|
|
204
|
+
core = parse_to_core(adoc)
|
|
205
|
+
xrefs = find_all_xrefs(core)
|
|
206
|
+
expect(xrefs.length).to be >= 2
|
|
207
|
+
targets = xrefs.map(&:target)
|
|
208
|
+
expect(targets).to include('section-a', 'section-b')
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
describe 'Fix 06: Section hierarchy — bold in list items' do
|
|
213
|
+
it 'parses ordered list items starting with bold formatting' do
|
|
214
|
+
ast = parse_to_ast(". *First* text\n")
|
|
215
|
+
doc_nodes = ast[:document]
|
|
216
|
+
lists = doc_nodes.select { |n| n.is_a?(Hash) && n.key?(:list) }
|
|
217
|
+
expect(lists.length).to eq(1)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it 'parses ordered list items starting with bold inside a section' do
|
|
221
|
+
adoc = <<~ADOC
|
|
222
|
+
== Section
|
|
223
|
+
|
|
224
|
+
. *Bold item* — description
|
|
225
|
+
. Normal item
|
|
226
|
+
ADOC
|
|
227
|
+
|
|
228
|
+
ast = parse_to_ast(adoc)
|
|
229
|
+
section = ast[:document].find { |n| n.is_a?(Hash) && n.key?(:section) }
|
|
230
|
+
contents = section[:section][:contents]
|
|
231
|
+
lists = contents.select { |n| n.is_a?(Hash) && n.key?(:list) }
|
|
232
|
+
expect(lists.length).to eq(1)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
it 'parses source blocks with YAML delimiters inside' do
|
|
236
|
+
adoc = <<~ADOC
|
|
237
|
+
= Doc
|
|
238
|
+
|
|
239
|
+
[source]
|
|
240
|
+
----
|
|
241
|
+
---
|
|
242
|
+
frontmatter
|
|
243
|
+
---
|
|
244
|
+
----
|
|
245
|
+
ADOC
|
|
246
|
+
|
|
247
|
+
core = parse_to_core(adoc)
|
|
248
|
+
expect(core).to be_a(Coradoc::CoreModel::StructuralElement)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
it 'does not let highlight unconstrained match across lines' do
|
|
252
|
+
ast = parse_to_ast("## heading\nSome text\n")
|
|
253
|
+
doc_nodes = ast[:document]
|
|
254
|
+
paragraphs = doc_nodes.select { |n| n.is_a?(Hash) && n.key?(:paragraph) }
|
|
255
|
+
highlight_nodes = paragraphs.select do |p|
|
|
256
|
+
text = p[:paragraph]
|
|
257
|
+
text.to_s.include?('highlight')
|
|
258
|
+
end
|
|
259
|
+
expect(highlight_nodes).to be_empty
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
describe 'Fix 08: List marker_type' do
|
|
264
|
+
it 'sets marker_type to unordered for bullet lists' do
|
|
265
|
+
adoc = <<~ADOC
|
|
266
|
+
= Doc
|
|
267
|
+
|
|
268
|
+
* Item one
|
|
269
|
+
* Item two
|
|
270
|
+
* Item three
|
|
271
|
+
ADOC
|
|
272
|
+
|
|
273
|
+
core = parse_to_core(adoc)
|
|
274
|
+
lists = find_all_lists(core)
|
|
275
|
+
expect(lists).not_to be_empty
|
|
276
|
+
expect(lists.first.marker_type).to eq('unordered')
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
it 'sets marker_type to ordered for numbered lists' do
|
|
280
|
+
adoc = <<~ADOC
|
|
281
|
+
= Doc
|
|
282
|
+
|
|
283
|
+
. First item
|
|
284
|
+
. Second item
|
|
285
|
+
. Third item
|
|
286
|
+
ADOC
|
|
287
|
+
|
|
288
|
+
core = parse_to_core(adoc)
|
|
289
|
+
lists = find_all_lists(core)
|
|
290
|
+
expect(lists).not_to be_empty
|
|
291
|
+
expect(lists.first.marker_type).to eq('ordered')
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
private
|
|
296
|
+
|
|
297
|
+
def find_first_table(el)
|
|
298
|
+
return el if el.is_a?(Coradoc::CoreModel::Table)
|
|
299
|
+
return nil unless el.is_a?(Coradoc::CoreModel::Base)
|
|
300
|
+
|
|
301
|
+
if el.class.attributes.key?(:children) && el.children
|
|
302
|
+
el.children.each do |child|
|
|
303
|
+
result = find_first_table(child)
|
|
304
|
+
return result if result
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
nil
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def find_all_xrefs(el)
|
|
312
|
+
xrefs = []
|
|
313
|
+
return xrefs unless el
|
|
314
|
+
|
|
315
|
+
xrefs << el if el.is_a?(Coradoc::CoreModel::InlineElement) && el.format_type == 'xref'
|
|
316
|
+
|
|
317
|
+
children = if el.respond_to?(:children) && el.children
|
|
318
|
+
el.children
|
|
319
|
+
elsif el.is_a?(Coradoc::CoreModel::Base) && el.class.attributes.key?(:children)
|
|
320
|
+
el.children
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
if children
|
|
324
|
+
children.each { |c| xrefs.concat(find_all_xrefs(c)) }
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
xrefs
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def find_all_lists(el)
|
|
331
|
+
lists = []
|
|
332
|
+
return lists unless el
|
|
333
|
+
|
|
334
|
+
lists << el if el.is_a?(Coradoc::CoreModel::ListBlock)
|
|
335
|
+
|
|
336
|
+
if el.is_a?(Coradoc::CoreModel::ListBlock) && el.items
|
|
337
|
+
el.items.each { |c| lists.concat(find_all_lists(c)) }
|
|
338
|
+
elsif el.is_a?(Coradoc::CoreModel::Base) && el.class.attributes.key?(:children) && el.children
|
|
339
|
+
el.children.each { |c| lists.concat(find_all_lists(c)) }
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
lists
|
|
343
|
+
end
|
|
344
|
+
end
|
|
@@ -21,7 +21,7 @@ RSpec.describe 'AsciiDoc List Continuation' do
|
|
|
21
21
|
expect(result).to be_a(Coradoc::AsciiDoc::Model::Document)
|
|
22
22
|
|
|
23
23
|
# Navigate to the list
|
|
24
|
-
contents = result.
|
|
24
|
+
contents = result.is_a?(Coradoc::AsciiDoc::Model::Document) ? result.sections : result.contents
|
|
25
25
|
list = contents.first
|
|
26
26
|
expect(list).to be_a(Coradoc::AsciiDoc::Model::List::Unordered)
|
|
27
27
|
|
|
@@ -33,10 +33,24 @@ RSpec.describe Coradoc::AsciiDoc::Model::Base do
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
describe '#to_adoc' do
|
|
36
|
-
it '
|
|
36
|
+
it 'serializes via ElementRegistry' do
|
|
37
37
|
instance = test_class.new(name: 'test')
|
|
38
38
|
|
|
39
|
-
expect(instance).to
|
|
39
|
+
expect(instance).to be_a(described_class)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe 'element classification' do
|
|
44
|
+
it 'defaults block_level? to false' do
|
|
45
|
+
instance = test_class.new
|
|
46
|
+
|
|
47
|
+
expect(instance.block_level?).to be(false)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'defaults inline? to false' do
|
|
51
|
+
instance = test_class.new
|
|
52
|
+
|
|
53
|
+
expect(instance.inline?).to be(false)
|
|
40
54
|
end
|
|
41
55
|
end
|
|
42
56
|
|