aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support/xml_mini/libxml.rb
blob: 0f7ba1918b3ae4268aced0f2113aa4dd7d9615ec (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
require 'libxml'

# = XmlMini LibXML implementation
module ActiveSupport
  module XmlMini_LibXML #:nodoc:
    extend self

    # Parse an XML Document string or IO into a simple hash using libxml.
    # data::
    #   XML Document string or IO to parse
    def parse(data)
      if !data.respond_to?(:read)
        data = StringIO.new(data || '')
      end
      
      char = data.getc
      if char.nil?
        {}
      else
        data.ungetc(char)
        LibXML::XML::Parser.io(data).parse.to_hash
      end
    end

  end
end

module LibXML #:nodoc:
  module Conversions #:nodoc:
    module Document #:nodoc:
      def to_hash
        root.to_hash
      end
    end

    module Node #:nodoc:
      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? || cdata?
          raise LibXML::XML::Error if hash[CONTENT_ROOT].to_s.length + content.length >= LIB_XML_LIMIT
          hash[CONTENT_ROOT] = hash[CONTENT_ROOT].to_s + 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) }

          if hash.length > 1 && hash[CONTENT_ROOT].blank?
            hash.delete(CONTENT_ROOT)
          end

          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)