moxml 0.1.23 → 0.1.24

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -1
  3. data/lib/compat/opal/moxml_boot.rb +53 -0
  4. data/lib/moxml/adapter/base.rb +5 -5
  5. data/lib/moxml/adapter/customized_libxml/cdata.rb +0 -2
  6. data/lib/moxml/adapter/customized_libxml/comment.rb +0 -2
  7. data/lib/moxml/adapter/customized_libxml/element.rb +3 -5
  8. data/lib/moxml/adapter/customized_libxml/node.rb +2 -2
  9. data/lib/moxml/adapter/customized_libxml/processing_instruction.rb +0 -2
  10. data/lib/moxml/adapter/customized_libxml/text.rb +0 -2
  11. data/lib/moxml/adapter/customized_rexml/formatter.rb +3 -4
  12. data/lib/moxml/adapter/headed_ox.rb +1 -5
  13. data/lib/moxml/adapter/libxml.rb +12 -10
  14. data/lib/moxml/adapter/nokogiri.rb +4 -2
  15. data/lib/moxml/adapter/oga.rb +10 -10
  16. data/lib/moxml/adapter/ox.rb +29 -16
  17. data/lib/moxml/adapter/rexml.rb +5 -6
  18. data/lib/moxml/adapter.rb +12 -4
  19. data/lib/moxml/attribute.rb +5 -2
  20. data/lib/moxml/config.rb +1 -0
  21. data/lib/moxml/context.rb +8 -8
  22. data/lib/moxml/declaration.rb +2 -7
  23. data/lib/moxml/document.rb +0 -11
  24. data/lib/moxml/document_builder.rb +12 -8
  25. data/lib/moxml/element.rb +4 -4
  26. data/lib/moxml/node.rb +37 -14
  27. data/lib/moxml/node_set.rb +2 -4
  28. data/lib/moxml/sax/block_handler.rb +0 -2
  29. data/lib/moxml/sax/element_handler.rb +0 -2
  30. data/lib/moxml/sax/namespace_splitter.rb +5 -4
  31. data/lib/moxml/sax.rb +4 -25
  32. data/lib/moxml/version.rb +1 -1
  33. data/lib/moxml/xml_utils.rb +1 -3
  34. data/lib/moxml/xpath/compiler.rb +1 -49
  35. data/lib/moxml/xpath/conversion.rb +7 -6
  36. data/lib/moxml/xpath/ruby/generator.rb +12 -19
  37. data/lib/moxml/xpath/ruby/node.rb +1 -9
  38. data/lib/moxml/xpath.rb +6 -14
  39. data/lib/moxml.rb +67 -20
  40. data/spec/moxml/attribute_spec.rb +16 -0
  41. data/spec/moxml/context_spec.rb +14 -0
  42. data/spec/moxml/moxml_spec.rb +13 -0
  43. data/spec/moxml/node_spec.rb +58 -0
  44. data/spec/moxml/xpath/ruby/node_spec.rb +3 -3
  45. metadata +3 -2
@@ -1,16 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "node"
4
- require_relative "element"
5
- require_relative "text"
6
- require_relative "cdata"
7
- require_relative "comment"
8
- require_relative "processing_instruction"
9
- require_relative "declaration"
10
- require_relative "namespace"
11
- require_relative "doctype"
12
- require_relative "entity_reference"
13
-
14
3
  module Moxml
15
4
  class Document < Node
16
5
  attr_accessor :has_xml_declaration
@@ -13,15 +13,13 @@ module Moxml
13
13
  @current_doc = context.create_document(native_doc)
14
14
 
15
15
  # Transfer has_declaration flag if present in attachments
16
- if adapter.respond_to?(:attachments) &&
17
- adapter.attachments.key?(native_doc, :has_declaration)
16
+ if adapter.attachments.key?(native_doc, :has_declaration)
18
17
  has_declaration = adapter.attachments.get(native_doc, :has_declaration)
19
18
  @current_doc.has_xml_declaration = has_declaration
20
19
  end
21
20
 
22
21
  # Transfer DOCTYPE from parsed document if it exists in attachments
23
- if adapter.respond_to?(:attachments) &&
24
- adapter.attachments.key?(native_doc, :doctype)
22
+ if adapter.attachments.key?(native_doc, :doctype)
25
23
  doctype = adapter.attachments.get(native_doc, :doctype)
26
24
  if doctype
27
25
  adapter.attachments.set(@current_doc.native, :doctype, doctype)
@@ -35,10 +33,16 @@ module Moxml
35
33
  private
36
34
 
37
35
  def visit_node(node)
38
- method_name = "visit_#{node_type(node)}"
39
- return unless respond_to?(method_name, true)
40
-
41
- send(method_name, node)
36
+ case node_type(node)
37
+ when :document then visit_document(node)
38
+ when :element then visit_element(node)
39
+ when :text then visit_text(node)
40
+ when :cdata then visit_cdata(node)
41
+ when :comment then visit_comment(node)
42
+ when :processing_instruction then visit_processing_instruction(node)
43
+ when :doctype then visit_doctype(node)
44
+ when :entity_reference then visit_entity_reference(node)
45
+ end
42
46
  end
43
47
 
44
48
  def visit_document(doc)
data/lib/moxml/element.rb CHANGED
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "attribute"
4
- require_relative "namespace"
5
-
6
3
  module Moxml
7
4
  class Element < Node
8
5
  def name
@@ -137,6 +134,8 @@ module Moxml
137
134
  adapter.restore_entities(val)
138
135
  end
139
136
 
137
+ alias content text
138
+
140
139
  def text=(content)
141
140
  adapter.set_text_content(@native, normalize_xml_value(content))
142
141
  invalidate_children_cache!
@@ -158,7 +157,8 @@ module Moxml
158
157
  end
159
158
 
160
159
  def inner_xml=(xml)
161
- doc = context.parse("<root>#{xml}</root>")
160
+ wrapper = "_moxml_inner_#{Process.pid}_#{object_id}"
161
+ doc = context.parse("<#{wrapper}>#{xml}</#{wrapper}>")
162
162
  adapter.replace_children(@native, doc.root.children.map(&:native))
163
163
  invalidate_children_cache!
164
164
  end
data/lib/moxml/node.rb CHANGED
@@ -1,11 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "xml_utils"
4
- require_relative "node_set"
5
-
6
3
  module Moxml
7
4
  class Node
8
5
  include XmlUtils
6
+ include Enumerable
9
7
 
10
8
  TYPES = %i[
11
9
  element text cdata comment processing_instruction document
@@ -143,13 +141,14 @@ module Moxml
143
141
  ""
144
142
  end
145
143
 
146
- # Attribute accessor - only works on Element nodes
147
- # Returns nil for non-element nodes
148
- def [](name)
149
- return nil unless respond_to?(:attribute)
150
-
151
- attr = attribute(name)
152
- attr&.value if attr.respond_to?(:value)
144
+ # Returns the content/value of this node as a string.
145
+ # Each subclass overrides this with type-specific semantics:
146
+ # - Text, Comment, Cdata: raw text content
147
+ # - ProcessingInstruction: instruction content
148
+ # - Attribute: attribute value
149
+ # - Element: delegates to text (descendant text concatenation)
150
+ def content
151
+ ""
153
152
  end
154
153
 
155
154
  # Returns the namespace of this node
@@ -176,15 +175,39 @@ module Moxml
176
175
  def each_node(&block)
177
176
  children.each do |child|
178
177
  yield child
179
- child.each_node(&block) if child.respond_to?(:each_node)
178
+ child.each_node(&block)
180
179
  end
181
180
  end
182
181
 
183
- # Clone node (deep copy)
184
- def clone
182
+ # Yield direct children, enabling Enumerable on the node.
183
+ def each(&block)
184
+ return to_enum(:each) unless block
185
+
186
+ children.each(&block)
187
+ end
188
+
189
+ def outer_xml
190
+ to_xml
191
+ end
192
+
193
+ def before(node)
194
+ add_previous_sibling(node)
195
+ end
196
+
197
+ def after(node)
198
+ add_next_sibling(node)
199
+ end
200
+
201
+ def blank?
202
+ text.strip.empty?
203
+ end
204
+
205
+ # Deep copy of the node (both dup and clone create deep copies for XML nodes)
206
+ def dup
185
207
  Moxml::Node.wrap(adapter.dup(@native), context)
186
208
  end
187
- alias dup clone
209
+
210
+ alias clone dup
188
211
 
189
212
  def ==(other)
190
213
  self.class == other.class && @native == other.native
@@ -68,8 +68,7 @@ module Moxml
68
68
  end
69
69
 
70
70
  def <<(node)
71
- # If it's a wrapped Moxml node, unwrap to native before storing
72
- native_node = node.respond_to?(:native) ? node.native : node
71
+ native_node = node.is_a?(Node) ? node.native : node
73
72
  @nodes << native_node
74
73
  @wrapped << nil
75
74
  self
@@ -113,8 +112,7 @@ module Moxml
113
112
  # Delete a node from the set
114
113
  # Accepts both wrapped Moxml nodes and native nodes
115
114
  def delete(node)
116
- # If it's a wrapped Moxml node, unwrap to native
117
- native_node = node.respond_to?(:native) ? node.native : node
115
+ native_node = node.is_a?(Node) ? node.native : node
118
116
  idx = @nodes.index(native_node)
119
117
  if idx
120
118
  @nodes.delete_at(idx)
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "handler"
4
-
5
3
  module Moxml
6
4
  module SAX
7
5
  # Block-based SAX handler with DSL
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "handler"
4
-
5
3
  module Moxml
6
4
  module SAX
7
5
  # Element-focused SAX handler with stack tracking
@@ -38,11 +38,12 @@ module Moxml
38
38
  when nil
39
39
  # nothing
40
40
  else
41
- if attributes.respond_to?(:each)
41
+ if attributes.is_a?(Enumerable)
42
42
  attributes.each do |item|
43
- if item.is_a?(Array) && item.size >= 2
44
- yield item[0], item[1]
45
- elsif item.respond_to?(:name) && item.respond_to?(:value)
43
+ case item
44
+ when Array
45
+ yield item[0], item[1] if item.size >= 2
46
+ else
46
47
  yield item.name, item.value
47
48
  end
48
49
  end
data/lib/moxml/sax.rb CHANGED
@@ -1,31 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "sax/handler"
4
- require_relative "sax/element_handler"
5
- require_relative "sax/block_handler"
6
-
7
3
  module Moxml
8
- # SAX (Simple API for XML) parsing interface
9
- #
10
- # Provides event-driven XML parsing across all Moxml adapters.
11
- # SAX parsing is memory-efficient and suitable for processing large XML files.
12
- #
13
- # @example Class-based handler
14
- # class MyHandler < Moxml::SAX::Handler
15
- # def on_start_element(name, attributes = {}, namespaces = {})
16
- # puts "Started element: #{name}"
17
- # end
18
- # end
19
- #
20
- # context = Moxml.new
21
- # context.sax_parse(xml_string, MyHandler.new)
22
- #
23
- # @example Block-based handler
24
- # context.sax_parse(xml_string) do
25
- # start_element { |name, attrs| puts "Element: #{name}" }
26
- # characters { |text| puts "Text: #{text}" }
27
- # end
28
- #
29
4
  module SAX
5
+ autoload :Handler, "moxml/sax/handler"
6
+ autoload :ElementHandler, "moxml/sax/element_handler"
7
+ autoload :BlockHandler, "moxml/sax/block_handler"
8
+ autoload :NamespaceSplitter, "moxml/sax/namespace_splitter"
30
9
  end
31
10
  end
data/lib/moxml/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Moxml
4
- VERSION = "0.1.23"
4
+ VERSION = "0.1.24"
5
5
  end
@@ -1,12 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "xml_utils/encoder"
4
-
5
- # Ruby 3.3+ requires the URI module to be explicitly required
6
3
  require "uri" unless defined?(URI)
7
4
 
8
5
  module Moxml
9
6
  module XmlUtils
7
+ autoload :Encoder, "moxml/xml_utils/encoder"
10
8
  def encode_entities(text, mode = nil)
11
9
  Encoder.new(text, mode).call
12
10
  end
@@ -62,16 +62,6 @@ module Moxml
62
62
  matched = matched_literal
63
63
  context_var = context_literal
64
64
 
65
- # Enable debug output
66
- debug = ENV["DEBUG_XPATH"] == "1"
67
- if debug
68
- puts "\n#{'=' * 60}"
69
- puts "COMPILING XPath"
70
- puts "=" * 60
71
- puts "AST: #{ast.inspect}"
72
- puts
73
- end
74
-
75
65
  ruby_ast = if return_nodeset?(ast)
76
66
  process(ast, document) { |node| matched.push(node) }
77
67
  else
@@ -103,14 +93,6 @@ module Moxml
103
93
  generator = Ruby::Generator.new
104
94
  source = generator.process(proc_ast)
105
95
 
106
- if debug
107
- puts "GENERATED RUBY CODE:"
108
- puts "-" * 60
109
- puts source
110
- puts "=" * 60
111
- puts
112
- end
113
-
114
96
  CONTEXT.evaluate(source)
115
97
  ensure
116
98
  @literal_id = 0
@@ -1506,7 +1488,7 @@ module Moxml
1506
1488
  xml_lang.assign(string("xml:lang"))
1507
1489
  end
1508
1490
  .followed_by do
1509
- node.respond_to?(symbol(:attribute)).while_true do
1491
+ node.is_a?(const_ref("Moxml", "Element")).while_true do
1510
1492
  found.assign(node.get(xml_lang))
1511
1493
  .followed_by do
1512
1494
  found.if_true do
@@ -1622,36 +1604,6 @@ module Moxml
1622
1604
  arg_var.assign(arg_ast).followed_by { yield arg_var }
1623
1605
  end
1624
1606
 
1625
- # Helper: Try to match first node v1
1626
- def try_match_first_node_v1(ast, input, optimize_first = true)
1627
- if return_nodeset?(ast) && optimize_first
1628
- matched_set = unique_literal(:matched_set)
1629
- first_node = unique_literal(:first_node)
1630
- context_var = context_literal
1631
-
1632
- # Create NodeSet for results
1633
- nodeset_class = const_ref("Moxml", "NodeSet")
1634
- empty_array = Ruby::Node.new(:array, [])
1635
- nodeset_new = Ruby::Node.new(:send,
1636
- [nodeset_class, "new", empty_array,
1637
- context_var])
1638
-
1639
- matched_set.assign(nodeset_new)
1640
- .followed_by do
1641
- # Process with block to accumulate results
1642
- process(ast, input) { |node| matched_set.push(node) }
1643
- end
1644
- .followed_by do
1645
- first_node.assign(matched_set[literal(0)])
1646
- end
1647
- .followed_by do
1648
- first_node.if_true { first_node }.else { string("") }
1649
- end
1650
- else
1651
- process(ast, input)
1652
- end
1653
- end
1654
-
1655
1607
  # Helper: Create mass assignment node
1656
1608
  def mass_assign(vars, value)
1657
1609
  Ruby::Node.new(:massign, [vars, value])
@@ -12,11 +12,11 @@ module Moxml
12
12
  # @param [Object] right
13
13
  # @return [Array<Object, Object>]
14
14
  def self.to_compatible_types(left, right)
15
- if left.is_a?(Moxml::NodeSet) || left.respond_to?(:text)
15
+ if left.is_a?(Moxml::NodeSet) || left.is_a?(Moxml::Node)
16
16
  left = to_string(left)
17
17
  end
18
18
 
19
- if right.is_a?(Moxml::NodeSet) || right.respond_to?(:text)
19
+ if right.is_a?(Moxml::NodeSet) || right.is_a?(Moxml::Node)
20
20
  right = to_string(right)
21
21
  end
22
22
 
@@ -51,7 +51,7 @@ module Moxml
51
51
  value = first_node_text(value)
52
52
  end
53
53
 
54
- if value.respond_to?(:text)
54
+ if value.is_a?(Moxml::Node)
55
55
  value = value.text
56
56
  end
57
57
 
@@ -67,7 +67,7 @@ module Moxml
67
67
  value = first_node_text(value)
68
68
  end
69
69
 
70
- if value.respond_to?(:text)
70
+ if value.is_a?(Moxml::Node)
71
71
  value = value.text
72
72
  end
73
73
 
@@ -95,7 +95,7 @@ module Moxml
95
95
  bool = !value.nan? && !value.zero?
96
96
  elsif value.is_a?(Integer)
97
97
  bool = !value.zero?
98
- elsif value.respond_to?(:empty?)
98
+ elsif value.is_a?(String) || value.is_a?(Moxml::NodeSet) || value.is_a?(Array)
99
99
  bool = !value.empty?
100
100
  elsif value
101
101
  bool = true
@@ -117,7 +117,8 @@ module Moxml
117
117
  # @param [Moxml::NodeSet] set
118
118
  # @return [String]
119
119
  def self.first_node_text(set)
120
- set[0].respond_to?(:text) ? set[0].text : ""
120
+ first = set[0]
121
+ first.is_a?(Moxml::Node) ? first.text : ""
121
122
  end
122
123
  end
123
124
  end
@@ -13,13 +13,7 @@ module Moxml
13
13
  # @param [Moxml::XPath::Ruby::Node] ast
14
14
  # @return [String]
15
15
  def process(ast)
16
- handler = :"on_#{ast.type}"
17
- unless respond_to?(handler, true)
18
- raise NotImplementedError,
19
- "Generator missing handler for node type :#{ast.type}. Node: #{ast.inspect}"
20
- end
21
-
22
- send(handler, ast)
16
+ send(:"on_#{ast.type}", ast)
23
17
  end
24
18
 
25
19
  # @param [Moxml::XPath::Ruby::Node] ast
@@ -81,6 +75,15 @@ module Moxml
81
75
  "#{left_str} == #{right_str}"
82
76
  end
83
77
 
78
+ def on_neq(ast)
79
+ left, right = *ast
80
+
81
+ left_str = process(left)
82
+ right_str = process(right)
83
+
84
+ "#{left_str} != #{right_str}"
85
+ end
86
+
84
87
  # Processes a boolean "and" node.
85
88
  #
86
89
  # @param [Moxml::XPath::Ruby::Node] ast
@@ -167,14 +170,8 @@ module Moxml
167
170
  brackets = name == "[]"
168
171
 
169
172
  unless args.empty?
170
- arg_strs = []
171
- args.each do |arg|
172
- result = process(arg)
173
- # Keep processing if we got a Node back (happens with nested send nodes)
174
- while result.respond_to?(:type)
175
- result = process(result)
176
- end
177
- arg_strs << result
173
+ arg_strs = args.map do |arg|
174
+ process(arg)
178
175
  end
179
176
  arg_str = arg_strs.join(", ")
180
177
  call = brackets ? "[#{arg_str}]" : "#{call}(#{arg_str})"
@@ -182,10 +179,6 @@ module Moxml
182
179
 
183
180
  if receiver
184
181
  rec_str = process(receiver)
185
- # Keep processing if we got a Node back
186
- while rec_str.respond_to?(:type)
187
- rec_str = process(rec_str)
188
- end
189
182
  call = brackets ? "#{rec_str}#{call}" : "#{rec_str}.#{call}"
190
183
  end
191
184
 
@@ -95,15 +95,7 @@ module Moxml
95
95
  # @param [Class] klass
96
96
  # @return [Moxml::XPath::Ruby::Node]
97
97
  def is_a?(klass)
98
- # If klass is already a Node (e.g., a const node), use it directly
99
- # Otherwise wrap it in a lit node
100
- klass_node = if klass.respond_to?(:type)
101
- klass
102
- else
103
- Node.new(:lit, [klass.to_s])
104
- end
105
-
106
- Node.new(:send, [self, "is_a?", klass_node])
98
+ Node.new(:send, [self, "is_a?", klass])
107
99
  end
108
100
 
109
101
  # Wraps the current node in a block.
data/lib/moxml/xpath.rb CHANGED
@@ -1,16 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Moxml
4
- # XPath 1.0 implementation for Moxml
5
- #
6
- # This module provides a complete XPath 1.0 engine for querying XML
7
- # documents, particularly for the Ox adapter which has limited native
8
- # XPath support.
9
- #
10
- # @example Basic usage
11
- # engine = Moxml::XPath::Engine.new(document)
12
- # results = engine.evaluate("//book[@id='123']")
13
- #
14
4
  module XPath
15
5
  autoload :Engine, "moxml/xpath/engine"
16
6
  autoload :Context, "moxml/xpath/context"
@@ -20,15 +10,17 @@ module Moxml
20
10
  autoload :Parser, "moxml/xpath/parser"
21
11
  autoload :Compiler, "moxml/xpath/compiler"
22
12
 
23
- # Require errors directly so classes are immediately available
24
- require_relative "xpath/errors"
13
+ autoload :Error, "moxml/xpath/errors"
14
+ autoload :SyntaxError, "moxml/xpath/errors"
15
+ autoload :EvaluationError, "moxml/xpath/errors"
16
+ autoload :FunctionError, "moxml/xpath/errors"
17
+ autoload :NodeTypeError, "moxml/xpath/errors"
18
+ autoload :InvalidContextError, "moxml/xpath/errors"
25
19
 
26
- # AST nodes for expression representation
27
20
  module AST
28
21
  autoload :Node, "moxml/xpath/ast/node"
29
22
  end
30
23
 
31
- # Ruby AST generation for compiling XPath
32
24
  module Ruby
33
25
  autoload :Node, "moxml/xpath/ruby/node"
34
26
  autoload :Generator, "moxml/xpath/ruby/generator"
data/lib/moxml.rb CHANGED
@@ -8,13 +8,30 @@ module Moxml
8
8
  context
9
9
  end
10
10
 
11
+ def parse(xml, adapter: nil, **options)
12
+ Context.new(adapter).parse(xml, options)
13
+ end
14
+
11
15
  def configure
12
16
  yield Config.default if block_given?
13
17
  end
14
18
 
15
19
  def with_config(adapter_name = nil, strict_parsing = nil,
16
20
  default_encoding = nil)
17
- original_config = Config.default.dup
21
+ original = Config.default
22
+ saved_values = {
23
+ adapter: original.adapter_name,
24
+ strict_parsing: original.strict_parsing,
25
+ default_encoding: original.default_encoding,
26
+ default_indent: original.default_indent,
27
+ default_line_ending: original.default_line_ending,
28
+ entity_load_mode: original.entity_load_mode,
29
+ entity_encoding: original.entity_encoding,
30
+ restore_entities: original.restore_entities,
31
+ namespace_validation_mode: original.namespace_validation_mode,
32
+ entity_restoration_mode: original.entity_restoration_mode,
33
+ preload_entity_sets: original.preload_entity_sets.dup,
34
+ }
18
35
 
19
36
  configure do |config|
20
37
  config.adapter = adapter_name unless adapter_name.nil?
@@ -23,15 +40,22 @@ module Moxml
23
40
  end
24
41
 
25
42
  yield if block_given?
26
-
27
- # restore the original config
43
+ ensure
28
44
  configure do |config|
29
- config.adapter = original_config.adapter_name
30
- config.strict_parsing = original_config.strict_parsing
31
- config.default_encoding = original_config.default_encoding
45
+ config.adapter = saved_values[:adapter]
46
+ config.strict_parsing = saved_values[:strict_parsing]
47
+ config.default_encoding = saved_values[:default_encoding]
48
+ config.default_indent = saved_values[:default_indent]
49
+ config.default_line_ending = saved_values[:default_line_ending]
50
+ config.entity_load_mode = saved_values[:entity_load_mode]
51
+ config.entity_encoding = saved_values[:entity_encoding]
52
+ config.restore_entities = saved_values[:restore_entities]
53
+ config.namespace_validation_mode = saved_values[:namespace_validation_mode]
54
+ config.entity_restoration_mode = saved_values[:entity_restoration_mode]
55
+ config.preload_entity_sets = saved_values[:preload_entity_sets]
32
56
  end
33
- original_config = nil
34
57
  end
58
+
35
59
  def preprocess_entities(xml)
36
60
  Adapter::Base.preprocess_entities(xml)
37
61
  end
@@ -40,17 +64,40 @@ module Moxml
40
64
  Adapter::Base.restore_entities(text)
41
65
  end
42
66
  end
43
- end
44
67
 
45
- require_relative "moxml/version"
46
- require_relative "moxml/document"
47
- require_relative "moxml/document_builder"
48
- require_relative "moxml/error"
49
- require_relative "moxml/builder"
50
- require_relative "moxml/config"
51
- require_relative "moxml/context"
52
- require_relative "moxml/entity_registry"
53
- require_relative "moxml/native_attachment"
54
- require_relative "moxml/adapter"
55
- require_relative "moxml/xpath"
56
- require_relative "moxml/sax"
68
+ autoload :VERSION, "moxml/version"
69
+ autoload :Config, "moxml/config"
70
+ autoload :Context, "moxml/context"
71
+ autoload :Node, "moxml/node"
72
+ autoload :NodeSet, "moxml/node_set"
73
+ autoload :Document, "moxml/document"
74
+ autoload :Element, "moxml/element"
75
+ autoload :Text, "moxml/text"
76
+ autoload :Cdata, "moxml/cdata"
77
+ autoload :Comment, "moxml/comment"
78
+ autoload :Attribute, "moxml/attribute"
79
+ autoload :ProcessingInstruction, "moxml/processing_instruction"
80
+ autoload :Declaration, "moxml/declaration"
81
+ autoload :Namespace, "moxml/namespace"
82
+ autoload :Doctype, "moxml/doctype"
83
+ autoload :EntityReference, "moxml/entity_reference"
84
+ autoload :DocumentBuilder, "moxml/document_builder"
85
+ autoload :Builder, "moxml/builder"
86
+ autoload :EntityRegistry, "moxml/entity_registry"
87
+ autoload :NativeAttachment, "moxml/native_attachment"
88
+ autoload :XmlUtils, "moxml/xml_utils"
89
+ autoload :Adapter, "moxml/adapter"
90
+ autoload :XPath, "moxml/xpath"
91
+ autoload :SAX, "moxml/sax"
92
+
93
+ # Error hierarchy — each subclass autoloads from the same file
94
+ autoload :Error, "moxml/error"
95
+ autoload :NotImplementedError, "moxml/error"
96
+ autoload :ValidationError, "moxml/error"
97
+ autoload :ParseError, "moxml/error"
98
+ autoload :DocumentStructureError, "moxml/error"
99
+ autoload :NamespaceError, "moxml/error"
100
+ autoload :EntityDataError, "moxml/error"
101
+ autoload :XPathError, "moxml/error"
102
+ autoload :AdapterError, "moxml/error"
103
+ end
@@ -27,4 +27,20 @@ RSpec.describe Moxml::Attribute do
27
27
  expect(attr.to_s).to match(/\w+="\w+"/)
28
28
  end
29
29
  end
30
+
31
+ describe "#element" do
32
+ it "returns wrapped Element" do
33
+ attr = element.attributes.find { |a| a.name == "id" }
34
+ parent = attr.element
35
+ expect(parent).to be_a(Moxml::Element)
36
+ expect(parent.name).to eq("root")
37
+ end
38
+ end
39
+
40
+ describe "#content" do
41
+ it "returns attribute value" do
42
+ attr = element.attributes.find { |a| a.name == "id" }
43
+ expect(attr.content).to eq("123")
44
+ end
45
+ end
30
46
  end