Methods
N
T
Classes and Modules
Attributes
[R] format

Data from the format specification file.

[R] roots

All root nodes in the document.

[R] nodes

All nodes in the document.

[R] nodes_by_type

All nodes in the document arranged by node type.

Class Public methods
new(format_name, input_text, input_file, options = {})

Parameters

format_name
Either the short-hand name of a built-in format or the path to a format specification file.
input_text
The body of the input document.
input_file
Name of the file from which the input document originated.

Options

:unindent
If true, all node content is unindented hierarchically.
# File lib/erbook/document.rb, line 42
    def initialize format_name, input_text, input_file, options = {}
      # process format specification
        @format_file = format_name.to_s

        File.file? @format_file or
          @format_file = File.join(ERBook::FORMATS_DIR, @format_file + '.yaml')

        begin
          @format = YAML.load_file(@format_file)
          @format[:file] = File.expand_path(@format_file)
          @format[:name] = File.basename(@format_file).sub(/\..*?$/, '')

          if @format.key? 'code'
            eval @format['code'].to_s, TOPLEVEL_BINDING, "#{@format_file}:code"
          end

        rescue Exception
          error "Could not load format specification file #{@format_file.inspect}"
        end

        @node_defs = @format['nodes']

      # process input document
      begin
        # create sandbox for input evaluation
          template = Template.new(input_file, input_text, options[:unindent])
          sandbox = template.sandbox

          @template_vars = {
            :@format        => @format,
            :@roots         => @roots = [], # root nodes of all trees
            :@nodes         => @nodes = [], # all nodes in the forest
            :@nodes_by_type => @nodes_by_type = Hash.new {|h,k| h[k] = [] },
            :@stack         => [], # stack for all nodes
          }.each_pair {|k,v| sandbox.instance_variable_set(k, v) }

          #:stopdoc:

          ##
          # Handles the method call from a node
          # placeholder in the input document.
          #
          def sandbox.__node_impl__ node_type, *node_args, &node_content
            node = Node.new(
              :type     => node_type,
              :defn     => @format['nodes'][node_type],
              :args     => node_args,
              :trace    => caller,
              :children => []
            )
            @nodes << node
            @nodes_by_type[node.type] << node

            # calculate occurrence number for this node
            if node.defn['number']
              @count_by_type ||= Hash.new {|h,k| h[k] = 0 }
              node.number = (@count_by_type[node.type] += 1)
            end

            # assign node family
            if parent = @stack.last
              parent.children << node
              node.parent = parent
              node.depth = parent.depth
              node.depth += 1 if node.defn['depth']

              # calculate latex-style index number for this node
              if node.defn['index']
                ancestry = @stack.reverse.find {|n| n.defn['index'] }.index
                branches = node.parent.children.select {|n| n.index }

                node.index = [ancestry, branches.length + 1].join('.')
              end
            else
              @roots << node
              node.parent = nil
              node.depth = 0

              # calculate latex-style index number for this node
              if node.defn['index']
                branches = @roots.select {|n| n.index }
                node.index = (branches.length + 1).to_s
              end
            end

            # assign node content
            if block_given?
              @stack.push node
              node.content = __block_content__(node, &node_content)
              @stack.pop
            end

            @buffer << node

            nil
          end

          #:startdoc:

          @node_defs.each_key do |type|
            # XXX: using a string because define_method()
            #      does not accept a block until Ruby 1.9
            file, line = __FILE__, __LINE__; eval %{
              def sandbox.#{type} *node_args, &node_content
                __node_impl__ #{type.inspect}, *node_args, &node_content
              end
            }, binding, file, line
          end

        # evaluate the input & build the document tree
          template.render
          @processed_document = template.buffer

        # chain block-level nodes together for local navigation
          block_nodes = @nodes.reject do |n|
            n.defn['bypass'] || n.defn['inline']
          end

          require 'enumerator'
          block_nodes.each_cons(2) do |a, b|
            a.next_node = b
            b.prev_node = a
          end

        # calculate output for all nodes
          actual_output_by_node = {}

          visitor = lambda do |n|
            #
            # allow child nodes to calculate their actual
            # output and to set their identifier as Node#output
            #
            # we do this nodes first because this node's
            # content contains the child nodes' output
            #
            n.children.each {|c| visitor.call c }

            # calculate the output for this node
            actual_output = Template.new(
              "#{@format_file}:nodes:#{n.type}:output",
              n.defn['output'].to_s.chomp
            ).render_with(@template_vars.merge(:@node => n))

            # reveal child nodes' actual output in this node's actual output
            n.children.each do |c|
              if c.defn['inline'] && !c.defn['bypass']
                actual_output[c.output] = actual_output_by_node[c]

              else
                # pull block-level node out of paragraph tag added by Maruku
                actual_output.sub! %r/(<p>\s*)?#{Regexp.quote c.output}/ do
                  actual_output_by_node[c] + $1.to_s
                end
              end
            end

            actual_output_by_node[n] = actual_output

            #
            # allow the parent node to calculate its actual
            # output without interference from the output of
            # this node (Node#to_s is aliased to Node#output)
            #
            # this assumes that having this node's string
            # representation be a consecutive sequence of digits
            # will not interfere with the text-to-whatever
            # transformation defined by the format specification
            #
            n.output = Digest::SHA1.digest(n.object_id.to_s).unpack('I*').join
          end

          @roots.each {|n| visitor.call n }

          # replace the temporary identifier with each node's actual output
          @nodes.each {|n| n.output = actual_output_by_node[n] }

      rescue Exception
        puts input_text # so the user can debug line numbers in stack trace
        error "Could not process input document #{input_file.inspect}"
      end
    end
Instance Public methods
to_s()

Returns the output of this document.

# File lib/erbook/document.rb, line 227
    def to_s
      Template.new("#{@format_file}:output", @format['output'].to_s).
      render_with(@template_vars.merge(:@content => @processed_document.join))
    end