Socket
Book a DemoInstallSign in
Socket

xml-mapping_extensions

Package Overview
Dependencies
Maintainers
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

xml-mapping_extensions

bundlerRubygems
Version
0.4.9
Version published
Maintainers
1
Created
Source

XML::MappingExtensions

Build Status Code Climate Inline docs Gem Version

Additional mapping nodes and other utility code for working with XML::Mapping.

Table of contents

Extension methods

This gem adds two methods, write_xml and parse_xml, to XML mapping instances and classes respectively, to reduce boilerplate.

The write_xml method supplements [XML::Mapping#save_to_xml] by writing the object out as a String rather than as an REXML::Element.

elem = MyElement.new
elem.attribute = 123
elem.text = 'element text'
elem.children = ['child 1', 'child 2']
puts elem.write_xml

outputs

<my-element attribute='123'>
  element text
  <child>child 1</child>
  <child>child 2</child>
</my-element>

The parse_xml method supplements XML::Mapping::ClassMethods#load_from_xml by abstracting away the difference between strings, XML documents, XML elements, files, and IO-like objects.

my_xml_path = 'my_xml.xml'
my_xml_file = File.new(my_xml_path)
my_xml_string = File.read(my_xml_path)
my_xml_io = StringIO.new(my_xml_string)
my_xml_document = REXML::Document.new(my_xml_string)
my_xml_element = my_xml_document.root

# Standard XML::Mapping load_from_xml method
elem = MyXMLClass.load_from_xml(my_xml_element)

# parse_xml equivalent
[my_xml_file, my_xml_string, my_xml_io, my_xml_document, my_xml_element].each do |xml_source|
  expect(MyXMLClass.parse_xml(xml_source)).to eq(elem) # assuming MyXMLClass implements ==
end

Both write_xml and parse_xml accept an options hash, to be passed on to save_to_xml or load_from_xml, respectively:

elem = MyXMLClass.parse(my_xml_string, { mapping: :alternate })
new_xml_string = elem.write_xml({ mapping: :alternate })

Custom nodes

To create a custom node type, require xml/mapping_extensions and extend one of the abstract node classes, or use one of the provided implementations.

Abstract nodes

  • NodeBase: Base class for simple single-attribute nodes that convert XML strings to object values.

Note that you must call ::XML::Mapping.add_node_class for your new node class to be registered with the XML mapping engine.

Example

class LaTeXRationalNode < XML::MappingExtensions::NodeBase
  def to_value(xml_text)
    match_data = /\\frac\{([0-9.]+)\}\{([0-9.]+)\}/.match(xml_text)
    Rational("#{match_data[1]}/#{match_data[2]}")
  end

  def to_xml_text(value)
    "\\frac{#{value.numerator}}{#{value.denominator}}"
  end
end

Provided implementations

  • DateNode: maps XML Schema dates to Date objects
  • TimeNode: ISO 8601 strings to Time objects
  • UriNode: maps URI strings to URI objects
  • MimeTypeNode: maps MIME type strings to MIME::Type objects
  • TypesafeEnumNode: maps XML strings to typesafe_enum values

Example

require 'xml/mapping_extensions'
require 'rexml/document'

class MyElem
  include ::XML::Mapping

  root_element_name 'my_elem'

  date_node :plain_date, 'plain_date'
  date_node :zulu_date, 'zulu_date', zulu: true
  time_node :time, 'time'
  uri_node :uri, 'uri'
  mime_type_node :mime_type, 'mime_type'
end

Reading

xml_str = '<my_elem>
  <plain_date>1999-12-31</plain_date>
  <zulu_date>2000-01-01Z</zulu_date>
  <time>2000-01-01T02:34:56Z</time>
  <uri>http://example.org</uri>
  <mime_type>text/plain</mime_type>
</my_elem>'

xml_doc = REXML::Document.new(xml_str)
xml_elem = xml_doc.root

elem = MyElem.load_from_xml(xml_elem)

puts elem.plain_date.inspect
puts elem.zulu_date.inspect
puts elem.time.inspect
puts elem.uri.inspect
puts elem.mime_type.inspect

Outputs

#<Date: 1999-12-31 ((2451544j,0s,0n),+0s,2299161j)>
#<Date: 2000-01-01 ((2451545j,0s,0n),+0s,2299161j)>
2000-01-01 02:34:56 UTC
#<URI::HTTP http://example.org>
#<MIME::Type:0x007f864bdc4f78 @friendly={"en"=>"Text File"}, @system=nil, @obsolete=false, @registered=true, @use_instead=nil, @signature=false, @content_type="text/plain", @raw_media_type="text", @raw_sub_type="plain", @simplified="text/plain", @i18n_key="text.plain", @media_type="text", @sub_type="plain", @docs=[], @encoding="quoted-printable", @extensions=["txt", "asc", "c", "cc", "h", "hh", "cpp", "hpp", "dat", "hlp", "conf", "def", "doc", "in", "list", "log", "markdown", "md", "rst", "text", "textile"], @references=["IANA", "RFC2046", "RFC3676", "RFC5147"], @xrefs={"rfc"=>["rfc2046", "rfc3676", "rfc5147"]}>

Writing

elem = MyElem.new
elem.plain_date = Date.new(1999, 12, 31)
elem.zulu_date = Date.new(2000, 1, 1)
elem.time = Time.utc(2000, 1, 1, 2, 34, 56)
elem.uri = URI('http://example.org')
elem.mime_type = MIME::Types['text/plain'].first

puts(elem.write_xml)

Outputs:

<my_elem>
  <plain_date>1999-12-31</plain_date>
  <zulu_date>2000-01-01Z</zulu_date>
  <time>2000-01-01T02:34:56Z</time>
  <uri>http://example.org</uri>
  <mime_type>text/plain</mime_type>
</my_elem>

Namespaces

The Namespace class encapsulates an XML namespace. The Namespaced module extends XML::Mapping to add a namespace attribute and write the namespace out when saving to XML.

class MyElem
  include XML::MappingExtensions::Namespaced # instead of XML::Mapping

  namespace Namespace.new(
    prefix: 'px',
    uri: 'http://example.org/px'
  )

  root_element_name 'my_elem'

  date_node :plain_date, 'plain_date'
  date_node :zulu_date, 'zulu_date', zulu: true
  time_node :time, 'time'
  uri_node :uri, 'uri'
  mime_type_node :mime_type, 'mime_type'
end

MyElem.namespace
# => #<XML::MappingExtensions::Namespace:0x007fb1c6b73e80>

The namespace will then be written out when the object is saved to XML:

obj = MyElem.new(...)
obj.namespace = namespace

puts obj.write_xml
<element
    xmlns='http://example.org/px/'
    xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
    xsi:schemaLocation='http://example.org/px.xsd'
    attribute='123'>
  element text
  <child>child 1</child>
  <child>child 2</child>
</element>

Setting a prefix attribute on the namespace will set the prefix on each element in the output:


class MyElem
  namespace Namespace.new(
    prefix: 'px',
    uri: 'http://example.org/px',
    schema_location: 'http://example.org/px.xsd'
  )
end

obj = MyElem.new(...)
obj.namespace = namespace

puts obj.write_xml
<px:element
    xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
    xsi:schemaLocation='http://example.org/px.xsd'
    xmlns:px='http://example.org/px/'
    attribute='123'>
  element text
  <px:child>child 1</px:child>
  <px:child>child 2</px:child>
</px:element>

Fallback mappings

The XML::Mapping library provides an “alternate mapping” mechanism allowing multiple XML mappings per class. However, it requires each mapping to be exhaustive -- an alternate mapping must redefine all attribute mappings defined in the primary, or else ignore those attributes. Sometimes, however, it is

class ValidatingElement
  include ::XML::Mapping

  root_element_name 'element'
  text_node :name, '@name'
  text_node :value, '@value'

  use_mapping :strict
  numeric_node :value, '@value', writer: proc { |obj, xml| xml.add_attribute('value', Float(obj.value)) }
end

invalid_string = '<element name="abc" value="efg"/>'
ValidatingElement.parse_xml(invalid_string, mapping: :strict)
# ArgumentError: invalid value for Float(): "efg"

elem = ValidatingElement.parse_xml(invalid_string) # OK
# => #<XML::Mapping::ValidatingElement:0x007fa5631ae9c8>
elem.write_xml
# => "<element name='abc' value='efg'/>"

elem.write_xml(mapping: :strict)
# ArgumentError: invalid value for Float(): "efg"

So far, so good; but say we set a valid value and try to output with the :strict mapping?

elem.value = 123
elem.write_xml(mapping: :strict)
# => <validating-element value='123'/>

Since the :strict mapping doesn't define a root element name, we get the default (based on the class name), and since it doesn't define a mapping for the name attribute, we lose that entirely.

But if we add a fallback mapping --

class ValidatingElement
  fallback_mapping :strict, :_default
end

elem.write_xml(mapping: :strict)
# => <element value='123' name='abc'/>

-- the :strict mapping now gets the root element name element, and the name attribute, as defined under the :_default mapping.

FAQs

Package last updated on 02 Jan 2018

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts