exml/
xinclude.rs

1//! Provide methods and data structures for XInclude.
2//!
3//! This module is based on `libxml/xinclude.h`, `xinclude.c`, and so on in `libxml2-v2.11.8`.  
4//! Please refer to original libxml2 documents also.
5
6// Copyright of the original code is the following.
7// --------
8// Summary: implementation of XInclude
9// Description: API to handle XInclude processing,
10// implements the
11// World Wide Web Consortium Last Call Working Draft 10 November 2003
12// http://www.w3.org/TR/2003/WD-xinclude-20031110
13//
14// Copy: See Copyright for the status of this software.
15//
16// Author: Daniel Veillard
17// --------
18// xinclude.c : Code to implement XInclude processing
19//
20// World Wide Web Consortium W3C Last Call Working Draft 10 November 2003
21// http://www.w3.org/TR/2003/WD-xinclude-20031110
22//
23// See Copyright for the status of this software.
24//
25// daniel@veillard.com
26
27use std::{borrow::Cow, mem::take, os::raw::c_void, ptr::null_mut};
28
29use crate::{
30    chvalid::XmlCharValid,
31    encoding::{XmlCharEncoding, get_encoding_handler},
32    error::{__xml_raise_error, XmlErrorDomain, XmlErrorLevel, XmlParserErrors},
33    io::xml_parser_get_directory,
34    parser::{
35        XML_DETECT_IDS, XmlParserCtxt, XmlParserOption, xml_init_parser, xml_load_external_entity,
36    },
37    tree::{
38        NodeCommon, XML_XML_NAMESPACE, XmlDocPtr, XmlElementType, XmlEntityPtr, XmlEntityType,
39        XmlGenericNodePtr, XmlNodePtr, xml_add_doc_entity, xml_create_int_subset,
40        xml_doc_copy_node, xml_free_doc, xml_free_node, xml_free_node_list, xml_get_doc_entity,
41        xml_new_doc_node, xml_new_doc_text, xml_static_copy_node, xml_static_copy_node_list,
42    },
43    uri::{XmlURI, build_relative_uri, build_uri, escape_url},
44    xpath::{XmlXPathObject, XmlXPathObjectType},
45    xpointer::{xml_xptr_eval, xml_xptr_new_context},
46};
47
48/// A constant defining the Xinclude namespace: `http://www.w3.org/2003/XInclude`
49pub const XINCLUDE_NS: &str = "http://www.w3.org/2003/XInclude";
50/// A constant defining the draft Xinclude namespace: `http://www.w3.org/2001/XInclude`
51pub const XINCLUDE_OLD_NS: &str = "http://www.w3.org/2001/XInclude";
52/// A constant defining "include"
53pub const XINCLUDE_NODE: &str = "include";
54/// A constant defining "fallback"
55pub const XINCLUDE_FALLBACK: &str = "fallback";
56/// A constant defining "href"
57pub const XINCLUDE_HREF: &str = "href";
58/// A constant defining "parse"
59pub const XINCLUDE_PARSE: &str = "parse";
60/// A constant defining "xml"
61pub const XINCLUDE_PARSE_XML: &str = "xml";
62/// A constant defining "text"
63pub const XINCLUDE_PARSE_TEXT: &str = "text";
64/// A constant defining "encoding"
65pub const XINCLUDE_PARSE_ENCODING: &str = "encoding";
66/// A constant defining "xpointer"
67pub const XINCLUDE_PARSE_XPOINTER: &str = "xpointer";
68
69/// Handle an XInclude error
70#[doc(alias = "xmlXIncludeErr")]
71macro_rules! xml_xinclude_err {
72    ($ctxt:expr, $node:expr, $error:expr, $msg:expr) => {
73        xml_xinclude_err!(@inner, $ctxt, $node, $error, $msg, None);
74    };
75    ($ctxt:expr, $node:expr, $error:expr, $msg:expr, $extra:expr) => {
76        let msg = format!($msg, $extra);
77        xml_xinclude_err!(@inner, $ctxt, $node, $error, &msg, Some($extra.to_owned().into()));
78    };
79    (@inner, $ctxt:expr, $node:expr, $error:expr, $msg:expr, $extra:expr) => {
80        let ctxt = $ctxt as *mut XmlXIncludeCtxt;
81        if !ctxt.is_null() {
82            (*ctxt).nb_errors += 1;
83        }
84        __xml_raise_error!(
85            None,
86            None,
87            None,
88            ctxt as _,
89            $node,
90            XmlErrorDomain::XmlFromXInclude,
91            $error,
92            XmlErrorLevel::XmlErrError,
93            None,
94            0,
95            $extra,
96            None,
97            None,
98            0,
99            0,
100            Some($msg),
101        );
102    };
103}
104
105#[doc(alias = "xmlXIncludeRef")]
106#[repr(C)]
107#[derive(Default)]
108pub struct XmlXIncludeRef {
109    uri: Option<Box<str>>,      /* the fully resolved resource URL */
110    fragment: Option<Box<str>>, /* the fragment in the URI */
111    elem: Option<XmlNodePtr>,   /* the xi:include element */
112    inc: Option<XmlNodePtr>,    /* the included copy */
113    xml: i32,                   /* xml or txt */
114    fallback: i32,              /* fallback was loaded */
115    empty_fb: i32,              /* flag to show fallback empty */
116    expanding: i32,             /* flag to detect inclusion loops */
117    replace: i32,               /* should the node be replaced? */
118}
119
120#[doc(alias = "xmlXIncludeDoc")]
121#[repr(C)]
122pub struct XmlXIncludeDoc {
123    doc: Option<XmlDocPtr>, /* the parsed document */
124    url: Box<str>,          /* the URL */
125    expanding: i32,         /* flag to detect inclusion loops */
126}
127
128#[doc(alias = "xmlXIncludeTxt")]
129#[repr(C)]
130pub struct XmlXIncludeTxt {
131    text: Box<str>, /* text string */
132    url: Box<str>,  /* the URL */
133}
134
135/// An XInclude context
136#[doc(alias = "xmlXIncludeCtxt")]
137#[repr(C)]
138pub struct XmlXIncludeCtxt {
139    doc: XmlDocPtr,               /* the source document */
140    inc_tab: Vec<XmlXIncludeRef>, /* array of included references */
141
142    txt_tab: Vec<XmlXIncludeTxt>, /* array of unparsed documents */
143
144    url_tab: Vec<XmlXIncludeDoc>, /* document stack */
145
146    nb_errors: i32,         /* the number of errors detected */
147    fatal_err: i32,         /* abort processing */
148    legacy: i32,            /* using XINCLUDE_OLD_NS */
149    parse_flags: i32,       /* the flags used for parsing XML documents */
150    base: Option<Box<str>>, /* the current xml:base */
151
152    _private: *mut c_void, /* application data */
153
154    depth: i32,     /* recursion depth */
155    is_stream: i32, /* streaming mode */
156}
157
158impl XmlXIncludeCtxt {
159    /// Creates a new XInclude context
160    ///
161    /// Returns the new set
162    #[doc(alias = "xmlXIncludeNewContext")]
163    pub fn new(doc: XmlDocPtr) -> Self {
164        XmlXIncludeCtxt {
165            doc,
166            inc_tab: vec![],
167            txt_tab: vec![],
168            url_tab: vec![],
169            nb_errors: 0,
170            fatal_err: 0,
171            legacy: 0,
172            parse_flags: 0,
173            base: None,
174            _private: null_mut(),
175            depth: 0,
176            is_stream: 0,
177        }
178    }
179
180    /// Get an XInclude attribute
181    ///
182    /// Returns the value (to be freed) or NULL if not found
183    #[doc(alias = "xmlXIncludeGetProp")]
184    fn get_prop(&self, cur: XmlNodePtr, name: &str) -> Option<String> {
185        if let Some(ret) = cur.get_ns_prop(XINCLUDE_NS, Some(name)) {
186            return Some(ret);
187        }
188        if self.legacy != 0 {
189            if let Some(ret) = cur.get_ns_prop(XINCLUDE_OLD_NS, Some(name)) {
190                return Some(ret);
191            }
192        }
193        cur.get_prop(name)
194    }
195
196    /// In streaming mode, XPointer expressions aren't allowed.
197    ///
198    /// Returns 0 in case of success and -1 in case of error.
199    #[doc(alias = "xmlXIncludeSetStreamingMode")]
200    pub(crate) fn set_streaming_mode(&mut self, mode: i32) -> i32 {
201        self.is_stream = (mode != 0) as i32;
202        0
203    }
204
205    /// Set the flags used for further processing of XML resources.
206    ///
207    /// Returns 0 in case of success and -1 in case of error.
208    #[doc(alias = "xmlXIncludeSetFlags")]
209    pub fn set_flags(&mut self, flags: i32) -> i32 {
210        self.parse_flags = flags;
211        0
212    }
213
214    /// Add a new node to process to an XInclude context
215    #[doc(alias = "xmlXIncludeAddNode")]
216    unsafe fn add_node(&mut self, cur: XmlNodePtr) -> usize {
217        unsafe {
218            let mut xml: i32 = 1;
219            let mut local: i32 = 0;
220
221            // read the attributes
222            let href = self.get_prop(cur, XINCLUDE_HREF).unwrap_or("".to_owned());
223            let parse = self.get_prop(cur, XINCLUDE_PARSE);
224            if let Some(parse) = parse {
225                if parse == XINCLUDE_PARSE_XML {
226                    xml = 1;
227                } else if parse == XINCLUDE_PARSE_TEXT {
228                    xml = 0;
229                } else {
230                    xml_xinclude_err!(
231                        self,
232                        Some(cur.into()),
233                        XmlParserErrors::XmlXIncludeParseValue,
234                        "invalid value {} for 'parse'\n",
235                        parse
236                    );
237                    return usize::MAX;
238                }
239            }
240
241            // compute the URI
242            let mut base = None;
243            let mut uri = if let Some(b) = cur.get_base(Some(self.doc)) {
244                base = Some(b);
245                build_uri(&href, base.as_deref().unwrap())
246            } else {
247                self.doc
248                    .url
249                    .as_deref()
250                    .and_then(|base| build_uri(&href, base))
251            };
252            if uri.is_none() {
253                if let Some(base) = base.as_deref() {
254                    // Some escaping may be needed
255                    if let (Some(escbase), Some(eschref)) = (escape_url(base), escape_url(&href)) {
256                        uri = build_uri(&eschref, &escbase);
257                    }
258                }
259            }
260            let Some(uri) = uri else {
261                xml_xinclude_err!(
262                    self,
263                    Some(cur.into()),
264                    XmlParserErrors::XmlXIncludeHrefURI,
265                    "failed build URL\n"
266                );
267                return usize::MAX;
268            };
269            let mut fragment = self.get_prop(cur, XINCLUDE_PARSE_XPOINTER);
270
271            // Check the URL and remove any fragment identifier
272            let Some(mut parsed_uri) = XmlURI::parse(&uri) else {
273                xml_xinclude_err!(
274                    self,
275                    Some(cur.into()),
276                    XmlParserErrors::XmlXIncludeHrefURI,
277                    "invalid value URI {}\n",
278                    uri
279                );
280                return usize::MAX;
281            };
282
283            if parsed_uri.fragment.is_some() {
284                if self.legacy != 0 {
285                    if fragment.is_none() {
286                        fragment = parsed_uri.fragment.as_deref().map(|f| f.to_owned());
287                    }
288                } else {
289                    xml_xinclude_err!(
290                        self,
291                        Some(cur.into()),
292                        XmlParserErrors::XmlXIncludeFragmentID,
293                        "Invalid fragment identifier in URI {} use the xpointer attribute\n",
294                        uri
295                    );
296                    return usize::MAX;
297                }
298                parsed_uri.fragment = None;
299            }
300            let url = parsed_uri.save();
301
302            if self.doc.url.as_deref() == Some(url.as_str()) {
303                local = 1;
304            }
305
306            // If local and xml then we need a fragment
307            if local == 1 && xml == 1 && fragment.as_deref().is_none_or(|f| f.is_empty()) {
308                xml_xinclude_err!(
309                    self,
310                    Some(cur.into()),
311                    XmlParserErrors::XmlXIncludeRecursion,
312                    "detected a local recursion with no xpointer in {}\n",
313                    url
314                );
315                return usize::MAX;
316            }
317
318            let refe = self.add_ref(Some(&url), cur);
319            self.inc_tab[refe].fragment = fragment.map(|fragment| fragment.into());
320            self.inc_tab[refe].xml = xml;
321            refe
322        }
323    }
324
325    /// Creates a new reference within an XInclude context
326    ///
327    /// Returns the new set
328    #[doc(alias = "xmlXIncludeNewRef")]
329    fn add_ref(&mut self, uri: Option<&str>, elem: XmlNodePtr) -> usize {
330        self.inc_tab.push(XmlXIncludeRef {
331            uri: uri.map(|uri| uri.into()),
332            fragment: None,
333            elem: Some(elem),
334            xml: 0,
335            inc: None,
336            ..Default::default()
337        });
338        self.inc_tab.len() - 1
339    }
340
341    /// Make a copy of the node while expanding nested XIncludes.
342    ///
343    /// Returns a node list, not a single node.
344    #[doc(alias = "xmlXIncludeCopyNode")]
345    unsafe fn copy_node(&mut self, elem: XmlNodePtr, copy_children: i32) -> Option<XmlNodePtr> {
346        unsafe {
347            let mut result: Option<XmlNodePtr> = None;
348            let mut insert_parent: Option<XmlNodePtr> = None;
349            let mut insert_last: Option<XmlNodePtr> = None;
350
351            let mut cur = if copy_children != 0 {
352                elem.children.map(|c| XmlNodePtr::try_from(c).unwrap())?
353            } else {
354                elem
355            };
356
357            loop {
358                let mut copy = None;
359                let mut recurse: i32 = 0;
360
361                if matches!(
362                    cur.element_type(),
363                    XmlElementType::XmlDocumentNode | XmlElementType::XmlDTDNode
364                ) {
365                } else if cur.element_type() == XmlElementType::XmlElementNode
366                    && cur.name().as_deref() == Some(XINCLUDE_NODE)
367                    && cur.ns.is_some_and(|ns| {
368                        ns.href().as_deref() == Some(XINCLUDE_NS)
369                            || ns.href().as_deref() == Some(XINCLUDE_OLD_NS)
370                    })
371                {
372                    let ref_index = self.expand_node(cur);
373
374                    if ref_index == usize::MAX {
375                        // goto error;
376                        xml_free_node_list(result);
377                        return None;
378                    }
379                    // TODO: Insert xmlElementType::XML_XINCLUDE_START and xmlElementType::XML_XINCLUDE_END nodes
380                    if let Some(inc) = self.inc_tab[ref_index].inc {
381                        let Some(res) = xml_static_copy_node_list(
382                            Some(XmlGenericNodePtr::from(inc)),
383                            Some(self.doc),
384                            insert_parent.map(|parent| parent.into()),
385                        ) else {
386                            // goto error;
387                            xml_free_node_list(result);
388                            return None;
389                        };
390                        copy = Some(XmlNodePtr::try_from(res).unwrap());
391                    }
392                } else {
393                    let Some(res) = xml_static_copy_node(
394                        XmlGenericNodePtr::from(cur),
395                        Some(self.doc),
396                        insert_parent.map(|parent| parent.into()),
397                        2,
398                    ) else {
399                        // goto error;
400                        xml_free_node_list(result);
401                        return None;
402                    };
403                    copy = Some(XmlNodePtr::try_from(res).unwrap());
404
405                    recurse = (cur.element_type() != XmlElementType::XmlEntityRefNode
406                        && cur.children().is_some()) as i32;
407                }
408
409                if let Some(mut copy) = copy {
410                    if result.is_none() {
411                        result = Some(copy);
412                    }
413                    if let Some(mut insert_last) = insert_last {
414                        insert_last.next = Some(copy.into());
415                        copy.prev = Some(insert_last.into());
416                    } else if let Some(mut insert_parent) = insert_parent {
417                        insert_parent.children = Some(copy.into());
418                    }
419                    let mut now = copy;
420                    while let Some(next) = now.next.map(|node| XmlNodePtr::try_from(node).unwrap())
421                    {
422                        now = next;
423                    }
424                    insert_last = Some(now);
425                }
426
427                if recurse != 0 {
428                    cur = cur
429                        .children
430                        .map(|c| XmlNodePtr::try_from(c).unwrap())
431                        .unwrap();
432                    insert_parent = insert_last.take();
433                    continue;
434                }
435
436                if cur == elem {
437                    return result;
438                }
439
440                while cur.next.is_none() {
441                    if let Some(mut insert_parent) = insert_parent {
442                        insert_parent.last = insert_last.map(|node| node.into());
443                    }
444                    cur = cur
445                        .parent
446                        .map(|p| XmlNodePtr::try_from(p).unwrap())
447                        .unwrap();
448                    if cur == elem {
449                        return result;
450                    }
451                    insert_last = insert_parent;
452                    insert_parent = insert_parent
453                        .unwrap()
454                        .parent
455                        .map(|p| XmlNodePtr::try_from(p).unwrap());
456                }
457
458                cur = cur
459                    .next
460                    .map(|node| XmlNodePtr::try_from(node).unwrap())
461                    .unwrap();
462            }
463
464            // error:
465            // xmlFreeNodeList(result);
466            // return null_mut();
467        }
468    }
469
470    /// Test if the node is an XInclude node
471    ///
472    /// Returns 1 true, 0 otherwise
473    #[doc(alias = "xmlXIncludeTestNode")]
474    unsafe fn test_node(&mut self, node: XmlNodePtr) -> i32 {
475        unsafe {
476            if node.element_type() != XmlElementType::XmlElementNode {
477                return 0;
478            }
479            let Some(node_ns) = node.ns else {
480                return 0;
481            };
482            if node_ns.href().as_deref() == Some(XINCLUDE_NS)
483                || node_ns.href().as_deref() == Some(XINCLUDE_OLD_NS)
484            {
485                if node_ns.href().as_deref() == Some(XINCLUDE_OLD_NS) && self.legacy == 0 {
486                    self.legacy = 1;
487                }
488                if node.name().as_deref() == Some(XINCLUDE_NODE) {
489                    let mut child = node.children.map(|c| XmlNodePtr::try_from(c).unwrap());
490                    let mut nb_fallback: i32 = 0;
491
492                    while let Some(cur_node) = child {
493                        if cur_node.element_type() == XmlElementType::XmlElementNode
494                            && cur_node.ns.is_some_and(|ns| {
495                                ns.href().as_deref() == Some(XINCLUDE_NS)
496                                    || ns.href().as_deref() == Some(XINCLUDE_OLD_NS)
497                            })
498                        {
499                            if cur_node.name().as_deref() == Some(XINCLUDE_NODE) {
500                                xml_xinclude_err!(
501                                    self,
502                                    Some(node.into()),
503                                    XmlParserErrors::XmlXIncludeIncludeInInclude,
504                                    "{} has an 'include' child\n",
505                                    XINCLUDE_NODE
506                                );
507                                return 0;
508                            }
509                            if cur_node.name().as_deref() == Some(XINCLUDE_FALLBACK) {
510                                nb_fallback += 1;
511                            }
512                        }
513                        child = cur_node
514                            .next
515                            .map(|node| XmlNodePtr::try_from(node).unwrap());
516                    }
517                    if nb_fallback > 1 {
518                        xml_xinclude_err!(
519                            self,
520                            Some(node.into()),
521                            XmlParserErrors::XmlXIncludeFallbacksInInclude,
522                            "{} has multiple fallback children\n",
523                            XINCLUDE_NODE
524                        );
525                        return 0;
526                    }
527                    return 1;
528                }
529                if node.name().as_deref() == Some(XINCLUDE_FALLBACK)
530                    && (node.parent().is_none()
531                        || node.parent().unwrap().element_type() != XmlElementType::XmlElementNode
532                        || XmlNodePtr::try_from(node.parent().unwrap())
533                            .unwrap()
534                            .ns
535                            .is_none_or(|ns| {
536                                ns.href().as_deref() != Some(XINCLUDE_NS)
537                                    && ns.href().as_deref() != Some(XINCLUDE_OLD_NS)
538                            })
539                        || node.parent().unwrap().name().as_deref() != Some(XINCLUDE_NODE))
540                {
541                    xml_xinclude_err!(
542                        self,
543                        Some(node.into()),
544                        XmlParserErrors::XmlXIncludeFallbackNotInInclude,
545                        "{} is not the child of an 'include'\n",
546                        XINCLUDE_FALLBACK
547                    );
548                }
549            }
550            0
551        }
552    }
553
554    /// If the XInclude node wasn't processed yet, create a new RefPtr,
555    /// add it to self.incTab and load the included items.
556    ///
557    /// Returns the index of new or existing `XmlXIncludeRef` or `usize::MAX` in case of error.
558    #[doc(alias = "xmlXIncludeExpandNode")]
559    unsafe fn expand_node(&mut self, node: XmlNodePtr) -> usize {
560        unsafe {
561            if self.fatal_err != 0 {
562                return usize::MAX;
563            }
564            if self.depth >= XINCLUDE_MAX_DEPTH {
565                xml_xinclude_err!(
566                    self,
567                    Some(node.into()),
568                    XmlParserErrors::XmlXIncludeRecursion,
569                    "maximum recursion depth exceeded\n"
570                );
571                self.fatal_err = 1;
572                return usize::MAX;
573            }
574
575            for (i, inc) in self.inc_tab.iter().enumerate() {
576                if inc.elem == Some(node) {
577                    if inc.expanding != 0 {
578                        xml_xinclude_err!(
579                            self,
580                            Some(node.into()),
581                            XmlParserErrors::XmlXIncludeRecursion,
582                            "inclusion loop detected\n"
583                        );
584                        return usize::MAX;
585                    }
586                    return i;
587                }
588            }
589
590            let refe = self.add_node(node);
591            if refe == usize::MAX {
592                return usize::MAX;
593            }
594            self.inc_tab[refe].expanding = 1;
595            self.depth += 1;
596            self.load_node(refe);
597            self.depth -= 1;
598            self.inc_tab[refe].expanding = 0;
599
600            refe
601        }
602    }
603
604    /// Implement the infoset replacement for the given node
605    ///
606    /// Returns 0 if substitution succeeded, -1 if some processing failed
607    #[doc(alias = "xmlXIncludeIncludeNode")]
608    unsafe fn include_node(&mut self, ref_index: usize) -> i32 {
609        unsafe {
610            if ref_index == usize::MAX {
611                return -1;
612            }
613            let cur = self.inc_tab[ref_index].elem;
614            let Some(mut cur) =
615                cur.filter(|cur| cur.element_type() != XmlElementType::XmlNamespaceDecl)
616            else {
617                return -1;
618            };
619
620            let mut list = self.inc_tab[ref_index].inc.take();
621            self.inc_tab[ref_index].empty_fb = 0;
622
623            // Check against the risk of generating a multi-rooted document
624            if cur
625                .parent()
626                .filter(|p| p.element_type() != XmlElementType::XmlElementNode)
627                .is_some()
628            {
629                let mut nb_elem: i32 = 0;
630
631                let mut tmp = list;
632                while let Some(cur) = tmp {
633                    if cur.element_type() == XmlElementType::XmlElementNode {
634                        nb_elem += 1;
635                    }
636                    tmp = cur.next.map(|node| XmlNodePtr::try_from(node).unwrap());
637                }
638                if nb_elem > 1 {
639                    xml_xinclude_err!(
640                        self,
641                        self.inc_tab[ref_index].elem.map(|node| node.into()),
642                        XmlParserErrors::XmlXIncludeMultipleRoot,
643                        "XInclude error: would result in multiple root nodes\n"
644                    );
645                    xml_free_node_list(list);
646                    return -1;
647                }
648            }
649
650            if self.parse_flags & XmlParserOption::XmlParseNoXIncnode as i32 != 0 {
651                // Add the list of nodes
652                while let Some(cur_node) = list {
653                    list = cur_node
654                        .next
655                        .map(|node| XmlNodePtr::try_from(node).unwrap());
656
657                    cur.add_prev_sibling(XmlGenericNodePtr::from(cur_node));
658                }
659                // FIXME: xmlUnlinkNode doesn't coalesce text nodes.
660                cur.unlink();
661                xml_free_node(cur);
662            } else {
663                // Change the current node as an XInclude start one, and add an XInclude end one
664                if self.inc_tab[ref_index].fallback != 0 {
665                    cur.unset_prop("href");
666                }
667                cur.typ = XmlElementType::XmlXIncludeStart;
668                // Remove fallback children
669                let mut child = cur.children();
670                while let Some(mut now) = child {
671                    let next = now.next();
672                    now.unlink();
673                    xml_free_node(now);
674                    child = next;
675                }
676                let Some(mut end) = xml_new_doc_node(cur.doc, cur.ns, &cur.name().unwrap(), None)
677                else {
678                    xml_xinclude_err!(
679                        self,
680                        self.inc_tab[ref_index].elem.map(|node| node.into()),
681                        XmlParserErrors::XmlXIncludeBuildFailed,
682                        "failed to build node\n"
683                    );
684                    xml_free_node_list(list);
685                    return -1;
686                };
687                end.typ = XmlElementType::XmlXIncludeEnd;
688                cur.add_next_sibling(end.into());
689
690                // Add the list of nodes
691                while let Some(cur_node) = list {
692                    list = cur_node
693                        .next
694                        .map(|node| XmlNodePtr::try_from(node).unwrap());
695
696                    end.add_prev_sibling(XmlGenericNodePtr::from(cur_node));
697                }
698            }
699
700            0
701        }
702    }
703
704    /// Implements the entity merge
705    ///
706    /// Returns 0 if merge succeeded, -1 if some processing failed
707    #[doc(alias = "xmlXIncludeMergeEntities")]
708    unsafe fn merge_entities(&mut self, doc: XmlDocPtr, from: XmlDocPtr) -> i32 {
709        unsafe {
710            if from.int_subset.is_none() {
711                return 0;
712            }
713
714            let Some(target) = doc.int_subset.or_else(|| {
715                let cur = doc.get_root_element()?;
716                xml_create_int_subset(Some(doc), cur.name().as_deref(), None, None)
717            }) else {
718                return -1;
719            };
720
721            let source = from.int_subset;
722            if let Some(source) = source {
723                for &entity in source.entities.values() {
724                    self.merge_entity(entity, doc);
725                }
726            }
727            let source = from.ext_subset;
728            if let Some(source) = source {
729                // don't duplicate existing stuff when external subsets are the same
730                if target.external_id != source.external_id && target.system_id != source.system_id
731                {
732                    for &entity in source.entities.values() {
733                        self.merge_entity(entity, doc);
734                    }
735                }
736            }
737            0
738        }
739    }
740
741    /// Implements the merge of one entity
742    #[doc(alias = "xmlXIncludeMergeOneEntity")]
743    unsafe fn merge_entity(&mut self, ent: XmlEntityPtr, doc: XmlDocPtr) {
744        unsafe {
745            match ent.etype {
746                XmlEntityType::XmlInternalParameterEntity
747                | XmlEntityType::XmlExternalParameterEntity
748                | XmlEntityType::XmlInternalPredefinedEntity => return,
749                XmlEntityType::XmlInternalGeneralEntity
750                | XmlEntityType::XmlExternalGeneralParsedEntity
751                | XmlEntityType::XmlExternalGeneralUnparsedEntity => {}
752            }
753
754            let ret = xml_add_doc_entity(
755                doc,
756                &ent.name().unwrap(),
757                ent.etype,
758                ent.external_id.as_deref(),
759                ent.system_id.as_deref(),
760                ent.content.as_deref(),
761            );
762            if let Some(mut ret) = ret {
763                ret.uri = ent.uri.clone();
764                return;
765            }
766
767            'error: {
768                let prev = xml_get_doc_entity(Some(doc), &ent.name().unwrap());
769                if let Some(prev) = prev {
770                    if ent.etype != prev.etype {
771                        break 'error;
772                    }
773
774                    if ent.system_id.is_some() && prev.system_id.is_some() {
775                        if ent.system_id != prev.system_id {
776                            break 'error;
777                        }
778                    } else if ent.external_id.is_some() && prev.external_id.is_some() {
779                        if ent.external_id != prev.external_id {
780                            break 'error;
781                        }
782                    } else if ent.content.is_some() && prev.content.is_some() {
783                        if ent.content != prev.content {
784                            break 'error;
785                        }
786                    } else {
787                        break 'error;
788                    }
789                }
790                return;
791            }
792            match ent.etype {
793                XmlEntityType::XmlInternalParameterEntity
794                | XmlEntityType::XmlExternalParameterEntity
795                | XmlEntityType::XmlInternalPredefinedEntity
796                | XmlEntityType::XmlInternalGeneralEntity
797                | XmlEntityType::XmlExternalGeneralParsedEntity => return,
798                XmlEntityType::XmlExternalGeneralUnparsedEntity => {}
799            }
800            xml_xinclude_err!(
801                self,
802                Some(ent.into()),
803                XmlParserErrors::XmlXIncludeEntityDefMismatch,
804                "mismatch in redefinition of entity {}\n",
805                ent.name().unwrap().into_owned()
806            );
807        }
808    }
809
810    /// Build a node list tree copy of the XPointer result.
811    /// This will drop Attributes and Namespace declarations.
812    ///
813    /// Returns an xmlNodePtr list or NULL.
814    /// The caller has to free the node tree.
815    #[doc(alias = "xmlXIncludeCopyXPointer")]
816    unsafe fn copy_xpointer(&mut self, obj: &XmlXPathObject) -> Option<XmlNodePtr> {
817        unsafe {
818            let mut list: Option<XmlNodePtr> = None;
819
820            match obj.typ {
821                XmlXPathObjectType::XPathNodeset => {
822                    let set = obj.nodesetval.as_deref()?;
823                    let mut last: Option<XmlNodePtr> = None;
824                    for &now in &set.node_tab {
825                        let node = match now.element_type() {
826                            XmlElementType::XmlDocumentNode
827                            | XmlElementType::XmlHTMLDocumentNode => {
828                                let Some(node) =
829                                    XmlDocPtr::try_from(now).unwrap().get_root_element()
830                                else {
831                                    xml_xinclude_err!(
832                                        self,
833                                        Some(now),
834                                        XmlParserErrors::XmlErrInternalError,
835                                        "document without root\n"
836                                    );
837                                    continue;
838                                };
839                                node
840                            }
841                            XmlElementType::XmlTextNode
842                            | XmlElementType::XmlCDATASectionNode
843                            | XmlElementType::XmlElementNode
844                            | XmlElementType::XmlPINode
845                            | XmlElementType::XmlCommentNode => XmlNodePtr::try_from(now).unwrap(),
846                            _ => {
847                                xml_xinclude_err!(
848                                    self,
849                                    Some(now),
850                                    XmlParserErrors::XmlXIncludeXPtrResult,
851                                    "invalid node type in XPtr result\n"
852                                );
853                                continue;
854                            }
855                        };
856                        // OPTIMIZE TODO: External documents should already be
857                        // expanded, so xmlDocCopyNode should work as well.
858                        // xmlXIncludeCopyNode is only required for the initial document.
859                        let Some(mut copy) = self.copy_node(node, 0) else {
860                            xml_free_node_list(list);
861                            return None;
862                        };
863                        if let Some(mut last) = last {
864                            while let Some(next) =
865                                last.next.map(|node| XmlNodePtr::try_from(node).unwrap())
866                            {
867                                last = next;
868                            }
869                            copy.prev = Some(last.into());
870                            last.next = Some(copy.into());
871                        } else {
872                            list = Some(copy);
873                        }
874                        last = Some(copy);
875                    }
876                }
877                #[cfg(feature = "libxml_xptr_locs")]
878                XmlXPathObjectType::XPathLocationset => {
879                    let set = obj.user.as_ref().and_then(|user| user.as_location_set())?;
880
881                    let mut last: Option<XmlNodePtr> = None;
882                    for loc in &set.loc_tab {
883                        if let Some(mut last) = last {
884                            last.add_next_sibling(self.copy_xpointer(loc).unwrap().into());
885                        } else {
886                            list = self.copy_xpointer(loc);
887                            last = list;
888                        }
889                        if let Some(mut l) = last {
890                            while let Some(next) =
891                                l.next.map(|node| XmlNodePtr::try_from(node).unwrap())
892                            {
893                                l = next;
894                            }
895                            last = Some(l);
896                        }
897                    }
898                }
899                #[cfg(feature = "libxml_xptr_locs")]
900                XmlXPathObjectType::XPathRange => {
901                    return self
902                        .copy_range(obj)
903                        .map(|node| XmlNodePtr::try_from(node).unwrap());
904                }
905                #[cfg(feature = "libxml_xptr_locs")]
906                XmlXPathObjectType::XPathPoint => { /* points are ignored in XInclude */ }
907                _ => {}
908            }
909            list
910        }
911    }
912
913    /// Build a node list tree copy of the XPointer result.
914    ///
915    /// Returns an xmlNodePtr list or NULL.
916    /// The caller has to free the node tree.
917    #[doc(alias = "xmlXIncludeCopyRange")]
918    #[cfg(feature = "libxml_xptr_locs")]
919    unsafe fn copy_range(&self, range: &XmlXPathObject) -> Option<XmlGenericNodePtr> {
920        unsafe {
921            use crate::{tree::xml_new_doc_text, xpointer::xml_xptr_advance_node};
922
923            // pointers to generated nodes
924            let mut list = None;
925            let mut last = None;
926            let mut list_parent = None;
927            let mut level: i32 = 0;
928            let mut last_level: i32 = 0;
929            let mut end_level: i32 = 0;
930            let mut end_flag: i32 = 0;
931
932            if range.typ != XmlXPathObjectType::XPathRange {
933                return None;
934            }
935            let start = range
936                .user
937                .as_ref()
938                .and_then(|user| user.as_node())
939                .copied()
940                .filter(|node| node.element_type() != XmlElementType::XmlNamespaceDecl)?;
941
942            let Some(mut end) = range
943                .user2
944                .as_ref()
945                .and_then(|user| user.as_node())
946                .copied()
947            else {
948                return xml_doc_copy_node(start, Some(self.doc), 1);
949            };
950            if end.element_type() == XmlElementType::XmlNamespaceDecl {
951                return None;
952            }
953
954            let mut cur = Some(start);
955            let mut index1 = range.index;
956            let mut index2 = range.index2;
957            // level is depth of the current node under consideration
958            // list is the pointer to the root of the output tree
959            // listParent is a pointer to the parent of output tree (within
960            // the included file) in case we need to add another level
961            // last is a pointer to the last node added to the output tree
962            // lastLevel is the depth of last (relative to the root)
963            while let Some(cur_node) = cur {
964                // Check if our output tree needs a parent
965                if level < 0 {
966                    while level < 0 {
967                        // copy must include namespaces and properties
968                        let mut tmp2 =
969                            xml_doc_copy_node(list_parent.unwrap(), Some(self.doc), 2).unwrap();
970                        tmp2.add_child(list.unwrap());
971                        list = Some(tmp2);
972                        list_parent = list_parent.unwrap().parent();
973                        level += 1;
974                    }
975                    last = list;
976                    last_level = 0;
977                }
978                // Check whether we need to change our insertion point
979                while level < last_level {
980                    last = last.unwrap().parent();
981                    last_level -= 1;
982                }
983                if cur_node == end {
984                    // Are we at the end of the range?
985                    if cur_node.element_type() == XmlElementType::XmlTextNode {
986                        let cur_node = XmlNodePtr::try_from(cur_node).unwrap();
987
988                        let tmp = if let Some(mut content) = cur_node.content.as_deref() {
989                            let mut len = index2 as usize;
990                            if start == XmlGenericNodePtr::from(cur_node) && index1 > 1 {
991                                content = &content[index1 as usize - 1..];
992                                len -= index1 as usize - 1;
993                            }
994                            xml_new_doc_text(Some(self.doc), Some(&content[..len]))
995                        } else {
996                            xml_new_doc_text(Some(self.doc), None)
997                        };
998                        // single sub text node selection
999                        if list.is_none() {
1000                            return tmp.map(|node| node.into());
1001                        }
1002                        // prune and return full set
1003                        if level == last_level {
1004                            last.unwrap().add_next_sibling(tmp.unwrap().into());
1005                        } else {
1006                            last.unwrap().add_child(tmp.unwrap().into());
1007                        }
1008                        return list;
1009                    } else {
1010                        // ending node not a text node
1011                        end_level = level; /* remember the level of the end node */
1012                        end_flag = 1;
1013                        // last node - need to take care of properties + namespaces
1014                        let tmp = xml_doc_copy_node(cur_node, Some(self.doc), 2);
1015                        if list.is_none() {
1016                            list = tmp;
1017                            list_parent = cur_node.parent();
1018                            last = tmp;
1019                        } else if level == last_level {
1020                            last = last.unwrap().add_next_sibling(tmp.unwrap());
1021                        } else {
1022                            last = last.unwrap().add_child(tmp.unwrap());
1023                            last_level = level;
1024                        }
1025
1026                        if index2 > 1 {
1027                            end = xml_xinclude_get_nth_child(cur_node, index2 - 1).unwrap();
1028                            index2 = 0;
1029                        }
1030                        if cur_node == start && index1 > 1 {
1031                            cur = xml_xinclude_get_nth_child(cur_node, index1 - 1);
1032                            index1 = 0;
1033                        } else {
1034                            cur = cur_node.children();
1035                        }
1036                        // increment level to show change
1037                        level += 1;
1038                        // Now gather the remaining nodes from cur to end
1039                        continue; /* while */
1040                    }
1041                } else if cur_node == start {
1042                    // Not at the end, are we at start?
1043                    if matches!(
1044                        cur_node.element_type(),
1045                        XmlElementType::XmlTextNode | XmlElementType::XmlCDATASectionNode
1046                    ) {
1047                        let cur_node = XmlNodePtr::try_from(cur_node).unwrap();
1048
1049                        let tmp = if let Some(mut content) = cur_node.content.as_deref() {
1050                            if index1 > 1 {
1051                                content = &content[index1 as usize - 1..];
1052                                index1 = 0;
1053                            }
1054                            xml_new_doc_text(Some(self.doc), Some(content))
1055                        } else {
1056                            xml_new_doc_text(Some(self.doc), None)
1057                        };
1058                        last = tmp.map(|node| node.into());
1059                        list = tmp.map(|node| node.into());
1060                        list_parent = cur_node.parent();
1061                    } else {
1062                        // Not text node
1063
1064                        // start of the range - need to take care of
1065                        // properties and namespaces
1066                        let tmp = xml_doc_copy_node(cur_node, Some(self.doc), 2);
1067                        list = tmp;
1068                        last = tmp;
1069                        list_parent = cur_node.parent();
1070                        if index1 > 1 {
1071                            // Do we need to position?
1072                            cur = xml_xinclude_get_nth_child(cur_node, index1 - 1);
1073                            level = 1;
1074                            last_level = 1;
1075                            index1 = 0;
1076                            // Now gather the remaining nodes from cur to end
1077                            continue; /* while */
1078                        }
1079                    }
1080                } else {
1081                    let mut tmp = None;
1082                    match cur_node.element_type() {
1083                        XmlElementType::XmlDTDNode
1084                        | XmlElementType::XmlElementDecl
1085                        | XmlElementType::XmlAttributeDecl
1086                        | XmlElementType::XmlEntityNode => { /* Do not copy DTD information */ }
1087                        XmlElementType::XmlEntityDecl => { /* handle crossing entities -> stack needed */
1088                        }
1089                        XmlElementType::XmlXIncludeStart | XmlElementType::XmlXIncludeEnd => {
1090                            // don't consider it part of the tree content
1091                        }
1092                        XmlElementType::XmlAttributeNode => { /* Humm, should not happen ! */ }
1093                        _ => {
1094                            // Middle of the range - need to take care of
1095                            // properties and namespaces
1096                            tmp = xml_doc_copy_node(cur_node, Some(self.doc), 2);
1097                        }
1098                    }
1099                    if let Some(tmp) = tmp {
1100                        if level == last_level {
1101                            last = last.unwrap().add_next_sibling(tmp);
1102                        } else {
1103                            last = last.unwrap().add_child(tmp);
1104                            last_level = level;
1105                        }
1106                    }
1107                }
1108                // Skip to next node in document order
1109                cur = xml_xptr_advance_node(cur_node, &mut level);
1110                if end_flag != 0 && level >= end_level {
1111                    break;
1112                }
1113            }
1114            list
1115        }
1116    }
1117
1118    /// The XInclude recursive nature is handled at this point.
1119    #[doc(alias = "xmlXIncludeRecurseDoc")]
1120    unsafe fn recurse_doc(&mut self, doc: XmlDocPtr, _url: &str) {
1121        unsafe {
1122            let old_doc = self.doc;
1123            let old_inc_tab = take(&mut self.inc_tab);
1124            let old_is_stream: i32 = self.is_stream;
1125            self.doc = doc;
1126            self.is_stream = 0;
1127
1128            self.do_process(doc.get_root_element().unwrap());
1129
1130            self.doc = old_doc;
1131            self.inc_tab = old_inc_tab;
1132            self.is_stream = old_is_stream;
1133        }
1134    }
1135
1136    /// Load the document, and store the result in the XInclude context
1137    ///
1138    /// Returns 0 in case of success, -1 in case of failure
1139    #[doc(alias = "xmlXIncludeLoadDoc")]
1140    unsafe fn load_doc(&mut self, url: &str, ref_index: usize) -> i32 {
1141        unsafe {
1142            let ret: i32 = -1;
1143            #[cfg(feature = "xpointer")]
1144            let save_flags: i32;
1145
1146            // Check the URL and remove any fragment identifier
1147            let Some(mut uri) = XmlURI::parse(url) else {
1148                xml_xinclude_err!(
1149                    self,
1150                    self.inc_tab[ref_index].elem.map(|node| node.into()),
1151                    XmlParserErrors::XmlXIncludeHrefURI,
1152                    "invalid value URI {}\n",
1153                    url
1154                );
1155                return ret;
1156            };
1157            let mut fragment = uri.fragment.take();
1158            if let Some(frag) = self.inc_tab[ref_index].fragment.as_deref() {
1159                fragment = Some(Cow::Owned(frag.to_owned()));
1160            }
1161            let mut url = uri.save();
1162
1163            // Handling of references to the local document are done
1164            // directly through (*ctxt).doc.
1165            let doc = 'load: {
1166                if url.is_empty() || url.starts_with('#') || self.doc.url.as_deref() == Some(&url) {
1167                    break 'load self.doc;
1168                }
1169                // Prevent reloading the document twice.
1170                for inc_doc in &self.url_tab {
1171                    if *url == *inc_doc.url {
1172                        if inc_doc.expanding != 0 {
1173                            xml_xinclude_err!(
1174                                self,
1175                                self.inc_tab[ref_index].elem.map(|node| node.into()),
1176                                XmlParserErrors::XmlXIncludeRecursion,
1177                                "inclusion loop detected\n"
1178                            );
1179                            return ret;
1180                        }
1181                        let Some(doc) = inc_doc.doc else {
1182                            return ret;
1183                        };
1184                        break 'load doc;
1185                    }
1186                }
1187
1188                // Load it.
1189                #[cfg(feature = "xpointer")]
1190                {
1191                    // If this is an XPointer evaluation, we want to assure that
1192                    // all entities have been resolved prior to processing the
1193                    // referenced document
1194                    save_flags = self.parse_flags;
1195                    if fragment.is_some() {
1196                        // if this is an XPointer eval
1197                        self.parse_flags |= XmlParserOption::XmlParseNoEnt as i32;
1198                    }
1199                }
1200
1201                let doc = self.parse_file(&url);
1202                #[cfg(feature = "xpointer")]
1203                {
1204                    self.parse_flags = save_flags;
1205                }
1206
1207                // Also cache NULL docs
1208                let cache_nr = self.url_tab.len();
1209                self.url_tab.push(XmlXIncludeDoc {
1210                    doc,
1211                    url: url.clone().into_boxed_str(),
1212                    expanding: 0,
1213                });
1214
1215                let Some(doc) = doc else {
1216                    return ret;
1217                };
1218                // It's possible that the requested URL has been mapped to a
1219                // completely different location (e.g. through a catalog entry).
1220                // To check for this, we compare the URL with that of the doc
1221                // and change it if they disagree (bug 146988).
1222                if doc.url.as_deref() != Some(&url) {
1223                    url = doc.url.clone().unwrap();
1224                }
1225
1226                // Make sure we have all entities fixed up
1227                self.merge_entities(self.doc, doc);
1228
1229                // We don't need the DTD anymore, free up space
1230                // if ((*doc).intSubset != null_mut()) {
1231                //     xmlUnlinkNode((xmlNodePtr) (*doc).intSubset);
1232                //     xmlFreeNode((xmlNodePtr) (*doc).intSubset);
1233                //     (*doc).intSubset = NULL;
1234                // }
1235                // if ((*doc).extSubset != null_mut()) {
1236                //     xmlUnlinkNode((xmlNodePtr) (*doc).extSubset);
1237                //     xmlFreeNode((xmlNodePtr) (*doc).extSubset);
1238                //     (*doc).extSubset = NULL;
1239                // }
1240                self.url_tab[cache_nr].expanding = 1;
1241                self.recurse_doc(doc, &url);
1242                // urlTab might be reallocated.
1243                self.url_tab[cache_nr].expanding = 0;
1244                doc
1245            };
1246
1247            // loaded:
1248            if let Some(fragment) = fragment {
1249                #[cfg(feature = "xpointer")]
1250                {
1251                    // Computes the XPointer expression and make a copy used
1252                    // as the replacement copy.
1253
1254                    if self.is_stream != 0 && doc == self.doc {
1255                        xml_xinclude_err!(
1256                            self,
1257                            self.inc_tab[ref_index].elem.map(|node| node.into()),
1258                            XmlParserErrors::XmlXIncludeXPtrFailed,
1259                            "XPointer expressions not allowed in streaming mode\n"
1260                        );
1261                        return ret;
1262                    }
1263
1264                    let mut xptrctxt = xml_xptr_new_context(Some(doc), None, None);
1265                    let Some(mut xptr) = xml_xptr_eval(&fragment, &mut xptrctxt) else {
1266                        xml_xinclude_err!(
1267                            self,
1268                            self.inc_tab[ref_index].elem.map(|node| node.into()),
1269                            XmlParserErrors::XmlXIncludeXPtrFailed,
1270                            "XPointer evaluation failed: #{}\n",
1271                            fragment
1272                        );
1273                        return ret;
1274                    };
1275                    match xptr.typ {
1276                        XmlXPathObjectType::XPathUndefined
1277                        | XmlXPathObjectType::XPathBoolean
1278                        | XmlXPathObjectType::XPathNumber
1279                        | XmlXPathObjectType::XPathString
1280                        | XmlXPathObjectType::XPathUsers
1281                        | XmlXPathObjectType::XPathXSLTTree => {
1282                            xml_xinclude_err!(
1283                                self,
1284                                self.inc_tab[ref_index].elem.map(|node| node.into()),
1285                                XmlParserErrors::XmlXIncludeXPtrResult,
1286                                "XPointer is not a range: #{}\n",
1287                                fragment
1288                            );
1289                            return ret;
1290                        }
1291                        #[cfg(feature = "libxml_xptr_locs")]
1292                        XmlXPathObjectType::XPathPoint => {
1293                            xml_xinclude_err!(
1294                                self,
1295                                self.inc_tab[ref_index].elem.map(|node| node.into()),
1296                                XmlParserErrors::XmlXIncludeXPtrResult,
1297                                "XPointer is not a range: #{}\n",
1298                                fragment
1299                            );
1300                            return ret;
1301                        }
1302                        XmlXPathObjectType::XPathNodeset => {
1303                            if xptr.nodesetval.as_deref().is_none_or(|n| n.is_empty()) {
1304                                return ret;
1305                            }
1306                        }
1307                        #[cfg(feature = "libxml_xptr_locs")]
1308                        XmlXPathObjectType::XPathRange | XmlXPathObjectType::XPathLocationset => {} // _ => {}
1309                    }
1310                    if let Some(set) = xptr.nodesetval.as_deref_mut() {
1311                        let mut i = 0;
1312                        while i < set.node_tab.len() {
1313                            let node = set.node_tab[i];
1314                            match node.element_type() {
1315                                XmlElementType::XmlElementNode
1316                                | XmlElementType::XmlTextNode
1317                                | XmlElementType::XmlCDATASectionNode
1318                                | XmlElementType::XmlEntityRefNode
1319                                | XmlElementType::XmlEntityNode
1320                                | XmlElementType::XmlPINode
1321                                | XmlElementType::XmlCommentNode
1322                                | XmlElementType::XmlDocumentNode
1323                                | XmlElementType::XmlHTMLDocumentNode => {
1324                                    // continue to next loop
1325                                }
1326
1327                                XmlElementType::XmlAttributeNode => {
1328                                    xml_xinclude_err!(
1329                                        self,
1330                                        self.inc_tab[ref_index].elem.map(|node| node.into()),
1331                                        XmlParserErrors::XmlXIncludeXPtrResult,
1332                                        "XPointer selects an attribute: #{}\n",
1333                                        fragment
1334                                    );
1335                                    set.node_tab.swap_remove(i);
1336                                    continue;
1337                                }
1338                                XmlElementType::XmlNamespaceDecl => {
1339                                    xml_xinclude_err!(
1340                                        self,
1341                                        self.inc_tab[ref_index].elem.map(|node| node.into()),
1342                                        XmlParserErrors::XmlXIncludeXPtrResult,
1343                                        "XPointer selects a namespace: #{}\n",
1344                                        fragment
1345                                    );
1346                                    set.node_tab.swap_remove(i);
1347                                    continue;
1348                                }
1349                                XmlElementType::XmlDocumentTypeNode
1350                                | XmlElementType::XmlDocumentFragNode
1351                                | XmlElementType::XmlNotationNode
1352                                | XmlElementType::XmlDTDNode
1353                                | XmlElementType::XmlElementDecl
1354                                | XmlElementType::XmlAttributeDecl
1355                                | XmlElementType::XmlEntityDecl
1356                                | XmlElementType::XmlXIncludeStart
1357                                | XmlElementType::XmlXIncludeEnd => {
1358                                    xml_xinclude_err!(
1359                                        self,
1360                                        self.inc_tab[ref_index].elem.map(|node| node.into()),
1361                                        XmlParserErrors::XmlXIncludeXPtrResult,
1362                                        "XPointer selects unexpected nodes: #{}\n",
1363                                        fragment
1364                                    );
1365                                    set.node_tab.swap_remove(i);
1366                                    continue; /* for */
1367                                }
1368                                _ => unreachable!(),
1369                            }
1370                            i += 1;
1371                        }
1372                    }
1373                    self.inc_tab[ref_index].inc = self.copy_xpointer(&xptr);
1374                }
1375            } else {
1376                // Add the top children list as the replacement copy.
1377                self.inc_tab[ref_index].inc =
1378                    xml_doc_copy_node(doc.get_root_element().unwrap().into(), Some(self.doc), 1)
1379                        .and_then(|node| XmlNodePtr::try_from(node).ok());
1380            }
1381
1382            // Do the xml:base fixup if needed
1383            if doc.parse_flags & XmlParserOption::XmlParseNoBasefix as i32 == 0
1384                && self.parse_flags & XmlParserOption::XmlParseNoBasefix as i32 == 0
1385            {
1386                // The base is only adjusted if "necessary", i.e. if the xinclude node
1387                // has a base specified, or the URL is relative
1388                let mut base = self.inc_tab[ref_index]
1389                    .elem
1390                    .unwrap()
1391                    .get_ns_prop("base", Some(XML_XML_NAMESPACE));
1392                if base.is_none() {
1393                    // No xml:base on the xinclude node, so we check whether the
1394                    // URI base is different than (relative to) the context base
1395                    if let Some(cur_base) = build_relative_uri(&url, self.base.as_deref()) {
1396                        // If the URI doesn't contain a slash, it's not relative
1397                        if cur_base.contains('/') {
1398                            base = Some(cur_base.into_owned());
1399                        }
1400                    } else {
1401                        // Error return
1402                        xml_xinclude_err!(
1403                            self,
1404                            self.inc_tab[ref_index].elem.map(|node| node.into()),
1405                            XmlParserErrors::XmlXIncludeHrefURI,
1406                            "trying to build relative URI from {}\n",
1407                            url
1408                        );
1409                    }
1410                }
1411                if let Some(base) = base {
1412                    // Adjustment may be needed
1413                    let mut node = self.inc_tab[ref_index].inc;
1414                    while let Some(mut cur_node) = node {
1415                        // Only work on element nodes
1416                        if cur_node.element_type() == XmlElementType::XmlElementNode {
1417                            if let Some(cur_base) = cur_node.get_base(cur_node.doc) {
1418                                // If the current base is the same as the
1419                                // URL of the document, then reset it to be
1420                                // the specified xml:base or the relative URI
1421                                if cur_node.doc.as_deref().and_then(|doc| doc.url.as_deref())
1422                                    == Some(cur_base.as_str())
1423                                {
1424                                    cur_node.set_base(Some(&base));
1425                                } else {
1426                                    // If the element already has an xml:base set,
1427                                    // then relativise it if necessary
1428
1429                                    if let Some(xml_base) =
1430                                        cur_node.get_ns_prop("base", Some(XML_XML_NAMESPACE))
1431                                    {
1432                                        let rel_base = build_uri(&xml_base, &base);
1433                                        if let Some(rel_base) = rel_base {
1434                                            cur_node.set_base(Some(&rel_base));
1435                                        } else {
1436                                            // error
1437                                            xml_xinclude_err!(
1438                                                self,
1439                                                self.inc_tab[ref_index]
1440                                                    .elem
1441                                                    .map(|node| node.into()),
1442                                                XmlParserErrors::XmlXIncludeHrefURI,
1443                                                "trying to rebuild base from {}\n",
1444                                                xml_base
1445                                            );
1446                                        }
1447                                    }
1448                                }
1449                            } else {
1450                                // If no current base, set it
1451                                cur_node.set_base(Some(&base));
1452                            }
1453                        }
1454                        node = cur_node
1455                            .next
1456                            .map(|node| XmlNodePtr::try_from(node).unwrap());
1457                    }
1458                }
1459            }
1460            0
1461        }
1462    }
1463
1464    /// Load the content, and store the result in the XInclude context
1465    ///
1466    /// Returns 0 in case of success, -1 in case of failure
1467    #[doc(alias = "xmlXIncludeLoadTxt")]
1468    unsafe fn load_txt(&mut self, mut url: &str, ref_index: usize) -> i32 {
1469        unsafe {
1470            let ret: i32 = -1;
1471            let mut enc = XmlCharEncoding::None;
1472
1473            // Don't read from stdin.
1474            if url == "-" {
1475                url = "./-";
1476            }
1477
1478            // Check the URL and remove any fragment identifier
1479            let Some(uri) = XmlURI::parse(url) else {
1480                xml_xinclude_err!(
1481                    self,
1482                    self.inc_tab[ref_index].elem.map(|node| node.into()),
1483                    XmlParserErrors::XmlXIncludeHrefURI,
1484                    "invalid value URI {}\n",
1485                    url
1486                );
1487                return ret;
1488            };
1489            if let Some(fragment) = uri.fragment.as_deref() {
1490                xml_xinclude_err!(
1491                    self,
1492                    self.inc_tab[ref_index].elem.map(|node| node.into()),
1493                    XmlParserErrors::XmlXIncludeTextFragment,
1494                    "fragment identifier forbidden for text: {}\n",
1495                    fragment
1496                );
1497                return ret;
1498            }
1499            let url = uri.save();
1500
1501            // Handling of references to the local document are done directly through (*ctxt).doc.
1502            if url.is_empty() {
1503                xml_xinclude_err!(
1504                    self,
1505                    self.inc_tab[ref_index].elem.map(|node| node.into()),
1506                    XmlParserErrors::XmlXIncludeTextDocument,
1507                    "text serialization of document not available\n"
1508                );
1509                return ret;
1510            }
1511
1512            // Prevent reloading the document twice.
1513            for txt in &self.txt_tab {
1514                if *url == *txt.url {
1515                    let node = xml_new_doc_text(Some(self.doc), Some(&txt.text));
1516                    self.inc_tab[ref_index].inc = node;
1517                    return 0;
1518                }
1519            }
1520
1521            // Try to get the encoding if available
1522            let mut encoding = None;
1523            if let Some(elem) = self.inc_tab[ref_index].elem {
1524                encoding = elem.get_prop(XINCLUDE_PARSE_ENCODING);
1525            }
1526            if let Some(encoding) = encoding {
1527                // TODO: we should not have to remap to the xmlCharEncoding
1528                //       predefined set, a better interface than
1529                //       xmlParserInputBufferCreateFilename should allow any
1530                //       encoding supported by iconv
1531                match encoding.parse::<XmlCharEncoding>() {
1532                    Ok(e) => enc = e,
1533                    _ => {
1534                        xml_xinclude_err!(
1535                            self,
1536                            self.inc_tab[ref_index].elem.map(|node| node.into()),
1537                            XmlParserErrors::XmlXIncludeUnknownEncoding,
1538                            "encoding {} not supported\n",
1539                            encoding
1540                        );
1541                        return ret;
1542                    }
1543                }
1544            }
1545
1546            // Load it.
1547            let mut pctxt = XmlParserCtxt::new().unwrap();
1548            let Some(mut input_stream) = xml_load_external_entity(Some(&url), None, &mut pctxt)
1549            else {
1550                return ret;
1551            };
1552            let Some(buf) = input_stream.buf.as_mut() else {
1553                return ret;
1554            };
1555            buf.encoder = get_encoding_handler(enc);
1556            let Some(mut node) = xml_new_doc_text(Some(self.doc), None) else {
1557                let node = self.inc_tab[ref_index].elem.map(|node| node.into());
1558                xml_xinclude_err_memory(Some(self), node, None);
1559                return ret;
1560            };
1561
1562            // Scan all chars from the resource and add the to the node
1563            while buf.grow(4096) > 0 {}
1564
1565            let content = buf.buffer.as_ref();
1566            match std::str::from_utf8(content) {
1567                Ok(content) if content.chars().all(|c| c.is_xml_char()) => {
1568                    node.add_content(content);
1569                }
1570                _ => {
1571                    xml_xinclude_err!(
1572                        self,
1573                        self.inc_tab[ref_index].elem.map(|node| node.into()),
1574                        XmlParserErrors::XmlXIncludeInvalidChar,
1575                        "{} contains invalid char\n",
1576                        url
1577                    );
1578                    // goto error;
1579                    xml_free_node(node);
1580                    return ret;
1581                }
1582            }
1583
1584            self.txt_tab.push(XmlXIncludeTxt {
1585                text: node.content.as_deref().unwrap().into(),
1586                url: url.into_boxed_str(),
1587            });
1588
1589            // loaded:
1590            // Add the element as the replacement copy.
1591            self.inc_tab[ref_index].inc = Some(node);
1592            0
1593        }
1594    }
1595
1596    /// Load the content of the fallback node, and store the result in the XInclude context
1597    ///
1598    /// Returns 0 in case of success, -1 in case of failure
1599    #[doc(alias = "xmlXIncludeLoadFallback")]
1600    unsafe fn load_fallback(&mut self, fallback: XmlNodePtr, ref_index: usize) -> i32 {
1601        unsafe {
1602            let mut ret: i32 = 0;
1603
1604            if fallback.element_type() == XmlElementType::XmlNamespaceDecl {
1605                return -1;
1606            }
1607            if fallback.children().is_some() {
1608                // It's possible that the fallback also has 'includes'
1609                // (Bug 129969), so we re-process the fallback just in case
1610                let old_nb_errors = self.nb_errors;
1611                self.inc_tab[ref_index].inc = self.copy_node(fallback, 1);
1612                if self.nb_errors > old_nb_errors {
1613                    ret = -1;
1614                } else if self.inc_tab[ref_index].inc.is_none() {
1615                    self.inc_tab[ref_index].empty_fb = 1;
1616                }
1617            } else {
1618                self.inc_tab[ref_index].inc = None;
1619                self.inc_tab[ref_index].empty_fb = 1; /* flag empty callback */
1620            }
1621            self.inc_tab[ref_index].fallback = 1;
1622            ret
1623        }
1624    }
1625
1626    /// Find and load the infoset replacement for the given node.
1627    ///
1628    /// Returns 0 if substitution succeeded, -1 if some processing failed
1629    #[doc(alias = "xmlXIncludeLoadNode")]
1630    unsafe fn load_node(&mut self, ref_index: usize) -> i32 {
1631        unsafe {
1632            let mut xml: i32 = 1; /* default Issue 64 */
1633            let mut ret: i32;
1634
1635            if ref_index == usize::MAX {
1636                return -1;
1637            }
1638            let Some(cur) = self.inc_tab[ref_index].elem else {
1639                return -1;
1640            };
1641
1642            // read the attributes
1643            let href = self.get_prop(cur, XINCLUDE_HREF).unwrap_or("".to_owned());
1644            let parse = self.get_prop(cur, XINCLUDE_PARSE);
1645            if let Some(parse) = parse {
1646                if parse == XINCLUDE_PARSE_XML {
1647                    xml = 1;
1648                } else if parse == XINCLUDE_PARSE_TEXT {
1649                    xml = 0;
1650                } else {
1651                    xml_xinclude_err!(
1652                        self,
1653                        Some(cur.into()),
1654                        XmlParserErrors::XmlXIncludeParseValue,
1655                        "invalid value {} for 'parse'\n",
1656                        parse
1657                    );
1658                    return -1;
1659                }
1660            }
1661
1662            // compute the URI
1663            let mut base = None;
1664            let mut uri = if let Some(b) = cur.get_base(Some(self.doc)) {
1665                base = Some(b);
1666                build_uri(&href, base.as_deref().unwrap())
1667            } else {
1668                self.doc
1669                    .url
1670                    .as_deref()
1671                    .and_then(|base| build_uri(&href, base))
1672            };
1673            if uri.is_none() {
1674                if let Some(base) = base.as_deref() {
1675                    // Some escaping may be needed
1676                    if let (Some(escbase), Some(eschref)) = (escape_url(base), escape_url(&href)) {
1677                        uri = build_uri(&eschref, &escbase);
1678                    }
1679                }
1680            }
1681            let Some(uri) = uri else {
1682                xml_xinclude_err!(
1683                    self,
1684                    Some(cur.into()),
1685                    XmlParserErrors::XmlXIncludeHrefURI,
1686                    "failed build URL\n"
1687                );
1688                return -1;
1689            };
1690
1691            // Save the base for this include (saving the current one)
1692            let old_base = self.base.take();
1693            self.base = base.map(|base| base.into());
1694
1695            if xml != 0 {
1696                ret = self.load_doc(&uri, ref_index);
1697                // xmlXIncludeGetFragment(self, cur, URI);
1698            } else {
1699                ret = self.load_txt(&uri, ref_index);
1700            }
1701
1702            // Restore the original base before checking for fallback
1703            self.base = old_base;
1704
1705            if ret < 0 {
1706                // Time to try a fallback if available
1707                let mut children = cur.children.map(|c| XmlNodePtr::try_from(c).unwrap());
1708                while let Some(cur_node) = children {
1709                    if cur_node.element_type() == XmlElementType::XmlElementNode
1710                        && cur_node.name().as_deref() == Some(XINCLUDE_FALLBACK)
1711                        && cur_node.ns.is_some_and(|ns| {
1712                            ns.href().as_deref() == Some(XINCLUDE_NS)
1713                                || ns.href().as_deref() == Some(XINCLUDE_OLD_NS)
1714                        })
1715                    {
1716                        ret = self.load_fallback(cur_node, ref_index);
1717                        break;
1718                    }
1719                    children = cur_node
1720                        .next
1721                        .map(|node| XmlNodePtr::try_from(node).unwrap());
1722                }
1723            }
1724            if ret < 0 {
1725                xml_xinclude_err!(
1726                    self,
1727                    Some(cur.into()),
1728                    XmlParserErrors::XmlXIncludeNoFallback,
1729                    "could not load {}, and no fallback was found\n",
1730                    uri
1731                );
1732            }
1733
1734            0
1735        }
1736    }
1737
1738    /// Parse a document for XInclude
1739    #[doc(alias = "xmlXIncludeParseFile")]
1740    unsafe fn parse_file(&mut self, mut url: &str) -> Option<XmlDocPtr> {
1741        unsafe {
1742            xml_init_parser();
1743
1744            let Some(mut pctxt) = XmlParserCtxt::new() else {
1745                xml_xinclude_err_memory(Some(self), None, Some("cannot allocate parser context"));
1746                return None;
1747            };
1748
1749            // pass in the application data to the parser context.
1750            pctxt._private = self._private;
1751
1752            pctxt.use_options(self.parse_flags | XmlParserOption::XmlParseDTDLoad as i32);
1753
1754            // Don't read from stdin.
1755            if url == "-" {
1756                url = "./-";
1757            }
1758
1759            let input_stream = xml_load_external_entity(Some(url), None, &mut pctxt)?;
1760            pctxt.input_push(input_stream);
1761
1762            if pctxt.directory.is_none() {
1763                if let Some(dir) = xml_parser_get_directory(url) {
1764                    pctxt.directory = Some(dir.to_string_lossy().into_owned());
1765                }
1766            }
1767
1768            pctxt.loadsubset |= XML_DETECT_IDS as i32;
1769            pctxt.parse_document();
1770
1771            if pctxt.well_formed {
1772                pctxt.my_doc
1773            } else {
1774                if let Some(my_doc) = pctxt.my_doc.take() {
1775                    xml_free_doc(my_doc);
1776                }
1777                None
1778            }
1779        }
1780    }
1781
1782    /// Implement the XInclude substitution on the XML document @doc
1783    ///
1784    /// Returns 0 if no substitution were done, -1 if some processing failed
1785    /// or the number of substitutions done.
1786    #[doc(alias = "xmlXIncludeDoProcess")]
1787    unsafe fn do_process(&mut self, tree: XmlNodePtr) -> i32 {
1788        unsafe {
1789            let mut ret: i32 = 0;
1790
1791            if tree.element_type() == XmlElementType::XmlNamespaceDecl {
1792                return -1;
1793            }
1794
1795            // First phase: lookup the elements in the document
1796            let start = self.inc_tab.len();
1797            let mut cur = tree;
1798            'main: while {
1799                'inner: {
1800                    // TODO: need to work on entities -> stack
1801                    if self.test_node(cur) == 1 {
1802                        let ref_index = self.expand_node(cur);
1803                        // Mark direct includes.
1804                        if ref_index != usize::MAX {
1805                            self.inc_tab[ref_index].replace = 1;
1806                        }
1807                    } else if let Some(children) = cur
1808                        .children()
1809                        .filter(|_| {
1810                            matches!(
1811                                cur.element_type(),
1812                                XmlElementType::XmlDocumentNode | XmlElementType::XmlElementNode
1813                            )
1814                        })
1815                        .map(|children| XmlNodePtr::try_from(children).unwrap())
1816                    {
1817                        cur = children;
1818                        break 'inner;
1819                    }
1820                    'b: loop {
1821                        if cur == tree {
1822                            break 'main;
1823                        }
1824                        if let Some(next) = cur.next.map(|node| XmlNodePtr::try_from(node).unwrap())
1825                        {
1826                            cur = next;
1827                            break 'b;
1828                        }
1829                        let Some(next) = cur.parent.map(|p| XmlNodePtr::try_from(p).unwrap())
1830                        else {
1831                            break 'main;
1832                        };
1833
1834                        cur = next;
1835                    }
1836                }
1837
1838                cur != tree
1839            } {}
1840
1841            // Second phase: extend the original document infoset.
1842            let len = self.inc_tab.len();
1843            for i in start..len {
1844                if self.inc_tab[i].replace != 0 {
1845                    if self.inc_tab[i].inc.is_some() || self.inc_tab[i].empty_fb != 0 {
1846                        // (empty fallback)
1847                        self.include_node(i);
1848                    }
1849                    self.inc_tab[i].replace = 0;
1850                } else {
1851                    // Ignore includes which were added indirectly, for example
1852                    // inside xi:fallback elements.
1853                    if let Some(inc) = self.inc_tab[i].inc.take() {
1854                        xml_free_node_list(Some(inc));
1855                    }
1856                }
1857                ret += 1;
1858            }
1859
1860            if self.is_stream != 0 {
1861                // incTab references nodes which will eventually be deleted in
1862                // streaming mode. The table is only required for XPointer
1863                // expressions which aren't allowed in streaming mode.
1864                self.inc_tab.clear();
1865            }
1866
1867            ret
1868        }
1869    }
1870
1871    /// Implement the XInclude substitution for the given subtree reusing
1872    /// the information and data coming from the given context.
1873    ///
1874    /// Returns 0 if no substitution were done, -1 if some processing failed
1875    /// or the number of substitutions done.
1876    #[doc(alias = "xmlXIncludeProcessNode")]
1877    pub unsafe fn process_node(&mut self, node: XmlNodePtr) -> i32 {
1878        unsafe {
1879            if node.element_type() == XmlElementType::XmlNamespaceDecl || node.doc.is_none() {
1880                return -1;
1881            }
1882            let mut ret = self.do_process(node);
1883            if ret >= 0 && self.nb_errors > 0 {
1884                ret = -1;
1885            }
1886            ret
1887        }
1888    }
1889}
1890
1891impl Drop for XmlXIncludeCtxt {
1892    /// Free an XInclude context
1893    #[doc(alias = "xmlXIncludeFreeContext")]
1894    fn drop(&mut self) {
1895        for inc_doc in self.url_tab.drain(..) {
1896            if let Some(doc) = inc_doc.doc {
1897                unsafe {
1898                    xml_free_doc(doc);
1899                }
1900            }
1901        }
1902    }
1903}
1904
1905/// Implement the XInclude substitution on the XML document @doc
1906///
1907/// Returns 0 if no substitution were done, -1 if some processing failed
1908/// or the number of substitutions done.
1909#[doc(alias = "xmlXIncludeProcess")]
1910pub unsafe fn xml_xinclude_process(doc: XmlDocPtr) -> i32 {
1911    unsafe { xml_xinclude_process_flags(doc, 0) }
1912}
1913
1914/// Implement the XInclude substitution on the XML document @doc
1915///
1916/// Returns 0 if no substitution were done, -1 if some processing failed
1917/// or the number of substitutions done.
1918#[doc(alias = "xmlXIncludeProcessFlags")]
1919pub unsafe fn xml_xinclude_process_flags(doc: XmlDocPtr, flags: i32) -> i32 {
1920    unsafe { xml_xinclude_process_flags_data(doc, flags, null_mut()) }
1921}
1922
1923/// Implement the XInclude substitution on the XML document @doc
1924///
1925/// Returns 0 if no substitution were done, -1 if some processing failed
1926/// or the number of substitutions done.
1927#[doc(alias = "xmlXIncludeProcessFlagsData")]
1928pub unsafe fn xml_xinclude_process_flags_data(
1929    doc: XmlDocPtr,
1930    flags: i32,
1931    data: *mut c_void,
1932) -> i32 {
1933    unsafe {
1934        let Some(tree) = doc.get_root_element() else {
1935            return -1;
1936        };
1937        xml_xinclude_process_tree_flags_data(tree, flags, data)
1938    }
1939}
1940
1941const XINCLUDE_MAX_DEPTH: i32 = 40;
1942
1943/// Handle an out of memory condition
1944#[doc(alias = "xmlXIncludeErrMemory")]
1945unsafe fn xml_xinclude_err_memory(
1946    ctxt: Option<&mut XmlXIncludeCtxt>,
1947    node: Option<XmlGenericNodePtr>,
1948    extra: Option<&str>,
1949) {
1950    let mut ptr = null_mut();
1951    if let Some(ctxt) = ctxt {
1952        ctxt.nb_errors += 1;
1953        ptr = ctxt as *mut XmlXIncludeCtxt;
1954    }
1955    if let Some(extra) = extra {
1956        __xml_raise_error!(
1957            None,
1958            None,
1959            None,
1960            ptr as _,
1961            node,
1962            XmlErrorDomain::XmlFromXInclude,
1963            XmlParserErrors::XmlErrNoMemory,
1964            XmlErrorLevel::XmlErrError,
1965            None,
1966            0,
1967            Some(extra.to_owned().into()),
1968            None,
1969            None,
1970            0,
1971            0,
1972            "Memory allocation failed : {}\n",
1973            extra
1974        );
1975    } else {
1976        __xml_raise_error!(
1977            None,
1978            None,
1979            None,
1980            ptr as _,
1981            node,
1982            XmlErrorDomain::XmlFromXInclude,
1983            XmlParserErrors::XmlErrNoMemory,
1984            XmlErrorLevel::XmlErrError,
1985            None,
1986            0,
1987            None,
1988            None,
1989            None,
1990            0,
1991            0,
1992            "Memory allocation failed\n",
1993        );
1994    }
1995}
1996
1997/// Returns the @n'th element child of @cur or NULL
1998#[doc(alias = "xmlXIncludeGetNthChild")]
1999#[cfg(feature = "libxml_xptr_locs")]
2000fn xml_xinclude_get_nth_child(cur: XmlGenericNodePtr, no: i32) -> Option<XmlGenericNodePtr> {
2001    if cur.element_type() == XmlElementType::XmlNamespaceDecl {
2002        return None;
2003    }
2004    let mut cur = cur.children();
2005    let mut i = 0;
2006    while i <= no {
2007        let now = cur?;
2008        if matches!(
2009            now.element_type(),
2010            XmlElementType::XmlElementNode
2011                | XmlElementType::XmlDocumentNode
2012                | XmlElementType::XmlHTMLDocumentNode
2013        ) {
2014            i += 1;
2015            if i == no {
2016                break;
2017            }
2018        }
2019
2020        cur = now.next();
2021    }
2022    cur
2023}
2024
2025/// Implement the XInclude substitution on the XML node @tree
2026///
2027/// Returns 0 if no substitution were done, -1 if some processing failed
2028/// or the number of substitutions done.
2029#[doc(alias = "xmlXIncludeProcessTreeFlagsData")]
2030pub unsafe fn xml_xinclude_process_tree_flags_data(
2031    tree: XmlNodePtr,
2032    flags: i32,
2033    data: *mut c_void,
2034) -> i32 {
2035    unsafe {
2036        if tree.element_type() == XmlElementType::XmlNamespaceDecl {
2037            return -1;
2038        }
2039        let Some(doc) = tree.doc else {
2040            return -1;
2041        };
2042
2043        let mut ctxt = XmlXIncludeCtxt::new(doc);
2044        ctxt._private = data;
2045        ctxt.base = doc.url.as_deref().map(|url| url.into());
2046        ctxt.set_flags(flags);
2047        let mut ret = ctxt.do_process(tree);
2048        if ret >= 0 && ctxt.nb_errors > 0 {
2049            ret = -1;
2050        }
2051
2052        ret
2053    }
2054}
2055
2056/// Implement the XInclude substitution for the given subtree
2057///
2058/// Returns 0 if no substitution were done, -1 if some processing failed
2059/// or the number of substitutions done.
2060#[doc(alias = "xmlXIncludeProcessTree")]
2061pub unsafe fn xml_xinclude_process_tree(tree: XmlNodePtr) -> i32 {
2062    unsafe { xml_xinclude_process_tree_flags(tree, 0) }
2063}
2064
2065/// Implement the XInclude substitution for the given subtree
2066///
2067/// Returns 0 if no substitution were done, -1 if some processing failed
2068/// or the number of substitutions done.
2069#[doc(alias = "xmlXIncludeProcessTreeFlags")]
2070pub unsafe fn xml_xinclude_process_tree_flags(tree: XmlNodePtr, flags: i32) -> i32 {
2071    unsafe {
2072        if tree.element_type() == XmlElementType::XmlNamespaceDecl {
2073            return -1;
2074        }
2075        let Some(doc) = tree.doc else {
2076            return -1;
2077        };
2078        let mut ctxt = XmlXIncludeCtxt::new(doc);
2079        ctxt.base = tree.get_base(Some(doc)).map(|base| base.into());
2080        ctxt.set_flags(flags);
2081        let mut ret = ctxt.do_process(tree);
2082        if ret >= 0 && ctxt.nb_errors > 0 {
2083            ret = -1;
2084        }
2085
2086        ret
2087    }
2088}