coradoc-markdown 1.0.2 → 1.0.3

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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/lib/coradoc/markdown/model/admonition.rb +35 -0
  3. data/lib/coradoc/markdown/model/comment.rb +14 -0
  4. data/lib/coradoc/markdown/model/definition_term.rb +16 -6
  5. data/lib/coradoc/markdown/model/document.rb +9 -0
  6. data/lib/coradoc/markdown/model/example_block.rb +26 -0
  7. data/lib/coradoc/markdown/model/hard_line_break.rb +21 -0
  8. data/lib/coradoc/markdown/model/horizontal_rule.rb +1 -6
  9. data/lib/coradoc/markdown/model/list_item.rb +1 -1
  10. data/lib/coradoc/markdown/model/literal.rb +21 -0
  11. data/lib/coradoc/markdown/model/open_block.rb +22 -0
  12. data/lib/coradoc/markdown/model/paragraph.rb +2 -2
  13. data/lib/coradoc/markdown/model/pass.rb +21 -0
  14. data/lib/coradoc/markdown/model/sidebar.rb +22 -0
  15. data/lib/coradoc/markdown/model/verse.rb +27 -0
  16. data/lib/coradoc/markdown/parser/block_parser.rb +1 -1
  17. data/lib/coradoc/markdown/parser/frontmatter_parser.rb +23 -0
  18. data/lib/coradoc/markdown/serializer/builder.rb +57 -0
  19. data/lib/coradoc/markdown/serializer/config.rb +77 -0
  20. data/lib/coradoc/markdown/serializer/context.rb +66 -0
  21. data/lib/coradoc/markdown/serializer/element_serializer.rb +46 -0
  22. data/lib/coradoc/markdown/serializer/flavor.rb +82 -0
  23. data/lib/coradoc/markdown/serializer/registrations.rb +111 -0
  24. data/lib/coradoc/markdown/serializer/registry.rb +62 -0
  25. data/lib/coradoc/markdown/serializer/runner.rb +84 -0
  26. data/lib/coradoc/markdown/serializer/serializers/abbreviation.rb +19 -0
  27. data/lib/coradoc/markdown/serializer/serializers/admonition.rb +20 -0
  28. data/lib/coradoc/markdown/serializer/serializers/attribute_list.rb +25 -0
  29. data/lib/coradoc/markdown/serializer/serializers/blockquote.rb +19 -0
  30. data/lib/coradoc/markdown/serializer/serializers/code.rb +19 -0
  31. data/lib/coradoc/markdown/serializer/serializers/code_block.rb +19 -0
  32. data/lib/coradoc/markdown/serializer/serializers/comment.rb +31 -0
  33. data/lib/coradoc/markdown/serializer/serializers/cross_reference.rb +19 -0
  34. data/lib/coradoc/markdown/serializer/serializers/definition_list.rb +20 -0
  35. data/lib/coradoc/markdown/serializer/serializers/document.rb +22 -0
  36. data/lib/coradoc/markdown/serializer/serializers/emphasis.rb +19 -0
  37. data/lib/coradoc/markdown/serializer/serializers/example_block.rb +40 -0
  38. data/lib/coradoc/markdown/serializer/serializers/extension.rb +30 -0
  39. data/lib/coradoc/markdown/serializer/serializers/footnote.rb +20 -0
  40. data/lib/coradoc/markdown/serializer/serializers/footnote_reference.rb +19 -0
  41. data/lib/coradoc/markdown/serializer/serializers/hard_line_break.rb +22 -0
  42. data/lib/coradoc/markdown/serializer/serializers/heading.rb +19 -0
  43. data/lib/coradoc/markdown/serializer/serializers/highlight.rb +19 -0
  44. data/lib/coradoc/markdown/serializer/serializers/horizontal_rule.rb +19 -0
  45. data/lib/coradoc/markdown/serializer/serializers/image.rb +19 -0
  46. data/lib/coradoc/markdown/serializer/serializers/link.rb +29 -0
  47. data/lib/coradoc/markdown/serializer/serializers/list.rb +45 -0
  48. data/lib/coradoc/markdown/serializer/serializers/literal.rb +21 -0
  49. data/lib/coradoc/markdown/serializer/serializers/math.rb +23 -0
  50. data/lib/coradoc/markdown/serializer/serializers/open_block.rb +38 -0
  51. data/lib/coradoc/markdown/serializer/serializers/paragraph.rb +23 -0
  52. data/lib/coradoc/markdown/serializer/serializers/pass.rb +21 -0
  53. data/lib/coradoc/markdown/serializer/serializers/sidebar.rb +20 -0
  54. data/lib/coradoc/markdown/serializer/serializers/strikethrough.rb +19 -0
  55. data/lib/coradoc/markdown/serializer/serializers/strong.rb +19 -0
  56. data/lib/coradoc/markdown/serializer/serializers/subscript.rb +19 -0
  57. data/lib/coradoc/markdown/serializer/serializers/superscript.rb +19 -0
  58. data/lib/coradoc/markdown/serializer/serializers/table.rb +37 -0
  59. data/lib/coradoc/markdown/serializer/serializers/underline.rb +19 -0
  60. data/lib/coradoc/markdown/serializer/serializers/verse.rb +31 -0
  61. data/lib/coradoc/markdown/serializer/strategies/admonition/base.rb +30 -0
  62. data/lib/coradoc/markdown/serializer/strategies/admonition/container.rb +34 -0
  63. data/lib/coradoc/markdown/serializer/strategies/admonition/gfm_alert.rb +28 -0
  64. data/lib/coradoc/markdown/serializer/strategies/admonition/github.rb +29 -0
  65. data/lib/coradoc/markdown/serializer/strategies/admonition/html.rb +25 -0
  66. data/lib/coradoc/markdown/serializer/strategies/admonition/registry.rb +50 -0
  67. data/lib/coradoc/markdown/serializer/strategies/autolink/angle.rb +35 -0
  68. data/lib/coradoc/markdown/serializer/strategies/autolink/bare.rb +23 -0
  69. data/lib/coradoc/markdown/serializer/strategies/autolink/base.rb +33 -0
  70. data/lib/coradoc/markdown/serializer/strategies/autolink/none.rb +27 -0
  71. data/lib/coradoc/markdown/serializer/strategies/autolink/registry.rb +58 -0
  72. data/lib/coradoc/markdown/serializer/strategies/definition_list/base.rb +37 -0
  73. data/lib/coradoc/markdown/serializer/strategies/definition_list/flat.rb +48 -0
  74. data/lib/coradoc/markdown/serializer/strategies/definition_list/nested_html.rb +54 -0
  75. data/lib/coradoc/markdown/serializer/strategies/definition_list/registry.rb +37 -0
  76. data/lib/coradoc/markdown/serializer.rb +29 -243
  77. data/lib/coradoc/markdown/toc_generator.rb +2 -2
  78. data/lib/coradoc/markdown/transform/block_transformer.rb +163 -0
  79. data/lib/coradoc/markdown/transform/from_core_model.rb +187 -28
  80. data/lib/coradoc/markdown/transform/image_transformer.rb +20 -0
  81. data/lib/coradoc/markdown/transform/inline_transformer.rb +74 -0
  82. data/lib/coradoc/markdown/transform/list_transformer.rb +52 -0
  83. data/lib/coradoc/markdown/transform/structural_transformer.rb +94 -0
  84. data/lib/coradoc/markdown/transform/table_transformer.rb +40 -0
  85. data/lib/coradoc/markdown/transform/to_core_model.rb +24 -3
  86. data/lib/coradoc/markdown/transformer.rb +87 -2
  87. data/lib/coradoc/markdown/version.rb +1 -1
  88. data/lib/coradoc/markdown.rb +16 -2
  89. metadata +75 -1
@@ -20,6 +20,9 @@ module Coradoc
20
20
  when Coradoc::CoreModel::AnnotationBlock
21
21
  # Must be checked before Block since AnnotationBlock < Block
22
22
  transform_annotation_block(model)
23
+ when Coradoc::CoreModel::CommentBlock
24
+ # Must be checked before Block since CommentBlock < Block
25
+ Coradoc::Markdown::Comment.new(text: model.content.to_s)
23
26
  when Coradoc::CoreModel::Block
24
27
  transform_block(model)
25
28
  when Coradoc::CoreModel::ListBlock
@@ -48,8 +51,12 @@ module Coradoc
48
51
  transform_bibliography_entry(model)
49
52
  when Coradoc::CoreModel::TocEntry
50
53
  Coradoc::Markdown::Text.new(content: model.title.to_s)
54
+ when Coradoc::CoreModel::CommentLine
55
+ Coradoc::Markdown::Comment.new(text: model.text.to_s)
56
+ when Coradoc::CoreModel::TextContent
57
+ model.text.to_s
51
58
  when Array
52
- model.map { |item| transform(item) }
59
+ model.flat_map { |item| flatten_result(transform(item)) }
53
60
  else
54
61
  model
55
62
  end
@@ -69,23 +76,39 @@ module Coradoc
69
76
  end
70
77
 
71
78
  def transform_document(doc)
72
- blocks = Array(doc.children).map { |child| transform(child) }
79
+ blocks, frontmatter = extract_frontmatter(Array(doc.children))
80
+ blocks = blocks.flat_map { |child| flatten_result(transform(child)) }
73
81
 
74
82
  Coradoc::Markdown::Document.new(
75
83
  id: doc.id,
76
- blocks: blocks
84
+ blocks: blocks,
85
+ frontmatter: frontmatter
77
86
  )
78
87
  end
79
88
 
89
+ # If the first CoreModel child is a FrontmatterBlock, serialize
90
+ # it to YAML text via Codec (single source of truth) and pop
91
+ # it from the children list. Returns [remaining_children,
92
+ # frontmatter_text].
93
+ def extract_frontmatter(children)
94
+ first = children.first
95
+ return [children, nil] unless first.is_a?(CoreModel::FrontmatterBlock)
96
+
97
+ yaml = CoreModel::FrontmatterBlock::Codec.to_yaml(first)
98
+ [children.drop(1), yaml.nil? || yaml.empty? ? nil : yaml]
99
+ end
100
+
80
101
  def transform_section(section)
81
- Coradoc::Markdown::Heading.new(
102
+ heading = Coradoc::Markdown::Heading.new(
82
103
  level: section.level || 1,
83
104
  text: section.title.to_s
84
105
  )
106
+ child_blocks = Array(section.children).map { |child| transform(child) }
107
+ [heading, *child_blocks]
85
108
  end
86
109
 
87
110
  def transform_generic_element(element)
88
- blocks = Array(element.children).map { |child| transform(child) }
111
+ blocks = Array(element.children).flat_map { |child| flatten_result(transform(child)) }
89
112
 
90
113
  Coradoc::Markdown::Document.new(
91
114
  id: element.id,
@@ -99,7 +122,7 @@ module Coradoc
99
122
  when :paragraph
100
123
  transform_paragraph(block)
101
124
  when :comment
102
- Coradoc::Markdown::Extension.comment(block.content.to_s)
125
+ Coradoc::Markdown::Comment.new(text: block.content.to_s)
103
126
  else
104
127
  transform_delimited_block(block)
105
128
  end
@@ -107,8 +130,17 @@ module Coradoc
107
130
 
108
131
  def transform_paragraph(block)
109
132
  content = block.renderable_content
110
- has_structured = content.is_a?(Array) && content.any? { |c| !c.is_a?(CoreModel::TextContent) }
111
- if has_structured
133
+ has_nested_blocks = content.is_a?(Array) && content.any? do |c|
134
+ c.is_a?(CoreModel::Block) || c.is_a?(CoreModel::StructuralElement)
135
+ end
136
+ if has_nested_blocks
137
+ content.filter_map do |c|
138
+ next c.text if c.is_a?(CoreModel::TextContent)
139
+ next nil if c.is_a?(String) && c.strip.empty?
140
+
141
+ transform(c)
142
+ end.flat_map { |c| flatten_result(c) }
143
+ elsif content.is_a?(Array) && content.any? { |c| !c.is_a?(CoreModel::TextContent) }
112
144
  children = content.map { |c| transform_inline_content(c) }
113
145
  Coradoc::Markdown::Paragraph.new(text: block.flat_text, children: children)
114
146
  else
@@ -122,8 +154,12 @@ module Coradoc
122
154
  transform_inline(element)
123
155
  when CoreModel::TextContent
124
156
  element.text
157
+ when CoreModel::Base
158
+ transform(element)
125
159
  when String
126
160
  element
161
+ when Array
162
+ element.map { |e| transform_inline_content(e) }
127
163
  else
128
164
  element.to_s
129
165
  end
@@ -135,19 +171,97 @@ module Coradoc
135
171
  case semantic
136
172
  when :source_code, :listing
137
173
  transform_code_block(block)
138
- when :quote, :verse
174
+ when :quote
139
175
  transform_blockquote(block)
176
+ when :verse
177
+ transform_verse_block(block)
140
178
  when :horizontal_rule
141
179
  transform_horizontal_rule(block)
142
180
  when :pass
143
- Coradoc::Markdown::Extension.nomarkdown(block.content.to_s)
181
+ transform_pass_block(block)
144
182
  when :literal
145
- transform_code_block(block)
183
+ transform_literal_block(block)
184
+ when :example
185
+ transform_example_block(block)
186
+ when :sidebar
187
+ transform_sidebar_block(block)
188
+ when :open
189
+ transform_open_block(block)
190
+ when :reviewer
191
+ transform_admonition_block(block, default_type: 'reviewer')
146
192
  else
147
193
  transform_paragraph(block)
148
194
  end
149
195
  end
150
196
 
197
+ def transform_container_block(block)
198
+ rc = block.renderable_content
199
+ if rc.is_a?(Array) && rc.any? { |c| c.is_a?(CoreModel::Block) || c.is_a?(CoreModel::ListBlock) }
200
+ rc.filter_map do |c|
201
+ next c.text if c.is_a?(CoreModel::TextContent)
202
+ next nil if c.is_a?(String) && c.strip.empty?
203
+
204
+ transform(c)
205
+ end.flat_map { |c| flatten_result(c) }
206
+ else
207
+ Coradoc::Markdown::Blockquote.new(content: block.flat_text)
208
+ end
209
+ end
210
+
211
+ def transform_verse_block(block)
212
+ Coradoc::Markdown::Verse.new(
213
+ content: block.flat_text,
214
+ attribution: block.respond_to?(:attribution) ? block.attribution : nil
215
+ )
216
+ end
217
+
218
+ def transform_pass_block(block)
219
+ return Coradoc::Markdown::Math.block(block.content.to_s) if block.language == 'latexmath'
220
+
221
+ Coradoc::Markdown::Pass.new(content: block.content.to_s)
222
+ end
223
+
224
+ def transform_literal_block(block)
225
+ Coradoc::Markdown::Literal.new(content: block.content.to_s)
226
+ end
227
+
228
+ def transform_example_block(block)
229
+ Coradoc::Markdown::ExampleBlock.new(
230
+ content: block.flat_text,
231
+ caption: block.title.to_s
232
+ )
233
+ end
234
+
235
+ def transform_sidebar_block(block)
236
+ Coradoc::Markdown::Sidebar.new(
237
+ content: block.flat_text,
238
+ title: block.title.to_s
239
+ )
240
+ end
241
+
242
+ def transform_open_block(block)
243
+ children = Array(block.children).map { |c| transform(c) }.flat_map { |c| flatten_result(c) }
244
+ children = paragraphs_from_content(block.content) if children.empty?
245
+ Coradoc::Markdown::OpenBlock.new(
246
+ children: children,
247
+ id: block.id
248
+ )
249
+ end
250
+
251
+ def paragraphs_from_content(content)
252
+ content.to_s.split(/\n\s*\n/).map do |chunk|
253
+ Coradoc::Markdown::Paragraph.new(text: chunk.strip)
254
+ end
255
+ end
256
+
257
+ def transform_admonition_block(block, default_type: 'note')
258
+ Coradoc::Markdown::Admonition.new(
259
+ admonition_type: block.respond_to?(:annotation_type) ? (block.annotation_type || default_type) : default_type,
260
+ content: block.flat_text,
261
+ title: block.respond_to?(:annotation_label) ? block.annotation_label : nil
262
+ )
263
+ end
264
+
151
265
  def resolve_markdown_semantic(block)
152
266
  # Polymorphic dispatch: typed classes override semantic_type
153
267
  semantic = block.resolve_semantic_type
@@ -167,10 +281,21 @@ module Coradoc
167
281
  end
168
282
 
169
283
  def transform_code_block(block)
170
- Coradoc::Markdown::CodeBlock.new(
171
- code: block.content.to_s,
284
+ code = CoreModel::CalloutText.strip_markers(block.content.to_s, block.callouts)
285
+ code_block = Coradoc::Markdown::CodeBlock.new(
286
+ code: code,
172
287
  language: block.language
173
288
  )
289
+ return code_block if block.callouts.nil? || block.callouts.empty?
290
+
291
+ [code_block, transform_callout_list(block.callouts)]
292
+ end
293
+
294
+ def transform_callout_list(callouts)
295
+ items = CoreModel::CalloutText.ordered(callouts).map do |callout|
296
+ Coradoc::Markdown::ListItem.new(text: callout.content.to_s)
297
+ end
298
+ Coradoc::Markdown::List.new(ordered: true, items: items)
174
299
  end
175
300
 
176
301
  def transform_blockquote(block)
@@ -180,19 +305,21 @@ module Coradoc
180
305
  end
181
306
 
182
307
  def transform_horizontal_rule(_block)
183
- Coradoc::Markdown::HorizontalRule.new
308
+ Coradoc::Markdown::HorizontalRule.new(style: '---')
184
309
  end
185
310
 
186
311
  def transform_list(list)
187
312
  items = Array(list.items).map do |item|
188
313
  content = item.renderable_content
189
314
  has_structured = content.is_a?(Array) && content.any? { |c| !c.is_a?(CoreModel::TextContent) }
190
- if has_structured
191
- children = content.map { |c| transform_inline_content(c) }
192
- Coradoc::Markdown::ListItem.new(text: item.flat_text, children: children)
193
- else
194
- Coradoc::Markdown::ListItem.new(text: item.flat_text)
195
- end
315
+ md_item = if has_structured
316
+ children = content.map { |c| transform_inline_content(c) }
317
+ Coradoc::Markdown::ListItem.new(text: item.flat_text, children: children)
318
+ else
319
+ Coradoc::Markdown::ListItem.new(text: item.flat_text)
320
+ end
321
+ md_item.sublist = transform_list(item.nested_list) if item.nested_list
322
+ md_item
196
323
  end
197
324
 
198
325
  Coradoc::Markdown::List.new(
@@ -211,8 +338,8 @@ module Coradoc
211
338
  first_row = table_rows.first
212
339
  first_row_cells = Array(first_row&.cells)
213
340
 
214
- # Check if first row has header cells
215
- if first_row_cells.any?(&:header)
341
+ # Check if first row is marked as header, or if any of its cells are header cells
342
+ if first_row&.header || first_row_cells.any?(&:header)
216
343
  headers = first_row_cells.map(&:flat_text)
217
344
  table_rows = table_rows[1..] || []
218
345
  end
@@ -278,9 +405,11 @@ module Coradoc
278
405
  definitions = Array(item.definitions).map do |defn|
279
406
  Coradoc::Markdown::DefinitionItem.new(content: defn.to_s)
280
407
  end
408
+ nested = item.nested ? transform_definition_list(item.nested) : nil
281
409
  Coradoc::Markdown::DefinitionTerm.new(
282
410
  text: item.term.to_s,
283
- definitions: definitions
411
+ definitions: definitions,
412
+ nested: nested
284
413
  )
285
414
  end
286
415
 
@@ -307,10 +436,7 @@ module Coradoc
307
436
  end
308
437
 
309
438
  def transform_annotation_block(annotation)
310
- text = annotation.flat_text
311
- Coradoc::Markdown::Paragraph.new(
312
- text: "**#{annotation.annotation_type}:** #{text}"
313
- )
439
+ transform_admonition_block(annotation, default_type: 'note')
314
440
  end
315
441
 
316
442
  def transform_bibliography(bib)
@@ -322,7 +448,40 @@ module Coradoc
322
448
  end
323
449
 
324
450
  def transform_bibliography_entry(entry)
325
- Coradoc::Markdown::Paragraph.new(text: entry.display_text)
451
+ text = entry.display_text.to_s
452
+ text = convert_asciidoc_markers_to_markdown(text)
453
+ Coradoc::Markdown::Paragraph.new(text: text)
454
+ end
455
+
456
+ def convert_asciidoc_markers_to_markdown(text)
457
+ return '' if text.nil?
458
+
459
+ result = text.dup
460
+
461
+ # footnote:[text] -> ^[text]
462
+ result.gsub!(/footnote:\[(.*?)\]/, '^[\1]')
463
+
464
+ # [smallcap]#text# -> text
465
+ result.gsub!(/\[smallcap\]#(.*?)#/, '\1')
466
+
467
+ # *text* -> **text**
468
+ # Use negative lookbehind and lookahead to avoid replacing **text** to ****text****
469
+ result.gsub!(/(?<!\*)\*(.*?)\*(?!\*)/, '**\1**')
470
+
471
+ # _text_ -> *text*
472
+ # Use negative lookbehind and lookahead to avoid replacing __text__ to **text**
473
+ result.gsub!(/(?<!_)_(.*?)_(?!_)/, '*\1*')
474
+
475
+ result
476
+ end
477
+
478
+ def flatten_result(result)
479
+ case result
480
+ when Array
481
+ result.flat_map { |r| flatten_result(r) }
482
+ else
483
+ [result]
484
+ end
326
485
  end
327
486
  end
328
487
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Markdown
5
+ module Transform
6
+ module ImageTransformer
7
+ class << self
8
+ def transform_image(image)
9
+ Coradoc::Markdown::Image.new(
10
+ src: image.src,
11
+ alt: image.alt.to_s
12
+ )
13
+ end
14
+ end
15
+
16
+ FromCoreModel.register(CoreModel::Image, method(:transform_image))
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Markdown
5
+ module Transform
6
+ module InlineTransformer
7
+ class << self
8
+ def transform_inline(element)
9
+ case element.resolve_format_type
10
+ when 'bold'
11
+ Coradoc::Markdown::Strong.new(text: element.content.to_s)
12
+ when 'italic'
13
+ Coradoc::Markdown::Emphasis.new(text: element.content.to_s)
14
+ when 'monospace'
15
+ Coradoc::Markdown::Code.new(text: element.content.to_s)
16
+ when 'link'
17
+ Coradoc::Markdown::Link.new(
18
+ text: element.content.to_s,
19
+ url: element.target.to_s
20
+ )
21
+ when 'footnote'
22
+ Coradoc::Markdown::FootnoteReference.new(id: element.target.to_s)
23
+ when 'stem'
24
+ Coradoc::Markdown::Math.inline(element.content.to_s)
25
+ when 'highlight'
26
+ Coradoc::Markdown::Highlight.new(text: element.content.to_s)
27
+ when 'strikethrough'
28
+ Coradoc::Markdown::Strikethrough.new(text: element.content.to_s)
29
+ when 'subscript'
30
+ Coradoc::Markdown::Subscript.new(text: element.content.to_s)
31
+ when 'superscript'
32
+ Coradoc::Markdown::Superscript.new(text: element.content.to_s)
33
+ when 'underline'
34
+ Coradoc::Markdown::Underline.new(text: element.content.to_s)
35
+ when 'xref'
36
+ Coradoc::Markdown::CrossReference.new(
37
+ text: element.content.to_s,
38
+ target: element.target.to_s
39
+ )
40
+ else
41
+ element.content.to_s
42
+ end
43
+ end
44
+
45
+ def transform_footnote(fn)
46
+ Coradoc::Markdown::Footnote.new(
47
+ id: fn.id.to_s,
48
+ content: fn.content.to_s,
49
+ backlink: fn.backlink
50
+ )
51
+ end
52
+
53
+ def transform_footnote_reference(ref)
54
+ Coradoc::Markdown::FootnoteReference.new(id: ref.id.to_s)
55
+ end
56
+
57
+ def transform_abbreviation(abbr)
58
+ Coradoc::Markdown::Abbreviation.new(
59
+ term: abbr.term.to_s,
60
+ definition: abbr.definition.to_s
61
+ )
62
+ end
63
+ end
64
+
65
+ FromCoreModel.register(CoreModel::InlineElement, method(:transform_inline))
66
+ FromCoreModel.register(CoreModel::Footnote, method(:transform_footnote))
67
+ FromCoreModel.register(CoreModel::FootnoteReference, method(:transform_footnote_reference))
68
+ FromCoreModel.register(CoreModel::Abbreviation, method(:transform_abbreviation))
69
+ FromCoreModel.register(CoreModel::TextContent, lambda(&:text))
70
+ FromCoreModel.register(CoreModel::Term, ->(m) { Coradoc::Markdown::Strong.new(text: m.text.to_s) })
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Markdown
5
+ module Transform
6
+ module ListTransformer
7
+ class << self
8
+ def transform_list(list)
9
+ items = Array(list.items).map do |item|
10
+ transform_list_item(item)
11
+ end
12
+
13
+ Coradoc::Markdown::List.new(
14
+ ordered: list.marker_type == 'ordered',
15
+ items: items
16
+ )
17
+ end
18
+
19
+ def transform_list_item(item)
20
+ content = item.renderable_content
21
+ has_structured = content.is_a?(Array) && content.any? do |c|
22
+ !c.is_a?(CoreModel::TextContent)
23
+ end
24
+ if has_structured
25
+ children = content.map { |c| BlockTransformer.transform_content_node(c) }
26
+ Coradoc::Markdown::ListItem.new(text: item.flat_text, children: children)
27
+ else
28
+ Coradoc::Markdown::ListItem.new(text: item.flat_text)
29
+ end
30
+ end
31
+
32
+ def transform_definition_list(dl)
33
+ items = Array(dl.items).map do |item|
34
+ definitions = Array(item.definitions).map do |defn|
35
+ Coradoc::Markdown::DefinitionItem.new(content: defn.to_s)
36
+ end
37
+ Coradoc::Markdown::DefinitionTerm.new(
38
+ text: item.term.to_s,
39
+ definitions: definitions
40
+ )
41
+ end
42
+
43
+ Coradoc::Markdown::DefinitionList.new(items: items)
44
+ end
45
+ end
46
+
47
+ FromCoreModel.register(CoreModel::ListBlock, method(:transform_list))
48
+ FromCoreModel.register(CoreModel::DefinitionList, method(:transform_definition_list))
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Markdown
5
+ module Transform
6
+ module StructuralTransformer
7
+ class << self
8
+ def transform_structural_element(element)
9
+ case element
10
+ when CoreModel::DocumentElement
11
+ transform_document(element)
12
+ when CoreModel::SectionElement
13
+ transform_section(element)
14
+ when CoreModel::PreambleElement
15
+ transform_preamble(element)
16
+ when CoreModel::HeaderElement
17
+ transform_header(element)
18
+ else
19
+ transform_generic_element(element)
20
+ end
21
+ end
22
+
23
+ def transform_document(doc)
24
+ blocks = Array(doc.children)
25
+ .flat_map { |child| FromCoreModel.flatten_transform_result(FromCoreModel.transform(child)) }
26
+ .compact
27
+
28
+ Coradoc::Markdown::Document.new(
29
+ id: doc.id,
30
+ blocks: blocks
31
+ )
32
+ end
33
+
34
+ def transform_section(section)
35
+ blocks = []
36
+ blocks << Coradoc::Markdown::Heading.new(
37
+ level: section.level || 1,
38
+ text: section.title.to_s
39
+ )
40
+ child_blocks = Array(section.children)
41
+ .flat_map { |child| FromCoreModel.flatten_transform_result(FromCoreModel.transform(child)) }
42
+ .compact
43
+ blocks.concat(child_blocks)
44
+ blocks
45
+ end
46
+
47
+ def transform_preamble(preamble)
48
+ Array(preamble.children)
49
+ .flat_map { |child| FromCoreModel.flatten_transform_result(FromCoreModel.transform(child)) }
50
+ .compact
51
+ end
52
+
53
+ def transform_header(header)
54
+ level = header.level || 1
55
+ Coradoc::Markdown::Heading.new(
56
+ level: level,
57
+ text: header.title.to_s
58
+ )
59
+ end
60
+
61
+ def transform_generic_element(element)
62
+ blocks = Array(element.children)
63
+ .flat_map { |child| FromCoreModel.flatten_transform_result(FromCoreModel.transform(child)) }
64
+ .compact
65
+
66
+ Coradoc::Markdown::Document.new(
67
+ id: element.id,
68
+ blocks: blocks
69
+ )
70
+ end
71
+
72
+ def transform_bibliography(bib)
73
+ entries = Array(bib.entries).map { |e| FromCoreModel.transform(e) }
74
+ blocks = []
75
+ blocks << Coradoc::Markdown::Heading.new(level: 2, text: bib.title.to_s) if bib.title
76
+ blocks.concat(entries)
77
+ Coradoc::Markdown::Document.new(id: bib.id, blocks: blocks)
78
+ end
79
+
80
+ def transform_bibliography_entry(entry)
81
+ FromCoreModel.transform_bibliography_entry(entry)
82
+ end
83
+ end
84
+
85
+ # Register with FromCoreModel
86
+ FromCoreModel.register(CoreModel::StructuralElement, method(:transform_structural_element))
87
+ FromCoreModel.register(CoreModel::Bibliography, method(:transform_bibliography))
88
+ FromCoreModel.register(CoreModel::BibliographyEntry, method(:transform_bibliography_entry))
89
+ FromCoreModel.register(CoreModel::Toc, ->(_m) { Coradoc::Markdown::Extension.toc })
90
+ FromCoreModel.register(CoreModel::TocEntry, ->(m) { Coradoc::Markdown::Text.new(content: m.title.to_s) })
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Markdown
5
+ module Transform
6
+ module TableTransformer
7
+ class << self
8
+ def transform_table(table)
9
+ headers = []
10
+ rows = []
11
+
12
+ table_rows = Array(table.rows)
13
+ if table_rows.any?
14
+ first_row = table_rows.first
15
+ first_row_cells = Array(first_row&.cells)
16
+
17
+ # Check if first row has header cells
18
+ if first_row_cells.any?(&:header)
19
+ headers = first_row_cells.map(&:flat_text)
20
+ table_rows = table_rows[1..] || []
21
+ end
22
+
23
+ # Convert remaining rows to pipe-separated strings
24
+ rows = table_rows.map do |row|
25
+ Array(row.cells).map(&:flat_text).join(' | ')
26
+ end
27
+ end
28
+
29
+ Coradoc::Markdown::Table.new(
30
+ headers: headers,
31
+ rows: rows
32
+ )
33
+ end
34
+ end
35
+
36
+ FromCoreModel.register(CoreModel::Table, method(:transform_table))
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'coradoc/core_model'
3
+ require 'coradoc'
4
4
 
5
5
  module Coradoc
6
6
  module Markdown
@@ -62,6 +62,8 @@ module Coradoc
62
62
  transform_extension(model)
63
63
  when Coradoc::Markdown::AttributeList
64
64
  transform_attribute_list(model)
65
+ when Coradoc::Markdown::Comment
66
+ Coradoc::CoreModel::CommentBlock.new(content: model.text.to_s)
65
67
  when Coradoc::Markdown::Text
66
68
  model.content.to_s
67
69
  when Array
@@ -75,6 +77,7 @@ module Coradoc
75
77
 
76
78
  def transform_document(doc)
77
79
  children = Array(doc.blocks).map { |block| transform(block) }
80
+ children = prepend_frontmatter(children, doc.frontmatter)
78
81
 
79
82
  Coradoc::CoreModel::DocumentElement.new(
80
83
  id: doc.id,
@@ -83,6 +86,22 @@ module Coradoc
83
86
  )
84
87
  end
85
88
 
89
+ # If the Markdown document had raw frontmatter text, parse it
90
+ # via Codec into a typed FrontmatterBlock and prepend it so
91
+ # it participates in the standard block pipeline. Codec is
92
+ # the single source of truth for YAML parsing (DRY/MECE).
93
+ #
94
+ # The block is prepended even when the YAML is malformed —
95
+ # Codec returns an empty FrontmatterBlock in that case, which
96
+ # preserves the signal "frontmatter was present but invalid"
97
+ # for downstream consumers.
98
+ def prepend_frontmatter(children, frontmatter_text)
99
+ return children if frontmatter_text.nil? || frontmatter_text.strip.empty?
100
+
101
+ block = Coradoc::CoreModel::FrontmatterBlock::Codec.from_yaml(frontmatter_text)
102
+ [block, *children]
103
+ end
104
+
86
105
  def transform_heading(heading)
87
106
  Coradoc::CoreModel::SectionElement.new(
88
107
  level: heading.level,
@@ -114,9 +133,11 @@ module Coradoc
114
133
 
115
134
  def transform_list(list)
116
135
  items = Array(list.items).map do |item|
136
+ nested = item.sublist ? transform_list(item.sublist) : nil
117
137
  Coradoc::CoreModel::ListItem.new(
118
138
  content: extract_text(item.text),
119
- marker: list.ordered ? '1.' : '*'
139
+ marker: list.ordered ? '1.' : '*',
140
+ nested_list: nested
120
141
  )
121
142
  end
122
143
 
@@ -144,7 +165,7 @@ module Coradoc
144
165
  cells = if row.is_a?(Array)
145
166
  row.map { |c| Coradoc::CoreModel::TableCell.new(content: c.to_s, header: false) }
146
167
  else
147
- [Coradoc::CoreModel::TableCell.new(content: row.to_s, header: false)]
168
+ row.to_s.split(' | ').map { |c| Coradoc::CoreModel::TableCell.new(content: c.to_s, header: false) }
148
169
  end
149
170
  rows << Coradoc::CoreModel::TableRow.new(cells: cells)
150
171
  end