moxml 0.1.19 → 0.1.20

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76508cc0d2699469ade87f48e38ea73289abc4f95cb7a313f6d71548477248f3
4
- data.tar.gz: 1752cb433953a869cedd1d88fc565c53f77bd08201c5977247150e1e7b75a386
3
+ metadata.gz: 0633c51783a25d02190769345f0c6a4af79c29665b180e5b46fc28f0b6eeee31
4
+ data.tar.gz: b3d9d706cc185c2d045ed8d3c9a62bb884196cc2cc520cd51fd17cecb07cd897
5
5
  SHA512:
6
- metadata.gz: a4219577f2e00a9e4f00f1ce1bbaf4f03d6c841dfe8adb9983162a6389b1e276725984dc010f451cac55fbd8049a81451d641fc073fc1cf8417f90ee6e50cdf5
7
- data.tar.gz: 44f3e08b174e2689fed3a60ee3ef0b805fdc14cbeeb905ebde15e5241be3e3f168441dc95afe99dd244fc1047c581fce912ec3e061299e4e101f63329f00def9
6
+ metadata.gz: 6626a13b9dda295113caa1e1d99085afd20776b1d3d4f156bbdf63efcefccef59cd1ea37d14439aa7487f3f617ed65ac1003ec775b22e77614a4b0082c832307
7
+ data.tar.gz: 68b26d50fa35b206835f633dfd7b26f6c389fc3507a524a84784929b089e0c699d1361af8838d86e5fcec0e8031f81ca82e2e799b544737cc1616a3da5d15707
data/.rubocop_todo.yml CHANGED
@@ -1,11 +1,65 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2026-04-23 07:48:23 UTC using RuboCop version 1.86.0.
3
+ # on 2026-05-03 12:53:32 UTC using RuboCop version 1.86.0.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
+ # Offense count: 5
10
+ # This cop supports safe autocorrection (--autocorrect).
11
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
12
+ # SupportedStyles: with_first_argument, with_fixed_indentation
13
+ Layout/ArgumentAlignment:
14
+ Exclude:
15
+ - 'lib/moxml/adapter/base.rb'
16
+ - 'lib/moxml/adapter/libxml.rb'
17
+ - 'lib/moxml/builder.rb'
18
+
19
+ # Offense count: 2
20
+ # This cop supports safe autocorrection (--autocorrect).
21
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
22
+ # SupportedStyles: with_first_element, with_fixed_indentation
23
+ Layout/ArrayAlignment:
24
+ Exclude:
25
+ - 'lib/moxml/xpath/compiler.rb'
26
+
27
+ # Offense count: 9
28
+ # This cop supports safe autocorrection (--autocorrect).
29
+ # Configuration parameters: EnforcedStyleAlignWith.
30
+ # SupportedStylesAlignWith: either, start_of_block, start_of_line
31
+ Layout/BlockAlignment:
32
+ Exclude:
33
+ - 'lib/moxml/adapter/libxml.rb'
34
+ - 'lib/moxml/adapter/ox.rb'
35
+ - 'spec/integration/shared_examples/edge_cases.rb'
36
+ - 'spec/integration/shared_examples/high_level/document_builder_behavior.rb'
37
+ - 'spec/integration/shared_examples/node_wrappers/entity_reference_behavior.rb'
38
+ - 'spec/moxml/xpath/functions/node_functions_spec.rb'
39
+
40
+ # Offense count: 9
41
+ # This cop supports safe autocorrection (--autocorrect).
42
+ Layout/BlockEndNewline:
43
+ Exclude:
44
+ - 'lib/moxml/adapter/libxml.rb'
45
+ - 'lib/moxml/adapter/ox.rb'
46
+ - 'spec/integration/shared_examples/edge_cases.rb'
47
+ - 'spec/integration/shared_examples/high_level/document_builder_behavior.rb'
48
+ - 'spec/integration/shared_examples/node_wrappers/entity_reference_behavior.rb'
49
+ - 'spec/moxml/xpath/functions/node_functions_spec.rb'
50
+
51
+ # Offense count: 2
52
+ # This cop supports safe autocorrection (--autocorrect).
53
+ Layout/ClosingParenthesisIndentation:
54
+ Exclude:
55
+ - 'lib/moxml/adapter/oga.rb'
56
+
57
+ # Offense count: 1
58
+ # This cop supports safe autocorrection (--autocorrect).
59
+ Layout/ElseAlignment:
60
+ Exclude:
61
+ - 'lib/moxml/adapter/base.rb'
62
+
9
63
  # Offense count: 4
10
64
  # This cop supports safe autocorrection (--autocorrect).
11
65
  Layout/EmptyLineAfterGuardClause:
@@ -27,13 +81,67 @@ Layout/EmptyLines:
27
81
  Exclude:
28
82
  - 'lib/moxml/adapter/ox.rb'
29
83
 
30
- # Offense count: 330
84
+ # Offense count: 2
85
+ # This cop supports safe autocorrection (--autocorrect).
86
+ Layout/EmptyLinesAroundMethodBody:
87
+ Exclude:
88
+ - 'lib/moxml/adapter/ox.rb'
89
+
90
+ # Offense count: 1
91
+ # This cop supports safe autocorrection (--autocorrect).
92
+ # Configuration parameters: EnforcedStyleAlignWith.
93
+ # SupportedStylesAlignWith: keyword, variable, start_of_line
94
+ Layout/EndAlignment:
95
+ Exclude:
96
+ - 'lib/moxml/adapter/base.rb'
97
+
98
+ # Offense count: 2
99
+ # This cop supports safe autocorrection (--autocorrect).
100
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
101
+ # SupportedStyles: consistent, consistent_relative_to_receiver, special_for_inner_method_call, special_for_inner_method_call_in_parentheses
102
+ Layout/FirstArgumentIndentation:
103
+ Exclude:
104
+ - 'lib/moxml/adapter/oga.rb'
105
+
106
+ # Offense count: 2
107
+ # This cop supports safe autocorrection (--autocorrect).
108
+ # Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.
109
+ # SupportedHashRocketStyles: key, separator, table
110
+ # SupportedColonStyles: key, separator, table
111
+ # SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit
112
+ Layout/HashAlignment:
113
+ Exclude:
114
+ - 'lib/moxml/builder.rb'
115
+
116
+ # Offense count: 20
117
+ # This cop supports safe autocorrection (--autocorrect).
118
+ # Configuration parameters: Width, EnforcedStyleAlignWith, AllowedPatterns.
119
+ # SupportedStylesAlignWith: start_of_line, relative_to_receiver
120
+ Layout/IndentationWidth:
121
+ Exclude:
122
+ - 'lib/moxml/adapter/base.rb'
123
+ - 'lib/moxml/adapter/libxml.rb'
124
+ - 'lib/moxml/adapter/ox.rb'
125
+ - 'spec/integration/shared_examples/edge_cases.rb'
126
+ - 'spec/integration/shared_examples/high_level/document_builder_behavior.rb'
127
+ - 'spec/integration/shared_examples/node_wrappers/entity_reference_behavior.rb'
128
+ - 'spec/moxml/xpath/functions/node_functions_spec.rb'
129
+
130
+ # Offense count: 344
31
131
  # This cop supports safe autocorrection (--autocorrect).
32
132
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
33
133
  # URISchemes: http, https
34
134
  Layout/LineLength:
35
135
  Enabled: false
36
136
 
137
+ # Offense count: 2
138
+ # This cop supports safe autocorrection (--autocorrect).
139
+ # Configuration parameters: EnforcedStyle.
140
+ # SupportedStyles: symmetrical, new_line, same_line
141
+ Layout/MultilineMethodCallBraceLayout:
142
+ Exclude:
143
+ - 'lib/moxml/adapter/oga.rb'
144
+
37
145
  # Offense count: 1
38
146
  # This cop supports safe autocorrection (--autocorrect).
39
147
  # Configuration parameters: EnforcedStyle, IndentationWidth.
@@ -42,6 +150,17 @@ Layout/MultilineOperationIndentation:
42
150
  Exclude:
43
151
  - 'lib/moxml/adapter/ox.rb'
44
152
 
153
+ # Offense count: 10
154
+ # This cop supports safe autocorrection (--autocorrect).
155
+ # Configuration parameters: AllowInHeredoc.
156
+ Layout/TrailingWhitespace:
157
+ Exclude:
158
+ - 'lib/moxml/adapter/base.rb'
159
+ - 'lib/moxml/adapter/libxml.rb'
160
+ - 'lib/moxml/adapter/ox.rb'
161
+ - 'lib/moxml/builder.rb'
162
+ - 'lib/moxml/xpath/compiler.rb'
163
+
45
164
  # Offense count: 7
46
165
  # Configuration parameters: AllowedMethods.
47
166
  # AllowedMethods: enums
@@ -61,11 +180,10 @@ Lint/DuplicateBranch:
61
180
  - 'lib/moxml/document.rb'
62
181
  - 'lib/moxml/entity_registry.rb'
63
182
 
64
- # Offense count: 5
183
+ # Offense count: 4
65
184
  Lint/DuplicateMethods:
66
185
  Exclude:
67
186
  - 'lib/moxml/config.rb'
68
- - 'lib/moxml/element.rb'
69
187
  - 'lib/moxml/node.rb'
70
188
 
71
189
  # Offense count: 4
@@ -91,7 +209,7 @@ Lint/EmptyWhen:
91
209
  # Offense count: 3
92
210
  Lint/HashCompareByIdentity:
93
211
  Exclude:
94
- - 'lib/moxml/native_attachment.rb'
212
+ - 'lib/moxml/native_attachment/native.rb'
95
213
 
96
214
  # Offense count: 1
97
215
  Lint/IneffectiveAccessModifier:
@@ -127,12 +245,12 @@ Metrics/BlockLength:
127
245
  Metrics/BlockNesting:
128
246
  Max: 4
129
247
 
130
- # Offense count: 76
248
+ # Offense count: 75
131
249
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
132
250
  Metrics/CyclomaticComplexity:
133
251
  Enabled: false
134
252
 
135
- # Offense count: 186
253
+ # Offense count: 188
136
254
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
137
255
  Metrics/MethodLength:
138
256
  Max: 110
@@ -180,6 +298,12 @@ Naming/VariableNumber:
180
298
  - 'spec/moxml/allocation_guard_spec.rb'
181
299
  - 'spec/support/allocation_helper.rb'
182
300
 
301
+ # Offense count: 1
302
+ # Configuration parameters: MinSize.
303
+ Performance/CollectionLiteralInLoop:
304
+ Exclude:
305
+ - 'lib/moxml/xpath/compiler.rb'
306
+
183
307
  # Offense count: 5
184
308
  RSpec/BeforeAfterAll:
185
309
  Exclude:
@@ -205,12 +329,12 @@ RSpec/ContextWording:
205
329
  - 'spec/moxml/xpath/parser_spec.rb'
206
330
  - 'spec/performance/benchmark_spec.rb'
207
331
 
208
- # Offense count: 24
332
+ # Offense count: 25
209
333
  # Configuration parameters: IgnoredMetadata.
210
334
  RSpec/DescribeClass:
211
335
  Enabled: false
212
336
 
213
- # Offense count: 295
337
+ # Offense count: 328
214
338
  # Configuration parameters: CountAsOne.
215
339
  RSpec/ExampleLength:
216
340
  Max: 64
@@ -240,13 +364,13 @@ RSpec/LeakyConstantDeclaration:
240
364
  - 'spec/moxml/declaration_preservation_spec.rb'
241
365
  - 'spec/moxml/sax_spec.rb'
242
366
 
243
- # Offense count: 2
367
+ # Offense count: 4
244
368
  # Configuration parameters: .
245
369
  # SupportedStyles: have_received, receive
246
370
  RSpec/MessageSpies:
247
371
  EnforcedStyle: receive
248
372
 
249
- # Offense count: 390
373
+ # Offense count: 414
250
374
  RSpec/MultipleExpectations:
251
375
  Max: 10
252
376
 
@@ -306,6 +430,22 @@ Security/Eval:
306
430
  Exclude:
307
431
  - 'spec/moxml/xpath/ruby/generator_spec.rb'
308
432
 
433
+ # Offense count: 12
434
+ # This cop supports safe autocorrection (--autocorrect).
435
+ # Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, AllowBracesOnProceduralOneLiners, BracesRequiredMethods.
436
+ # SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces
437
+ # ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object
438
+ # FunctionalMethods: let, let!, subject, watch
439
+ # AllowedMethods: lambda, proc, it
440
+ Style/BlockDelimiters:
441
+ Exclude:
442
+ - 'lib/moxml/adapter/libxml.rb'
443
+ - 'lib/moxml/adapter/ox.rb'
444
+ - 'spec/integration/shared_examples/edge_cases.rb'
445
+ - 'spec/integration/shared_examples/high_level/document_builder_behavior.rb'
446
+ - 'spec/integration/shared_examples/node_wrappers/entity_reference_behavior.rb'
447
+ - 'spec/moxml/xpath/functions/node_functions_spec.rb'
448
+
309
449
  # Offense count: 1
310
450
  Style/DocumentDynamicEvalDefinition:
311
451
  Exclude:
@@ -329,6 +469,18 @@ Style/MissingRespondToMissing:
329
469
  Exclude:
330
470
  - 'lib/moxml/xpath/ruby/node.rb'
331
471
 
472
+ # Offense count: 1
473
+ # This cop supports safe autocorrection (--autocorrect).
474
+ Style/MultilineIfModifier:
475
+ Exclude:
476
+ - 'lib/moxml/builder.rb'
477
+
478
+ # Offense count: 1
479
+ # This cop supports safe autocorrection (--autocorrect).
480
+ Style/MultilineTernaryOperator:
481
+ Exclude:
482
+ - 'lib/moxml/adapter/base.rb'
483
+
332
484
  # Offense count: 1
333
485
  # This cop supports safe autocorrection (--autocorrect).
334
486
  # Configuration parameters: AllowMethodComparison, ComparisonsThreshold.
@@ -365,7 +517,25 @@ Style/RedundantConstantBase:
365
517
  - 'spec/moxml/adapter/headed_ox_spec.rb'
366
518
 
367
519
  # Offense count: 1
520
+ # This cop supports safe autocorrection (--autocorrect).
521
+ Style/RedundantParentheses:
522
+ Exclude:
523
+ - 'lib/moxml/xpath/compiler.rb'
524
+
525
+ # Offense count: 8
368
526
  # This cop supports unsafe autocorrection (--autocorrect-all).
369
527
  Style/SelectByKind:
370
528
  Exclude:
529
+ - 'spec/integration/shared_examples/edge_cases.rb'
530
+ - 'spec/integration/shared_examples/entity_reference_whitespace.rb'
371
531
  - 'spec/integration/shared_examples/node_wrappers/entity_reference_behavior.rb'
532
+ - 'spec/integration/shared_examples/node_wrappers/node_set_behavior.rb'
533
+ - 'spec/moxml/xpath/functions/node_functions_spec.rb'
534
+
535
+ # Offense count: 2
536
+ # This cop supports safe autocorrection (--autocorrect).
537
+ # Configuration parameters: EnforcedStyle, MinSize.
538
+ # SupportedStyles: percent, brackets
539
+ Style/SymbolArray:
540
+ Exclude:
541
+ - 'lib/moxml/xpath/compiler.rb'
@@ -390,6 +390,10 @@ Text nodes contain character data.
390
390
  | `#text`
391
391
  | Alias for #content
392
392
  | ✅ Yes
393
+
394
+ | `#to_s`
395
+ | Returns the text content (same as `#content`)
396
+ | ✅ Yes
393
397
  |===
394
398
 
395
399
  ==== Identity Methods
@@ -35,7 +35,12 @@ module Moxml
35
35
  # not valid UTF-8, fall back to encoding as UTF-8 with
36
36
  # replacement to avoid raising on gsub.
37
37
  dup = xml.dup.force_encoding("UTF-8")
38
- dup.valid_encoding? ? dup : xml.dup.encode("UTF-8", "ASCII-8BIT", invalid: :replace, undef: :replace)
38
+ if dup.valid_encoding?
39
+ dup
40
+ else
41
+ xml.dup.encode("UTF-8",
42
+ "ASCII-8BIT", invalid: :replace, undef: :replace)
43
+ end
39
44
  elsif xml.encoding == Encoding::UTF_8
40
45
  xml
41
46
  else
@@ -3,8 +3,21 @@
3
3
  module Moxml
4
4
  module Adapter
5
5
  module CustomizedOx
6
- # Ox uses Strings, but a string cannot have a parent reference
7
- class Text < ::Ox::Node; end
6
+ # Ox uses Strings for text content, but a String cannot carry a @parent
7
+ # back-reference. We subclass ::Ox::Node so a Text wrapper can hold one.
8
+ #
9
+ # ::Ox::Node subclasses that are neither ::Ox::Element nor ::Ox::Document
10
+ # are unknown to Ox.dump's standard XML emitter, so they fall through to
11
+ # Ox's generic object-marshalling format. The serializer in
12
+ # Moxml::Adapter::Ox#serialize special-cases this class to emit the value
13
+ # with proper XML escaping. The #to_s override ensures string
14
+ # interpolation (`"#{text}"`) produces the text content rather than the
15
+ # default Object representation.
16
+ class Text < ::Ox::Node
17
+ def to_s
18
+ value.to_s
19
+ end
20
+ end
8
21
  end
9
22
  end
10
23
  end
@@ -68,6 +68,7 @@ module Moxml
68
68
  next if child.is_a?(::REXML::Text) &&
69
69
  child.to_s.strip.empty? &&
70
70
  !(child.next_sibling.nil? && child.previous_sibling.nil?)
71
+
71
72
  write(child, output)
72
73
  end
73
74
  when :eref
@@ -565,7 +565,8 @@ module Moxml
565
565
  # Set as root element
566
566
  native_elem.root = native_child
567
567
  # Flag for actual_native to refresh the wrapper's native reference
568
- attachments.set(native_elem, :_pending_root_refresh, native_child.object_id)
568
+ attachments.set(native_elem, :_pending_root_refresh,
569
+ native_child.object_id)
569
570
  elsif native_elem.root
570
571
  # Document has root, add to it instead
571
572
  import_and_add(native_elem.doc, native_elem.root, native_child)
@@ -594,6 +595,7 @@ module Moxml
594
595
  def lookup_entity_refs(doc, element)
595
596
  pairs = attachments.get(doc, :_entity_ref_pairs)
596
597
  return nil unless pairs
598
+
597
599
  pair = pairs.find { |elem, _| elem == element }
598
600
  pair&.last
599
601
  end
@@ -614,6 +616,7 @@ module Moxml
614
616
  def lookup_child_sequence(doc, element)
615
617
  pairs = attachments.get(doc, :_child_seq_pairs)
616
618
  return nil unless pairs
619
+
617
620
  pair = pairs.find { |elem, _| elem == element }
618
621
  pair&.last
619
622
  end
@@ -1481,7 +1484,9 @@ module Moxml
1481
1484
  # Interleave native children with entity refs using tracked sequence
1482
1485
  native_children = []
1483
1486
  if elem.children?
1484
- elem.each_child { |c| native_children << c unless c.text? && c.content.to_s.strip.empty? }
1487
+ elem.each_child do |c|
1488
+ native_children << c unless c.text? && c.content.to_s.strip.empty?
1489
+ end
1485
1490
  end
1486
1491
 
1487
1492
  eref_idx = 0
@@ -470,7 +470,9 @@ module Moxml
470
470
  if node.is_a?(::Oga::XML::Document)
471
471
  # Check if we should include declaration
472
472
  # Priority: explicit option > existence of xml_declaration (native or attachment)
473
- effective_xml_declaration = node.xml_declaration || attachments.get(node, :xml_declaration)
473
+ effective_xml_declaration = node.xml_declaration || attachments.get(
474
+ node, :xml_declaration
475
+ )
474
476
  should_include_decl = if options.key?(:no_declaration)
475
477
  !options[:no_declaration]
476
478
  elsif options.key?(:declaration)
@@ -519,7 +521,9 @@ module Moxml
519
521
 
520
522
  # Default: use XmlGenerator
521
523
  # But first check if we need to handle declaration specially
522
- effective_xml_declaration = node.is_a?(::Oga::XML::Document) && (node.xml_declaration || attachments.get(node, :xml_declaration))
524
+ effective_xml_declaration = node.is_a?(::Oga::XML::Document) && (node.xml_declaration || attachments.get(
525
+ node, :xml_declaration
526
+ ))
523
527
  if node.is_a?(::Oga::XML::Document) && effective_xml_declaration
524
528
  # Document has declaration - use custom handling to avoid duplicates
525
529
  output = []
@@ -74,8 +74,7 @@ module Moxml
74
74
  end
75
75
 
76
76
  def create_native_element(name, _owner_doc = nil)
77
- element = ::Ox::Element.new(name)
78
- element
77
+ ::Ox::Element.new(name)
79
78
  end
80
79
 
81
80
  def create_native_text(content, _owner_doc = nil)
@@ -622,6 +621,12 @@ module Moxml
622
621
  end
623
622
 
624
623
  def serialize(node, options = {})
624
+ # CustomizedOx::Text subclasses ::Ox::Node so it can carry a @parent
625
+ # back-reference, but that makes it unknown to Ox.dump's XML emitter,
626
+ # which then falls back to generic object marshalling. Short-circuit
627
+ # here with proper XML escaping.
628
+ return escape_xml_text(node.value) if node.is_a?(CustomizedOx::Text)
629
+
625
630
  needs_custom = needs_custom_serialize?(node)
626
631
 
627
632
  unless needs_custom
@@ -643,7 +648,7 @@ module Moxml
643
648
  return true if attachments.get(node, :has_entity_refs)
644
649
  return true if attachments.get(node, :has_cdata_end_markers)
645
650
  return false if attachments.key?(node, :has_entity_refs) &&
646
- attachments.key?(node, :has_cdata_end_markers)
651
+ attachments.key?(node, :has_cdata_end_markers)
647
652
  end
648
653
 
649
654
  # Only scan tree on first call — short-circuit on first hit
@@ -694,9 +699,8 @@ module Moxml
694
699
  encoding: options[:encoding],
695
700
  no_empty: options[:expand_empty],
696
701
  }
697
- result = output + ::Ox.dump(node, ox_options)
702
+ output + ::Ox.dump(node, ox_options)
698
703
  # Fix CDATA ]]> end markers that Ox doesn't escape
699
- result
700
704
  end
701
705
 
702
706
  def tree_has_entity_references?(node)
@@ -721,9 +725,13 @@ module Moxml
721
725
  when ::Ox::CData
722
726
  node.value&.include?("]]>") || false
723
727
  when ::Ox::Element
724
- node.nodes&.any? { |child| tree_has_cdata_end_markers?(child) } || false
728
+ node.nodes&.any? do |child|
729
+ tree_has_cdata_end_markers?(child)
730
+ end || false
725
731
  when ::Ox::Document
726
- node.nodes&.any? { |child| tree_has_cdata_end_markers?(child) } || false
732
+ node.nodes&.any? do |child|
733
+ tree_has_cdata_end_markers?(child)
734
+ end || false
727
735
  else
728
736
  false
729
737
  end
@@ -814,7 +822,6 @@ module Moxml
814
822
  end
815
823
  end
816
824
 
817
-
818
825
  # Translate a subset of XPath to Ox locate() syntax
819
826
  # Supports: //element, /path/to/element, .//element, element[@attr]
820
827
  # Note: Ox locate() doesn't support namespace prefixes in the path
data/lib/moxml/builder.rb CHANGED
@@ -30,12 +30,14 @@ module Moxml
30
30
  # and a valid XML tag name (XSD/RelaxNG).
31
31
  def element(name_or_attrs = nil, attributes = {}, &block)
32
32
  if name_or_attrs.is_a?(Hash)
33
- return create_element_node("element", name_or_attrs, block: block, eval_block: false)
33
+ return create_element_node("element", name_or_attrs, block: block,
34
+ eval_block: false)
34
35
  end
35
36
 
36
37
  raise ArgumentError, "element requires a tag name" if name_or_attrs.nil?
37
38
 
38
- create_element_node(name_or_attrs, attributes, block: block, eval_block: true)
39
+ create_element_node(name_or_attrs, attributes, block: block,
40
+ eval_block: true)
39
41
  end
40
42
 
41
43
  def text(content)
@@ -101,10 +103,14 @@ module Moxml
101
103
  text_content = args.first.is_a?(String) ? args.shift : nil
102
104
  attrs = args.first.is_a?(Hash) ? args.shift : {}
103
105
 
104
- raise ArgumentError, "unexpected arguments for #{method_name}: #{args.inspect}" unless args.empty?
106
+ unless args.empty?
107
+ raise ArgumentError,
108
+ "unexpected arguments for #{method_name}: #{args.inspect}"
109
+ end
105
110
 
106
111
  if text_content && block
107
- raise ArgumentError, "#{method_name}: cannot combine text content with a block"
112
+ raise ArgumentError,
113
+ "#{method_name}: cannot combine text content with a block"
108
114
  end
109
115
 
110
116
  # Strip trailing underscore to allow reserved Ruby method names as tags
@@ -126,7 +132,8 @@ module Moxml
126
132
  # Single method for all element creation.
127
133
  # eval_block: true → instance_eval (build DSL context)
128
134
  # eval_block: false → yield (preserves caller's self)
129
- def create_element_node(tag_name, attrs = {}, text_content: nil, block: nil, eval_block: true)
135
+ def create_element_node(tag_name, attrs = {}, text_content: nil,
136
+ block: nil, eval_block: true)
130
137
  el = @document.create_element(tag_name)
131
138
 
132
139
  attrs.each do |key, value|
@@ -156,6 +156,7 @@ module Moxml
156
156
  def primary_name_for_codepoint(codepoint)
157
157
  names = @by_codepoint[codepoint]
158
158
  return nil unless names&.any?
159
+
159
160
  # Prefer lowercase names (e.g., "amp" over "AMP") for XML compatibility
160
161
  names.find { |n| n == n.downcase } || names.first
161
162
  end
data/lib/moxml/text.rb CHANGED
@@ -15,5 +15,9 @@ module Moxml
15
15
  def content=(text)
16
16
  adapter.set_text_content(@native, normalize_xml_value(text))
17
17
  end
18
+
19
+ def to_s
20
+ content
21
+ end
18
22
  end
19
23
  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.19"
4
+ VERSION = "0.1.20"
5
5
  end
@@ -1758,7 +1758,8 @@ module Moxml
1758
1758
  current = visit.pop
1759
1759
 
1760
1760
  # Function name is stored in :value field, not children
1761
- if (current.type == :call || current.type == :function) && current.value == name
1761
+ if %i[call
1762
+ function].include?(current.type) && current.value == name
1762
1763
  return true
1763
1764
  end
1764
1765
 
@@ -170,7 +170,9 @@ RSpec.shared_examples "Moxml Edge Cases" do
170
170
  describe "whitespace text node preservation" do
171
171
  # Ox/HeadedOx do not generate whitespace-only text nodes in their parser,
172
172
  # so these tests only apply to adapters that do (Nokogiri, OGA, REXML, LibXML)
173
- let(:preserves_ws) { !%i[ox headed_ox].include?(context.config.adapter_name) }
173
+ let(:preserves_ws) do
174
+ !%i[ox headed_ox].include?(context.config.adapter_name)
175
+ end
174
176
 
175
177
  it "preserves whitespace-only text nodes between sibling elements" do
176
178
  unless preserves_ws
@@ -196,7 +198,7 @@ RSpec.shared_examples "Moxml Edge Cases" do
196
198
  expect(ws_nodes).not_to be_empty
197
199
 
198
200
  # Element children should still be accessible
199
- elements = children.select { |c| c.is_a?(Moxml::Element) }
201
+ elements = children.grep(Moxml::Element)
200
202
  expect(elements.map(&:name)).to eq(%w[a b c])
201
203
  end
202
204
 
@@ -116,7 +116,7 @@ RSpec.shared_examples "Entity Reference Whitespace Preservation" do
116
116
  children = doc.root.children
117
117
 
118
118
  # Whitespace text nodes between elements are preserved
119
- elements = children.select { |c| c.is_a?(Moxml::Element) }
119
+ elements = children.grep(Moxml::Element)
120
120
  expect(elements.length).to eq(2)
121
121
  expect(elements.map(&:name)).to eq(%w[child1 child2])
122
122
  end
@@ -38,7 +38,9 @@ RSpec.shared_examples "Moxml::DocumentBuilder" do
38
38
  expect(non_ws_children[1]).to be_a(Moxml::Element)
39
39
  expect(non_ws_children[1].name).to eq("child")
40
40
  expect(non_ws_children[1]["id"]).to eq("1")
41
- expect(non_ws_children[1].children.find { |c| c.is_a?(Moxml::Cdata) }).to be_a(Moxml::Cdata)
41
+ expect(non_ws_children[1].children.find do |c|
42
+ c.is_a?(Moxml::Cdata)
43
+ end).to be_a(Moxml::Cdata)
42
44
  expect(non_ws_children[2]).to be_a(Moxml::ProcessingInstruction)
43
45
  end
44
46
  end
@@ -129,16 +129,20 @@ RSpec.shared_examples "Moxml::EntityReference" do
129
129
 
130
130
  describe "entity restoration" do
131
131
  it "restores standard XML entities when enabled" do
132
- ctx_restore = Moxml.new(context.config.adapter_name) { |c| c.restore_entities = true }
132
+ ctx_restore = Moxml.new(context.config.adapter_name) do |c|
133
+ c.restore_entities = true
134
+ end
133
135
  doc = ctx_restore.parse("<p>a&amp;b</p>")
134
136
  output = doc.to_xml
135
137
  expect(output).to include("&amp;")
136
138
  end
137
139
 
138
140
  it "does not create entity references when disabled" do
139
- ctx_no_restore = Moxml.new(context.config.adapter_name) { |c| c.restore_entities = false }
141
+ ctx_no_restore = Moxml.new(context.config.adapter_name) do |c|
142
+ c.restore_entities = false
143
+ end
140
144
  doc = ctx_no_restore.parse("<p>text</p>")
141
- refs = doc.root.children.select { |c| c.is_a?(Moxml::EntityReference) }
145
+ refs = doc.root.children.grep(Moxml::EntityReference)
142
146
  expect(refs).to be_empty
143
147
  end
144
148
  end
@@ -197,7 +201,9 @@ RSpec.shared_examples "Moxml::EntityReference" do
197
201
 
198
202
  it "rejects invalid modes" do
199
203
  cfg = Moxml::Config.new(context.config.adapter_name)
200
- expect { cfg.entity_restoration_mode = :bogus }.to raise_error(ArgumentError)
204
+ expect do
205
+ cfg.entity_restoration_mode = :bogus
206
+ end.to raise_error(ArgumentError)
201
207
  end
202
208
 
203
209
  it "restores non-standard entities in lenient mode" do
@@ -42,7 +42,7 @@ RSpec.shared_examples "Moxml::NodeSet" do
42
42
 
43
43
  it "compares nodes" do
44
44
  xpath_results = doc.xpath("//child")
45
- element_children = doc.root.children.select { |c| c.is_a?(Moxml::Element) }
45
+ element_children = doc.root.children.grep(Moxml::Element)
46
46
  expect(xpath_results.map(&:native)).to eq(element_children.map(&:native))
47
47
  end
48
48
  end
@@ -145,7 +145,7 @@ RSpec.describe Moxml::Adapter::HeadedOx do
145
145
  it "returns first matching node" do
146
146
  result = adapter.at_xpath(doc, "//book")
147
147
 
148
- expect(result).to be_a(::Ox::Element)
148
+ expect(result).to be_a(Ox::Element)
149
149
  expect(result.name).to eq("book")
150
150
  end
151
151
 
@@ -19,6 +19,29 @@ RSpec.describe Moxml::Text do
19
19
  text = doc.root.children.first
20
20
  expect(text.to_xml).to eq("plain text")
21
21
  end
22
+
23
+ it "escapes XML special characters" do
24
+ escaped_doc = context.parse("<root>a &lt; b &amp; c</root>")
25
+ text = escaped_doc.root.children.first
26
+ expect(text.to_xml).to eq("a &lt; b &amp; c")
27
+ end
28
+ end
29
+
30
+ describe "#to_s" do
31
+ it "returns text content" do
32
+ text = doc.root.children.first
33
+ expect(text.to_s).to eq("plain text")
34
+ end
35
+
36
+ it "is consistent across adapters" do
37
+ Moxml::Adapter::AVALIABLE_ADAPTERS.each do |adapter_name|
38
+ ctx = Moxml.new(adapter_name)
39
+ d = ctx.parse("<root>hello world</root>")
40
+ text = d.root.children.first
41
+ expect(text.to_s).to eq("hello world"),
42
+ "Text#to_s for #{adapter_name} adapter"
43
+ end
44
+ end
22
45
  end
23
46
 
24
47
  describe "creation" do
@@ -127,7 +127,7 @@ RSpec.describe "XPath Node Functions" do
127
127
  it "inherits language from parent element" do
128
128
  ast = Moxml::XPath::Parser.parse('lang("en")')
129
129
  proc = Moxml::XPath::Compiler.compile_with_cache(ast)
130
- child = doc_with_lang.root.children.select { |c| c.is_a?(Moxml::Element) }.first
130
+ child = doc_with_lang.root.children.grep(Moxml::Element).first
131
131
  result = proc.call(child)
132
132
 
133
133
  expect(result).to be true
@@ -136,7 +136,7 @@ RSpec.describe "XPath Node Functions" do
136
136
  it "uses closest xml:lang attribute" do
137
137
  ast = Moxml::XPath::Parser.parse('lang("fr")')
138
138
  proc = Moxml::XPath::Compiler.compile_with_cache(ast)
139
- elements = doc_with_lang.root.children.select { |c| c.is_a?(Moxml::Element) }
139
+ elements = doc_with_lang.root.children.grep(Moxml::Element)
140
140
  other = elements[1]
141
141
  result = proc.call(other)
142
142
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: moxml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.19
4
+ version: 0.1.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-01 00:00:00.000000000 Z
11
+ date: 2026-05-03 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  Moxml is a unified XML manipulation library that provides a common API