1#![allow(clippy::wrong_self_convention)]
2pub mod encoding;
3use core::str;
4use std::ops::Deref;
5
6use quick_xml::{
7 escape::resolve_predefined_entity,
8 events::{BytesPI, BytesText},
9 name::QName,
10};
11use tracing::debug;
12
13use crate::EditXMLError;
14#[cfg(not(feature = "ahash"))]
15pub type HashMap<K, V> = std::collections::HashMap<K, V>;
16#[cfg(feature = "ahash")]
17pub type HashMap<K, V> = ahash::AHashMap<K, V>;
18
19pub trait XMLStringUtils {
21 fn escape_ascii_into_string(&self) -> Result<String, EditXMLError>;
23 fn into_string(&self) -> Result<String, EditXMLError>;
25 fn unescape_to_string(&self) -> Result<String, EditXMLError> {
27 let value = self.into_string()?;
28 debug!("Unescaping: {}", value);
29 let unescape =
30 crate::utils::encoding::unescape_with(value.as_str(), resolve_predefined_entity)?;
31 debug!("Unescaped: {}", unescape);
32 Ok(unescape.into_owned())
33 }
34}
35
36impl XMLStringUtils for BytesText<'_> {
37 fn escape_ascii_into_string(&self) -> Result<String, EditXMLError> {
38 Ok(self.escape_ascii().to_string())
39 }
40
41 fn into_string(&self) -> Result<String, EditXMLError> {
42 String::from_utf8(self.to_vec()).map_err(EditXMLError::from)
43 }
44 fn unescape_to_string(&self) -> Result<String, EditXMLError> {
45 bytes_to_unescaped_string(self.deref())
46 }
47}
48impl XMLStringUtils for QName<'_> {
49 fn escape_ascii_into_string(&self) -> Result<String, EditXMLError> {
50 self.into_string()
51 }
52
53 fn into_string(&self) -> Result<String, EditXMLError> {
54 String::from_utf8(self.0.to_vec()).map_err(EditXMLError::from)
55 }
56
57 fn unescape_to_string(&self) -> Result<String, EditXMLError> {
58 bytes_to_unescaped_string(self.0)
59 }
60}
61impl XMLStringUtils for BytesPI<'_> {
62 fn escape_ascii_into_string(&self) -> Result<String, EditXMLError> {
63 self.into_string()
64 }
65
66 fn into_string(&self) -> Result<String, EditXMLError> {
67 Ok(String::from_utf8(self.to_vec())?)
68 }
69
70 fn unescape_to_string(&self) -> Result<String, EditXMLError> {
71 bytes_to_unescaped_string(self.content())
72 }
73}
74pub(crate) fn bytes_to_unescaped_string(cow: &[u8]) -> Result<String, EditXMLError> {
75 let value = str::from_utf8(cow).map_err(EditXMLError::from)?;
76
77 let unescape = crate::utils::encoding::unescape_with(value, resolve_predefined_entity)?;
78 Ok(unescape.into_owned())
79}
80#[cfg(test)]
81pub mod tests {
82 use std::path::PathBuf;
83
84 use std::sync::Once;
85 use tracing::{debug, info};
86 use tracing_subscriber::fmt;
87 use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
88 pub fn setup_logger() {
89 static INIT: Once = Once::new();
90 INIT.call_once(|| {
91 let stdout_log = fmt::layer().pretty();
92 tracing_subscriber::registry().with(stdout_log).init();
93 });
94 info!("Logger initialized");
95 debug!("Logger initialized");
96 }
97 pub fn test_dir() -> std::path::PathBuf {
98 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests")
99 }
100 pub fn documents_dir() -> std::path::PathBuf {
101 test_dir().join("documents")
102 }
103}