leptris 1.9.199.0-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 +2528 -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,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ffi"
4
+
5
+ # Compiled XPath expression: parse once, evaluate many times against
6
+ # any document (libleptris v1.6.0). The C handle is released by GC.
7
+ #
8
+ # expr = Leptris::XML::XPath.compile("//item[@qty > 3]")
9
+ # expr.eval(doc_a).length
10
+ # expr.eval(doc_b, "p" => "urn:p").length
11
+ #
12
+ class Leptris::XML::XPath
13
+ class CompiledHandle < ::FFI::AutoPointer
14
+ def self.release(ptr)
15
+ Leptris::XML::FFI.leptris_xpath_compiled_free(ptr)
16
+ end
17
+ end
18
+
19
+ attr_reader :expression
20
+
21
+ # +version+ (#183, mirroring leptris-py#105): nil compiles the
22
+ # full-grammar expression (the default); :xpath10 / "1.0" pins
23
+ # the strict XPath 1.0 surface. Validated at the boundary.
24
+ def self.compile(expression, version: nil)
25
+ raw = Leptris::XML::FFI.leptris_xpath_compile(expression.to_s)
26
+ if raw.null?
27
+ raise Leptris::XML::XPathError,
28
+ "invalid expression: #{Leptris::XML::FFI.leptris_last_error}"
29
+ end
30
+ version_code =
31
+ Leptris::XML::Searchable.xpath_version_code(version) if version
32
+ new(expression.to_s, CompiledHandle.new(raw), version_code)
33
+ end
34
+
35
+ # Raw-pointer evaluation for Searchable's compiled cache
36
+ # (TODO.perf/15): the same entry the string path uses, against
37
+ # the compiled handle — no per-call parse/cache lookup.
38
+ def eval_ptrs(doc_ptr, context_ptr)
39
+ Leptris::XML::FFI.leptris_xpath_compiled_eval(
40
+ @handle, doc_ptr, context_ptr)
41
+ end
42
+
43
+ # Namespace-bound evaluation against the compiled handle
44
+ # (TODO.perf/29): the handle is ns-independent — compile once,
45
+ # bind per call through the cached ns set.
46
+ def eval_ns_ptrs(doc_ptr, context_ptr, ns_set_ptr)
47
+ Leptris::XML::FFI.leptris_xpath_compiled_eval_ns(
48
+ @handle, doc_ptr, context_ptr, ns_set_ptr)
49
+ end
50
+
51
+ def initialize(expression, handle, version_code = nil)
52
+ @expression = expression
53
+ @handle = handle
54
+ @version_code = version_code
55
+ end
56
+
57
+ # Evaluates against +doc_or_element+. An optional trailing hash of
58
+ # namespace bindings ("prefix" => uri) routes the evaluation through
59
+ # the namespace-bound path, matching Searchable#xpath semantics.
60
+ # A compiled version pin routes through the engine's versioned
61
+ # entry instead of the compiled handle — there is no compiled
62
+ # versioned lane yet (engine gap), so the strict-1.0 surface is
63
+ # enforced per evaluation over the stored expression.
64
+ def eval(doc_or_element, ns = nil)
65
+ context = Leptris::XML::EvaluationContext.of(doc_or_element)
66
+ document = context.document
67
+ result_ptr =
68
+ if @version_code
69
+ raise ArgumentError,
70
+ "version: cannot be combined with namespace bindings " "(the versioned engine entry takes no ns set yet)" if ns && !ns.empty?
71
+ Leptris::XML::FFI.leptris_xpath_eval_versioned(
72
+ document.c_ptr, context.context_node_ptr,
73
+ @expression, @version_code, nil)
74
+ elsif ns && !ns.empty?
75
+ Leptris::XML::FFI.with_ns_set(ns) do |set|
76
+ Leptris::XML::FFI.leptris_xpath_compiled_eval_ns(
77
+ @handle, document.c_ptr, context.context_node_ptr, set)
78
+ end
79
+ else
80
+ Leptris::XML::FFI.leptris_xpath_compiled_eval(
81
+ @handle, document.c_ptr, context.context_node_ptr)
82
+ end
83
+ if result_ptr.null?
84
+ raise Leptris::XML::XPathError,
85
+ Leptris::XML::FFI.leptris_last_error.to_s
86
+ end
87
+ Leptris::XML::Searchable.wrap_xpath_result(document, result_ptr)
88
+ end
89
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ffi"
4
+
5
+ # Compiled XQuery 1.0 query (libleptris 1.9.64+): parse once,
6
+ # evaluate many times against any document. The engine layer
7
+ # orchestrates the XPath engine — the prolog (declare variable /
8
+ # namespace / function local:*) binds into the evaluation context,
9
+ # FLWOR clauses run as a tuple stream, and results reuse the XPath
10
+ # result handle.
11
+ #
12
+ # query = Leptris::XML::XQuery.parse(<<~XQ)
13
+ # for $i in //item
14
+ # where $i/@qty > 3
15
+ # order by $i/@qty descending
16
+ # return <big>{$i/name/text()}</big>
17
+ # XQ
18
+ # query.eval(doc) # => Leptris::XML::NodeSet
19
+ #
20
+ class Leptris::XML::XQuery
21
+ class Handle < ::FFI::AutoPointer
22
+ def self.release(ptr)
23
+ Leptris::XML::FFI.leptris_xquery_free(ptr)
24
+ end
25
+ end
26
+
27
+ def self.parse(query)
28
+ query = query.to_s
29
+ raw = Leptris::XML::FFI.leptris_xquery_parse(query, query.bytesize)
30
+ if raw.null?
31
+ raise Leptris::XML::XPathError,
32
+ "query parse failed: #{Leptris::XML::FFI.leptris_last_error}"
33
+ end
34
+ new(query, Handle.new(raw))
35
+ end
36
+
37
+ attr_reader :query
38
+
39
+ def initialize(query, handle)
40
+ @query = query
41
+ @handle = handle
42
+ end
43
+
44
+ # Evaluates against +doc_or_element+. Plain-expression bodies keep
45
+ # their XPath result type (NodeSet / true/false / Float / String);
46
+ # FLWOR results arrive as the sequence channel — read them through
47
+ # an aggregate until the engine materializes readable sequence
48
+ # items (same caveat as the XPath for-return subset).
49
+ # +params+ binds external variables (libleptris 1.9.133): each
50
+ # value is an XPath expression evaluated in an empty context
51
+ # (QT3 <param select> semantics) — {"x" => "21 * 2"} binds
52
+ # `declare variable $x external`. A binding overrides the
53
+ # declaration's default initializer; an external with neither
54
+ # fails evaluation. An empty params hash takes the same lane —
55
+ # the engine decides between default initializer and failure.
56
+ def eval(doc_or_element, params = nil)
57
+ context = Leptris::XML::EvaluationContext.of(doc_or_element)
58
+ result_ptr =
59
+ if params
60
+ names, name_anchors = Leptris::XML::CStringArray.to_c(
61
+ params.keys.map(&:to_s))
62
+ selects, select_anchors = Leptris::XML::CStringArray.to_c(
63
+ params.values.map(&:to_s))
64
+ Leptris::XML::FFI.leptris_xquery_eval_params(
65
+ @handle, context.document.c_ptr, context.context_node_ptr,
66
+ names, selects, params.size)
67
+ else
68
+ Leptris::XML::FFI.leptris_xquery_eval(
69
+ @handle, context.document.c_ptr, context.context_node_ptr)
70
+ end
71
+ if result_ptr.null?
72
+ raise Leptris::XML::XPathError,
73
+ Leptris::XML::FFI.leptris_last_error.to_s
74
+ end
75
+ Leptris::XML::Searchable.wrap_xpath_result(context.document, result_ptr)
76
+ end
77
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ffi"
4
+
5
+ module Leptris::XML::XSLT
6
+ module_function
7
+
8
+ def parse(stylesheet_xml)
9
+ Stylesheet.parse(stylesheet_xml)
10
+ end
11
+
12
+ def parse_file(path)
13
+ Stylesheet.parse_file(path)
14
+ end
15
+
16
+ # A compiled XSLT 1.0 stylesheet (libleptris 1.9.1): the stylesheet
17
+ # parses ONCE into an immutable instruction forest — patterns to
18
+ # XPath ASTs, selects through the compiled-XPath path — then applies
19
+ # to any number of documents with no re-parsing.
20
+ #
21
+ # style = Leptris::XML::XSLT.parse(<<~XSL)
22
+ # <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
23
+ # <xsl:template match="/"><out><xsl:value-of select="count(//item)"/></out></xsl:template>
24
+ # </xsl:stylesheet>
25
+ # XSL
26
+ # style.apply_to(doc) # => result Document (queryable tree)
27
+ # style.serialize(doc) # => String (fragments and top-level
28
+ # # text nodes preserved)
29
+ #
30
+ class Stylesheet
31
+ # GC-managed compiled handle.
32
+ class Handle < ::FFI::AutoPointer
33
+ def self.release(ptr)
34
+ Leptris::XML::FFI.leptris_xslt_free(ptr)
35
+ end
36
+ end
37
+
38
+ # Compile a stylesheet. Raises XPathError on a stylesheet syntax
39
+ # error (detail on the thread-global last error).
40
+ def self.parse(stylesheet_xml)
41
+ raw = Leptris::XML::FFI.leptris_xslt_parse(
42
+ stylesheet_xml.to_s, stylesheet_xml.to_s.bytesize)
43
+ if raw.null?
44
+ raise Leptris::XML::XPathError,
45
+ "stylesheet parse failed: #{Leptris::XML::FFI.leptris_last_error}"
46
+ end
47
+ new(Handle.new(raw))
48
+ end
49
+
50
+ # Compile from a file. Resolves §2.7 embedded stylesheets
51
+ # (xml-stylesheet PI with an href="#id" fragment) when the file's
52
+ # root is not itself a stylesheet.
53
+ def self.parse_file(path)
54
+ raw = Leptris::XML::FFI.leptris_xslt_parse_file(path.to_s)
55
+ if raw.null?
56
+ raise Leptris::XML::XPathError,
57
+ "stylesheet parse failed: #{Leptris::XML::FFI.leptris_last_error}"
58
+ end
59
+ new(Handle.new(raw))
60
+ end
61
+
62
+ def initialize(handle)
63
+ @handle = handle
64
+ end
65
+
66
+ # Apply to +document+ (not modified) and return the result tree
67
+ # as an owning Document — query it with xpath/css like any other.
68
+ def apply_to(document)
69
+ raw = Leptris::XML::FFI.leptris_xslt_apply(@handle, document.c_ptr)
70
+ if raw.null?
71
+ raise Leptris::XML::XPathError,
72
+ "transform failed: #{document.last_error || Leptris::XML::FFI.leptris_last_error}"
73
+ end
74
+ Leptris::XML::Document.wrap(raw)
75
+ end
76
+
77
+ # Apply and serialize in one call — keeps top-level text nodes and
78
+ # result fragments that the tree API would flatten.
79
+ def serialize(document)
80
+ str_ptr = Leptris::XML::FFI.leptris_xslt_apply_string(
81
+ @handle, document.c_ptr)
82
+ Leptris::XML::FFI.read_owned_string(str_ptr)
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leptris
4
+ module XML
5
+ autoload :FFI, "leptris/xml/ffi"
6
+ autoload :Node, "leptris/xml/node"
7
+ autoload :Element, "leptris/xml/element"
8
+ autoload :Text, "leptris/xml/text"
9
+ autoload :Comment, "leptris/xml/comment"
10
+ autoload :CDATA, "leptris/xml/cdata"
11
+ autoload :ProcessingInstruction, "leptris/xml/processing_instruction"
12
+ autoload :EntityReference, "leptris/xml/entity_reference"
13
+ autoload :Attr, "leptris/xml/attr"
14
+ autoload :Namespace, "leptris/xml/namespace"
15
+ autoload :Document, "leptris/xml/document"
16
+ autoload :DocumentFragment, "leptris/xml/document_fragment"
17
+ autoload :DocType, "leptris/xml/doc_type"
18
+ autoload :NodeSet, "leptris/xml/node_set"
19
+ autoload :Searchable, "leptris/xml/searchable"
20
+ autoload :ParseOptions, "leptris/xml/parse_options"
21
+ autoload :CssToXPath, "leptris/xml/css_to_xpath"
22
+ autoload :CStringArray, "leptris/xml/c_string_array"
23
+ autoload :Serialization, "leptris/xml/serialization"
24
+ autoload :SAX, "leptris/xml/sax"
25
+ autoload :XPath, "leptris/xml/xpath"
26
+ autoload :EvaluationContext, "leptris/xml/evaluation_context"
27
+ autoload :Schematron, "leptris/xml/schematron"
28
+ autoload :Diff, "leptris/xml/diff"
29
+ autoload :Descriptor, "leptris/xml/descriptor"
30
+ autoload :PlanValue, "leptris/xml/plan_value"
31
+ autoload :IterationScope, "leptris/xml/iteration_scope"
32
+ autoload :ResultText, "leptris/xml/result_text"
33
+ autoload :ResultAttr, "leptris/xml/result_attr"
34
+ autoload :XSLT, "leptris/xml/xslt"
35
+ autoload :XQuery, "leptris/xml/xquery"
36
+ autoload :RelaxNG, "leptris/xml/relaxng"
37
+ autoload :Pull, "leptris/xml/pull"
38
+ autoload :Iterparse, "leptris/xml/iterparse"
39
+
40
+ # Nokogiri-style top-level entry points (Nokogiri::XML(...) /
41
+ # Nokogiri::XML.parse). Delegates to Document.parse.
42
+ def self.parse(xml_or_io, options: nil, readonly: false, recover: false)
43
+ Document.parse(xml_or_io, options: options, readonly: readonly,
44
+ recover: recover)
45
+ end
46
+
47
+ # Tree diff between two documents: equal subtrees prune in
48
+ # O(1) by content digest; diverging regions align by LCS.
49
+ # +ignore_ws+ treats whitespace-only text nodes as absent.
50
+ def self.diff(document_a, document_b, ignore_ws: false)
51
+ Leptris::XML::Diff.compute(document_a, document_b,
52
+ ignore_ws: ignore_ws)
53
+ end
54
+
55
+ # Does +string+ contain a named entity reference that is not
56
+ # one of the five predefined XML entities (nor numeric)? One C
57
+ # pass (libleptris 1.9.62) — downstream adapters use it to skip
58
+ # whole-buffer rewrites when a document is already clean.
59
+ def self.buffer_has_nonstandard_entity?(string)
60
+ string = string.to_s
61
+ Leptris::XML::FFI.leptris_str_has_nonstandard_entity(
62
+ string, string.bytesize) != 0
63
+ end
64
+
65
+ # Tolerant HTML parse into a standard Document (libleptris
66
+ # 1.9.75): implied end tags, void elements, raw-text script and
67
+ # style, lowercased names, minimized/unquoted attributes, and
68
+ # the HTML named entities. html/head/body are synthesized; no
69
+ # implied tbody. Malformed input degrades to text rather than
70
+ # raising; only an entirely empty result is an error.
71
+ #
72
+ # MODES (libleptris 1.9.104, #659): :html4 (the default) pins
73
+ # the libxml2/Nokogiri compatibility shape — leading
74
+ # script/style stay in <body>, title/meta/link/base still lift
75
+ # — so byte-parity with Nokogiri::HTML holds. :whatwg selects
76
+ # the conformant engine: the full implied-<head> set
77
+ # (script/style/noscript/template/... lift) and foster
78
+ # parenting (1.9.105) — html5lib corpus 294/1555 vs Nokogiri's
79
+ # 372; the shapes genuinely differ by spec.
80
+ def self.parse_html(html, mode: :html4)
81
+ html = html.to_s
82
+ entry =
83
+ case mode
84
+ when :html4 then Leptris::XML::FFI.method(:leptris_parse_html4_string)
85
+ when :whatwg then Leptris::XML::FFI.method(:leptris_parse_html_string)
86
+ else
87
+ raise ArgumentError,
88
+ "mode must be :html4 or :whatwg, got #{mode.inspect}"
89
+ end
90
+ raw = entry.call(html, html.bytesize, nil)
91
+ if raw.null?
92
+ raise Leptris::XML::ParseError,
93
+ "HTML parse failed: #{Leptris::XML::FFI.leptris_last_error}"
94
+ end
95
+ Leptris::XML::Document.wrap(raw)
96
+ end
97
+
98
+ def self.parse_file(path)
99
+ Document.parse_file(path)
100
+ end
101
+
102
+ class Error < StandardError; end
103
+ class ParseError < Error; end
104
+ class ReadOnlyError < Error; end
105
+ class XPathError < Error; end
106
+ class UseAfterFreeError < Error; end
107
+ end
108
+ end
data/lib/leptris.rb ADDED
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "leptris/version"
4
+
5
+ module Leptris
6
+ autoload :XML, "leptris/xml"
7
+ end
8
+
9
+ # Eager library resolution (issue leptris-ruby#49): resolve the
10
+ # native library at require time instead of lazily inside
11
+ # Document.parse, so a missing library fails immediately.
12
+ #
13
+ # The require MUST come after the autoload registration above.
14
+ # ffi.rb opens `module Leptris; module XML`, and when that runs
15
+ # before `autoload :XML` exists it creates the constant first — the
16
+ # later autoload registration is then shadowed, xml.rb never loads,
17
+ # and the entire API is unreachable (Leptris::XML.constants ==
18
+ # [:FFI]; issue leptris-ruby#53). Requiring from here after the
19
+ # registration makes ffi.rb's module opening trigger the autoload,
20
+ # landing FFI inside the real manifest module; a downstream
21
+ # `require "leptris/xml"` is then a no-op.
22
+ begin
23
+ require "leptris/xml/ffi"
24
+ rescue LoadError => e
25
+ raise LoadError, <<~MSG
26
+ Leptris could not load the native libleptris library.
27
+ The ruby-platform gem is a fallback variant and ships no
28
+ binary; install the platform-specific variant for your system
29
+ (e.g. `gem install leptris --platform=arm64-darwin`), or set
30
+ LEPTRIS_LIB_PATH to a libleptris.{so,dylib,dll} file.
31
+ (Underlying error: #{e.message})
32
+ MSG
33
+ end
34
+
35
+ # TODO.perf/02: auto-enable the native acceleration layer when the
36
+ # compiled bundle is present (platform gems). Quietly falls back
37
+ # to pure FFI when absent (ruby-variant on TruffleRuby/JRuby,
38
+ # custom builds). LEPTRIS_NO_NATIVE=1 forces the FFI path (the
39
+ # suite uses it to exercise both surfaces). The explicit
40
+ # `require "leptris/xml/native_layer"` remains the parallel-API
41
+ # entry for NativeNode builders.
42
+ unless ENV["LEPTRIS_NO_NATIVE"] == "1"
43
+ begin
44
+ require "leptris/xml/native_layer"
45
+ rescue LoadError => e
46
+ # #207: a silent fallback hides a platform-gem packaging bug
47
+ # behind a walk-speed regression. Say so, then continue on
48
+ # the FFI surface (the explicit require still raises its full
49
+ # error for callers that opt in).
50
+ warn "leptris: native acceleration unavailable " \
51
+ "(#{e.message.gsub(/\s+/, ' ').strip[0, 200]}); using the FFI surface"
52
+ end
53
+ end
data/lib/libleptris.so ADDED
Binary file
Binary file
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Post-publish smoke (the teptris-ruby gem-smoke template): the
4
+ # INSTALLED gem from rubygems — not the checkout — must parse,
5
+ # query, mutate, serialize, and validate, with the native layer
6
+ # when the platform ships it. Also guards the #318 class: the
7
+ # NODE_* constants must exist in the shipped layer (a packaging
8
+ # skew surfaces as a NameError on first use, not at require).
9
+ require "leptris"
10
+
11
+ abort "SMOKE FAIL: leptris did not define Leptris::VERSION" unless defined?(Leptris::VERSION)
12
+ puts "leptris #{Leptris::VERSION} on #{RUBY_PLATFORM} (ruby #{RUBY_VERSION})"
13
+
14
+ doc = Leptris::XML.parse("<r id='1'><a x='2'>t1</a><a>t2</a></r>")
15
+ raise "parse: root" unless doc.root.name == "r"
16
+ raise "xpath: children" unless doc.root.xpath("a").size == 2
17
+ raise "attr read" unless doc.at_xpath("a")["x"] == "2"
18
+
19
+ doc.at_xpath("a").content = "z"
20
+ raise "mutate" unless doc.at_xpath("a").content == "z"
21
+ raise "serialize" unless doc.to_xml.include?('<a x="2">z</a>')
22
+
23
+ rng = Leptris::XML::RelaxNG.parse(
24
+ '<element name="r" xmlns="http://relaxng.org/ns/structure/1.0">' \
25
+ "<attribute name=\"id\"/></element>")
26
+ rng_doc = Leptris::XML::Document.parse(%q{<r id='1'/>})
27
+ raise "rng verdict" unless rng.valid?(rng_doc)
28
+ raise "rng errors shape" unless rng.validate_errors(rng_doc).is_a?(Array)
29
+ raise "rng report shape" unless rng.validate_report(rng_doc).is_a?(Array)
30
+
31
+ # The #318 class: node-kind constants must live in the shipped
32
+ # Ruby layer (plain constants cannot go missing with the lib).
33
+ raise "NODE constants" unless Leptris::XML::FFI::NODE_ELEMENT
34
+
35
+ layer = defined?(Leptris::XML::NATIVE_FAST) ? "native" : "ffi"
36
+ puts "SMOKE OK (#{layer} layer)"
metadata ADDED
@@ -0,0 +1,214 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: leptris
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.9.199.0
5
+ platform: arm-linux
6
+ authors:
7
+ - Ribose Inc.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-09-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ffi
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.15'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.15'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: |
56
+ Leptris is a Nokogiri-compatible Ruby binding for libleptris, a pure-C99 XML
57
+ 1.0 parser with full XPath 1.0 and SAX support. The C DOM is the single
58
+ source of truth; Ruby objects are thin FFI wrappers (one Ruby method =
59
+ one FFI call).
60
+ email:
61
+ - open.source@ribose.com
62
+ executables: []
63
+ extensions: []
64
+ extra_rdoc_files: []
65
+ files:
66
+ - CHANGELOG.md
67
+ - CLAUDE.md
68
+ - CONTEXT.md
69
+ - LICENSE.md
70
+ - README.adoc
71
+ - Rakefile
72
+ - TODO.perf/01-ext-accelerated-default-reads.md
73
+ - TODO.perf/02-auto-enable-native-layer.md
74
+ - TODO.perf/03-bulk-xpath-result-materialization.md
75
+ - TODO.perf/04-ext-bound-serialization.md
76
+ - TODO.perf/05-native-surface-completion.md
77
+ - TODO.perf/06-perf-battery-native-legs.md
78
+ - TODO.perf/07-native-read-floor.md
79
+ - TODO.perf/08-version-coherent-native-mutations.md
80
+ - TODO.perf/09-adoption-lift-fast-path.md
81
+ - TODO.perf/10-bulk-attribute-materialization.md
82
+ - TODO.perf/11-moxml-gap-battery.md
83
+ - TODO.perf/12-document-lifetime-in-c.md
84
+ - TODO.perf/13-structural-memos.md
85
+ - TODO.perf/14-insert-family-c-bound.md
86
+ - TODO.perf/15-compiled-expression-cache.md
87
+ - TODO.perf/16-at-xpath-single-result-seam.md
88
+ - TODO.perf/17-precomputed-fast-path-flags.md
89
+ - TODO.perf/18-inner-html-one-c-pass.md
90
+ - TODO.perf/19-lazy-node-pointer.md
91
+ - TODO.perf/20-css-translation-cache.md
92
+ - TODO.perf/21-key-memo-consult.md
93
+ - TODO.perf/22-eager-nodeset-materialization.md
94
+ - TODO.perf/23-cbound-value-mutations.md
95
+ - TODO.perf/24-immutable-read-lanes.md
96
+ - TODO.perf/25-scope-owned-bulk-path.md
97
+ - TODO.perf/26-post-mutation-memo-seeding.md
98
+ - TODO.perf/27-c-yield-traversal.md
99
+ - TODO.perf/28-address-fills.md
100
+ - TODO.perf/29-ns-xpath-compiled.md
101
+ - TODO.perf/30-copy-and-element-child-faces.md
102
+ - TODO.perf/31-battery-rounds-4-8.md
103
+ - TODO.perf/32-clean-host-floor-table.md
104
+ - TODO.perf/33-cbound-root-set.md
105
+ - TODO.perf/34-fragment-fast-lane.md
106
+ - TODO.perf/35-parse-default-c-face.md
107
+ - TODO.perf/36-raise-in-c-serializer-encoding.md
108
+ - TODO.perf/37-document-lazy-pointer.md
109
+ - TODO.perf/38-bulk-walk-and-bench-gate.md
110
+ - TODO.restructure/01-constraint-compliance-audit.md
111
+ - TODO.restructure/02-deep-copy-seam.md
112
+ - TODO.restructure/03-evaluation-context-seam.md
113
+ - TODO.restructure/04-spec-mece-restructure.md
114
+ - TODO.restructure/05-memory-documentation.md
115
+ - TODO.restructure/06-architecture-map-refresh.md
116
+ - TODO.restructure/07-external-gates.md
117
+ - TODO.restructure/08-new-code-audit-and-ext-memo.md
118
+ - TODO.restructure/09-iteration-scope.md
119
+ - TODO.restructure/10-cold-attr-read-diet.md
120
+ - TODO.restructure/11-construction-factory-diet.md
121
+ - TODO.restructure/12-resultattr-merge.md
122
+ - TODO.restructure/13-spec-mece-followup.md
123
+ - TODO.restructure/14-constraint-audit-refresh.md
124
+ - TODO.restructure/15-readme-feature-documentation.md
125
+ - TODO.restructure/16-claudemd-map-refresh.md
126
+ - TODO.restructure/17-perf-drift-record.md
127
+ - TODO.restructure/18-audit-spec-doubles-and-gem-contents.md
128
+ - TODO.restructure/19-gitignore-hygiene.md
129
+ - TODO.restructure/20-utf8proc-enablement.md
130
+ - TODO.restructure/21-typeddata-variant-prototype.md
131
+ - TODO.restructure/22-sax-drain-bulk-strip.md
132
+ - docs/adr/0001-lockstep-mirror.md
133
+ - docs/adr/0002-utf8-at-the-seam.md
134
+ - docs/adr/0003-readonly-memoization-pattern.md
135
+ - docs/adr/0004-lifetime-guard.md
136
+ - docs/adr/0005-autoload-manifest-ordering.md
137
+ - docs/adr/0006-ruby-variant-policy.md
138
+ - ext/build_windows_native.rb
139
+ - ext/leptris/native/extconf.rb
140
+ - ext/leptris/native/native.c
141
+ - leptris.gemspec
142
+ - lib/leptris.rb
143
+ - lib/leptris/version.rb
144
+ - lib/leptris/xml.rb
145
+ - lib/leptris/xml/attr.rb
146
+ - lib/leptris/xml/c_string_array.rb
147
+ - lib/leptris/xml/cdata.rb
148
+ - lib/leptris/xml/comment.rb
149
+ - lib/leptris/xml/css_to_xpath.rb
150
+ - lib/leptris/xml/descriptor.rb
151
+ - lib/leptris/xml/diff.rb
152
+ - lib/leptris/xml/doc_type.rb
153
+ - lib/leptris/xml/document.rb
154
+ - lib/leptris/xml/document_fragment.rb
155
+ - lib/leptris/xml/element.rb
156
+ - lib/leptris/xml/entity_reference.rb
157
+ - lib/leptris/xml/evaluation_context.rb
158
+ - lib/leptris/xml/ffi.rb
159
+ - lib/leptris/xml/iteration_scope.rb
160
+ - lib/leptris/xml/iterparse.rb
161
+ - lib/leptris/xml/namespace.rb
162
+ - lib/leptris/xml/native.so
163
+ - lib/leptris/xml/native_layer.rb
164
+ - lib/leptris/xml/node.rb
165
+ - lib/leptris/xml/node_set.rb
166
+ - lib/leptris/xml/parse_options.rb
167
+ - lib/leptris/xml/plan_value.rb
168
+ - lib/leptris/xml/processing_instruction.rb
169
+ - lib/leptris/xml/pull.rb
170
+ - lib/leptris/xml/relaxng.rb
171
+ - lib/leptris/xml/result_attr.rb
172
+ - lib/leptris/xml/result_text.rb
173
+ - lib/leptris/xml/sax.rb
174
+ - lib/leptris/xml/sax/document.rb
175
+ - lib/leptris/xml/sax/dom_dispatch.rb
176
+ - lib/leptris/xml/sax/parser.rb
177
+ - lib/leptris/xml/sax/recorder.rb
178
+ - lib/leptris/xml/schematron.rb
179
+ - lib/leptris/xml/searchable.rb
180
+ - lib/leptris/xml/serialization.rb
181
+ - lib/leptris/xml/text.rb
182
+ - lib/leptris/xml/xpath.rb
183
+ - lib/leptris/xml/xquery.rb
184
+ - lib/leptris/xml/xslt.rb
185
+ - lib/libleptris.so
186
+ - lib/libutf8proc.so.3
187
+ - scripts/gem_smoke.rb
188
+ homepage: https://github.com/leptris/leptris-ruby
189
+ licenses:
190
+ - MIT
191
+ metadata:
192
+ homepage_uri: https://github.com/leptris/leptris-ruby
193
+ source_code_uri: https://github.com/leptris/leptris-ruby
194
+ changelog_uri: https://github.com/leptris/leptris-ruby/blob/main/CHANGELOG.md
195
+ post_install_message:
196
+ rdoc_options: []
197
+ require_paths:
198
+ - lib
199
+ required_ruby_version: !ruby/object:Gem::Requirement
200
+ requirements:
201
+ - - ">="
202
+ - !ruby/object:Gem::Version
203
+ version: 3.0.0
204
+ required_rubygems_version: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
209
+ requirements: []
210
+ rubygems_version: 3.5.22
211
+ signing_key:
212
+ specification_version: 4
213
+ summary: Nokogiri-compatible Ruby binding for libleptris (XML 1.0, XPath 1.0, SAX)
214
+ test_files: []