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_handler__ node_type, *node_args, &node_content
            node = Node.new(
              :type       => node_type,
              :definition => @format['nodes'][node_type],
              :arguments  => node_args,
              :backtrace  => caller,
              :parent     => @stack.last,
              :children   => []
            )

            Array(node.definition['params']).each do |param|
              break if node_args.empty?
              node.__send__ "#{param}=", node_args.shift
            end

            @nodes << node
            @nodes_by_type[node.type] << node

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

            # assign node family
            if parent = node.parent
              parent.children << node
              node.parent = parent
              node.depth = parent.depth
              node.depth += 1 if node.anchor?

              # calculate section number for this node
              if node.section_number?
                ancestor = @stack.reverse.find {|n| n.section_number }
                branches = parent.children.select {|n| n.section_number }

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

              # calculate section number for this node
              if node.section_number?
                branches = @roots.select {|n| n.section_number }
                node.section_number = (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_handler__ #{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.select {|n| n.chain? }

          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.definition['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.silent?
                # this child's output is not meant to be revealed at this time
                next

              elsif c.inline?
                actual_output[c.output] = actual_output_by_node[c]

              else
                # remove <p> around block-level child (added by Markdown)
                actual_output.sub! %r{(<p>\s*)?#{
                  Regexp.quote c.output
                }(\s*</p>)?} do
                  actual_output_by_node[c] +
                    if $1 and $2
                      ''
                    else
                      [$1, $2].join
                    end
                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 246
    def to_s
      Template.new("#{@format_file}:output", @format['output'].to_s).
      render_with(@template_vars.merge(:@content => @processed_document.join))
    end