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.
Files changed (24) hide show
  1. checksums.yaml +4 -4
  2. data/lib/coradoc/asciidoc/model/base.rb +7 -0
  3. data/lib/coradoc/asciidoc/transform/element_transformers/admonition_styles.rb +6 -1
  4. data/lib/coradoc/asciidoc/transform/element_transformers/block_transformer.rb +39 -16
  5. data/lib/coradoc/asciidoc/transform/element_transformers/document_transformer.rb +2 -27
  6. data/lib/coradoc/asciidoc/transform/element_transformers/include_transformer.rb +2 -1
  7. data/lib/coradoc/asciidoc/transform/element_transformers/inline_transformer.rb +12 -6
  8. data/lib/coradoc/asciidoc/transform/element_transformers/list_transformer.rb +10 -4
  9. data/lib/coradoc/asciidoc/transform/element_transformers/other_transformer.rb +10 -5
  10. data/lib/coradoc/asciidoc/transform/element_transformers/table_transformer.rb +6 -3
  11. data/lib/coradoc/asciidoc/transform/from_core_model.rb +1 -28
  12. data/lib/coradoc/asciidoc/transform/inline_transform_visitor.rb +23 -5
  13. data/lib/coradoc/asciidoc/transform/to_core_model.rb +30 -6
  14. data/lib/coradoc/asciidoc/transformer/block_rules.rb +13 -3
  15. data/lib/coradoc/asciidoc/transformer/header_rules.rb +12 -3
  16. data/lib/coradoc/asciidoc/transformer/inline_rules.rb +63 -19
  17. data/lib/coradoc/asciidoc/transformer/list_rules.rb +20 -5
  18. data/lib/coradoc/asciidoc/transformer/misc_rules.rb +31 -12
  19. data/lib/coradoc/asciidoc/transformer/source_line_extractor.rb +67 -0
  20. data/lib/coradoc/asciidoc/transformer/structural_rules.rb +7 -3
  21. data/lib/coradoc/asciidoc/transformer/text_rules.rb +33 -9
  22. data/lib/coradoc/asciidoc/transformer.rb +6 -1
  23. data/lib/coradoc/asciidoc/version.rb +1 -1
  24. metadata +2 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 69bc04902f8020decc17928a5397de2876f060ed8ee36de95f0920ba6312e8b4
4
- data.tar.gz: ca56574d1b78863a574551732397ff65944f2b012781def3bb4ea4bcf8b0aaf1
3
+ metadata.gz: d135f788f7205a03a242f7d1e9242b84fdc76994f6eff372579db29c9c420d51
4
+ data.tar.gz: 6eafdb8eb6a101650e48e6a29d5e719f729d8c7888c506a5e67a33511640db2f
5
5
  SHA512:
6
- metadata.gz: 708cf53d7a9c6de58e15aac99a1e77343e004b7d014697335cd91f96143584b0701235e84030948a5ab6a09eba8612a361b215b1be9707ca07943d3f7179ad09
7
- data.tar.gz: ff60d697d4fff0ec229ca5b597f1fe2f37d34129bff79f10276b72824ecd418f770d9229800dee6f143f95345af0c1432aceb100385b6c578fd08abb01a3767a
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
- BUILTIN = %w[note tip warning caution important editor todo].freeze
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
- non_break_lines = Array(block.lines).reject do |line|
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: content_lines,
37
- language: language_override || ToCoreModel.extract_block_language(block)
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 native class).
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
- content_lines = ToCoreModel.extract_block_lines(block)
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: content_lines,
192
- language: ToCoreModel.extract_block_language(block)
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
 
@@ -21,7 +21,8 @@ module Coradoc
21
21
  target: model.path.to_s,
22
22
  options: options,
23
23
  raw_options: raw,
24
- line_break: model.line_break.to_s
24
+ line_break: model.line_break.to_s,
25
+ source_line: model.source_line
25
26
  )
26
27
  end
27
28
 
@@ -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(items: items)
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(without_title),
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
- items.each_with_index do |item, idx|
53
+ previous = nil
54
+ items.each do |item|
41
55
  transformed = visit_content(item)
42
56
  next if transformed.empty?
43
57
 
44
- needs_space = idx.positive? &&
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
- non_break_lines = Array(block.lines).reject do |line|
28
- line.is_a?(Coradoc::AsciiDoc::Model::LineBreak) ||
29
- line.is_a?(Coradoc::AsciiDoc::Model::Break::PageBreak)
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
- non_break_lines.map do |line|
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(title: '', lines: example)
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(content: content, type: canonical)
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(id:, title:, author:, revision:)
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(first_name:, last_name:, email:, middle_name: nil)
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(number:, date:, remark:)
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(href:, args:)
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(name:)
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(:_)) do
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(term:, type: term_type, lang: :en)
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(text: text_str)
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(text: text_str, id: id.to_s)
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(text: text_str, id: id.to_s)
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(text: text_str)
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(content: content, unconstrained: false)
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(content: content, unconstrained: true)
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(content:)
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(content:)
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(content: text)
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(items: list_items)
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(items: list_items, attrs: attribute_list)
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(items: list_items)
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(items: list_items, attrs: attribute_list)
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(text: comment_text, line_break: line_break)
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(text: comment_text)
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.to_s
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
- if a.is_a?(String)
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
- elsif a.is_a?(Model::Attribute)
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(content: text.to_s)
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(content: text.to_s, line_break: line_break)
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(content: text, line_break: line_break)
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(content: text.to_s, id: id.to_s)
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(content: text)
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(line_break:)
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(content: transformed, line_break: line[:line_break])
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
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Coradoc
4
4
  module AsciiDoc
5
- VERSION = '2.0.23'
5
+ VERSION = '2.0.24'
6
6
  end
7
7
  end
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.23
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