Skip to main content

libxml_rs/xml/
chvalid.rs

1//! XML character-class validation (upstream chvalid.c / parserInternals.c).
2//!
3//! The exported `xmlIs*` family and `xmlCharInRange` use the generated
4//! character-class tables from `unicode_tables.rs` (extracted verbatim from
5//! upstream `codegen/ranges.inc`; see
6//! `tools/archaeology/gen_chvalid_tables.py`).
7//!
8//! # UPSTREAM-PARITY
9//!
10//! The semantics mirror upstream chvalid.h macros exactly:
11//!
12//! - `xmlIsBaseCharQ`: `(c < 0x100) ? xmlIsBaseChar_ch(c) : xmlCharInRange(c, &xmlIsBaseCharGroup)`
13//! - `xmlIsBlankQ`: `(c < 0x100) ? (c==0x20 || 0x9<=c<=0xa || c==0xd) : 0`
14//! - `xmlIsCharQ`: `(c < 0x100) ? (0x9<=c<=0xa || c==0xd || 0x20<=c) : (0x100<=c<=0xd7ff || 0xe000<=c<=0xfffd || 0x10000<=c<=0x10ffff)`
15//! - `xmlIsCombiningQ`: `(c < 0x100) ? 0 : xmlCharInRange(c, &xmlIsCombiningGroup)`
16//! - `xmlIsDigitQ`: `(c < 0x100) ? (0x30<=c<=0x39) : xmlCharInRange(c, &xmlIsDigitGroup)`
17//! - `xmlIsExtenderQ`: `(c < 0x100) ? (c==0xb7) : xmlCharInRange(c, &xmlIsExtenderGroup)`
18//! - `xmlIsIdeographicQ`: `(c < 0x100) ? 0 : (0x4e00<=c<=0x9fa5 || c==0x3007 || 0x3021<=c<=0x3029)`
19//! - `xmlIsPubidCharQ`: `(c < 0x100) ? xmlIsPubidChar_tab[c] : 0`
20//! - `xmlIsLetter`: `xmlIsBaseCharQ(c) || xmlIsIdeographicQ(c)` (parserInternals.c)
21//! - `xmlIsBlankNode`: tree.c — text/CDATA node whose content is empty or
22//!   all blank.
23//!
24//! # Courts
25//!
26//! CHVALID-* differential tests compare against the oracle DSO for the whole
27//! BMP + representative supplementary-plane code points.
28//!
29//! # Upstream contract
30//!
31//! Mirrors upstream `chvalid.c` / `xmlunicode.c` / `chvalid.h`
32//! (`SRC-LIBXML2-2.15.0-CHVALID-C` et al., parity target libxml2 2.15.3
33//! oracle): `xmlCharInRange`, the exported `xmlIs*` predicates and the
34//! `xmlIsPubidChar_tab` data table.
35//!
36//! # Conceptual behavior
37//!
38//! Implements the upstream Q-macro semantics verbatim (each `xmlIs*Q`
39//! expansion is listed above): sub-0x100 code points are answered from
40//! tables/linear tests, larger code points from the generated range
41//! groups via binary search. `xmlIsLetter` (parserInternals.c) and
42//! `xmlIsBlankNode` (tree.c) complete the surface.
43//!
44//! # Ownership & safety invariants
45//!
46//! The tables are immutable `static` data (extracted from upstream
47//! `codegen/ranges.inc`); `xmlCharInRange` only reads its group argument
48//! (SAFETY: NULL group returns 0, valid group must cover
49//! nbShortRange/nbLongRange entries). Nothing here allocates.
50//!
51//! # Historical quirks & epochs
52//!
53//! R-000135: the seven char-class tables were extracted verbatim from
54//! upstream ranges.inc by tools/archaeology/gen_chvalid_tables.py
55//! (sha256-bound, oracle sha256 e7575963…) and the DATA-GLOBALS-001 court
56//! fingerprints FNV-1a hashes of all nine `xmlIs*` functions over the BMP
57//! — the tables are stable across the 2.7.8 → 2.15.3 oracle span.
58//!
59//! # Deliberate oddities
60//!
61//! `xmlIsPubidChar` only accepts < 0x100 (per the Q-macro); the Latin-1
62//! linear cases in `xmlIsBaseChar` etc. are kept exactly as upstream
63//! encodes them rather than merged into the range groups.
64//!
65//! # Proving courts
66//!
67//! DATA-GLOBALS-001 (tools/abi/data_globals_probe.py + committed C probe)
68//! compiles the probe against the system libxml2 and the candidate DSO and
69//! requires byte-identical output; CHVALID-* differential tests cover the
70//! whole BMP; cargo test runs the unit assertions.
71//!
72//! # Tempting simplifications that would break parity
73//!
74//! Do not regenerate the tables from Unicode data files: upstream tables
75//! carry historical drift (e.g. ideographs bounded at 0x9fa5, the
76//! pubid table) that byte-parity requires. Do not replace the binary
77//! search with a hash set: the group ranges are what the C ABI exposes
78//! through `xmlChRangeGroup`.
79
80use crate::abi::structs::{_xmlNode, xmlChRangeGroup};
81use crate::xml::unicode_tables::*;
82use std::os::raw::{c_int, c_uint, c_ushort};
83
84/// Binary search over the short/long range tables (upstream `xmlCharInRange`,
85/// chvalid.c — the tables are sorted, so the search is exact).
86///
87/// # SAFETY
88///
89/// - `group` must be NULL or point to a valid `xmlChRangeGroup` whose range
90///   arrays cover `nbShortRange`/`nbLongRange` entries.
91#[no_mangle]
92pub const unsafe extern "C" fn xmlCharInRange(val: c_uint, group: *const xmlChRangeGroup) -> c_int {
93    if group.is_null() {
94        return 0;
95    }
96    let g = unsafe { &*group };
97    if val < 0x10000 {
98        // Short (16-bit) ranges.
99        if g.nbShortRange == 0 {
100            return 0;
101        }
102        let mut low = 0;
103        let mut high = g.nbShortRange - 1;
104        let sptr = g.shortRange;
105        if sptr.is_null() {
106            return 0;
107        }
108        while low <= high {
109            let mid = (low + high) / 2;
110            let s = unsafe { &*sptr.add(mid as usize) };
111            if (val as c_ushort) < s.low {
112                high = mid - 1;
113            } else if (val as c_ushort) > s.high {
114                low = mid + 1;
115            } else {
116                return 1;
117            }
118        }
119        0
120    } else {
121        // Long (32-bit) ranges.
122        if g.nbLongRange == 0 {
123            return 0;
124        }
125        let mut low = 0;
126        let mut high = g.nbLongRange - 1;
127        let lptr = g.longRange;
128        if lptr.is_null() {
129            return 0;
130        }
131        while low <= high {
132            let mid = (low + high) / 2;
133            let l = unsafe { &*lptr.add(mid as usize) };
134            if val < l.low {
135                high = mid - 1;
136            } else if val > l.high {
137                low = mid + 1;
138            } else {
139                return 1;
140            }
141        }
142        0
143    }
144}
145
146#[inline]
147fn is_base_char_ch(c: c_uint) -> bool {
148    // upstream xmlIsBaseChar_ch (genChRanges.py): ASCII letters plus the
149    // Latin-1 letters that do not fall in the group's short ranges.
150    (0x41..=0x5a).contains(&c)
151        || (0x61..=0x7a).contains(&c)
152        || (0xc0..=0xd6).contains(&c)
153        || (0xd8..=0xf6).contains(&c)
154        || c >= 0xf8
155}
156
157/// `xmlIsBaseChar(unsigned int ch)` — XML 1.0 BaseChar production.
158///
159/// # SAFETY
160///
161/// The function touches crate-global state only; it is safe
162/// as long as the caller respects the library's global
163/// initialization/cleanup ordering (xmlInitParser before use,
164/// xmlCleanupParser only after all users are done).
165///
166/// Violating the global lifecycle ordering, or calling this after
167/// teardown or from a signal handler, is undefined behavior.
168#[no_mangle]
169pub unsafe extern "C" fn xmlIsBaseChar(ch: c_uint) -> c_int {
170    if ch < 0x100 {
171        is_base_char_ch(ch) as c_int
172    } else {
173        unsafe { xmlCharInRange(ch, &xmlIsBaseCharGroup) }
174    }
175}
176
177/// `xmlIsBlank(unsigned int ch)` — space, tab, LF, CR.
178///
179/// # SAFETY
180///
181/// The function touches crate-global state only; it is safe
182/// as long as the caller respects the library's global
183/// initialization/cleanup ordering (xmlInitParser before use,
184/// xmlCleanupParser only after all users are done).
185///
186/// Violating the global lifecycle ordering, or calling this after
187/// teardown or from a signal handler, is undefined behavior.
188#[no_mangle]
189pub unsafe extern "C" fn xmlIsBlank(ch: c_uint) -> c_int {
190    if ch < 0x100 {
191        (ch == 0x20 || (0x9..=0xa).contains(&ch) || ch == 0xd) as c_int
192    } else {
193        0
194    }
195}
196
197/// `xmlIsChar(unsigned int ch)` — XML 1.0 Char production.
198///
199/// # SAFETY
200///
201/// The function touches crate-global state only; it is safe
202/// as long as the caller respects the library's global
203/// initialization/cleanup ordering (xmlInitParser before use,
204/// xmlCleanupParser only after all users are done).
205///
206/// Violating the global lifecycle ordering, or calling this after
207/// teardown or from a signal handler, is undefined behavior.
208#[no_mangle]
209pub unsafe extern "C" fn xmlIsChar(ch: c_uint) -> c_int {
210    if ch < 0x100 {
211        ((0x9..=0xa).contains(&ch) || ch == 0xd || ch >= 0x20) as c_int
212    } else {
213        ((0x100..=0xd7ff).contains(&ch)
214            || (0xe000..=0xfffd).contains(&ch)
215            || (0x10000..=0x10ffff).contains(&ch)) as c_int
216    }
217}
218
219/// `xmlIsCombining(unsigned int ch)` — XML 1.0 CombiningChar production.
220///
221/// # SAFETY
222///
223/// The function touches crate-global state only; it is safe
224/// as long as the caller respects the library's global
225/// initialization/cleanup ordering (xmlInitParser before use,
226/// xmlCleanupParser only after all users are done).
227///
228/// Violating the global lifecycle ordering, or calling this after
229/// teardown or from a signal handler, is undefined behavior.
230#[no_mangle]
231pub unsafe extern "C" fn xmlIsCombining(ch: c_uint) -> c_int {
232    if ch < 0x100 {
233        0
234    } else {
235        unsafe { xmlCharInRange(ch, &xmlIsCombiningGroup) }
236    }
237}
238
239/// `xmlIsDigit(unsigned int ch)` — XML 1.0 Digit production.
240///
241/// # SAFETY
242///
243/// The function touches crate-global state only; it is safe
244/// as long as the caller respects the library's global
245/// initialization/cleanup ordering (xmlInitParser before use,
246/// xmlCleanupParser only after all users are done).
247///
248/// Violating the global lifecycle ordering, or calling this after
249/// teardown or from a signal handler, is undefined behavior.
250#[no_mangle]
251pub unsafe extern "C" fn xmlIsDigit(ch: c_uint) -> c_int {
252    if ch < 0x100 {
253        (0x30..=0x39).contains(&ch) as c_int
254    } else {
255        unsafe { xmlCharInRange(ch, &xmlIsDigitGroup) }
256    }
257}
258
259/// `xmlIsExtender(unsigned int ch)` — XML 1.0 Extender production.
260///
261/// # SAFETY
262///
263/// The function touches crate-global state only; it is safe
264/// as long as the caller respects the library's global
265/// initialization/cleanup ordering (xmlInitParser before use,
266/// xmlCleanupParser only after all users are done).
267///
268/// Violating the global lifecycle ordering, or calling this after
269/// teardown or from a signal handler, is undefined behavior.
270#[no_mangle]
271pub unsafe extern "C" fn xmlIsExtender(ch: c_uint) -> c_int {
272    if ch < 0x100 {
273        (ch == 0xb7) as c_int
274    } else {
275        unsafe { xmlCharInRange(ch, &xmlIsExtenderGroup) }
276    }
277}
278
279/// `xmlIsIdeographic(unsigned int ch)` — XML 1.0 Ideographic production.
280///
281/// # SAFETY
282///
283/// The function touches crate-global state only; it is safe
284/// as long as the caller respects the library's global
285/// initialization/cleanup ordering (xmlInitParser before use,
286/// xmlCleanupParser only after all users are done).
287///
288/// Violating the global lifecycle ordering, or calling this after
289/// teardown or from a signal handler, is undefined behavior.
290#[no_mangle]
291pub unsafe extern "C" fn xmlIsIdeographic(ch: c_uint) -> c_int {
292    if ch < 0x100 {
293        0
294    } else {
295        ((0x4e00..=0x9fa5).contains(&ch) || ch == 0x3007 || (0x3021..=0x3029).contains(&ch))
296            as c_int
297    }
298}
299
300/// `xmlIsPubidChar(unsigned int ch)` — PubidChar production (ASCII table).
301///
302/// # SAFETY
303///
304/// The function touches crate-global state only; it is safe
305/// as long as the caller respects the library's global
306/// initialization/cleanup ordering (xmlInitParser before use,
307/// xmlCleanupParser only after all users are done).
308///
309/// Violating the global lifecycle ordering, or calling this after
310/// teardown or from a signal handler, is undefined behavior.
311#[no_mangle]
312pub unsafe extern "C" fn xmlIsPubidChar(ch: c_uint) -> c_int {
313    if ch >= 0x100 {
314        0
315    } else {
316        xmlIsPubidChar_tab[ch as usize] as c_int
317    }
318}
319
320/// `xmlIsLetter(int c)` — BaseChar or Ideographic (parserInternals.c).
321///
322/// # SAFETY
323///
324/// The function touches crate-global state only; it is safe
325/// as long as the caller respects the library's global
326/// initialization/cleanup ordering (xmlInitParser before use,
327/// xmlCleanupParser only after all users are done).
328///
329/// Violating the global lifecycle ordering, or calling this after
330/// teardown or from a signal handler, is undefined behavior.
331#[no_mangle]
332pub unsafe extern "C" fn xmlIsLetter(c: c_int) -> c_int {
333    let ch = c as c_uint;
334    if ch < 0x100 {
335        is_base_char_ch(ch) as c_int
336    } else {
337        unsafe { xmlIsBaseChar(ch) | xmlIsIdeographic(ch) }
338    }
339}
340
341/// `xmlIsBlankNode(const xmlNode *node)` — text/CDATA node with empty or
342/// whitespace-only content (tree.c 2.15).
343///
344/// # SAFETY
345///
346/// - `node` must be NULL or a valid node pointer.
347#[no_mangle]
348pub unsafe extern "C" fn xmlIsBlankNode(node: *const _xmlNode) -> c_int {
349    if node.is_null() {
350        return 0;
351    }
352    let n = unsafe { &*node };
353    if n.type_ != crate::abi::types::xmlElementType::XML_TEXT_NODE as c_int
354        && n.type_ != crate::abi::types::xmlElementType::XML_CDATA_SECTION_NODE as c_int
355    {
356        return 0;
357    }
358    if n.content.is_null() {
359        return 1;
360    }
361    let mut cur = n.content;
362    while !cur.is_null() && *cur != 0 {
363        let ch = *cur as c_uint;
364        if ch != 0x20 && !(0x9..=0xa).contains(&ch) && ch != 0xd {
365            return 0;
366        }
367        cur = cur.add(1);
368    }
369    1
370}
371
372#[cfg(test)]
373mod tests {
374    use super::*;
375    use crate::abi::allocator;
376    use crate::abi::types::xmlChar;
377    use std::os::raw::c_uint;
378
379    /// Differential-oracle spot checks (values verified against the system
380    /// libxml2 2.15.3 DSO via tools/abi/data_globals_probe.py).
381    ///
382    /// # Safety
383    ///
384    /// - `ch` is a plain integer passed by value; `xmlIsBaseChar` only
385    ///   inspects the value, so no pointer validity requirements apply.
386    fn oracle_is_base_char(ch: c_uint) -> c_int {
387        unsafe { xmlIsBaseChar(ch) }
388    }
389
390    /// Check the XML 1.0 Char production for boundary code points.
391    ///
392    /// # Safety
393    ///
394    /// - All arguments are integers passed by value; `xmlIsChar` only
395    ///   inspects the value, so no pointer validity requirements apply.
396    #[test]
397    fn test_xml_is_char_basic() {
398        unsafe {
399            // XML 1.0 Char production.
400            assert_eq!(xmlIsChar(0x9), 1); // tab
401            assert_eq!(xmlIsChar(0xa), 1); // lf
402            assert_eq!(xmlIsChar(0xd), 1); // cr
403            assert_eq!(xmlIsChar(0x20), 1); // space
404            assert_eq!(xmlIsChar(0x1f), 0); // below space
405            assert_eq!(xmlIsChar(0xd7ff), 1);
406            assert_eq!(xmlIsChar(0xd800), 0); // surrogate
407            assert_eq!(xmlIsChar(0xe000), 1);
408            assert_eq!(xmlIsChar(0xfffe), 0);
409            assert_eq!(xmlIsChar(0x10000), 1);
410            assert_eq!(xmlIsChar(0x10ffff), 1);
411            assert_eq!(xmlIsChar(0x110000), 0);
412        }
413    }
414
415    /// Check blank-character classification against upstream.
416    ///
417    /// # Safety
418    ///
419    /// - All arguments are integers passed by value; `xmlIsBlank` only
420    ///   inspects the value, so no pointer validity requirements apply.
421    #[test]
422    fn test_xml_is_blank() {
423        unsafe {
424            assert_eq!(xmlIsBlank(0x20), 1);
425            assert_eq!(xmlIsBlank(0x9), 1);
426            assert_eq!(xmlIsBlank(0xa), 1);
427            assert_eq!(xmlIsBlank(0xd), 1);
428            assert_eq!(xmlIsBlank(b'x' as c_uint), 0);
429            assert_eq!(xmlIsBlank(0x100), 0);
430            assert_eq!(xmlIsBlank(0x3000), 0); // ideographic space NOT blank upstream
431        }
432    }
433
434    /// Check base-character classification for ASCII and range edges.
435    ///
436    /// # Safety
437    ///
438    /// - All arguments are integers passed by value; `xmlIsBaseChar` only
439    ///   inspects the value, so no pointer validity requirements apply.
440    #[test]
441    fn test_xml_is_base_char_ascii_and_ranges() {
442        unsafe {
443            assert_eq!(xmlIsBaseChar(b'A' as c_uint), 1);
444            assert_eq!(xmlIsBaseChar(b'z' as c_uint), 1);
445            assert_eq!(xmlIsBaseChar(b'0' as c_uint), 0);
446            assert_eq!(xmlIsBaseChar(0xc0), 1); // À
447            assert_eq!(xmlIsBaseChar(0xd7), 0);
448            assert_eq!(xmlIsBaseChar(0x100), 1); // Ā (short range)
449            assert_eq!(xmlIsBaseChar(0x132), 0); // between ranges
450            assert_eq!(xmlIsBaseChar(0x386), 1); // Greek
451            assert_eq!(xmlIsBaseChar(0x5d0), 1); // Hebrew
452            assert_eq!(xmlIsBaseChar(0xac00), 1); // Hangul
453            assert_eq!(xmlIsBaseChar(0xac00), oracle_is_base_char(0xac00));
454            assert_eq!(xmlIsBaseChar(0x2a8), 1);
455            assert_eq!(xmlIsBaseChar(0x2a9), 0);
456        }
457    }
458
459    /// Check digit classification including non-ASCII digits.
460    ///
461    /// # Safety
462    ///
463    /// - All arguments are integers passed by value; `xmlIsDigit` only
464    ///   inspects the value, so no pointer validity requirements apply.
465    #[test]
466    fn test_xml_is_digit() {
467        unsafe {
468            assert_eq!(xmlIsDigit(b'0' as c_uint), 1);
469            assert_eq!(xmlIsDigit(b'9' as c_uint), 1);
470            assert_eq!(xmlIsDigit(b'a' as c_uint), 0);
471            assert_eq!(xmlIsDigit(0x660), 1); // Arabic-Indic zero
472            assert_eq!(xmlIsDigit(0x6f9), 1);
473            assert_eq!(xmlIsDigit(0x670), 0);
474        }
475    }
476
477    /// Check combining, extender and ideographic classifications.
478    ///
479    /// # Safety
480    ///
481    /// - All arguments are integers passed by value; the classifier
482    ///   functions only inspect the value, so no pointer validity
483    ///   requirements apply.
484    #[test]
485    fn test_xml_is_combining_extender_ideographic() {
486        unsafe {
487            assert_eq!(xmlIsCombining(0x300), 1); // combining grave
488            assert_eq!(xmlIsCombining(0x20,), 0);
489            assert_eq!(xmlIsExtender(0xb7), 1); // middle dot
490            assert_eq!(xmlIsExtender(0x2d0), 1);
491            assert_eq!(xmlIsExtender(0x3005), 1);
492            assert_eq!(xmlIsExtender(0x20), 0);
493            assert_eq!(xmlIsIdeographic(0x4e00), 1); // CJK
494            assert_eq!(xmlIsIdeographic(0x3007), 1);
495            assert_eq!(xmlIsIdeographic(0x3029), 1);
496            assert_eq!(xmlIsIdeographic(0x302a), 0);
497            assert_eq!(xmlIsIdeographic(0x9fa5), 1);
498            assert_eq!(xmlIsIdeographic(b'A' as c_uint), 0);
499        }
500    }
501
502    /// Check PubidChar classification against upstream behavior.
503    ///
504    /// # Safety
505    ///
506    /// - All arguments are integers passed by value; `xmlIsPubidChar` only
507    ///   inspects the value, so no pointer validity requirements apply.
508    #[test]
509    fn test_xml_is_pubid_char() {
510        unsafe {
511            assert_eq!(xmlIsPubidChar(b'a' as c_uint), 1);
512            assert_eq!(xmlIsPubidChar(b' ' as c_uint), 1);
513            assert_eq!(xmlIsPubidChar(b'!' as c_uint), 1);
514            // @ IS a PubidChar ([-'()+,./:=?;!*#@$_%]).
515            assert_eq!(xmlIsPubidChar(b'@' as c_uint), 1);
516            // ^ and ~ are not.
517            assert_eq!(xmlIsPubidChar(b'^' as c_uint), 0);
518            assert_eq!(xmlIsPubidChar(b'~' as c_uint), 0);
519            assert_eq!(xmlIsPubidChar(0x80), 0);
520            assert_eq!(xmlIsPubidChar(0x100), 0);
521            // tab is not a pubid char upstream.
522            assert_eq!(xmlIsPubidChar(0x9), 0);
523        }
524    }
525
526    /// Check letter classification (base char plus ideographic).
527    ///
528    /// # Safety
529    ///
530    /// - All arguments are integers passed by value; `xmlIsLetter` only
531    ///   inspects the value, so no pointer validity requirements apply.
532    #[test]
533    fn test_xml_is_letter() {
534        unsafe {
535            assert_eq!(xmlIsLetter(b'A' as c_int), 1);
536            assert_eq!(xmlIsLetter(0x4e00), 1); // ideographic counts
537            assert_eq!(xmlIsLetter(b'0' as c_int), 0);
538            assert_eq!(xmlIsLetter(0x386), 1);
539        }
540    }
541
542    /// `xmlCharInRange` with a NULL group table returns 0.
543    ///
544    /// # Safety
545    ///
546    /// - The NULL group pointer is handled by `xmlCharInRange` without
547    ///   being dereferenced; the code point is passed by value.
548    #[test]
549    fn test_xml_char_in_range_null_group() {
550        unsafe {
551            assert_eq!(xmlCharInRange(0x41, core::ptr::null()), 0);
552        }
553    }
554
555    /// `xmlIsBlankNode` handles NULL, text and non-text nodes.
556    ///
557    /// # Safety
558    ///
559    /// - `node` is either NULL or a valid, aligned `_xmlNode` allocated
560    ///   with `xmlMallocImpl` and written with `ptr::write`; the `content`
561    ///   pointers are static NUL-terminated byte strings valid for the
562    ///   calls; the node is freed with `xmlFreeImpl` exactly once at the
563    ///   end.
564    #[test]
565    fn test_xml_is_blank_node() {
566        unsafe {
567            use crate::abi::types::xmlElementType::*;
568            // Null node -> 0.
569            assert_eq!(xmlIsBlankNode(core::ptr::null()), 0);
570            // Text node with NULL content -> 1.
571            let node = allocator::xmlMallocImpl(core::mem::size_of::<_xmlNode>()) as *mut _xmlNode;
572            assert!(!node.is_null());
573            core::ptr::write(
574                node,
575                _xmlNode {
576                    type_: XML_TEXT_NODE as c_int,
577                    content: core::ptr::null_mut(),
578                    ..core::mem::zeroed()
579                },
580            );
581            assert_eq!(xmlIsBlankNode(node), 1);
582            // Whitespace-only -> 1.
583            let ws = b" \t\n\r\0" as *const u8 as *mut xmlChar;
584            (*node).content = ws;
585            assert_eq!(xmlIsBlankNode(node), 1);
586            // Non-whitespace -> 0.
587            let nw = b" x\0" as *const u8 as *mut xmlChar;
588            (*node).content = nw;
589            assert_eq!(xmlIsBlankNode(node), 0);
590            // Non-text node -> 0.
591            (*node).type_ = XML_ELEMENT_NODE as c_int;
592            assert_eq!(xmlIsBlankNode(node), 0);
593            allocator::xmlFreeImpl(node as *mut libc::c_void);
594        }
595    }
596}