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.
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'ext/makiri/glue/ruby_xpath.c', line 208
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
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 |
# File 'ext/makiri/glue/ruby_xpath.c', line 554
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.
245 246 247 248 249 250 251 252 253 254 255 256 257 258 |
# File 'ext/makiri/glue/ruby_xpath.c', line 245
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_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 */
mkr_ctx_set_node(d->ctx, mkr_node_unwrap(rb_node));
return rb_node;
}
|
#register_namespace(rb_prefix, rb_uri) ⇒ Object Also known as: register_ns
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 |
# File 'ext/makiri/glue/ruby_xpath.c', line 592
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);
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.
28 29 30 31 |
# File 'lib/makiri/xpath_context.rb', line 28 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
608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 |
# File 'ext/makiri/glue/ruby_xpath.c', line 608
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);
mkr_ruby_borrowed_text_t nv = mkr_ruby_verified_text(rb_name, "variable name");
/* 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 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.
36 37 38 39 |
# File 'lib/makiri/xpath_context.rb', line 36 def register_variables(bindings) bindings.each { |name, value| register_variable(name.to_s, value) } self end |