Class: XSD::XML

Inherits:
Object
  • Object
show all
Defined in:
lib/xsd/xml.rb

Constant Summary collapse

REMOTE_LOCATION =

Matches locations that have to be fetched over the network

%r{\Ahttps?://}i
DEFAULT_RESOURCE_RESOLVER =

Legacy resource resolver, kept for backwards compatibility. Resolves absolute paths and URLs only, relative locations are resolved by the reader itself, see #read_resource

proc do |location, namespace|
  if location.match?(REMOTE_LOCATION)
    Net::HTTP.get(URI(location))
  elsif Pathname.new(location).absolute?
    File.read(location)
  else
    raise ImportError, "Failed to locate import '#{location}'#{namespace ? " for namespace '#{namespace}'" : ''}"
  end
end
CLASS_MAP =
{
  'schema'         => Schema,
  'element'        => Element,
  'attribute'      => Attribute,
  'choice'         => Choice,
  'complexType'    => ComplexType,
  'sequence'       => Sequence,
  'simpleContent'  => SimpleContent,
  'complexContent' => ComplexContent,
  'extension'      => Extension,
  'import'         => Import,
  'include'        => Include,
  'simpleType'     => SimpleType,
  'all'            => All,
  'restriction'    => Restriction,
  'group'          => Group,
  'any'            => Any,
  'union'          => Union,
  'attributeGroup' => AttributeGroup,
  'list'           => List,
  'unique'         => Unique,
  'selector'       => Selector,
  'field'          => Field,
  'annotation'     => Annotation,
  'documentation'  => Documentation,
  'appinfo'        => Appinfo,
  'anyAttribute'   => AnyAttribute,
  'key'            => Key,
  'keyref'         => Keyref,
  # Restriction facets
  'minExclusive'   => Facet,
  'minInclusive'   => Facet,
  'maxExclusive'   => Facet,
  'maxInclusive'   => Facet,
  'totalDigits'    => Facet,
  'fractionDigits' => Facet,
  'length'         => Facet,
  'minLength'      => Facet,
  'maxLength'      => Facet,
  'enumeration'    => Facet,
  'whiteSpace'     => Facet,
  'pattern'        => Facet
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(**options) ⇒ XML

Returns a new instance of XML.



82
83
84
85
86
87
88
# File 'lib/xsd/xml.rb', line 82

def initialize(**options)
  @options              = options
  # keyed by node identity, the parsed documents are treated as immutable
  @object_cache         = {}.compare_by_identity
  @schemas              = []
  @schemas_by_location  = {}
end

Instance Attribute Details

#object_cache ⇒ Object (readonly)

Returns the value of attribute object_cache.



11
12
13
# File 'lib/xsd/xml.rb', line 11

def object_cache
  @object_cache
end

#options ⇒ Object (readonly)

Returns the value of attribute options.



11
12
13
# File 'lib/xsd/xml.rb', line 11

def options
  @options
end

#schemas ⇒ Object (readonly)

Returns the value of attribute schemas.



11
12
13
# File 'lib/xsd/xml.rb', line 11

def schemas
  @schemas
end

Class Method Details

.open(path, **options) ⇒ Object

Create reader from a file path

Parameters:

  • path (String, Pathname)
  • options (Hash)

Returns:

  • XML



76
77
78
79
80
# File 'lib/xsd/xml.rb', line 76

def self.open(path, **options)
  reader = new(**options)
  reader.add_schema_xml(File.read(path.to_s), location: path.to_s)
  reader
end

Instance Method Details

#[](*args) ⇒ Object



167
168
169
170
171
172
173
174
# File 'lib/xsd/xml.rb', line 167

def [](*args)
  schemas.each do |schema|
    item = schema[*args]
    return item if item
  end

  nil
end

#add_schema_node(node, location: nil) ⇒ Object

Add schema node to reader instance

Parameters:

  • node (Nokogiri::XML::Node)
  • location (String, nil) (defaults to: nil) —

    Location the node was read from, used to resolve relative imports

Returns:

  • Schema

Raises:



123
124
125
126
127
128
129
130
131
# File 'lib/xsd/xml.rb', line 123

def add_schema_node(node, location: nil)
  raise Error, 'Added schema must be of type Nokogiri::XML::Node' unless node.is_a?(Nokogiri::XML::Node)

  new_schema = Schema.new(options.merge(node: node, reader: self, location: location))
  schemas.push(new_schema)
  # register the schema so that an import/include pointing back at this location reuses it
  @schemas_by_location[location] ||= new_schema if location
  new_schema
end

#add_schema_xml(xml, location: nil) ⇒ Object

Add schema xml to reader instance

Parameters:

  • xml (String)
  • location (String, nil) (defaults to: nil) —

    Location the xml was read from, used to resolve relative imports

Returns:

  • Schema

Raises:



112
113
114
115
116
117
# File 'lib/xsd/xml.rb', line 112

def add_schema_xml(xml, location: nil)
  doc = read_document(xml)
  raise Error, 'Schema node not found, xml does not seem to be a valid XSD' unless doc.root&.name == 'schema'

  add_schema_node(doc.root, location: location)
end

#attribute_groups ⇒ Object



184
185
186
# File 'lib/xsd/xml.rb', line 184

def attribute_groups
  collect(:attribute_groups)
end

#attributes(*args) ⇒ Object



180
181
182
# File 'lib/xsd/xml.rb', line 180

def attributes(*args)
  collect(:attributes, *args)
end

#complex_types ⇒ Object



188
189
190
# File 'lib/xsd/xml.rb', line 188

def complex_types
  collect(:complex_types)
end

#elements(*args) ⇒ Object



176
177
178
# File 'lib/xsd/xml.rb', line 176

def elements(*args)
  collect(:elements, *args)
end

#groups ⇒ Object



196
197
198
# File 'lib/xsd/xml.rb', line 196

def groups
  collect(:groups)
end

#load_schema(location, namespace: nil, base: nil) ⇒ Object

Load schema referenced by an import/include, reusing an already loaded one when possible

Parameters:

  • location (String)
  • namespace (String, nil) (defaults to: nil)
  • base (String, nil) (defaults to: nil) —

    Location of the referencing schema

Returns:

  • Schema

Raises:



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/xsd/xml.rb', line 138

def load_schema(location, namespace: nil, base: nil)
  raise ImportError, 'Schema location must be provided' if location.nil? || location.empty?

  expanded = expand_location(location, base)
  return @schemas_by_location[expanded] if @schemas_by_location.key?(expanded)

  # a custom resolver is responsible for resolving relative locations on its own
  xml = if options[:resource_resolver]
          options[:resource_resolver].call(location, namespace)
        else
          read_resource(expanded, namespace)
        end

  @schemas_by_location[expanded] = add_schema_xml(xml, location: expanded)
end

#logger ⇒ Object



90
91
92
# File 'lib/xsd/xml.rb', line 90

def logger
  options[:logger] || default_logger
end

#read_document(xml) ⇒ Object



104
105
106
# File 'lib/xsd/xml.rb', line 104

def read_document(xml)
  Nokogiri::XML(xml)
end

#resource_resolver ⇒ Object

Resolver used to fetch imported/included schemas

Returns:

  • Proc



96
97
98
# File 'lib/xsd/xml.rb', line 96

def resource_resolver
  @options[:resource_resolver] || DEFAULT_RESOURCE_RESOLVER
end

#schema ⇒ Object

Get first added (considered primary) schema

Returns:

  • Schema, nil



156
157
158
# File 'lib/xsd/xml.rb', line 156

def schema
  schemas.first
end

#schemas_for_namespace(namespace) ⇒ Object

Get schemas by namespace

Parameters:

  • namespace (String, nil)


163
164
165
# File 'lib/xsd/xml.rb', line 163

def schemas_for_namespace(namespace)
  schemas.select { |schema| schema.target_namespace == namespace }
end

#simple_types ⇒ Object



192
193
194
# File 'lib/xsd/xml.rb', line 192

def simple_types
  collect(:simple_types)
end

#tmp_dir ⇒ Object



100
101
102
# File 'lib/xsd/xml.rb', line 100

def tmp_dir
  options[:tmp_dir]
end