coradoc-mirror 0.1.1

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 (40) hide show
  1. checksums.yaml +7 -0
  2. data/lib/coradoc/mirror/core_model_to_mirror.rb +181 -0
  3. data/lib/coradoc/mirror/handler_registry.rb +105 -0
  4. data/lib/coradoc/mirror/handlers/admonition.rb +29 -0
  5. data/lib/coradoc/mirror/handlers/bibliography.rb +43 -0
  6. data/lib/coradoc/mirror/handlers/blockquote.rb +19 -0
  7. data/lib/coradoc/mirror/handlers/code_block.rb +69 -0
  8. data/lib/coradoc/mirror/handlers/comment.rb +14 -0
  9. data/lib/coradoc/mirror/handlers/definition_list.rb +69 -0
  10. data/lib/coradoc/mirror/handlers/example.rb +19 -0
  11. data/lib/coradoc/mirror/handlers/footnote.rb +18 -0
  12. data/lib/coradoc/mirror/handlers/frontmatter.rb +71 -0
  13. data/lib/coradoc/mirror/handlers/generic_block.rb +24 -0
  14. data/lib/coradoc/mirror/handlers/horizontal_rule.rb +14 -0
  15. data/lib/coradoc/mirror/handlers/image.rb +58 -0
  16. data/lib/coradoc/mirror/handlers/inline.rb +213 -0
  17. data/lib/coradoc/mirror/handlers/list.rb +80 -0
  18. data/lib/coradoc/mirror/handlers/open_block.rb +16 -0
  19. data/lib/coradoc/mirror/handlers/paragraph.rb +16 -0
  20. data/lib/coradoc/mirror/handlers/reviewer.rb +14 -0
  21. data/lib/coradoc/mirror/handlers/sidebar.rb +19 -0
  22. data/lib/coradoc/mirror/handlers/structural.rb +84 -0
  23. data/lib/coradoc/mirror/handlers/table.rb +82 -0
  24. data/lib/coradoc/mirror/handlers/toc.rb +48 -0
  25. data/lib/coradoc/mirror/handlers/verse.rb +22 -0
  26. data/lib/coradoc/mirror/handlers.rb +38 -0
  27. data/lib/coradoc/mirror/mark.rb +181 -0
  28. data/lib/coradoc/mirror/mark_reverse_builder.rb +142 -0
  29. data/lib/coradoc/mirror/mirror_json_format.rb +42 -0
  30. data/lib/coradoc/mirror/mirror_to_core_model.rb +73 -0
  31. data/lib/coradoc/mirror/mirror_yaml_format.rb +41 -0
  32. data/lib/coradoc/mirror/node.rb +856 -0
  33. data/lib/coradoc/mirror/output.rb +62 -0
  34. data/lib/coradoc/mirror/partitioner.rb +62 -0
  35. data/lib/coradoc/mirror/reverse_builder.rb +600 -0
  36. data/lib/coradoc/mirror/transformer.rb +41 -0
  37. data/lib/coradoc/mirror/version.rb +7 -0
  38. data/lib/coradoc/mirror.rb +161 -0
  39. data/lib/coradoc-mirror.rb +14 -0
  40. metadata +140 -0
@@ -0,0 +1,856 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lutaml/model'
4
+ require_relative 'mark'
5
+
6
+ module Coradoc
7
+ module Mirror
8
+ # ProseMirror-compatible document node.
9
+ #
10
+ # Wire format:
11
+ #
12
+ # { "type": "paragraph", "attrs": {...}, "content": [...], "marks": [...] }
13
+ #
14
+ # All built-in Node subclasses live below in this file so the
15
+ # TYPE_TO_CLASS registry can see every PM_TYPE at load time. Adding
16
+ # a new node type = adding one subclass (+ optional Attrs sub-model)
17
+ # and letting the registry walker pick it up (OCP).
18
+ #
19
+ # The TYPE_TO_CLASS and POLYMORPHIC constants are declared up-front
20
+ # (empty) because subclass `key_value` blocks reference them at class
21
+ # load time. The table is populated and frozen after every subclass
22
+ # is defined at the bottom of this file.
23
+ class Node < Lutaml::Model::Serializable
24
+ PM_TYPE = 'node'
25
+
26
+ TYPE_TO_CLASS = {}
27
+ POLYMORPHIC = { attribute: 'type', class_map: TYPE_TO_CLASS }.freeze
28
+
29
+ attribute :type, :string, default: -> { self.class::PM_TYPE }
30
+ attribute :content, Node, collection: true
31
+ attribute :marks, Mark, collection: true
32
+
33
+ key_value do
34
+ map 'type', to: :type, render_default: true
35
+ map 'content', to: :content, polymorphic: POLYMORPHIC
36
+ map 'marks', to: :marks, polymorphic: Mark::POLYMORPHIC
37
+ end
38
+
39
+ def text_content
40
+ return '' unless content
41
+
42
+ content.select { |c| c.is_a?(Node) }.map(&:text_content).join
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ module Coradoc
49
+ module Mirror
50
+ class Node
51
+ # ── Top-level ──
52
+
53
+ class Document < Node
54
+ PM_TYPE = 'doc'
55
+
56
+ class Attrs < Lutaml::Model::Serializable
57
+ attribute :title, :string
58
+ attribute :id, :string
59
+
60
+ key_value do
61
+ map 'title', to: :title
62
+ map 'id', to: :id
63
+ end
64
+ end
65
+
66
+ attribute :attrs, Attrs
67
+
68
+ key_value do
69
+ map 'type', to: :type, render_default: true
70
+ map 'attrs', to: :attrs
71
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
72
+ end
73
+ end
74
+
75
+ # ── Structural containers ──
76
+
77
+ class Section < Node
78
+ # JS SECTION_TYPES — all deserialize to Section. The type string
79
+ # is preserved as the type attribute on the instance.
80
+ PM_TYPE = 'section'
81
+ PM_ALIASES = %w[
82
+ clause annex content_section abstract foreword introduction
83
+ acknowledgements terms definitions references
84
+ ].freeze
85
+
86
+ class Attrs < Lutaml::Model::Serializable
87
+ attribute :title, :string
88
+ attribute :id, :string
89
+ attribute :level, :integer
90
+
91
+ key_value do
92
+ map 'title', to: :title
93
+ map 'id', to: :id
94
+ map 'level', to: :level
95
+ end
96
+ end
97
+
98
+ attribute :attrs, Attrs
99
+
100
+ key_value do
101
+ map 'type', to: :type, render_default: true
102
+ map 'attrs', to: :attrs
103
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
104
+ end
105
+ end
106
+
107
+ class Preamble < Node
108
+ PM_TYPE = 'preface'
109
+
110
+ key_value do
111
+ map 'type', to: :type, render_default: true
112
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
113
+ end
114
+ end
115
+
116
+ class Sections < Node
117
+ PM_TYPE = 'sections'
118
+
119
+ key_value do
120
+ map 'type', to: :type, render_default: true
121
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
122
+ end
123
+ end
124
+
125
+ class Header < Node
126
+ PM_TYPE = 'floating_title'
127
+
128
+ class Attrs < Lutaml::Model::Serializable
129
+ attribute :title, :string
130
+ attribute :level, :integer
131
+
132
+ key_value do
133
+ map 'title', to: :title
134
+ map 'level', to: :level
135
+ end
136
+ end
137
+
138
+ attribute :attrs, Attrs
139
+
140
+ key_value do
141
+ map 'type', to: :type, render_default: true
142
+ map 'attrs', to: :attrs
143
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
144
+ end
145
+ end
146
+
147
+ # ── Text (special: text at top level, not under attrs) ──
148
+
149
+ class Text < Node
150
+ PM_TYPE = 'text'
151
+
152
+ attribute :text, :string
153
+
154
+ key_value do
155
+ map 'type', to: :type, render_default: true
156
+ map 'text', to: :text
157
+ map 'marks', to: :marks, polymorphic: Mark::POLYMORPHIC
158
+ end
159
+
160
+ def text_content
161
+ text.to_s
162
+ end
163
+ end
164
+
165
+ # ── Paragraph / block text ──
166
+
167
+ class Paragraph < Node
168
+ PM_TYPE = 'paragraph'
169
+
170
+ key_value do
171
+ map 'type', to: :type, render_default: true
172
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
173
+ map 'marks', to: :marks, polymorphic: Mark::POLYMORPHIC
174
+ end
175
+ end
176
+
177
+ class CodeBlock < Node
178
+ PM_TYPE = 'sourcecode'
179
+
180
+ class Attrs < Lutaml::Model::Serializable
181
+ attribute :language, :string
182
+ attribute :title, :string
183
+ attribute :passthrough, :boolean
184
+ attribute :text, :string
185
+
186
+ key_value do
187
+ map 'language', to: :language
188
+ map 'title', to: :title
189
+ map 'passthrough', to: :passthrough
190
+ map 'text', to: :text
191
+ end
192
+ end
193
+
194
+ attribute :attrs, Attrs
195
+
196
+ key_value do
197
+ map 'type', to: :type, render_default: true
198
+ map 'attrs', to: :attrs
199
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
200
+ end
201
+ end
202
+
203
+ class Blockquote < Node
204
+ PM_TYPE = 'quote'
205
+
206
+ class Attrs < Lutaml::Model::Serializable
207
+ attribute :attribution, :string
208
+
209
+ key_value do
210
+ map 'attribution', to: :attribution
211
+ end
212
+ end
213
+
214
+ attribute :attrs, Attrs
215
+
216
+ key_value do
217
+ map 'type', to: :type, render_default: true
218
+ map 'attrs', to: :attrs
219
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
220
+ end
221
+ end
222
+
223
+ class Example < Node
224
+ PM_TYPE = 'example'
225
+
226
+ class Attrs < Lutaml::Model::Serializable
227
+ attribute :title, :string
228
+ attribute :id, :string
229
+
230
+ key_value do
231
+ map 'title', to: :title
232
+ map 'id', to: :id
233
+ end
234
+ end
235
+
236
+ attribute :attrs, Attrs
237
+
238
+ key_value do
239
+ map 'type', to: :type, render_default: true
240
+ map 'attrs', to: :attrs
241
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
242
+ end
243
+ end
244
+
245
+ class Sidebar < Node
246
+ PM_TYPE = 'sidebar'
247
+
248
+ class Attrs < Lutaml::Model::Serializable
249
+ attribute :title, :string
250
+ attribute :id, :string
251
+
252
+ key_value do
253
+ map 'title', to: :title
254
+ map 'id', to: :id
255
+ end
256
+ end
257
+
258
+ attribute :attrs, Attrs
259
+
260
+ key_value do
261
+ map 'type', to: :type, render_default: true
262
+ map 'attrs', to: :attrs
263
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
264
+ end
265
+ end
266
+
267
+ class OpenBlock < Node
268
+ PM_TYPE = 'open_block'
269
+
270
+ key_value do
271
+ map 'type', to: :type, render_default: true
272
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
273
+ end
274
+ end
275
+
276
+ class Verse < Node
277
+ PM_TYPE = 'verse'
278
+
279
+ class Attrs < Lutaml::Model::Serializable
280
+ attribute :attribution, :string
281
+
282
+ key_value do
283
+ map 'attribution', to: :attribution
284
+ end
285
+ end
286
+
287
+ attribute :attrs, Attrs
288
+
289
+ key_value do
290
+ map 'type', to: :type, render_default: true
291
+ map 'attrs', to: :attrs
292
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
293
+ end
294
+ end
295
+
296
+ class HorizontalRule < Node
297
+ PM_TYPE = 'horizontal_rule'
298
+ end
299
+
300
+ class ThematicBreak < Node
301
+ PM_TYPE = 'thematic_break'
302
+ end
303
+
304
+ class SoftBreak < Node
305
+ PM_TYPE = 'soft_break'
306
+ end
307
+
308
+ # ── Admonition (NOTE, TIP, WARNING, CAUTION, IMPORTANT) ──
309
+ #
310
+ # The Ruby attribute name is `admonition_type` (kept distinct from
311
+ # Node's built-in `type` discriminator). The wire attribute name is
312
+ # `type` per the @metanorma/mirror JS contract. The rename happens
313
+ # via a `map` declaration — no hand-rolled to_h/from_h.
314
+
315
+ class Admonition < Node
316
+ PM_TYPE = 'admonition'
317
+
318
+ class Attrs < Lutaml::Model::Serializable
319
+ attribute :admonition_type, :string
320
+ attribute :title, :string
321
+ attribute :label, :string
322
+ attribute :id, :string
323
+
324
+ key_value do
325
+ map 'type', to: :admonition_type # RENAME: Ruby ≠ wire
326
+ map 'title', to: :title
327
+ map 'label', to: :label
328
+ map 'id', to: :id
329
+ end
330
+ end
331
+
332
+ attribute :attrs, Attrs
333
+
334
+ key_value do
335
+ map 'type', to: :type, render_default: true
336
+ map 'attrs', to: :attrs
337
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
338
+ end
339
+ end
340
+
341
+ # ── Lists ──
342
+
343
+ class BulletList < Node
344
+ PM_TYPE = 'bullet_list'
345
+
346
+ class Attrs < Lutaml::Model::Serializable
347
+ attribute :id, :string
348
+ attribute :start, :integer
349
+
350
+ key_value do
351
+ map 'id', to: :id
352
+ map 'start', to: :start
353
+ end
354
+ end
355
+
356
+ attribute :attrs, Attrs
357
+
358
+ key_value do
359
+ map 'type', to: :type, render_default: true
360
+ map 'attrs', to: :attrs
361
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
362
+ end
363
+ end
364
+
365
+ class OrderedList < Node
366
+ PM_TYPE = 'ordered_list'
367
+
368
+ class Attrs < Lutaml::Model::Serializable
369
+ attribute :id, :string
370
+ attribute :start, :integer
371
+
372
+ key_value do
373
+ map 'id', to: :id
374
+ map 'start', to: :start
375
+ end
376
+ end
377
+
378
+ attribute :attrs, Attrs
379
+
380
+ key_value do
381
+ map 'type', to: :type, render_default: true
382
+ map 'attrs', to: :attrs
383
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
384
+ end
385
+ end
386
+
387
+ class ListItem < Node
388
+ PM_TYPE = 'list_item'
389
+
390
+ class Attrs < Lutaml::Model::Serializable
391
+ attribute :id, :string
392
+
393
+ key_value do
394
+ map 'id', to: :id
395
+ end
396
+ end
397
+
398
+ attribute :attrs, Attrs
399
+
400
+ key_value do
401
+ map 'type', to: :type, render_default: true
402
+ map 'attrs', to: :attrs
403
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
404
+ map 'marks', to: :marks, polymorphic: Mark::POLYMORPHIC
405
+ end
406
+ end
407
+
408
+ class DefinitionList < Node
409
+ PM_TYPE = 'dl'
410
+
411
+ class Attrs < Lutaml::Model::Serializable
412
+ attribute :id, :string
413
+
414
+ key_value do
415
+ map 'id', to: :id
416
+ end
417
+ end
418
+
419
+ attribute :attrs, Attrs
420
+
421
+ key_value do
422
+ map 'type', to: :type, render_default: true
423
+ map 'attrs', to: :attrs
424
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
425
+ end
426
+ end
427
+
428
+ class DefinitionTerm < Node
429
+ PM_TYPE = 'dt'
430
+
431
+ key_value do
432
+ map 'type', to: :type, render_default: true
433
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
434
+ map 'marks', to: :marks, polymorphic: Mark::POLYMORPHIC
435
+ end
436
+ end
437
+
438
+ class DefinitionDescription < Node
439
+ PM_TYPE = 'dd'
440
+
441
+ key_value do
442
+ map 'type', to: :type, render_default: true
443
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
444
+ map 'marks', to: :marks, polymorphic: Mark::POLYMORPHIC
445
+ end
446
+ end
447
+
448
+ # ── Media ──
449
+
450
+ class Image < Node
451
+ PM_TYPE = 'image'
452
+
453
+ class Attrs < Lutaml::Model::Serializable
454
+ attribute :src, :string
455
+ attribute :alt, :string
456
+ attribute :title, :string
457
+ attribute :caption, :string
458
+ attribute :width, :string
459
+ attribute :height, :string
460
+ attribute :inline, :boolean
461
+
462
+ key_value do
463
+ map 'src', to: :src
464
+ map 'alt', to: :alt
465
+ map 'title', to: :title
466
+ map 'caption', to: :caption
467
+ map 'width', to: :width
468
+ map 'height', to: :height
469
+ map 'inline', to: :inline
470
+ end
471
+ end
472
+
473
+ attribute :attrs, Attrs
474
+
475
+ key_value do
476
+ map 'type', to: :type, render_default: true
477
+ map 'attrs', to: :attrs
478
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
479
+ end
480
+ end
481
+
482
+ # JS @metanorma/mirror figure: wraps Image + optional Caption.
483
+ class Figure < Node
484
+ PM_TYPE = 'figure'
485
+
486
+ class Attrs < Lutaml::Model::Serializable
487
+ attribute :id, :string
488
+ attribute :title, :string
489
+
490
+ key_value do
491
+ map 'id', to: :id
492
+ map 'title', to: :title
493
+ end
494
+ end
495
+
496
+ attribute :attrs, Attrs
497
+
498
+ key_value do
499
+ map 'type', to: :type, render_default: true
500
+ map 'attrs', to: :attrs
501
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
502
+ end
503
+ end
504
+
505
+ class Caption < Node
506
+ PM_TYPE = 'caption'
507
+
508
+ key_value do
509
+ map 'type', to: :type, render_default: true
510
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
511
+ map 'marks', to: :marks, polymorphic: Mark::POLYMORPHIC
512
+ end
513
+ end
514
+
515
+ # ── Tables ──
516
+
517
+ class Table < Node
518
+ PM_TYPE = 'table'
519
+
520
+ class Attrs < Lutaml::Model::Serializable
521
+ attribute :title, :string
522
+ attribute :id, :string
523
+ attribute :width, :string
524
+
525
+ key_value do
526
+ map 'title', to: :title
527
+ map 'id', to: :id
528
+ map 'width', to: :width
529
+ end
530
+ end
531
+
532
+ attribute :attrs, Attrs
533
+
534
+ key_value do
535
+ map 'type', to: :type, render_default: true
536
+ map 'attrs', to: :attrs
537
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
538
+ end
539
+ end
540
+
541
+ class TableHead < Node
542
+ PM_TYPE = 'table_head'
543
+
544
+ key_value do
545
+ map 'type', to: :type, render_default: true
546
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
547
+ end
548
+ end
549
+
550
+ class TableBody < Node
551
+ PM_TYPE = 'table_body'
552
+
553
+ key_value do
554
+ map 'type', to: :type, render_default: true
555
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
556
+ end
557
+ end
558
+
559
+ class TableRow < Node
560
+ PM_TYPE = 'table_row'
561
+
562
+ key_value do
563
+ map 'type', to: :type, render_default: true
564
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
565
+ end
566
+ end
567
+
568
+ class TableCell < Node
569
+ PM_TYPE = 'table_cell'
570
+
571
+ class Attrs < Lutaml::Model::Serializable
572
+ attribute :colspan, :integer
573
+ attribute :rowspan, :integer
574
+ attribute :alignment, :string
575
+ attribute :header, :boolean
576
+
577
+ key_value do
578
+ map 'colspan', to: :colspan
579
+ map 'rowspan', to: :rowspan
580
+ map 'alignment', to: :alignment
581
+ map 'header', to: :header
582
+ end
583
+ end
584
+
585
+ attribute :attrs, Attrs
586
+
587
+ key_value do
588
+ map 'type', to: :type, render_default: true
589
+ map 'attrs', to: :attrs
590
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
591
+ map 'marks', to: :marks, polymorphic: Mark::POLYMORPHIC
592
+ end
593
+ end
594
+
595
+ # ── Bibliography ──
596
+
597
+ class Bibliography < Node
598
+ PM_TYPE = 'bibliography'
599
+
600
+ class Attrs < Lutaml::Model::Serializable
601
+ attribute :title, :string
602
+ attribute :id, :string
603
+ attribute :level, :integer
604
+
605
+ key_value do
606
+ map 'title', to: :title
607
+ map 'id', to: :id
608
+ map 'level', to: :level
609
+ end
610
+ end
611
+
612
+ attribute :attrs, Attrs
613
+
614
+ key_value do
615
+ map 'type', to: :type, render_default: true
616
+ map 'attrs', to: :attrs
617
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
618
+ end
619
+ end
620
+
621
+ class BibliographyEntry < Node
622
+ PM_TYPE = 'biblio_entry'
623
+
624
+ class Attrs < Lutaml::Model::Serializable
625
+ attribute :anchor_name, :string
626
+ attribute :document_id, :string
627
+ attribute :url, :string
628
+
629
+ key_value do
630
+ map 'anchor_name', to: :anchor_name
631
+ map 'document_id', to: :document_id
632
+ map 'url', to: :url
633
+ end
634
+ end
635
+
636
+ attribute :attrs, Attrs
637
+
638
+ key_value do
639
+ map 'type', to: :type, render_default: true
640
+ map 'attrs', to: :attrs
641
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
642
+ end
643
+ end
644
+
645
+ # ── Footnotes ──
646
+
647
+ class Footnotes < Node
648
+ PM_TYPE = 'footnotes'
649
+
650
+ key_value do
651
+ map 'type', to: :type, render_default: true
652
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
653
+ end
654
+ end
655
+
656
+ class FootnoteMarker < Node
657
+ PM_TYPE = 'footnote_marker'
658
+
659
+ class Attrs < Lutaml::Model::Serializable
660
+ attribute :id, :string
661
+ attribute :ref_id, :string
662
+ attribute :number, :integer
663
+
664
+ key_value do
665
+ map 'id', to: :id
666
+ map 'ref_id', to: :ref_id
667
+ map 'number', to: :number
668
+ end
669
+ end
670
+
671
+ attribute :attrs, Attrs
672
+
673
+ key_value do
674
+ map 'type', to: :type, render_default: true
675
+ map 'attrs', to: :attrs
676
+ end
677
+ end
678
+
679
+ class FootnoteEntry < Node
680
+ PM_TYPE = 'footnote_entry'
681
+
682
+ class Attrs < Lutaml::Model::Serializable
683
+ attribute :id, :string
684
+ attribute :ref_id, :string
685
+ attribute :number, :integer
686
+
687
+ key_value do
688
+ map 'id', to: :id
689
+ map 'ref_id', to: :ref_id
690
+ map 'number', to: :number
691
+ end
692
+ end
693
+
694
+ attribute :attrs, Attrs
695
+
696
+ key_value do
697
+ map 'type', to: :type, render_default: true
698
+ map 'attrs', to: :attrs
699
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
700
+ end
701
+ end
702
+
703
+ # ── TOC ──
704
+
705
+ class Toc < Node
706
+ PM_TYPE = 'toc'
707
+
708
+ class Attrs < Lutaml::Model::Serializable
709
+ attribute :title, :string
710
+
711
+ key_value do
712
+ map 'title', to: :title
713
+ end
714
+ end
715
+
716
+ attribute :attrs, Attrs
717
+
718
+ key_value do
719
+ map 'type', to: :type, render_default: true
720
+ map 'attrs', to: :attrs
721
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
722
+ end
723
+ end
724
+
725
+ class TocEntry < Node
726
+ PM_TYPE = 'toc_entry'
727
+
728
+ class Attrs < Lutaml::Model::Serializable
729
+ attribute :id, :string
730
+ attribute :title, :string
731
+ attribute :level, :integer
732
+
733
+ key_value do
734
+ map 'id', to: :id
735
+ map 'title', to: :title
736
+ map 'level', to: :level
737
+ end
738
+ end
739
+
740
+ attribute :attrs, Attrs
741
+
742
+ key_value do
743
+ map 'type', to: :type, render_default: true
744
+ map 'attrs', to: :attrs
745
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
746
+ end
747
+ end
748
+
749
+ # ── Generic (catch-all) ──
750
+
751
+ class GenericBlock < Node
752
+ PM_TYPE = 'generic_block'
753
+
754
+ class Attrs < Lutaml::Model::Serializable
755
+ attribute :semantic_type, :string
756
+ attribute :title, :string
757
+ attribute :id, :string
758
+
759
+ key_value do
760
+ map 'semantic_type', to: :semantic_type
761
+ map 'title', to: :title
762
+ map 'id', to: :id
763
+ end
764
+ end
765
+
766
+ attribute :attrs, Attrs
767
+
768
+ key_value do
769
+ map 'type', to: :type, render_default: true
770
+ map 'attrs', to: :attrs
771
+ map 'content', to: :content, polymorphic: Node::POLYMORPHIC
772
+ end
773
+ end
774
+
775
+ # ── Frontmatter (typed tree — no hashes) ──
776
+
777
+ class FrontmatterValue < Lutaml::Model::Serializable; end
778
+
779
+ class FrontmatterEntry < Lutaml::Model::Serializable
780
+ attribute :key, :string
781
+ attribute :value, FrontmatterValue
782
+
783
+ key_value do
784
+ map 'key', to: :key, render_default: true
785
+ map 'value', to: :value
786
+ end
787
+ end
788
+
789
+ class FrontmatterValue
790
+ attribute :value_type, :string
791
+ attribute :string_value, :string
792
+ attribute :integer_value, :integer
793
+ attribute :float_value, :float
794
+ attribute :boolean_value, :boolean
795
+ attribute :date_value, :date
796
+ attribute :datetime_value, :date_time
797
+ attribute :symbol_value, :string
798
+ attribute :items, FrontmatterValue, collection: true
799
+ attribute :entries, FrontmatterEntry, collection: true
800
+
801
+ key_value do
802
+ map 'value_type', to: :value_type, render_default: true
803
+ map 'string_value', to: :string_value
804
+ map 'integer_value', to: :integer_value
805
+ map 'float_value', to: :float_value
806
+ map 'boolean_value', to: :boolean_value
807
+ map 'date_value', to: :date_value
808
+ map 'datetime_value', to: :datetime_value
809
+ map 'symbol_value', to: :symbol_value
810
+ map 'items', to: :items
811
+ map 'entries', to: :entries
812
+ end
813
+ end
814
+
815
+ class Frontmatter < Node
816
+ PM_TYPE = 'frontmatter'
817
+
818
+ class Attrs < Lutaml::Model::Serializable
819
+ attribute :schema, :string
820
+ attribute :entries, FrontmatterEntry, collection: true
821
+
822
+ key_value do
823
+ map 'schema', to: :schema
824
+ map 'entries', to: :entries
825
+ end
826
+ end
827
+
828
+ attribute :attrs, Attrs
829
+
830
+ key_value do
831
+ map 'type', to: :type, render_default: true
832
+ map 'attrs', to: :attrs
833
+ end
834
+ end
835
+ end
836
+ end
837
+ end
838
+
839
+ module Coradoc
840
+ module Mirror
841
+ class Node
842
+ # Populate the polymorphic class map now that every subclass is
843
+ # defined. Maps each PM_TYPE wire string to its Ruby class name.
844
+ # Section's PM_ALIASES are added on top so all JS SECTION_TYPES
845
+ # route to Section on reverse parse.
846
+ Node.constants.each do |name|
847
+ k = Node.const_get(name)
848
+ next unless k.is_a?(Class) && k < Node && k::PM_TYPE != 'node'
849
+
850
+ TYPE_TO_CLASS[k::PM_TYPE] = k.name
851
+ Array(k::PM_ALIASES).each { |a| TYPE_TO_CLASS[a] = k.name } if k.const_defined?(:PM_ALIASES, false)
852
+ end
853
+ TYPE_TO_CLASS.freeze
854
+ end
855
+ end
856
+ end