Skip to main content

libxml_rs/xml/dtd/
mod.rs

1//! DTD declarations handling (§24, §85 Phase 6).
2//!
3//! Internal subsets, external subsets, element declarations, attribute
4//! declarations, default attributes, notations, content models.
5//!
6//! This module provides the foundational types and functions for DTD
7//! validation, used by the validation, RELAX NG, XML Schema, and
8//! Schematron modules.
9
10use core::ffi::c_void;
11use core::ptr;
12use std::os::raw::{c_char, c_int, c_uint, c_ulong};
13
14use crate::abi::allocator;
15use crate::abi::structs::*;
16use crate::abi::types::xmlAttributeDefault::*;
17use crate::abi::types::xmlAttributeType::*;
18use crate::abi::types::xmlElementContentOccur::*;
19use crate::abi::types::xmlElementContentType::*;
20use crate::abi::types::xmlElementType::*;
21use crate::abi::types::xmlElementTypeVal::*;
22use crate::abi::types::*;
23use crate::xml::hash;
24use crate::xml::string;
25
26// ═══════════════════════════════════════════════════════════════════════════════
27// DTD Access
28// ═══════════════════════════════════════════════════════════════════════════════
29
30/// Get the internal subset of a document.
31///
32/// # UPSTREAM-PARITY
33///
34/// ```c
35/// xmlDtdPtr xmlGetIntSubset(const xmlDoc *doc);
36/// ```
37///
38/// Returns a pointer to the DTD, or NULL if none.
39pub fn get_int_subset(doc: *const _xmlDoc) -> *mut _xmlDtd {
40    if doc.is_null() {
41        return ptr::null_mut();
42    }
43    unsafe { (*doc).intSubset }
44}
45
46/// Create an internal subset (DTD) for a document.
47///
48/// # UPSTREAM-PARITY
49///
50/// ```c
51/// xmlDtdPtr xmlCreateIntSubset(xmlDocPtr doc, const xmlChar *name,
52///                              const xmlChar *ExternalID, const xmlChar *SystemID);
53/// ```
54///
55/// Creates a new DTD and attaches it as the document's internal subset.
56/// If the document already has an internal subset, it is replaced.
57///
58/// # SAFETY
59///
60/// - `doc` may be NULL (returns NULL).
61/// - `name`, `ExternalID`, `SystemID` must be valid null-terminated strings or NULL.
62pub unsafe fn create_int_subset(
63    doc: *mut _xmlDoc,
64    name: *const xmlChar,
65    ExternalID: *const xmlChar,
66    SystemID: *const xmlChar,
67) -> *mut _xmlDtd {
68    if doc.is_null() {
69        return ptr::null_mut();
70    }
71
72    // UPSTREAM-PARITY (tree.c xmlCreateIntSubset): if the document already
73    // has an internal subset, return it — never create a second DTD node.
74    if !(*doc).intSubset.is_null() {
75        return (*doc).intSubset;
76    }
77
78    // SAFETY: Allocate zero-initialized memory for the DTD.
79    let dtd = allocator::xmlMallocZero(size_of::<_xmlDtd>() as usize) as *mut _xmlDtd;
80    if dtd.is_null() {
81        return ptr::null_mut();
82    }
83
84    unsafe {
85        (*dtd).type_ = XML_DTD_NODE as c_int;
86        (*dtd).name = string::xml_strdup(name);
87        (*dtd).ExternalID = string::xml_strdup(ExternalID);
88        (*dtd).SystemID = string::xml_strdup(SystemID);
89        (*dtd).parent = doc;
90        (*dtd).doc = doc;
91
92        // UPSTREAM-PARITY (tree.c xmlCreateIntSubset): the declaration hash
93        // tables are created lazily by the xmlAdd* functions on first use;
94        // an empty DTD exposes NULL table pointers.
95
96        // Attach to document
97        (*doc).intSubset = dtd;
98
99        // UPSTREAM-PARITY (tree.c xmlCreateIntSubset): the DTD node is
100        // inserted into the document's child chain immediately before the
101        // first element node (comments/PIs in the prolog stay ahead of it).
102        let doc_children = (*doc).children;
103        if doc_children.is_null() {
104            (*doc).children = dtd as *mut _xmlNode;
105            (*doc).last = dtd as *mut _xmlNode;
106        } else {
107            let mut next = doc_children;
108            while !next.is_null() && (*next).type_ != XML_ELEMENT_NODE as c_int {
109                next = (*next).next;
110            }
111            if next.is_null() {
112                (*dtd).prev = (*doc).last;
113                (*(*doc).last).next = dtd as *mut _xmlNode;
114                (*doc).last = dtd as *mut _xmlNode;
115            } else {
116                (*dtd).next = next;
117                (*dtd).prev = (*next).prev;
118                if (*dtd).prev.is_null() {
119                    (*doc).children = dtd as *mut _xmlNode;
120                } else {
121                    (*(*dtd).prev).next = dtd as *mut _xmlNode;
122                }
123                (*next).prev = dtd as *mut _xmlNode;
124            }
125        }
126    }
127
128    dtd
129}
130
131/// Create a new DTD.
132///
133/// # UPSTREAM-PARITY
134///
135/// ```c
136/// xmlDtdPtr xmlNewDtd(xmlDocPtr doc, const xmlChar *name,
137///                     const xmlChar *ExternalID, const xmlChar *SystemID);
138/// ```
139///
140/// Creates a new DTD and if `doc` is non-NULL and has no internal subset,
141/// attaches it as the document's internal subset.
142///
143/// # SAFETY
144///
145/// - `doc` may be NULL.
146/// - `name`, `ExternalID`, `SystemID` must be valid null-terminated strings or NULL.
147pub unsafe fn new_dtd(
148    doc: *mut _xmlDoc,
149    name: *const xmlChar,
150    ExternalID: *const xmlChar,
151    SystemID: *const xmlChar,
152) -> *mut _xmlDtd {
153    // SAFETY: Allocate zero-initialized memory for the DTD.
154    let dtd = allocator::xmlMallocZero(size_of::<_xmlDtd>() as usize) as *mut _xmlDtd;
155    if dtd.is_null() {
156        return ptr::null_mut();
157    }
158
159    unsafe {
160        (*dtd).type_ = XML_DTD_NODE as c_int;
161        (*dtd).name = string::xml_strdup(name);
162        (*dtd).ExternalID = string::xml_strdup(ExternalID);
163        (*dtd).SystemID = string::xml_strdup(SystemID);
164        (*dtd).parent = doc;
165        (*dtd).doc = doc;
166
167        // UPSTREAM-PARITY (tree.c xmlNewDtd): the declaration hash tables are
168        // created lazily by the xmlAdd* functions on first use.
169
170        // Attach to document if it has no internal subset yet
171        if !doc.is_null() && (*doc).intSubset.is_null() {
172            (*doc).intSubset = dtd;
173        }
174    }
175
176    dtd
177}
178
179/// extern "C" copier shims for `hash::hash_copy`.
180unsafe extern "C" fn copy_elem_cb(payload: *mut c_void, _name: *const xmlChar) -> *mut c_void {
181    unsafe { copy_element(payload as *mut _xmlElement) as *mut c_void }
182}
183
184unsafe extern "C" fn copy_attr_cb(payload: *mut c_void, _name: *const xmlChar) -> *mut c_void {
185    unsafe { copy_attribute_decl(payload as *mut _xmlAttribute) as *mut c_void }
186}
187
188unsafe extern "C" fn copy_ent_cb(payload: *mut c_void, _name: *const xmlChar) -> *mut c_void {
189    unsafe { crate::xml::entities::copy_entity(payload as *mut _xmlEntity) as *mut c_void }
190}
191
192unsafe extern "C" fn copy_notation_cb(payload: *mut c_void, _name: *const xmlChar) -> *mut c_void {
193    unsafe { copy_notation(payload as *mut _xmlNotation) as *mut c_void }
194}
195
196/// Deep-copy a DTD: name, external identifiers, and all declaration tables.
197///
198/// UPSTREAM-PARITY: the DTD portion of `xmlCopyDoc` / `xmlCopyDtd`.
199///
200/// Returns the new DTD, or NULL on failure.
201///
202/// # SAFETY
203///
204/// - `dtd` must be a valid pointer to an _xmlDtd, or NULL.
205pub unsafe fn copy_dtd(dtd: *const _xmlDtd) -> *mut _xmlDtd {
206    if dtd.is_null() {
207        return ptr::null_mut();
208    }
209    let d = unsafe { &*dtd };
210
211    let copy = unsafe { allocator::xmlMallocZero(size_of::<_xmlDtd>() as usize) as *mut _xmlDtd };
212    if copy.is_null() {
213        return ptr::null_mut();
214    }
215
216    unsafe {
217        (*copy).type_ = d.type_;
218        (*copy).name = string::xml_strdup(d.name);
219        (*copy).ExternalID = string::xml_strdup(d.ExternalID);
220        (*copy).SystemID = string::xml_strdup(d.SystemID);
221        (*copy).notations =
222            hash::hash_copy(d.notations as *mut hash::HashTable, Some(copy_notation_cb))
223                as *mut c_void;
224        (*copy).elements =
225            hash::hash_copy(d.elements as *mut hash::HashTable, Some(copy_elem_cb)) as *mut c_void;
226        (*copy).attributes =
227            hash::hash_copy(d.attributes as *mut hash::HashTable, Some(copy_attr_cb))
228                as *mut c_void;
229        (*copy).entities =
230            hash::hash_copy(d.entities as *mut hash::HashTable, Some(copy_ent_cb)) as *mut c_void;
231        (*copy).pentities =
232            hash::hash_copy(d.pentities as *mut hash::HashTable, Some(copy_ent_cb)) as *mut c_void;
233    }
234
235    copy
236}
237
238/// Free a DTD and all its declarations.
239///
240/// # UPSTREAM-PARITY
241///
242/// ```c
243/// void xmlFreeDtd(xmlDtdPtr dtd);
244/// ```
245///
246/// # SAFETY
247///
248/// - `dtd` must be a valid pointer to an _xmlDtd, or NULL.
249pub unsafe fn free_dtd(dtd: *mut _xmlDtd) {
250    if dtd.is_null() {
251        return;
252    }
253
254    unsafe {
255        let d = &mut *dtd;
256
257        // UPSTREAM-PARITY (tree.c xmlFreeDtd): element/attribute/entity
258        // declaration nodes in the child list are owned by the hash tables
259        // and are freed by the deallocators below; only non-declaration
260        // children (comments, PIs) are unlinked and freed from the list here.
261        // This must run BEFORE the hash tables are freed so the decl nodes
262        // are still alive when their type is inspected.
263        if !d.children.is_null() {
264            let mut c = d.children;
265            while !c.is_null() {
266                let next = (*c).next;
267                let t = (*c).type_;
268                if t != XML_ELEMENT_DECL as c_int
269                    && t != XML_ATTRIBUTE_DECL as c_int
270                    && t != XML_ENTITY_DECL as c_int
271                {
272                    crate::xml::tree::free_node(c);
273                }
274                c = next;
275            }
276        }
277
278        // Free hash tables with their deallocators
279        if !d.notations.is_null() {
280            hash::hash_free(
281                d.notations as *mut hash::HashTable,
282                Some(notation_deallocator),
283            );
284            d.notations = ptr::null_mut();
285        }
286        if !d.elements.is_null() {
287            hash::hash_free(
288                d.elements as *mut hash::HashTable,
289                Some(element_deallocator),
290            );
291            d.elements = ptr::null_mut();
292        }
293        if !d.attributes.is_null() {
294            hash::hash_free(
295                d.attributes as *mut hash::HashTable,
296                Some(attribute_deallocator),
297            );
298            d.attributes = ptr::null_mut();
299        }
300        if !d.entities.is_null() {
301            hash::hash_free(d.entities as *mut hash::HashTable, Some(entity_deallocator));
302            d.entities = ptr::null_mut();
303        }
304        if !d.pentities.is_null() {
305            hash::hash_free(
306                d.pentities as *mut hash::HashTable,
307                Some(entity_deallocator),
308            );
309            d.pentities = ptr::null_mut();
310        }
311
312        // Free strings
313        if !d.name.is_null() {
314            allocator::xmlFreeImpl(d.name as *mut c_void);
315        }
316        if !d.ExternalID.is_null() {
317            allocator::xmlFreeImpl(d.ExternalID as *mut c_void);
318        }
319        if !d.SystemID.is_null() {
320            allocator::xmlFreeImpl(d.SystemID as *mut c_void);
321        }
322
323        allocator::xmlFreeImpl(dtd as *mut c_void);
324    }
325}
326
327// ═══════════════════════════════════════════════════════════════════════════════
328// Hash Deallocators
329// ═══════════════════════════════════════════════════════════════════════════════
330
331unsafe extern "C" fn notation_deallocator(payload: *mut c_void, _name: *mut u8) {
332    if !payload.is_null() {
333        free_notation(payload as *mut _xmlNotation);
334    }
335}
336
337unsafe extern "C" fn element_deallocator(payload: *mut c_void, _name: *mut u8) {
338    if !payload.is_null() {
339        free_element(payload as *mut _xmlElement);
340    }
341}
342
343unsafe extern "C" fn attribute_deallocator(payload: *mut c_void, _name: *mut u8) {
344    if !payload.is_null() {
345        free_attribute(payload as *mut _xmlAttribute);
346    }
347}
348
349unsafe extern "C" fn entity_deallocator(payload: *mut c_void, _name: *mut u8) {
350    if !payload.is_null() {
351        crate::xml::entities::free_entity(payload as *mut _xmlEntity);
352    }
353}
354
355// ═══════════════════════════════════════════════════════════════════════════════
356// Notation Declarations
357// ═══════════════════════════════════════════════════════════════════════════════
358
359/// Add a notation declaration to a DTD.
360///
361/// # UPSTREAM-PARITY
362///
363/// ```c
364/// xmlNotationPtr xmlAddNotationDecl(xmlDtdPtr dtd, const xmlChar *name,
365///                                   const xmlChar *PublicID,
366///                                   const xmlChar *SystemID);
367/// ```
368///
369/// # SAFETY
370///
371/// - `dtd` must be a valid pointer to an _xmlDtd, or NULL.
372/// - `name` must be a valid null-terminated string.
373/// - `PublicID`, `SystemID` may be NULL.
374pub unsafe fn add_notation_decl(
375    dtd: *mut _xmlDtd,
376    name: *const xmlChar,
377    PublicID: *const xmlChar,
378    SystemID: *const xmlChar,
379) -> *mut _xmlNotation {
380    if dtd.is_null() || name.is_null() {
381        return ptr::null_mut();
382    }
383
384    unsafe {
385        // UPSTREAM-PARITY (valid.c xmlAddNotationDecl): the table is
386        // created lazily on first use.
387        if (*dtd).notations.is_null() {
388            (*dtd).notations = hash::hash_create(8) as *mut c_void;
389        }
390        let d = &*dtd;
391
392        // Check if notation already exists
393        let existing = hash::hash_lookup(d.notations as *mut hash::HashTable, name);
394        if !existing.is_null() {
395            return existing as *mut _xmlNotation;
396        }
397
398        // SAFETY: Allocate zero-initialized memory for the notation.
399        let not = allocator::xmlMallocZero(size_of::<_xmlNotation>() as usize) as *mut _xmlNotation;
400        if not.is_null() {
401            return ptr::null_mut();
402        }
403
404        (*not).name = string::xml_strdup(name);
405        (*not).PublicID = string::xml_strdup(PublicID);
406        (*not).SystemID = string::xml_strdup(SystemID);
407
408        // Add to hash table
409        let ret = hash::hash_add_entry(
410            d.notations as *mut hash::HashTable,
411            name,
412            not as *mut c_void,
413        );
414        if ret != 0 {
415            // Failed to add (shouldn't happen since we checked)
416            free_notation(not);
417            return ptr::null_mut();
418        }
419
420        not
421    }
422}
423
424/// Look up a notation declaration by name.
425///
426/// # UPSTREAM-PARITY
427///
428/// ```c
429/// xmlNotationPtr xmlGetNotationDecl(xmlDtdPtr dtd, const xmlChar *name);
430/// ```
431///
432/// # SAFETY
433///
434/// - `dtd` must be a valid pointer to an _xmlDtd, or NULL.
435/// - `name` must be a valid null-terminated string.
436pub unsafe fn get_notation_decl(dtd: *mut _xmlDtd, name: *const xmlChar) -> *mut _xmlNotation {
437    if dtd.is_null() || name.is_null() {
438        return ptr::null_mut();
439    }
440
441    unsafe {
442        let d = &*dtd;
443        let payload = hash::hash_lookup(d.notations as *mut hash::HashTable, name);
444        payload as *mut _xmlNotation
445    }
446}
447
448/// Deep copy a notation declaration.
449///
450/// # UPSTREAM-PARITY
451///
452/// ```c
453/// xmlNotationPtr xmlCopyNotation(xmlNotationPtr notation);
454/// ```
455///
456/// # SAFETY
457///
458/// - `notation` must be a valid pointer to an _xmlNotation, or NULL.
459pub unsafe fn copy_notation(notation: *mut _xmlNotation) -> *mut _xmlNotation {
460    if notation.is_null() {
461        return ptr::null_mut();
462    }
463
464    unsafe {
465        let n = &*notation;
466        // SAFETY: Allocate zero-initialized memory for the copy.
467        let copy =
468            allocator::xmlMallocZero(size_of::<_xmlNotation>() as usize) as *mut _xmlNotation;
469        if copy.is_null() {
470            return ptr::null_mut();
471        }
472
473        (*copy).name = string::xml_strdup(n.name);
474        (*copy).PublicID = string::xml_strdup(n.PublicID);
475        (*copy).SystemID = string::xml_strdup(n.SystemID);
476
477        copy
478    }
479}
480
481/// Free a notation declaration.
482///
483/// # UPSTREAM-PARITY
484///
485/// ```c
486/// void xmlFreeNotation(xmlNotationPtr notation);
487/// ```
488///
489/// # SAFETY
490///
491/// - `notation` must be a valid pointer to an _xmlNotation, or NULL.
492pub unsafe fn free_notation(notation: *mut _xmlNotation) {
493    if notation.is_null() {
494        return;
495    }
496
497    unsafe {
498        let n = &*notation;
499        if !n.name.is_null() {
500            allocator::xmlFreeImpl(n.name as *mut c_void);
501        }
502        if !n.PublicID.is_null() {
503            allocator::xmlFreeImpl(n.PublicID as *mut c_void);
504        }
505        if !n.SystemID.is_null() {
506            allocator::xmlFreeImpl(n.SystemID as *mut c_void);
507        }
508        allocator::xmlFreeImpl(notation as *mut c_void);
509    }
510}
511
512// ═══════════════════════════════════════════════════════════════════════════════
513// Element Content Model Functions
514// ═══════════════════════════════════════════════════════════════════════════════
515
516/// Create a new element content model node.
517///
518/// # UPSTREAM-PARITY
519///
520/// ```c
521/// xmlElementContentPtr xmlNewElementContent(const xmlChar *name, int type);
522/// ```
523///
524/// Creates a content model node with the given name and type.
525/// The `ocur` field is set to XML_ELEMENT_CONTENT_ONCE by default.
526///
527/// # SAFETY
528///
529/// - `name` may be NULL (for PCDATA and connector types).
530pub unsafe fn create_content_model(name: *const xmlChar, type_: c_int) -> *mut _xmlElementContent {
531    // SAFETY: Allocate zero-initialized memory for the content model.
532    let content = allocator::xmlMallocZero(size_of::<_xmlElementContent>() as usize)
533        as *mut _xmlElementContent;
534    if content.is_null() {
535        return ptr::null_mut();
536    }
537
538    unsafe {
539        (*content).type_ = type_;
540        (*content).ocur = XML_ELEMENT_CONTENT_ONCE as c_int;
541        (*content).name = string::xml_strdup(name);
542        (*content).c1 = ptr::null_mut();
543        (*content).c2 = ptr::null_mut();
544        (*content).parent = ptr::null_mut();
545        (*content).prefix = ptr::null_mut();
546    }
547
548    content
549}
550
551/// Free an element content model tree.
552///
553/// # UPSTREAM-PARITY
554///
555/// ```c
556/// void xmlFreeElementContent(xmlElementContentPtr cur);
557/// ```
558///
559/// Recursively frees the entire content model tree.
560///
561/// # SAFETY
562///
563/// - `cur` must be a valid pointer to an _xmlElementContent, or NULL.
564pub unsafe fn free_content_model(cur: *mut _xmlElementContent) {
565    if cur.is_null() {
566        return;
567    }
568
569    unsafe {
570        let c = &*cur;
571
572        // Recursively free children
573        if !c.c1.is_null() {
574            free_content_model(c.c1);
575        }
576        if !c.c2.is_null() {
577            free_content_model(c.c2);
578        }
579
580        // Free name and prefix
581        if !c.name.is_null() {
582            allocator::xmlFreeImpl(c.name as *mut c_void);
583        }
584        if !c.prefix.is_null() {
585            allocator::xmlFreeImpl(c.prefix as *mut c_void);
586        }
587
588        allocator::xmlFreeImpl(cur as *mut c_void);
589    }
590}
591
592/// Deep copy an element content model tree.
593///
594/// # UPSTREAM-PARITY
595///
596/// ```c
597/// xmlElementContentPtr xmlCopyElementContent(xmlElementContentPtr content);
598/// ```
599///
600/// Recursively copies the entire content model tree.
601///
602/// # SAFETY
603///
604/// - `content` must be a valid pointer to an _xmlElementContent, or NULL.
605pub unsafe fn copy_content_model(content: *mut _xmlElementContent) -> *mut _xmlElementContent {
606    if content.is_null() {
607        return ptr::null_mut();
608    }
609
610    unsafe {
611        let c = &*content;
612
613        // SAFETY: Allocate zero-initialized memory for the copy.
614        let copy = allocator::xmlMallocZero(size_of::<_xmlElementContent>() as usize)
615            as *mut _xmlElementContent;
616        if copy.is_null() {
617            return ptr::null_mut();
618        }
619
620        (*copy).type_ = c.type_;
621        (*copy).ocur = c.ocur;
622        (*copy).name = string::xml_strdup(c.name);
623        (*copy).prefix = string::xml_strdup(c.prefix);
624
625        // Recursively copy children
626        if !c.c1.is_null() {
627            (*copy).c1 = copy_content_model(c.c1);
628            if !(*copy).c1.is_null() {
629                (*(*copy).c1).parent = copy;
630            }
631        }
632        if !c.c2.is_null() {
633            (*copy).c2 = copy_content_model(c.c2);
634            if !(*copy).c2.is_null() {
635                (*(*copy).c2).parent = copy;
636            }
637        }
638
639        copy
640    }
641}
642
643// ═══════════════════════════════════════════════════════════════════════════════
644// Element Declarations
645// ═══════════════════════════════════════════════════════════════════════════════
646
647/// Add an element declaration to a DTD.
648///
649/// # UPSTREAM-PARITY
650///
651/// ```c
652/// xmlElementPtr xmlAddElementDecl(xmlDtdPtr dtd, const xmlChar *name, int type,
653///                                 xmlElementContentPtr content);
654/// ```
655///
656/// If an element with the same name already exists, the existing one is returned
657/// and no new declaration is created.
658///
659/// # SAFETY
660///
661/// - `dtd` must be a valid pointer to an _xmlDtd, or NULL.
662/// - `name` must be a valid null-terminated string.
663pub unsafe fn add_element_decl(
664    dtd: *mut _xmlDtd,
665    name: *const xmlChar,
666    type_: c_int,
667    content: *mut _xmlElementContent,
668) -> *mut _xmlElement {
669    if dtd.is_null() || name.is_null() {
670        return ptr::null_mut();
671    }
672
673    unsafe {
674        // UPSTREAM-PARITY (valid.c xmlAddElementDecl): the table is created
675        // lazily on first use.
676        if (*dtd).elements.is_null() {
677            (*dtd).elements = hash::hash_create(8) as *mut c_void;
678        }
679        let d = &*dtd;
680
681        // Check if element already exists
682        let existing = hash::hash_lookup(d.elements as *mut hash::HashTable, name);
683        if !existing.is_null() {
684            return existing as *mut _xmlElement;
685        }
686
687        // SAFETY: Allocate zero-initialized memory for the element.
688        let elem = allocator::xmlMallocZero(size_of::<_xmlElement>() as usize) as *mut _xmlElement;
689        if elem.is_null() {
690            return ptr::null_mut();
691        }
692
693        // UPSTREAM-PARITY (valid.c xmlAddElementDecl): the node type is
694        // XML_ELEMENT_DECL; the element type (EMPTY/ANY/MIXED/ELEMENT) is
695        // stored in `etype` only. The caller passes the element type as
696        // `type_` (matching the upstream parameter list).
697        (*elem).name = string::xml_strdup(name);
698        (*elem).type_ = XML_ELEMENT_DECL as c_int;
699        (*elem).etype = type_; // xmlElementTypeVal mirrors xmlElementType here
700        (*elem).content = content; // Takes ownership of the content model
701        (*elem).attributes = ptr::null_mut();
702        (*elem).prefix = ptr::null_mut();
703        (*elem).children = ptr::null_mut();
704        (*elem).last = ptr::null_mut();
705        (*elem).parent = dtd;
706        (*elem).next = ptr::null_mut();
707        (*elem).prev = ptr::null_mut();
708        (*elem).doc = (*dtd).doc;
709        (*elem).cont_model = ptr::null_mut();
710
711        // Add to hash table
712        let ret = hash::hash_add_entry(
713            d.elements as *mut hash::HashTable,
714            name,
715            elem as *mut c_void,
716        );
717        if ret != 0 {
718            // Failed to add
719            free_element(elem);
720            return ptr::null_mut();
721        }
722
723        // UPSTREAM-PARITY (valid.c xmlAddElementDecl "Link it to the DTD"):
724        // the element decl is a child node of the DTD.
725        if (*dtd).last.is_null() {
726            (*dtd).children = elem as *mut _xmlNode;
727            (*dtd).last = elem as *mut _xmlNode;
728        } else {
729            (*(*dtd).last).next = elem as *mut _xmlNode;
730            (*elem).prev = (*dtd).last;
731            (*dtd).last = elem as *mut _xmlNode;
732        }
733
734        elem
735    }
736}
737
738/// Look up an element declaration by name.
739///
740/// # UPSTREAM-PARITY
741///
742/// ```c
743/// xmlElementPtr xmlGetElementDecl(xmlDtdPtr dtd, const xmlChar *name);
744/// ```
745///
746/// # SAFETY
747///
748/// - `dtd` must be a valid pointer to an _xmlDtd, or NULL.
749/// - `name` must be a valid null-terminated string.
750pub unsafe fn get_element_decl(dtd: *mut _xmlDtd, name: *const xmlChar) -> *mut _xmlElement {
751    if dtd.is_null() || name.is_null() {
752        return ptr::null_mut();
753    }
754
755    unsafe {
756        let d = &*dtd;
757        if d.elements.is_null() {
758            return ptr::null_mut();
759        }
760        let payload = hash::hash_lookup(d.elements as *mut hash::HashTable, name);
761        payload as *mut _xmlElement
762    }
763}
764
765/// UPSTREAM-PARITY (valid.c xmlGetDtdElementDesc2): lookup an element
766/// declaration, creating an UNDEFINED placeholder when missing. The
767/// placeholder is registered in the elements table but NOT linked into the
768/// DTD's child list (the attribute-decl path that triggers this leaves the
769/// element undeclared).
770///
771/// # SAFETY
772///
773/// - `dtd` must be a valid pointer to an _xmlDtd, or NULL.
774/// - `name` must be a valid null-terminated string.
775pub unsafe fn get_element_decl_created(
776    dtd: *mut _xmlDtd,
777    name: *const xmlChar,
778) -> *mut _xmlElement {
779    if dtd.is_null() || name.is_null() {
780        return ptr::null_mut();
781    }
782
783    unsafe {
784        if (*dtd).elements.is_null() {
785            (*dtd).elements = hash::hash_create(8) as *mut c_void;
786        }
787        let existing = hash::hash_lookup((*dtd).elements as *mut hash::HashTable, name);
788        if !existing.is_null() {
789            return existing as *mut _xmlElement;
790        }
791
792        let elem = allocator::xmlMallocZero(size_of::<_xmlElement>() as usize) as *mut _xmlElement;
793        if elem.is_null() {
794            return ptr::null_mut();
795        }
796        (*elem).type_ = XML_ELEMENT_DECL as c_int;
797        (*elem).name = string::xml_strdup(name);
798        (*elem).etype = XML_ELEMENT_TYPE_UNDEFINED as c_int;
799        (*elem).doc = (*dtd).doc;
800        (*elem).parent = dtd;
801        if hash::hash_add_entry(
802            (*dtd).elements as *mut hash::HashTable,
803            name,
804            elem as *mut c_void,
805        ) != 0
806        {
807            free_element(elem);
808            return ptr::null_mut();
809        }
810        elem
811    }
812}
813
814/// Deep copy an element declaration.
815///
816/// # UPSTREAM-PARITY
817///
818/// ```c
819/// xmlElementPtr xmlCopyElement(xmlElementPtr elem);
820/// ```
821///
822/// # SAFETY
823///
824/// - `elem` must be a valid pointer to an _xmlElement, or NULL.
825pub unsafe fn copy_element(elem: *mut _xmlElement) -> *mut _xmlElement {
826    if elem.is_null() {
827        return ptr::null_mut();
828    }
829
830    unsafe {
831        let e = &*elem;
832
833        // SAFETY: Allocate zero-initialized memory for the copy.
834        let copy = allocator::xmlMallocZero(size_of::<_xmlElement>() as usize) as *mut _xmlElement;
835        if copy.is_null() {
836            return ptr::null_mut();
837        }
838
839        (*copy).name = string::xml_strdup(e.name);
840        (*copy).type_ = e.type_;
841        (*copy).etype = e.etype;
842        (*copy).content = copy_content_model(e.content);
843        (*copy).prefix = string::xml_strdup(e.prefix);
844        (*copy)._private = e._private;
845        (*copy).parent = e.parent;
846        (*copy).doc = e.doc;
847
848        // Copy attribute declarations (linked list)
849        if !e.attributes.is_null() {
850            // UPSTREAM-PARITY: We copy the attribute linked list by
851            // iterating and copying each attribute.
852            let mut src_attr = e.attributes;
853            let mut prev_copy: *mut _xmlAttribute = ptr::null_mut();
854            let mut first_copy: *mut _xmlAttribute = ptr::null_mut();
855
856            while !src_attr.is_null() {
857                let attr_copy = copy_attribute_decl(src_attr);
858                if attr_copy.is_null() {
859                    // Free what we've copied so far
860                    let mut to_free = first_copy;
861                    while !to_free.is_null() {
862                        let next = (*to_free).nexth;
863                        free_attribute(to_free);
864                        to_free = next;
865                    }
866                    allocator::xmlFreeImpl(copy as *mut c_void);
867                    return ptr::null_mut();
868                }
869
870                if prev_copy.is_null() {
871                    first_copy = attr_copy;
872                } else {
873                    (*prev_copy).nexth = attr_copy;
874                }
875                prev_copy = attr_copy;
876                src_attr = (*src_attr).nexth;
877            }
878
879            (*copy).attributes = first_copy;
880        }
881
882        copy
883    }
884}
885
886/// Free an element declaration and its content model.
887///
888/// # UPSTREAM-PARITY
889///
890/// ```c
891/// void xmlFreeElement(xmlElementPtr elem);
892/// ```
893///
894/// Frees the element declaration and its content model, but NOT the
895/// attribute declarations (which are owned by the DTD's attribute hash).
896///
897/// # SAFETY
898///
899/// - `elem` must be a valid pointer to an _xmlElement, or NULL.
900pub unsafe fn free_element(elem: *mut _xmlElement) {
901    if elem.is_null() {
902        return;
903    }
904
905    unsafe {
906        // Free name
907        if !(*elem).name.is_null() {
908            allocator::xmlFreeImpl((*elem).name as *mut c_void);
909        }
910
911        // Free prefix
912        if !(*elem).prefix.is_null() {
913            allocator::xmlFreeImpl((*elem).prefix as *mut c_void);
914        }
915
916        // Free content model
917        if !(*elem).content.is_null() {
918            free_content_model((*elem).content);
919        }
920
921        // Free the compiled content-model NFA (xmlValidBuildContentModel)
922        // UPSTREAM-PARITY: xmlFreeElement releases contModel via xmlRegFreeRegexp.
923        if !(*elem).cont_model.is_null() {
924            crate::xml::validation::free_content_model_nfa(
925                (*elem).cont_model as *mut crate::xml::validation::ContentModelNfa,
926            );
927        }
928
929        // UPSTREAM-PARITY: The attributes linked list on the element
930        // declaration is NOT owned by the element. The DTD's attribute
931        // hash table is the sole owner. When the DTD is freed, the
932        // hash table's deallocator frees all attributes.
933        // Therefore, we do NOT free the attributes list here.
934        (*elem).attributes = ptr::null_mut();
935
936        allocator::xmlFreeImpl(elem as *mut c_void);
937    }
938}
939
940// ═══════════════════════════════════════════════════════════════════════════════
941// Attribute Declarations
942// ═══════════════════════════════════════════════════════════════════════════════
943
944/// Free an enumeration value tree.
945///
946/// # SAFETY
947///
948/// - `tree` must be a valid pointer to an _xmlEnumeration, or NULL.
949unsafe fn free_enumeration(tree: *mut _xmlEnumeration) {
950    if tree.is_null() {
951        return;
952    }
953
954    unsafe {
955        let mut cur = tree;
956        while !cur.is_null() {
957            let next = (*cur).next;
958            if !(*cur).name.is_null() {
959                allocator::xmlFreeImpl((*cur).name as *mut c_void);
960            }
961            allocator::xmlFreeImpl(cur as *mut c_void);
962            cur = next;
963        }
964    }
965}
966
967/// Deep copy an enumeration value tree.
968///
969/// # SAFETY
970///
971/// - `tree` must be a valid pointer to an _xmlEnumeration, or NULL.
972unsafe fn copy_enumeration(tree: *mut _xmlEnumeration) -> *mut _xmlEnumeration {
973    if tree.is_null() {
974        return ptr::null_mut();
975    }
976
977    unsafe {
978        let mut src = tree;
979        let mut first_copy: *mut _xmlEnumeration = ptr::null_mut();
980        let mut prev_copy: *mut _xmlEnumeration = ptr::null_mut();
981
982        while !src.is_null() {
983            let copy = allocator::xmlMallocZero(size_of::<_xmlEnumeration>() as usize)
984                as *mut _xmlEnumeration;
985            if copy.is_null() {
986                // Free what we've allocated so far
987                let mut to_free = first_copy;
988                while !to_free.is_null() {
989                    let next = (*to_free).next;
990                    if !(*to_free).name.is_null() {
991                        allocator::xmlFreeImpl((*to_free).name as *mut c_void);
992                    }
993                    allocator::xmlFreeImpl(to_free as *mut c_void);
994                    to_free = next;
995                }
996                return ptr::null_mut();
997            }
998
999            (*copy).name = string::xml_strdup((*src).name);
1000            (*copy).next = ptr::null_mut();
1001
1002            if prev_copy.is_null() {
1003                first_copy = copy;
1004            } else {
1005                (*prev_copy).next = copy;
1006            }
1007            prev_copy = copy;
1008            src = (*src).next;
1009        }
1010
1011        first_copy
1012    }
1013}
1014
1015/// Add an attribute declaration to a DTD.
1016///
1017/// # UPSTREAM-PARITY
1018///
1019/// ```c
1020/// xmlAttributePtr xmlAddAttributeDecl(xmlDtdPtr dtd, xmlElementPtr elem,
1021///                                     const xmlChar *name, int type, int def,
1022///                                     const xmlChar *defaultValue,
1023///                                     xmlEnumerationPtr tree);
1024/// ```
1025///
1026/// Adds an attribute declaration to both the DTD's attribute hash table
1027/// (keyed by element name + attribute name) and the element's linked list.
1028/// If an attribute with the same name already exists for this element,
1029/// the existing declaration is returned.
1030///
1031/// # SAFETY
1032///
1033/// - `dtd` must be a valid pointer to an _xmlDtd, or NULL.
1034/// - `name` must be a valid null-terminated string.
1035/// - `elem`, `defaultValue`, `tree` may be NULL.
1036pub unsafe fn add_attribute_decl(
1037    dtd: *mut _xmlDtd,
1038    elem: *mut _xmlElement,
1039    name: *const xmlChar,
1040    type_: c_int,
1041    def: c_int,
1042    defaultValue: *const xmlChar,
1043    tree: *mut _xmlEnumeration,
1044) -> *mut _xmlAttribute {
1045    if dtd.is_null() || name.is_null() {
1046        return ptr::null_mut();
1047    }
1048
1049    unsafe {
1050        // UPSTREAM-PARITY (valid.c xmlAddAttributeDecl): the table is
1051        // created lazily on first use.
1052        if (*dtd).attributes.is_null() {
1053            (*dtd).attributes = hash::hash_create(8) as *mut c_void;
1054        }
1055        let d = &*dtd;
1056        let elem_name = if elem.is_null() {
1057            ptr::null()
1058        } else {
1059            (*elem).name
1060        };
1061
1062        // Check if attribute already exists for this element
1063        // UPSTREAM-PARITY (valid.c xmlAddAttributeDecl): the attribute table
1064        // is keyed by (name, prefix, elem).
1065        let existing = hash::hash_lookup3(
1066            d.attributes as *mut hash::HashTable,
1067            name,
1068            ptr::null(),
1069            elem_name,
1070        );
1071        if !existing.is_null() {
1072            return existing as *mut _xmlAttribute;
1073        }
1074
1075        // SAFETY: Allocate zero-initialized memory for the attribute.
1076        let attr =
1077            allocator::xmlMallocZero(size_of::<_xmlAttribute>() as usize) as *mut _xmlAttribute;
1078        if attr.is_null() {
1079            return ptr::null_mut();
1080        }
1081
1082        (*attr).type_ = XML_ATTRIBUTE_DECL as c_int;
1083        (*attr).name = string::xml_strdup(name);
1084        (*attr).parent = dtd;
1085        (*attr).doc = d.doc;
1086        (*attr).nexth = ptr::null_mut();
1087        (*attr).atype = type_;
1088        (*attr).def = def;
1089        (*attr).defaultValue = string::xml_strdup(defaultValue);
1090        (*attr).tree = tree; // Takes ownership of the enumeration tree
1091        (*attr).prefix = ptr::null_mut();
1092        (*attr).elem = string::xml_strdup(elem_name);
1093
1094        // Add to DTD's attribute hash table (keyed by attribute name,
1095        // prefix, element name — upstream xmlHashAdd3).
1096        let ret = hash::hash_add_entry3(
1097            d.attributes as *mut hash::HashTable,
1098            name,
1099            ptr::null(),
1100            elem_name,
1101            attr as *mut c_void,
1102        );
1103        if ret != 0 {
1104            // Failed to add
1105            if !(*attr).defaultValue.is_null() {
1106                allocator::xmlFreeImpl((*attr).defaultValue as *mut c_void);
1107            }
1108            if !(*attr).name.is_null() {
1109                allocator::xmlFreeImpl((*attr).name as *mut c_void);
1110            }
1111            if !(*attr).elem.is_null() {
1112                allocator::xmlFreeImpl((*attr).elem as *mut c_void);
1113            }
1114            allocator::xmlFreeImpl(attr as *mut c_void);
1115            // Don't free tree - caller still owns it on failure
1116            return ptr::null_mut();
1117        }
1118
1119        // Add to element's linked list
1120        if !elem.is_null() {
1121            (*attr).nexth = (*elem).attributes;
1122            (*elem).attributes = attr;
1123        }
1124
1125        // UPSTREAM-PARITY (valid.c xmlAddAttributeDecl "Link it to the
1126        // DTD"): the attribute decl is a child node of the DTD.
1127        if (*dtd).last.is_null() {
1128            (*dtd).children = attr as *mut _xmlNode;
1129            (*dtd).last = attr as *mut _xmlNode;
1130        } else {
1131            (*(*dtd).last).next = attr as *mut _xmlNode;
1132            (*attr).prev = (*dtd).last;
1133            (*dtd).last = attr as *mut _xmlNode;
1134        }
1135
1136        attr
1137    }
1138}
1139
1140/// Look up an attribute declaration by element name and attribute name.
1141///
1142/// # UPSTREAM-PARITY
1143///
1144/// ```c
1145/// xmlAttributePtr xmlGetAttributeDecl(xmlDtdPtr dtd, xmlElementPtr elem,
1146///                                     const xmlChar *name, int namePrefix);
1147/// ```
1148///
1149/// The `namePrefix` parameter is ignored in this implementation
1150/// (it's a legacy parameter in libxml2).
1151///
1152/// # SAFETY
1153///
1154/// - `dtd` must be a valid pointer to an _xmlDtd, or NULL.
1155/// - `name` must be a valid null-terminated string.
1156/// - `elem` may be NULL.
1157pub unsafe fn get_attribute_decl(
1158    dtd: *mut _xmlDtd,
1159    elem: *mut _xmlElement,
1160    name: *const xmlChar,
1161    _namePrefix: c_int,
1162) -> *mut _xmlAttribute {
1163    if dtd.is_null() || name.is_null() {
1164        return ptr::null_mut();
1165    }
1166
1167    unsafe {
1168        let d = &*dtd;
1169        let elem_name = if elem.is_null() {
1170            ptr::null()
1171        } else {
1172            (*elem).name
1173        };
1174
1175        // UPSTREAM-PARITY (valid.c xmlGetDtdQAttrDesc): keyed by
1176        // (name, prefix, elem).
1177        let payload = hash::hash_lookup3(
1178            d.attributes as *mut hash::HashTable,
1179            name,
1180            ptr::null(),
1181            elem_name,
1182        );
1183        payload as *mut _xmlAttribute
1184    }
1185}
1186
1187/// Deep copy an attribute declaration.
1188///
1189/// # UPSTREAM-PARITY
1190///
1191/// ```c
1192/// xmlAttributePtr xmlCopyAttribute(xmlAttributePtr attr);
1193/// ```
1194///
1195/// # SAFETY
1196///
1197/// - `attr` must be a valid pointer to an _xmlAttribute, or NULL.
1198pub unsafe fn copy_attribute_decl(attr: *mut _xmlAttribute) -> *mut _xmlAttribute {
1199    if attr.is_null() {
1200        return ptr::null_mut();
1201    }
1202
1203    unsafe {
1204        let a = &*attr;
1205
1206        // SAFETY: Allocate zero-initialized memory for the copy.
1207        let copy =
1208            allocator::xmlMallocZero(size_of::<_xmlAttribute>() as usize) as *mut _xmlAttribute;
1209        if copy.is_null() {
1210            return ptr::null_mut();
1211        }
1212
1213        (*copy).type_ = a.type_;
1214        (*copy).name = string::xml_strdup(a.name);
1215        (*copy).parent = a.parent;
1216        (*copy).doc = a.doc;
1217        (*copy).nexth = ptr::null_mut();
1218        (*copy).atype = a.atype;
1219        (*copy).def = a.def;
1220        (*copy).defaultValue = string::xml_strdup(a.defaultValue);
1221        (*copy).tree = copy_enumeration(a.tree);
1222        (*copy).prefix = string::xml_strdup(a.prefix);
1223        (*copy).elem = string::xml_strdup(a.elem);
1224
1225        copy
1226    }
1227}
1228
1229/// Free an attribute declaration.
1230///
1231/// # UPSTREAM-PARITY
1232///
1233/// ```c
1234/// void xmlFreeAttribute(xmlAttributePtr attr);
1235/// ```
1236///
1237/// # SAFETY
1238///
1239/// - `attr` must be a valid pointer to an _xmlAttribute, or NULL.
1240pub unsafe fn free_attribute(attr: *mut _xmlAttribute) {
1241    if attr.is_null() {
1242        return;
1243    }
1244
1245    unsafe {
1246        let a = &*attr;
1247
1248        if !a.name.is_null() {
1249            allocator::xmlFreeImpl(a.name as *mut c_void);
1250        }
1251        if !a.defaultValue.is_null() {
1252            allocator::xmlFreeImpl(a.defaultValue as *mut c_void);
1253        }
1254        if !a.prefix.is_null() {
1255            allocator::xmlFreeImpl(a.prefix as *mut c_void);
1256        }
1257        if !a.elem.is_null() {
1258            allocator::xmlFreeImpl(a.elem as *mut c_void);
1259        }
1260        if !a.tree.is_null() {
1261            free_enumeration(a.tree);
1262        }
1263
1264        allocator::xmlFreeImpl(attr as *mut c_void);
1265    }
1266}
1267
1268// ═══════════════════════════════════════════════════════════════════════════════
1269// Content Model Validation (Automata-based)
1270// ═══════════════════════════════════════════════════════════════════════════════
1271
1272/// Result of content model validation.
1273#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1274pub enum ContentModelResult {
1275    /// Content is valid.
1276    Valid,
1277    /// Content is invalid.
1278    Invalid,
1279    /// Content model is indeterminate (mixed content with PCDATA).
1280    Indeterminate,
1281}
1282
1283/// Validate content (a list of element names) against a content model,
1284/// taking occurrence indicators into account.
1285///
1286/// # UPSTREAM-PARITY
1287///
1288/// ```c
1289/// int xmlValidContentModel(xmlElementContentPtr model, ...)
1290/// ```
1291///
1292/// This implements a simple recursive descent validator for content models.
1293/// For simple content models (EMPTY, ANY, PCDATA), the check is direct.
1294/// For sequence/choice models, it recursively validates.
1295///
1296/// Returns `ContentModelResult::Valid` if the content matches the model,
1297/// `ContentModelResult::Invalid` otherwise.
1298///
1299/// # SAFETY
1300///
1301/// - `model` must be a valid pointer to an _xmlElementContent, or NULL.
1302/// - `names` must be a slice of element names (null-terminated xmlChar strings).
1303pub unsafe fn valid_content_model(
1304    model: *mut _xmlElementContent,
1305    names: &[*const xmlChar],
1306) -> ContentModelResult {
1307    if model.is_null() {
1308        return ContentModelResult::Invalid;
1309    }
1310
1311    unsafe {
1312        let m = &*model;
1313
1314        // Handle occurrence indicators at this level first
1315        match m.ocur as u32 {
1316            o if o == XML_ELEMENT_CONTENT_OPT as u32 => {
1317                // Optional: zero or one occurrence
1318                if names.is_empty() {
1319                    return ContentModelResult::Valid;
1320                }
1321                return valid_content_model_inner(model, names);
1322            }
1323            o if o == XML_ELEMENT_CONTENT_MULT as u32 => {
1324                // Zero or more
1325                if names.is_empty() {
1326                    return ContentModelResult::Valid;
1327                }
1328                return valid_content_model_zero_or_more(model, names);
1329            }
1330            o if o == XML_ELEMENT_CONTENT_PLUS as u32 => {
1331                // One or more
1332                if names.is_empty() {
1333                    return ContentModelResult::Invalid;
1334                }
1335                return valid_content_model_one_or_more(model, names);
1336            }
1337            _ => {}
1338        }
1339
1340        valid_content_model_inner(model, names)
1341    }
1342}
1343
1344/// Validate content against a content model without considering occurrence.
1345unsafe fn valid_content_model_inner(
1346    model: *mut _xmlElementContent,
1347    names: &[*const xmlChar],
1348) -> ContentModelResult {
1349    unsafe {
1350        let m = &*model;
1351
1352        match m.type_ as u32 {
1353            t if t == XML_ELEMENT_CONTENT_PCDATA as u32 => {
1354                // PCDATA: content must be empty (just text)
1355                if names.is_empty() {
1356                    ContentModelResult::Valid
1357                } else {
1358                    ContentModelResult::Invalid
1359                }
1360            }
1361            t if t == XML_ELEMENT_CONTENT_ELEMENT as u32 => {
1362                // Single element: must match exactly one element
1363                if names.len() != 1 {
1364                    return ContentModelResult::Invalid;
1365                }
1366                if names[0].is_null() {
1367                    return ContentModelResult::Invalid;
1368                }
1369                // Compare with model name
1370                if string::xml_strcmp(names[0], m.name) != 0 {
1371                    return ContentModelResult::Invalid;
1372                }
1373                ContentModelResult::Valid
1374            }
1375            t if t == XML_ELEMENT_CONTENT_SEQ as u32 => {
1376                // Sequence: validate children in order
1377                valid_content_model_seq(m, names)
1378            }
1379            t if t == XML_ELEMENT_CONTENT_OR as u32 => {
1380                // Choice: one of the alternatives must match all names
1381                valid_content_model_or(m, names)
1382            }
1383            _ => ContentModelResult::Invalid,
1384        }
1385    }
1386}
1387
1388/// Validate content for zero-or-more occurrence.
1389unsafe fn valid_content_model_zero_or_more(
1390    model: *mut _xmlElementContent,
1391    names: &[*const xmlChar],
1392) -> ContentModelResult {
1393    // Zero or more: try each possible split
1394    let mut i = 0;
1395    while i <= names.len() {
1396        let consumed = &names[..i];
1397        let remaining = &names[i..];
1398
1399        let consumed_valid = unsafe { valid_content_model_inner(model, consumed) };
1400        if consumed_valid == ContentModelResult::Valid {
1401            if remaining.is_empty() {
1402                return ContentModelResult::Valid;
1403            }
1404            // Try to match remaining with same model
1405            let remaining_valid = unsafe { valid_content_model_zero_or_more(model, remaining) };
1406            if remaining_valid == ContentModelResult::Valid {
1407                return ContentModelResult::Valid;
1408            }
1409        }
1410
1411        i += 1;
1412    }
1413    ContentModelResult::Invalid
1414}
1415
1416/// Validate content for one-or-more occurrence.
1417unsafe fn valid_content_model_one_or_more(
1418    model: *mut _xmlElementContent,
1419    names: &[*const xmlChar],
1420) -> ContentModelResult {
1421    // One or more: must match at least once
1422    let mut i = 1;
1423    while i <= names.len() {
1424        let consumed = &names[..i];
1425        let remaining = &names[i..];
1426
1427        let consumed_valid = unsafe { valid_content_model_inner(model, consumed) };
1428        if consumed_valid == ContentModelResult::Valid {
1429            if remaining.is_empty() {
1430                return ContentModelResult::Valid;
1431            }
1432            let remaining_valid = unsafe { valid_content_model_zero_or_more(model, remaining) };
1433            if remaining_valid == ContentModelResult::Valid {
1434                return ContentModelResult::Valid;
1435            }
1436        }
1437
1438        i += 1;
1439    }
1440    ContentModelResult::Invalid
1441}
1442
1443/// Validate content against a sequence content model.
1444unsafe fn valid_content_model_seq(
1445    model: &_xmlElementContent,
1446    names: &[*const xmlChar],
1447) -> ContentModelResult {
1448    // For a sequence, we need to split the names between c1 and c2
1449    // This is a simplified validation - full automata-based validation
1450    // would be more complex.
1451
1452    let c1 = model.c1;
1453    let c2 = model.c2;
1454
1455    if c1.is_null() && c2.is_null() {
1456        return ContentModelResult::Valid;
1457    }
1458
1459    if c1.is_null() {
1460        return unsafe { valid_content_model(c2, names) };
1461    }
1462
1463    if c2.is_null() {
1464        return unsafe { valid_content_model(c1, names) };
1465    }
1466
1467    // Try to split the names at each possible position
1468    // This implements a simple backtracking validator
1469    for split in 0..=names.len() {
1470        let left = &names[..split];
1471        let right = &names[split..];
1472
1473        let left_valid = unsafe { valid_content_model(c1, left) };
1474        if left_valid != ContentModelResult::Valid {
1475            continue;
1476        }
1477
1478        let right_valid = unsafe { valid_content_model(c2, right) };
1479        if right_valid == ContentModelResult::Valid {
1480            return ContentModelResult::Valid;
1481        }
1482    }
1483
1484    ContentModelResult::Invalid
1485}
1486
1487/// Validate content against a choice content model.
1488unsafe fn valid_content_model_or(
1489    model: &_xmlElementContent,
1490    names: &[*const xmlChar],
1491) -> ContentModelResult {
1492    let c1 = model.c1;
1493    let c2 = model.c2;
1494
1495    if c1.is_null() && c2.is_null() {
1496        return ContentModelResult::Invalid;
1497    }
1498
1499    if !c1.is_null() {
1500        let result = unsafe { valid_content_model(c1, names) };
1501        if result == ContentModelResult::Valid {
1502            return ContentModelResult::Valid;
1503        }
1504    }
1505
1506    if !c2.is_null() {
1507        let result = unsafe { valid_content_model(c2, names) };
1508        if result == ContentModelResult::Valid {
1509            return ContentModelResult::Valid;
1510        }
1511    }
1512
1513    ContentModelResult::Invalid
1514}
1515
1516// ═══════════════════════════════════════════════════════════════════════════════
1517// Tests
1518// ═══════════════════════════════════════════════════════════════════════════════
1519
1520#[cfg(test)]
1521mod tests {
1522    use super::*;
1523    use crate::abi::allocator::xmlFreeImpl;
1524    use crate::abi::structs::*;
1525    use core::ffi::c_void;
1526    use core::ptr;
1527
1528    // ── Helpers ──────────────────────────────────────────────────────────
1529
1530    unsafe fn c_str(s: &[u8]) -> *const xmlChar {
1531        // Create a null-terminated xmlChar string
1532        let len = s.len();
1533        let buf = allocator::xmlMallocImpl(len + 1) as *mut xmlChar;
1534        assert!(!buf.is_null());
1535        ptr::copy_nonoverlapping(s.as_ptr(), buf, len);
1536        *buf.add(len) = 0;
1537        buf as *const xmlChar
1538    }
1539
1540    unsafe fn make_doc_and_dtd() -> (*mut _xmlDoc, *mut _xmlDtd) {
1541        let doc = allocator::xmlMallocZero(size_of::<_xmlDoc>() as usize) as *mut _xmlDoc;
1542        assert!(!doc.is_null());
1543        (*doc).type_ = XML_DOCUMENT_NODE as c_int;
1544        (*doc).doc = doc;
1545        let dtd = create_int_subset(doc, c_str(b"root"), ptr::null(), ptr::null());
1546        assert!(!dtd.is_null());
1547        (doc, dtd)
1548    }
1549
1550    // ── DTD Access Tests ────────────────────────────────────────────────
1551
1552    #[test]
1553    fn test_get_int_subset_null() {
1554        unsafe {
1555            assert!(get_int_subset(ptr::null()).is_null());
1556        }
1557    }
1558
1559    #[test]
1560    fn test_create_int_subset() {
1561        unsafe {
1562            let (doc, dtd) = make_doc_and_dtd();
1563            assert_eq!((*dtd).type_, XML_DTD_NODE as c_int);
1564            assert!(!(*dtd).name.is_null());
1565            assert_eq!((*doc).intSubset, dtd);
1566
1567            // Cleanup
1568            free_dtd(dtd);
1569            allocator::xmlFreeImpl(doc as *mut c_void);
1570        }
1571    }
1572
1573    #[test]
1574    fn test_create_int_subset_null_doc() {
1575        unsafe {
1576            let dtd = create_int_subset(ptr::null_mut(), c_str(b"root"), ptr::null(), ptr::null());
1577            assert!(dtd.is_null());
1578        }
1579    }
1580
1581    #[test]
1582    fn test_new_dtd() {
1583        unsafe {
1584            let doc = allocator::xmlMallocZero(size_of::<_xmlDoc>() as usize) as *mut _xmlDoc;
1585            assert!(!doc.is_null());
1586            (*doc).type_ = XML_DOCUMENT_NODE as c_int;
1587            (*doc).doc = doc;
1588
1589            let dtd = new_dtd(doc, c_str(b"test"), c_str(b"-//TEST//"), c_str(b"test.dtd"));
1590            assert!(!dtd.is_null());
1591            assert_eq!((*dtd).type_, XML_DTD_NODE as c_int);
1592            assert_eq!((*doc).intSubset, dtd);
1593
1594            free_dtd(dtd);
1595            allocator::xmlFreeImpl(doc as *mut c_void);
1596        }
1597    }
1598
1599    #[test]
1600    fn test_new_dtd_no_doc() {
1601        unsafe {
1602            let dtd = new_dtd(ptr::null_mut(), c_str(b"test"), ptr::null(), ptr::null());
1603            assert!(!dtd.is_null());
1604            free_dtd(dtd);
1605        }
1606    }
1607
1608    // ── Notation Tests ──────────────────────────────────────────────────
1609
1610    #[test]
1611    fn test_add_get_notation() {
1612        unsafe {
1613            let (doc, dtd) = make_doc_and_dtd();
1614            let name = c_str(b"note");
1615            let pubid = c_str(b"-//TEST//NOTATION");
1616            let sysid = c_str(b"note.ent");
1617
1618            let n = add_notation_decl(dtd, name, pubid, sysid);
1619            assert!(!n.is_null());
1620            assert_eq!(string::xml_strcmp((*n).name, name), 0);
1621
1622            // Lookup
1623            let found = get_notation_decl(dtd, name);
1624            assert_eq!(found, n);
1625
1626            // Lookup non-existent
1627            let not_found = get_notation_decl(dtd, c_str(b"nonexistent"));
1628            assert!(not_found.is_null());
1629
1630            free_dtd(dtd);
1631            allocator::xmlFreeImpl(doc as *mut c_void);
1632        }
1633    }
1634
1635    #[test]
1636    fn test_add_notation_null_dtd() {
1637        unsafe {
1638            let n = add_notation_decl(ptr::null_mut(), c_str(b"test"), ptr::null(), ptr::null());
1639            assert!(n.is_null());
1640        }
1641    }
1642
1643    #[test]
1644    fn test_copy_notation() {
1645        unsafe {
1646            let (doc, dtd) = make_doc_and_dtd();
1647            let name = c_str(b"note1");
1648            let pubid = c_str(b"public");
1649            let sysid = c_str(b"system");
1650
1651            let n = add_notation_decl(dtd, name, pubid, sysid);
1652            assert!(!n.is_null());
1653
1654            let copy = copy_notation(n);
1655            assert!(!copy.is_null());
1656            assert_ne!(copy, n);
1657            assert_eq!(string::xml_strcmp((*copy).name, name), 0);
1658            assert_eq!(string::xml_strcmp((*copy).PublicID, pubid), 0);
1659            assert_eq!(string::xml_strcmp((*copy).SystemID, sysid), 0);
1660
1661            free_notation(copy);
1662            free_dtd(dtd);
1663            allocator::xmlFreeImpl(doc as *mut c_void);
1664        }
1665    }
1666
1667    #[test]
1668    fn test_copy_notation_null() {
1669        unsafe {
1670            assert!(copy_notation(ptr::null_mut()).is_null());
1671        }
1672    }
1673
1674    #[test]
1675    fn test_free_notation_null() {
1676        unsafe {
1677            free_notation(ptr::null_mut()); // Should not crash
1678        }
1679    }
1680
1681    // ── Content Model Tests ─────────────────────────────────────────────
1682
1683    #[test]
1684    fn test_create_free_content_model() {
1685        unsafe {
1686            let cm = create_content_model(c_str(b"child"), XML_ELEMENT_CONTENT_ELEMENT as c_int);
1687            assert!(!cm.is_null());
1688            assert_eq!((*cm).type_, XML_ELEMENT_CONTENT_ELEMENT as c_int);
1689            assert_eq!((*cm).ocur, XML_ELEMENT_CONTENT_ONCE as c_int);
1690
1691            free_content_model(cm);
1692        }
1693    }
1694
1695    #[test]
1696    fn test_create_content_model_pcdata() {
1697        unsafe {
1698            let cm = create_content_model(ptr::null(), XML_ELEMENT_CONTENT_PCDATA as c_int);
1699            assert!(!cm.is_null());
1700            assert_eq!((*cm).type_, XML_ELEMENT_CONTENT_PCDATA as c_int);
1701            free_content_model(cm);
1702        }
1703    }
1704
1705    #[test]
1706    fn test_copy_content_model() {
1707        unsafe {
1708            let cm = create_content_model(c_str(b"child"), XML_ELEMENT_CONTENT_ELEMENT as c_int);
1709            assert!(!cm.is_null());
1710
1711            let copy = copy_content_model(cm);
1712            assert!(!copy.is_null());
1713            assert_ne!(copy, cm);
1714            assert_eq!((*copy).type_, XML_ELEMENT_CONTENT_ELEMENT as c_int);
1715            assert_eq!((*copy).ocur, XML_ELEMENT_CONTENT_ONCE as c_int);
1716            assert_eq!(string::xml_strcmp((*copy).name, (*cm).name), 0);
1717
1718            free_content_model(cm);
1719            free_content_model(copy);
1720        }
1721    }
1722
1723    #[test]
1724    fn test_copy_content_model_null() {
1725        unsafe {
1726            assert!(copy_content_model(ptr::null_mut()).is_null());
1727        }
1728    }
1729
1730    #[test]
1731    fn test_free_content_model_null() {
1732        unsafe {
1733            free_content_model(ptr::null_mut()); // Should not crash
1734        }
1735    }
1736
1737    #[test]
1738    fn test_create_sequence_content_model() {
1739        unsafe {
1740            let c1 = create_content_model(c_str(b"a"), XML_ELEMENT_CONTENT_ELEMENT as c_int);
1741            let c2 = create_content_model(c_str(b"b"), XML_ELEMENT_CONTENT_ELEMENT as c_int);
1742            let seq = create_content_model(ptr::null(), XML_ELEMENT_CONTENT_SEQ as c_int);
1743            assert!(!seq.is_null());
1744            (*seq).c1 = c1;
1745            (*seq).c2 = c2;
1746            (*c1).parent = seq;
1747            (*c2).parent = seq;
1748
1749            free_content_model(seq);
1750        }
1751    }
1752
1753    // ── Element Declaration Tests ───────────────────────────────────────
1754
1755    #[test]
1756    fn test_add_get_element() {
1757        unsafe {
1758            let (doc, dtd) = make_doc_and_dtd();
1759            let name = c_str(b"myElement");
1760
1761            let elem =
1762                add_element_decl(dtd, name, XML_ELEMENT_TYPE_EMPTY as c_int, ptr::null_mut());
1763            assert!(!elem.is_null());
1764            assert_eq!((*elem).etype, XML_ELEMENT_TYPE_EMPTY as c_int);
1765
1766            let found = get_element_decl(dtd, name);
1767            assert_eq!(found, elem);
1768
1769            let not_found = get_element_decl(dtd, c_str(b"nonexistent"));
1770            assert!(not_found.is_null());
1771
1772            free_dtd(dtd);
1773            allocator::xmlFreeImpl(doc as *mut c_void);
1774        }
1775    }
1776
1777    #[test]
1778    fn test_add_element_duplicate() {
1779        unsafe {
1780            let (doc, dtd) = make_doc_and_dtd();
1781            let name = c_str(b"dup");
1782
1783            let e1 = add_element_decl(dtd, name, XML_ELEMENT_TYPE_EMPTY as c_int, ptr::null_mut());
1784            assert!(!e1.is_null());
1785
1786            let e2 = add_element_decl(dtd, name, XML_ELEMENT_TYPE_ANY as c_int, ptr::null_mut());
1787            assert_eq!(e1, e2); // Same pointer returned
1788            assert_eq!((*e2).type_, XML_ELEMENT_DECL as c_int); // node type
1789            assert_eq!((*e2).etype, XML_ELEMENT_TYPE_EMPTY as c_int); // Still empty
1790
1791            free_dtd(dtd);
1792            allocator::xmlFreeImpl(doc as *mut c_void);
1793        }
1794    }
1795
1796    #[test]
1797    fn test_add_element_null_dtd() {
1798        unsafe {
1799            let elem = add_element_decl(
1800                ptr::null_mut(),
1801                c_str(b"test"),
1802                XML_ELEMENT_TYPE_EMPTY as c_int,
1803                ptr::null_mut(),
1804            );
1805            assert!(elem.is_null());
1806        }
1807    }
1808
1809    #[test]
1810    fn test_copy_element() {
1811        unsafe {
1812            let (doc, dtd) = make_doc_and_dtd();
1813            let name = c_str(b"source");
1814            let cm = create_content_model(c_str(b"child"), XML_ELEMENT_CONTENT_ELEMENT as c_int);
1815
1816            let elem = add_element_decl(dtd, name, XML_ELEMENT_TYPE_ELEMENT as c_int, cm);
1817            assert!(!elem.is_null());
1818
1819            let copy = copy_element(elem);
1820            assert!(!copy.is_null());
1821            assert_ne!(copy, elem);
1822            assert_eq!((*copy).type_, XML_ELEMENT_DECL as c_int);
1823            assert_eq!((*copy).etype, XML_ELEMENT_TYPE_ELEMENT as c_int);
1824            assert_eq!(string::xml_strcmp((*copy).name, name), 0);
1825            assert!(!(*copy).content.is_null());
1826            assert_ne!((*copy).content, cm);
1827
1828            free_element(copy);
1829            free_dtd(dtd);
1830            allocator::xmlFreeImpl(doc as *mut c_void);
1831        }
1832    }
1833
1834    #[test]
1835    fn test_free_element_null() {
1836        unsafe {
1837            free_element(ptr::null_mut()); // Should not crash
1838        }
1839    }
1840
1841    // ── Attribute Declaration Tests ─────────────────────────────────────
1842
1843    #[test]
1844    fn test_add_get_attribute() {
1845        unsafe {
1846            let (doc, dtd) = make_doc_and_dtd();
1847            let elem_name = c_str(b"elem");
1848            let attr_name = c_str(b"attr1");
1849
1850            let elem = add_element_decl(
1851                dtd,
1852                elem_name,
1853                XML_ELEMENT_TYPE_EMPTY as c_int,
1854                ptr::null_mut(),
1855            );
1856            assert!(!elem.is_null());
1857
1858            let attr = add_attribute_decl(
1859                dtd,
1860                elem,
1861                attr_name,
1862                XML_ATTRIBUTE_CDATA as c_int,
1863                XML_ATTRIBUTE_IMPLIED as c_int,
1864                ptr::null(),
1865                ptr::null_mut(),
1866            );
1867            assert!(!attr.is_null());
1868            assert_eq!((*attr).atype, XML_ATTRIBUTE_CDATA as c_int);
1869            assert_eq!((*attr).def, XML_ATTRIBUTE_IMPLIED as c_int);
1870
1871            // Lookup by element + attribute name
1872            let found = get_attribute_decl(dtd, elem, attr_name, 0);
1873            assert_eq!(found, attr);
1874
1875            // Lookup non-existent
1876            let not_found = get_attribute_decl(dtd, elem, c_str(b"nonexistent"), 0);
1877            assert!(not_found.is_null());
1878
1879            free_dtd(dtd);
1880            allocator::xmlFreeImpl(doc as *mut c_void);
1881        }
1882    }
1883
1884    #[test]
1885    fn test_add_attribute_with_default() {
1886        unsafe {
1887            let (doc, dtd) = make_doc_and_dtd();
1888            let elem_name = c_str(b"elem");
1889            let attr_name = c_str(b"color");
1890            let default_val = c_str(b"red");
1891
1892            let elem = add_element_decl(
1893                dtd,
1894                elem_name,
1895                XML_ELEMENT_TYPE_EMPTY as c_int,
1896                ptr::null_mut(),
1897            );
1898
1899            let attr = add_attribute_decl(
1900                dtd,
1901                elem,
1902                attr_name,
1903                XML_ATTRIBUTE_CDATA as c_int,
1904                XML_ATTRIBUTE_FIXED as c_int,
1905                default_val,
1906                ptr::null_mut(),
1907            );
1908            assert!(!attr.is_null());
1909            assert_eq!((*attr).def, XML_ATTRIBUTE_FIXED as c_int);
1910            assert_eq!(string::xml_strcmp((*attr).defaultValue, default_val), 0);
1911
1912            free_dtd(dtd);
1913            allocator::xmlFreeImpl(doc as *mut c_void);
1914        }
1915    }
1916
1917    #[test]
1918    fn test_add_attribute_enumeration() {
1919        unsafe {
1920            let (doc, dtd) = make_doc_and_dtd();
1921            let elem_name = c_str(b"elem");
1922            let attr_name = c_str(b"size");
1923
1924            // Build enumeration: small, medium, large
1925            let v3 = allocator::xmlMallocZero(size_of::<_xmlEnumeration>() as usize)
1926                as *mut _xmlEnumeration;
1927            (*v3).name = string::xml_strdup(c_str(b"large"));
1928            let v2 = allocator::xmlMallocZero(size_of::<_xmlEnumeration>() as usize)
1929                as *mut _xmlEnumeration;
1930            (*v2).name = string::xml_strdup(c_str(b"medium"));
1931            (*v2).next = v3;
1932            let v1 = allocator::xmlMallocZero(size_of::<_xmlEnumeration>() as usize)
1933                as *mut _xmlEnumeration;
1934            (*v1).name = string::xml_strdup(c_str(b"small"));
1935            (*v1).next = v2;
1936
1937            let elem = add_element_decl(
1938                dtd,
1939                elem_name,
1940                XML_ELEMENT_TYPE_EMPTY as c_int,
1941                ptr::null_mut(),
1942            );
1943            let attr = add_attribute_decl(
1944                dtd,
1945                elem,
1946                attr_name,
1947                XML_ATTRIBUTE_ENUMERATION as c_int,
1948                XML_ATTRIBUTE_REQUIRED as c_int,
1949                ptr::null(),
1950                v1,
1951            );
1952            assert!(!attr.is_null());
1953            assert_eq!((*attr).atype, XML_ATTRIBUTE_ENUMERATION as c_int);
1954
1955            free_dtd(dtd);
1956            allocator::xmlFreeImpl(doc as *mut c_void);
1957        }
1958    }
1959
1960    #[test]
1961    fn test_add_attribute_null_dtd() {
1962        unsafe {
1963            let attr = add_attribute_decl(
1964                ptr::null_mut(),
1965                ptr::null_mut(),
1966                c_str(b"test"),
1967                XML_ATTRIBUTE_CDATA as c_int,
1968                XML_ATTRIBUTE_IMPLIED as c_int,
1969                ptr::null(),
1970                ptr::null_mut(),
1971            );
1972            assert!(attr.is_null());
1973        }
1974    }
1975
1976    #[test]
1977    fn test_copy_attribute() {
1978        unsafe {
1979            let (doc, dtd) = make_doc_and_dtd();
1980            let elem_name = c_str(b"elem");
1981            let attr_name = c_str(b"id");
1982            let default_val = c_str(b"default");
1983
1984            let elem = add_element_decl(
1985                dtd,
1986                elem_name,
1987                XML_ELEMENT_TYPE_EMPTY as c_int,
1988                ptr::null_mut(),
1989            );
1990            let attr = add_attribute_decl(
1991                dtd,
1992                elem,
1993                attr_name,
1994                XML_ATTRIBUTE_ID as c_int,
1995                XML_ATTRIBUTE_IMPLIED as c_int,
1996                default_val,
1997                ptr::null_mut(),
1998            );
1999            assert!(!attr.is_null());
2000
2001            let copy = copy_attribute_decl(attr);
2002            assert!(!copy.is_null());
2003            assert_ne!(copy, attr);
2004            assert_eq!((*copy).atype, XML_ATTRIBUTE_ID as c_int);
2005            assert_eq!(string::xml_strcmp((*copy).name, attr_name), 0);
2006
2007            free_attribute(copy);
2008            free_dtd(dtd);
2009            allocator::xmlFreeImpl(doc as *mut c_void);
2010        }
2011    }
2012
2013    #[test]
2014    fn test_free_attribute_null() {
2015        unsafe {
2016            free_attribute(ptr::null_mut()); // Should not crash
2017        }
2018    }
2019
2020    // ── Content Model Validation Tests ──────────────────────────────────
2021
2022    #[test]
2023    fn test_valid_content_model_null() {
2024        unsafe {
2025            assert_eq!(
2026                valid_content_model(ptr::null_mut(), &[]),
2027                ContentModelResult::Invalid
2028            );
2029        }
2030    }
2031
2032    #[test]
2033    fn test_valid_content_model_pcdata() {
2034        unsafe {
2035            let cm = create_content_model(ptr::null(), XML_ELEMENT_CONTENT_PCDATA as c_int);
2036            assert!(!cm.is_null());
2037
2038            // Empty content is valid for PCDATA
2039            assert_eq!(valid_content_model(cm, &[]), ContentModelResult::Valid);
2040
2041            // Non-empty content is invalid for PCDATA
2042            let name = c_str(b"child");
2043            assert_eq!(
2044                valid_content_model(cm, &[name]),
2045                ContentModelResult::Invalid
2046            );
2047
2048            free_content_model(cm);
2049        }
2050    }
2051
2052    #[test]
2053    fn test_valid_content_model_element() {
2054        unsafe {
2055            let child_name = c_str(b"child");
2056            let cm = create_content_model(child_name, XML_ELEMENT_CONTENT_ELEMENT as c_int);
2057            assert!(!cm.is_null());
2058
2059            // Correct element
2060            assert_eq!(
2061                valid_content_model(cm, &[child_name]),
2062                ContentModelResult::Valid
2063            );
2064
2065            // Wrong element
2066            let other = c_str(b"other");
2067            assert_eq!(
2068                valid_content_model(cm, &[other]),
2069                ContentModelResult::Invalid
2070            );
2071
2072            // Too many elements
2073            assert_eq!(
2074                valid_content_model(cm, &[child_name, child_name]),
2075                ContentModelResult::Invalid
2076            );
2077
2078            // Empty
2079            assert_eq!(valid_content_model(cm, &[]), ContentModelResult::Invalid);
2080
2081            free_content_model(cm);
2082        }
2083    }
2084
2085    #[test]
2086    fn test_valid_content_model_seq() {
2087        unsafe {
2088            let a_name = c_str(b"a");
2089            let b_name = c_str(b"b");
2090
2091            let c1 = create_content_model(a_name, XML_ELEMENT_CONTENT_ELEMENT as c_int);
2092            let c2 = create_content_model(b_name, XML_ELEMENT_CONTENT_ELEMENT as c_int);
2093            let seq = create_content_model(ptr::null(), XML_ELEMENT_CONTENT_SEQ as c_int);
2094            (*seq).c1 = c1;
2095            (*seq).c2 = c2;
2096            (*c1).parent = seq;
2097            (*c2).parent = seq;
2098
2099            // Correct sequence
2100            assert_eq!(
2101                valid_content_model(seq, &[a_name, b_name]),
2102                ContentModelResult::Valid
2103            );
2104
2105            // Wrong order
2106            assert_eq!(
2107                valid_content_model(seq, &[b_name, a_name]),
2108                ContentModelResult::Invalid
2109            );
2110
2111            // Missing element
2112            assert_eq!(
2113                valid_content_model(seq, &[a_name]),
2114                ContentModelResult::Invalid
2115            );
2116
2117            free_content_model(seq);
2118        }
2119    }
2120
2121    #[test]
2122    fn test_valid_content_model_or() {
2123        unsafe {
2124            let a_name = c_str(b"a");
2125            let b_name = c_str(b"b");
2126
2127            let c1 = create_content_model(a_name, XML_ELEMENT_CONTENT_ELEMENT as c_int);
2128            let c2 = create_content_model(b_name, XML_ELEMENT_CONTENT_ELEMENT as c_int);
2129            let choice = create_content_model(ptr::null(), XML_ELEMENT_CONTENT_OR as c_int);
2130            (*choice).c1 = c1;
2131            (*choice).c2 = c2;
2132            (*c1).parent = choice;
2133            (*c2).parent = choice;
2134
2135            // First alternative
2136            assert_eq!(
2137                valid_content_model(choice, &[a_name]),
2138                ContentModelResult::Valid
2139            );
2140
2141            // Second alternative
2142            assert_eq!(
2143                valid_content_model(choice, &[b_name]),
2144                ContentModelResult::Valid
2145            );
2146
2147            // Neither
2148            let other = c_str(b"other");
2149            assert_eq!(
2150                valid_content_model(choice, &[other]),
2151                ContentModelResult::Invalid
2152            );
2153
2154            free_content_model(choice);
2155        }
2156    }
2157
2158    #[test]
2159    fn test_valid_content_model_optional() {
2160        unsafe {
2161            let a_name = c_str(b"a");
2162
2163            let c1 = create_content_model(a_name, XML_ELEMENT_CONTENT_ELEMENT as c_int);
2164            (*c1).ocur = XML_ELEMENT_CONTENT_OPT as c_int;
2165
2166            // Empty is valid for optional
2167            assert_eq!(valid_content_model(c1, &[]), ContentModelResult::Valid);
2168
2169            // One is valid
2170            assert_eq!(
2171                valid_content_model(c1, &[a_name]),
2172                ContentModelResult::Valid
2173            );
2174
2175            free_content_model(c1);
2176        }
2177    }
2178
2179    #[test]
2180    fn test_valid_content_model_zero_or_more() {
2181        unsafe {
2182            let a_name = c_str(b"a");
2183
2184            let c1 = create_content_model(a_name, XML_ELEMENT_CONTENT_ELEMENT as c_int);
2185            (*c1).ocur = XML_ELEMENT_CONTENT_MULT as c_int;
2186
2187            // Empty is valid
2188            assert_eq!(valid_content_model(c1, &[]), ContentModelResult::Valid);
2189
2190            // One is valid
2191            assert_eq!(
2192                valid_content_model(c1, &[a_name]),
2193                ContentModelResult::Valid
2194            );
2195
2196            // Multiple is valid
2197            assert_eq!(
2198                valid_content_model(c1, &[a_name, a_name, a_name]),
2199                ContentModelResult::Valid
2200            );
2201
2202            free_content_model(c1);
2203        }
2204    }
2205
2206    #[test]
2207    fn test_valid_content_model_one_or_more() {
2208        unsafe {
2209            let a_name = c_str(b"a");
2210
2211            let c1 = create_content_model(a_name, XML_ELEMENT_CONTENT_ELEMENT as c_int);
2212            (*c1).ocur = XML_ELEMENT_CONTENT_PLUS as c_int;
2213
2214            // Empty is invalid
2215            assert_eq!(valid_content_model(c1, &[]), ContentModelResult::Invalid);
2216
2217            // One is valid
2218            assert_eq!(
2219                valid_content_model(c1, &[a_name]),
2220                ContentModelResult::Valid
2221            );
2222
2223            // Multiple is valid
2224            assert_eq!(
2225                valid_content_model(c1, &[a_name, a_name]),
2226                ContentModelResult::Valid
2227            );
2228
2229            free_content_model(c1);
2230        }
2231    }
2232}