Skip to main content

libxml_rs/xml/debug/
mod.rs

1//! Debug/memory debugging infrastructure (§85 Phase 7).
2//!
3//! UPSTREAM-PARITY: Corresponds to `debugXML.c` / `debugXML.h` in libxml2.
4//!
5//! The libxml2 debug APIs for printing tree structure, XPath expressions, etc.
6//! These are used by `xmllint --debug` and other diagnostic tools.
7//!
8//! # Upstream contract
9//!
10//! Mirrors upstream `debugXML.c` / `debugXML.h`
11//! (`SRC-LIBXML2-2.15.0-DEBUGXML-C`, parity target libxml2 2.15.3 oracle):
12//! `xmlDebugDumpNode`, `xmlDebugDumpNodeList`, `xmlDebugDumpOneNode`,
13//! `xmlDebugDumpAttrList`, `xmlDebugDumpString` and the `xmllint --debug`
14//! tree dump format.
15//!
16//! # Conceptual behavior
17//!
18//! Implements the recursive debug dumper: nodes are printed with type,
19//! name, content, attributes, namespaces, line numbers and children at
20//! increasing indentation (`MAX_DEPTH` guard), plus the XPath expression
21//! and string dumps used by xmllint diagnostics.
22//!
23//! # Ownership & safety invariants
24//!
25//! The dump functions borrow the tree — they never free or detach nodes;
26//! output goes to the caller-provided FILE* (or a buffer) that the caller
27//! owns. Namespace strings are read via their href (bounded scans), never
28//! stored.
29//!
30//! # Historical quirks & epochs
31//!
32//! The dump format is one of the most stable surfaces: the debug-simple /
33//! debug-dtd / debug-nodes / debug-ns matrix cases are byte-identical
34//! across the whole 2.7.8 → 2.15.3 span (SEMANTIC_EPOCHS.md stable cases).
35//! One deliberate epoch join: entity content children dump as `TEXT compact`
36//! since 2.13.0 (E-004, commit 8d04f0ee 2024-03-11, R-000119) — the
37//! candidate matches the 2.13+ epoch, i.e. the current oracle.
38//!
39//! # Deliberate oddities
40//!
41//! The `TEXT compact` marker is reproduced exactly (including the
42//! 14/15-byte compact threshold that is unchanged in every tested version),
43//! rather than normalized to plain `TEXT`.
44//!
45//! # Proving courts
46//!
47//! The xmllint --debug CLI differential courts and the debug-* matrix cases
48//! compare dumps byte-identical against each built oracle (2.7.8 through
49//! 2.15.3); cargo test covers the unit dump suites.
50//!
51//! # Tempting simplifications that would break parity
52//!
53//! Do not normalize the dump format (e.g. always printing `TEXT` or
54//! collapsing attributes): xmllint --debug output is a byte-exact parity
55//! target and downstream tooling parses it. Do not drop the compact-text
56//! threshold logic — R-000119/E-004 depend on it.
57
58use crate::abi::structs::{_xmlAttr, _xmlDoc, _xmlNode};
59use core::ffi::{c_char, c_int, c_void};
60use core::ptr;
61
62/// Maximum indentation depth for debug output.
63const MAX_DEPTH: c_int = 100;
64
65/// Check if a node is an XInclude start node.
66///
67/// UPSTREAM-PARITY: `xmlDebugIsXInclude()` — internal check used by debug dumper.
68fn is_xinclude_node(node: *mut _xmlNode) -> bool {
69    if node.is_null() {
70        return false;
71    }
72    unsafe {
73        let ns = (*node).ns;
74        if ns.is_null() {
75            return false;
76        }
77        let ns_href = (*ns).href;
78        let _ns_prefix = (*ns).prefix;
79        if ns_href.is_null() {
80            return false;
81        }
82        // Check for XInclude namespace
83        let href = core::slice::from_raw_parts(ns_href, 30);
84        let xi_ns = b"http://www.w3.org/2001/XInclude\0";
85        let mut matches = true;
86        for i in 0..30 {
87            if i >= href.len() || href[i] != xi_ns[i] {
88                matches = false;
89                break;
90            }
91        }
92        if !matches {
93            return false;
94        }
95        // Check for xi:include element
96        let name_bytes = if !(*node).name.is_null() {
97            core::slice::from_raw_parts((*node).name, 8)
98        } else {
99            return false;
100        };
101        name_bytes.len() >= 7 && &name_bytes[..7] == b"include"
102    }
103}
104
105/// Convert a boolean to text.
106///
107/// UPSTREAM-PARITY: `xmlBoolToText()`
108///
109/// # SAFETY
110///
111/// The function touches crate-global state only; it is safe
112/// as long as the caller respects the library's global
113/// initialization/cleanup ordering (xmlInitParser before use,
114/// xmlCleanupParser only after all users are done).
115///
116/// Violating the global lifecycle ordering, or calling this after
117/// teardown or from a signal handler, is undefined behavior.
118#[no_mangle]
119pub const unsafe extern "C" fn xmlBoolToText(boolval: c_int) -> *const c_char {
120    if boolval != 0 {
121        c"true".as_ptr() as *const c_char
122    } else {
123        c"false".as_ptr() as *const c_char
124    }
125}
126
127/// Dump a debug representation of an xmlChar string.
128///
129/// UPSTREAM-PARITY: `xmlDebugDumpString()` — line breaks, tabs and CRs are
130/// rendered as a single space.
131///
132/// # SAFETY
133///
134/// - `output`, `str_val` must be valid pointers (or NULL
135///   where the upstream C contract allows), obtained from the
136///   matching constructor/owner and not yet freed; the callee may
137///   take or keep ownership exactly as the C API specifies.
138///
139/// The caller must not race this call with concurrent mutation of the
140/// same objects from other threads (per-object state is not internally
141/// synchronized). Violating any of the above is undefined behavior.
142///
143/// Exercised by the C-API differential courts
144/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
145/// courts; those pass byte-for-byte against the upstream oracle.
146#[no_mangle]
147pub unsafe extern "C" fn xmlDebugDumpString(output: *mut _IO_FILE, str_val: *const u8) {
148    if output.is_null() {
149        return;
150    }
151    if str_val.is_null() {
152        unsafe {
153            libc::fprintf(output, c"(NULL)".as_ptr() as *const c_char);
154        }
155        return;
156    }
157    unsafe {
158        // UPSTREAM-PARITY: xmlCtxtDumpString prints at most 40 characters;
159        // blank characters become spaces, bytes >= 0x80 are printed as
160        // `#%X`, and a longer string is truncated with "...".
161        let mut i = 0;
162        while i < 40 {
163            let c = *str_val.add(i);
164            if c == 0 {
165                return;
166            }
167            if c == b' ' || c == b'\t' || c == b'\n' || c == b'\r' {
168                libc::fprintf(output, c" ".as_ptr() as *const c_char);
169            } else if c >= 0x80 {
170                libc::fprintf(output, c"#%X".as_ptr() as *const c_char, c as c_int);
171            } else {
172                libc::fprintf(output, c"%c".as_ptr() as *const c_char, c as c_int);
173            }
174            i += 1;
175        }
176        libc::fprintf(output, c"...".as_ptr() as *const c_char);
177    }
178}
179
180/// Dump a debug representation of an attribute.
181///
182/// UPSTREAM-PARITY: `xmlDebugDumpAttr()`
183///
184/// # SAFETY
185///
186/// - `output`, `attr` must be valid pointers (or NULL
187///   where the upstream C contract allows), obtained from the
188///   matching constructor/owner and not yet freed; the callee may
189///   take or keep ownership exactly as the C API specifies.
190///
191/// The caller must not race this call with concurrent mutation of the
192/// same objects from other threads (per-object state is not internally
193/// synchronized). Violating any of the above is undefined behavior.
194///
195/// Exercised by the C-API differential courts
196/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
197/// courts; those pass byte-for-byte against the upstream oracle.
198#[no_mangle]
199pub unsafe extern "C" fn xmlDebugDumpAttr(
200    output: *mut _IO_FILE,
201    attr: *mut _xmlAttr,
202    depth: c_int,
203) {
204    if output.is_null() || attr.is_null() {
205        return;
206    }
207    unsafe {
208        for _ in 0..depth {
209            libc::fprintf(output, c"  ".as_ptr() as *const c_char);
210        }
211        libc::fprintf(output, c"ATTRIBUTE ".as_ptr() as *const c_char);
212        xmlDebugDumpString(output, (*attr).name);
213        libc::fprintf(output, c"\n".as_ptr() as *const c_char);
214        // The attribute value is dumped as a compact text node child.
215        xmlDebugDumpNode(output, (*attr).children, depth + 1);
216    }
217}
218
219/// Dump a debug representation of an attribute list.
220///
221/// UPSTREAM-PARITY: `xmlDebugDumpAttrList()`
222///
223/// # SAFETY
224///
225/// - `output`, `attr` must be valid pointers (or NULL
226///   where the upstream C contract allows), obtained from the
227///   matching constructor/owner and not yet freed; the callee may
228///   take or keep ownership exactly as the C API specifies.
229///
230/// The caller must not race this call with concurrent mutation of the
231/// same objects from other threads (per-object state is not internally
232/// synchronized). Violating any of the above is undefined behavior.
233///
234/// Exercised by the C-API differential courts
235/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
236/// courts; those pass byte-for-byte against the upstream oracle.
237#[no_mangle]
238pub unsafe extern "C" fn xmlDebugDumpAttrList(
239    output: *mut _IO_FILE,
240    attr: *mut _xmlAttr,
241    depth: c_int,
242) {
243    if output.is_null() {
244        return;
245    }
246    let mut cur = attr;
247    while !cur.is_null() {
248        unsafe {
249            xmlDebugDumpAttr(output, cur, depth);
250            cur = (*cur).next;
251        }
252    }
253}
254
255/// Dump a single node for debug output.
256///
257/// UPSTREAM-PARITY: `xmlDebugDumpOneNode()`
258///
259/// # SAFETY
260///
261/// - `output`, `node` must be valid pointers (or NULL
262///   where the upstream C contract allows), obtained from the
263///   matching constructor/owner and not yet freed; the callee may
264///   take or keep ownership exactly as the C API specifies.
265///
266/// The caller must not race this call with concurrent mutation of the
267/// same objects from other threads (per-object state is not internally
268/// synchronized). Violating any of the above is undefined behavior.
269///
270/// Exercised by the C-API differential courts
271/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
272/// courts; those pass byte-for-byte against the upstream oracle.
273#[no_mangle]
274pub unsafe extern "C" fn xmlDebugDumpOneNode(
275    output: *mut _IO_FILE,
276    node: *mut _xmlNode,
277    depth: c_int,
278) {
279    if output.is_null() || node.is_null() {
280        return;
281    }
282    unsafe {
283        // Indent
284        for _ in 0..depth {
285            libc::fprintf(output, c"  ".as_ptr() as *const c_char);
286        }
287
288        // Print node type
289        match (*node).type_ {
290            1 => {
291                // XML_ELEMENT_NODE
292                libc::fprintf(output, c"ELEMENT ".as_ptr() as *const c_char);
293                // QName: prefix:name when a namespace prefix is present.
294                if !(*node).ns.is_null() && !(*(*node).ns).prefix.is_null() {
295                    libc::fprintf(
296                        output,
297                        c"%s:".as_ptr() as *const c_char,
298                        (*(*node).ns).prefix,
299                    );
300                }
301                xmlDebugDumpString(output, (*node).name);
302                libc::fprintf(output, c"\n".as_ptr() as *const c_char);
303
304                // Namespace declarations on the element (upstream prints them
305                // before attributes).
306                if !(*node).nsDef.is_null() {
307                    let mut ns = (*node).nsDef;
308                    while !ns.is_null() {
309                        for _ in 0..(depth + 1) {
310                            libc::fprintf(output, c"  ".as_ptr() as *const c_char);
311                        }
312                        libc::fprintf(output, c"namespace ".as_ptr() as *const c_char);
313                        if (*ns).prefix.is_null() {
314                            libc::fprintf(output, c" ".as_ptr() as *const c_char);
315                        } else {
316                            libc::fprintf(output, c"%s".as_ptr() as *const c_char, (*ns).prefix);
317                        }
318                        libc::fprintf(output, c" href=".as_ptr() as *const c_char);
319                        libc::fprintf(output, c"%s\n".as_ptr() as *const c_char, (*ns).href);
320                        ns = (*ns).next;
321                    }
322                }
323
324                // Attributes
325                if !(*node).properties.is_null() {
326                    xmlDebugDumpAttrList(output, (*node).properties, depth + 1);
327                }
328            }
329            2 => {
330                // XML_ATTRIBUTE_NODE
331                libc::fprintf(output, c"ATTRIBUTE ".as_ptr() as *const c_char);
332                xmlDebugDumpString(output, (*node).name);
333                libc::fprintf(output, c"\n".as_ptr() as *const c_char);
334            }
335            3 => {
336                // XML_TEXT_NODE
337                libc::fprintf(output, c"TEXT".as_ptr() as *const c_char);
338                // UPSTREAM-PARITY: debugXML.c marks compact text via
339                // `node->content == (xmlChar *) &(node->properties)`.
340                let inline_addr = std::ptr::addr_of_mut!((*node).properties) as *const c_void;
341                if (*node).content as *const c_void == inline_addr {
342                    libc::fprintf(output, c" compact".as_ptr() as *const c_char);
343                }
344                libc::fprintf(output, c"\n".as_ptr() as *const c_char);
345                for _ in 0..(depth + 1) {
346                    libc::fprintf(output, c"  ".as_ptr() as *const c_char);
347                }
348                libc::fprintf(output, c"content=".as_ptr() as *const c_char);
349                if !(*node).content.is_null() {
350                    xmlDebugDumpString(output, (*node).content as *const u8);
351                } else {
352                    let c = crate::xml::tree::node_get_content(node);
353                    if !c.is_null() {
354                        libc::fprintf(output, c"%s".as_ptr() as *const c_char, c);
355                        crate::abi::allocator::xmlFreeImpl(c as *mut c_void);
356                    }
357                }
358                libc::fprintf(output, c"\n".as_ptr() as *const c_char);
359            }
360            4 => {
361                // XML_CDATA_SECTION_NODE
362                libc::fprintf(output, c"CDATA_SECTION\n".as_ptr() as *const c_char);
363                for _ in 0..(depth + 1) {
364                    libc::fprintf(output, c"  ".as_ptr() as *const c_char);
365                }
366                libc::fprintf(output, c"content=".as_ptr() as *const c_char);
367                xmlDebugDumpString(output, (*node).content as *const u8);
368                libc::fprintf(output, c"\n".as_ptr() as *const c_char);
369            }
370            5 => {
371                // XML_ENTITY_REF_NODE
372                libc::fprintf(output, c"ENTITY_REF(".as_ptr() as *const c_char);
373                xmlDebugDumpString(output, (*node).name);
374                libc::fprintf(output, c")\n".as_ptr() as *const c_char);
375                // The referenced entity's declaration.
376                let doc = (*node).doc;
377                let ent = if doc.is_null() {
378                    ptr::null_mut()
379                } else {
380                    crate::xml::entities::get_entity(doc, (*node).name)
381                };
382                if !ent.is_null() {
383                    for _ in 0..(depth + 1) {
384                        libc::fprintf(output, c"  ".as_ptr() as *const c_char);
385                    }
386                    let etype = (*ent).etype;
387                    let etype_name = entity_type_name(etype);
388                    libc::fprintf(
389                        output,
390                        c"%s ".as_ptr() as *const c_char,
391                        etype_name.as_ptr() as *const c_char,
392                    );
393                    libc::fprintf(output, c"%s\n".as_ptr() as *const c_char, (*ent).name);
394                    for _ in 0..(depth + 1) {
395                        libc::fprintf(output, c"  ".as_ptr() as *const c_char);
396                    }
397                    libc::fprintf(output, c"content=".as_ptr() as *const c_char);
398                    if !(*ent).content.is_null() {
399                        xmlDebugDumpString(output, (*ent).content as *const u8);
400                    }
401                    libc::fprintf(output, c"\n".as_ptr() as *const c_char);
402                }
403            }
404            6 => {
405                // XML_ENTITY_NODE
406                libc::fprintf(output, c"ENTITYDECL(".as_ptr() as *const c_char);
407                xmlDebugDumpString(output, (*node).name);
408                libc::fprintf(output, c")".as_ptr() as *const c_char);
409                if !(*node).content.is_null() {
410                    libc::fprintf(output, c", internal\n ".as_ptr() as *const c_char);
411                    libc::fprintf(output, c"content=".as_ptr() as *const c_char);
412                    xmlDebugDumpString(output, (*node).content as *const u8);
413                }
414                libc::fprintf(output, c"\n".as_ptr() as *const c_char);
415            }
416            7 => {
417                // XML_PI_NODE
418                libc::fprintf(output, c"PI ".as_ptr() as *const c_char);
419                xmlDebugDumpString(output, (*node).name);
420                libc::fprintf(output, c"\n".as_ptr() as *const c_char);
421                if !(*node).content.is_null() {
422                    for _ in 0..(depth + 1) {
423                        libc::fprintf(output, c"  ".as_ptr() as *const c_char);
424                    }
425                    libc::fprintf(output, c"content=".as_ptr() as *const c_char);
426                    xmlDebugDumpString(output, (*node).content as *const u8);
427                    libc::fprintf(output, c"\n".as_ptr() as *const c_char);
428                }
429            }
430            8 => {
431                // XML_COMMENT_NODE
432                libc::fprintf(output, c"COMMENT\n".as_ptr() as *const c_char);
433                for _ in 0..(depth + 1) {
434                    libc::fprintf(output, c"  ".as_ptr() as *const c_char);
435                }
436                libc::fprintf(output, c"content=".as_ptr() as *const c_char);
437                xmlDebugDumpString(output, (*node).content as *const u8);
438                libc::fprintf(output, c"\n".as_ptr() as *const c_char);
439            }
440            9 => {
441                // XML_DOCUMENT_NODE
442                libc::fprintf(output, c"DOCUMENT".as_ptr() as *const c_char);
443            }
444            10 => {
445                // XML_DOCUMENT_TYPE_NODE
446                libc::fprintf(output, c"DOCTYPE".as_ptr() as *const c_char);
447            }
448            14 => {
449                // XML_DTD_NODE. UPSTREAM-PARITY: xmlCtxtDumpDtdNode prints
450                // `DTD(name)`, `, PUBLIC extID` and `, SYSTEM sysID` all on
451                // one line.
452                libc::fprintf(output, c"DTD(".as_ptr() as *const c_char);
453                xmlDebugDumpString(output, (*node).name);
454                libc::fprintf(output, c")".as_ptr() as *const c_char);
455                let dtd = node as *mut crate::abi::structs::_xmlDtd;
456                if !(*dtd).ExternalID.is_null() {
457                    libc::fprintf(output, c", PUBLIC ".as_ptr() as *const c_char);
458                    libc::fprintf(output, c"%s".as_ptr() as *const c_char, (*dtd).ExternalID);
459                }
460                if !(*dtd).SystemID.is_null() {
461                    libc::fprintf(output, c", SYSTEM ".as_ptr() as *const c_char);
462                    libc::fprintf(output, c"%s".as_ptr() as *const c_char, (*dtd).SystemID);
463                }
464                libc::fprintf(output, c"\n".as_ptr() as *const c_char);
465                // Element declarations (hash table).
466                let dtd = node as *mut crate::abi::structs::_xmlDtd;
467                if !(*dtd).elements.is_null() {
468                    let ctx = DtdDumpCtx {
469                        output,
470                        depth: depth + 1,
471                    };
472                    crate::xml::hash::hash_scan(
473                        (*dtd).elements as *mut crate::xml::hash::HashTable,
474                        Some(dump_elemscan_cb),
475                        &ctx as *const DtdDumpCtx as *mut c_void,
476                    );
477                }
478                // Entity declarations.
479                if !(*dtd).entities.is_null() {
480                    let ctx = DtdDumpCtx {
481                        output,
482                        depth: depth + 1,
483                    };
484                    crate::xml::hash::hash_scan(
485                        (*dtd).entities as *mut crate::xml::hash::HashTable,
486                        Some(dump_entityscan_cb),
487                        &ctx as *const DtdDumpCtx as *mut c_void,
488                    );
489                }
490            }
491            13 => {
492                // XML_HTML_DOCUMENT_NODE
493                libc::fprintf(output, c"HTML DOCUMENT".as_ptr() as *const c_char);
494            }
495            18 => {
496                // XML_NAMESPACE_DECL
497                libc::fprintf(output, c"NAMESPACE".as_ptr() as *const c_char);
498                if !(*node).ns.is_null() && !(*(*node).ns).prefix.is_null() {
499                    libc::fprintf(
500                        output,
501                        c" %s=%s".as_ptr() as *const c_char,
502                        (*(*node).ns).prefix,
503                        (*(*node).ns).href,
504                    );
505                }
506            }
507            19 => {
508                // XML_XINCLUDE_START
509                if is_xinclude_node(node) {
510                    libc::fprintf(output, c"XINCLUDE".as_ptr() as *const c_char);
511                } else {
512                    libc::fprintf(output, c"XINCLUDE_START".as_ptr() as *const c_char);
513                }
514            }
515            20 => {
516                // XML_XINCLUDE_END
517                libc::fprintf(output, c"XINCLUDE_END".as_ptr() as *const c_char);
518            }
519            _ => {
520                libc::fprintf(
521                    output,
522                    c"UNKNOWN (%d)".as_ptr() as *const c_char,
523                    (*node).type_ as c_int,
524                );
525            }
526        }
527    }
528}
529
530/// Map an entity type to its upstream debug name.
531fn entity_type_name(etype: c_int) -> Vec<u8> {
532    use crate::abi::types::xmlEntityType::*;
533    match etype {
534        t if t == XML_INTERNAL_GENERAL_ENTITY as c_int => b"INTERNAL_GENERAL_ENTITY\0".to_vec(),
535        t if t == XML_INTERNAL_PARAMETER_ENTITY as c_int => b"INTERNAL_PARAMETER_ENTITY\0".to_vec(),
536        t if t == XML_EXTERNAL_GENERAL_PARSED_ENTITY as c_int => {
537            b"EXTERNAL_GENERAL_PARSED_ENTITY\0".to_vec()
538        }
539        t if t == XML_EXTERNAL_GENERAL_UNPARSED_ENTITY as c_int => {
540            b"EXTERNAL_GENERAL_UNPARSED_ENTITY\0".to_vec()
541        }
542        t if t == XML_EXTERNAL_PARAMETER_ENTITY as c_int => b"EXTERNAL_PARAMETER_ENTITY\0".to_vec(),
543        t if t == XML_INTERNAL_PREDEFINED_ENTITY as c_int => {
544            b"INTERNAL_PREDEFINED_ENTITY\0".to_vec()
545        }
546        _ => b"UNKNOWN_ENTITY\0".to_vec(),
547    }
548}
549
550/// Context for DTD hash-scan debug callbacks.
551#[repr(C)]
552struct DtdDumpCtx {
553    output: *mut _IO_FILE,
554    depth: c_int,
555}
556
557/// Dump an element declaration (`ELEMDECL(name), TYPE (model)`).
558unsafe extern "C" fn dump_elemscan_cb(payload: *mut c_void, data: *mut c_void, _name: *const u8) {
559    if payload.is_null() || data.is_null() {
560        return;
561    }
562    let ctx = unsafe { &*(data as *const DtdDumpCtx) };
563    let elem = unsafe { &*(payload as *mut crate::abi::structs::_xmlElement) };
564    unsafe {
565        for _ in 0..ctx.depth {
566            libc::fprintf(ctx.output, c"  ".as_ptr() as *const c_char);
567        }
568        libc::fprintf(ctx.output, c"ELEMDECL(".as_ptr() as *const c_char);
569        if !elem.name.is_null() {
570            libc::fprintf(ctx.output, c"%s".as_ptr() as *const c_char, elem.name);
571        }
572        libc::fprintf(ctx.output, c")".as_ptr() as *const c_char);
573        // UPSTREAM-PARITY: 2.15 prints the MIXED label for every
574        // parenthesized content model (even element-only ones) in the debug
575        // dump.
576        match elem.type_ {
577            t if t == crate::abi::types::xmlElementTypeVal::XML_ELEMENT_TYPE_EMPTY as c_int => {
578                libc::fprintf(ctx.output, c", EMPTY\n".as_ptr() as *const c_char);
579            }
580            t if t == crate::abi::types::xmlElementTypeVal::XML_ELEMENT_TYPE_ANY as c_int => {
581                libc::fprintf(ctx.output, c", ANY\n".as_ptr() as *const c_char);
582            }
583            _ => {
584                libc::fprintf(ctx.output, c", MIXED ".as_ptr() as *const c_char);
585                dump_debug_content_model(ctx.output, elem.content);
586                libc::fprintf(ctx.output, c"\n".as_ptr() as *const c_char);
587            }
588        }
589    }
590}
591
592/// Render a content model tree in the upstream debug format
593/// (`xmlDebugDumpContentModel`, flattened).
594unsafe fn dump_debug_content_model(
595    output: *mut _IO_FILE,
596    content: *mut crate::abi::structs::_xmlElementContent,
597) {
598    use crate::abi::types::xmlElementContentType::*;
599    if content.is_null() {
600        return;
601    }
602    unsafe {
603        let c = &*content;
604        match c.type_ {
605            t if t == XML_ELEMENT_CONTENT_PCDATA as c_int => {
606                libc::fprintf(output, c"(#PCDATA)".as_ptr() as *const c_char);
607            }
608            t if t == XML_ELEMENT_CONTENT_ELEMENT as c_int => {
609                if !c.prefix.is_null() {
610                    libc::fprintf(output, c"%s:".as_ptr() as *const c_char, c.prefix);
611                }
612                libc::fprintf(output, c"%s".as_ptr() as *const c_char, c.name);
613            }
614            _ => {
615                let sep = if c.type_ == XML_ELEMENT_CONTENT_SEQ as c_int {
616                    c" , ".as_ptr() as *const c_char
617                } else {
618                    c" | ".as_ptr() as *const c_char
619                };
620                libc::fprintf(output, c"(".as_ptr() as *const c_char);
621                let mut parts: Vec<*mut crate::abi::structs::_xmlElementContent> = Vec::new();
622                flatten_chain(c as *const _ as *mut _, c.type_, &mut parts);
623                for (i, &p) in parts.iter().enumerate() {
624                    if i > 0 {
625                        libc::fprintf(output, c"%s".as_ptr() as *const c_char, sep);
626                    }
627                    let pc = &*p;
628                    if pc.type_ == XML_ELEMENT_CONTENT_PCDATA as c_int {
629                        libc::fprintf(output, c"#PCDATA".as_ptr() as *const c_char);
630                    } else if pc.type_ == XML_ELEMENT_CONTENT_ELEMENT as c_int {
631                        if !pc.prefix.is_null() {
632                            libc::fprintf(output, c"%s:".as_ptr() as *const c_char, pc.prefix);
633                        }
634                        libc::fprintf(output, c"%s".as_ptr() as *const c_char, pc.name);
635                    } else {
636                        dump_debug_content_model(output, p);
637                    }
638                    dump_debug_occurrence(output, pc.ocur);
639                }
640                libc::fprintf(output, c")".as_ptr() as *const c_char);
641            }
642        }
643        dump_debug_occurrence(output, c.ocur);
644    }
645}
646
647/// Collect the leaves of a same-type chain (left-leaning trees flatten).
648fn flatten_chain(
649    node: *mut crate::abi::structs::_xmlElementContent,
650    chain_type: c_int,
651    parts: &mut Vec<*mut crate::abi::structs::_xmlElementContent>,
652) {
653    unsafe {
654        let c = &*node;
655        if c.type_ == chain_type && !c.c1.is_null() {
656            flatten_chain(c.c1, chain_type, parts);
657            if !c.c2.is_null() {
658                flatten_chain(c.c2, chain_type, parts);
659            }
660        } else {
661            parts.push(node);
662        }
663    }
664}
665
666/// Print the occurrence suffix.
667unsafe fn dump_debug_occurrence(output: *mut _IO_FILE, ocur: c_int) {
668    use crate::abi::types::xmlElementContentOccur::*;
669    let s = match ocur {
670        t if t == XML_ELEMENT_CONTENT_OPT as c_int => c"?".as_ptr() as *const c_char,
671        t if t == XML_ELEMENT_CONTENT_MULT as c_int => c"*".as_ptr() as *const c_char,
672        t if t == XML_ELEMENT_CONTENT_PLUS as c_int => c"+".as_ptr() as *const c_char,
673        _ => return,
674    };
675    unsafe {
676        libc::fprintf(output, c"%s".as_ptr() as *const c_char, s);
677    }
678}
679
680/// True when a null-terminated string contains markup-significant bytes
681/// (`<` or `&`), i.e. it cannot be a single plain text node.
682const unsafe fn contains_markup(s: *const crate::abi::types::xmlChar) -> bool {
683    if s.is_null() {
684        return false;
685    }
686    unsafe {
687        let mut i = 0usize;
688        while *s.add(i) != 0 {
689            let c = *s.add(i);
690            if c == b'<' || c == b'&' {
691                return true;
692            }
693            i += 1;
694        }
695    }
696    false
697}
698
699/// Dump an entity declaration (`ENTITYDECL(name), internal`).
700unsafe extern "C" fn dump_entityscan_cb(payload: *mut c_void, data: *mut c_void, _name: *const u8) {
701    if payload.is_null() || data.is_null() {
702        return;
703    }
704    let ctx = unsafe { &*(data as *const DtdDumpCtx) };
705    let ent = unsafe { &*(payload as *mut crate::abi::structs::_xmlEntity) };
706    unsafe {
707        for _ in 0..ctx.depth {
708            libc::fprintf(ctx.output, c"  ".as_ptr() as *const c_char);
709        }
710        libc::fprintf(ctx.output, c"ENTITYDECL(".as_ptr() as *const c_char);
711        if !ent.name.is_null() {
712            libc::fprintf(ctx.output, c"%s".as_ptr() as *const c_char, ent.name);
713        }
714        if ent.etype == crate::abi::types::xmlEntityType::XML_INTERNAL_GENERAL_ENTITY as c_int {
715            libc::fprintf(ctx.output, c"), internal\n".as_ptr() as *const c_char);
716            // UPSTREAM-PARITY: the content line is the ENTITYDECL indent plus
717            // one extra leading space.
718            for _ in 0..ctx.depth {
719                libc::fprintf(ctx.output, c"  ".as_ptr() as *const c_char);
720            }
721            libc::fprintf(ctx.output, c" content=".as_ptr() as *const c_char);
722            if !ent.content.is_null() {
723                xmlDebugDumpString(ctx.output, ent.content as *const u8);
724            }
725            libc::fprintf(ctx.output, c"\n".as_ptr() as *const c_char);
726            // The entity's parsed content tree: upstream (debugXML.c
727            // xmlCtxtDumpNode) recurses into ent->children, which the parser
728            // populates on first reference (xmlCtxtParseEntity). For entity
729            // declarations that were never referenced (no children) the raw
730            // content is synthesized as a compact text node for plain text.
731            if !ent.children.is_null() {
732                let mut c = ent.children;
733                while !c.is_null() {
734                    xmlDebugDumpNode(ctx.output, c, (ctx.depth + 1) as c_int);
735                    c = (*c).next;
736                }
737            } else if !ent.content.is_null() && !contains_markup(ent.content) {
738                for _ in 0..(ctx.depth + 1) {
739                    libc::fprintf(ctx.output, c"  ".as_ptr() as *const c_char);
740                }
741                libc::fprintf(ctx.output, c"TEXT compact\n".as_ptr() as *const c_char);
742                for _ in 0..(ctx.depth + 2) {
743                    libc::fprintf(ctx.output, c"  ".as_ptr() as *const c_char);
744                }
745                libc::fprintf(ctx.output, c"content=".as_ptr() as *const c_char);
746                xmlDebugDumpString(ctx.output, ent.content as *const u8);
747                libc::fprintf(ctx.output, c"\n".as_ptr() as *const c_char);
748            }
749        } else {
750            libc::fprintf(ctx.output, c")\n".as_ptr() as *const c_char);
751        }
752    }
753}
754
755/// Dump a node and its subtree.
756///
757/// UPSTREAM-PARITY: `xmlDebugDumpNode()`
758///
759/// # SAFETY
760///
761/// - `output`, `node` must be valid pointers (or NULL
762///   where the upstream C contract allows), obtained from the
763///   matching constructor/owner and not yet freed; the callee may
764///   take or keep ownership exactly as the C API specifies.
765///
766/// The caller must not race this call with concurrent mutation of the
767/// same objects from other threads (per-object state is not internally
768/// synchronized). Violating any of the above is undefined behavior.
769///
770/// Exercised by the C-API differential courts
771/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
772/// courts; those pass byte-for-byte against the upstream oracle.
773#[no_mangle]
774pub unsafe extern "C" fn xmlDebugDumpNode(
775    output: *mut _IO_FILE,
776    node: *mut _xmlNode,
777    depth: c_int,
778) {
779    if output.is_null() || node.is_null() || depth > MAX_DEPTH {
780        return;
781    }
782    unsafe {
783        xmlDebugDumpOneNode(output, node, depth);
784
785        // UPSTREAM-PARITY: only element-like nodes recurse into children;
786        // text nodes (including the non-compact merged representation) do not.
787        // The DTD node's declaration children are dumped from the hash tables
788        // inside xmlDebugDumpOneNode, so the children chain must not be
789        // walked again (upstream debugXML.c xmlCtxtDumpNode reaches the decl
790        // nodes through the chain, but the candidate keeps them in the DTD
791        // tables as well — walking both would duplicate them).
792        let t = (*node).type_;
793        let recurse = t == 1 // XML_ELEMENT_NODE
794            || t == 9  // XML_DOCUMENT_NODE
795            || t == 13 // XML_HTML_DOCUMENT_NODE
796            || t == 11; // XML_DOCUMENT_FRAG_NODE
797        if recurse && !(*node).children.is_null() {
798            let mut child = (*node).children;
799            while !child.is_null() {
800                xmlDebugDumpNode(output, child, depth + 1);
801                child = (*child).next;
802            }
803        }
804    }
805}
806
807/// Dump a node list.
808///
809/// UPSTREAM-PARITY: `xmlDebugDumpNodeList()`
810///
811/// # SAFETY
812///
813/// - `output`, `node` must be valid pointers (or NULL
814///   where the upstream C contract allows), obtained from the
815///   matching constructor/owner and not yet freed; the callee may
816///   take or keep ownership exactly as the C API specifies.
817///
818/// The caller must not race this call with concurrent mutation of the
819/// same objects from other threads (per-object state is not internally
820/// synchronized). Violating any of the above is undefined behavior.
821///
822/// Exercised by the C-API differential courts
823/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
824/// courts; those pass byte-for-byte against the upstream oracle.
825#[no_mangle]
826pub unsafe extern "C" fn xmlDebugDumpNodeList(
827    output: *mut _IO_FILE,
828    node: *mut _xmlNode,
829    depth: c_int,
830) {
831    if output.is_null() {
832        return;
833    }
834    let mut cur = node;
835    while !cur.is_null() {
836        unsafe {
837            xmlDebugDumpNode(output, cur, depth);
838            cur = (*cur).next;
839        }
840    }
841}
842
843/// Dump an entire document.
844///
845/// UPSTREAM-PARITY: `xmlDebugDumpDocument()`
846///
847/// # SAFETY
848///
849/// - `output`, `doc` must be valid pointers (or NULL
850///   where the upstream C contract allows), obtained from the
851///   matching constructor/owner and not yet freed; the callee may
852///   take or keep ownership exactly as the C API specifies.
853///
854/// The caller must not race this call with concurrent mutation of the
855/// same objects from other threads (per-object state is not internally
856/// synchronized). Violating any of the above is undefined behavior.
857///
858/// Exercised by the C-API differential courts
859/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
860/// courts; those pass byte-for-byte against the upstream oracle.
861#[no_mangle]
862pub unsafe extern "C" fn xmlDebugDumpDocument(output: *mut _IO_FILE, doc: *mut _xmlDoc) {
863    if output.is_null() || doc.is_null() {
864        return;
865    }
866    unsafe {
867        // UPSTREAM-PARITY: xmlCtxtDumpDocHead prints "HTML DOCUMENT" for
868        // HTML documents and "DOCUMENT" otherwise (debugXML.c).
869        if (*doc).type_ == crate::abi::types::xmlElementType::XML_HTML_DOCUMENT_NODE as c_int {
870            libc::fprintf(output, c"HTML DOCUMENT\n".as_ptr() as *const c_char);
871        } else {
872            libc::fprintf(output, c"DOCUMENT\n".as_ptr() as *const c_char);
873        }
874        if !(*doc).version.is_null() {
875            libc::fprintf(output, c"version=".as_ptr() as *const c_char);
876            xmlDebugDumpString(output, (*doc).version as *const u8);
877            libc::fprintf(output, c"\n".as_ptr() as *const c_char);
878        }
879        if !(*doc).URL.is_null() {
880            libc::fprintf(output, c"URL=".as_ptr() as *const c_char);
881            // UPSTREAM-PARITY: xmlCtxtDumpDocHead prints the URL through
882            // xmlCtxtDumpString, so it is truncated at 40 characters.
883            xmlDebugDumpString(output, (*doc).URL as *const u8);
884            libc::fprintf(output, c"\n".as_ptr() as *const c_char);
885        }
886        // UPSTREAM-PARITY: the standalone flag is tri-state; the debug dump
887        // prints "standalone=true" whenever it is not 0 (unset defaults to
888        // true in the parser).
889        if (*doc).standalone != 0 {
890            libc::fprintf(output, c"standalone=true\n".as_ptr() as *const c_char);
891        }
892
893        // Document-level namespace declarations (upstream keeps the xml
894        // namespace here; other prefixes live on the root element's nsDef).
895        if !(*doc).oldNs.is_null() {
896            let mut ns = (*doc).oldNs;
897            while !ns.is_null() {
898                libc::fprintf(output, c"namespace ".as_ptr() as *const c_char);
899                if (*ns).prefix.is_null() {
900                    libc::fprintf(output, c" ".as_ptr() as *const c_char);
901                } else {
902                    libc::fprintf(output, c"%s".as_ptr() as *const c_char, (*ns).prefix);
903                }
904                libc::fprintf(output, c" href=%s\n".as_ptr() as *const c_char, (*ns).href);
905                ns = (*ns).next;
906            }
907        }
908
909        // Dump the internal subset. UPSTREAM-PARITY: xmlCreateIntSubset keeps
910        // the DTD as a member of the document's children chain, so the
911        // children loop below dumps it; only dump it here when the
912        // construction path kept it solely on doc->intSubset (xmlCopyDoc,
913        // lazily-created subsets). Never dump both.
914        if !(*doc).intSubset.is_null() {
915            let mut in_chain = false;
916            let mut c = (*doc).children;
917            while !c.is_null() {
918                if c as *mut c_void == (*doc).intSubset as *mut c_void {
919                    in_chain = true;
920                    break;
921                }
922                c = (*c).next;
923            }
924            if !in_chain {
925                xmlDebugDumpNode(output, (*doc).intSubset as *mut _xmlNode, 1);
926            }
927        }
928
929        // Dump children of doc
930        if !(*doc).children.is_null() {
931            let mut child = (*doc).children;
932            while !child.is_null() {
933                xmlDebugDumpNode(output, child, 1);
934                child = (*child).next;
935            }
936        }
937    }
938}
939
940/// Dump the document head (first few nodes).
941///
942/// UPSTREAM-PARITY: `xmlDebugDumpDocumentHead()`
943///
944/// # SAFETY
945///
946/// - `output`, `doc` must be valid pointers (or NULL
947///   where the upstream C contract allows), obtained from the
948///   matching constructor/owner and not yet freed; the callee may
949///   take or keep ownership exactly as the C API specifies.
950///
951/// The caller must not race this call with concurrent mutation of the
952/// same objects from other threads (per-object state is not internally
953/// synchronized). Violating any of the above is undefined behavior.
954///
955/// Exercised by the C-API differential courts
956/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
957/// courts; those pass byte-for-byte against the upstream oracle.
958#[no_mangle]
959pub unsafe extern "C" fn xmlDebugDumpDocumentHead(output: *mut _IO_FILE, doc: *mut _xmlDoc) {
960    if output.is_null() || doc.is_null() {
961        return;
962    }
963    unsafe {
964        xmlDebugDumpDocument(output, doc);
965    }
966}
967
968/// Count the number of nodes in a list reachable via next pointers.
969///
970/// UPSTREAM-PARITY: `xmlLsCountNode()`
971///
972/// # SAFETY
973///
974/// - `node` must be valid pointers (or NULL
975///   where the upstream C contract allows), obtained from the
976///   matching constructor/owner and not yet freed; the callee may
977///   take or keep ownership exactly as the C API specifies.
978///
979/// The caller must not race this call with concurrent mutation of the
980/// same objects from other threads (per-object state is not internally
981/// synchronized). Violating any of the above is undefined behavior.
982///
983/// Exercised by the C-API differential courts
984/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
985/// courts; those pass byte-for-byte against the upstream oracle.
986#[no_mangle]
987pub unsafe extern "C" fn xmlLsCountNode(node: *mut _xmlNode) -> c_int {
988    if node.is_null() {
989        return 0;
990    }
991    let mut count: c_int = 0;
992    let mut cur = node;
993    while !cur.is_null() {
994        count += 1;
995        unsafe {
996            cur = (*cur).next;
997        }
998    }
999    count
1000}
1001
1002/// Dump a single node summary (like `ls -l` for nodes).
1003///
1004/// UPSTREAM-PARITY: `xmlLsOneNode()`
1005///
1006/// # SAFETY
1007///
1008/// - `output`, `node` must be valid pointers (or NULL
1009///   where the upstream C contract allows), obtained from the
1010///   matching constructor/owner and not yet freed; the callee may
1011///   take or keep ownership exactly as the C API specifies.
1012///
1013/// The caller must not race this call with concurrent mutation of the
1014/// same objects from other threads (per-object state is not internally
1015/// synchronized). Violating any of the above is undefined behavior.
1016///
1017/// Exercised by the C-API differential courts
1018/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1019/// courts; those pass byte-for-byte against the upstream oracle.
1020#[no_mangle]
1021pub unsafe extern "C" fn xmlLsOneNode(output: *mut _IO_FILE, node: *mut _xmlNode) {
1022    if output.is_null() || node.is_null() {
1023        return;
1024    }
1025    unsafe {
1026        match (*node).type_ {
1027            1 => {
1028                // XML_ELEMENT_NODE
1029                libc::fprintf(output, c"E ".as_ptr() as *const c_char);
1030                if !(*node).ns.is_null() && !(*(*node).ns).prefix.is_null() {
1031                    libc::fprintf(
1032                        output,
1033                        c"%s:".as_ptr() as *const c_char,
1034                        (*(*node).ns).prefix,
1035                    );
1036                }
1037                xmlDebugDumpString(output, (*node).name);
1038            }
1039            2 => {
1040                libc::fprintf(output, c"A ".as_ptr() as *const c_char);
1041                xmlDebugDumpString(output, (*node).name);
1042            }
1043            3 => {
1044                libc::fprintf(output, c"T ".as_ptr() as *const c_char);
1045                if !(*node).content.is_null() {
1046                    xmlDebugDumpString(output, (*node).content as *const u8);
1047                }
1048            }
1049            4 => {
1050                libc::fprintf(output, c"C ".as_ptr() as *const c_char);
1051            }
1052            5 => {
1053                libc::fprintf(output, c"E ".as_ptr() as *const c_char);
1054                xmlDebugDumpString(output, (*node).name);
1055            }
1056            6 => {
1057                libc::fprintf(output, c"E ".as_ptr() as *const c_char);
1058                xmlDebugDumpString(output, (*node).name);
1059            }
1060            7 => {
1061                libc::fprintf(output, c"PI ".as_ptr() as *const c_char);
1062                xmlDebugDumpString(output, (*node).name);
1063            }
1064            8 => {
1065                libc::fprintf(output, c"C ".as_ptr() as *const c_char);
1066            }
1067            9 => {
1068                libc::fprintf(output, c"D ".as_ptr() as *const c_char);
1069            }
1070            10 => {
1071                libc::fprintf(output, c"DTD ".as_ptr() as *const c_char);
1072            }
1073            14 => {
1074                libc::fprintf(output, c"X ".as_ptr() as *const c_char);
1075            }
1076            _ => {
1077                libc::fprintf(
1078                    output,
1079                    c"? (%d)".as_ptr() as *const c_char,
1080                    (*node).type_ as c_int,
1081                );
1082            }
1083        }
1084        libc::fprintf(output, c"\n".as_ptr() as *const c_char);
1085    }
1086}
1087
1088/// Re-export _IO_FILE type for C ABI compatibility.
1089///
1090/// This is typically `FILE` in C. On Linux with libc, `_IO_FILE` is the struct
1091/// behind `FILE *`. We use `*mut _IO_FILE` to match the upstream signature.
1092pub type _IO_FILE = libc::FILE;
1093
1094// ═══════════════════════════════════════════════════════════════════════════════
1095// Tests
1096// ═══════════════════════════════════════════════════════════════════════════════
1097
1098#[cfg(test)]
1099mod tests {
1100    use super::*;
1101
1102    use crate::abi::types::xmlChar;
1103    use crate::xml::tree::*;
1104
1105    /// Helper: create a simple document for testing.
1106    #[allow(dead_code)]
1107    unsafe fn create_test_doc() -> *mut _xmlDoc {
1108        let doc = new_doc(c"1.0".as_ptr() as *const xmlChar);
1109        let root = new_node(ptr::null_mut(), c"root".as_ptr() as *const xmlChar);
1110        doc_set_root_element(doc, root);
1111        let child = new_child(root, ptr::null_mut(), c"child".as_ptr() as *const xmlChar);
1112        // Set a property using set_prop
1113        set_prop(
1114            child,
1115            c"attr1".as_ptr() as *const xmlChar,
1116            c"value1".as_ptr() as *const xmlChar,
1117        );
1118        doc
1119    }
1120
1121    #[test]
1122    fn test_xml_bool_to_text() {
1123        unsafe {
1124            let t = xmlBoolToText(1);
1125            assert!(!t.is_null());
1126            let f = xmlBoolToText(0);
1127            assert!(!f.is_null());
1128            // Check that the strings are correct by comparing first byte
1129            assert_eq!(*t as u8, b't');
1130            assert_eq!(*f as u8, b'f');
1131        }
1132    }
1133
1134    #[test]
1135    fn test_debug_dump_string_null() {
1136        unsafe {
1137            // Should not crash
1138            xmlDebugDumpString(ptr::null_mut(), ptr::null());
1139            // Should print "(null)"
1140            let f = libc::fmemopen(ptr::null_mut(), 0, c"w".as_ptr() as *const c_char);
1141            if !f.is_null() {
1142                xmlDebugDumpString(f, ptr::null());
1143                libc::fclose(f);
1144            }
1145        }
1146    }
1147
1148    #[test]
1149    fn test_debug_dump_document_null() {
1150        unsafe {
1151            xmlDebugDumpDocument(ptr::null_mut(), ptr::null_mut());
1152            let f = libc::fmemopen(ptr::null_mut(), 0, c"w".as_ptr() as *const c_char);
1153            if !f.is_null() {
1154                xmlDebugDumpDocument(f, ptr::null_mut());
1155                libc::fclose(f);
1156            }
1157        }
1158    }
1159
1160    #[test]
1161    fn test_debug_dump_node_null() {
1162        unsafe {
1163            xmlDebugDumpNode(ptr::null_mut(), ptr::null_mut(), 0);
1164            let f = libc::fmemopen(ptr::null_mut(), 0, c"w".as_ptr() as *const c_char);
1165            if !f.is_null() {
1166                xmlDebugDumpNode(f, ptr::null_mut(), 0);
1167                libc::fclose(f);
1168            }
1169        }
1170    }
1171
1172    #[test]
1173    fn test_ls_count_node() {
1174        unsafe {
1175            let node = new_node(ptr::null_mut(), c"test".as_ptr() as *const xmlChar);
1176            assert!(!node.is_null());
1177            let count = xmlLsCountNode(node);
1178            assert_eq!(count, 1);
1179
1180            // Add a sibling
1181            let sibling = new_node(ptr::null_mut(), c"sibling".as_ptr() as *const xmlChar);
1182            add_sibling(node, sibling);
1183            let count = xmlLsCountNode(node);
1184            assert_eq!(count, 2);
1185
1186            free_node(node);
1187        }
1188    }
1189
1190    #[test]
1191    fn test_debug_dump_attr_null() {
1192        unsafe {
1193            xmlDebugDumpAttr(ptr::null_mut(), ptr::null_mut(), 0);
1194            let f = libc::fmemopen(ptr::null_mut(), 0, c"w".as_ptr() as *const c_char);
1195            if !f.is_null() {
1196                xmlDebugDumpAttr(f, ptr::null_mut(), 0);
1197                libc::fclose(f);
1198            }
1199        }
1200    }
1201
1202    #[test]
1203    fn test_debug_dump_attr_list_null() {
1204        unsafe {
1205            xmlDebugDumpAttrList(ptr::null_mut(), ptr::null_mut(), 0);
1206        }
1207    }
1208
1209    #[test]
1210    fn test_debug_dump_node_list_null() {
1211        unsafe {
1212            xmlDebugDumpNodeList(ptr::null_mut(), ptr::null_mut(), 0);
1213        }
1214    }
1215
1216    #[test]
1217    fn test_ls_one_node_null() {
1218        unsafe {
1219            xmlLsOneNode(ptr::null_mut(), ptr::null_mut());
1220            let f = libc::fmemopen(ptr::null_mut(), 0, c"w".as_ptr() as *const c_char);
1221            if !f.is_null() {
1222                xmlLsOneNode(f, ptr::null_mut());
1223                libc::fclose(f);
1224            }
1225        }
1226    }
1227
1228    #[test]
1229    fn test_dump_document_head_null() {
1230        unsafe {
1231            xmlDebugDumpDocumentHead(ptr::null_mut(), ptr::null_mut());
1232        }
1233    }
1234
1235    #[test]
1236    fn test_ls_count_node_null() {
1237        assert_eq!(unsafe { xmlLsCountNode(ptr::null_mut()) }, 0);
1238    }
1239}