class OM::XML::Terminology class BadPointerError < StandardError; end class CircularReferenceError < StandardError; end # Terminology::Builder Class Definition # # When coding against Builders, remember that they rely on MethodMissing, # so any time you call a method on the Builder that it doesn't explicitly recognize, # the Builder will add your method & arguments to the it's settings and return itself. class Builder attr_accessor :schema, :namespaces attr_reader :term_builders ### # Create a new Terminology Builder object. +options+ are sent to the top level # Document that is being built. # (not yet supported:) +root+ can be a point in an existing Terminology that you want to add Mappers into # # Building a document with a particular encoding for example: # # Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml| # ... # end def initialize(options = {}, root = nil, &block) @schema = options.fetch(:schema,nil) @namespaces = options.fetch(:namespaces,{}) @term_builders = {} @cur_term_builder = nil yield self if block_given? end # Set the root of the Terminology, along with namespace & schema info def root opts, &block @schema = opts.fetch(:schema,nil) opts.select {|k,v| k.to_s.include?("xmlns")}.each do |ns_pair| @namespaces[ns_pair.first.to_s] = ns_pair.last if ns_pair.first.to_s == "xmlns" @namespaces["oxns"] = ns_pair.last end end root_term_builder = OM::XML::Term::Builder.new(opts.fetch(:path,:root).to_s.sub(/[_!]$/, '')).is_root_term(true) @term_builders[root_term_builder.name] = root_term_builder return root_term_builder end # Returns an array of Terms that have been marked as "root" terms def root_term_builders @term_builders.values.select {|term_builder| term_builder.settings[:is_root_term] == true } end def method_missing method, *args, &block # :nodoc: parent_builder = @cur_term_builder @cur_term_builder = OM::XML::Term::Builder.new(method.to_s.sub(/[_!]$/, ''), self) # Attach to parent if parent_builder parent_builder.add_child @cur_term_builder else @term_builders [@cur_term_builder.name] = @cur_term_builder end # Apply options opts = args.shift @cur_term_builder.settings.merge!(opts) if opts # Parse children yield if block @cur_term_builder = parent_builder end # Returns the TermBuilder corresponding to the given _pointer_. def retrieve_term_builder(*args) args_cp = args.dup current_term = @term_builders[args_cp.delete_at(0)] if current_term.nil? raise OM::XML::Terminology::BadPointerError, "This TerminologyBuilder does not have a root TermBuilder defined that corresponds to \"#{args.first.inspect}\"" end args_cp.each do |arg| current_term = current_term.retrieve_child(arg) if current_term.nil? raise OM::XML::Terminology::BadPointerError, "You attempted to retrieve a TermBuilder using this pointer: #{args.inspect} but no TermBuilder exists at that location. Everything is fine until \"#{arg.inspect}\", which doesn't exist." end end return current_term end def build terminology = OM::XML::Terminology.new(:schema=>@schema, :namespaces=>@namespaces) root_term_builders.each do |root_term_builder| root_term_builder.children = self.term_builders.dup root_term_builder.children.delete(root_term_builder.name) end @term_builders.each_value do |root_builder| terminology.add_term root_builder.build(terminology) end terminology end end # Terminology Class Definition attr_accessor :terms, :schema, :namespaces def initialize(options={}) @schema = options.fetch(:schema,nil) @namespaces = options.fetch(:namespaces,{}) @terms = {} end # Add a term to the root of the terminology def add_term(term) @terms[term.name.to_sym] = term end # Returns true if the current terminology has a term defined at the location indicated by +pointers+ array def has_term?(*pointers) begin retrieve_term(*OM.pointers_to_flat_array(pointers, false)) return true rescue return false end end # Returns the Term corresponding to the given _pointer_. def retrieve_term(*args) args_cp = args.dup current_term = terms[args_cp.delete_at(0)] if current_term.nil? raise OM::XML::Terminology::BadPointerError, "This Terminology does not have a root term defined that corresponds to \"#{args.first.inspect}\"" else args_cp.each do |arg| current_term = current_term.retrieve_child(arg) if current_term.nil? raise OM::XML::Terminology::BadPointerError, "You attempted to retrieve a Term using this pointer: #{args.inspect} but no Term exists at that location. Everything is fine until \"#{arg.inspect}\", which doesn't exist." end end end return current_term end # Return the appropriate xpath query for retrieving nodes corresponding to the term identified by +pointers+. # If the last argument is a String or a Hash, it will be used to add +constraints+ to the resulting xpath query. # If you provide an xpath query as the argument, it will be returne untouched. def xpath_for(*pointers) if pointers.length == 1 && pointers.first.instance_of?(String) xpath_query = pointers.first else query_constraints = nil if pointers.length > 1 && !pointers.last.kind_of?(Symbol) query_constraints = pointers.pop end term = retrieve_term( *pointers ) if !term.nil? if query_constraints.kind_of?(String) constraint_value = query_constraints xpath_template = term.xpath_constrained xpath_query = eval( '"' + xpath_template + '"' ) elsif query_constraints.kind_of?(Hash) && !query_constraints.empty? key_value_pair = query_constraints.first constraint_value = key_value_pair.last xpath_template = term.children[key_value_pair.first].xpath_constrained xpath_query = eval( '"' + xpath_template + '"' ) else xpath_query = term.xpath end else xpath_query = nil end end return xpath_query end # Use the current terminology to generate an xpath with (optional) node indexes for each of the term pointers. # Ex. terminology.xpath_with_indexes({:conference=>0}, {:role=>1}, :text ) # will yield an xpath like this: '//oxns:name[@type="conference"][1]/oxns:role[2]/oxns:roleTerm[@type="text"]' def xpath_with_indexes(*pointers) OM::XML::TermXpathGenerator.generate_xpath_with_indexes(self, *pointers) end # Retrieves a Term corresponding to +term_pointers+ and return the corresponding xml_builder_template for that term. # The resulting xml_builder_template can be passed as a block into Nokogiri::XML::Builder.new # # +term_pointers+ point to the Term you want to generate a builder template for # If the last term_pointer is a String or a Hash, it will be passed into the Term's xml_builder_template method as extra_opts # see also: Term.xml_builder_template def xml_builder_template(*term_pointers) extra_opts = {} if term_pointers.length > 1 && !term_pointers.last.kind_of?(Symbol) extra_opts = term_pointers.pop end term = retrieve_term(*term_pointers) return term.xml_builder_template(extra_opts) end # Returns an array of Terms that have been marked as "root" terms def root_terms terms.values.select {|term| term.is_root_term? } end def self.term_generic_name(*pointers) pointers_to_flat_array(pointers, false).join("_") end def self.term_hierarchical_name(*pointers) pointers_to_flat_array(pointers, true).join("_") end def self.pointers_to_flat_array(pointers, include_indices=true) OM.pointers_to_flat_array(pointers, include_indices) end end