class BookChef::TreeMerger

Attributes

document[R]
version[R]

Public Class Methods

new(path, fn) click to toggle source
# File lib/bookchef/tree_merger.rb, line 9
def initialize(path, fn)
  @path     = File.expand_path(path)
  @filename = fn
  @document = File.read("#@path/#@filename").gsub(/<%(.*?)%>/, '&lt;%\1%&gt;')
  @document = Nokogiri::XML.parse @document
  insert_version_from_git_tag!
  if File.exists?("#@path/settings.xml")
    @settings = File.read("#@path/settings.xml") 
    @document.root << @settings
  end
end

Public Instance Methods

run() click to toggle source
# File lib/bookchef/tree_merger.rb, line 21
def run
  @documents = []
  @document  = process_level(@document)
end
save_to(fn) click to toggle source
# File lib/bookchef/tree_merger.rb, line 26
def save_to(fn)
  f = File.open(fn, "w")
  f.write @document.to_s
  f.close
end

Private Instance Methods

assign_id_to_section!(node, full_current_path) click to toggle source

Sets section id like this: <section id=“/section1/subsection_a/intro.xml”>

# File lib/bookchef/tree_merger.rb, line 122
def assign_id_to_section!(node, full_current_path)
  node[:id] = full_current_path
  node.remove_attribute("src")
end
convert_references_and_footnotes!(document, current_path) click to toggle source
# File lib/bookchef/tree_merger.rb, line 88
def convert_references_and_footnotes!(document, current_path)
  
  # Search the document tree and find the closest file
  # that contains <footnotes> and <references>
  container_paths = { footnote: current_path.split("/"), reference: current_path.split("/") }
  [:footnote, :reference].each do |container|
    catch :break_inner_loop do
      (@documents + [document]).reverse.each_with_index do |d,i|
        throw :break_inner_loop if d.search("//#{container}s").size > 0
        container_paths[container].pop if container_paths[container].pop == "index.xml"
        container_paths["#{container}_file"] = "/index.xml"
      end
    end
  end
  
  document.search("//footnote|//reference").each do |node|
    node[:id] = current_path + "/#{node.name}_#{node[:id]}"
  end
  
  document.search("//@footnote|//@reference").each do |node|
    node.parent["number"]  = node.value
    node.parent[node.name] = container_paths[node.name.to_sym].join("/")  +
                             (container_paths["#{node.name}_file"] || '') +
                             "/#{node.name}_#{node.value}"
  end
end
filename_or_index(path) click to toggle source

returns just the filename out of the full path For example, if the path is “/section1/subsection” it returns “index.xml”

# File lib/bookchef/tree_merger.rb, line 130
def filename_or_index(path)
  (path.match(/[^\/]*\.xml\Z/) || ["index.xml"])[0]
end
insert_version_from_git_tag!() click to toggle source
# File lib/bookchef/tree_merger.rb, line 178
def insert_version_from_git_tag!

  `cd #{@path} && git tag`.split("\n").sort.reverse.each do |t|
    if t =~ /\Av[0-9]/
      @version = t.sub(/\Av/,'').rstrip
      @document.root["version"] = t.sub(/\Av/,'').rstrip
      return
    end
  end

end
make_image_paths_absolute!(document, current_path) click to toggle source
# File lib/bookchef/tree_merger.rb, line 115
def make_image_paths_absolute!(document, current_path)
  document.search("img").each do |img|
    img[:src] = "file://" + @path + current_path + "/#{img[:src]}"
  end
end
normalize_code!(document) click to toggle source
# File lib/bookchef/tree_merger.rb, line 145
def normalize_code!(document)
  document.search("code").each do |c|
    minimum_whitespace = nil
    lines_arr = c.children.to_s.split("\n")
    new_lines_arr = []
    lines_arr.each_with_index do |l,i|
      next if l =~ /\A\s*\Z/
      match_size = l.match(/\A( )*/).to_s.size
      minimum_whitespace = match_size if !minimum_whitespace || match_size < minimum_whitespace
    end
    lines_arr.each_with_index do |l,i|
      next if l =~ /\A\s*\Z/ && (i == 0 || i == lines_arr.size-1)
      l.sub!("\n", '')
      l.sub!(/\A( ){#{minimum_whitespace}}/, '')
      new_lines_arr << split_long_line(l, l.match(/\A( )*/).to_s.size)
    end
    c.content = new_lines_arr.compact.join("\n")
  end
end
path_with_filename(path) click to toggle source

returns full path attaching “index.xml” to the end of no file specified For example, if the path is “/section/subsection” it returns “/section/subsection/index.xml”

# File lib/bookchef/tree_merger.rb, line 137
def path_with_filename(path)
  if path.match(/[^\/]*\.xml\Z/)
    path
  else
    path + "/index.xml"
  end
end
process_level(level_document, current_path="") click to toggle source
# File lib/bookchef/tree_merger.rb, line 34
def process_level(level_document, current_path="")
  @documents << level_document
  sourced_sections = level_document.xpath('//section[@src]|//chapter[@src]')

  normalize_code!            level_document
  convert_links!             level_document, current_path
  make_image_paths_absolute! level_document, current_path
  
  sourced_sections.each do |s|
    current_fn        = filename_or_index(s[:src])
    current_dir       = s[:src].sub(/\/?[^\/]*\.xml\Z/, '')
    path              = current_path
    path              += "/#{current_dir}" unless current_dir.empty?
    full_current_path = "#{path}/#{current_fn}"
    
    puts "processing #{full_current_path}" 
    assign_id_to_section! s, full_current_path
    
    # Parse the sourced file
    level_file = File.read("#@path#{full_current_path}")
    level_file = BookChef.protect_special_chars(level_file)
    sourced_document = Nokogiri::XML.parse(level_file)
    convert_references_and_footnotes! sourced_document, full_current_path

    # Now process it too, replacing all src-s with xml from sourced files
    sourced_document = process_level(sourced_document, path)

    # replace the contents of the tag with the actual sourced file
    s.children = sourced_document.root.children
  end unless sourced_sections.empty?

  @documents.delete(level_document) and return level_document
end
split_long_line(line, leading_spaces_number=0) click to toggle source
# File lib/bookchef/tree_merger.rb, line 165
def split_long_line(line, leading_spaces_number=0)
  if line.length > 70
    lines = line[61..line.length-1].split(/[ .]/, 2)
    line1 = line[0..60] + lines[0]
    leading_spaces = (0..leading_spaces_number-1).map { ' ' }.join
    line2 = leading_spaces.to_s + lines[1].to_s
    line2 = split_long_line(line2, leading_spaces_number)
    return "#{line1}\n#{line2}"
  else
    line
  end
end