moxml 0.5.0 → 0.5.2
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 +4 -4
- data/lib/moxml/adapter/leptris.rb +33 -6
- data/lib/moxml/adapter/libxml.rb +14 -2
- data/lib/moxml/adapter/ox.rb +41 -21
- data/lib/moxml/adapter/rexml.rb +9 -5
- data/lib/moxml/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 566f12c7bcda29e079d0fd5b496a6becff643b9c15afd328d5f99fb7ef70d35c
|
|
4
|
+
data.tar.gz: a4d7858218db7d38300b963b11d5e40b5aec176d2072a177c4e5448f839b8671
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 45cf69a5c753718d3bcef44580ef2e8e0dcfa536033537602952e8cbeb73e8bb59e68ad365fdc72c6b7422a998c50e623a1f7b894ed5e7da3cd8e129ff40c703
|
|
7
|
+
data.tar.gz: 0fd18d4891bd29ce56ab1174aa494f8dddc251d298067a0290bf84df41f6854d61bdeedf6e9fd813c32669a9a550419865a637633ddd6e558e84ff0d9f6eca2e
|
|
@@ -266,7 +266,13 @@ module Moxml
|
|
|
266
266
|
def split_entity_markers(natives, parent)
|
|
267
267
|
result = []
|
|
268
268
|
natives.each do |child|
|
|
269
|
-
|
|
269
|
+
# FFI Text#content returns a fresh unfrozen BINARY string:
|
|
270
|
+
# retag in place (dup would be a throwaway allocation), but
|
|
271
|
+
# before include? — BINARY.include? with the UTF-8 marker raises
|
|
272
|
+
if child.is_a?(::Leptris::XML::Text)
|
|
273
|
+
content = child.content
|
|
274
|
+
content.force_encoding("UTF-8")
|
|
275
|
+
end
|
|
270
276
|
if content&.include?(Entity::MARKER)
|
|
271
277
|
content.scan(/([^#{Entity::MARKER}]*)(?:#{Entity::MARKER}([\w.:-]+);)?/o) do
|
|
272
278
|
text_part = Regexp.last_match(1)
|
|
@@ -540,6 +546,10 @@ module Moxml
|
|
|
540
546
|
# returns those without name accessors). Both engines resolve
|
|
541
547
|
# expression prefixes against document-declared namespaces.
|
|
542
548
|
NATIVE_XPATH_CACHE = XPath::Cache.new(100)
|
|
549
|
+
# The gate decision is expression-intrinsic; re-walking the
|
|
550
|
+
# cached AST three times per xpath call cost ~23% of repeated
|
|
551
|
+
# selective queries, so the boolean caches beside it.
|
|
552
|
+
NATIVE_GATE_CACHE = XPath::Cache.new(100)
|
|
543
553
|
|
|
544
554
|
def xpath(node, expression, namespaces = {})
|
|
545
555
|
native = native_xpath(node, expression, namespaces)
|
|
@@ -617,11 +627,13 @@ module Moxml
|
|
|
617
627
|
# attribute-node results, or the nokogiri-compat xmlns:
|
|
618
628
|
# reserved prefix convention.
|
|
619
629
|
def native_expression?(expression)
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
630
|
+
NATIVE_GATE_CACHE.get_or_set(expression) do
|
|
631
|
+
ast = XPath::Parser.parse_with_cache(expression)
|
|
632
|
+
next false if ast_contains_type?(ast, :variable)
|
|
633
|
+
next false if uses_xmlns_prefix?(ast)
|
|
623
634
|
|
|
624
|
-
|
|
635
|
+
!selects_attribute_results?(ast)
|
|
636
|
+
end
|
|
625
637
|
rescue XPath::SyntaxError
|
|
626
638
|
false
|
|
627
639
|
end
|
|
@@ -862,8 +874,23 @@ module Moxml
|
|
|
862
874
|
|
|
863
875
|
private
|
|
864
876
|
|
|
877
|
+
# The variable-length \s* gap after \A defeats Onigmo's anchor
|
|
878
|
+
# optimization, forcing a full-buffer scan — 6.5 ms on a 1 MB
|
|
879
|
+
# document without a declaration (the common case). Match a
|
|
880
|
+
# head slice instead; fall back to the full string only when
|
|
881
|
+
# the head is all whitespace (pathological prefixes).
|
|
882
|
+
SOURCE_DECLARATION_RE = /\A\s*<\?xml\b/
|
|
883
|
+
private_constant :SOURCE_DECLARATION_RE
|
|
884
|
+
|
|
865
885
|
def record_source_declaration(native_doc, xml_string)
|
|
866
|
-
|
|
886
|
+
head = xml_string[0, 256]
|
|
887
|
+
had = if head.match?(SOURCE_DECLARATION_RE)
|
|
888
|
+
true
|
|
889
|
+
elsif head.match?(/\A\s*\z/)
|
|
890
|
+
xml_string.match?(SOURCE_DECLARATION_RE)
|
|
891
|
+
else
|
|
892
|
+
false
|
|
893
|
+
end
|
|
867
894
|
attachments.set(native_doc, :had_source_declaration, had)
|
|
868
895
|
end
|
|
869
896
|
|
data/lib/moxml/adapter/libxml.rb
CHANGED
|
@@ -7,6 +7,11 @@ require "libxml"
|
|
|
7
7
|
module Moxml
|
|
8
8
|
module Adapter
|
|
9
9
|
class Libxml < Base
|
|
10
|
+
# DOCTYPE prolog sniffer; see parse for the index-then-match
|
|
11
|
+
# strategy that keeps this off the hot path.
|
|
12
|
+
DOCTYPE_RE = /<!DOCTYPE\s+([^\s>]+)(?:\s+PUBLIC\s+"([^"]+)"\s+"([^"]+)"|\s+SYSTEM\s+"([^"]+)")?\s*>/i
|
|
13
|
+
private_constant :DOCTYPE_RE
|
|
14
|
+
|
|
10
15
|
autoload :EntityRefRegistry, "moxml/adapter/libxml/entity_ref_registry"
|
|
11
16
|
|
|
12
17
|
# Wrapper class to store DOCTYPE information
|
|
@@ -89,8 +94,15 @@ module Moxml
|
|
|
89
94
|
# parameter or XML declaration for byte interpretation.
|
|
90
95
|
xml_string = preprocess_entities(xml_string)
|
|
91
96
|
|
|
92
|
-
# Extract DOCTYPE before parsing
|
|
93
|
-
|
|
97
|
+
# Extract DOCTYPE before parsing. Locate the literal first:
|
|
98
|
+
# a plain memscan is ~2x cheaper than the regex's literal
|
|
99
|
+
# prefix scan on doctype-less documents (the common case),
|
|
100
|
+
# and matching from the found offset keeps captures
|
|
101
|
+
# identical.
|
|
102
|
+
doctype_idx = xml_string.index("<!DOCTYPE")
|
|
103
|
+
doctype_match = if doctype_idx
|
|
104
|
+
xml_string.match(DOCTYPE_RE, doctype_idx)
|
|
105
|
+
end
|
|
94
106
|
|
|
95
107
|
native_doc = begin
|
|
96
108
|
# Handle both string and file inputs
|
data/lib/moxml/adapter/ox.rb
CHANGED
|
@@ -717,28 +717,48 @@ module Moxml
|
|
|
717
717
|
# Only documents/elements can contain entity refs or CDATA issues
|
|
718
718
|
return false unless node.is_a?(::Ox::Document) || node.is_a?(::Ox::Element)
|
|
719
719
|
|
|
720
|
-
#
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
has_cdata =
|
|
736
|
-
if
|
|
737
|
-
attachments.set(
|
|
738
|
-
attachments.set(
|
|
720
|
+
# Element serializes consult the owning document's cached
|
|
721
|
+
# flags — a rescan per call made element to_xml ~30x slower
|
|
722
|
+
# than document to_xml on plain documents.
|
|
723
|
+
scan_root = node
|
|
724
|
+
if node.is_a?(::Ox::Element) && (doc = document(node))
|
|
725
|
+
return true if attachments.get(doc, :has_entity_refs)
|
|
726
|
+
return true if attachments.get(doc, :has_cdata_end_markers)
|
|
727
|
+
return false if attachments.key?(doc, :has_entity_refs) &&
|
|
728
|
+
attachments.key?(doc, :has_cdata_end_markers)
|
|
729
|
+
|
|
730
|
+
scan_root = doc
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
# One merged tree scan instead of two; flags cache on the
|
|
734
|
+
# document either way.
|
|
735
|
+
has_er, has_cdata = tree_scan_custom_needs(scan_root)
|
|
736
|
+
if scan_root.is_a?(::Ox::Document)
|
|
737
|
+
attachments.set(scan_root, :has_entity_refs, has_er)
|
|
738
|
+
attachments.set(scan_root, :has_cdata_end_markers, has_cdata)
|
|
739
|
+
end
|
|
740
|
+
|
|
741
|
+
has_er || has_cdata
|
|
742
|
+
end
|
|
743
|
+
|
|
744
|
+
# Single walk collecting both custom-serialize triggers.
|
|
745
|
+
def tree_scan_custom_needs(node)
|
|
746
|
+
has_er = false
|
|
747
|
+
has_cdata = false
|
|
748
|
+
stack = [node]
|
|
749
|
+
until stack.empty?
|
|
750
|
+
current = stack.pop
|
|
751
|
+
case current
|
|
752
|
+
when ::Moxml::Adapter::CustomizedOx::EntityReference
|
|
753
|
+
has_er = true
|
|
754
|
+
when ::Ox::CData
|
|
755
|
+
has_cdata = true if current.value&.include?("]]>")
|
|
756
|
+
when ::Ox::Element, ::Ox::Document
|
|
757
|
+
current.nodes&.each { |child| stack << child }
|
|
758
|
+
end
|
|
759
|
+
return [true, has_cdata] if has_er
|
|
739
760
|
end
|
|
740
|
-
|
|
741
|
-
has_cdata
|
|
761
|
+
[has_er, has_cdata]
|
|
742
762
|
end
|
|
743
763
|
|
|
744
764
|
def has_declaration?(native_doc, _wrapper)
|
data/lib/moxml/adapter/rexml.rb
CHANGED
|
@@ -186,12 +186,16 @@ module Moxml
|
|
|
186
186
|
# Return all children preserving whitespace text nodes
|
|
187
187
|
result = node.children.dup
|
|
188
188
|
|
|
189
|
-
# Include any EntityReference wrappers stored alongside native
|
|
189
|
+
# Include any EntityReference wrappers stored alongside native
|
|
190
|
+
# children. Native children are identity-unique; only this
|
|
191
|
+
# concat can duplicate (the same wrapper added twice), so the
|
|
192
|
+
# dedupe lives here and the common path stays scan-free.
|
|
190
193
|
entity_refs = attachments.get(node, :entity_refs)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
194
|
+
if entity_refs
|
|
195
|
+
result.concat(entity_refs).uniq!(&:object_id) || result
|
|
196
|
+
else
|
|
197
|
+
result
|
|
198
|
+
end
|
|
195
199
|
end
|
|
196
200
|
|
|
197
201
|
def parent(node)
|
data/lib/moxml/version.rb
CHANGED