Class: Makiri::XPathContext
- Inherits:
-
Object
- Object
- Makiri::XPathContext
- Defined in:
- lib/makiri/xpath_context.rb,
ext/makiri/makiri.c
Overview
Per-query XPath evaluation context. Holds the context node, namespace bindings, and variable bindings, and evaluates expressions against them.
ctx = Makiri::XPathContext.new(doc)
ctx.register_namespace("svg", "http://www.w3.org/2000/svg")
ctx.evaluate("//svg:circle") # => Makiri::NodeSet
evaluate returns a NodeSet for node-set expressions, and a String, Float, or boolean for the corresponding scalar XPath types.
The bulk of the implementation lives in C (see ext/makiri/glue/ruby_xpath.c and ext/makiri/xpath/).
Class Method Summary collapse
-
.new(node, namespace_matching: :strict) ⇒ XPathContext
Build a context whose context node is
node(a Node or Document).
Instance Method Summary collapse
- #evaluate(*args) ⇒ Object
-
#node=(rb_node) ⇒ Object
XPathContext#node= : rebind the context node (must be in the same document), so the context can be reused to evaluate relative expressions against several nodes.
- #register_namespace(rb_prefix, rb_uri) ⇒ Object (also: #register_ns)
-
#register_namespaces(bindings) ⇒ self
Register several prefix => URI namespace bindings at once.
- #register_variable(rb_name, rb_value) ⇒ Object
-
#register_variables(bindings) ⇒ self
Register several name => value variable bindings at once.
Class Method Details
.new(node, namespace_matching: :strict) ⇒ XPathContext
Build a context whose context node is node (a Node or Document). namespace_matching: :strict (default) resolves unprefixed name tests in the HTML namespace (HTML5/WHATWG-faithful; SVG/MathML need a prefix); :lax makes unprefixed tests namespace-agnostic.
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'ext/makiri/glue/ruby_xpath.c', line 230
static VALUE
mkr_xpath_ctx_s_new(int argc, VALUE *argv, VALUE klass)
{
VALUE rb_node, opts;
rb_scan_args(argc, argv, "1:", &rb_node, &opts);
int lax = mkr_ns_matching_lax(opts);
if (!rb_obj_is_kind_of(rb_node, mkr_cNode)) {
rb_raise(rb_eTypeError, "expected a Makiri::Node");
}
VALUE document = mkr_node_document(rb_node);
mkr_xpath_ctx_data_t *d;
VALUE obj = TypedData_Make_Struct(klass, mkr_xpath_ctx_data_t,
&mkr_xpath_ctx_type, d);
d->ctx = NULL;
d->document = document;
d->node = rb_node;
d->cache = NULL;
d->cache_count = 0;
d->cache_cap = 0;
d->ctx = mkr_xpath_context_for(rb_node, document);
mkr_ctx_set_unprefixed_lax(d->ctx, lax);
return obj;
}
|
Instance Method Details
#evaluate(*args) ⇒ Object
590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 |
# File 'ext/makiri/glue/ruby_xpath.c', line 590
static VALUE
mkr_xpath_ctx_evaluate(int argc, VALUE *argv, VALUE self)
{
VALUE rb_expr, handler;
rb_scan_args(argc, argv, "11", &rb_expr, &handler);
mkr_xpath_ctx_data_t *d = mkr_xpath_ctx_unwrap(self);
mkr_ruby_borrowed_text_t ev = mkr_ruby_verified_text(rb_expr, "XPath expression");
mkr_xpath_error_t error = {0};
int owned = 0;
mkr_node_t *ast = mkr_ctx_cached_ast(d, mkr_verified_text_from_view(ev), &error, &owned);
RB_GC_GUARD(ev.value);
if (ast == NULL) {
mkr_xpath_raise(&error); /* parse error, never returns */
}
mkr_handler_bridge_t bridge = { handler, d->document };
int has_handler = !NIL_P(handler);
if (has_handler) {
mkr_xpath_context_set_user_data(d->ctx, &bridge);
mkr_xpath_set_func_resolver(d->ctx, mkr_handler_resolver);
}
mkr_xpath_value_t value = {0};
int rc = mkr_eval_compiled(d->ctx, ast, &value, &error);
if (has_handler) {
mkr_xpath_set_func_resolver(d->ctx, NULL);
mkr_xpath_context_set_user_data(d->ctx, NULL);
}
if (owned) {
mkr_node_free(ast);
}
if (rc != 0) {
mkr_xpath_raise(&error); /* never returns */
}
return mkr_xpath_value_to_ruby(&value, d->document);
}
|
#node=(rb_node) ⇒ Object
XPathContext#node= : rebind the context node (must be in the same document), so the context can be reused to evaluate relative expressions against several nodes. Namespace/variable registrations are preserved.
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 |
# File 'ext/makiri/glue/ruby_xpath.c', line 267
static VALUE
mkr_xpath_ctx_set_node(VALUE self, VALUE rb_node)
{
if (!rb_obj_is_kind_of(rb_node, mkr_cNode)) {
rb_raise(rb_eTypeError, "expected a Makiri::Node");
}
mkr_xpath_ctx_data_t *d = mkr_xpath_ctx_unwrap(self);
if (mkr_ctx_is_evaluating(d->ctx)) {
rb_raise(mkr_eError, "cannot change the context node while evaluating "
"(re-entrant mutation from a handler)");
}
if (mkr_node_document(rb_node) != d->document) {
rb_raise(mkr_eError, "context node must belong to the same document");
}
d->node = rb_node; /* keepalive; marked in mkr_xpath_ctx_mark */
/* Same-document is verified above, so rb_node is the context's representation;
* the engine (monomorphized per kind) takes the raw pointer. */
mkr_ctx_set_node(d->ctx, mkr_node_raw(rb_node));
return rb_node;
}
|
#register_namespace(rb_prefix, rb_uri) ⇒ Object Also known as: register_ns
628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 |
# File 'ext/makiri/glue/ruby_xpath.c', line 628
static VALUE
mkr_xpath_ctx_register_ns(VALUE self, VALUE rb_prefix, VALUE rb_uri)
{
mkr_xpath_ctx_data_t *d = mkr_xpath_ctx_unwrap(self);
if (mkr_ctx_is_evaluating(d->ctx)) {
rb_raise(mkr_eError, "cannot register a namespace while evaluating "
"(re-entrant mutation from a handler)");
}
mkr_ruby_borrowed_text_t pv = mkr_ruby_verified_text(rb_prefix, "namespace prefix");
mkr_ruby_borrowed_text_t uv = mkr_ruby_verified_text(rb_uri, "namespace URI");
int rc = mkr_xpath_register_ns(d->ctx, mkr_verified_text_from_view(pv),
mkr_verified_text_from_view(uv)); /* copies both */
RB_GC_GUARD(pv.value);
RB_GC_GUARD(uv.value);
if (rc != 0) {
rb_raise(mkr_eError, "failed to register namespace");
}
return self;
}
|
#register_namespaces(bindings) ⇒ self
Register several prefix => URI namespace bindings at once.
36 37 38 39 |
# File 'lib/makiri/xpath_context.rb', line 36 def register_namespaces(bindings) bindings.each { |prefix, uri| register_namespace(prefix.to_s, uri.to_s) } self end |
#register_variable(rb_name, rb_value) ⇒ Object
648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 |
# File 'ext/makiri/glue/ruby_xpath.c', line 648
static VALUE
mkr_xpath_ctx_register_variable(VALUE self, VALUE rb_name, VALUE rb_value)
{
mkr_xpath_ctx_data_t *d = mkr_xpath_ctx_unwrap(self);
if (mkr_ctx_is_evaluating(d->ctx)) {
rb_raise(mkr_eError, "cannot register a variable while evaluating "
"(re-entrant mutation from a handler)");
}
/* Coerce the value FIRST (rb_obj_as_string allocates = a GC point), so no
* borrowed name bytes are held across it. The value gets the stricter
* engine-string check (adds the byte cap on top of no-NUL / valid-UTF-8). */
VALUE value = rb_obj_as_string(rb_value);
mkr_ruby_borrowed_text_t nv = mkr_ruby_verified_text(rb_name, "variable name");
mkr_ruby_borrowed_text_t vv;
const char *bad =
mkr_ruby_try_verified_text(value, mkr_ctx_limits(d->ctx)->max_string_bytes, &vv);
if (bad != NULL) {
rb_raise(mkr_eError, "invalid variable value: %s", bad);
}
int rc = mkr_xpath_register_variable_string(d->ctx, mkr_verified_text_from_view(nv),
mkr_verified_text_from_view(vv)); /* copies both */
RB_GC_GUARD(nv.value);
RB_GC_GUARD(value);
if (rc != 0) {
rb_raise(mkr_eError, "failed to register variable");
}
return self;
}
|
#register_variables(bindings) ⇒ self
Register several name => value variable bindings at once.
44 45 46 47 |
# File 'lib/makiri/xpath_context.rb', line 44 def register_variables(bindings) bindings.each { |name, value| register_variable(name.to_s, value) } self end |