coradoc-adoc 2.0.0

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 (217) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/lib/coradoc/asciidoc/model/admonition.rb +37 -0
  4. data/lib/coradoc/asciidoc/model/anchorable.rb +64 -0
  5. data/lib/coradoc/asciidoc/model/attached.rb +26 -0
  6. data/lib/coradoc/asciidoc/model/attribute.rb +22 -0
  7. data/lib/coradoc/asciidoc/model/attribute_list/matchers.rb +45 -0
  8. data/lib/coradoc/asciidoc/model/attribute_list.rb +230 -0
  9. data/lib/coradoc/asciidoc/model/attribute_list_attribute.rb +11 -0
  10. data/lib/coradoc/asciidoc/model/audio.rb +44 -0
  11. data/lib/coradoc/asciidoc/model/author.rb +36 -0
  12. data/lib/coradoc/asciidoc/model/base.rb +141 -0
  13. data/lib/coradoc/asciidoc/model/bibliography.rb +37 -0
  14. data/lib/coradoc/asciidoc/model/bibliography_entry.rb +38 -0
  15. data/lib/coradoc/asciidoc/model/block/core.rb +139 -0
  16. data/lib/coradoc/asciidoc/model/block/example.rb +14 -0
  17. data/lib/coradoc/asciidoc/model/block/listing.rb +14 -0
  18. data/lib/coradoc/asciidoc/model/block/literal.rb +14 -0
  19. data/lib/coradoc/asciidoc/model/block/open.rb +14 -0
  20. data/lib/coradoc/asciidoc/model/block/pass.rb +14 -0
  21. data/lib/coradoc/asciidoc/model/block/quote.rb +14 -0
  22. data/lib/coradoc/asciidoc/model/block/reviewer_comment.rb +14 -0
  23. data/lib/coradoc/asciidoc/model/block/side.rb +14 -0
  24. data/lib/coradoc/asciidoc/model/block/source_code.rb +14 -0
  25. data/lib/coradoc/asciidoc/model/block.rb +21 -0
  26. data/lib/coradoc/asciidoc/model/break.rb +33 -0
  27. data/lib/coradoc/asciidoc/model/comment_block.rb +33 -0
  28. data/lib/coradoc/asciidoc/model/comment_line.rb +30 -0
  29. data/lib/coradoc/asciidoc/model/content_list.rb +334 -0
  30. data/lib/coradoc/asciidoc/model/document.rb +197 -0
  31. data/lib/coradoc/asciidoc/model/document_attributes.rb +43 -0
  32. data/lib/coradoc/asciidoc/model/glossaries.rb +11 -0
  33. data/lib/coradoc/asciidoc/model/header.rb +57 -0
  34. data/lib/coradoc/asciidoc/model/highlight.rb +11 -0
  35. data/lib/coradoc/asciidoc/model/image/block_image/attribute_list.rb +23 -0
  36. data/lib/coradoc/asciidoc/model/image/block_image.rb +25 -0
  37. data/lib/coradoc/asciidoc/model/image/core/attribute_list.rb +43 -0
  38. data/lib/coradoc/asciidoc/model/image/core.rb +72 -0
  39. data/lib/coradoc/asciidoc/model/image/inline_image.rb +17 -0
  40. data/lib/coradoc/asciidoc/model/image.rb +14 -0
  41. data/lib/coradoc/asciidoc/model/include.rb +66 -0
  42. data/lib/coradoc/asciidoc/model/inline/anchor.rb +41 -0
  43. data/lib/coradoc/asciidoc/model/inline/attribute_reference.rb +25 -0
  44. data/lib/coradoc/asciidoc/model/inline/base.rb +15 -0
  45. data/lib/coradoc/asciidoc/model/inline/bold.rb +38 -0
  46. data/lib/coradoc/asciidoc/model/inline/cross_reference.rb +29 -0
  47. data/lib/coradoc/asciidoc/model/inline/cross_reference_arg.rb +15 -0
  48. data/lib/coradoc/asciidoc/model/inline/footnote.rb +34 -0
  49. data/lib/coradoc/asciidoc/model/inline/hard_line_break.rb +24 -0
  50. data/lib/coradoc/asciidoc/model/inline/highlight.rb +36 -0
  51. data/lib/coradoc/asciidoc/model/inline/italic.rb +38 -0
  52. data/lib/coradoc/asciidoc/model/inline/link.rb +46 -0
  53. data/lib/coradoc/asciidoc/model/inline/monospace.rb +39 -0
  54. data/lib/coradoc/asciidoc/model/inline/quotation.rb +25 -0
  55. data/lib/coradoc/asciidoc/model/inline/small.rb +25 -0
  56. data/lib/coradoc/asciidoc/model/inline/span.rb +38 -0
  57. data/lib/coradoc/asciidoc/model/inline/stem.rb +24 -0
  58. data/lib/coradoc/asciidoc/model/inline/strikethrough.rb +39 -0
  59. data/lib/coradoc/asciidoc/model/inline/subscript.rb +33 -0
  60. data/lib/coradoc/asciidoc/model/inline/superscript.rb +33 -0
  61. data/lib/coradoc/asciidoc/model/inline/underline.rb +25 -0
  62. data/lib/coradoc/asciidoc/model/inline.rb +31 -0
  63. data/lib/coradoc/asciidoc/model/line_break.rb +11 -0
  64. data/lib/coradoc/asciidoc/model/list/core.rb +61 -0
  65. data/lib/coradoc/asciidoc/model/list/definition.rb +27 -0
  66. data/lib/coradoc/asciidoc/model/list/definition_item.rb +43 -0
  67. data/lib/coradoc/asciidoc/model/list/item.rb +72 -0
  68. data/lib/coradoc/asciidoc/model/list/nestable.rb +14 -0
  69. data/lib/coradoc/asciidoc/model/list/ordered.rb +34 -0
  70. data/lib/coradoc/asciidoc/model/list/unordered.rb +34 -0
  71. data/lib/coradoc/asciidoc/model/list.rb +29 -0
  72. data/lib/coradoc/asciidoc/model/named_attribute.rb +12 -0
  73. data/lib/coradoc/asciidoc/model/paragraph.rb +59 -0
  74. data/lib/coradoc/asciidoc/model/rejected_positional_attribute.rb +12 -0
  75. data/lib/coradoc/asciidoc/model/resolvable.rb +71 -0
  76. data/lib/coradoc/asciidoc/model/resolver.rb +430 -0
  77. data/lib/coradoc/asciidoc/model/reviewer_note.rb +54 -0
  78. data/lib/coradoc/asciidoc/model/revision.rb +47 -0
  79. data/lib/coradoc/asciidoc/model/section.rb +109 -0
  80. data/lib/coradoc/asciidoc/model/serialization/asciidoc_adapter.rb +28 -0
  81. data/lib/coradoc/asciidoc/model/serialization/asciidoc_mapping.rb +42 -0
  82. data/lib/coradoc/asciidoc/model/serialization/asciidoc_mapping_rule.rb +41 -0
  83. data/lib/coradoc/asciidoc/model/serialization/asciidoc_transform.rb +211 -0
  84. data/lib/coradoc/asciidoc/model/serialization/errors.rb +57 -0
  85. data/lib/coradoc/asciidoc/model/serialization.rb +39 -0
  86. data/lib/coradoc/asciidoc/model/spacing.rb +282 -0
  87. data/lib/coradoc/asciidoc/model/table.rb +44 -0
  88. data/lib/coradoc/asciidoc/model/table_cell.rb +122 -0
  89. data/lib/coradoc/asciidoc/model/table_row.rb +26 -0
  90. data/lib/coradoc/asciidoc/model/tag.rb +36 -0
  91. data/lib/coradoc/asciidoc/model/term.rb +48 -0
  92. data/lib/coradoc/asciidoc/model/text_element.rb +66 -0
  93. data/lib/coradoc/asciidoc/model/title.rb +85 -0
  94. data/lib/coradoc/asciidoc/model/video/attribute_list.rb +43 -0
  95. data/lib/coradoc/asciidoc/model/video.rb +49 -0
  96. data/lib/coradoc/asciidoc/model.rb +75 -0
  97. data/lib/coradoc/asciidoc/parse_error.rb +161 -0
  98. data/lib/coradoc/asciidoc/parser/admonition.rb +26 -0
  99. data/lib/coradoc/asciidoc/parser/attribute_list.rb +110 -0
  100. data/lib/coradoc/asciidoc/parser/base.rb +159 -0
  101. data/lib/coradoc/asciidoc/parser/bibliography.rb +31 -0
  102. data/lib/coradoc/asciidoc/parser/block.rb +186 -0
  103. data/lib/coradoc/asciidoc/parser/block_assembler.rb +183 -0
  104. data/lib/coradoc/asciidoc/parser/cache.rb +155 -0
  105. data/lib/coradoc/asciidoc/parser/citation.rb +32 -0
  106. data/lib/coradoc/asciidoc/parser/content.rb +76 -0
  107. data/lib/coradoc/asciidoc/parser/document_attributes.rb +27 -0
  108. data/lib/coradoc/asciidoc/parser/fix_files.rb +76 -0
  109. data/lib/coradoc/asciidoc/parser/header.rb +31 -0
  110. data/lib/coradoc/asciidoc/parser/inline.rb +199 -0
  111. data/lib/coradoc/asciidoc/parser/list.rb +130 -0
  112. data/lib/coradoc/asciidoc/parser/metadata_detector.rb +164 -0
  113. data/lib/coradoc/asciidoc/parser/paragraph.rb +64 -0
  114. data/lib/coradoc/asciidoc/parser/section.rb +62 -0
  115. data/lib/coradoc/asciidoc/parser/stem.rb +19 -0
  116. data/lib/coradoc/asciidoc/parser/table.rb +166 -0
  117. data/lib/coradoc/asciidoc/parser/term.rb +70 -0
  118. data/lib/coradoc/asciidoc/parser/text.rb +156 -0
  119. data/lib/coradoc/asciidoc/parser.rb +10 -0
  120. data/lib/coradoc/asciidoc/serializer/adoc_serializer.rb +86 -0
  121. data/lib/coradoc/asciidoc/serializer/element_registry.rb +95 -0
  122. data/lib/coradoc/asciidoc/serializer/fallback_serializer.rb +21 -0
  123. data/lib/coradoc/asciidoc/serializer/formatter.rb +144 -0
  124. data/lib/coradoc/asciidoc/serializer/registrations.rb +108 -0
  125. data/lib/coradoc/asciidoc/serializer/serialization_context.rb +238 -0
  126. data/lib/coradoc/asciidoc/serializer/serializers/admonition.rb +19 -0
  127. data/lib/coradoc/asciidoc/serializer/serializers/attribute.rb +23 -0
  128. data/lib/coradoc/asciidoc/serializer/serializers/attribute_list.rb +40 -0
  129. data/lib/coradoc/asciidoc/serializer/serializers/attribute_list_attribute.rb +18 -0
  130. data/lib/coradoc/asciidoc/serializer/serializers/audio.rb +33 -0
  131. data/lib/coradoc/asciidoc/serializer/serializers/author.rb +20 -0
  132. data/lib/coradoc/asciidoc/serializer/serializers/base.rb +152 -0
  133. data/lib/coradoc/asciidoc/serializer/serializers/bibliography.rb +35 -0
  134. data/lib/coradoc/asciidoc/serializer/serializers/bibliography_entry.rb +24 -0
  135. data/lib/coradoc/asciidoc/serializer/serializers/block/core.rb +70 -0
  136. data/lib/coradoc/asciidoc/serializer/serializers/block/example.rb +17 -0
  137. data/lib/coradoc/asciidoc/serializer/serializers/block/listing.rb +22 -0
  138. data/lib/coradoc/asciidoc/serializer/serializers/block/literal.rb +17 -0
  139. data/lib/coradoc/asciidoc/serializer/serializers/block/open.rb +22 -0
  140. data/lib/coradoc/asciidoc/serializer/serializers/block/pass.rb +17 -0
  141. data/lib/coradoc/asciidoc/serializer/serializers/block/quote.rb +17 -0
  142. data/lib/coradoc/asciidoc/serializer/serializers/block/reviewer_comment.rb +17 -0
  143. data/lib/coradoc/asciidoc/serializer/serializers/block/side.rb +22 -0
  144. data/lib/coradoc/asciidoc/serializer/serializers/block/source_code.rb +22 -0
  145. data/lib/coradoc/asciidoc/serializer/serializers/block.rb +23 -0
  146. data/lib/coradoc/asciidoc/serializer/serializers/break.rb +18 -0
  147. data/lib/coradoc/asciidoc/serializer/serializers/comment_block.rb +22 -0
  148. data/lib/coradoc/asciidoc/serializer/serializers/comment_line.rb +22 -0
  149. data/lib/coradoc/asciidoc/serializer/serializers/document.rb +65 -0
  150. data/lib/coradoc/asciidoc/serializer/serializers/document_attributes.rb +21 -0
  151. data/lib/coradoc/asciidoc/serializer/serializers/header.rb +24 -0
  152. data/lib/coradoc/asciidoc/serializer/serializers/highlight.rb +23 -0
  153. data/lib/coradoc/asciidoc/serializer/serializers/image/core.rb +30 -0
  154. data/lib/coradoc/asciidoc/serializer/serializers/image.rb +14 -0
  155. data/lib/coradoc/asciidoc/serializer/serializers/include.rb +19 -0
  156. data/lib/coradoc/asciidoc/serializer/serializers/inline/anchor.rb +20 -0
  157. data/lib/coradoc/asciidoc/serializer/serializers/inline/attribute_reference.rb +20 -0
  158. data/lib/coradoc/asciidoc/serializer/serializers/inline/bold.rb +26 -0
  159. data/lib/coradoc/asciidoc/serializer/serializers/inline/cross_reference.rb +30 -0
  160. data/lib/coradoc/asciidoc/serializer/serializers/inline/cross_reference_arg.rb +20 -0
  161. data/lib/coradoc/asciidoc/serializer/serializers/inline/footnote.rb +24 -0
  162. data/lib/coradoc/asciidoc/serializer/serializers/inline/hard_line_break.rb +20 -0
  163. data/lib/coradoc/asciidoc/serializer/serializers/inline/highlight.rb +26 -0
  164. data/lib/coradoc/asciidoc/serializer/serializers/inline/italic.rb +26 -0
  165. data/lib/coradoc/asciidoc/serializer/serializers/inline/link.rb +38 -0
  166. data/lib/coradoc/asciidoc/serializer/serializers/inline/monospace.rb +26 -0
  167. data/lib/coradoc/asciidoc/serializer/serializers/inline/quotation.rb +21 -0
  168. data/lib/coradoc/asciidoc/serializer/serializers/inline/small.rb +20 -0
  169. data/lib/coradoc/asciidoc/serializer/serializers/inline/span.rb +35 -0
  170. data/lib/coradoc/asciidoc/serializer/serializers/inline/stem.rb +23 -0
  171. data/lib/coradoc/asciidoc/serializer/serializers/inline/strikethrough.rb +29 -0
  172. data/lib/coradoc/asciidoc/serializer/serializers/inline/subscript.rb +29 -0
  173. data/lib/coradoc/asciidoc/serializer/serializers/inline/superscript.rb +26 -0
  174. data/lib/coradoc/asciidoc/serializer/serializers/inline/underline.rb +20 -0
  175. data/lib/coradoc/asciidoc/serializer/serializers/inline.rb +32 -0
  176. data/lib/coradoc/asciidoc/serializer/serializers/line_break.rb +18 -0
  177. data/lib/coradoc/asciidoc/serializer/serializers/list/core.rb +47 -0
  178. data/lib/coradoc/asciidoc/serializer/serializers/list/definition.rb +35 -0
  179. data/lib/coradoc/asciidoc/serializer/serializers/list/definition_item.rb +38 -0
  180. data/lib/coradoc/asciidoc/serializer/serializers/list/item.rb +120 -0
  181. data/lib/coradoc/asciidoc/serializer/serializers/list/ordered.rb +24 -0
  182. data/lib/coradoc/asciidoc/serializer/serializers/list/unordered.rb +29 -0
  183. data/lib/coradoc/asciidoc/serializer/serializers/list.rb +19 -0
  184. data/lib/coradoc/asciidoc/serializer/serializers/named_attribute.rb +22 -0
  185. data/lib/coradoc/asciidoc/serializer/serializers/paragraph.rb +65 -0
  186. data/lib/coradoc/asciidoc/serializer/serializers/reviewer_note.rb +28 -0
  187. data/lib/coradoc/asciidoc/serializer/serializers/revision.rb +26 -0
  188. data/lib/coradoc/asciidoc/serializer/serializers/section.rb +37 -0
  189. data/lib/coradoc/asciidoc/serializer/serializers/table.rb +24 -0
  190. data/lib/coradoc/asciidoc/serializer/serializers/table_cell.rb +75 -0
  191. data/lib/coradoc/asciidoc/serializer/serializers/table_row.rb +24 -0
  192. data/lib/coradoc/asciidoc/serializer/serializers/tag.rb +19 -0
  193. data/lib/coradoc/asciidoc/serializer/serializers/term.rb +20 -0
  194. data/lib/coradoc/asciidoc/serializer/serializers/text_element.rb +23 -0
  195. data/lib/coradoc/asciidoc/serializer/serializers/title.rb +55 -0
  196. data/lib/coradoc/asciidoc/serializer/serializers/video.rb +33 -0
  197. data/lib/coradoc/asciidoc/serializer/spacing_strategy.rb +70 -0
  198. data/lib/coradoc/asciidoc/serializer.rb +75 -0
  199. data/lib/coradoc/asciidoc/transform/from_core_model.rb +502 -0
  200. data/lib/coradoc/asciidoc/transform/from_core_model_registrations.rb +126 -0
  201. data/lib/coradoc/asciidoc/transform/registry.rb +146 -0
  202. data/lib/coradoc/asciidoc/transform/to_core_model.rb +564 -0
  203. data/lib/coradoc/asciidoc/transform/to_core_model_registrations.rb +257 -0
  204. data/lib/coradoc/asciidoc/transform.rb +13 -0
  205. data/lib/coradoc/asciidoc/transformer/block_rules.rb +101 -0
  206. data/lib/coradoc/asciidoc/transformer/header_rules.rb +91 -0
  207. data/lib/coradoc/asciidoc/transformer/inline_rules.rb +179 -0
  208. data/lib/coradoc/asciidoc/transformer/list_rules.rb +131 -0
  209. data/lib/coradoc/asciidoc/transformer/misc_rules.rb +196 -0
  210. data/lib/coradoc/asciidoc/transformer/structural_rules.rb +216 -0
  211. data/lib/coradoc/asciidoc/transformer/text_rules.rb +107 -0
  212. data/lib/coradoc/asciidoc/transformer.rb +406 -0
  213. data/lib/coradoc/asciidoc/version.rb +7 -0
  214. data/lib/coradoc/asciidoc.rb +148 -0
  215. data/lib/coradoc/util/asciidoc.rb +71 -0
  216. data/lib/coradoc/util.rb +8 -0
  217. metadata +343 -0
@@ -0,0 +1,334 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module AsciiDoc
5
+ module Model
6
+ # A list of content elements that provides unified content handling
7
+ #
8
+ # ContentList handles mixed content types (strings, TextElements, model objects)
9
+ # and provides a consistent API for querying and manipulating content.
10
+ #
11
+ # @example Create from strings
12
+ # content = ContentList.new("Hello", "World")
13
+ # content.text # => "HelloWorld"
14
+ #
15
+ # @example Create from mixed types
16
+ # content = ContentList.new([
17
+ # "Hello ",
18
+ # Bold.new("World"),
19
+ # "!"
20
+ # ])
21
+ # content.text # => "Hello World!"
22
+ #
23
+ # @example Query by type
24
+ # bold_items = content.find_type(Inline::Bold)
25
+ #
26
+ # @example Iterate elements
27
+ # content.each do |element|
28
+ # puts element.class
29
+ # end
30
+ #
31
+ class ContentList
32
+ include Enumerable
33
+
34
+ # Get the raw items array
35
+ #
36
+ # @return [Array] The content items
37
+ attr_reader :items
38
+
39
+ # Create a new ContentList
40
+ #
41
+ # @param items [Array, String, Object] Content items to initialize with
42
+ #
43
+ # @example From strings
44
+ # ContentList.new("Hello", "World")
45
+ #
46
+ # @example From array
47
+ # ContentList.new(["Hello", Bold.new("World")])
48
+ #
49
+ # @example From nested array
50
+ # ContentList.new([["Hello", " "], "World"])
51
+ #
52
+ def initialize(*items)
53
+ @items = normalize(items.flatten)
54
+ freeze
55
+ end
56
+
57
+ # Create a ContentList from a single value
58
+ #
59
+ # @param value [Object] The content value
60
+ # @return [ContentList] New ContentList
61
+ #
62
+ # @example From string
63
+ # ContentList.from("Hello")
64
+ #
65
+ # @example From array
66
+ # ContentList.from(["Hello", "World"])
67
+ #
68
+ def self.from(value)
69
+ case value
70
+ when ContentList
71
+ value
72
+ when Array
73
+ new(*value)
74
+ when nil
75
+ new
76
+ else
77
+ new(value)
78
+ end
79
+ end
80
+
81
+ # Iterate over content items
82
+ #
83
+ # @yield [Object] Each content item
84
+ # @return [Enumerator] If no block given
85
+ #
86
+ # @example Iterate
87
+ # content.each { |item| puts item }
88
+ #
89
+ def each(&block)
90
+ @items.each(&block)
91
+ end
92
+
93
+ # Add an item to the content
94
+ #
95
+ # @param item [Object] Item to add
96
+ # @return [ContentList] Self for chaining
97
+ #
98
+ # @example Add item
99
+ # content << "more text"
100
+ #
101
+ # Note: This returns a new ContentList since ContentList is immutable
102
+ #
103
+ def <<(item)
104
+ ContentList.new(*@items, coerce(item))
105
+ end
106
+
107
+ # Get all items as a plain string
108
+ #
109
+ # @return [String] All items joined as string
110
+ #
111
+ # @example Get text
112
+ # content.text # => "Hello World"
113
+ #
114
+ def text
115
+ @items.map(&:to_s).join
116
+ end
117
+ alias to_str text
118
+ alias to_s text
119
+
120
+ # Find all items of a specific type
121
+ #
122
+ # @param type [Class, Module] Type to find
123
+ # @return [Array] Items of the specified type
124
+ #
125
+ # @example Find bold items
126
+ # content.find_type(Inline::Bold)
127
+ #
128
+ def find_type(type)
129
+ @items.select { |item| item.is_a?(type) }
130
+ end
131
+
132
+ # Check if content is empty
133
+ #
134
+ # @return [Boolean] true if no items
135
+ #
136
+ def empty?
137
+ @items.empty?
138
+ end
139
+
140
+ # Get number of items
141
+ #
142
+ # @return [Integer] Number of items
143
+ #
144
+ def size
145
+ @items.size
146
+ end
147
+ alias length size
148
+
149
+ # Get item at index
150
+ #
151
+ # @param index [Integer] Index
152
+ # @return [Object, nil] Item at index or nil
153
+ #
154
+ def [](index)
155
+ @items[index]
156
+ end
157
+
158
+ # Get first item
159
+ #
160
+ # @return [Object, nil] First item or nil
161
+ #
162
+ def first
163
+ @items.first
164
+ end
165
+
166
+ # Get last item
167
+ #
168
+ # @return [Object, nil] Last item or nil
169
+ #
170
+ def last
171
+ @items.last
172
+ end
173
+
174
+ # Convert to array
175
+ #
176
+ # @return [Array] Items as array
177
+ #
178
+ def to_a
179
+ @items.dup
180
+ end
181
+
182
+ # Join items with a separator
183
+ #
184
+ # @param sep [String] Separator
185
+ # @return [String] Joined string
186
+ #
187
+ # @example Join with spaces
188
+ # content.join(" ") # => "Hello World"
189
+ #
190
+ def join(sep = '')
191
+ @items.map(&:to_s).join(sep)
192
+ end
193
+
194
+ # Map over items
195
+ #
196
+ # @yield [Object] Each item
197
+ # @return [Array] Mapped items
198
+ #
199
+ # @example Map
200
+ # content.map(&:class) # => [String, Bold, String]
201
+ #
202
+ def map(&block)
203
+ @items.map(&block)
204
+ end
205
+
206
+ # Select items matching predicate
207
+ #
208
+ # @yield [Object] Each item
209
+ # @return [Array] Selected items
210
+ #
211
+ # @example Select strings
212
+ # content.select { |i| i.is_a?(String) }
213
+ #
214
+ def select(&block)
215
+ @items.select(&block)
216
+ end
217
+
218
+ # Reject items matching predicate
219
+ #
220
+ # @yield [Object] Each item
221
+ # @return [Array] Remaining items
222
+ #
223
+ def reject(&block)
224
+ @items.reject(&block)
225
+ end
226
+
227
+ # Check if content includes an item
228
+ #
229
+ # @param item [Object] Item to check
230
+ # @return [Boolean] true if item is in content
231
+ #
232
+ def include?(item)
233
+ @items.include?(item)
234
+ end
235
+
236
+ # Check if content includes an item of a type
237
+ #
238
+ # @param type [Class, Module] Type to check
239
+ # @return [Boolean] true if any item is of the type
240
+ #
241
+ # @example Check for bold
242
+ # content.include_type?(Inline::Bold)
243
+ #
244
+ def include_type?(type)
245
+ @items.any? { |item| item.is_a?(type) }
246
+ end
247
+
248
+ # Concatenate another ContentList or array
249
+ #
250
+ # @param other [ContentList, Array] Other content to add
251
+ # @return [ContentList] New ContentList with combined items
252
+ #
253
+ # @example Concatenate
254
+ # content + ContentList.new("more")
255
+ #
256
+ def +(other)
257
+ other_items = other.is_a?(ContentList) ? other.items : Array(other)
258
+ ContentList.new(*@items, *other_items)
259
+ end
260
+
261
+ # String representation for debugging
262
+ #
263
+ # @return [String] Debug string
264
+ #
265
+ def inspect
266
+ "#<#{self.class.name} size=#{@items.size} items=#{@items.inspect}>"
267
+ end
268
+
269
+ # Two ContentLists are equal if their items are equal
270
+ #
271
+ # @param other [Object] Object to compare
272
+ # @return [Boolean] true if equal
273
+ #
274
+ def ==(other)
275
+ return false unless other.is_a?(ContentList)
276
+
277
+ @items == other.items
278
+ end
279
+ alias eql? ==
280
+
281
+ # Hash code for use in Hash keys
282
+ #
283
+ # @return [Integer] Hash code
284
+ #
285
+ def hash
286
+ @items.hash
287
+ end
288
+
289
+ private
290
+
291
+ # Normalize items to appropriate content types
292
+ #
293
+ # @param items [Array] Raw items
294
+ # @return [Array] Normalized items
295
+ #
296
+ def normalize(items)
297
+ items.map { |item| coerce(item) }
298
+ end
299
+
300
+ # Coerce an item to appropriate content type
301
+ #
302
+ # @param item [Object] Item to coerce
303
+ # @return [Object] Coerced item
304
+ #
305
+ def coerce(item)
306
+ case item
307
+ when String
308
+ # Convert string to TextElement
309
+ TextElement.new(content: item)
310
+ when TextElement, Model::Base
311
+ # Already appropriate type
312
+ item
313
+ when nil
314
+ # Skip nil items
315
+ nil
316
+ when Hash
317
+ # Convert hash to TextElement if it has content key
318
+ if item[:content] || item['content']
319
+ TextElement.from_hash(item)
320
+ else
321
+ TextElement.new(content: item.inspect)
322
+ end
323
+ when Array
324
+ # Flatten nested arrays
325
+ normalize(item)
326
+ else
327
+ # Try to convert to string, then to TextElement
328
+ TextElement.new(content: item.to_s)
329
+ end
330
+ end
331
+ end
332
+ end
333
+ end
334
+ end
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module AsciiDoc
5
+ module Model
6
+ autoload :Resolver, "#{__dir__}/resolver"
7
+
8
+ # Document model representing an AsciiDoc document.
9
+ #
10
+ # The Document class is the main container for parsed AsciiDoc content.
11
+ # It holds the document's header, attributes, and sections (blocks, lists, etc.).
12
+ #
13
+ # @!attribute [r] document_attributes
14
+ # @return [DocumentAttributes] Document-level attributes like author, date, etc.
15
+ # @!attribute [r] header
16
+ # @return [Header] Document header containing title and metadata
17
+ # @!attribute [r] sections
18
+ # @return [Array<Base>] Document content blocks (sections, paragraphs, lists, etc.)
19
+ #
20
+ # @example Create a new document
21
+ # doc = Coradoc::AsciiDoc::Model::Document.new(
22
+ # header: Coradoc::AsciiDoc::Model::Header.new(title: "My Document"),
23
+ # sections: [Coradoc::AsciiDoc::Model::Paragraph.new("Hello World")]
24
+ # )
25
+ #
26
+ # @example Parse and serialize
27
+ # doc = Coradoc.parse("= Title\n\nContent")
28
+ # doc.to_adoc # => "= Title\n\nContent"
29
+ #
30
+ # @example Expand includes
31
+ # doc = Coradoc.parse_file("main.adoc")
32
+ # expanded = doc.expand_includes("/path/to/docs")
33
+ #
34
+ # @example Freeze document with unified resolution
35
+ # frozen = doc.freeze(base_dir: "/path/to/docs", includes: true, images: :reference)
36
+ #
37
+ class Document < Base
38
+ attribute :document_attributes,
39
+ Coradoc::AsciiDoc::Model::DocumentAttributes,
40
+ default: lambda {
41
+ Coradoc::AsciiDoc::Model::DocumentAttributes.new
42
+ }
43
+ attribute :header,
44
+ Coradoc::AsciiDoc::Model::Header,
45
+ default: lambda {
46
+ Coradoc::AsciiDoc::Model::Header.new(
47
+ title: Coradoc::AsciiDoc::Model::Title.new(content: '')
48
+ )
49
+ }
50
+
51
+ attribute :sections,
52
+ Coradoc::AsciiDoc::Model::Base,
53
+ collection: true,
54
+ initialize_empty: true,
55
+ polymorphic: [
56
+ Coradoc::AsciiDoc::Model::Admonition,
57
+ Coradoc::AsciiDoc::Model::Audio,
58
+ Coradoc::AsciiDoc::Model::BibliographyEntry,
59
+ Coradoc::AsciiDoc::Model::Block::Core,
60
+ Coradoc::AsciiDoc::Model::Image::BlockImage,
61
+ Coradoc::AsciiDoc::Model::CommentBlock,
62
+ Coradoc::AsciiDoc::Model::CommentLine,
63
+ Coradoc::AsciiDoc::Model::Include,
64
+ Coradoc::AsciiDoc::Model::LineBreak,
65
+ Coradoc::AsciiDoc::Model::List::Core,
66
+ Coradoc::AsciiDoc::Model::Paragraph,
67
+ Coradoc::AsciiDoc::Model::Table,
68
+ Coradoc::AsciiDoc::Model::Tag,
69
+ Coradoc::AsciiDoc::Model::Video
70
+ ]
71
+
72
+ # @param [Integer] index The index of the section to retrieve
73
+ # @return [Coradoc::AsciiDoc::Model::Base] The section at the specified index
74
+ def [](index)
75
+ sections[index]
76
+ end
77
+
78
+ # @param [Integer] index The index of the section to set
79
+ # @param [Coradoc::AsciiDoc::Model::Base] value The section to set at the specified index
80
+ # @return [Coradoc::AsciiDoc::Model::Base] The section that was set
81
+ def []=(index, value)
82
+ sections[index] = value
83
+ end
84
+
85
+ # Expand include directives in the document
86
+ # @param base_dir [String] Base directory for resolving relative includes
87
+ # @return [Coradoc::AsciiDoc::Model::Document] A new document with includes expanded
88
+ def expand_includes(base_dir = '.')
89
+ freeze(base_dir: base_dir, includes: true, images: :reference, media: :reference)
90
+ end
91
+
92
+ # Freeze the document by resolving external references.
93
+ #
94
+ # This method creates a NEW document with resolved references.
95
+ # The original document is never modified (immutable principle).
96
+ #
97
+ # @param options [Hash] Resolution options
98
+ # @option options [String] :base_dir Base directory for relative paths (default: ".")
99
+ # @option options [Boolean] :includes Resolve include:: directives (default: true)
100
+ # @option options [Symbol] :images Image resolution: :reference, :copy, :embed (default: :reference)
101
+ # @option options [Symbol] :media Media resolution: :reference, :copy (default: :reference)
102
+ # @option options [String] :output_dir Output directory for :copy mode
103
+ # @option options [Integer] :max_recursion Maximum recursion depth for includes (default: 10)
104
+ # @return [Document] NEW document with resolved references
105
+ #
106
+ # @example Resolve includes only
107
+ # frozen = doc.freeze(base_dir: "/docs", includes: true)
108
+ #
109
+ # @example Create self-contained document
110
+ # frozen = doc.freeze(
111
+ # base_dir: "/docs",
112
+ # includes: true,
113
+ # images: :embed,
114
+ # output_dir: "/output"
115
+ # )
116
+ #
117
+ def freeze(options = {})
118
+ resolver = Resolver.new(options)
119
+ base_dir = options[:base_dir] || '.'
120
+ resolver.resolve_document(self, base_dir)
121
+ end
122
+
123
+ class << self
124
+ def from_ast(elements)
125
+ sections = []
126
+ document_attributes = nil
127
+ header = nil
128
+
129
+ elements.each do |element|
130
+ case element
131
+ when Coradoc::AsciiDoc::Model::DocumentAttributes
132
+ document_attributes = element
133
+
134
+ when Coradoc::AsciiDoc::Model::Header
135
+ header = element
136
+
137
+ when Coradoc::AsciiDoc::Model::Base
138
+ sections << element
139
+
140
+ else
141
+ warn "Unknown element type: #{element.class}"
142
+ warn "Element: #{element.inspect}"
143
+ end
144
+ end
145
+
146
+ # Merge standalone LineBreak elements into the previous element's line_break
147
+ merge_line_breaks(sections)
148
+
149
+ # Only pass non-nil values to preserve defaults
150
+ attrs = { sections: sections }
151
+ attrs[:document_attributes] = document_attributes if document_attributes
152
+ attrs[:header] = header if header
153
+
154
+ new(attrs)
155
+ end
156
+
157
+ private
158
+
159
+ def merge_line_breaks(sections)
160
+ return if sections.empty?
161
+
162
+ # Skip leading LineBreak elements
163
+ sections.shift while sections.first.is_a?(Coradoc::AsciiDoc::Model::LineBreak)
164
+
165
+ i = 0
166
+ while i < sections.length
167
+ # If current element is a LineBreak and there's a previous element
168
+ if sections[i].is_a?(Coradoc::AsciiDoc::Model::LineBreak) && i.positive?
169
+ prev = sections[i - 1]
170
+ line_break = sections[i]
171
+
172
+ # Skip consecutive LineBreaks
173
+ if prev.is_a?(Coradoc::AsciiDoc::Model::LineBreak)
174
+ sections.delete_at(i)
175
+ next
176
+ end
177
+
178
+ # Merge the line break into the previous element if it has a line_break attribute
179
+ if prev.is_a?(Coradoc::AsciiDoc::Model::Base) && prev.class.attributes.key?(:line_break)
180
+ prev.line_break = prev.line_break.to_s + line_break.line_break.to_s
181
+ sections.delete_at(i)
182
+ # Don't increment i since we deleted an element
183
+ next
184
+ else
185
+ # Keep as standalone if no suitable previous element
186
+ i += 1
187
+ end
188
+ else
189
+ i += 1
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module AsciiDoc
5
+ module Model
6
+ # Container for document-level attributes.
7
+ #
8
+ # DocumentAttributes holds key-value pairs that define document metadata
9
+ # and configuration options. These are the `:key: value` declarations at
10
+ # the top of an AsciiDoc file.
11
+ #
12
+ # @!attribute [r] data
13
+ # @return [Array<Attribute>] Array of attribute key-value pairs
14
+ #
15
+ # @example Access attributes as hash
16
+ # attrs = Coradoc::AsciiDoc::Model::DocumentAttributes.new
17
+ # attrs.data << Coradoc::AsciiDoc::Model::Attribute.new("author", "John Doe")
18
+ # attrs.to_hash # => {"author" => "John Doe"}
19
+ #
20
+ # @example Get specific attribute
21
+ # value = attrs.get_attribute("author")
22
+ #
23
+ class DocumentAttributes < Base
24
+ attribute :data, Attribute, collection: true
25
+
26
+ def to_hash
27
+ return {} if data.nil?
28
+
29
+ data.each_with_object({}) do |attribute, hash|
30
+ hash[attribute.key.to_s] = attribute.value
31
+ end
32
+ end
33
+
34
+ def get_attribute(name)
35
+ return nil if data.nil?
36
+
37
+ attribute = data.find { |attr| attr.key.to_s == name.to_s }
38
+ attribute&.value
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module AsciiDoc
5
+ module Model
6
+ class Glossaries < Base
7
+ attribute :items, :string, collection: true
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module AsciiDoc
5
+ module Model
6
+ # Document header containing title and metadata.
7
+ #
8
+ # The Header represents the document-level metadata including the main title,
9
+ # author information, and revision details. This corresponds to the AsciiDoc
10
+ # header line (e.g., `= Document Title`) and associated metadata.
11
+ #
12
+ # @!attribute [r] title
13
+ # @return [String] The main document title
14
+ # @!attribute [r] author
15
+ # @return [Author, nil] Document author information
16
+ # @!attribute [r] revision
17
+ # @return [Revision, nil] Document revision information (version, date)
18
+ #
19
+ # @example Create a simple header
20
+ # header = Coradoc::AsciiDoc::Model::Header.new(title: "My Document")
21
+ #
22
+ # @example Create a header with author and revision
23
+ # header = Coradoc::AsciiDoc::Model::Header.new(
24
+ # title: "My Document",
25
+ # author: Coradoc::AsciiDoc::Model::Author.new("John Doe"),
26
+ # revision: Coradoc::AsciiDoc::Model::Revision.new("1.0", "2024-01-01")
27
+ # )
28
+ #
29
+ class Header < Base
30
+ include Coradoc::AsciiDoc::Model::Anchorable
31
+
32
+ attribute :title, Coradoc::AsciiDoc::Model::Title
33
+ attribute :author, Coradoc::AsciiDoc::Model::Author
34
+ attribute :revision, Coradoc::AsciiDoc::Model::Revision
35
+
36
+ def validate
37
+ validate_author_type
38
+ validate_revision_type
39
+ end
40
+
41
+ private
42
+
43
+ def validate_author_type
44
+ return if author.nil? || author.is_a?(Coradoc::AsciiDoc::Model::Author)
45
+
46
+ raise TypeError, "author must be a Coradoc::AsciiDoc::Model::Author, got #{author.class}"
47
+ end
48
+
49
+ def validate_revision_type
50
+ return if revision.nil? || revision.is_a?(Coradoc::AsciiDoc::Model::Revision)
51
+
52
+ raise TypeError, "revision must be a Coradoc::AsciiDoc::Model::Revision, got #{revision.class}"
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module AsciiDoc
5
+ module Model
6
+ class Highlight < TextElement
7
+ attribute :unconstrained, :boolean, default: -> { false }
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module AsciiDoc
5
+ module Model
6
+ class BlockImage < Coradoc::AsciiDoc::Model::Image::Core
7
+ class AttributeList < Coradoc::AsciiDoc::Model::Image::Core::AttributeList
8
+ extend AttributeList::Matchers
9
+
10
+ def named_validators
11
+ super.merge(
12
+ {
13
+ caption: String,
14
+ align: one('left', 'center', 'right'),
15
+ float: one('left', 'right')
16
+ }
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module AsciiDoc
5
+ module Model
6
+ module Image
7
+ class BlockImage < Coradoc::AsciiDoc::Model::Image::Core
8
+ def block_level?
9
+ true
10
+ end
11
+
12
+ # Autoload nested AttributeList class
13
+ autoload :AttributeList, 'coradoc/asciidoc/model/image/block_image/attribute_list'
14
+
15
+ attribute :colons, :string, default: -> { '::' }
16
+ attribute :attributes,
17
+ Coradoc::AsciiDoc::Model::Image::BlockImage::AttributeList,
18
+ default: lambda {
19
+ ::Coradoc::AsciiDoc::Model::AttributeList.new
20
+ }
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end