Class: Coradoc::AsciiDoc::Model::Document

Inherits:
Base
  • Object
show all
Defined in:
lib/coradoc/asciidoc/model/document.rb

Overview

Document model representing an AsciiDoc document.

The Document class is the main container for parsed AsciiDoc content. It holds the document’s header, attributes, and sections (blocks, lists, etc.).

Examples:

Create a new document

doc = Coradoc::AsciiDoc::Model::Document.new(
  header: Coradoc::AsciiDoc::Model::Header.new(title: "My Document"),
  sections: [Coradoc::AsciiDoc::Model::Paragraph.new("Hello World")]
)

Parse and serialize

doc = Coradoc.parse("= Title\n\nContent")
doc.to_adoc # => "= Title\n\nContent"

Expand includes

doc = Coradoc.parse_file("main.adoc")
expanded = doc.expand_includes("/path/to/docs")

Freeze document with unified resolution

frozen = doc.freeze(base_dir: "/path/to/docs", includes: true, images: :reference)

Instance Attribute Summary collapse

Attributes inherited from Base

#id

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

#block_level?, #inline?, #serialize_content, #simplify_block_content, #to_adoc, #to_h, visit, #visit

Instance Attribute Details

#document_attributesDocumentAttributes (readonly)

Returns Document-level attributes like author, date, etc.

Returns:



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/coradoc/asciidoc/model/document.rb', line 37

class Document < Base
  attribute :document_attributes,
            Coradoc::AsciiDoc::Model::DocumentAttributes,
            default: lambda {
              Coradoc::AsciiDoc::Model::DocumentAttributes.new
            }
  attribute :header,
            Coradoc::AsciiDoc::Model::Header,
            default: lambda {
              Coradoc::AsciiDoc::Model::Header.new(
                title: Coradoc::AsciiDoc::Model::Title.new(content: '')
              )
            }

  attribute :sections,
            Coradoc::AsciiDoc::Model::Base,
            collection: true,
            initialize_empty: true,
            polymorphic: [
              Coradoc::AsciiDoc::Model::Admonition,
              Coradoc::AsciiDoc::Model::Audio,
              Coradoc::AsciiDoc::Model::BibliographyEntry,
              Coradoc::AsciiDoc::Model::Block::Core,
              Coradoc::AsciiDoc::Model::Image::BlockImage,
              Coradoc::AsciiDoc::Model::CommentBlock,
              Coradoc::AsciiDoc::Model::CommentLine,
              Coradoc::AsciiDoc::Model::Include,
              Coradoc::AsciiDoc::Model::LineBreak,
              Coradoc::AsciiDoc::Model::List::Core,
              Coradoc::AsciiDoc::Model::Paragraph,
              Coradoc::AsciiDoc::Model::Table,
              Coradoc::AsciiDoc::Model::Tag,
              Coradoc::AsciiDoc::Model::Video
            ]

  # @param [Integer] index The index of the section to retrieve
  # @return [Coradoc::AsciiDoc::Model::Base] The section at the specified index
  def [](index)
    sections[index]
  end

  # @param [Integer] index The index of the section to set
  # @param [Coradoc::AsciiDoc::Model::Base] value The section to set at the specified index
  # @return [Coradoc::AsciiDoc::Model::Base] The section that was set
  def []=(index, value)
    sections[index] = value
  end

  # Expand include directives in the document
  # @param base_dir [String] Base directory for resolving relative includes
  # @return [Coradoc::AsciiDoc::Model::Document] A new document with includes expanded
  def expand_includes(base_dir = '.')
    freeze(base_dir: base_dir, includes: true, images: :reference, media: :reference)
  end

  # Freeze the document by resolving external references.
  #
  # This method creates a NEW document with resolved references.
  # The original document is never modified (immutable principle).
  #
  # @param options [Hash] Resolution options
  # @option options [String] :base_dir Base directory for relative paths (default: ".")
  # @option options [Boolean] :includes Resolve include:: directives (default: true)
  # @option options [Symbol] :images Image resolution: :reference, :copy, :embed (default: :reference)
  # @option options [Symbol] :media Media resolution: :reference, :copy (default: :reference)
  # @option options [String] :output_dir Output directory for :copy mode
  # @option options [Integer] :max_recursion Maximum recursion depth for includes (default: 10)
  # @return [Document] NEW document with resolved references
  #
  # @example Resolve includes only
  #   frozen = doc.freeze(base_dir: "/docs", includes: true)
  #
  # @example Create self-contained document
  #   frozen = doc.freeze(
  #     base_dir: "/docs",
  #     includes: true,
  #     images: :embed,
  #     output_dir: "/output"
  #   )
  #
  def freeze(options = {})
    resolver = Resolver.new(options)
    base_dir = options[:base_dir] || '.'
    resolver.resolve_document(self, base_dir)
  end

  class << self
    def from_ast(elements)
      sections = []
      document_attributes = nil
      header = nil

      elements.each do |element|
        case element
        when Coradoc::AsciiDoc::Model::DocumentAttributes
          document_attributes = element

        when Coradoc::AsciiDoc::Model::Header
          header = element

        when Coradoc::AsciiDoc::Model::Base
          sections << element

        else
          warn "Unknown element type: #{element.class}"
          warn "Element: #{element.inspect}"
        end
      end

      # Merge standalone LineBreak elements into the previous element's line_break
      merge_line_breaks(sections)

      # Only pass non-nil values to preserve defaults
      attrs = { sections: sections }
      attrs[:document_attributes] = document_attributes if document_attributes
      attrs[:header] = header if header

      new(attrs)
    end

    private

    def merge_line_breaks(sections)
      return if sections.empty?

      # Skip leading LineBreak elements
      sections.shift while sections.first.is_a?(Coradoc::AsciiDoc::Model::LineBreak)

      i = 0
      while i < sections.length
        # If current element is a LineBreak and there's a previous element
        if sections[i].is_a?(Coradoc::AsciiDoc::Model::LineBreak) && i.positive?
          prev = sections[i - 1]
          line_break = sections[i]

          # Skip consecutive LineBreaks
          if prev.is_a?(Coradoc::AsciiDoc::Model::LineBreak)
            sections.delete_at(i)
            next
          end

          # Merge the line break into the previous element if it has a line_break attribute
          if prev.is_a?(Coradoc::AsciiDoc::Model::Base) && prev.class.attributes.key?(:line_break)
            prev.line_break = prev.line_break.to_s + line_break.line_break.to_s
            sections.delete_at(i)
            # Don't increment i since we deleted an element
            next
          else
            # Keep as standalone if no suitable previous element
            i += 1
          end
        else
          i += 1
        end
      end
    end
  end
end

#headerHeader (readonly)

Returns Document header containing title and metadata.

Returns:

  • (Header)

    Document header containing title and metadata



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/coradoc/asciidoc/model/document.rb', line 37

class Document < Base
  attribute :document_attributes,
            Coradoc::AsciiDoc::Model::DocumentAttributes,
            default: lambda {
              Coradoc::AsciiDoc::Model::DocumentAttributes.new
            }
  attribute :header,
            Coradoc::AsciiDoc::Model::Header,
            default: lambda {
              Coradoc::AsciiDoc::Model::Header.new(
                title: Coradoc::AsciiDoc::Model::Title.new(content: '')
              )
            }

  attribute :sections,
            Coradoc::AsciiDoc::Model::Base,
            collection: true,
            initialize_empty: true,
            polymorphic: [
              Coradoc::AsciiDoc::Model::Admonition,
              Coradoc::AsciiDoc::Model::Audio,
              Coradoc::AsciiDoc::Model::BibliographyEntry,
              Coradoc::AsciiDoc::Model::Block::Core,
              Coradoc::AsciiDoc::Model::Image::BlockImage,
              Coradoc::AsciiDoc::Model::CommentBlock,
              Coradoc::AsciiDoc::Model::CommentLine,
              Coradoc::AsciiDoc::Model::Include,
              Coradoc::AsciiDoc::Model::LineBreak,
              Coradoc::AsciiDoc::Model::List::Core,
              Coradoc::AsciiDoc::Model::Paragraph,
              Coradoc::AsciiDoc::Model::Table,
              Coradoc::AsciiDoc::Model::Tag,
              Coradoc::AsciiDoc::Model::Video
            ]

  # @param [Integer] index The index of the section to retrieve
  # @return [Coradoc::AsciiDoc::Model::Base] The section at the specified index
  def [](index)
    sections[index]
  end

  # @param [Integer] index The index of the section to set
  # @param [Coradoc::AsciiDoc::Model::Base] value The section to set at the specified index
  # @return [Coradoc::AsciiDoc::Model::Base] The section that was set
  def []=(index, value)
    sections[index] = value
  end

  # Expand include directives in the document
  # @param base_dir [String] Base directory for resolving relative includes
  # @return [Coradoc::AsciiDoc::Model::Document] A new document with includes expanded
  def expand_includes(base_dir = '.')
    freeze(base_dir: base_dir, includes: true, images: :reference, media: :reference)
  end

  # Freeze the document by resolving external references.
  #
  # This method creates a NEW document with resolved references.
  # The original document is never modified (immutable principle).
  #
  # @param options [Hash] Resolution options
  # @option options [String] :base_dir Base directory for relative paths (default: ".")
  # @option options [Boolean] :includes Resolve include:: directives (default: true)
  # @option options [Symbol] :images Image resolution: :reference, :copy, :embed (default: :reference)
  # @option options [Symbol] :media Media resolution: :reference, :copy (default: :reference)
  # @option options [String] :output_dir Output directory for :copy mode
  # @option options [Integer] :max_recursion Maximum recursion depth for includes (default: 10)
  # @return [Document] NEW document with resolved references
  #
  # @example Resolve includes only
  #   frozen = doc.freeze(base_dir: "/docs", includes: true)
  #
  # @example Create self-contained document
  #   frozen = doc.freeze(
  #     base_dir: "/docs",
  #     includes: true,
  #     images: :embed,
  #     output_dir: "/output"
  #   )
  #
  def freeze(options = {})
    resolver = Resolver.new(options)
    base_dir = options[:base_dir] || '.'
    resolver.resolve_document(self, base_dir)
  end

  class << self
    def from_ast(elements)
      sections = []
      document_attributes = nil
      header = nil

      elements.each do |element|
        case element
        when Coradoc::AsciiDoc::Model::DocumentAttributes
          document_attributes = element

        when Coradoc::AsciiDoc::Model::Header
          header = element

        when Coradoc::AsciiDoc::Model::Base
          sections << element

        else
          warn "Unknown element type: #{element.class}"
          warn "Element: #{element.inspect}"
        end
      end

      # Merge standalone LineBreak elements into the previous element's line_break
      merge_line_breaks(sections)

      # Only pass non-nil values to preserve defaults
      attrs = { sections: sections }
      attrs[:document_attributes] = document_attributes if document_attributes
      attrs[:header] = header if header

      new(attrs)
    end

    private

    def merge_line_breaks(sections)
      return if sections.empty?

      # Skip leading LineBreak elements
      sections.shift while sections.first.is_a?(Coradoc::AsciiDoc::Model::LineBreak)

      i = 0
      while i < sections.length
        # If current element is a LineBreak and there's a previous element
        if sections[i].is_a?(Coradoc::AsciiDoc::Model::LineBreak) && i.positive?
          prev = sections[i - 1]
          line_break = sections[i]

          # Skip consecutive LineBreaks
          if prev.is_a?(Coradoc::AsciiDoc::Model::LineBreak)
            sections.delete_at(i)
            next
          end

          # Merge the line break into the previous element if it has a line_break attribute
          if prev.is_a?(Coradoc::AsciiDoc::Model::Base) && prev.class.attributes.key?(:line_break)
            prev.line_break = prev.line_break.to_s + line_break.line_break.to_s
            sections.delete_at(i)
            # Don't increment i since we deleted an element
            next
          else
            # Keep as standalone if no suitable previous element
            i += 1
          end
        else
          i += 1
        end
      end
    end
  end
end

#sectionsArray<Base> (readonly)

Returns Document content blocks (sections, paragraphs, lists, etc.).

Returns:

  • (Array<Base>)

    Document content blocks (sections, paragraphs, lists, etc.)



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/coradoc/asciidoc/model/document.rb', line 37

class Document < Base
  attribute :document_attributes,
            Coradoc::AsciiDoc::Model::DocumentAttributes,
            default: lambda {
              Coradoc::AsciiDoc::Model::DocumentAttributes.new
            }
  attribute :header,
            Coradoc::AsciiDoc::Model::Header,
            default: lambda {
              Coradoc::AsciiDoc::Model::Header.new(
                title: Coradoc::AsciiDoc::Model::Title.new(content: '')
              )
            }

  attribute :sections,
            Coradoc::AsciiDoc::Model::Base,
            collection: true,
            initialize_empty: true,
            polymorphic: [
              Coradoc::AsciiDoc::Model::Admonition,
              Coradoc::AsciiDoc::Model::Audio,
              Coradoc::AsciiDoc::Model::BibliographyEntry,
              Coradoc::AsciiDoc::Model::Block::Core,
              Coradoc::AsciiDoc::Model::Image::BlockImage,
              Coradoc::AsciiDoc::Model::CommentBlock,
              Coradoc::AsciiDoc::Model::CommentLine,
              Coradoc::AsciiDoc::Model::Include,
              Coradoc::AsciiDoc::Model::LineBreak,
              Coradoc::AsciiDoc::Model::List::Core,
              Coradoc::AsciiDoc::Model::Paragraph,
              Coradoc::AsciiDoc::Model::Table,
              Coradoc::AsciiDoc::Model::Tag,
              Coradoc::AsciiDoc::Model::Video
            ]

  # @param [Integer] index The index of the section to retrieve
  # @return [Coradoc::AsciiDoc::Model::Base] The section at the specified index
  def [](index)
    sections[index]
  end

  # @param [Integer] index The index of the section to set
  # @param [Coradoc::AsciiDoc::Model::Base] value The section to set at the specified index
  # @return [Coradoc::AsciiDoc::Model::Base] The section that was set
  def []=(index, value)
    sections[index] = value
  end

  # Expand include directives in the document
  # @param base_dir [String] Base directory for resolving relative includes
  # @return [Coradoc::AsciiDoc::Model::Document] A new document with includes expanded
  def expand_includes(base_dir = '.')
    freeze(base_dir: base_dir, includes: true, images: :reference, media: :reference)
  end

  # Freeze the document by resolving external references.
  #
  # This method creates a NEW document with resolved references.
  # The original document is never modified (immutable principle).
  #
  # @param options [Hash] Resolution options
  # @option options [String] :base_dir Base directory for relative paths (default: ".")
  # @option options [Boolean] :includes Resolve include:: directives (default: true)
  # @option options [Symbol] :images Image resolution: :reference, :copy, :embed (default: :reference)
  # @option options [Symbol] :media Media resolution: :reference, :copy (default: :reference)
  # @option options [String] :output_dir Output directory for :copy mode
  # @option options [Integer] :max_recursion Maximum recursion depth for includes (default: 10)
  # @return [Document] NEW document with resolved references
  #
  # @example Resolve includes only
  #   frozen = doc.freeze(base_dir: "/docs", includes: true)
  #
  # @example Create self-contained document
  #   frozen = doc.freeze(
  #     base_dir: "/docs",
  #     includes: true,
  #     images: :embed,
  #     output_dir: "/output"
  #   )
  #
  def freeze(options = {})
    resolver = Resolver.new(options)
    base_dir = options[:base_dir] || '.'
    resolver.resolve_document(self, base_dir)
  end

  class << self
    def from_ast(elements)
      sections = []
      document_attributes = nil
      header = nil

      elements.each do |element|
        case element
        when Coradoc::AsciiDoc::Model::DocumentAttributes
          document_attributes = element

        when Coradoc::AsciiDoc::Model::Header
          header = element

        when Coradoc::AsciiDoc::Model::Base
          sections << element

        else
          warn "Unknown element type: #{element.class}"
          warn "Element: #{element.inspect}"
        end
      end

      # Merge standalone LineBreak elements into the previous element's line_break
      merge_line_breaks(sections)

      # Only pass non-nil values to preserve defaults
      attrs = { sections: sections }
      attrs[:document_attributes] = document_attributes if document_attributes
      attrs[:header] = header if header

      new(attrs)
    end

    private

    def merge_line_breaks(sections)
      return if sections.empty?

      # Skip leading LineBreak elements
      sections.shift while sections.first.is_a?(Coradoc::AsciiDoc::Model::LineBreak)

      i = 0
      while i < sections.length
        # If current element is a LineBreak and there's a previous element
        if sections[i].is_a?(Coradoc::AsciiDoc::Model::LineBreak) && i.positive?
          prev = sections[i - 1]
          line_break = sections[i]

          # Skip consecutive LineBreaks
          if prev.is_a?(Coradoc::AsciiDoc::Model::LineBreak)
            sections.delete_at(i)
            next
          end

          # Merge the line break into the previous element if it has a line_break attribute
          if prev.is_a?(Coradoc::AsciiDoc::Model::Base) && prev.class.attributes.key?(:line_break)
            prev.line_break = prev.line_break.to_s + line_break.line_break.to_s
            sections.delete_at(i)
            # Don't increment i since we deleted an element
            next
          else
            # Keep as standalone if no suitable previous element
            i += 1
          end
        else
          i += 1
        end
      end
    end
  end
end

Class Method Details

.from_ast(elements) ⇒ Object



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/coradoc/asciidoc/model/document.rb', line 124

def from_ast(elements)
  sections = []
  document_attributes = nil
  header = nil

  elements.each do |element|
    case element
    when Coradoc::AsciiDoc::Model::DocumentAttributes
      document_attributes = element

    when Coradoc::AsciiDoc::Model::Header
      header = element

    when Coradoc::AsciiDoc::Model::Base
      sections << element

    else
      warn "Unknown element type: #{element.class}"
      warn "Element: #{element.inspect}"
    end
  end

  # Merge standalone LineBreak elements into the previous element's line_break
  merge_line_breaks(sections)

  # Only pass non-nil values to preserve defaults
  attrs = { sections: sections }
  attrs[:document_attributes] = document_attributes if document_attributes
  attrs[:header] = header if header

  new(attrs)
end

Instance Method Details

#[](index) ⇒ Coradoc::AsciiDoc::Model::Base

Returns The section at the specified index.

Parameters:

  • index (Integer)

    The index of the section to retrieve

Returns:



74
75
76
# File 'lib/coradoc/asciidoc/model/document.rb', line 74

def [](index)
  sections[index]
end

#[]=(index, value) ⇒ Coradoc::AsciiDoc::Model::Base

Returns The section that was set.

Parameters:

Returns:



81
82
83
# File 'lib/coradoc/asciidoc/model/document.rb', line 81

def []=(index, value)
  sections[index] = value
end

#expand_includes(base_dir = '.') ⇒ Coradoc::AsciiDoc::Model::Document

Expand include directives in the document

Parameters:

  • base_dir (String) (defaults to: '.')

    Base directory for resolving relative includes

Returns:



88
89
90
# File 'lib/coradoc/asciidoc/model/document.rb', line 88

def expand_includes(base_dir = '.')
  freeze(base_dir: base_dir, includes: true, images: :reference, media: :reference)
end

#freeze(options = {}) ⇒ Document

Freeze the document by resolving external references.

This method creates a NEW document with resolved references. The original document is never modified (immutable principle).

Examples:

Resolve includes only

frozen = doc.freeze(base_dir: "/docs", includes: true)

Create self-contained document

frozen = doc.freeze(
  base_dir: "/docs",
  includes: true,
  images: :embed,
  output_dir: "/output"
)

Parameters:

  • options (Hash) (defaults to: {})

    Resolution options

Options Hash (options):

  • :base_dir (String)

    Base directory for relative paths (default: “.”)

  • :includes (Boolean)
    Resolve include

    directives (default: true)

  • :images (Symbol)

    Image resolution: :reference, :copy, :embed (default: :reference)

  • :media (Symbol)

    Media resolution: :reference, :copy (default: :reference)

  • :output_dir (String)

    Output directory for :copy mode

  • :max_recursion (Integer)

    Maximum recursion depth for includes (default: 10)

Returns:

  • (Document)

    NEW document with resolved references



117
118
119
120
121
# File 'lib/coradoc/asciidoc/model/document.rb', line 117

def freeze(options = {})
  resolver = Resolver.new(options)
  base_dir = options[:base_dir] || '.'
  resolver.resolve_document(self, base_dir)
end