moxml 0.5.43 → 0.5.44
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/Gemfile +1 -1
- data/lib/moxml/adapter/leptris.rb +77 -12
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xpath/cache.rb +18 -25
- 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: cd5c99b547004d2f6521f882b006da2b612ee74cabf92775a3b061d9f096bc20
|
|
4
|
+
data.tar.gz: 652bce88c043068ecd97ef7008044557f764661a2e792f1128d435d4e83d715a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ba8297809db361e15cac3cbfbc132aa5545ceee6fe4e3d8b7df9a6250e7585ce930eaf7ba7d15de155975848f0d2deb205dcf8b0ebbdd3e00d96cbfc8d758633
|
|
7
|
+
data.tar.gz: 982406a0ff5f858aeed1569fd4ca1d4536bbb2b9841d791b4f560475b19b77641db0831fac7efd6760c3493216a9db8635de55d5e146f158cc0f16297fc97895
|
data/Gemfile
CHANGED
|
@@ -42,6 +42,17 @@ module Moxml
|
|
|
42
42
|
DIGEST_SUPPORTED =
|
|
43
43
|
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.99")
|
|
44
44
|
|
|
45
|
+
# 1.9.163.7 added the XPath seams (TODO.perf/15+16):
|
|
46
|
+
# Searchable#at_xpath materializes nodeset entry 0 and frees
|
|
47
|
+
# the handle in one C dispatch — no NodeSet container, no
|
|
48
|
+
# AutoPointer (2.8x on repeat at_xpath) — and
|
|
49
|
+
# CompiledXPath#eval_ptrs runs the compiled handle against
|
|
50
|
+
# raw pointers, skipping the per-call EvaluationContext
|
|
51
|
+
# (~2us on every multi-result query).
|
|
52
|
+
NATIVE_XPATH_SEAMS =
|
|
53
|
+
defined?(::Leptris::XML::Native) &&
|
|
54
|
+
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.163.7")
|
|
55
|
+
|
|
45
56
|
# Opt-in native read layer (leptris-ruby#185, bindings >= 1.9.162.6):
|
|
46
57
|
# TypedData node wrappers with C-bound hot reads and bulk
|
|
47
58
|
# children construction. Minted from #root downward; the C
|
|
@@ -1303,6 +1314,9 @@ module Moxml
|
|
|
1303
1314
|
# cached AST three times per xpath call cost ~23% of repeated
|
|
1304
1315
|
# selective queries, so the boolean caches beside it.
|
|
1305
1316
|
NATIVE_GATE_CACHE = XPath::Cache.new(100)
|
|
1317
|
+
# Root-context queries alone take the parent-axis guard; the
|
|
1318
|
+
# AST walk is per-expression, so the verdict caches too.
|
|
1319
|
+
PARENT_AXIS_CACHE = XPath::Cache.new(100)
|
|
1306
1320
|
|
|
1307
1321
|
def xpath(node, expression, namespaces = {})
|
|
1308
1322
|
node = to_binding(node) if NATIVE_READ_LAYER
|
|
@@ -1323,6 +1337,14 @@ module Moxml
|
|
|
1323
1337
|
# @return [Array, Object, nil] native results, or nil when the
|
|
1324
1338
|
# query must run on the Ruby engine
|
|
1325
1339
|
def native_xpath(node, expression, namespaces, first_only: false)
|
|
1340
|
+
# Canonical natives (the #219 unification) are bare TypedData
|
|
1341
|
+
# wrappers — no Searchable methods, no document handle for
|
|
1342
|
+
# the gates below. The memoized bridge recovers the binding
|
|
1343
|
+
# identity; without it every element-context query fell to
|
|
1344
|
+
# the Ruby engine (a silent native-path loss since #219).
|
|
1345
|
+
if NATIVE_READ_LAYER && node.is_a?(::Leptris::XML::NativeNode)
|
|
1346
|
+
node = to_binding(node)
|
|
1347
|
+
end
|
|
1326
1348
|
return nil unless native_context_node?(node)
|
|
1327
1349
|
return nil unless native_expression?(expression)
|
|
1328
1350
|
# The binding reports the root element parentless while moxml
|
|
@@ -1330,27 +1352,70 @@ module Moxml
|
|
|
1330
1352
|
# root element keep the Ruby engine.
|
|
1331
1353
|
if node.is_a?(::Leptris::XML::Element) &&
|
|
1332
1354
|
node.document&.root.equal?(node) &&
|
|
1333
|
-
|
|
1355
|
+
PARENT_AXIS_CACHE.get_or_set(expression) do
|
|
1356
|
+
expression_uses_parent_axis?(expression)
|
|
1357
|
+
end
|
|
1334
1358
|
return nil
|
|
1335
1359
|
end
|
|
1336
1360
|
|
|
1337
1361
|
compiled = NATIVE_XPATH_CACHE.get_or_set(expression) do
|
|
1338
1362
|
::Leptris::XML::XPath.compile(expression)
|
|
1339
1363
|
end
|
|
1340
|
-
|
|
1341
|
-
|
|
1364
|
+
document = node.is_a?(::Leptris::XML::Document) ? node : node.document
|
|
1365
|
+
|
|
1366
|
+
if namespaces && !namespaces.empty?
|
|
1367
|
+
# Namespace-bound evaluation has no raw-pointer entry —
|
|
1368
|
+
# Searchable resolves the ns set for both result shapes.
|
|
1369
|
+
result = if first_only
|
|
1370
|
+
node.at_xpath(expression, namespaces)
|
|
1371
|
+
else
|
|
1372
|
+
compiled.eval(node, namespaces)
|
|
1373
|
+
end
|
|
1374
|
+
return case result
|
|
1375
|
+
when ::Leptris::XML::NodeSet
|
|
1376
|
+
first_only ? result.first : result.extend(Moxml::LazyNodeSet)
|
|
1342
1377
|
else
|
|
1343
|
-
|
|
1378
|
+
result
|
|
1344
1379
|
end
|
|
1345
|
-
case result
|
|
1346
|
-
when ::Leptris::XML::NodeSet
|
|
1347
|
-
# The binding's #to_a mints one Ruby wrapper per result
|
|
1348
|
-
# node; hand the native set through so .size/.first stay
|
|
1349
|
-
# native-side (LazyNodeSet holds it unmaterialized).
|
|
1350
|
-
first_only ? result.first : result.extend(Moxml::LazyNodeSet)
|
|
1351
|
-
else
|
|
1352
|
-
result
|
|
1353
1380
|
end
|
|
1381
|
+
|
|
1382
|
+
unless NATIVE_XPATH_SEAMS
|
|
1383
|
+
result = compiled.eval(node)
|
|
1384
|
+
return case result
|
|
1385
|
+
when ::Leptris::XML::NodeSet
|
|
1386
|
+
first_only ? result.first : result.extend(Moxml::LazyNodeSet)
|
|
1387
|
+
else
|
|
1388
|
+
result
|
|
1389
|
+
end
|
|
1390
|
+
end
|
|
1391
|
+
|
|
1392
|
+
# eval_ptrs against raw pointers — the per-call
|
|
1393
|
+
# EvaluationContext that #eval builds costs ~2us of Ruby
|
|
1394
|
+
# on every query (leptris-ruby TODO.perf/15).
|
|
1395
|
+
doc_ptr = node.is_a?(::Leptris::XML::Document) ? node.c_ptr : document.c_ptr
|
|
1396
|
+
context_ptr = node.is_a?(::Leptris::XML::Document) ? nil : node.c_ptr
|
|
1397
|
+
result_ptr = compiled.eval_ptrs(doc_ptr, context_ptr)
|
|
1398
|
+
return nil if result_ptr.null?
|
|
1399
|
+
|
|
1400
|
+
if first_only
|
|
1401
|
+
# The single-result seam: nodeset entry 0 materializes
|
|
1402
|
+
# and frees in one C dispatch — no NodeSet container, no
|
|
1403
|
+
# AutoPointer (leptris-ruby TODO.perf/16). The module
|
|
1404
|
+
# object back means a scalar, whose wrap (and free)
|
|
1405
|
+
# stays with wrap_xpath_first_result.
|
|
1406
|
+
return ::Leptris::XML::Searchable.wrap_xpath_first_result(
|
|
1407
|
+
document, result_ptr
|
|
1408
|
+
)
|
|
1409
|
+
end
|
|
1410
|
+
|
|
1411
|
+
# The binding's #to_a mints one Ruby wrapper per result
|
|
1412
|
+
# node; hand the native set through so .size/.first stay
|
|
1413
|
+
# native-side (LazyNodeSet holds it unmaterialized).
|
|
1414
|
+
# Scalars pass through unwrapped-and-unextended.
|
|
1415
|
+
result = ::Leptris::XML::Searchable.wrap_xpath_result(
|
|
1416
|
+
document, result_ptr
|
|
1417
|
+
)
|
|
1418
|
+
result.is_a?(::Leptris::XML::NodeSet) ? result.extend(Moxml::LazyNodeSet) : result
|
|
1354
1419
|
rescue ::Leptris::XML::XPathError
|
|
1355
1420
|
# Not supported by the native engine — the Ruby engine is a
|
|
1356
1421
|
# full XPath 1.0 implementation, including Moxml's syntax
|
data/lib/moxml/version.rb
CHANGED
data/lib/moxml/xpath/cache.rb
CHANGED
|
@@ -11,8 +11,12 @@ module Moxml
|
|
|
11
11
|
# @param [Integer] max_size Maximum number of entries to cache
|
|
12
12
|
def initialize(max_size = DEFAULT_SIZE)
|
|
13
13
|
@max_size = max_size
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
# One insertion-ordered hash: re-inserting a key on access
|
|
15
|
+
# makes the first entry the least-recently-used. An
|
|
16
|
+
# Array-backed order list cost ~600ns per hit in O(n)
|
|
17
|
+
# deletes — the adapter's xpath path hits three caches per
|
|
18
|
+
# call.
|
|
19
|
+
@entries = {}
|
|
16
20
|
end
|
|
17
21
|
|
|
18
22
|
# Gets a value from the cache or sets it using the provided block.
|
|
@@ -21,11 +25,9 @@ module Moxml
|
|
|
21
25
|
# @yield Block to execute if key is not in cache
|
|
22
26
|
# @return [Object] Cached or newly computed value
|
|
23
27
|
def get_or_set(key)
|
|
24
|
-
if @
|
|
25
|
-
|
|
26
|
-
@
|
|
27
|
-
@access_order.push(key)
|
|
28
|
-
@cache[key]
|
|
28
|
+
if @entries.key?(key)
|
|
29
|
+
value = @entries.delete(key)
|
|
30
|
+
@entries[key] = value
|
|
29
31
|
else
|
|
30
32
|
value = yield
|
|
31
33
|
set(key, value)
|
|
@@ -39,16 +41,9 @@ module Moxml
|
|
|
39
41
|
# @param [Object] value
|
|
40
42
|
# @return [Object] The value
|
|
41
43
|
def set(key, value)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
# Remove least recently used
|
|
46
|
-
lru_key = @access_order.shift
|
|
47
|
-
@cache.delete(lru_key)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
@cache[key] = value
|
|
51
|
-
@access_order.push(key)
|
|
44
|
+
@entries.delete(key)
|
|
45
|
+
@entries[key] = value
|
|
46
|
+
@entries.shift if @entries.size > @max_size
|
|
52
47
|
value
|
|
53
48
|
end
|
|
54
49
|
|
|
@@ -57,26 +52,24 @@ module Moxml
|
|
|
57
52
|
# @param [Object] key
|
|
58
53
|
# @return [Object, nil]
|
|
59
54
|
def get(key)
|
|
60
|
-
return unless @
|
|
55
|
+
return unless @entries.key?(key)
|
|
61
56
|
|
|
62
|
-
@
|
|
63
|
-
@
|
|
64
|
-
@cache[key]
|
|
57
|
+
value = @entries.delete(key)
|
|
58
|
+
@entries[key] = value
|
|
65
59
|
end
|
|
66
60
|
|
|
67
61
|
# Clears the cache.
|
|
68
62
|
#
|
|
69
63
|
# @return [void]
|
|
70
64
|
def clear
|
|
71
|
-
@
|
|
72
|
-
@access_order.clear
|
|
65
|
+
@entries.clear
|
|
73
66
|
end
|
|
74
67
|
|
|
75
68
|
# Returns the current size of the cache.
|
|
76
69
|
#
|
|
77
70
|
# @return [Integer]
|
|
78
71
|
def size
|
|
79
|
-
@
|
|
72
|
+
@entries.size
|
|
80
73
|
end
|
|
81
74
|
|
|
82
75
|
# Checks if a key exists in the cache.
|
|
@@ -84,7 +77,7 @@ module Moxml
|
|
|
84
77
|
# @param [Object] key
|
|
85
78
|
# @return [Boolean]
|
|
86
79
|
def key?(key)
|
|
87
|
-
@
|
|
80
|
+
@entries.key?(key)
|
|
88
81
|
end
|
|
89
82
|
end
|
|
90
83
|
end
|