leptris 1.9.197.2-arm-linux

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 (124) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +3890 -0
  3. data/CLAUDE.md +115 -0
  4. data/CONTEXT.md +39 -0
  5. data/LICENSE.md +33 -0
  6. data/README.adoc +755 -0
  7. data/Rakefile +407 -0
  8. data/TODO.perf/01-ext-accelerated-default-reads.md +28 -0
  9. data/TODO.perf/02-auto-enable-native-layer.md +19 -0
  10. data/TODO.perf/03-bulk-xpath-result-materialization.md +18 -0
  11. data/TODO.perf/04-ext-bound-serialization.md +15 -0
  12. data/TODO.perf/05-native-surface-completion.md +19 -0
  13. data/TODO.perf/06-perf-battery-native-legs.md +16 -0
  14. data/TODO.perf/07-native-read-floor.md +31 -0
  15. data/TODO.perf/08-version-coherent-native-mutations.md +24 -0
  16. data/TODO.perf/09-adoption-lift-fast-path.md +38 -0
  17. data/TODO.perf/10-bulk-attribute-materialization.md +20 -0
  18. data/TODO.perf/11-moxml-gap-battery.md +18 -0
  19. data/TODO.perf/12-document-lifetime-in-c.md +47 -0
  20. data/TODO.perf/13-structural-memos.md +49 -0
  21. data/TODO.perf/14-insert-family-c-bound.md +31 -0
  22. data/TODO.perf/15-compiled-expression-cache.md +35 -0
  23. data/TODO.perf/16-at-xpath-single-result-seam.md +38 -0
  24. data/TODO.perf/17-precomputed-fast-path-flags.md +35 -0
  25. data/TODO.perf/18-inner-html-one-c-pass.md +32 -0
  26. data/TODO.perf/19-lazy-node-pointer.md +39 -0
  27. data/TODO.perf/20-css-translation-cache.md +24 -0
  28. data/TODO.perf/21-key-memo-consult.md +20 -0
  29. data/TODO.perf/22-eager-nodeset-materialization.md +30 -0
  30. data/TODO.perf/23-cbound-value-mutations.md +27 -0
  31. data/TODO.perf/24-immutable-read-lanes.md +22 -0
  32. data/TODO.perf/25-scope-owned-bulk-path.md +39 -0
  33. data/TODO.perf/26-post-mutation-memo-seeding.md +26 -0
  34. data/TODO.perf/27-c-yield-traversal.md +38 -0
  35. data/TODO.perf/28-address-fills.md +25 -0
  36. data/TODO.perf/29-ns-xpath-compiled.md +23 -0
  37. data/TODO.perf/30-copy-and-element-child-faces.md +31 -0
  38. data/TODO.perf/31-battery-rounds-4-8.md +23 -0
  39. data/TODO.perf/32-clean-host-floor-table.md +29 -0
  40. data/TODO.perf/33-cbound-root-set.md +29 -0
  41. data/TODO.perf/34-fragment-fast-lane.md +41 -0
  42. data/TODO.perf/35-parse-default-c-face.md +24 -0
  43. data/TODO.perf/36-raise-in-c-serializer-encoding.md +34 -0
  44. data/TODO.perf/37-document-lazy-pointer.md +28 -0
  45. data/TODO.perf/38-bulk-walk-and-bench-gate.md +23 -0
  46. data/TODO.restructure/01-constraint-compliance-audit.md +40 -0
  47. data/TODO.restructure/02-deep-copy-seam.md +21 -0
  48. data/TODO.restructure/03-evaluation-context-seam.md +18 -0
  49. data/TODO.restructure/04-spec-mece-restructure.md +20 -0
  50. data/TODO.restructure/05-memory-documentation.md +13 -0
  51. data/TODO.restructure/06-architecture-map-refresh.md +12 -0
  52. data/TODO.restructure/07-external-gates.md +16 -0
  53. data/TODO.restructure/08-new-code-audit-and-ext-memo.md +11 -0
  54. data/TODO.restructure/09-iteration-scope.md +29 -0
  55. data/TODO.restructure/10-cold-attr-read-diet.md +19 -0
  56. data/TODO.restructure/11-construction-factory-diet.md +16 -0
  57. data/TODO.restructure/12-resultattr-merge.md +12 -0
  58. data/TODO.restructure/13-spec-mece-followup.md +9 -0
  59. data/TODO.restructure/14-constraint-audit-refresh.md +15 -0
  60. data/TODO.restructure/15-readme-feature-documentation.md +10 -0
  61. data/TODO.restructure/16-claudemd-map-refresh.md +5 -0
  62. data/TODO.restructure/17-perf-drift-record.md +12 -0
  63. data/TODO.restructure/18-audit-spec-doubles-and-gem-contents.md +12 -0
  64. data/TODO.restructure/19-gitignore-hygiene.md +10 -0
  65. data/TODO.restructure/20-utf8proc-enablement.md +34 -0
  66. data/TODO.restructure/21-typeddata-variant-prototype.md +42 -0
  67. data/TODO.restructure/22-sax-drain-bulk-strip.md +28 -0
  68. data/docs/adr/0001-lockstep-mirror.md +17 -0
  69. data/docs/adr/0002-utf8-at-the-seam.md +19 -0
  70. data/docs/adr/0003-readonly-memoization-pattern.md +34 -0
  71. data/docs/adr/0004-lifetime-guard.md +21 -0
  72. data/docs/adr/0005-autoload-manifest-ordering.md +19 -0
  73. data/docs/adr/0006-ruby-variant-policy.md +25 -0
  74. data/ext/build_windows_native.rb +42 -0
  75. data/ext/leptris/native/extconf.rb +55 -0
  76. data/ext/leptris/native/native.c +2646 -0
  77. data/leptris.gemspec +43 -0
  78. data/lib/leptris/version.rb +5 -0
  79. data/lib/leptris/xml/attr.rb +76 -0
  80. data/lib/leptris/xml/c_string_array.rb +37 -0
  81. data/lib/leptris/xml/cdata.rb +27 -0
  82. data/lib/leptris/xml/comment.rb +27 -0
  83. data/lib/leptris/xml/css_to_xpath.rb +192 -0
  84. data/lib/leptris/xml/descriptor.rb +214 -0
  85. data/lib/leptris/xml/diff.rb +74 -0
  86. data/lib/leptris/xml/doc_type.rb +54 -0
  87. data/lib/leptris/xml/document.rb +716 -0
  88. data/lib/leptris/xml/document_fragment.rb +63 -0
  89. data/lib/leptris/xml/element.rb +771 -0
  90. data/lib/leptris/xml/entity_reference.rb +13 -0
  91. data/lib/leptris/xml/evaluation_context.rb +32 -0
  92. data/lib/leptris/xml/ffi.rb +1527 -0
  93. data/lib/leptris/xml/iteration_scope.rb +56 -0
  94. data/lib/leptris/xml/iterparse.rb +144 -0
  95. data/lib/leptris/xml/namespace.rb +43 -0
  96. data/lib/leptris/xml/native.so +0 -0
  97. data/lib/leptris/xml/native_layer.rb +92 -0
  98. data/lib/leptris/xml/node.rb +686 -0
  99. data/lib/leptris/xml/node_set.rb +212 -0
  100. data/lib/leptris/xml/parse_options.rb +97 -0
  101. data/lib/leptris/xml/plan_value.rb +121 -0
  102. data/lib/leptris/xml/processing_instruction.rb +90 -0
  103. data/lib/leptris/xml/pull.rb +212 -0
  104. data/lib/leptris/xml/relaxng.rb +126 -0
  105. data/lib/leptris/xml/result_attr.rb +39 -0
  106. data/lib/leptris/xml/result_text.rb +36 -0
  107. data/lib/leptris/xml/sax/document.rb +45 -0
  108. data/lib/leptris/xml/sax/dom_dispatch.rb +144 -0
  109. data/lib/leptris/xml/sax/parser.rb +326 -0
  110. data/lib/leptris/xml/sax/recorder.rb +374 -0
  111. data/lib/leptris/xml/sax.rb +14 -0
  112. data/lib/leptris/xml/schematron.rb +88 -0
  113. data/lib/leptris/xml/searchable.rb +361 -0
  114. data/lib/leptris/xml/serialization.rb +180 -0
  115. data/lib/leptris/xml/text.rb +32 -0
  116. data/lib/leptris/xml/xpath.rb +89 -0
  117. data/lib/leptris/xml/xquery.rb +77 -0
  118. data/lib/leptris/xml/xslt.rb +85 -0
  119. data/lib/leptris/xml.rb +108 -0
  120. data/lib/leptris.rb +53 -0
  121. data/lib/libleptris.so +0 -0
  122. data/lib/libutf8proc.so.3 +0 -0
  123. data/scripts/gem_smoke.rb +36 -0
  124. metadata +214 -0
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ffi"
4
+
5
+ # The owning context for elements yielded by an Iterparse run —
6
+ # the lifetime and memoization authority that a Document is for
7
+ # parsed trees, scoped to one iteration:
8
+ #
9
+ # - `#c_ptr` carries the iterator handle and goes nil on `#free` —
10
+ # the same signal `Node#ensure_alive!` reads, so using a held
11
+ # element after the iterator frees RAISES UseAfterFreeError
12
+ # instead of segfaulting (ruby#152).
13
+ # - `#wrapper_cache` gives wrapper identity within a yielded
14
+ # subtree (children() twice returns the same wrappers).
15
+ # - `#version` advances per yield and on mutation — stale memos on
16
+ # held wrappers invalidate as subtrees are released.
17
+ #
18
+ # `Node#document` answers nil for scope-owned elements (the
19
+ # documented iterparse contract) while the internal machinery
20
+ # engages — including the fast bare-name attribute path, which is
21
+ # what makes iterparse attribute reads match document-backed cost.
22
+ class Leptris::XML::IterationScope
23
+ def initialize(iterator_handle)
24
+ @c_ptr = iterator_handle
25
+ @wrapper_cache = {}
26
+ @version = 0
27
+ end
28
+
29
+ attr_reader :c_ptr, :wrapper_cache
30
+
31
+ def version
32
+ @version
33
+ end
34
+
35
+ def advance_version
36
+ @version += 1
37
+ end
38
+
39
+ def readonly?
40
+ false
41
+ end
42
+
43
+ # Called by the owning Iterparse when it frees the C iterator —
44
+ # every wrapper it handed out fails liveness from here on.
45
+ def mark_freed
46
+ @c_ptr = nil
47
+ end
48
+
49
+ # Called before each yield: the previous subtree is released and
50
+ # its pool memory recycled — a fresh element may land at a cached
51
+ # address, so the identity cache and the memo version both reset.
52
+ def new_subtree!
53
+ @wrapper_cache = {}
54
+ @version += 1
55
+ end
56
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ffi"
4
+
5
+ # Incremental tree iteration (libleptris v1.6.0): yields each
6
+ # completed TOP-LEVEL child element of the document root as it
7
+ # parses. The previous element's whole subtree is released when the
8
+ # next is produced — memory stays bounded by the largest subtree,
9
+ # not the document.
10
+ #
11
+ # Leptris::XML::Iterparse.parse(huge_xml) do |element|
12
+ # process(element) # subtree valid until the next iteration
13
+ # end
14
+ #
15
+ # v2 (libleptris 1.9.4, #586): an explicit yield mode —
16
+ # :top_level (v1: the root's completed children) or
17
+ # :full_document (every element in document-order completion —
18
+ # post-order, child before parent, subtree attached) — plus
19
+ # namespace resolution on the last yielded element and an error
20
+ # channel for truncated input:
21
+ #
22
+ # it = Leptris::XML::Iterparse.parse(xml, mode: :full_document)
23
+ # it.run do |el|
24
+ # it.namespace_uri("p") # in scope on el, the last yielded
25
+ # end
26
+ # it.free
27
+ #
28
+ # Leptris::XML::Iterparse.parse(xml) { |el| ... }.error
29
+ # # => nil, or the parse failure (readable after the run: the
30
+ # # block form frees the iterator on return, snapshotting it)
31
+ #
32
+ # The yielded elements are owned by an internal IterationScope:
33
+ # #document returns nil (the documented contract) while liveness
34
+ # guards and memoization engage — using an element after the
35
+ # iterator frees raises UseAfterFreeError instead of crashing.
36
+ class Leptris::XML::Iterparse
37
+ MODES = {
38
+ top_level: Leptris::XML::FFI::ITERPARSE_TOP_LEVEL,
39
+ full_document: Leptris::XML::FFI::ITERPARSE_FULL_DOCUMENT,
40
+ }.freeze
41
+
42
+ # NOTE: an IO argument is read fully into memory before the C
43
+ # iterator starts — the bounded-memory path is parse_file (C-side
44
+ # file streaming). The C API takes one (xml, len) buffer.
45
+ def self.parse(xml_or_io, mode: :top_level, &block)
46
+ xml = xml_or_io.is_a?(String) ? xml_or_io : xml_or_io.read
47
+ iterator = new(Leptris::XML::FFI.leptris_iterparse_new_ex(
48
+ xml, xml.bytesize, mode_code(mode)))
49
+ return iterator unless block
50
+ begin
51
+ iterator.run(&block)
52
+ ensure
53
+ iterator.free
54
+ end
55
+ iterator
56
+ end
57
+
58
+ def self.parse_file(path, mode: :top_level, &block)
59
+ iterator = new(Leptris::XML::FFI.leptris_iterparse_new_file_ex(
60
+ path, mode_code(mode)))
61
+ return iterator unless block
62
+ begin
63
+ iterator.run(&block)
64
+ ensure
65
+ iterator.free
66
+ end
67
+ iterator
68
+ end
69
+
70
+ def self.mode_code(mode)
71
+ MODES.fetch(mode) do
72
+ raise ArgumentError,
73
+ "mode must be :top_level or :full_document, got #{mode.inspect}"
74
+ end
75
+ end
76
+ private_class_method :mode_code
77
+
78
+ def initialize(handle)
79
+ raise Leptris::XML::ParseError, "leptris_iterparse_new failed" if handle.null?
80
+ @handle = handle
81
+ @scope = Leptris::XML::IterationScope.new(handle)
82
+ end
83
+
84
+ # Yields completed elements until the document is exhausted or the
85
+ # parse fails (see #error). Does not free the iterator — the block
86
+ # form of .parse/.parse_file does, or call #free explicitly.
87
+ def run
88
+ return enum_for(:run) unless block_given?
89
+ while @handle
90
+ ptr = Leptris::XML::FFI.leptris_iterparse_next(@handle)
91
+ break if ptr.null?
92
+ # The scope owns lifetime + memoization for yielded elements
93
+ # (ruby#152): liveness guards engage (post-free use raises
94
+ # instead of segfaulting), wrapper identity holds within a
95
+ # subtree, and memoized reads match document-backed cost.
96
+ # Each yield starts a fresh subtree — the released pool
97
+ # memory is recycled, so the identity cache resets with the
98
+ # memo version. #document still answers nil on the elements.
99
+ @scope.new_subtree!
100
+ yield Leptris::XML::Node.wrap(ptr, @scope)
101
+ end
102
+ self
103
+ end
104
+
105
+ # The URI bound to +prefix+ ("" for the default namespace) in the
106
+ # last yielded element's in-scope namespace snapshot, or nil when
107
+ # unbound (v2, #586). Valid only while the iterator is live —
108
+ # call it inside the #run block; nil after #free.
109
+ def namespace_uri(prefix)
110
+ return nil if @handle.nil?
111
+ Leptris::XML::FFI.leptris_iterparse_ns_uri(@handle, prefix.to_s)
112
+ end
113
+
114
+ # Number of namespace bindings in the last yielded element's
115
+ # scope. Valid only while the iterator is live; 0 after #free.
116
+ def namespace_count
117
+ return 0 if @handle.nil?
118
+ Leptris::XML::FFI.leptris_iterparse_ns_count(@handle)
119
+ end
120
+
121
+ # The parse error for malformed/truncated input, or nil when the
122
+ # document is well-formed and exhausted. Yielded elements stop at
123
+ # the failure point; #free snapshots the message so this stays
124
+ # readable after the block form of .parse returns.
125
+ def error
126
+ @handle ? live_error : @last_error
127
+ end
128
+
129
+ # Releases the C iterator. Safe to call twice.
130
+ def free
131
+ return if @handle.nil?
132
+ @last_error = live_error
133
+ Leptris::XML::FFI.leptris_iterparse_free(@handle)
134
+ @handle = nil
135
+ @scope.mark_freed
136
+ end
137
+
138
+ private
139
+
140
+ def live_error
141
+ msg = Leptris::XML::FFI.leptris_iterparse_error(@handle)
142
+ msg.nil? || msg.empty? ? nil : msg
143
+ end
144
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # libleptris's LeptrisNamespace is `const char*` (the URI directly). The Ruby
4
+ # Namespace wrapper holds (element, uri, optional prefix). When the prefix
5
+ # isn't supplied (e.g. via Element#namespace), it can be derived by walking
6
+ # the element's namespace declarations via leptris_element_namespace_decl_*.
7
+
8
+ class Leptris::XML::Namespace
9
+ attr_reader :element, :href, :prefix
10
+
11
+ def initialize(element, href, prefix: :derive)
12
+ @element = element
13
+ @href = href
14
+ @prefix = prefix == :derive ? derive_prefix : prefix
15
+ end
16
+
17
+ def document
18
+ @element&.document
19
+ end
20
+
21
+ def ==(other)
22
+ other.is_a?(Leptris::XML::Namespace) &&
23
+ @href == other.href && @prefix == other.prefix
24
+ end
25
+
26
+ def inspect
27
+ "#<#{self.class.name} prefix=#{@prefix.inspect} href=#{@href.inspect}>"
28
+ end
29
+
30
+ private
31
+
32
+ def derive_prefix
33
+ return nil if @element.nil?
34
+ count = Leptris::XML::FFI.leptris_element_namespace_count(@element.c_ptr)
35
+ count.times do |i|
36
+ uri = Leptris::XML::FFI.leptris_element_namespace_decl_uri(@element.c_ptr, i)
37
+ next unless uri == @href
38
+ prefix = Leptris::XML::FFI.leptris_element_namespace_decl_prefix(@element.c_ptr, i)
39
+ return prefix.nil? || prefix.empty? ? nil : prefix
40
+ end
41
+ nil
42
+ end
43
+ end
Binary file
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Opt-in native read layer (TODO.restructure/21 round 2, #185):
4
+ # TypedData node wrappers with C-bound hot reads and bulk children
5
+ # construction — measured 6.2x on raw walks. Load explicitly:
6
+ #
7
+ # require "leptris/xml/native"
8
+ # root = doc.native_node
9
+ # root.children.each { |c| c.name }
10
+ #
11
+ # Identity is shared with the binding through the per-document
12
+ # wrapper cache, so native and binding views of one document
13
+ # interoperate. The default require path never loads this file;
14
+ # installs without the compiled bundle (ruby-platform gem) raise
15
+ # a clear LoadError from the require.
16
+
17
+ require "ffi" unless defined?(::FFI::Library)
18
+
19
+ begin
20
+ # The compiled bundle. A sanctioned require exception: a
21
+ # .bundle/.so cannot go through autoload. Missing in installs
22
+ # that do not vendor it (ruby-platform gem). Windows names the
23
+ # artifact per Ruby minor, DOT-FREE (#207/#227): a PE DLL must
24
+ # bind its build Ruby's runtime (one DLL per minor), and MRI
25
+ # derives the init symbol from the basename cut at the first
26
+ # dot — native_3_3.so resolves Init_native_3_3.
27
+ if Gem.win_platform?
28
+ require "leptris/xml/native_#{RUBY_VERSION[/\A\d+\.\d+/].tr('.', '_')}"
29
+ else
30
+ require "leptris/xml/native"
31
+ end
32
+ rescue LoadError => e
33
+ raise LoadError, <<~MSG
34
+ leptris: the compiled native read layer is not available in
35
+ this install (#{e.message}). It ships in platform gems; the
36
+ ruby-platform variant does not carry it.
37
+ MSG
38
+ end
39
+
40
+ module Leptris::XML::Native
41
+ # FFI.libleptris_candidates is the single source of truth — the
42
+ # native layer MUST dlsym the exact image the FFI layer loaded,
43
+ # or two library instances split per-document state.
44
+ def self.lib_candidates
45
+ Leptris::XML::FFI.libleptris_candidates
46
+ end
47
+ end
48
+
49
+ # TODO.perf/01: accelerate the DEFAULT binding classes while the
50
+ # bundle is loaded (name/content/prefix/[] hot reads).
51
+ Leptris::XML::NATIVE_FAST = true
52
+
53
+ candidates = Leptris::XML::Native.lib_candidates
54
+ resolved = candidates.any? do |path|
55
+ begin
56
+ Leptris::XML::NativeNode.resolve!(path)
57
+ true
58
+ rescue RuntimeError
59
+ false
60
+ end
61
+ end
62
+ raise LoadError,
63
+ "leptris: native layer loaded but libleptris could not be " \
64
+ "resolved from #{candidates.inspect}" unless resolved
65
+
66
+ class Leptris::XML::Document
67
+ # Separate identity cache for NativeNodes so binding Node.wrap
68
+ # and the native layer never overwrite each other.
69
+ def native_cache
70
+ @native_cache ||= {}
71
+ end
72
+
73
+ def native_node
74
+ r = root or raise Leptris::XML::Error, "document has no root element"
75
+ Leptris::XML::NativeNode.from(self, r.c_ptr)
76
+ end
77
+
78
+ # Builder factories that return NativeNodes (#149): one C call
79
+ # and a TypedData wrap — no FFI::Pointer, no wrap_fresh path.
80
+ def native_create_element(name)
81
+ Leptris::XML::NativeNode.create_element(self, name.to_s)
82
+ end
83
+
84
+ def native_create_text(content)
85
+ Leptris::XML::NativeNode.create_text(self, content.to_s)
86
+ end
87
+
88
+ def native_root=(element)
89
+ Leptris::XML::NativeNode.set_root(self, element)
90
+ element
91
+ end
92
+ end