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,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module AsciiDoc
5
+ module Model
6
+ # Module for nodes that reference external resources.
7
+ #
8
+ # Any model element that references an external file or resource
9
+ # should include this module to provide a unified interface for
10
+ # resolution operations.
11
+ #
12
+ # @example Implementing Resolvable in a class
13
+ # class Include < Base
14
+ # include Resolvable
15
+ #
16
+ # def reference_path
17
+ # path
18
+ # end
19
+ #
20
+ # def reference_type
21
+ # :include
22
+ # end
23
+ # end
24
+ #
25
+ module Resolvable
26
+ # Returns the path to the external resource.
27
+ #
28
+ # @return [String] the path or URL to the external resource
29
+ # @raise [NotImplementedError] if not implemented by including class
30
+ def reference_path
31
+ raise NotImplementedError,
32
+ "#{self.class} must implement #reference_path"
33
+ end
34
+
35
+ # Returns the type of reference.
36
+ #
37
+ # @return [Symbol] the reference type (:include, :image, :video, :audio, :link)
38
+ # @raise [NotImplementedError] if not implemented by including class
39
+ def reference_type
40
+ raise NotImplementedError,
41
+ "#{self.class} must implement #reference_type"
42
+ end
43
+
44
+ # Returns additional options for reference resolution.
45
+ #
46
+ # @return [Hash] options for resolution (e.g., leveloffset, lines, tags)
47
+ def reference_options
48
+ {}
49
+ end
50
+
51
+ # Checks if the reference is to a local file.
52
+ #
53
+ # @return [Boolean] true if the reference is a local file path
54
+ def local_reference?
55
+ path = reference_path
56
+ return false if path.nil?
57
+
58
+ # Not a URL if it doesn't start with a scheme
59
+ !path.match?(%r{^[a-z][a-z0-9+.-]*://}i)
60
+ end
61
+
62
+ # Checks if the reference is to a remote URL.
63
+ #
64
+ # @return [Boolean] true if the reference is a URL
65
+ def remote_reference?
66
+ !local_reference?
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,430 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+
5
+ module Coradoc
6
+ module AsciiDoc
7
+ module Model
8
+ # Resolver classes for handling external document references.
9
+ #
10
+ # The resolver infrastructure provides a unified way to handle different
11
+ # types of external references (includes, images, media) with configurable
12
+ # resolution strategies.
13
+ #
14
+ # @example Resolving includes only
15
+ # resolver = Resolver.new(includes: true, images: :reference, media: :reference)
16
+ # resolved = resolver.resolve_document(doc, base_dir)
17
+ #
18
+ # @example Creating a self-contained document
19
+ # resolver = Resolver.new(
20
+ # includes: true,
21
+ # images: :embed,
22
+ # media: :copy,
23
+ # output_dir: "/path/to/output"
24
+ # )
25
+ # resolved = resolver.resolve_document(doc, base_dir)
26
+ #
27
+ class Resolver
28
+ # @return [IncludeResolver, nil] the include resolver (nil if disabled)
29
+ attr_reader :include_resolver
30
+
31
+ # @return [ImageResolver] the image resolver
32
+ attr_reader :image_resolver
33
+
34
+ # @return [MediaResolver] the media resolver
35
+ attr_reader :media_resolver
36
+
37
+ # @return [String, nil] the output directory for copied files
38
+ attr_reader :output_dir
39
+
40
+ # Create a new Resolver with specified options.
41
+ #
42
+ # @param options [Hash] resolution options
43
+ # @option options [Boolean] :includes Whether to resolve include directives
44
+ # @option options [Symbol] :images Image resolution strategy (:reference, :copy, :embed)
45
+ # @option options [Symbol] :media Media resolution strategy (:reference, :copy, :embed)
46
+ # @option options [String] :output_dir Output directory for :copy mode
47
+ # @option options [Integer] :max_recursion Maximum recursion depth for includes
48
+ #
49
+ def initialize(options = {})
50
+ @include_resolver = options[:includes] ? IncludeResolver.new : nil
51
+ @image_resolver = ImageResolver.new(strategy: options[:images] || :reference)
52
+ @media_resolver = MediaResolver.new(strategy: options[:media] || :reference)
53
+ @output_dir = options[:output_dir]
54
+ @max_recursion = options[:max_recursion] || 10
55
+ @resolved_paths = {} # Track resolved paths to prevent infinite recursion
56
+ end
57
+
58
+ # Resolve a single node.
59
+ #
60
+ # @param node [Base] the node to resolve
61
+ # @param base_dir [String] base directory for relative paths
62
+ # @param depth [Integer] current recursion depth
63
+ # @return [Base, Array<Base>] the resolved node(s)
64
+ #
65
+ def resolve(node, base_dir, depth = 0)
66
+ return node if depth > @max_recursion
67
+
68
+ case node
69
+ when Include
70
+ resolve_include(node, base_dir, depth)
71
+ when Image::BlockImage, Image::InlineImage
72
+ resolve_image(node, base_dir)
73
+ when Video
74
+ resolve_media(node, base_dir, :video)
75
+ when Audio
76
+ resolve_media(node, base_dir, :audio)
77
+ else
78
+ node
79
+ end
80
+ end
81
+
82
+ # Resolve an entire document.
83
+ #
84
+ # @param document [Document] the document to resolve
85
+ # @param base_dir [String] base directory for relative paths
86
+ # @return [Document] a NEW document with resolved references
87
+ #
88
+ def resolve_document(document, base_dir)
89
+ resolved_sections = document.sections.map do |section|
90
+ resolve_node_recursive(section, base_dir, 0)
91
+ end.flatten.compact
92
+
93
+ document.class.new(
94
+ document_attributes: document.document_attributes,
95
+ header: document.header,
96
+ sections: resolved_sections
97
+ )
98
+ end
99
+
100
+ private
101
+
102
+ def resolve_include(include_node, base_dir, depth)
103
+ return [include_node] unless @include_resolver
104
+
105
+ path = include_node.reference_path
106
+ full_path = File.expand_path(path, base_dir)
107
+
108
+ # Check for recursion
109
+ if @resolved_paths[full_path]
110
+ warn "[Coradoc] Warning: Circular include detected: #{path}"
111
+ return [include_node]
112
+ end
113
+
114
+ @resolved_paths[full_path] = true
115
+
116
+ result = @include_resolver.resolve(include_node, base_dir)
117
+
118
+ # Recursively resolve the included content
119
+ if result.is_a?(Array)
120
+ result.map { |node| resolve_node_recursive(node, File.dirname(full_path), depth + 1) }.flatten
121
+ else
122
+ resolve_node_recursive(result, File.dirname(full_path), depth + 1)
123
+ end
124
+ ensure
125
+ @resolved_paths.delete(full_path) if full_path
126
+ end
127
+
128
+ def resolve_image(image_node, base_dir)
129
+ @image_resolver.resolve(image_node, base_dir, @output_dir)
130
+ end
131
+
132
+ def resolve_media(media_node, base_dir, _type)
133
+ @media_resolver.resolve(media_node, base_dir, @output_dir)
134
+ end
135
+
136
+ def resolve_node_recursive(node, base_dir, depth)
137
+ return node if depth > @max_recursion
138
+
139
+ resolved = resolve(node, base_dir, depth)
140
+
141
+ # If resolution returns an array (e.g., expanded includes), process each
142
+ return resolved.map { |n| resolve_node_recursive(n, base_dir, depth) }.flatten if resolved.is_a?(Array)
143
+
144
+ # Handle nodes with nested content
145
+ if resolved.is_a?(Coradoc::AsciiDoc::Model::Base) && resolved.class.attributes.key?(:contents) && resolved.contents
146
+ resolved = deep_copy_node(resolved)
147
+ resolved.contents = resolved.contents.map do |child|
148
+ resolve_node_recursive(child, base_dir, depth + 1)
149
+ end.flatten.compact
150
+ end
151
+
152
+ # Handle sections with nested content
153
+ if resolved.is_a?(Coradoc::AsciiDoc::Model::Base) && resolved.class.attributes.key?(:sections) && resolved.sections && !resolved.is_a?(Document)
154
+ resolved = deep_copy_node(resolved)
155
+ resolved.sections = resolved.sections.map do |child|
156
+ resolve_node_recursive(child, base_dir, depth + 1)
157
+ end.flatten.compact
158
+ end
159
+
160
+ resolved
161
+ end
162
+
163
+ def deep_copy_node(node)
164
+ # Create a new instance with the same attributes
165
+ node.class.new(node.to_h)
166
+ rescue StandardError
167
+ # If to_h doesn't work, return the original node
168
+ node
169
+ end
170
+ end
171
+
172
+ # Resolves include directives by parsing and including file contents.
173
+ class IncludeResolver
174
+ # Resolve an include directive.
175
+ #
176
+ # @param include_node [Include] the include directive
177
+ # @param base_dir [String] base directory for relative paths
178
+ # @return [Array<Base>, Include] the included content or original node if not found
179
+ #
180
+ def resolve(include_node, base_dir)
181
+ path = include_node.reference_path
182
+ full_path = File.expand_path(path, base_dir)
183
+
184
+ unless File.exist?(full_path)
185
+ warn "[Coradoc] Warning: Include file not found: #{path}"
186
+ return [include_node]
187
+ end
188
+
189
+ content = File.read(full_path)
190
+
191
+ # Apply include options (lines, tags, etc.)
192
+ content = apply_include_options(content, include_node.reference_options)
193
+
194
+ # Parse the included content
195
+ included_doc = Coradoc::AsciiDoc.parse(content)
196
+
197
+ # Return the sections from the included document
198
+ included_doc.sections || []
199
+ end
200
+
201
+ private
202
+
203
+ def apply_include_options(content, options)
204
+ return content if options.empty?
205
+
206
+ lines = content.lines
207
+
208
+ # Apply lines filter
209
+ if options[:lines]
210
+ range_match = options[:lines].match(/(\d+)\.\.(\d+)/)
211
+ if range_match
212
+ start_line = [1, range_match[1].to_i].max
213
+ end_line = [lines.length, range_match[2].to_i].min
214
+ lines = lines[(start_line - 1)...end_line] || []
215
+ end
216
+ end
217
+
218
+ # Apply tags filter
219
+ lines = filter_by_tags(lines, options[:tags]) if options[:tags]
220
+
221
+ lines.join
222
+ end
223
+
224
+ def filter_by_tags(lines, tags)
225
+ # Simple tag filtering - find lines between tag markers
226
+ result = []
227
+ in_tag = false
228
+ current_tags = []
229
+
230
+ lines.each do |line|
231
+ # Check for tag start: // tag::tagname[]
232
+ if line =~ %r{//\s*tag::(\w+)\[\]}
233
+ tag_name = Regexp.last_match(1)
234
+ if tags.include?(tag_name)
235
+ in_tag = true
236
+ current_tags << tag_name
237
+ end
238
+ next
239
+ end
240
+
241
+ # Check for tag end: // end::tagname[]
242
+ if line =~ %r{//\s*end::(\w+)\[\]}
243
+ tag_name = Regexp.last_match(1)
244
+ if current_tags.include?(tag_name)
245
+ current_tags.delete(tag_name)
246
+ in_tag = false if current_tags.empty?
247
+ end
248
+ next
249
+ end
250
+
251
+ result << line if in_tag
252
+ end
253
+
254
+ result
255
+ end
256
+ end
257
+
258
+ # Resolves image references with configurable strategies.
259
+ class ImageResolver
260
+ # @return [Symbol] the resolution strategy
261
+ attr_reader :strategy
262
+
263
+ # Create a new ImageResolver.
264
+ #
265
+ # @param strategy [Symbol] resolution strategy (:reference, :copy, :embed)
266
+ #
267
+ def initialize(strategy: :reference)
268
+ @strategy = strategy
269
+ end
270
+
271
+ # Resolve an image reference.
272
+ #
273
+ # @param image_node [Image::Core] the image node
274
+ # @param base_dir [String] base directory for relative paths
275
+ # @param output_dir [String, nil] output directory for :copy mode
276
+ # @return [Image::Core] the resolved image node
277
+ #
278
+ def resolve(image_node, base_dir, output_dir = nil)
279
+ case strategy
280
+ when :reference
281
+ image_node
282
+ when :copy
283
+ copy_image(image_node, base_dir, output_dir)
284
+ when :embed
285
+ embed_image(image_node, base_dir)
286
+ else
287
+ image_node
288
+ end
289
+ end
290
+
291
+ private
292
+
293
+ def copy_image(image_node, base_dir, output_dir)
294
+ return image_node unless output_dir
295
+
296
+ src = image_node.src
297
+ return image_node if src.nil? || src.start_with?('data:')
298
+
299
+ full_path = File.expand_path(src, base_dir)
300
+
301
+ unless File.exist?(full_path)
302
+ warn "[Coradoc] Warning: Image file not found: #{src}"
303
+ return image_node
304
+ end
305
+
306
+ # Create images subdirectory in output
307
+ images_dir = File.join(output_dir, 'images')
308
+ FileUtils.mkdir_p(images_dir)
309
+
310
+ # Copy the file
311
+ dest_path = File.join(images_dir, File.basename(src))
312
+ FileUtils.cp(full_path, dest_path)
313
+
314
+ # Update the image node with new path
315
+ new_node = image_node.class.new(image_node.to_h)
316
+ new_node.src = "images/#{File.basename(src)}"
317
+ new_node
318
+ rescue StandardError => e
319
+ warn "[Coradoc] Warning: Failed to copy image #{src}: #{e.message}"
320
+ image_node
321
+ end
322
+
323
+ def embed_image(image_node, base_dir)
324
+ src = image_node.src
325
+ return image_node if src.nil? || src.start_with?('data:')
326
+
327
+ full_path = File.expand_path(src, base_dir)
328
+
329
+ unless File.exist?(full_path)
330
+ warn "[Coradoc] Warning: Image file not found: #{src}"
331
+ return image_node
332
+ end
333
+
334
+ # Read and encode the image
335
+ image_data = File.read(full_path, mode: 'rb')
336
+ base64_data = Base64.strict_encode64(image_data)
337
+
338
+ # Determine MIME type from extension
339
+ mime_type = mime_type_for(File.extname(src))
340
+
341
+ # Create data URI
342
+ data_uri = "data:#{mime_type};base64,#{base64_data}"
343
+
344
+ # Update the image node
345
+ new_node = image_node.class.new(image_node.to_h)
346
+ new_node.src = data_uri
347
+ new_node
348
+ rescue StandardError => e
349
+ warn "[Coradoc] Warning: Failed to embed image #{src}: #{e.message}"
350
+ image_node
351
+ end
352
+
353
+ def mime_type_for(extension)
354
+ case extension.downcase
355
+ when '.png' then 'image/png'
356
+ when '.jpg', '.jpeg' then 'image/jpeg'
357
+ when '.gif' then 'image/gif'
358
+ when '.svg' then 'image/svg+xml'
359
+ when '.webp' then 'image/webp'
360
+ else 'application/octet-stream'
361
+ end
362
+ end
363
+ end
364
+
365
+ # Resolves media (video/audio) references with configurable strategies.
366
+ class MediaResolver
367
+ # @return [Symbol] the resolution strategy
368
+ attr_reader :strategy
369
+
370
+ # Create a new MediaResolver.
371
+ #
372
+ # @param strategy [Symbol] resolution strategy (:reference, :copy)
373
+ #
374
+ def initialize(strategy: :reference)
375
+ @strategy = strategy
376
+ end
377
+
378
+ # Resolve a media reference.
379
+ #
380
+ # @param media_node [Video, Audio] the media node
381
+ # @param base_dir [String] base directory for relative paths
382
+ # @param output_dir [String, nil] output directory for :copy mode
383
+ # @return [Video, Audio] the resolved media node
384
+ #
385
+ def resolve(media_node, base_dir, output_dir = nil)
386
+ case strategy
387
+ when :reference
388
+ media_node
389
+ when :copy
390
+ copy_media(media_node, base_dir, output_dir)
391
+ else
392
+ media_node
393
+ end
394
+ end
395
+
396
+ private
397
+
398
+ def copy_media(media_node, base_dir, output_dir)
399
+ return media_node unless output_dir
400
+
401
+ src = media_node.src
402
+ return media_node if src.nil? || src.match?(%r{^[a-z][a-z0-9+.-]*://}i)
403
+
404
+ full_path = File.expand_path(src, base_dir)
405
+
406
+ unless File.exist?(full_path)
407
+ warn "[Coradoc] Warning: Media file not found: #{src}"
408
+ return media_node
409
+ end
410
+
411
+ # Create media subdirectory in output
412
+ media_dir = File.join(output_dir, 'media')
413
+ FileUtils.mkdir_p(media_dir)
414
+
415
+ # Copy the file
416
+ dest_path = File.join(media_dir, File.basename(src))
417
+ FileUtils.cp(full_path, dest_path)
418
+
419
+ # Update the media node with new path
420
+ new_node = media_node.class.new(media_node.to_h)
421
+ new_node.src = "media/#{File.basename(src)}"
422
+ new_node
423
+ rescue StandardError => e
424
+ warn "[Coradoc] Warning: Failed to copy media #{src}: #{e.message}"
425
+ media_node
426
+ end
427
+ end
428
+ end
429
+ end
430
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module AsciiDoc
5
+ module Model
6
+ # Reviewer note block element for AsciiDoc documents.
7
+ #
8
+ # Reviewer notes capture feedback from document reviewers with
9
+ # metadata about who provided the feedback and when.
10
+ #
11
+ # @!attribute [r] reviewer
12
+ # @return [String, nil] Name of the reviewer
13
+ #
14
+ # @!attribute [r] date
15
+ # @return [String, nil] Date of the review
16
+ #
17
+ # @!attribute [r] from
18
+ # @return [String, nil] Review start indicator
19
+ #
20
+ # @!attribute [r] to
21
+ # @return [String, nil] Review end indicator
22
+ #
23
+ # @!attribute [r] content
24
+ # @return [Array<Coradoc::AsciiDoc::Model::Base>] Polymorphic content (paragraphs, lists, admonitions, etc.)
25
+ #
26
+ # @example Create a reviewer note
27
+ # note = Coradoc::AsciiDoc::Model::ReviewerNote.new
28
+ # note.reviewer = "John Doe"
29
+ # note.date = "2024-01-15"
30
+ # note.content = [Coradoc::AsciiDoc::Model::Paragraph.new]
31
+ #
32
+ class ReviewerNote < Base
33
+ # Reviewer note attributes
34
+ attribute :reviewer, :string
35
+ attribute :date, :string
36
+ attribute :from, :string
37
+ attribute :to, :string
38
+
39
+ # Content can be any AsciiDoc elements (paragraphs, lists, etc.)
40
+ attribute :content,
41
+ Coradoc::AsciiDoc::Model::Base,
42
+ collection: true,
43
+ initialize_empty: true,
44
+ polymorphic: [
45
+ Coradoc::AsciiDoc::Model::TextElement,
46
+ Coradoc::AsciiDoc::Model::Paragraph,
47
+ Coradoc::AsciiDoc::Model::Admonition,
48
+ Coradoc::AsciiDoc::Model::LineBreak,
49
+ Coradoc::AsciiDoc::Model::List::Core
50
+ ]
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module AsciiDoc
5
+ module Model
6
+ # Revision information for document headers.
7
+ #
8
+ # Revision metadata tracks document version numbers, dates, and remarks.
9
+ #
10
+ # @!attribute [r] number
11
+ # @return [String, nil] The revision number (e.g., "1.0", "2.1")
12
+ #
13
+ # @!attribute [r] date
14
+ # @return [Date, nil] The revision date
15
+ #
16
+ # @!attribute [r] remark
17
+ # @return [String, nil] Optional revision notes
18
+ #
19
+ # @example Create a revision
20
+ # rev = Coradoc::AsciiDoc::Model::Revision.new
21
+ # rev.number = "1.0"
22
+ # rev.date = Date.new(2024, 1, 15)
23
+ # rev.remark = "Initial release"
24
+ #
25
+ # @raise [TypeError] if date is not a Date object
26
+ #
27
+ class Revision < Base
28
+ attribute :number, :string
29
+ attribute :date, :date
30
+ attribute :remark, :string
31
+
32
+ def validate
33
+ super
34
+ validate_date_type
35
+ end
36
+
37
+ private
38
+
39
+ def validate_date_type
40
+ return if date.nil? || date.is_a?(Date)
41
+
42
+ raise TypeError, "date must be a Date, got #{date.class}"
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end