multi_xml 0.6.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,169 @@
1
+ require "cgi/escape"
2
+
3
+ module MultiXML
4
+ module Parsers
5
+ # Shared SAX handler logic for building hash trees from XML events.
6
+ #
7
+ # Provides a stack machine used by both NokogiriSax and LibxmlSax
8
+ # handlers. Parser-specific subclasses translate their native callbacks
9
+ # into calls on this entrypoint:
10
+ #
11
+ # - handle_start_element_ns(local, prefix, attr_tuples, ns_decls)
12
+ # where attr_tuples = [[attr_prefix_or_nil, local, value], ...]
13
+ # ns_decls = [[prefix_or_nil, uri], ...]
14
+ #
15
+ # @api private
16
+ module SaxHandler
17
+ # Initialize the handler state
18
+ #
19
+ # @api private
20
+ # @param mode [Symbol] Namespace handling mode
21
+ # @return [void]
22
+ def initialize_handler(mode)
23
+ @mode = mode
24
+ @result = {}
25
+ @stack = [@result]
26
+ @pending = []
27
+ end
28
+
29
+ # Get the parsed result
30
+ #
31
+ # @api private
32
+ # @return [Hash] the parsed hash
33
+ attr_reader :result
34
+
35
+ private
36
+
37
+ # Get the current element hash on top of the stack
38
+ #
39
+ # @api private
40
+ # @return [Hash] current hash being built
41
+ def current = @stack.last
42
+
43
+ # Entry point for namespace-aware start events
44
+ #
45
+ # @api private
46
+ # @param local [String] Local element name
47
+ # @param prefix [String, nil] Element namespace prefix
48
+ # @param attr_tuples [Array] Attributes as [prefix, local, value]
49
+ # @param ns_decls [Array] xmlns declarations as [prefix, uri] pairs
50
+ # @return [void]
51
+ def handle_start_element_ns(local, prefix, attr_tuples, ns_decls)
52
+ child = {TEXT_CONTENT_KEY => +""}
53
+ add_child_to_current(format_name(prefix, local), child)
54
+ @stack << child
55
+
56
+ @pending << build_pending_attrs(ns_decls, attr_tuples)
57
+ end
58
+
59
+ # Apply attributes and pop the current element from the stack
60
+ #
61
+ # @api private
62
+ # @return [void]
63
+ def handle_end_element
64
+ @pending.pop.each { |key, value| add_attr_to_current(key, value) }
65
+ strip_whitespace_content
66
+ @stack.pop
67
+ end
68
+
69
+ # Append text to the current element's content
70
+ #
71
+ # @api private
72
+ # @param text [String] Text to append
73
+ # @return [void]
74
+ def append_text(text)
75
+ current[TEXT_CONTENT_KEY] << text
76
+ end
77
+
78
+ # Build the list of attributes to apply at element-end
79
+ #
80
+ # @api private
81
+ # @param ns_decls [Array] xmlns declarations
82
+ # @param attr_tuples [Array] Attribute [prefix, local, value] tuples
83
+ # @return [Array<Array>] list of [key, value] pairs
84
+ def build_pending_attrs(ns_decls, attr_tuples)
85
+ preserved_ns_decls(ns_decls) + attr_tuples.map do |prefix, local, value|
86
+ [format_name(prefix, local), CGI.unescapeHTML(value)]
87
+ end
88
+ end
89
+
90
+ # Transform xmlns declarations into attribute pairs for :preserve mode
91
+ #
92
+ # @api private
93
+ # @param ns_decls [Array] Declarations as [prefix, uri]
94
+ # @return [Array<Array>] [xmlns key, uri] pairs (empty outside :preserve)
95
+ def preserved_ns_decls(ns_decls)
96
+ return [] unless @mode == :preserve
97
+
98
+ ns_decls.map { |prefix, uri| [prefix ? "xmlns:#{prefix}" : "xmlns", uri] }
99
+ end
100
+
101
+ # Produce a name string for a [prefix, local] tuple
102
+ #
103
+ # @api private
104
+ # @param prefix [String, nil] Namespace prefix
105
+ # @param local [String] Local part of the name
106
+ # @return [String] formatted name
107
+ def format_name(prefix, local)
108
+ (@mode == :preserve && prefix) ? "#{prefix}:#{local}" : local
109
+ end
110
+
111
+ # Add a child element to the current hash, folding on collision
112
+ #
113
+ # @api private
114
+ # @param name [String] Child element name
115
+ # @param child [Hash] Child hash to add
116
+ # @return [void]
117
+ def add_child_to_current(name, child)
118
+ existing = current[name]
119
+ current[name] = case existing
120
+ when Array then existing << child
121
+ when Hash then [existing, child]
122
+ else child
123
+ end
124
+ end
125
+
126
+ # Add an attribute value to the current hash (attr wins on collision)
127
+ #
128
+ # Attributes are applied at end_element, after children have already
129
+ # populated the hash. When an attribute collides with a child of the
130
+ # same local name, the attribute is placed first in the resulting
131
+ # array (matching DomParser / REXML behavior and existing tests).
132
+ #
133
+ # @api private
134
+ # @param key [String] Attribute key
135
+ # @param value [String] Attribute value
136
+ # @return [void]
137
+ def add_attr_to_current(key, value)
138
+ existing = current[key]
139
+ current[key] = case existing
140
+ when nil then value
141
+ when Array then insert_attribute_before_children(existing, value)
142
+ when Hash then [value, existing]
143
+ else [existing, value]
144
+ end
145
+ end
146
+
147
+ # Insert a later attribute before any child-element entries
148
+ #
149
+ # @api private
150
+ # @param values [Array] Existing colliding values
151
+ # @param value [String] Attribute value to insert
152
+ # @return [Array] Updated value list
153
+ def insert_attribute_before_children(values, value)
154
+ child_index = values.index { |entry| entry.is_a?(Hash) } || values.length
155
+ values.dup.insert(child_index, value)
156
+ end
157
+
158
+ # Remove empty or whitespace-only text content from the current hash
159
+ #
160
+ # @api private
161
+ # @return [void]
162
+ def strip_whitespace_content
163
+ content = current[TEXT_CONTENT_KEY]
164
+ should_remove = content.empty? || (current.size > 1 && content.strip.empty?)
165
+ current.delete(TEXT_CONTENT_KEY) if should_remove
166
+ end
167
+ end
168
+ end
169
+ end
@@ -1,45 +1,7 @@
1
- module MultiXml
2
- module Version
3
- module_function
4
-
5
- # @return [Integer]
6
- def major
7
- 0
8
- end
9
-
10
- # @return [Integer]
11
- def minor
12
- 6
13
- end
14
-
15
- # @return [Integer]
16
- def patch
17
- 0
18
- end
19
-
20
- # @return [Integer, NilClass]
21
- def pre
22
- nil
23
- end
24
-
25
- # @return [Hash]
26
- def to_h
27
- {
28
- :major => major,
29
- :minor => minor,
30
- :patch => patch,
31
- :pre => pre,
32
- }
33
- end
34
-
35
- # @return [Array]
36
- def to_a
37
- [major, minor, patch, pre].compact
38
- end
39
-
40
- # @return [String]
41
- def to_s
42
- to_a.join('.')
43
- end
44
- end
1
+ module MultiXML
2
+ # The current version of MultiXML
3
+ #
4
+ # @api public
5
+ # @return [Gem::Version] the gem version
6
+ VERSION = Gem::Version.create("0.9.1")
45
7
  end