moxml 0.5.0 → 0.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 837d647ac39dac23aae2389b6150eafbecf405788f5c62e09af616aa0644715c
4
- data.tar.gz: c7a427e0cf685dd3121e0e5afdfcf388a0d18c52766f22717c8bb8b7d02cecf2
3
+ metadata.gz: ca6fefbf114afcaf13d9c241b4df5081eef1e004019618ca12d31a655d89dd53
4
+ data.tar.gz: d361373f16dafff997d4ca53b5dbc23c332b09331e8f49ef5abed09f526bdea7
5
5
  SHA512:
6
- metadata.gz: 2e59a3705d492b273708e0d82fa10078be543c4aca86daa7fe7c7c1e90b13ecc774ac89934458b3373d46045e4a2f3fa7eef46172045bbe8ae4fd5105c03030c
7
- data.tar.gz: c8f15cc1577514a368e551444f0e0759170e77e5969a383e0b43025d733e75be8ec99869e43f9fd459f2ac1c0d0c422b26d9e98c73823d76fd18d585d11ebdff
6
+ metadata.gz: 0a2e61a613511970e4c6945c074a578aefcb12591def797a726bf0959796c43dbf2e71b77509271b1dad15b9f0f1e81f5ec3b8b0af0e384bccad35745bc2dbb6
7
+ data.tar.gz: 183964ef083c888b3b7d58b896a547acf25c0414d997040c10b805b8379088825c2ec996b149aa9e8fe51b6419135d1341d8ebe9c88ddb0656101e25d3291557
@@ -266,7 +266,13 @@ module Moxml
266
266
  def split_entity_markers(natives, parent)
267
267
  result = []
268
268
  natives.each do |child|
269
- content = child.content.to_s.dup.force_encoding("UTF-8") if child.is_a?(::Leptris::XML::Text)
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)
@@ -862,8 +868,23 @@ module Moxml
862
868
 
863
869
  private
864
870
 
871
+ # The variable-length \s* gap after \A defeats Onigmo's anchor
872
+ # optimization, forcing a full-buffer scan — 6.5 ms on a 1 MB
873
+ # document without a declaration (the common case). Match a
874
+ # head slice instead; fall back to the full string only when
875
+ # the head is all whitespace (pathological prefixes).
876
+ SOURCE_DECLARATION_RE = /\A\s*<\?xml\b/
877
+ private_constant :SOURCE_DECLARATION_RE
878
+
865
879
  def record_source_declaration(native_doc, xml_string)
866
- had = xml_string.match?(/\A\s*<\?xml\b/)
880
+ head = xml_string[0, 256]
881
+ had = if head.match?(SOURCE_DECLARATION_RE)
882
+ true
883
+ elsif head.match?(/\A\s*\z/)
884
+ xml_string.match?(SOURCE_DECLARATION_RE)
885
+ else
886
+ false
887
+ end
867
888
  attachments.set(native_doc, :had_source_declaration, had)
868
889
  end
869
890
 
@@ -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
- # Check cached flags on documents (most common case)
721
- if node.is_a?(::Ox::Document)
722
- return true if attachments.get(node, :has_entity_refs)
723
- return true if attachments.get(node, :has_cdata_end_markers)
724
- return false if attachments.key?(node, :has_entity_refs) &&
725
- attachments.key?(node, :has_cdata_end_markers)
726
- end
727
-
728
- # Only scan tree on first call — short-circuit on first hit
729
- has_er = tree_has_entity_references?(node)
730
- if has_er
731
- attachments.set(node, :has_entity_refs, true) if node.is_a?(::Ox::Document)
732
- return true
733
- end
734
-
735
- has_cdata = tree_has_cdata_end_markers?(node)
736
- if node.is_a?(::Ox::Document)
737
- attachments.set(node, :has_entity_refs, false)
738
- attachments.set(node, :has_cdata_end_markers, has_cdata)
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)
@@ -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 children
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
- result.concat(entity_refs) if entity_refs
192
-
193
- # Ensure uniqueness by object_id to prevent duplicates
194
- result.uniq(&:object_id)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Moxml
4
- VERSION = "0.5.0"
4
+ VERSION = "0.5.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: moxml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.