Class: Makiri::XPathContext

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

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.

Returns:



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.

Parameters:

  • bindings (Hash{String => String})

Returns:

  • (self)


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.

Parameters:

  • bindings (Hash{String => Object})

Returns:

  • (self)


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