module BuildMaster class TemplateRunner BuildMaster::NAMESPACE = "http://buildmaster.rubyforge.org/xtemplate/1.0" def initialize(result, template, source, &evaluator) @result = result @template = template @source = source @evaluator = evaluator end def process process_children(@result, @template) end private def process_children(target, template) template.each do |element| if (element.kind_of? REXML::Element) process_element(target, element) else target.add(deep_clone(element)) end end end def process_element(target, element) if (template_directive?(element)) process_directive(target, element) else output_element = clone_element(element) target.add(output_element) process_children(output_element, element) end end def template_directive?(element) element.namespace == NAMESPACE end def process_directive(target, template_element) message = "process_#{template_element.name}_directive" raise TemplateException, "unable to process element template:#{template_element.name}" unless respond_to?(message, true) send(message, target, template_element) end def clone_element( template_element ) cloned_element = REXML::Element.new( template_element.expanded_name ) copy_attributes( template_element, cloned_element ) cloned_element end def copy_attributes( template_element, expanded_element ) template_element.attributes.each_attribute do |attribute| expanded_element.attributes.add( attribute.clone ) end end def deep_clone( node ) if node.kind_of? REXML::Parent node.deep_clone else node.clone end end def process_include_directive(target_element, template_element) elements_attribute = template_element.attributes["elements"] if (elements_attribute) REXML::XPath.match(@source, elements_attribute).each do |matched| target_element.add(deep_clone(matched)) end return end end def process_when_directive(target_element, template_element) if evaluate(template_element, 'test') process_children(target_element, template_element) end end def process_attribute_directive(target_element, template_element) name = template_element.attributes['name'] target_element.attributes[name]=evaluate(template_element, 'eval') end def process_text_directive(target_element, template_element) target_element.add(REXML::Text.new(evaluate(template_element, 'eval'))) end def process_each_directive(target_element, template_element) source_xml = evaluate!(template_element, 'source') matched_element = REXML::XPath.match(REXML::Document.new(source_xml), template_element.attributes['select']) count = attribute!(template_element, 'count') count.to_i.times do |i| TemplateRunner.new(target_element, template_element, matched_element[i], &@evaluator).process end end def process_link_directive(target_element, template_element) current_path = @evaluator.call('relative_to_root') file_name = current_path.basename(current_path.extname).to_s + ".html" current_html_path = current_path.parent().join(file_name) href = Pathname.new(attribute!(template_element, 'href')) if (href.absolute?) href = href.relative_path_from(Pathname.new('/')) end anchor_or_div = nil if (current_html_path == href) anchor_or_div = REXML::Element.new('div') anchor_or_div.attributes['class']='current' else anchor_or_div = REXML::Element.new('a') anchor_or_div.attributes['href'] = href.relative_path_from(current_html_path.parent()) end template_element.attributes.each do |name, value| anchor_or_div.attributes[name] = value unless name == 'href' end process_children(anchor_or_div, template_element) target_element.add(anchor_or_div) end def evaluate!(xml_element, attribute_name) value = evaluate(xml_element, attribute_name) message = xml_element.attributes[attribute_name] if (not value) raise TemplateException, "#{message} from attribute #{attribute_name} cannot be nil" end return value end def evaluate(xml_element, attribute_name) message = attribute!(xml_element, attribute_name) return @evaluator.call(message) end def attribute!(xml_element, attribute_name) value = xml_element.attributes[attribute_name] if (not value) raise TemplateException, "attribute #{attribute_name} not found in #{xml_element}" end return value end end end