exml/tree/
attribute.rs

1// Copyright of the original code is the following.
2// --------
3// Summary: interfaces for tree manipulation
4// Description: this module describes the structures found in an tree resulting
5//              from an XML or HTML parsing, as well as the API provided for
6//              various processing on that tree
7//
8// Copy: See Copyright for the status of this software.
9//
10// Author: Daniel Veillard
11// --------
12// tree.c : implementation of access function for an XML tree.
13//
14// References:
15//   XHTML 1.0 W3C REC: http://www.w3.org/TR/2002/REC-xhtml1-20020801/
16//
17// See Copyright for the status of this software.
18//
19// daniel@veillard.com
20
21use std::{
22    any::type_name,
23    borrow::Cow,
24    ops::{Deref, DerefMut},
25    os::raw::c_void,
26    ptr::{NonNull, null_mut},
27};
28
29use crate::{
30    globals::{get_deregister_node_func, get_register_node_func},
31    valid::{xml_add_id, xml_is_id, xml_remove_id},
32};
33
34use super::{
35    InvalidNodePointerCastError, NodeCommon, XML_XML_NAMESPACE, XmlAttributeType, XmlDocPtr,
36    XmlElementType, XmlGenericNodePtr, XmlNodePtr, XmlNsPtr, xml_free_node_list, xml_new_doc_text,
37    xml_new_ns, xml_new_reconciled_ns, xml_ns_in_scope, xml_static_copy_node_list,
38    xml_tree_err_memory,
39};
40
41#[repr(C)]
42pub struct XmlAttr {
43    pub _private: *mut c_void,                  /* application data */
44    pub(crate) typ: XmlElementType,             /* XML_ATTRIBUTE_NODE, must be second ! */
45    pub name: Box<str>,                         /* the name of the property */
46    pub(crate) children: Option<XmlNodePtr>,    /* the value of the property */
47    pub(crate) last: Option<XmlNodePtr>,        /* NULL */
48    pub(crate) parent: Option<XmlNodePtr>,      /* child->parent link */
49    pub next: Option<XmlAttrPtr>,               /* next sibling link  */
50    pub(crate) prev: Option<XmlAttrPtr>,        /* previous sibling link  */
51    pub(crate) doc: Option<XmlDocPtr>,          /* the containing document */
52    pub ns: Option<XmlNsPtr>,                   /* pointer to the associated namespace */
53    pub(crate) atype: Option<XmlAttributeType>, /* the attribute type if validating */
54    pub(crate) psvi: *mut c_void,               /* for type/PSVI information */
55}
56
57impl XmlAttr {
58    /// Search a Ns aliasing a given URI.
59    /// Recurse on the parents until it finds the defined namespace or return NULL otherwise.
60    ///
61    /// Returns the namespace pointer or NULL.
62    #[doc(alias = "xmlSearchNsByHref")]
63    pub fn search_ns_by_href(&mut self, doc: Option<XmlDocPtr>, href: &str) -> Option<XmlNsPtr> {
64        if href == XML_XML_NAMESPACE {
65            let mut doc = doc.or(self.document())?;
66            // Return the XML namespace declaration held by the doc.
67            if doc.old_ns.is_none() {
68                return doc.ensure_xmldecl();
69            } else {
70                return doc.old_ns;
71            }
72        }
73        let mut node = self.parent.map(XmlGenericNodePtr::from);
74        while let Some(now) = node {
75            if matches!(
76                now.element_type(),
77                XmlElementType::XmlEntityRefNode
78                    | XmlElementType::XmlEntityNode
79                    | XmlElementType::XmlEntityDecl
80            ) {
81                return None;
82            }
83            if let Some(now) = XmlNodePtr::try_from(now)
84                .ok()
85                .filter(|now| now.element_type() == XmlElementType::XmlElementNode)
86            {
87                // let href = CString::new(href).unwrap();
88                let mut cur = now.ns_def;
89                while let Some(cur_ns) = cur {
90                    if cur_ns.href.is_some()
91                        && cur_ns.href().as_deref() == Some(href)
92                        && cur_ns.prefix().is_some()
93                        && xml_ns_in_scope(
94                            doc,
95                            XmlGenericNodePtr::from_raw(self as *mut Self),
96                            Some(now.into()),
97                            cur_ns.prefix.as_deref(),
98                        ) == 1
99                    {
100                        return Some(cur_ns);
101                    }
102                    cur = cur_ns.next;
103                }
104                let cur = now.ns;
105                if let Some(cur) = cur.filter(|cur| {
106                    cur.href.as_deref().is_some_and(|h| h == href)
107                        && cur.prefix().is_some()
108                        && xml_ns_in_scope(
109                            doc,
110                            XmlGenericNodePtr::from_raw(self as *mut Self),
111                            Some(now.into()),
112                            cur.prefix.as_deref(),
113                        ) == 1
114                }) {
115                    return Some(cur);
116                }
117            }
118            node = now.parent();
119        }
120        None
121    }
122
123    pub(super) fn get_prop_node_value_internal(&self) -> Option<String> {
124        // Note that we return at least the empty string.
125        // TODO: Do we really always want that?
126        if let Some(children) = self.children() {
127            if children.next().is_none()
128                && matches!(
129                    children.element_type(),
130                    XmlElementType::XmlTextNode | XmlElementType::XmlCDATASectionNode
131                )
132            {
133                let children = XmlNodePtr::try_from(children).unwrap();
134                // Optimization for the common case: only 1 text node.
135                return children.content.clone();
136            } else if let Some(ret) = children.get_string(self.document(), 1) {
137                return Some(ret);
138            }
139        }
140        Some("".to_owned())
141    }
142
143    /// Read the value of a node, this can be either the text carried
144    /// directly by this node if it's a TEXT node or the aggregate string
145    /// of the values carried by this node child's (TEXT and ENTITY_REF).  
146    ///
147    /// Entity references are substituted.
148    ///
149    /// Returns a new #XmlChar * or null_mut() if no content is available.  
150    /// It's up to the caller to free the memory with xml_free().
151    #[doc(alias = "xmlNodeGetContent")]
152    pub fn get_content(&self) -> Option<String> {
153        self.get_prop_node_value_internal()
154    }
155
156    /// Read the value of a node `cur`, this can be either the text carried
157    /// directly by this node if it's a TEXT node or the aggregate string
158    /// of the values carried by this node child's (TEXT and ENTITY_REF).
159    ///
160    /// Entity references are substituted. Fills up the buffer `buf` with this value.
161    ///
162    /// Returns 0 in case of success and -1 in case of error.
163    #[doc(alias = "xmlBufGetNodeContent")]
164    pub fn get_content_to(&self, buf: &mut String) -> i32 {
165        assert!(matches!(
166            self.element_type(),
167            XmlElementType::XmlAttributeNode
168        ));
169        let mut tmp = self.children();
170
171        while let Some(now) = tmp {
172            if matches!(now.element_type(), XmlElementType::XmlTextNode) {
173                let now = XmlNodePtr::try_from(now).unwrap();
174                buf.push_str(now.content.as_deref().unwrap());
175            } else {
176                now.get_content_to(buf);
177            }
178            tmp = now.next();
179        }
180        0
181    }
182
183    /// Set (or reset) the base URI of a node, i.e. the value of the xml:base attribute.
184    #[doc(alias = "xmlNodeSetBase")]
185    #[cfg(any(feature = "libxml_tree", feature = "xinclude"))]
186    pub fn set_base(&mut self, _uri: Option<&str>) {
187        use crate::tree::XML_XML_NAMESPACE;
188
189        self.search_ns_by_href(self.document(), XML_XML_NAMESPACE);
190    }
191
192    /// update all nodes under the tree to point to the right document
193    #[doc(alias = "xmlSetTreeDoc")]
194    pub fn set_doc(&mut self, doc: Option<XmlDocPtr>) {
195        if self.document() != doc {
196            if let Some(children) = self.children() {
197                children.set_doc_all_sibling(doc);
198            }
199
200            // FIXME: self.ns should be updated as in xmlStaticCopyNode().
201            self.set_document(doc);
202        }
203    }
204}
205
206impl Default for XmlAttr {
207    fn default() -> Self {
208        Self {
209            _private: null_mut(),
210            typ: XmlElementType::XmlAttributeNode,
211            name: "".into(),
212            children: None,
213            last: None,
214            parent: None,
215            next: None,
216            prev: None,
217            doc: None,
218            ns: None,
219            atype: None,
220            psvi: null_mut(),
221        }
222    }
223}
224
225impl NodeCommon for XmlAttr {
226    fn document(&self) -> Option<XmlDocPtr> {
227        self.doc
228    }
229    fn set_document(&mut self, doc: Option<XmlDocPtr>) {
230        self.doc = doc;
231    }
232    fn element_type(&self) -> XmlElementType {
233        self.typ
234    }
235    fn name(&self) -> Option<Cow<'_, str>> {
236        Some(Cow::Borrowed(&self.name))
237    }
238    fn children(&self) -> Option<XmlGenericNodePtr> {
239        self.children.map(|children| children.into())
240    }
241    fn set_children(&mut self, children: Option<XmlGenericNodePtr>) {
242        self.children = children.map(|children| XmlNodePtr::try_from(children).unwrap());
243    }
244    fn last(&self) -> Option<XmlGenericNodePtr> {
245        self.last.map(|last| last.into())
246    }
247    fn set_last(&mut self, last: Option<XmlGenericNodePtr>) {
248        self.last = last.map(|children| XmlNodePtr::try_from(children).unwrap());
249    }
250    fn next(&self) -> Option<XmlGenericNodePtr> {
251        self.next.map(|next| next.into())
252    }
253    fn set_next(&mut self, next: Option<XmlGenericNodePtr>) {
254        self.next = next.map(|next| XmlAttrPtr::try_from(next).unwrap())
255    }
256    fn prev(&self) -> Option<XmlGenericNodePtr> {
257        self.prev.map(|prev| prev.into())
258    }
259    fn set_prev(&mut self, prev: Option<XmlGenericNodePtr>) {
260        self.prev = prev.map(|prev| XmlAttrPtr::try_from(prev).unwrap())
261    }
262    fn parent(&self) -> Option<XmlGenericNodePtr> {
263        self.parent.map(|parent| parent.into())
264    }
265    fn set_parent(&mut self, parent: Option<XmlGenericNodePtr>) {
266        self.parent = parent.map(|children| XmlNodePtr::try_from(children).unwrap());
267    }
268
269    fn unlink(&mut self) {
270        if let Some(mut parent) = self.parent {
271            let attr = unsafe {
272                // # Safety
273                // Please see the document of `XmlAttrPtr::from_raw`.
274                // In addition, this pointer is not leaked to the out of this function.
275                XmlAttrPtr::from_raw(self).unwrap()
276            };
277            if parent.properties == attr {
278                parent.properties = attr.and_then(|attr| attr.next);
279            }
280            self.set_parent(None);
281        }
282        if let Some(mut next) = self.next() {
283            next.set_prev(self.prev());
284        }
285        if let Some(mut prev) = self.prev() {
286            prev.set_next(self.next());
287        }
288        self.set_next(None);
289        self.set_prev(None);
290    }
291}
292
293#[derive(PartialEq, Eq, PartialOrd, Ord)]
294pub struct XmlAttrPtr(NonNull<XmlAttr>);
295
296impl XmlAttrPtr {
297    /// Allocate new memory and create new `XmlAttrPtr` from an owned xml node.
298    ///
299    /// This method leaks allocated memory.  
300    /// Users can use `free` method for deallocating memory.
301    pub(crate) fn new(node: XmlAttr) -> Option<Self> {
302        let boxed = Box::new(node);
303        NonNull::new(Box::leak(boxed)).map(Self)
304    }
305
306    /// Create `XmlAttrPtr` from a raw pointer.  
307    ///
308    /// If `ptr` is a NULL pointer, return `Ok(None)`.  
309    /// If `ptr` is a valid pointer of `XmlAttr`, return `Ok(Some(Self))`.  
310    /// Otherwise, return `Err`.
311    ///
312    /// # Safety
313    /// - `ptr` must be a pointer of types that is implemented `NodeCommon` at least.
314    pub(crate) unsafe fn from_raw(
315        ptr: *mut XmlAttr,
316    ) -> Result<Option<Self>, InvalidNodePointerCastError> {
317        unsafe {
318            if ptr.is_null() {
319                return Ok(None);
320            }
321            match (*ptr).element_type() {
322                XmlElementType::XmlAttributeNode => Ok(Some(Self(NonNull::new_unchecked(ptr)))),
323                _ => Err(InvalidNodePointerCastError {
324                    from: (*ptr).element_type(),
325                    to: type_name::<Self>(),
326                }),
327            }
328        }
329    }
330
331    // pub(crate) fn as_ptr(self) -> *mut XmlAttr {
332    //     self.0.as_ptr()
333    // }
334
335    /// Deallocate memory.
336    ///
337    /// # Safety
338    /// This method should be called only once.  
339    /// If called more than twice, the behavior is undefined.
340    pub(crate) unsafe fn free(self) {
341        unsafe {
342            let _ = *Box::from_raw(self.0.as_ptr());
343        }
344    }
345
346    // /// Acquire the ownership of the inner value.
347    // /// As a result, `self` will be invalid. `self` must not be used after performs this method.
348    // ///
349    // /// # Safety
350    // /// This method should be called only once.
351    // /// If called more than twice, the behavior is undefined.
352    // pub(crate) unsafe fn into_inner(self) -> Box<XmlAttr> {
353    //     unsafe { Box::from_raw(self.0.as_ptr()) }
354    // }
355
356    /// Unlink and free one attribute, all the content is freed too.
357    ///
358    /// Note this doesn't work for namespace definition attributes.
359    ///
360    /// Returns 0 if success and -1 in case of error.
361    #[doc(alias = "xmlRemoveProp")]
362    pub unsafe fn remove_prop(self) -> i32 {
363        unsafe {
364            let Some(mut parent) = self
365                .parent()
366                .map(|parent| XmlNodePtr::try_from(parent).unwrap())
367            else {
368                return -1;
369            };
370            let mut tmp = parent.properties;
371            if tmp == Some(self) {
372                parent.properties = self.next;
373                if let Some(mut next) = self.next {
374                    next.prev = None;
375                }
376                xml_free_prop(self);
377                return 0;
378            }
379            while let Some(mut now) = tmp {
380                if now.next == Some(self) {
381                    now.next = self.next;
382                    if let Some(mut next) = now.next {
383                        next.prev = Some(now);
384                    }
385                    xml_free_prop(self);
386                    return 0;
387                }
388                tmp = now.next;
389            }
390            -1
391        }
392    }
393}
394
395impl Clone for XmlAttrPtr {
396    fn clone(&self) -> Self {
397        *self
398    }
399}
400
401impl Copy for XmlAttrPtr {}
402
403impl Deref for XmlAttrPtr {
404    type Target = XmlAttr;
405    fn deref(&self) -> &Self::Target {
406        // # Safety
407        // I don't implement the pointer casting and addition/subtraction methods
408        // and don't expose the inner `NonNull` for `*mut XmlAttr`.
409        // Therefore, as long as the constructor is correctly implemented,
410        // the pointer dereference is valid.
411        unsafe { self.0.as_ref() }
412    }
413}
414
415impl DerefMut for XmlAttrPtr {
416    fn deref_mut(&mut self) -> &mut Self::Target {
417        // # Safety
418        // I don't implement the pointer casting and addition/subtraction methods
419        // and don't expose the inner `NonNull` for `*mut XmlAttr`.
420        // Therefore, as long as the constructor is correctly implemented,
421        // the pointer dereference is valid.
422        unsafe { self.0.as_mut() }
423    }
424}
425
426impl TryFrom<XmlGenericNodePtr> for XmlAttrPtr {
427    type Error = InvalidNodePointerCastError;
428
429    fn try_from(value: XmlGenericNodePtr) -> Result<Self, Self::Error> {
430        match value.element_type() {
431            XmlElementType::XmlAttributeNode => Ok(Self(value.0.cast())),
432            _ => Err(InvalidNodePointerCastError {
433                from: value.element_type(),
434                to: type_name::<Self>(),
435            }),
436        }
437    }
438}
439
440impl From<XmlAttrPtr> for XmlGenericNodePtr {
441    fn from(value: XmlAttrPtr) -> Self {
442        Self(value.0 as NonNull<dyn NodeCommon>)
443    }
444}
445
446impl From<XmlAttrPtr> for *mut XmlAttr {
447    fn from(value: XmlAttrPtr) -> Self {
448        value.0.as_ptr()
449    }
450}
451
452/// Create a new property carried by a document.  
453/// Returns a pointer to the attribute
454///
455/// # NOTE
456/// `value` is supposed to be a piece of XML CDATA, so it allows entity references,
457/// but XML special chars need to be escaped first by using.  
458/// xmlEncodeEntitiesReentrant(). Use xmlNewProp() if you don't need entities support.
459#[doc(alias = "xmlNewDocProp")]
460pub fn xml_new_doc_prop(
461    doc: Option<XmlDocPtr>,
462    name: &str,
463    value: Option<&str>,
464) -> Option<XmlAttrPtr> {
465    // Allocate a new property and fill the fields.
466    let Some(mut cur) = XmlAttrPtr::new(XmlAttr {
467        typ: XmlElementType::XmlAttributeNode,
468        name: name.into(),
469        doc,
470        ..Default::default()
471    }) else {
472        xml_tree_err_memory("building attribute");
473        return None;
474    };
475    if let Some(value) = value {
476        cur.children = doc.and_then(|doc| doc.get_node_list(value));
477        cur.last = None;
478
479        let mut tmp = cur.children();
480        while let Some(mut now) = tmp {
481            now.set_parent(Some(cur.into()));
482            if now.next().is_none() {
483                cur.set_last(Some(now));
484            }
485            tmp = now.next();
486        }
487    }
488
489    if let Some(register) = get_register_node_func() {
490        register(cur.into());
491    }
492
493    Some(cur)
494}
495
496pub(super) fn xml_new_prop_internal(
497    node: Option<XmlNodePtr>,
498    ns: Option<XmlNsPtr>,
499    name: &str,
500    value: Option<&str>,
501) -> Option<XmlAttrPtr> {
502    if node.is_some_and(|node| !matches!(node.element_type(), XmlElementType::XmlElementNode)) {
503        return None;
504    }
505
506    // Allocate a new property and fill the fields.
507    let Some(mut cur) = XmlAttrPtr::new(XmlAttr {
508        typ: XmlElementType::XmlAttributeNode,
509        parent: node,
510        ns,
511        name: name.into(),
512        ..Default::default()
513    }) else {
514        xml_tree_err_memory("building attribute");
515        return None;
516    };
517
518    let mut doc = None;
519    if let Some(node) = node {
520        doc = node.doc;
521        cur.doc = doc;
522    }
523
524    if let Some(value) = value {
525        cur.set_children(xml_new_doc_text(doc, Some(value)).map(|node| node.into()));
526        cur.set_last(None);
527        let mut tmp = cur.children();
528        while let Some(mut now) = tmp {
529            now.set_parent(Some(cur.into()));
530            if now.next().is_none() {
531                cur.set_last(Some(now));
532            }
533            tmp = now.next();
534        }
535    }
536
537    // Add it at the end to preserve parsing order ...
538    if let Some(mut node) = node {
539        if let Some(mut prev) = node.properties {
540            while let Some(next) = prev.next {
541                prev = next;
542            }
543            prev.next = Some(cur);
544            cur.prev = Some(prev);
545        } else {
546            node.properties = Some(cur);
547        }
548    }
549
550    if let Some(value) = value {
551        if let Some(node) = node {
552            if xml_is_id(node.doc, Some(node), Some(cur)) == 1 {
553                xml_add_id(None, node.doc.unwrap(), value, cur);
554            }
555        }
556    }
557
558    if let Some(register) = get_register_node_func() {
559        register(cur.into());
560    }
561
562    Some(cur)
563}
564
565/// Create a new property carried by a node.  
566/// Returns a pointer to the attribute
567#[doc(alias = "xmlNewProp")]
568#[cfg(any(feature = "libxml_tree", feature = "html", feature = "schema"))]
569pub fn xml_new_prop(
570    node: Option<XmlNodePtr>,
571    name: &str,
572    value: Option<&str>,
573) -> Option<XmlAttrPtr> {
574    xml_new_prop_internal(node, None, name, value)
575}
576
577/// Create a new property tagged with a namespace and carried by a node.  
578/// Returns a pointer to the attribute
579#[doc(alias = "xmlNewNsProp")]
580pub fn xml_new_ns_prop(
581    node: Option<XmlNodePtr>,
582    ns: Option<XmlNsPtr>,
583    name: &str,
584    value: Option<&str>,
585) -> Option<XmlAttrPtr> {
586    xml_new_prop_internal(node, ns, name, value)
587}
588
589pub(super) unsafe fn xml_copy_prop_internal(
590    doc: Option<XmlDocPtr>,
591    target: Option<XmlNodePtr>,
592    cur: XmlAttrPtr,
593) -> Option<XmlAttrPtr> {
594    unsafe {
595        if target
596            .is_some_and(|target| !matches!(target.element_type(), XmlElementType::XmlElementNode))
597        {
598            return None;
599        }
600        let mut ret = if let Some(target) = target {
601            xml_new_doc_prop(target.doc, &cur.name, None)
602        } else if let Some(doc) = doc {
603            xml_new_doc_prop(Some(doc), &cur.name, None)
604        } else if let Some(parent) = cur.parent() {
605            xml_new_doc_prop(parent.document(), &cur.name, None)
606        } else if let Some(children) = cur.children() {
607            xml_new_doc_prop(children.document(), &cur.name, None)
608        } else {
609            xml_new_doc_prop(None, &cur.name, None)
610        }?;
611        ret.parent = target;
612
613        if let Some((cur_ns, mut target)) = cur.ns.zip(target) {
614            let prefix = cur_ns.prefix();
615            let target_doc = target.doc;
616            if let Some(ns) = target.search_ns(target_doc, prefix.as_deref()) {
617                // we have to find something appropriate here since
618                // we can't be sure, that the namespace we found is identified
619                // by the prefix
620                if ns.href == cur_ns.href {
621                    // this is the nice case
622                    ret.ns = Some(ns);
623                } else {
624                    // we are in trouble: we need a new reconciled namespace.
625                    // This is expensive
626                    ret.ns = xml_new_reconciled_ns(target.doc, target, cur_ns);
627                }
628            } else {
629                // Humm, we are copying an element whose namespace is defined
630                // out of the new tree scope. Search it in the original tree
631                // and add it at the top of the new tree
632                if let Some(ns) = cur.parent().unwrap().search_ns(cur.doc, prefix.as_deref()) {
633                    let mut root = XmlGenericNodePtr::from(target);
634                    let mut pred = None;
635
636                    while let Some(parent) = root.parent() {
637                        pred = Some(root);
638                        root = parent;
639                    }
640                    ret.ns = if Some(root) == target.doc.map(|doc| doc.into()) {
641                        // correct possibly cycling above the document elt
642                        xml_new_ns(
643                            pred.map(|p| XmlNodePtr::try_from(p).unwrap()),
644                            ns.href.as_deref(),
645                            ns.prefix().as_deref(),
646                        )
647                    } else {
648                        xml_new_ns(
649                            Some(XmlNodePtr::try_from(root).unwrap()),
650                            ns.href.as_deref(),
651                            ns.prefix().as_deref(),
652                        )
653                    };
654                }
655            }
656        } else {
657            ret.ns = None;
658        }
659
660        if let Some(children) = cur.children() {
661            let doc = ret.doc;
662            let parent = Some(ret.into());
663            ret.set_children(xml_static_copy_node_list(Some(children), doc, parent));
664            ret.set_last(None);
665            let mut tmp = ret.children();
666            while let Some(now) = tmp {
667                // (*tmp).parent = ret;
668                if now.next().is_none() {
669                    ret.set_last(Some(now));
670                }
671                tmp = now.next();
672            }
673        }
674        // Try to handle IDs
675        if let Some(target_doc) = target.and_then(|target| target.doc).filter(|_| {
676            cur.doc.is_some()
677                && cur
678                    .parent
679                    .filter(|&p| xml_is_id(cur.doc, Some(p), Some(cur)) != 0)
680                    .is_some()
681        }) {
682            let children = cur.children();
683            if let Some(id) = children.and_then(|c| c.get_string(cur.doc, 1)) {
684                xml_add_id(None, target_doc, &id, ret);
685            }
686        }
687        Some(ret)
688    }
689}
690
691/// Do a copy of the attribute.
692///
693/// Returns: a new #xmlAttrPtr, or null_mut() in case of error.
694#[doc(alias = "xmlCopyProp")]
695pub unsafe fn xml_copy_prop(target: Option<XmlNodePtr>, cur: XmlAttrPtr) -> Option<XmlAttrPtr> {
696    unsafe { xml_copy_prop_internal(None, target, cur) }
697}
698
699/// Do a copy of an attribute list.
700///
701/// Returns: a new #xmlAttrPtr, or null_mut() in case of error.
702#[doc(alias = "xmlCopyPropList")]
703pub unsafe fn xml_copy_prop_list(
704    target: Option<XmlNodePtr>,
705    mut cur: Option<XmlAttrPtr>,
706) -> Option<XmlAttrPtr> {
707    unsafe {
708        if target
709            .is_some_and(|target| !matches!(target.element_type(), XmlElementType::XmlElementNode))
710        {
711            return None;
712        }
713        let mut ret = None;
714        let mut p = None::<XmlAttrPtr>;
715        while let Some(now) = cur {
716            let Some(mut q) = xml_copy_prop(target, now) else {
717                xml_free_prop_list(ret);
718                return None;
719            };
720            if let Some(mut np) = p {
721                np.next = Some(q);
722                q.prev = Some(np);
723                p = Some(q);
724            } else {
725                ret = Some(q);
726                p = Some(q);
727            }
728            cur = now.next;
729        }
730        ret
731    }
732}
733
734/// Free one attribute, all the content is freed too
735#[doc(alias = "xmlFreeProp")]
736pub unsafe fn xml_free_prop(cur: XmlAttrPtr) {
737    unsafe {
738        if let Some(deregister) = get_deregister_node_func() {
739            deregister(cur.into());
740        }
741
742        // Check for ID removal -> leading to invalid references !
743        if let Some(doc) = cur
744            .doc
745            .filter(|_| matches!(cur.atype, Some(XmlAttributeType::XmlAttributeID)))
746        {
747            xml_remove_id(doc, cur);
748        }
749        if let Some(children) = cur.children() {
750            xml_free_node_list(Some(children));
751        }
752        cur.free();
753    }
754}
755
756/// Free a property and all its siblings, all the children are freed too.
757#[doc(alias = "xmlFreePropList")]
758pub unsafe fn xml_free_prop_list(cur: Option<XmlAttrPtr>) {
759    unsafe {
760        if let Some(cur) = cur {
761            let mut next = cur.next;
762            xml_free_prop(cur);
763            while let Some(now) = next {
764                next = now.next;
765                xml_free_prop(now);
766            }
767        }
768    }
769}