Class: ERBook::Document
- Inherits:
-
Object
- Object
- ERBook::Document
- Defined in:
- lib/erbook/document.rb
Defined Under Namespace
Classes: Node
Instance Attribute Summary collapse
-
#format ⇒ Object
readonly
Data from the format specification file.
-
#nodes ⇒ Object
readonly
All nodes in the document.
-
#nodes_by_type ⇒ Object
readonly
All nodes in the document arranged by node type.
-
#roots ⇒ Object
readonly
All root nodes in the document.
Instance Method Summary collapse
-
#initialize(format_name, input_text, input_file, options = {}) ⇒ Document
constructor
Parameters.
-
#to_s ⇒ Object
Returns the output of this document.
Constructor Details
#initialize(format_name, input_text, input_file, options = {}) ⇒ Document
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.
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 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 |
# File 'lib/erbook/document.rb', line 42 def initialize format_name, input_text, input_file, = {} # 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.(@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, [: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 Attribute Details
#format ⇒ Object (readonly)
Data from the format specification file.
13 14 15 |
# File 'lib/erbook/document.rb', line 13 def format @format end |
#nodes ⇒ Object (readonly)
All nodes in the document.
19 20 21 |
# File 'lib/erbook/document.rb', line 19 def nodes @nodes end |
#nodes_by_type ⇒ Object (readonly)
All nodes in the document arranged by node type.
22 23 24 |
# File 'lib/erbook/document.rb', line 22 def nodes_by_type @nodes_by_type end |
#roots ⇒ Object (readonly)
All root nodes in the document.
16 17 18 |
# File 'lib/erbook/document.rb', line 16 def roots @roots end |