markkorput/xsd-reader

View on GitHub
lib/xsd_reader/shared.rb

Summary

Maintainability
A
50 mins
Test Coverage
require 'logger'

module XsdReader

  module Shared

    attr_reader :options

    def initialize(opts = {})
      @options = opts || {}
      raise "#{self.class}.new expects a hash parameter" if !@options.is_a?(Hash)
    end

    def self.default_logger
      @default_logger ||= Logger.new(STDOUT).tap do |logr|
        logr.level = Logger::WARN
      end
    end

    def logger
      options[:logger] || default_logger
    end

    def node
      options[:node]
    end

    def schema_namespace_prefix
      @schema_namespace_prefix ||= [(self.node.namespaces||{}).detect {|ns| ns[1]=~/XMLSchema/}.first.split(':')[1],nil].uniq.join(':')
    end

    def nodes
      node.search("./*")
    end

    def [](*args)
      # now name is always an array
      names = args.flatten

      result = self

      names.each do |curname|
        next if result.nil?
        if curname.to_s =~ /^\@/
          attr_name = curname.to_s.gsub(/^\@/, '')
          result = result.attributes.find{|attr| attr.name == attr_name}
        else
          result = result.elements.find{|child| child.name == curname.to_s}
        end
      end

      return result
    end

    #
    # attribute properties
    #
    def name
      name_local || name_referenced
    end

    def name_local
      node.attributes['name'] ? node.attributes['name'].value : nil
    end

    def name_referenced
      referenced_element ? referenced_element.name : nil
    end

    def ref
      node.attributes['ref'] ? node.attributes['ref'].value : nil
    end

    def referenced_element
      ref && schema ? schema.elements.find{|el| el.name == ref} : nil
    end


    def type
      type = node.attributes['type'] ? node.attributes['type'].value : nil
      type || (referenced_element ? referenced_element.type : nil)
    end

    def type_name
      type ? type.split(':').last : nil
    end

    def type_namespace
      type ? type.split(':').first : nil
    end


    # base stuff belongs to extension type objects only, but let's be flexible
    def base
      node.attributes['base'] ? node.attributes['base'].value : nil
    end

    def base_name
      base ? base.split(':').last : nil
    end

    def base_namespace
      base ? base.split(':').first : nil
    end


    #
    # Node to class mapping
    #
    def class_for(n)
      class_mapping = {
        "#{schema_namespace_prefix}schema" => Schema,
        "#{schema_namespace_prefix}element" => Element,
        "#{schema_namespace_prefix}attribute" => Attribute,
        "#{schema_namespace_prefix}choice" => Choice,
        "#{schema_namespace_prefix}complexType" => ComplexType,
        "#{schema_namespace_prefix}sequence" => Sequence,
        "#{schema_namespace_prefix}simpleContent" => SimpleContent,
        "#{schema_namespace_prefix}complexContent" => ComplexContent,
        "#{schema_namespace_prefix}extension" => Extension,
        "#{schema_namespace_prefix}import" => Import,
        "#{schema_namespace_prefix}simpleType" => SimpleType
      }

      return class_mapping[n.is_a?(Nokogiri::XML::Node) ? n.name : n]
    end

    def node_to_object(node)
      fullname = [node.namespace ? node.namespace.prefix : nil, node.name].reject{|str| str.nil? || str == ''}.join(':')
      klass = class_for(fullname)
      # logger.debug "node_to_object, klass: #{klass}, fullname: #{fullname}"
      klass.nil? ? nil : klass.new(options.merge(:node => node, :schema => schema))
    end

    #
    # Child objects
    #

    def prepend_namespace name
      name =~ /^#{schema_namespace_prefix}/ ? name : "#{schema_namespace_prefix}#{name}"
    end

    def mappable_children(xml_name)
#3      p prepend_namespace(xml_name )
      node.search("./#{prepend_namespace xml_name}").to_a
    end

    def map_children(xml_name)
      # puts "Map Children with #{xml_name} for #{self.class}"
      mappable_children(xml_name).map{|current_node| node_to_object(current_node)}
    end

    def direct_elements
      @direct_elements ||= map_children("element")
    end

    def elements
      direct_elements
    end

    def ordered_elements
      # loop over each interpretable child xml node, and if we can convert a child node
      # to an XsdReader object, let it give its compilation of all_elements
      nodes.map{|node| node_to_object(node)}.compact.map do |obj|
        obj.is_a?(Element) ? obj : obj.ordered_elements
      end.flatten
    end

    def all_elements
      @all_elements ||= ordered_elements +
        (linked_complex_type ? linked_complex_type.all_elements : []) +
        (referenced_element ? referenced_element.all_elements : [])
    end

    def child_elements?
      elements.length > 0
    end

    def attributes
      @attributes ||= map_children("attribute") #+
        #(referenced_element ? referenced_element.attributes : [])
    end

    def sequences
      @sequences ||= map_children("sequence")
    end

    def choices
      @choices ||= map_children("choice")
    end

    def complex_types
      @complex_types ||= map_children("complexType")
    end

    def complex_type
      complex_types.first || linked_complex_type || (referenced_element ? referenced_element.complex_type : nil)
    end

    def linked_complex_type
      @linked_complex_type ||= (schema_for_namespace(type_namespace) || schema).complex_types.find{|ct| ct.name == (type_name || type)}
      #@linked_complex_type ||= object_by_name('#{schema_namespace_prefix}complexType', type) || object_by_name('#{schema_namespace_prefix}complexType', type_name)
    end

    def simple_contents
      @simple_contents ||= map_children("simpleContent")
    end

    def simple_content
      simple_contents.first
    end

    def complex_contents
      @complex_contents ||= map_children("complexContent")
    end

    def complex_content
      complex_contents.first
    end

    def extensions
      @extensions ||= map_children("extension")
    end

    def extension
      extensions.first
    end

    def simple_types
      @simple_types ||= map_children("simpleType")
    end

    def linked_simple_type
      @linked_simple_type ||= object_by_name("#{schema_namespace_prefix}simpleType", type) || object_by_name("#{schema_namespace_prefix}simpleType", type_name)
      # @linked_simple_type ||= (type_namespace ? schema_for_namespace(type_namespace) : schema).simple_types.find{|st| st.name == (type_name || type)}
    end

    #
    # Related objects
    #

    def parent
      if node && node.respond_to?(:parent) && node.parent

        return node_to_object(node.parent)
      end

      nil
    end

    def schema
      return options[:schema] if options[:schema]
      schema_node = node.search('//xs:schema')[0]
      return schema_node.nil? ? nil : node_to_object(schema_node)
    end

    def object_by_name(xml_name, name)
      # find in local schema, then look in imported schemas
      nod = node.search("//#{xml_name}[@name=\"#{name}\"]").first
      return node_to_object(nod) if nod

      # try to find in any of the importers
      self.schema.imports.each do |import|
        obj = import.reader.schema.object_by_name(xml_name, name)
        return obj if obj
      end

      return nil
    end

    def schema_for_namespace(ns)
      logger.debug "Shared#schema_for_namespace with namespace: #{ns}"
      return schema if schema.targets_namespace?(ns)

      if import = schema.import_by_namespace(ns)
        logger.debug "Shared#schema_for_namespace found import schema"
        return import.reader.schema
      end

      logger.debug "Shared#schema_for_namespace no result"
      return nil
    end
  end

end # module XsdReader