Skip to main content

fastxml/
lib.rs

1//! fastxml - Fast, memory-efficient XML library with XPath and XSD validation support.
2//!
3//! This library provides XML parsing, XPath evaluation, and XSD schema validation
4//! with a focus on memory efficiency through streaming processing.
5//!
6//! # Features
7//!
8//! - **Fast XML Parsing**: Built on quick-xml for high-performance parsing
9//! - **XPath Support**: Evaluate XPath 1.0 expressions
10//! - **XSD Validation**: Stream-based schema validation
11//! - **Memory Efficient**: Streaming APIs to minimize memory usage
12//! - **Thread Safe**: Safe concurrent access through careful design
13//! - **Async Support**: Async schema fetching and resolution with tokio
14//!
15//! # Feature Flags
16//!
17//! - `ureq`: Enables synchronous HTTP client for schema fetching (recommended)
18//! - `tokio`: Enables async HTTP client and schema resolution with reqwest
19//! - `async-trait`: Enables async trait support for custom implementations
20//! - `profile`: Enables memory profiling utilities
21//!
22//! # Quick Start
23//!
24//! ```rust
25//! use fastxml::{parse, xpath, get_root_node, get_node_tag};
26//!
27//! // Parse XML
28//! let xml = r#"<root xmlns:gml="http://www.opengis.net/gml">
29//!     <gml:name>Hello World</gml:name>
30//! </root>"#;
31//!
32//! let doc = parse(xml).unwrap();
33//!
34//! // Get root element
35//! let root = get_root_node(&doc).unwrap();
36//! println!("Root element: {}", get_node_tag(&root));
37//!
38//! // Evaluate XPath
39//! let result = xpath::evaluate(&doc, "//gml:name").unwrap();
40//! let texts = xpath::collect_text_values(&result);
41//! println!("Found: {:?}", texts);
42//! ```
43//!
44//! # libxml API Compatibility
45//!
46//! This library provides API compatibility with the libxml crate for easier migration:
47//!
48//! ```rust
49//! use fastxml::{
50//!     // Types
51//!     XmlDocument, XmlNode, XmlRoNode, XmlContext,
52//!     // Parsing
53//!     parse,
54//!     // XPath
55//!     evaluate, create_context, find_nodes_by_xpath,
56//!     collect_text_values, collect_text_value,
57//!     // Node operations
58//!     get_root_node, get_root_readonly_node, get_node_tag,
59//!     node_to_xml_string, readonly_node_to_xml_string,
60//!     // Schema validation
61//!     create_xml_schema_validation_context,
62//!     validate_document_by_schema,
63//!     parse_schema_locations,
64//! };
65//! ```
66//!
67//! # Streaming Processing
68//!
69//! For large files, use streaming APIs to minimize memory usage:
70//!
71//! ```rust
72//! use fastxml::event::{StreamingParser, XmlEventHandler, XmlEvent};
73//! use fastxml::error::Result;
74//!
75//! struct MyHandler;
76//!
77//! impl XmlEventHandler for MyHandler {
78//!     fn handle(&mut self, event: &XmlEvent) -> Result<()> {
79//!         match event {
80//!             XmlEvent::StartElement { name, .. } => {
81//!                 println!("Start: {}", name);
82//!             }
83//!             _ => {}
84//!         }
85//!         Ok(())
86//!     }
87//!
88//!     fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> {
89//!         self
90//!     }
91//! }
92//!
93//! let xml = "<root><child/></root>";
94//! let mut parser = StreamingParser::new(xml.as_bytes());
95//! parser.add_handler(Box::new(MyHandler));
96//! parser.parse().unwrap();
97//! ```
98//!
99//! # Async Schema Resolution
100//!
101//! Parse XSD schemas with async import/include resolution (requires `tokio` feature):
102//!
103//! ```ignore
104//! use fastxml::schema::{
105//!     AsyncDefaultFetcher, InMemoryStore,
106//!     parse_xsd_with_imports_async,
107//! };
108//!
109//! #[tokio::main]
110//! async fn main() -> fastxml::error::Result<()> {
111//!     let xsd_content = std::fs::read("schema.xsd")?;
112//!     let fetcher = AsyncDefaultFetcher::new()?;
113//!     let store = InMemoryStore::new();
114//!
115//!     let schema = parse_xsd_with_imports_async(
116//!         &xsd_content,
117//!         "http://example.com/schema.xsd",
118//!         &fetcher,
119//!         &store,
120//!     ).await?;
121//!
122//!     Ok(())
123//! }
124//! ```
125
126#![warn(missing_docs)]
127#![warn(rustdoc::missing_crate_level_docs)]
128// Allow some clippy lints for code clarity
129#![allow(clippy::collapsible_if)]
130#![allow(clippy::collapsible_else_if)]
131#![allow(clippy::collapsible_match)]
132#![allow(clippy::large_enum_variant)]
133
134pub mod document;
135pub mod error;
136pub mod event;
137pub mod generator;
138pub mod namespace;
139pub mod namespace_error;
140pub mod node;
141pub mod node_error;
142pub mod parse_error;
143pub mod parser;
144pub mod position;
145pub mod profile;
146pub mod schema;
147pub mod serialize;
148pub mod transform;
149pub mod xpath;
150
151// Re-export error types
152pub use error::{Error, ErrorLevel, ErrorLocation, Result, StructuredError, ValidationErrorType};
153
154// Re-export document types
155pub use document::{DocumentBuilder, XmlDocument};
156
157// Re-export node types
158pub use node::{NodeId, NodeType, XmlNode, XmlRoNode};
159
160// Re-export namespace types
161pub use namespace::{Namespace, NamespaceResolver};
162
163// Re-export compact_str for use in XmlEvent attributes
164pub use compact_str::CompactString;
165
166// Re-export parser functions
167pub use parser::{
168    ParserOptions, parse, parse_from_bufread, parse_schema_locations, parse_with_options,
169};
170
171// Re-export position tracking
172pub use position::PositionTrackingReader;
173
174// Re-export XPath context (for libxml compatibility)
175pub use xpath::context::{XmlContext, XmlSafeContext};
176
177// ============================================================================
178// libxml-compatible API
179// ============================================================================
180
181/// Evaluates an XPath expression on a document.
182///
183/// This is the main XPath evaluation entry point, compatible with libxml's API.
184pub fn evaluate<T: AsRef<str>>(
185    document: &XmlDocument,
186    xpath_expr: T,
187) -> Result<xpath::XPathResult> {
188    xpath::evaluate(document, xpath_expr.as_ref())
189}
190
191/// Creates an XPath context for a document.
192///
193/// The context automatically registers namespace bindings from the root element.
194pub fn create_context(document: &XmlDocument) -> Result<XmlContext> {
195    xpath::create_context(document)
196}
197
198/// Creates a thread-safe XPath context for a document.
199pub fn create_safe_context(document: &XmlDocument) -> Result<XmlSafeContext> {
200    xpath::create_safe_context(document)
201}
202
203/// Finds nodes by XPath expression relative to a node.
204pub fn find_nodes_by_xpath(
205    ctx: &XmlContext,
206    xpath_expr: &str,
207    node: &XmlNode,
208) -> Result<Vec<XmlNode>> {
209    xpath::find_nodes_by_xpath(ctx, xpath_expr, node)
210}
211
212/// Finds read-only nodes by XPath expression.
213pub fn find_readonly_nodes_by_xpath(
214    ctx: &XmlContext,
215    xpath_expr: &str,
216    node: &XmlRoNode,
217) -> Result<Vec<XmlRoNode>> {
218    xpath::find_readonly_nodes_by_xpath(ctx, xpath_expr, node)
219}
220
221/// Finds read-only nodes using a thread-safe context.
222pub fn find_safe_readonly_nodes_by_xpath(
223    ctx: &XmlSafeContext,
224    xpath_expr: &str,
225    node: &XmlRoNode,
226) -> Result<Vec<XmlRoNode>> {
227    xpath::find_safe_readonly_nodes_by_xpath(ctx, xpath_expr, node)
228}
229
230/// Finds read-only nodes matching element names.
231pub fn find_readonly_nodes_in_elements(
232    ctx: &XmlContext,
233    node: &XmlRoNode,
234    elements_to_match: &[&str],
235) -> Result<Vec<XmlRoNode>> {
236    xpath::find_readonly_nodes_in_elements(ctx, node, elements_to_match)
237}
238
239/// Collects text values from an XPath result.
240pub fn collect_text_values(xpath_value: &xpath::XPathResult) -> Vec<String> {
241    xpath::collect_text_values(xpath_value)
242}
243
244/// Collects a single text value from an XPath result.
245pub fn collect_text_value(xpath_value: &xpath::XPathResult) -> String {
246    xpath::collect_text_value(xpath_value)
247}
248
249/// Gets the root element node from a document.
250pub fn get_root_node(document: &XmlDocument) -> Result<XmlNode> {
251    document.get_root_element()
252}
253
254/// Gets the root element as a read-only node.
255pub fn get_root_readonly_node(document: &XmlDocument) -> Result<XmlRoNode> {
256    document.get_root_element_ro()
257}
258
259/// Gets the qualified tag name of a node (prefix:name or just name).
260pub fn get_node_tag(node: &XmlNode) -> String {
261    node.qname()
262}
263
264/// Gets the qualified tag name of a read-only node.
265pub fn get_readonly_node_tag(node: &XmlRoNode) -> String {
266    node.qname()
267}
268
269/// Gets the namespace prefix of a node.
270pub fn get_node_prefix(node: &XmlNode) -> String {
271    node.get_prefix().unwrap_or_default()
272}
273
274/// Gets the namespace prefix of a read-only node.
275pub fn get_readonly_node_prefix(node: &XmlRoNode) -> String {
276    node.get_prefix().unwrap_or_default()
277}
278
279/// Serializes a node to an XML string.
280pub fn node_to_xml_string(document: &XmlDocument, node: &mut XmlNode) -> Result<String> {
281    serialize::node_to_xml_string(document, node)
282}
283
284/// Serializes a read-only node to an XML string.
285pub fn readonly_node_to_xml_string(document: &XmlDocument, node: &XmlRoNode) -> Result<String> {
286    serialize::readonly_node_to_xml_string(document, node)
287}
288
289/// Creates an XSD schema validation context.
290pub fn create_xml_schema_validation_context(
291    schema_location: String,
292) -> Result<schema::XmlSchemaValidationContext> {
293    schema::create_xml_schema_validation_context(&schema_location)
294}
295
296/// Creates an XSD schema validation context from a buffer.
297pub fn create_xml_schema_validation_context_from_buffer(
298    schema: &[u8],
299) -> Result<schema::XmlSchemaValidationContext> {
300    schema::create_xml_schema_validation_context_from_buffer(schema)
301}
302
303/// Validates a document against an XSD schema.
304pub fn validate_document_by_schema(
305    document: &XmlDocument,
306    schema_location: String,
307) -> Result<Vec<StructuredError>> {
308    schema::validate_document_by_schema(document, &schema_location)
309}
310
311/// Validates a document using an existing validation context.
312pub fn validate_document_by_schema_context(
313    document: &XmlDocument,
314    ctx: &schema::XmlSchemaValidationContext,
315) -> Result<Vec<StructuredError>> {
316    schema::validate_document_by_schema_context(document, ctx)
317}
318
319/// Parses XSD content and returns a compiled schema.
320pub fn parse_xsd(content: &[u8]) -> Result<schema::types::CompiledSchema> {
321    schema::parse_xsd(content)
322}
323
324/// Parses XSD content with import resolution.
325#[cfg(feature = "ureq")]
326pub fn parse_xsd_with_imports(
327    content: &[u8],
328    base_uri: &str,
329    fetcher: &schema::UreqFetcher,
330    store: &impl schema::store::SchemaStore,
331) -> Result<schema::types::CompiledSchema> {
332    schema::parse_xsd_with_imports(content, base_uri, fetcher, store)
333}
334
335/// Parses XSD content with async import resolution.
336///
337/// This is the async version of [`parse_xsd_with_imports`]. It resolves all
338/// `xs:import` and `xs:include` dependencies asynchronously.
339///
340/// # Example
341///
342/// ```ignore
343/// use fastxml::schema::{AsyncDefaultFetcher, InMemoryStore};
344/// use fastxml::parse_xsd_with_imports_async;
345///
346/// let fetcher = AsyncDefaultFetcher::new()?;
347/// let store = InMemoryStore::new();
348///
349/// let schema = parse_xsd_with_imports_async(
350///     &xsd_content,
351///     "http://example.com/schema.xsd",
352///     &fetcher,
353///     &store,
354/// ).await?;
355/// ```
356#[cfg(feature = "tokio")]
357pub async fn parse_xsd_with_imports_async<
358    F: schema::AsyncSchemaFetcher,
359    S: schema::AsyncSchemaStore,
360>(
361    content: &[u8],
362    base_uri: &str,
363    fetcher: &F,
364    store: &S,
365) -> Result<schema::types::CompiledSchema> {
366    schema::parse_xsd_with_imports_async(content, base_uri, fetcher, store).await
367}
368
369/// Validates a document using schemas referenced in xsi:schemaLocation.
370///
371/// This function reads the `xsi:schemaLocation` attribute from the document's
372/// root element, fetches the referenced schemas, and validates the document.
373///
374/// # Example
375///
376/// ```ignore
377/// use fastxml::{parse, validate_with_schema_location};
378///
379/// let xml = r#"<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
380///                   xsi:schemaLocation="http://example.com/ns http://example.com/schema.xsd">
381///     <child>content</child>
382/// </root>"#;
383///
384/// let doc = parse(xml)?;
385/// let errors = validate_with_schema_location(&doc)?;
386/// ```
387#[cfg(feature = "ureq")]
388pub fn validate_with_schema_location(document: &XmlDocument) -> Result<Vec<StructuredError>> {
389    schema::validate_with_schema_location(document)
390}
391
392/// Validates a document using schemas referenced in xsi:schemaLocation with a custom fetcher.
393///
394/// This function reads the `xsi:schemaLocation` attribute from the document's
395/// root element, fetches the referenced schemas using the provided fetcher,
396/// and validates the document.
397pub fn validate_with_schema_location_and_fetcher<F: schema::SchemaFetcher>(
398    document: &XmlDocument,
399    fetcher: &F,
400) -> Result<Vec<StructuredError>> {
401    schema::validate_with_schema_location_and_fetcher(document, fetcher)
402}
403
404/// Gets a compiled schema from xsi:schemaLocation in XML content.
405///
406/// This function parses the XML to extract xsi:schemaLocation, fetches the
407/// referenced schemas, and returns a compiled schema suitable for streaming validation.
408///
409/// # Example
410///
411/// ```ignore
412/// use fastxml::get_schema_from_schema_location;
413/// use fastxml::event::StreamingParser;
414/// use fastxml::schema::validator::OnePassSchemaValidator;
415/// use std::sync::Arc;
416/// use std::io::BufReader;
417///
418/// let xml_bytes = std::fs::read("document.xml")?;
419/// let schema = Arc::new(get_schema_from_schema_location(&xml_bytes)?);
420///
421/// let mut parser = StreamingParser::new(BufReader::new(xml_bytes.as_slice()));
422/// parser.add_handler(Box::new(OnePassSchemaValidator::new(schema)));
423/// parser.parse()?;
424/// ```
425#[cfg(feature = "ureq")]
426pub fn get_schema_from_schema_location(
427    xml_content: &[u8],
428) -> Result<schema::types::CompiledSchema> {
429    schema::get_schema_from_schema_location(xml_content)
430}
431
432/// Gets a compiled schema from xsi:schemaLocation with a custom fetcher.
433pub fn get_schema_from_schema_location_with_fetcher<F: schema::SchemaFetcher>(
434    xml_content: &[u8],
435    fetcher: &F,
436) -> Result<schema::types::CompiledSchema> {
437    schema::get_schema_from_schema_location_with_fetcher(xml_content, fetcher)
438}
439
440/// Validates XML from a reader using streaming parser with schemas from xsi:schemaLocation.
441///
442/// This performs true single-pass streaming validation:
443/// 1. On the first StartElement, extracts xsi:schemaLocation
444/// 2. Fetches and compiles the referenced schemas
445/// 3. Continues streaming validation with the fetched schema
446///
447/// # Example
448///
449/// ```ignore
450/// use fastxml::streaming_validate_with_schema_location;
451/// use std::fs::File;
452/// use std::io::BufReader;
453///
454/// let file = File::open("large_document.xml")?;
455/// let errors = streaming_validate_with_schema_location(BufReader::new(file))?;
456/// ```
457#[cfg(feature = "ureq")]
458pub fn streaming_validate_with_schema_location<R: std::io::BufRead>(
459    reader: R,
460) -> Result<Vec<StructuredError>> {
461    schema::streaming_validate_with_schema_location(reader)
462}
463
464/// Validates XML from a reader using streaming parser with a custom fetcher.
465pub fn streaming_validate_with_schema_location_and_fetcher<
466    R: std::io::BufRead,
467    F: schema::SchemaFetcher + 'static,
468>(
469    reader: R,
470    fetcher: F,
471) -> Result<Vec<StructuredError>> {
472    schema::streaming_validate_with_schema_location_and_fetcher(reader, fetcher)
473}
474
475/// Validates a document using schemas referenced in xsi:schemaLocation asynchronously.
476///
477/// This is the async version of [`validate_with_schema_location`]. It fetches schemas
478/// asynchronously using the default async fetcher (`AsyncDefaultFetcher`).
479///
480/// # Example
481///
482/// ```ignore
483/// use fastxml::{parse, validate_with_schema_location_async};
484///
485/// let xml = r#"<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
486///                   xsi:schemaLocation="http://example.com/ns http://example.com/schema.xsd">
487///     <child>content</child>
488/// </root>"#;
489///
490/// let doc = parse(xml)?;
491/// let errors = validate_with_schema_location_async(&doc).await?;
492/// ```
493#[cfg(feature = "tokio")]
494pub async fn validate_with_schema_location_async(
495    document: &XmlDocument,
496) -> Result<Vec<StructuredError>> {
497    schema::validate_with_schema_location_async(document).await
498}
499
500/// Validates a document using schemas referenced in xsi:schemaLocation with an async fetcher.
501///
502/// This is the async version of [`validate_with_schema_location_and_fetcher`].
503/// It fetches schemas asynchronously using the provided fetcher.
504#[cfg(feature = "tokio")]
505pub async fn validate_with_schema_location_with_async_fetcher<
506    F: schema::AsyncSchemaFetcher,
507    S: schema::AsyncSchemaStore,
508>(
509    document: &XmlDocument,
510    fetcher: &F,
511    store: &S,
512) -> Result<Vec<StructuredError>> {
513    schema::validate_with_schema_location_with_async_fetcher(document, fetcher, store).await
514}
515
516/// Gets a compiled schema from xsi:schemaLocation asynchronously.
517///
518/// This is the async version of [`get_schema_from_schema_location`]. It fetches schemas
519/// asynchronously using the default async fetcher (`AsyncDefaultFetcher`).
520///
521/// # Example
522///
523/// ```ignore
524/// use fastxml::get_schema_from_schema_location_async;
525/// use fastxml::schema::validator::OnePassSchemaValidator;
526/// use std::sync::Arc;
527///
528/// let xml_bytes = std::fs::read("document.xml")?;
529/// let schema = Arc::new(get_schema_from_schema_location_async(&xml_bytes).await?);
530/// ```
531#[cfg(feature = "tokio")]
532pub async fn get_schema_from_schema_location_async(
533    xml_content: &[u8],
534) -> Result<schema::types::CompiledSchema> {
535    schema::get_schema_from_schema_location_async(xml_content).await
536}
537
538/// Gets a compiled schema from xsi:schemaLocation with an async fetcher.
539///
540/// This is the async version of [`get_schema_from_schema_location_with_fetcher`].
541/// It fetches schemas asynchronously using the provided fetcher.
542#[cfg(feature = "tokio")]
543pub async fn get_schema_from_schema_location_with_async_fetcher<
544    F: schema::AsyncSchemaFetcher,
545    S: schema::AsyncSchemaStore,
546>(
547    xml_content: &[u8],
548    fetcher: &F,
549    store: &S,
550) -> Result<schema::types::CompiledSchema> {
551    schema::get_schema_from_schema_location_with_async_fetcher(xml_content, fetcher, store).await
552}
553
554#[cfg(test)]
555mod tests {
556    use super::*;
557
558    #[test]
559    fn test_basic_parsing() {
560        let xml = r#"<root attr="value"><child>text</child></root>"#;
561        let doc = parse(xml).unwrap();
562
563        let root = get_root_node(&doc).unwrap();
564        assert_eq!(get_node_tag(&root), "root");
565    }
566
567    #[test]
568    fn test_xpath_evaluation() {
569        let xml = r#"<root><Building/><Room/><Window/></root>"#;
570        let doc = parse(xml).unwrap();
571
572        let result = evaluate(&doc, "//*[name()='Building']").unwrap();
573        let nodes = result.into_nodes();
574        assert_eq!(nodes.len(), 1);
575        assert_eq!(nodes[0].get_name(), "Building");
576    }
577
578    #[test]
579    fn test_context_xpath() {
580        let xml = r#"<root><a>1</a><a>2</a></root>"#;
581        let doc = parse(xml).unwrap();
582        let ctx = create_context(&doc).unwrap();
583        let root = get_root_readonly_node(&doc).unwrap();
584
585        let nodes = find_readonly_nodes_by_xpath(&ctx, "//a", &root).unwrap();
586        assert_eq!(nodes.len(), 2);
587    }
588
589    #[test]
590    fn test_text_collection() {
591        let xml = r#"<root><a>one</a><a>two</a></root>"#;
592        let doc = parse(xml).unwrap();
593
594        let result = evaluate(&doc, "/root/a").unwrap();
595        let texts = collect_text_values(&result);
596        assert_eq!(texts, vec!["one", "two"]);
597    }
598
599    #[test]
600    fn test_namespaced_xml() {
601        let xml = r#"<gml:root xmlns:gml="http://www.opengis.net/gml">
602            <gml:name>test</gml:name>
603        </gml:root>"#;
604        let doc = parse(xml).unwrap();
605
606        let result = evaluate(&doc, "/gml:root/gml:name").unwrap();
607        let nodes = result.into_nodes();
608        assert_eq!(nodes.len(), 1);
609    }
610
611    #[test]
612    fn test_serialization() {
613        let xml = r#"<root><child>text</child></root>"#;
614        let doc = parse(xml).unwrap();
615        let mut root = get_root_node(&doc).unwrap();
616
617        let serialized = node_to_xml_string(&doc, &mut root).unwrap();
618        assert!(serialized.contains("<root>"));
619        assert!(serialized.contains("<child>text</child>"));
620    }
621
622    #[test]
623    fn test_create_safe_context() {
624        let xml = r#"<root><a>1</a></root>"#;
625        let doc = parse(xml).unwrap();
626        let ctx = create_safe_context(&doc).unwrap();
627        let root = get_root_readonly_node(&doc).unwrap();
628
629        let nodes = find_safe_readonly_nodes_by_xpath(&ctx, "//a", &root).unwrap();
630        assert_eq!(nodes.len(), 1);
631    }
632
633    #[test]
634    fn test_find_nodes_by_xpath() {
635        let xml = r#"<root><a>1</a><b>2</b></root>"#;
636        let doc = parse(xml).unwrap();
637        let ctx = create_context(&doc).unwrap();
638        let root = get_root_node(&doc).unwrap();
639
640        let nodes = find_nodes_by_xpath(&ctx, "//a", &root).unwrap();
641        assert_eq!(nodes.len(), 1);
642        assert_eq!(nodes[0].get_name(), "a");
643    }
644
645    #[test]
646    fn test_find_readonly_nodes_in_elements() {
647        let xml = r#"<root><a>1</a><b>2</b><c>3</c></root>"#;
648        let doc = parse(xml).unwrap();
649        let ctx = create_context(&doc).unwrap();
650        let root = get_root_readonly_node(&doc).unwrap();
651
652        let nodes = find_readonly_nodes_in_elements(&ctx, &root, &["a", "c"]).unwrap();
653        assert_eq!(nodes.len(), 2);
654    }
655
656    #[test]
657    fn test_collect_text_value_single() {
658        let xml = r#"<root><a>hello</a></root>"#;
659        let doc = parse(xml).unwrap();
660
661        let result = evaluate(&doc, "/root/a").unwrap();
662        let text = collect_text_value(&result);
663        assert_eq!(text, "hello");
664    }
665
666    #[test]
667    fn test_collect_text_value_empty() {
668        let xml = r#"<root><a/></root>"#;
669        let doc = parse(xml).unwrap();
670
671        let result = evaluate(&doc, "/root/nonexistent").unwrap();
672        let text = collect_text_value(&result);
673        assert!(text.is_empty());
674    }
675
676    #[test]
677    fn test_get_readonly_node_tag() {
678        let xml = r#"<ns:root xmlns:ns="http://example.com"/>"#;
679        let doc = parse(xml).unwrap();
680        let root = get_root_readonly_node(&doc).unwrap();
681
682        assert_eq!(get_readonly_node_tag(&root), "ns:root");
683    }
684
685    #[test]
686    fn test_get_node_prefix() {
687        let xml = r#"<ns:root xmlns:ns="http://example.com"/>"#;
688        let doc = parse(xml).unwrap();
689        let root = get_root_node(&doc).unwrap();
690
691        assert_eq!(get_node_prefix(&root), "ns");
692    }
693
694    #[test]
695    fn test_get_node_prefix_empty() {
696        let xml = r#"<root/>"#;
697        let doc = parse(xml).unwrap();
698        let root = get_root_node(&doc).unwrap();
699
700        assert_eq!(get_node_prefix(&root), "");
701    }
702
703    #[test]
704    fn test_get_readonly_node_prefix() {
705        let xml = r#"<ns:root xmlns:ns="http://example.com"/>"#;
706        let doc = parse(xml).unwrap();
707        let root = get_root_readonly_node(&doc).unwrap();
708
709        assert_eq!(get_readonly_node_prefix(&root), "ns");
710    }
711
712    #[test]
713    fn test_get_readonly_node_prefix_empty() {
714        let xml = r#"<root/>"#;
715        let doc = parse(xml).unwrap();
716        let root = get_root_readonly_node(&doc).unwrap();
717
718        assert_eq!(get_readonly_node_prefix(&root), "");
719    }
720
721    #[test]
722    fn test_readonly_node_to_xml_string() {
723        let xml = r#"<root><child>text</child></root>"#;
724        let doc = parse(xml).unwrap();
725        let root = get_root_readonly_node(&doc).unwrap();
726
727        let serialized = readonly_node_to_xml_string(&doc, &root).unwrap();
728        assert!(serialized.contains("<root>"));
729        assert!(serialized.contains("<child>text</child>"));
730    }
731
732    #[test]
733    fn test_evaluate_with_string_ref() {
734        let xml = r#"<root><a>1</a></root>"#;
735        let doc = parse(xml).unwrap();
736        let xpath_str = String::from("//a");
737
738        let result = evaluate(&doc, &xpath_str).unwrap();
739        let nodes = result.into_nodes();
740        assert_eq!(nodes.len(), 1);
741    }
742
743    #[test]
744    fn test_parse_xsd_basic() {
745        let xsd = br#"<?xml version="1.0"?>
746<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
747    <xs:element name="root" type="xs:string"/>
748</xs:schema>"#;
749
750        let schema = parse_xsd(xsd).unwrap();
751        assert!(!schema.elements.is_empty() || !schema.types.is_empty());
752    }
753
754    #[test]
755    fn test_create_xml_schema_validation_context_from_buffer() {
756        let xsd = br#"<?xml version="1.0"?>
757<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
758    <xs:element name="root" type="xs:string"/>
759</xs:schema>"#;
760
761        let ctx = create_xml_schema_validation_context_from_buffer(xsd).unwrap();
762        // Just verify it creates successfully
763        let _ = ctx;
764    }
765}