Module: Makiri::XML::NodeMethods

Included in:
Document
Defined in:
lib/makiri/xml/node_methods.rb,
ext/makiri/makiri.c

Overview

Ruby additions over the C-defined XML node readers, mirroring Makiri::HTML::NodeMethods so the XML node surface matches the HTML one for the methods consumers (e.g. Dommy) rely on. Each is guarded with ‘method_defined?` so a future native implementation on this module takes precedence rather than being shadowed.

Instance Method Summary collapse

Instance Method Details

#<<(arg) ⇒ Object

element << node -> self (Nokogiri’s <<: append and return the receiver).



1208
1209
1210
1211
1212
1213
# File 'ext/makiri/glue/ruby_xml_node.c', line 1208

static VALUE
mkr_xml_node_lshift(VALUE self, VALUE arg)
{
    mkr_xml_node_insert(self, arg, MKR_INS_CHILD);
    return self;
}

#==(other) ⇒ Object

Pointer identity: equal iff both wrappers resolve to the same node pointer (an HTML node is thus never equal to an XML node).



133
134
135
136
137
138
139
140
# File 'ext/makiri/glue/ruby_node.c', line 133

VALUE
mkr_node_equals(VALUE self, VALUE other)
{
    if (!rb_obj_is_kind_of(other, mkr_cNode)) {
        return Qfalse;
    }
    return mkr_node_id(self) == mkr_node_id(other) ? Qtrue : Qfalse;
}

#[](rb_name) ⇒ Object

—- attributes —-



336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
# File 'ext/makiri/glue/ruby_xml_node.c', line 336

static VALUE
mkr_xml_node_aref(VALUE self, VALUE rb_name)
{
    mkr_xml_node_t *n = mkr_xml_node_unwrap(self);
    if (n->type != MKR_XML_NODE_TYPE_ELEMENT) return Qnil;
    mkr_ruby_borrowed_text_t nv = mkr_ruby_verified_text(rb_name, "attribute name");
    VALUE out = Qnil;
    for (mkr_xml_node_t *a = n->attrs; a != NULL; a = a->next) {
        if (mkr_bytes_eq(a->qname, a->qname_len, nv.ptr, nv.len)) {
            out = rb_utf8_str_new(a->value ? a->value : "", (long)a->value_len);
            break;
        }
    }
    RB_GC_GUARD(nv.value);
    return out;
}

#[]=(rb_name, rb_value) ⇒ Object

element = value -> value. Adds or replaces the attribute.



1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
# File 'ext/makiri/glue/ruby_xml_node.c', line 1004

static VALUE
mkr_xml_node_aset(VALUE self, VALUE rb_name, VALUE rb_value)
{
    mkr_xml_node_t *n = mkr_xml_node_unwrap_mutable(self);
    if (n->type != MKR_XML_NODE_TYPE_ELEMENT) {
        rb_raise(mkr_eError, "cannot set an attribute on a non-element node");
    }
    mkr_ruby_borrowed_text_t nv = mkr_ruby_verified_text(rb_name, "attribute name");
    mkr_ruby_borrowed_text_t vv = mkr_ruby_verified_text(rb_value, "attribute value");
    mkr_xml_mut_status_t st = mkr_xml_set_attribute(
        mkr_xml_node_xdoc(self), n,
        nv.ptr, mkr_xml_u32_len(nv.len), vv.ptr, mkr_xml_u32_len(vv.len), NULL);
    RB_GC_GUARD(nv.value);
    RB_GC_GUARD(vv.value);
    mkr_xml_mut_check(st);
    return rb_value;
}

#add_child(arg) ⇒ Object

element.add_child(node) -> the inserted node.



1199
# File 'ext/makiri/glue/ruby_xml_node.c', line 1199

static VALUE mkr_xml_node_add_child(VALUE self, VALUE arg) { return mkr_xml_node_insert(self, arg, MKR_INS_CHILD); }

#add_next_sibling(arg) ⇒ Object

node.add_next_sibling(other) / node.after(other) -> the inserted node.



1203
# File 'ext/makiri/glue/ruby_xml_node.c', line 1203

static VALUE mkr_xml_node_after(VALUE self, VALUE arg)     { return mkr_xml_node_insert(self, arg, MKR_INS_AFTER); }

#add_previous_sibling(arg) ⇒ Object

node.add_previous_sibling(other) / node.before(other) -> the inserted node.



1201
# File 'ext/makiri/glue/ruby_xml_node.c', line 1201

static VALUE mkr_xml_node_before(VALUE self, VALUE arg)    { return mkr_xml_node_insert(self, arg, MKR_INS_BEFORE); }

#after(arg) ⇒ Object

node.add_next_sibling(other) / node.after(other) -> the inserted node.



1203
# File 'ext/makiri/glue/ruby_xml_node.c', line 1203

static VALUE mkr_xml_node_after(VALUE self, VALUE arg)     { return mkr_xml_node_insert(self, arg, MKR_INS_AFTER); }

#ancestorsObject



14
15
16
17
18
19
20
21
22
# File 'lib/makiri/xml/node_methods.rb', line 14

def ancestors
  out = []
  node = parent
  while node
    out << node if node.node_type == 1
    node = node.respond_to?(:parent) ? node.parent : nil
  end
  out
end

#at_css(selector, ns = nil) ⇒ Object

First descendant matching selector, or nil. @return [Makiri::Node, nil]



52
53
54
# File 'lib/makiri/xml/node_methods.rb', line 52

def at_css(selector, ns = nil)
  _at_css(selector.to_s, _css_namespaces(ns))
end

#at_xpath(*args) ⇒ Object



298
299
300
301
302
303
304
# File 'ext/makiri/glue/ruby_xml.c', line 298

static VALUE
mkr_xml_doc_at_xpath(int argc, VALUE *argv, VALUE self)
{
    VALUE expr, ns;
    rb_scan_args(argc, argv, "11", &expr, &ns);
    return mkr_xml_doc_xpath_run(self, expr, ns, 1);
}

#attribute_nodesObject



353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'ext/makiri/glue/ruby_xml_node.c', line 353

static VALUE
mkr_xml_node_attribute_nodes(VALUE self)
{
    VALUE doc = mkr_xml_node_document(self);
    VALUE set = mkr_node_set_new(doc);
    mkr_xml_node_t *n = mkr_xml_node_unwrap(self);
    if (n->type == MKR_XML_NODE_TYPE_ELEMENT) {
        for (mkr_xml_node_t *a = n->attrs; a != NULL; a = a->next) {
            mkr_node_set_push(set, (mkr_raw_node_t *)a);
        }
    }
    return set;
}

#before(arg) ⇒ Object

node.add_previous_sibling(other) / node.before(other) -> the inserted node.



1201
# File 'ext/makiri/glue/ruby_xml_node.c', line 1201

static VALUE mkr_xml_node_before(VALUE self, VALUE arg)    { return mkr_xml_node_insert(self, arg, MKR_INS_BEFORE); }

#canonicalize(comments: false) ⇒ String

Inclusive Canonical XML 1.0 (UTF-8). A Document canonicalizes its element + top-level PIs (and comments when requested); any other node canonicalizes its subtree, with the apex inheriting the ancestors’ in-scope namespaces.

Returns:

  • (String)


859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
# File 'ext/makiri/glue/ruby_xml_node.c', line 859

static VALUE
mkr_xml_node_canonicalize(int argc, VALUE *argv, VALUE self)
{
    VALUE opts;
    rb_scan_args(argc, argv, "0:", &opts);
    int comments = !NIL_P(opts) && RTEST(rb_hash_aref(opts, ID2SYM(rb_intern("comments"))));

    mkr_xml_node_t *n = mkr_xml_node_unwrap(self);
    mkr_buf_t buf;
    mkr_buf_init(&buf, mkr_xml_serialize_cap(self));   /* fail closed past the cap, never OOM */
    int rc = 0;

    if (rb_obj_is_kind_of(self, mkr_cXmlDocument)) {
        /* §2.4: a PI/comment before the document element is followed by #xA, one
         * after is preceded by #xA; the element itself has no surrounding line. */
        int seen_root = 0;
        for (mkr_xml_node_t *c = n->first_child; rc == 0 && c != NULL; c = c->next) {
            if (c->type == MKR_XML_NODE_TYPE_ELEMENT) {
                rc = mkr_c14n_node(&buf, c, 1, comments);   /* the root element is the apex */
                seen_root = 1;
            } else if (c->type == MKR_XML_NODE_TYPE_PI ||
                       (c->type == MKR_XML_NODE_TYPE_COMMENT && comments)) {
                if (seen_root && rc == 0) rc = (mkr_buf_append(&buf, "\n", 1) == MKR_OK) ? 0 : -1;
                if (rc == 0) rc = mkr_c14n_node(&buf, c, 0, comments);
                if (!seen_root && rc == 0) rc = (mkr_buf_append(&buf, "\n", 1) == MKR_OK) ? 0 : -1;
            }
        }
    } else {
        rc = mkr_c14n_node(&buf, n, 1, comments);  /* the node is the apex (inherits ancestors' ns) */
    }

    if (rc != 0) {
        mkr_buf_free(&buf);
        rb_raise(mkr_eError, "failed to canonicalize XML: output exceeded the size limit or out of memory");
    }
    VALUE str = rb_utf8_str_new(buf.len ? buf.data : "", (long)buf.len);
    mkr_buf_free(&buf);
    return str;
}

#childObject



320
# File 'ext/makiri/glue/ruby_xml_node.c', line 320

static VALUE mkr_xml_node_first_child(VALUE self) { return mkr_xml_wrap_rel(self, mkr_xml_node_unwrap(self)->first_child); }

#childrenObject



323
324
325
326
327
328
329
330
331
332
# File 'ext/makiri/glue/ruby_xml_node.c', line 323

static VALUE
mkr_xml_node_children(VALUE self)
{
    VALUE doc = mkr_xml_node_document(self);
    VALUE set = mkr_node_set_new(doc);
    for (mkr_xml_node_t *c = mkr_xml_node_unwrap(self)->first_child; c != NULL; c = c->next) {
        mkr_node_set_push(set, (mkr_raw_node_t *)c);
    }
    return set;
}

#clone_node(*args) ⇒ Object

clone_node(deep = false) -> a detached copy of this node in the same document (element/attribute name case, namespaces, and the CDATA node type preserved); deep copies the whole subtree. Backs Node#dup / #clone and DOM cloneNode.



1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
# File 'ext/makiri/glue/ruby_xml_node.c', line 1245

static VALUE
mkr_xml_node_clone_node(int argc, VALUE *argv, VALUE self)
{
    VALUE rb_deep;
    rb_scan_args(argc, argv, "01", &rb_deep);
    mkr_xml_node_t *out = NULL;
    mkr_xml_mut_check(mkr_xml_clone_node(mkr_xml_node_xdoc(self),
                                         mkr_xml_node_unwrap(self),
                                         RTEST(rb_deep), &out));
    return mkr_xml_wrap_rel(self, out);
}

#collect_namespacesObject



242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'ext/makiri/glue/ruby_xml_node.c', line 242

static VALUE
mkr_xml_node_collect_namespaces(VALUE self)
{
    mkr_xml_node_t *n = mkr_xml_node_unwrap(self);
    mkr_xml_node_t *root = n;
    while (root->parent != NULL) root = root->parent;   /* the DOCUMENT node */
    VALUE h = rb_hash_new();
    /* iterative pre-order over the whole tree (parent-pointer walk, no recursion) */
    for (mkr_xml_node_t *cur = root; cur != NULL;) {
        if (cur->type == MKR_XML_NODE_TYPE_ELEMENT) {
            for (mkr_xml_node_t *a = cur->attrs; a != NULL; a = a->next) {
                const char *p, *u; uint32_t pl, ul;
                if (!mkr_xml_node_xmlns_decl(a, &p, &pl, &u, &ul)) continue;
                rb_hash_aset(h, rb_utf8_str_new(a->qname, (long)a->qname_len),
                             rb_utf8_str_new(u, (long)ul));
            }
        }
        if (cur->first_child != NULL) { cur = cur->first_child; continue; }
        while (cur != root && cur->next == NULL) cur = cur->parent;
        if (cur == root) break;
        cur = cur->next;
    }
    return h;
}

#contentObject

—- content / text —-



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'ext/makiri/glue/ruby_xml_node.c', line 275

static VALUE
mkr_xml_node_content(VALUE self)
{
    mkr_xml_node_t *n = mkr_xml_node_unwrap(self);
    /* leaf data nodes return their own value verbatim. */
    if (n->type == MKR_XML_NODE_TYPE_TEXT || n->type == MKR_XML_NODE_TYPE_CDATA_SECTION
        || n->type == MKR_XML_NODE_TYPE_COMMENT || n->type == MKR_XML_NODE_TYPE_ATTRIBUTE
        || n->type == MKR_XML_NODE_TYPE_PI) {
        return rb_utf8_str_new(n->value ? n->value : "", (long)n->value_len);
    }
    /* element / document: concatenate every TEXT/CDATA descendant in document
     * order. Iterative pre-order (parent-pointer) walk - no C recursion, so a
     * deep tree cannot overflow the stack. */
    VALUE str = rb_utf8_str_new("", 0);
    mkr_xml_node_t *cur = n->first_child;
    while (cur != NULL) {
        if ((cur->type == MKR_XML_NODE_TYPE_TEXT || cur->type == MKR_XML_NODE_TYPE_CDATA_SECTION) && cur->value_len) {
            rb_str_cat(str, cur->value, (long)cur->value_len);
        }
        if (cur->first_child != NULL) { cur = cur->first_child; continue; }
        while (cur != NULL && cur != n && cur->next == NULL) cur = cur->parent;
        if (cur == NULL || cur == n) break;
        cur = cur->next;
    }
    return str;
}

#content=(rb_text) ⇒ Object

node.content = text -> text. For an element: replace its children with one text node (the string is stored verbatim and escaped on serialization). For a text/cdata/comment/PI leaf: set its data.



1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
# File 'ext/makiri/glue/ruby_xml_node.c', line 1087

static VALUE
mkr_xml_node_set_content(VALUE self, VALUE rb_text)
{
    mkr_xml_node_t *n = mkr_xml_node_unwrap_mutable(self);
    mkr_ruby_borrowed_text_t tv = mkr_ruby_verified_text(rb_text, "node content");
    mkr_xml_mut_status_t st = mkr_xml_set_content(
        mkr_xml_node_xdoc(self), n, tv.ptr, mkr_xml_u32_len(tv.len));
    RB_GC_GUARD(tv.value);
    mkr_xml_mut_check(st);
    return rb_text;
}

#css(selector, ns = nil) ⇒ Makiri::NodeSet

CSS selector queries over XML, lowered to the native XPath engine (so matching is case-sensitive and namespace-aware, unlike a Lexbor HTML matcher). Nokogiri-compatible namespaces: the document’s in-scope declarations are collected automatically (a bare type selector binds to the default namespace), and an optional ns hash of => uri supplements/overrides them.

doc.css("entry")               # default-namespace bound (Atom/RSS just work)
doc.css("a|entry", "a" => uri) # explicit prefix

Returns:



47
48
49
# File 'lib/makiri/xml/node_methods.rb', line 47

def css(selector, ns = nil)
  _css(selector.to_s, _css_namespaces(ns))
end

#delete(rb_name) ⇒ Object

element.delete(name) -> self. Removes the attribute if present (no-op otherwise).



1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
# File 'ext/makiri/glue/ruby_xml_node.c', line 1073

static VALUE
mkr_xml_node_delete(VALUE self, VALUE rb_name)
{
    mkr_xml_node_t *n = mkr_xml_node_unwrap_mutable(self);
    if (n->type != MKR_XML_NODE_TYPE_ELEMENT) return self;
    mkr_ruby_borrowed_text_t nv = mkr_ruby_verified_text(rb_name, "attribute name");
    mkr_xml_remove_attribute(n, nv.ptr, mkr_xml_u32_len(nv.len));
    RB_GC_GUARD(nv.value);
    return self;
}

#documentObject



367
368
369
370
371
# File 'ext/makiri/glue/ruby_xml_node.c', line 367

static VALUE
mkr_xml_node_get_document(VALUE self)
{
    return mkr_xml_node_document(self);
}

#element_childrenObject

element_children -> NodeSet of the child element nodes (nodeType 1) only, in document order (the counterpart of HTML’s #element_children).



1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
# File 'ext/makiri/glue/ruby_xml_node.c', line 1229

static VALUE
mkr_xml_node_element_children(VALUE self)
{
    VALUE doc = mkr_xml_node_document(self);
    VALUE set = mkr_node_set_new(doc);
    for (mkr_xml_node_t *c = mkr_xml_node_unwrap(self)->first_child; c != NULL; c = c->next) {
        if (c->type == MKR_XML_NODE_TYPE_ELEMENT) {
            mkr_node_set_push(set, (mkr_raw_node_t *)c);
        }
    }
    return set;
}

#eql?(other) ⇒ Boolean

Pointer identity: equal iff both wrappers resolve to the same node pointer (an HTML node is thus never equal to an XML node).

Returns:

  • (Boolean)


133
134
135
136
137
138
139
140
# File 'ext/makiri/glue/ruby_node.c', line 133

VALUE
mkr_node_equals(VALUE self, VALUE other)
{
    if (!rb_obj_is_kind_of(other, mkr_cNode)) {
        return Qfalse;
    }
    return mkr_node_id(self) == mkr_node_id(other) ? Qtrue : Qfalse;
}

#hashObject

Stable hash derived from the node pointer, so a == b implies a.hash == b.hash even across separately-created wrappers. Shares the pointer value with #pointer_id.



155
156
157
158
159
# File 'ext/makiri/glue/ruby_node.c', line 155

VALUE
mkr_node_hash(VALUE self)
{
    return mkr_node_pointer_id(self);
}

#inner_html(*args) ⇒ Object

—- fail-closed guard for the unsupported serialization surface —-

HTML serialization (to_html/inner_html/outer_html) would silently misbehave on XML (escaping / CDATA / void elements differ), so it is an explicit NotImplementedError rather than a wrong result. Use #to_xml. (CSS selectors, once unsupported here, are now lowered to the native XPath engine - see ruby_xml.c.)



906
907
908
909
910
911
912
913
# File 'ext/makiri/glue/ruby_xml_node.c', line 906

static VALUE
mkr_xml_node_no_serialize(int argc, VALUE *argv, VALUE self)
{
    (void)argc; (void)argv; (void)self;
    rb_raise(rb_eNotImpError,
             "Makiri::XML does not HTML-serialize (to_html / inner_html / "
             "outer_html); use #to_xml for XML output.");
}

#inner_textObject

—- content / text —-



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'ext/makiri/glue/ruby_xml_node.c', line 275

static VALUE
mkr_xml_node_content(VALUE self)
{
    mkr_xml_node_t *n = mkr_xml_node_unwrap(self);
    /* leaf data nodes return their own value verbatim. */
    if (n->type == MKR_XML_NODE_TYPE_TEXT || n->type == MKR_XML_NODE_TYPE_CDATA_SECTION
        || n->type == MKR_XML_NODE_TYPE_COMMENT || n->type == MKR_XML_NODE_TYPE_ATTRIBUTE
        || n->type == MKR_XML_NODE_TYPE_PI) {
        return rb_utf8_str_new(n->value ? n->value : "", (long)n->value_len);
    }
    /* element / document: concatenate every TEXT/CDATA descendant in document
     * order. Iterative pre-order (parent-pointer) walk - no C recursion, so a
     * deep tree cannot overflow the stack. */
    VALUE str = rb_utf8_str_new("", 0);
    mkr_xml_node_t *cur = n->first_child;
    while (cur != NULL) {
        if ((cur->type == MKR_XML_NODE_TYPE_TEXT || cur->type == MKR_XML_NODE_TYPE_CDATA_SECTION) && cur->value_len) {
            rb_str_cat(str, cur->value, (long)cur->value_len);
        }
        if (cur->first_child != NULL) { cur = cur->first_child; continue; }
        while (cur != NULL && cur != n && cur->next == NULL) cur = cur->parent;
        if (cur == NULL || cur == n) break;
        cur = cur->next;
    }
    return str;
}

#key?(name) ⇒ Boolean Also known as: has_attribute?

Returns:

  • (Boolean)


28
29
30
31
# File 'lib/makiri/xml/node_methods.rb', line 28

def key?(name)
  wanted = name.to_s
  attribute_nodes.any? { |attr| attr.name == wanted }
end

#last_element_childObject



321
# File 'ext/makiri/glue/ruby_xml_node.c', line 321

static VALUE mkr_xml_node_last_child(VALUE self)  { return mkr_xml_wrap_rel(self, mkr_xml_node_unwrap(self)->last_child); }

#local_nameObject



122
123
124
125
126
127
128
129
130
# File 'ext/makiri/glue/ruby_xml_node.c', line 122

static VALUE
mkr_xml_node_local_name(VALUE self)
{
    mkr_xml_node_t *n = mkr_xml_node_unwrap(self);
    if (n->type == MKR_XML_NODE_TYPE_ELEMENT || n->type == MKR_XML_NODE_TYPE_ATTRIBUTE) {
        return rb_utf8_str_new(n->local, (long)n->local_len);
    }
    return Qnil;
}

#matches?(selector, ns = nil) ⇒ Boolean

Whether this node matches selector (full selector, combinators included).

Returns:

  • (Boolean)


58
59
60
# File 'lib/makiri/xml/node_methods.rb', line 58

def matches?(selector, ns = nil)
  _css_matches(selector.to_s, _css_namespaces(ns))
end

#nameObject

—- name / namespace —-



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'ext/makiri/glue/ruby_xml_node.c', line 83

static VALUE
mkr_xml_node_name(VALUE self)
{
    mkr_xml_node_t *n = mkr_xml_node_unwrap(self);
    switch (n->type) {
    case MKR_XML_NODE_TYPE_ELEMENT:
    case MKR_XML_NODE_TYPE_ATTRIBUTE: return rb_utf8_str_new(n->qname, (long)n->qname_len);
    case MKR_XML_NODE_TYPE_PI:        return rb_utf8_str_new(n->local, (long)n->local_len); /* target */
    case MKR_XML_NODE_TYPE_TEXT:      return rb_utf8_str_new_cstr("text");
    case MKR_XML_NODE_TYPE_CDATA_SECTION:     return rb_utf8_str_new_cstr("#cdata-section");
    case MKR_XML_NODE_TYPE_COMMENT:   return rb_utf8_str_new_cstr("comment");
    case MKR_XML_NODE_TYPE_DOCUMENT_TYPE: return rb_utf8_str_new(n->local, (long)n->local_len); /* the DOCTYPE name */
    case MKR_XML_NODE_TYPE_DOCUMENT_FRAGMENT: return rb_utf8_str_new_cstr("#document-fragment");
    default:               return rb_utf8_str_new_cstr("document");
    }
}

#name=(rb_name) ⇒ Object

node.name = new_name -> new_name. Renames an element or attribute in place (identity + tree position preserved); the namespace is re-resolved against the node’s in-scope declarations.



1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
# File 'ext/makiri/glue/ruby_xml_node.c', line 1102

static VALUE
mkr_xml_node_set_name(VALUE self, VALUE rb_name)
{
    mkr_xml_node_t *n = mkr_xml_node_unwrap_mutable(self);
    mkr_ruby_borrowed_text_t nv = mkr_ruby_verified_text(rb_name, "node name");
    mkr_xml_mut_status_t st = mkr_xml_rename(
        mkr_xml_node_xdoc(self), n, nv.ptr, mkr_xml_u32_len(nv.len));
    RB_GC_GUARD(nv.value);
    mkr_xml_mut_check(st);
    return rb_name;
}

#namespaceObject

The xmlns-declaration detector lives in the node layer (mkr_xml_node_xmlns_decl) so the namespace introspection, the C14N walk, and the mutation namespace resolver all share one definition.



194
195
196
197
198
199
200
201
202
203
204
# File 'ext/makiri/glue/ruby_xml_node.c', line 194

static VALUE
mkr_xml_node_namespace(VALUE self)
{
    mkr_xml_node_t *n = mkr_xml_node_unwrap(self);
    if ((n->type == MKR_XML_NODE_TYPE_ELEMENT || n->type == MKR_XML_NODE_TYPE_ATTRIBUTE)
        && n->ns_uri_len > 0) {
        VALUE prefix = n->prefix_len ? rb_utf8_str_new(n->prefix, (long)n->prefix_len) : Qnil;
        return mkr_ns_new(prefix, rb_utf8_str_new(n->ns_uri, (long)n->ns_uri_len));
    }
    return Qnil;
}

#namespace_definitionsObject



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'ext/makiri/glue/ruby_xml_node.c', line 206

static VALUE
mkr_xml_node_namespace_definitions(VALUE self)
{
    mkr_xml_node_t *n = mkr_xml_node_unwrap(self);
    VALUE arr = rb_ary_new();
    if (n->type == MKR_XML_NODE_TYPE_ELEMENT) {
        for (mkr_xml_node_t *a = n->attrs; a != NULL; a = a->next) {
            const char *p, *u; uint32_t pl, ul;
            if (mkr_xml_node_xmlns_decl(a, &p, &pl, &u, &ul)) {
                VALUE prefix = pl ? rb_utf8_str_new(p, (long)pl) : Qnil;
                rb_ary_push(arr, mkr_ns_new(prefix, rb_utf8_str_new(u, (long)ul)));
            }
        }
    }
    return arr;
}

#namespace_uriObject



139
140
141
142
143
144
# File 'ext/makiri/glue/ruby_xml_node.c', line 139

static VALUE
mkr_xml_node_namespace_uri(VALUE self)
{
    mkr_xml_node_t *n = mkr_xml_node_unwrap(self);
    return n->ns_uri_len ? rb_utf8_str_new(n->ns_uri, (long)n->ns_uri_len) : Qnil;
}

#namespacesObject



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'ext/makiri/glue/ruby_xml_node.c', line 223

static VALUE
mkr_xml_node_namespaces(VALUE self)
{
    mkr_xml_node_t *n = mkr_xml_node_unwrap(self);
    VALUE h = rb_hash_new();
    for (mkr_xml_node_t *e = n; e != NULL; e = e->parent) {   /* inner scope wins */
        if (e->type != MKR_XML_NODE_TYPE_ELEMENT) continue;
        for (mkr_xml_node_t *a = e->attrs; a != NULL; a = a->next) {
            const char *p, *u; uint32_t pl, ul;
            if (!mkr_xml_node_xmlns_decl(a, &p, &pl, &u, &ul)) continue;
            VALUE key = rb_utf8_str_new(a->qname, (long)a->qname_len); /* "xmlns" / "xmlns:p" */
            if (rb_hash_lookup2(h, key, Qundef) == Qundef) {
                rb_hash_aset(h, key, rb_utf8_str_new(u, (long)ul));
            }
        }
    }
    return h;
}

#nextObject



318
# File 'ext/makiri/glue/ruby_xml_node.c', line 318

static VALUE mkr_xml_node_next(VALUE self)     { return mkr_xml_wrap_rel(self, mkr_xml_node_unwrap(self)->next); }

#next_siblingObject



318
# File 'ext/makiri/glue/ruby_xml_node.c', line 318

static VALUE mkr_xml_node_next(VALUE self)     { return mkr_xml_wrap_rel(self, mkr_xml_node_unwrap(self)->next); }

#node_typeObject



267
268
269
270
271
# File 'ext/makiri/glue/ruby_xml_node.c', line 267

static VALUE
mkr_xml_node_node_type(VALUE self)
{
    return INT2NUM((int)mkr_xml_node_unwrap(self)->type);
}

#outer_html(*args) ⇒ Object

—- fail-closed guard for the unsupported serialization surface —-

HTML serialization (to_html/inner_html/outer_html) would silently misbehave on XML (escaping / CDATA / void elements differ), so it is an explicit NotImplementedError rather than a wrong result. Use #to_xml. (CSS selectors, once unsupported here, are now lowered to the native XPath engine - see ruby_xml.c.)



906
907
908
909
910
911
912
913
# File 'ext/makiri/glue/ruby_xml_node.c', line 906

static VALUE
mkr_xml_node_no_serialize(int argc, VALUE *argv, VALUE self)
{
    (void)argc; (void)argv; (void)self;
    rb_raise(rb_eNotImpError,
             "Makiri::XML does not HTML-serialize (to_html / inner_html / "
             "outer_html); use #to_xml for XML output.");
}

#parentObject



317
# File 'ext/makiri/glue/ruby_xml_node.c', line 317

static VALUE mkr_xml_node_parent(VALUE self)   { return mkr_xml_wrap_rel(self, mkr_xml_node_unwrap(self)->parent); }

#pointer_idObject

Nokogiri-compatible identity: the underlying node pointer as an Integer. Stable for the node’s lifetime and unique among currently-live nodes; a freed-then-reallocated node may reuse an address (same caveat as Nokogiri::XML::Node#pointer_id). a.pointer_id == b.pointer_id iff a.eql?(b).



146
147
148
149
150
# File 'ext/makiri/glue/ruby_node.c', line 146

VALUE
mkr_node_pointer_id(VALUE self)
{
    return ULL2NUM((unsigned long long)mkr_node_id(self));
}

#prefixObject



132
133
134
135
136
137
# File 'ext/makiri/glue/ruby_xml_node.c', line 132

static VALUE
mkr_xml_node_prefix(VALUE self)
{
    mkr_xml_node_t *n = mkr_xml_node_unwrap(self);
    return n->prefix_len ? rb_utf8_str_new(n->prefix, (long)n->prefix_len) : Qnil;
}

#previousObject



319
# File 'ext/makiri/glue/ruby_xml_node.c', line 319

static VALUE mkr_xml_node_previous(VALUE self) { return mkr_xml_wrap_rel(self, mkr_xml_node_unwrap(self)->prev); }

#previous_siblingObject



319
# File 'ext/makiri/glue/ruby_xml_node.c', line 319

static VALUE mkr_xml_node_previous(VALUE self) { return mkr_xml_wrap_rel(self, mkr_xml_node_unwrap(self)->prev); }

#removeObject

node.remove / node.unlink -> node. Detach from the tree (or, for an attribute, from its owner element); the node stays usable.



990
991
992
993
994
995
996
997
998
999
1000
1001
# File 'ext/makiri/glue/ruby_xml_node.c', line 990

static VALUE
mkr_xml_node_remove(VALUE self)
{
    if (rb_obj_is_kind_of(self, mkr_cXmlDocument)) {
        rb_raise(mkr_eError, "cannot remove the document node");
    }
    mkr_xml_node_t *n = mkr_xml_node_unwrap_mutable(self);
    mkr_xml_doc_t *xdoc = mkr_xml_node_xdoc(self);
    if (xdoc != NULL && n == xdoc->root) xdoc->root = NULL;   /* detaching the root element */
    mkr_xml_detach(n);
    return self;
}

#remove_attribute(rb_name) ⇒ Object

element.delete(name) -> self. Removes the attribute if present (no-op otherwise).



1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
# File 'ext/makiri/glue/ruby_xml_node.c', line 1073

static VALUE
mkr_xml_node_delete(VALUE self, VALUE rb_name)
{
    mkr_xml_node_t *n = mkr_xml_node_unwrap_mutable(self);
    if (n->type != MKR_XML_NODE_TYPE_ELEMENT) return self;
    mkr_ruby_borrowed_text_t nv = mkr_ruby_verified_text(rb_name, "attribute name");
    mkr_xml_remove_attribute(n, nv.ptr, mkr_xml_u32_len(nv.len));
    RB_GC_GUARD(nv.value);
    return self;
}

#remove_attribute_ns(rb_ns, rb_local) ⇒ Object

element.remove_attribute_ns(namespace_or_nil, local_name) -> self.



1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
# File 'ext/makiri/glue/ruby_xml_node.c', line 1054

static VALUE
mkr_xml_node_remove_attribute_ns(VALUE self, VALUE rb_ns, VALUE rb_local)
{
    mkr_xml_node_t *n = mkr_xml_node_unwrap_mutable(self);
    if (n->type != MKR_XML_NODE_TYPE_ELEMENT) return self;
    mkr_ruby_borrowed_text_t lv = mkr_ruby_verified_text(rb_local, "attribute local name");
    mkr_ruby_borrowed_text_t nv = {0};
    const char *ns_ptr = NULL; uint32_t ns_len = 0;
    if (!NIL_P(rb_ns)) {
        nv = mkr_ruby_verified_text(rb_ns, "namespace");
        ns_ptr = nv.ptr; ns_len = mkr_xml_u32_len(nv.len);
    }
    mkr_xml_remove_attribute_ns(n, ns_ptr, ns_len, lv.ptr, mkr_xml_u32_len(lv.len));
    RB_GC_GUARD(lv.value);
    RB_GC_GUARD(nv.value);
    return self;
}

#replace(arg) ⇒ Object

node.replace(other) -> the inserted node (the replaced node is detached).



1205
# File 'ext/makiri/glue/ruby_xml_node.c', line 1205

static VALUE mkr_xml_node_replace(VALUE self, VALUE arg)   { return mkr_xml_node_insert(self, arg, MKR_INS_REPLACE); }

#set_attribute_ns(rb_ns, rb_qname, rb_value) ⇒ Object

element.set_attribute_ns(namespace_or_nil, qualified_name, value) -> value.

Stores the attribute keyed on (explicit namespace, local name) - the DOM key - with its qualified name case-preserved. A null/“” namespace is the null namespace. xmlns declarations pass through as ordinary attributes in the xmlns namespace.



1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
# File 'ext/makiri/glue/ruby_xml_node.c', line 1028

static VALUE
mkr_xml_node_set_attribute_ns(VALUE self, VALUE rb_ns, VALUE rb_qname, VALUE rb_value)
{
    mkr_xml_node_t *n = mkr_xml_node_unwrap_mutable(self);
    if (n->type != MKR_XML_NODE_TYPE_ELEMENT) {
        rb_raise(mkr_eError, "cannot set an attribute on a non-element node");
    }
    mkr_ruby_borrowed_text_t qv = mkr_ruby_verified_text(rb_qname, "attribute qualified name");
    mkr_ruby_borrowed_text_t vv = mkr_ruby_verified_text(rb_value, "attribute value");
    mkr_ruby_borrowed_text_t nv = {0};
    const char *ns_ptr = NULL; uint32_t ns_len = 0;
    if (!NIL_P(rb_ns)) {
        nv = mkr_ruby_verified_text(rb_ns, "namespace");
        ns_ptr = nv.ptr; ns_len = mkr_xml_u32_len(nv.len);
    }
    mkr_xml_mut_status_t st = mkr_xml_set_attribute_ns(
        mkr_xml_node_xdoc(self), n, ns_ptr, ns_len,
        qv.ptr, mkr_xml_u32_len(qv.len), vv.ptr, mkr_xml_u32_len(vv.len), NULL);
    RB_GC_GUARD(qv.value);
    RB_GC_GUARD(vv.value);
    RB_GC_GUARD(nv.value);
    mkr_xml_mut_check(st);
    return rb_value;
}

#textObject

—- content / text —-



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'ext/makiri/glue/ruby_xml_node.c', line 275

static VALUE
mkr_xml_node_content(VALUE self)
{
    mkr_xml_node_t *n = mkr_xml_node_unwrap(self);
    /* leaf data nodes return their own value verbatim. */
    if (n->type == MKR_XML_NODE_TYPE_TEXT || n->type == MKR_XML_NODE_TYPE_CDATA_SECTION
        || n->type == MKR_XML_NODE_TYPE_COMMENT || n->type == MKR_XML_NODE_TYPE_ATTRIBUTE
        || n->type == MKR_XML_NODE_TYPE_PI) {
        return rb_utf8_str_new(n->value ? n->value : "", (long)n->value_len);
    }
    /* element / document: concatenate every TEXT/CDATA descendant in document
     * order. Iterative pre-order (parent-pointer) walk - no C recursion, so a
     * deep tree cannot overflow the stack. */
    VALUE str = rb_utf8_str_new("", 0);
    mkr_xml_node_t *cur = n->first_child;
    while (cur != NULL) {
        if ((cur->type == MKR_XML_NODE_TYPE_TEXT || cur->type == MKR_XML_NODE_TYPE_CDATA_SECTION) && cur->value_len) {
            rb_str_cat(str, cur->value, (long)cur->value_len);
        }
        if (cur->first_child != NULL) { cur = cur->first_child; continue; }
        while (cur != NULL && cur != n && cur->next == NULL) cur = cur->parent;
        if (cur == NULL || cur == n) break;
        cur = cur->next;
    }
    return str;
}

#to_html(*args) ⇒ Object

—- fail-closed guard for the unsupported serialization surface —-

HTML serialization (to_html/inner_html/outer_html) would silently misbehave on XML (escaping / CDATA / void elements differ), so it is an explicit NotImplementedError rather than a wrong result. Use #to_xml. (CSS selectors, once unsupported here, are now lowered to the native XPath engine - see ruby_xml.c.)



906
907
908
909
910
911
912
913
# File 'ext/makiri/glue/ruby_xml_node.c', line 906

static VALUE
mkr_xml_node_no_serialize(int argc, VALUE *argv, VALUE self)
{
    (void)argc; (void)argv; (void)self;
    rb_raise(rb_eNotImpError,
             "Makiri::XML does not HTML-serialize (to_html / inner_html / "
             "outer_html); use #to_xml for XML output.");
}

#to_xml(pretty: false, indent: 2, encoding: "UTF-8") ⇒ String

A Document also emits the XML declaration and its DOCTYPE; any other node serializes just its own subtree. encoding (a String or Encoding) transcodes the output - a character the target cannot represent becomes a hexadecimal character reference - and is named in a Document’s declaration.

Returns:

  • (String)


547
548
549
550
551
552
553
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
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
# File 'ext/makiri/glue/ruby_xml_node.c', line 547

static VALUE
mkr_xml_node_to_xml(int argc, VALUE *argv, VALUE self)
{
    VALUE opts;
    rb_scan_args(argc, argv, "0:", &opts);
    int width = 0;
    VALUE enc_opt = Qnil;
    if (!NIL_P(opts)) {
        if (RTEST(rb_hash_aref(opts, ID2SYM(rb_intern("pretty"))))) width = 2;
        VALUE iv = rb_hash_aref(opts, ID2SYM(rb_intern("indent")));
        if (!NIL_P(iv)) width = NUM2INT(iv) < 0 ? 0 : NUM2INT(iv);
        enc_opt = rb_hash_aref(opts, ID2SYM(rb_intern("encoding")));
    }
    /* resolve the target encoding (raises on an unknown name) + its declared name */
    rb_encoding *to_enc = NIL_P(enc_opt) ? NULL : rb_to_encoding(enc_opt);
    VALUE enc_name = NIL_P(enc_opt) ? Qnil : rb_obj_as_string(enc_opt);

    mkr_xml_node_t *n = mkr_xml_node_unwrap(self);
    mkr_buf_t buf;
    mkr_buf_init(&buf, mkr_xml_serialize_cap(self));   /* fail closed past the cap, never OOM */
    int rc = 0;

    if (rb_obj_is_kind_of(self, mkr_cXmlDocument)) {
        mkr_xml_doc_t *xdoc = mkr_parsed_xml_doc(mkr_doc_parsed(self));
        /* Emit the encoding pseudo-attribute only when an explicit encoding: was
         * requested or the parsed source declared one; otherwise a built or
         * declaration-less document round-trips to a bare `<?xml version="1.0"?>`,
         * matching Nokogiri (the output is UTF-8 either way). */
        int emit_enc = !NIL_P(enc_name) || (xdoc != NULL && xdoc->has_encoding_decl);
        if (emit_enc) {
            static const char decl_a[] = "<?xml version=\"1.0\" encoding=\"";
            static const char decl_b[] = "\"?>\n";
            VALUE name = NIL_P(enc_name) ? rb_utf8_str_new_cstr("UTF-8") : enc_name;
            mkr_ruby_borrowed_bytes_t nv = mkr_ruby_bytes_view(name);
            rc = (mkr_buf_append(&buf, decl_a, sizeof(decl_a) - 1) == MKR_OK) ? 0 : -1;
            if (rc == 0) rc = (mkr_buf_append(&buf, nv.ptr, nv.len) == MKR_OK) ? 0 : -1;
            if (rc == 0) rc = (mkr_buf_append(&buf, decl_b, sizeof(decl_b) - 1) == MKR_OK) ? 0 : -1;
            RB_GC_GUARD(nv.value);
        } else {
            static const char decl[] = "<?xml version=\"1.0\"?>\n";
            rc = (mkr_buf_append(&buf, decl, sizeof(decl) - 1) == MKR_OK) ? 0 : -1;
        }
        if (rc == 0 && xdoc != NULL && xdoc->doctype != NULL) {
            rc = mkr_xser_doctype(&buf, xdoc->doctype);
            if (rc == 0) rc = (mkr_buf_append(&buf, "\n", 1) == MKR_OK) ? 0 : -1;
        }
        for (mkr_xml_node_t *c = n->first_child; rc == 0 && c != NULL; c = c->next) {
            rc = mkr_xser_node(&buf, c, 0, width);
            if (rc == 0) rc = (mkr_buf_append(&buf, "\n", 1) == MKR_OK) ? 0 : -1;
        }
    } else {
        rc = mkr_xser_node(&buf, n, 0, width);
    }

    if (rc != 0) {
        mkr_buf_free(&buf);
        rb_raise(mkr_eError, "failed to serialize XML: output exceeded the size limit or out of memory");
    }
    VALUE str = rb_utf8_str_new(buf.len ? buf.data : "", (long)buf.len);
    mkr_buf_free(&buf);

    /* Transcode to the requested encoding; an unrepresentable character becomes a
     * &#xNN; reference (ECONV_UNDEF_HEX_CHARREF) rather than raising or dropping. */
    if (to_enc != NULL && to_enc != rb_utf8_encoding() && to_enc != rb_usascii_encoding()) {
        str = rb_str_encode(str, rb_enc_from_encoding(to_enc), ECONV_UNDEF_HEX_CHARREF, Qnil);
    }
    return str;
}

#to_xml(pretty: false, indent: 2, encoding: "UTF-8") ⇒ String

A Document also emits the XML declaration and its DOCTYPE; any other node serializes just its own subtree. encoding (a String or Encoding) transcodes the output - a character the target cannot represent becomes a hexadecimal character reference - and is named in a Document’s declaration.

Returns:

  • (String)


547
548
549
550
551
552
553
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
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
# File 'ext/makiri/glue/ruby_xml_node.c', line 547

static VALUE
mkr_xml_node_to_xml(int argc, VALUE *argv, VALUE self)
{
    VALUE opts;
    rb_scan_args(argc, argv, "0:", &opts);
    int width = 0;
    VALUE enc_opt = Qnil;
    if (!NIL_P(opts)) {
        if (RTEST(rb_hash_aref(opts, ID2SYM(rb_intern("pretty"))))) width = 2;
        VALUE iv = rb_hash_aref(opts, ID2SYM(rb_intern("indent")));
        if (!NIL_P(iv)) width = NUM2INT(iv) < 0 ? 0 : NUM2INT(iv);
        enc_opt = rb_hash_aref(opts, ID2SYM(rb_intern("encoding")));
    }
    /* resolve the target encoding (raises on an unknown name) + its declared name */
    rb_encoding *to_enc = NIL_P(enc_opt) ? NULL : rb_to_encoding(enc_opt);
    VALUE enc_name = NIL_P(enc_opt) ? Qnil : rb_obj_as_string(enc_opt);

    mkr_xml_node_t *n = mkr_xml_node_unwrap(self);
    mkr_buf_t buf;
    mkr_buf_init(&buf, mkr_xml_serialize_cap(self));   /* fail closed past the cap, never OOM */
    int rc = 0;

    if (rb_obj_is_kind_of(self, mkr_cXmlDocument)) {
        mkr_xml_doc_t *xdoc = mkr_parsed_xml_doc(mkr_doc_parsed(self));
        /* Emit the encoding pseudo-attribute only when an explicit encoding: was
         * requested or the parsed source declared one; otherwise a built or
         * declaration-less document round-trips to a bare `<?xml version="1.0"?>`,
         * matching Nokogiri (the output is UTF-8 either way). */
        int emit_enc = !NIL_P(enc_name) || (xdoc != NULL && xdoc->has_encoding_decl);
        if (emit_enc) {
            static const char decl_a[] = "<?xml version=\"1.0\" encoding=\"";
            static const char decl_b[] = "\"?>\n";
            VALUE name = NIL_P(enc_name) ? rb_utf8_str_new_cstr("UTF-8") : enc_name;
            mkr_ruby_borrowed_bytes_t nv = mkr_ruby_bytes_view(name);
            rc = (mkr_buf_append(&buf, decl_a, sizeof(decl_a) - 1) == MKR_OK) ? 0 : -1;
            if (rc == 0) rc = (mkr_buf_append(&buf, nv.ptr, nv.len) == MKR_OK) ? 0 : -1;
            if (rc == 0) rc = (mkr_buf_append(&buf, decl_b, sizeof(decl_b) - 1) == MKR_OK) ? 0 : -1;
            RB_GC_GUARD(nv.value);
        } else {
            static const char decl[] = "<?xml version=\"1.0\"?>\n";
            rc = (mkr_buf_append(&buf, decl, sizeof(decl) - 1) == MKR_OK) ? 0 : -1;
        }
        if (rc == 0 && xdoc != NULL && xdoc->doctype != NULL) {
            rc = mkr_xser_doctype(&buf, xdoc->doctype);
            if (rc == 0) rc = (mkr_buf_append(&buf, "\n", 1) == MKR_OK) ? 0 : -1;
        }
        for (mkr_xml_node_t *c = n->first_child; rc == 0 && c != NULL; c = c->next) {
            rc = mkr_xser_node(&buf, c, 0, width);
            if (rc == 0) rc = (mkr_buf_append(&buf, "\n", 1) == MKR_OK) ? 0 : -1;
        }
    } else {
        rc = mkr_xser_node(&buf, n, 0, width);
    }

    if (rc != 0) {
        mkr_buf_free(&buf);
        rb_raise(mkr_eError, "failed to serialize XML: output exceeded the size limit or out of memory");
    }
    VALUE str = rb_utf8_str_new(buf.len ? buf.data : "", (long)buf.len);
    mkr_buf_free(&buf);

    /* Transcode to the requested encoding; an unrepresentable character becomes a
     * &#xNN; reference (ECONV_UNDEF_HEX_CHARREF) rather than raising or dropping. */
    if (to_enc != NULL && to_enc != rb_utf8_encoding() && to_enc != rb_usascii_encoding()) {
        str = rb_str_encode(str, rb_enc_from_encoding(to_enc), ECONV_UNDEF_HEX_CHARREF, Qnil);
    }
    return str;
}

node.remove / node.unlink -> node. Detach from the tree (or, for an attribute, from its owner element); the node stays usable.



990
991
992
993
994
995
996
997
998
999
1000
1001
# File 'ext/makiri/glue/ruby_xml_node.c', line 990

static VALUE
mkr_xml_node_remove(VALUE self)
{
    if (rb_obj_is_kind_of(self, mkr_cXmlDocument)) {
        rb_raise(mkr_eError, "cannot remove the document node");
    }
    mkr_xml_node_t *n = mkr_xml_node_unwrap_mutable(self);
    mkr_xml_doc_t *xdoc = mkr_xml_node_xdoc(self);
    if (xdoc != NULL && n == xdoc->root) xdoc->root = NULL;   /* detaching the root element */
    mkr_xml_detach(n);
    return self;
}

#valueObject



302
303
304
305
306
307
# File 'ext/makiri/glue/ruby_xml_node.c', line 302

static VALUE
mkr_xml_node_value(VALUE self)
{
    mkr_xml_node_t *n = mkr_xml_node_unwrap(self);
    return rb_utf8_str_new(n->value ? n->value : "", (long)n->value_len);
}

#xpath(*args) ⇒ Object



290
291
292
293
294
295
296
# File 'ext/makiri/glue/ruby_xml.c', line 290

static VALUE
mkr_xml_doc_xpath(int argc, VALUE *argv, VALUE self)
{
    VALUE expr, ns;
    rb_scan_args(argc, argv, "11", &expr, &ns);
    return mkr_xml_doc_xpath_run(self, expr, ns, 0);
}