aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support/xml_mini
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport/lib/active_support/xml_mini')
-rw-r--r--activesupport/lib/active_support/xml_mini/libxml.rb133
-rw-r--r--activesupport/lib/active_support/xml_mini/nokogiri.rb77
-rw-r--r--activesupport/lib/active_support/xml_mini/rexml.rb108
3 files changed, 318 insertions, 0 deletions
diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb
new file mode 100644
index 0000000000..3586b24a6b
--- /dev/null
+++ b/activesupport/lib/active_support/xml_mini/libxml.rb
@@ -0,0 +1,133 @@
+require 'libxml'
+
+# = XmlMini LibXML implementation
+module ActiveSupport
+ module XmlMini_LibXML #:nodoc:
+ extend self
+
+ # Parse an XML Document string into a simple hash using libxml.
+ # string::
+ # XML Document string to parse
+ def parse(string)
+ LibXML::XML.default_keep_blanks = false
+
+ if string.blank?
+ {}
+ else
+ LibXML::XML::Parser.string(string.strip).parse.to_hash
+ end
+ end
+
+ end
+end
+
+module LibXML
+ module Conversions
+ module Document
+ def to_hash
+ root.to_hash
+ end
+ end
+
+ module Node
+ CONTENT_ROOT = '__content__'
+ LIB_XML_LIMIT = 30000000 # Hardcoded LibXML limit
+
+ # Convert XML document to hash
+ #
+ # hash::
+ # Hash to merge the converted element into.
+ def to_hash(hash={})
+ if text?
+ raise LibXML::XML::Error if content.length >= LIB_XML_LIMIT
+ hash[CONTENT_ROOT] = content
+ else
+ sub_hash = insert_name_into_hash(hash, name)
+ attributes_to_hash(sub_hash)
+ if array?
+ children_array_to_hash(sub_hash)
+ elsif yaml?
+ children_yaml_to_hash(sub_hash)
+ else
+ children_to_hash(sub_hash)
+ end
+ end
+ hash
+ end
+
+ protected
+
+ # Insert name into hash
+ #
+ # hash::
+ # Hash to merge the converted element into.
+ # name::
+ # name to to merge into hash
+ def insert_name_into_hash(hash, name)
+ sub_hash = {}
+ if hash[name]
+ if !hash[name].kind_of? Array
+ hash[name] = [hash[name]]
+ end
+ hash[name] << sub_hash
+ else
+ hash[name] = sub_hash
+ end
+ sub_hash
+ end
+
+ # Insert children into hash
+ #
+ # hash::
+ # Hash to merge the children into.
+ def children_to_hash(hash={})
+ each { |child| child.to_hash(hash) }
+ attributes_to_hash(hash)
+ hash
+ end
+
+ # Convert xml attributes to hash
+ #
+ # hash::
+ # Hash to merge the attributes into
+ def attributes_to_hash(hash={})
+ each_attr { |attr| hash[attr.name] = attr.value }
+ hash
+ end
+
+ # Convert array into hash
+ #
+ # hash::
+ # Hash to merge the array into
+ def children_array_to_hash(hash={})
+ hash[child.name] = map do |child|
+ returning({}) { |sub_hash| child.children_to_hash(sub_hash) }
+ end
+ hash
+ end
+
+ # Convert yaml into hash
+ #
+ # hash::
+ # Hash to merge the yaml into
+ def children_yaml_to_hash(hash = {})
+ hash[CONTENT_ROOT] = content unless content.blank?
+ hash
+ end
+
+ # Check if child is of type array
+ def array?
+ child? && child.next? && child.name == child.next.name
+ end
+
+ # Check if child is of type yaml
+ def yaml?
+ attributes.collect{|x| x.value}.include?('yaml')
+ end
+
+ end
+ end
+end
+
+LibXML::XML::Document.send(:include, LibXML::Conversions::Document)
+LibXML::XML::Node.send(:include, LibXML::Conversions::Node)
diff --git a/activesupport/lib/active_support/xml_mini/nokogiri.rb b/activesupport/lib/active_support/xml_mini/nokogiri.rb
new file mode 100644
index 0000000000..10281584fc
--- /dev/null
+++ b/activesupport/lib/active_support/xml_mini/nokogiri.rb
@@ -0,0 +1,77 @@
+require 'nokogiri'
+
+# = XmlMini Nokogiri implementation
+module ActiveSupport
+ module XmlMini_Nokogiri #:nodoc:
+ extend self
+
+ # Parse an XML Document string into a simple hash using libxml / nokogiri.
+ # string::
+ # XML Document string to parse
+ def parse(string)
+ if string.blank?
+ {}
+ else
+ doc = Nokogiri::XML(string)
+ raise doc.errors.first if doc.errors.length > 0
+ doc.to_hash
+ end
+ end
+
+ module Conversions
+ module Document
+ def to_hash
+ root.to_hash
+ end
+ end
+
+ module Node
+ CONTENT_ROOT = '__content__'
+
+ # Convert XML document to hash
+ #
+ # hash::
+ # Hash to merge the converted element into.
+ def to_hash(hash = {})
+ hash[name] ||= attributes_as_hash
+
+ walker = lambda { |memo, parent, child, callback|
+ next if child.blank? && 'file' != parent['type']
+
+ if child.text?
+ (memo[CONTENT_ROOT] ||= '') << child.content
+ next
+ end
+
+ name = child.name
+
+ child_hash = child.attributes_as_hash
+ if memo[name]
+ memo[name] = [memo[name]].flatten
+ memo[name] << child_hash
+ else
+ memo[name] = child_hash
+ end
+
+ # Recusively walk children
+ child.children.each { |c|
+ callback.call(child_hash, child, c, callback)
+ }
+ }
+
+ children.each { |c| walker.call(hash[name], self, c, walker) }
+ hash
+ end
+
+ def attributes_as_hash
+ Hash[*(attribute_nodes.map { |node|
+ [node.node_name, node.value]
+ }.flatten)]
+ end
+ end
+ end
+
+ Nokogiri::XML::Document.send(:include, Conversions::Document)
+ Nokogiri::XML::Node.send(:include, Conversions::Node)
+ end
+end
diff --git a/activesupport/lib/active_support/xml_mini/rexml.rb b/activesupport/lib/active_support/xml_mini/rexml.rb
new file mode 100644
index 0000000000..a8fdeca967
--- /dev/null
+++ b/activesupport/lib/active_support/xml_mini/rexml.rb
@@ -0,0 +1,108 @@
+# = XmlMini ReXML implementation
+module ActiveSupport
+ module XmlMini_REXML #:nodoc:
+ extend self
+
+ CONTENT_KEY = '__content__'.freeze
+
+ # Parse an XML Document string into a simple hash
+ #
+ # Same as XmlSimple::xml_in but doesn't shoot itself in the foot,
+ # and uses the defaults from ActiveSupport
+ #
+ # string::
+ # XML Document string to parse
+ def parse(string)
+ require 'rexml/document' unless defined?(REXML::Document)
+ doc = REXML::Document.new(string)
+ merge_element!({}, doc.root)
+ end
+
+ private
+ # Convert an XML element and merge into the hash
+ #
+ # hash::
+ # Hash to merge the converted element into.
+ # element::
+ # XML element to merge into hash
+ def merge_element!(hash, element)
+ merge!(hash, element.name, collapse(element))
+ end
+
+ # Actually converts an XML document element into a data structure.
+ #
+ # element::
+ # The document element to be collapsed.
+ def collapse(element)
+ hash = get_attributes(element)
+
+ if element.has_elements?
+ element.each_element {|child| merge_element!(hash, child) }
+ merge_texts!(hash, element) unless empty_content?(element)
+ hash
+ else
+ merge_texts!(hash, element)
+ end
+ end
+
+ # Merge all the texts of an element into the hash
+ #
+ # hash::
+ # Hash to add the converted emement to.
+ # element::
+ # XML element whose texts are to me merged into the hash
+ def merge_texts!(hash, element)
+ unless element.has_text?
+ hash
+ else
+ # must use value to prevent double-escaping
+ merge!(hash, CONTENT_KEY, element.texts.sum(&:value))
+ end
+ end
+
+ # Adds a new key/value pair to an existing Hash. If the key to be added
+ # already exists and the existing value associated with key is not
+ # an Array, it will be wrapped in an Array. Then the new value is
+ # appended to that Array.
+ #
+ # hash::
+ # Hash to add key/value pair to.
+ # key::
+ # Key to be added.
+ # value::
+ # Value to be associated with key.
+ def merge!(hash, key, value)
+ if hash.has_key?(key)
+ if hash[key].instance_of?(Array)
+ hash[key] << value
+ else
+ hash[key] = [hash[key], value]
+ end
+ elsif value.instance_of?(Array)
+ hash[key] = [value]
+ else
+ hash[key] = value
+ end
+ hash
+ end
+
+ # Converts the attributes array of an XML element into a hash.
+ # Returns an empty Hash if node has no attributes.
+ #
+ # element::
+ # XML element to extract attributes from.
+ def get_attributes(element)
+ attributes = {}
+ element.attributes.each { |n,v| attributes[n] = v }
+ attributes
+ end
+
+ # Determines if a document element has text content
+ #
+ # element::
+ # XML element to be checked.
+ def empty_content?(element)
+ element.texts.join.blank?
+ end
+ end
+end