1#![warn(missing_docs)]
2use core::{fmt, str};
18use fmt::Display;
19use std::{borrow::Cow, str::FromStr};
20
21pub mod de;
22pub use de::{DeserializationGroup, Deserialize, DeserializeOwned, Deserializer};
23pub mod ser;
24pub use ser::{AttributeSerializer, SerializationGroup, Serialize, SerializeAttribute, Serializer};
25mod macros;
26pub mod types;
27pub mod value;
28pub use value::XmlValue;
29mod noop;
30pub use noop::NoopDeSerializer;
31
32#[cfg(feature = "derive")]
33extern crate xmlity_derive;
34
35#[cfg(feature = "derive")]
36pub use xmlity_derive::{
37 DeserializationGroup, Deserialize, SerializationGroup, Serialize, SerializeAttribute,
38};
39
40mod name_tokens {
48 const fn is_name_start_char(c: char) -> bool {
49 matches!(
50 c, 'A'..='Z' | '_' | 'a'..='z' | '\u{00C0}'..='\u{00D6}' | '\u{00D8}'..='\u{00F6}' | '\u{00F8}'..='\u{02FF}' | '\u{0370}'..='\u{037D}' | '\u{037F}'..='\u{1FFF}' | '\u{200C}'..='\u{200D}' | '\u{2070}'..='\u{218F}' | '\u{2C00}'..='\u{2FEF}' | '\u{3001}'..='\u{D7FF}' | '\u{F900}'..='\u{FDCF}' | '\u{FDF0}'..='\u{FFFD}' | '\u{10000}'..='\u{EFFFF}'
52 )
53 }
54 const fn is_name_char(c: char) -> bool {
55 is_name_start_char(c)
56 || matches!(c, '-' | '.' | '0'..='9' | '\u{00B7}' | '\u{0300}'..='\u{036F}' | '\u{203F}'..='\u{2040}')
57 }
58
59 #[derive(Debug, Clone, thiserror::Error, PartialEq, Eq, PartialOrd, Ord, Hash)]
61 #[non_exhaustive]
62 pub enum InvalidXmlNameError {
63 #[error("Invalid start character")]
65 InvalidStartChar,
66 #[error("Invalid character at index {index}")]
68 InvalidChar {
69 index: usize,
71 character: char,
73 },
74 #[error("Empty name")]
76 Empty,
77 }
78
79 pub fn is_valid_name(name: &str) -> Result<(), InvalidXmlNameError> {
80 let mut chars = name.chars();
81 if let Some(c) = chars.next() {
82 if !is_name_start_char(c) {
83 return Err(InvalidXmlNameError::InvalidStartChar);
84 }
85 } else {
86 return Err(InvalidXmlNameError::Empty);
87 }
88 for (index, character) in chars.enumerate().map(|(i, c)| (i + 1, c)) {
89 if !is_name_char(character) {
90 return Err(InvalidXmlNameError::InvalidChar { index, character });
91 }
92 }
93
94 Ok(())
95 }
96}
97
98pub use name_tokens::InvalidXmlNameError;
99
100#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
103pub struct ExpandedName<'a> {
104 local_name: LocalName<'a>,
105 namespace: Option<XmlNamespace<'a>>,
106}
107
108impl<'a> ExpandedName<'a> {
109 pub fn new(local_name: LocalName<'a>, namespace: Option<XmlNamespace<'a>>) -> Self {
111 Self {
112 local_name,
113 namespace,
114 }
115 }
116
117 pub fn into_parts(self) -> (LocalName<'a>, Option<XmlNamespace<'a>>) {
119 (self.local_name, self.namespace)
120 }
121
122 pub fn into_owned(self) -> ExpandedName<'static> {
124 ExpandedName::new(
125 self.local_name.into_owned(),
126 self.namespace.map(|n| n.into_owned()),
127 )
128 }
129
130 pub fn as_ref(&self) -> ExpandedName<'_> {
132 ExpandedName::new(
133 self.local_name.as_ref(),
134 self.namespace.as_ref().map(|n| n.as_ref()),
135 )
136 }
137
138 pub fn local_name(&self) -> &LocalName<'_> {
140 &self.local_name
141 }
142
143 pub fn namespace(&self) -> Option<&XmlNamespace<'_>> {
145 self.namespace.as_ref()
146 }
147
148 pub fn to_q_name(self, resolved_prefix: Option<Prefix<'a>>) -> QName<'a> {
150 QName::new(resolved_prefix, self.local_name.clone())
151 }
152}
153
154impl Display for ExpandedName<'_> {
155 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156 self.local_name.fmt(f)
157 }
158}
159
160#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
163pub struct QName<'a> {
164 prefix: Option<Prefix<'a>>,
165 local_name: LocalName<'a>,
166}
167
168#[derive(Debug, thiserror::Error, PartialEq, Eq)]
170pub enum QNameParseError {
171 #[error("Invalid prefix: {0}")]
173 InvalidPrefix(#[from] PrefixParseError),
174 #[error("Invalid local name: {0}")]
176 InvalidLocalName(#[from] LocalNameParseError),
177}
178
179impl<'a> QName<'a> {
180 pub fn new<P: Into<Option<Prefix<'a>>>, L: Into<LocalName<'a>>>(
182 prefix: P,
183 local_name: L,
184 ) -> Self {
185 QName {
186 prefix: prefix.into(),
187 local_name: local_name.into(),
188 }
189 }
190
191 pub fn into_parts(self) -> (Option<Prefix<'a>>, LocalName<'a>) {
193 (self.prefix, self.local_name)
194 }
195
196 pub fn into_owned(self) -> QName<'static> {
198 QName {
199 prefix: self.prefix.map(|prefix| prefix.into_owned()),
200 local_name: self.local_name.into_owned(),
201 }
202 }
203
204 pub fn as_ref(&self) -> QName<'_> {
206 QName {
207 prefix: self.prefix.as_ref().map(|prefix| prefix.as_ref()),
208 local_name: self.local_name.as_ref(),
209 }
210 }
211
212 pub fn prefix(&self) -> Option<&Prefix<'a>> {
214 self.prefix.as_ref()
215 }
216
217 pub fn local_name(&self) -> &LocalName<'a> {
219 &self.local_name
220 }
221}
222
223impl Display for QName<'_> {
224 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225 if let Some(prefix) = &self.prefix.as_ref().filter(|prefix| !prefix.is_default()) {
226 write!(f, "{}:{}", prefix, self.local_name)
227 } else {
228 write!(f, "{}", self.local_name)
229 }
230 }
231}
232impl FromStr for QName<'_> {
233 type Err = QNameParseError;
234 fn from_str(s: &str) -> Result<Self, Self::Err> {
235 let (prefix, local_name) = s.split_once(':').unwrap_or(("", s));
236
237 let prefix = Prefix::from_str(prefix)?;
238 let local_name = LocalName::from_str(local_name)?;
239
240 Ok(QName::new(prefix, local_name))
241 }
242}
243
244impl<'a> From<QName<'a>> for Option<Prefix<'a>> {
245 fn from(value: QName<'a>) -> Self {
246 value.prefix
247 }
248}
249
250impl<'a> From<QName<'a>> for LocalName<'a> {
251 fn from(value: QName<'a>) -> Self {
252 value.local_name
253 }
254}
255
256#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
259pub struct XmlNamespace<'a>(Cow<'a, str>);
260
261#[derive(Debug, thiserror::Error)]
263pub enum XmlNamespaceParseError {}
264
265impl<'a> XmlNamespace<'a> {
266 pub fn new<T: Into<Cow<'a, str>>>(value: T) -> Result<Self, XmlNamespaceParseError> {
268 Ok(Self(value.into()))
269 }
270
271 pub const fn new_dangerous(value: &'a str) -> Self {
276 Self(Cow::Borrowed(value))
277 }
278
279 pub fn into_owned(self) -> XmlNamespace<'static> {
281 XmlNamespace(Cow::Owned(self.0.into_owned()))
282 }
283
284 pub fn as_ref(&self) -> XmlNamespace<'_> {
286 XmlNamespace(Cow::Borrowed(&self.0))
287 }
288
289 pub fn as_str(&self) -> &str {
291 &self.0
292 }
293
294 pub const XMLNS: XmlNamespace<'static> =
296 XmlNamespace::new_dangerous("http://www.w3.org/2000/xmlns/");
297 pub const XML: XmlNamespace<'static> =
299 XmlNamespace::new_dangerous("http://www.w3.org/XML/1998/namespace");
300 pub const XHTML: XmlNamespace<'static> =
302 XmlNamespace::new_dangerous("http://www.w3.org/1999/xhtml");
303 pub const XS: XmlNamespace<'static> =
305 XmlNamespace::new_dangerous("http://www.w3.org/2001/XMLSchema");
306 pub const XSI: XmlNamespace<'static> =
308 XmlNamespace::new_dangerous("http://www.w3.org/2001/XMLSchema-instance");
309}
310
311impl FromStr for XmlNamespace<'_> {
312 type Err = XmlNamespaceParseError;
313
314 fn from_str(value: &str) -> Result<Self, Self::Err> {
315 Self::new(value.to_owned())
316 }
317}
318
319impl Display for XmlNamespace<'_> {
320 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321 self.0.fmt(f)
322 }
323}
324
325impl Serialize for XmlNamespace<'_> {
326 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
327 where
328 S: Serializer,
329 {
330 serializer.serialize_text(&self.0)
331 }
332}
333
334impl<'de> Deserialize<'de> for XmlNamespace<'_> {
335 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
336 where
337 D: Deserializer<'de>,
338 {
339 deserializer.deserialize_any(types::string::FromStrVisitor::default())
340 }
341}
342
343#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
346pub struct Prefix<'a>(Cow<'a, str>);
347
348#[derive(Debug, thiserror::Error, PartialEq, Eq)]
350pub enum PrefixParseError {
351 #[error("Prefix has an invalid XML name: {0}")]
353 InvalidXmlName(#[from] name_tokens::InvalidXmlNameError),
354}
355
356impl<'a> Prefix<'a> {
357 pub fn new<T: Into<Cow<'a, str>>>(value: T) -> Result<Self, PrefixParseError> {
359 let value = value.into();
360
361 name_tokens::is_valid_name(&value)?;
362
363 Ok(Self(value))
364 }
365
366 pub const fn new_dangerous(value: &'a str) -> Self {
371 Self(Cow::Borrowed(value))
372 }
373
374 pub fn into_owned(self) -> Prefix<'static> {
376 Prefix(Cow::Owned(self.0.into_owned()))
377 }
378
379 pub fn as_ref(&self) -> Prefix<'_> {
381 Prefix(Cow::Borrowed(&self.0))
382 }
383
384 pub fn as_str(&self) -> &str {
386 &self.0
387 }
388
389 pub fn xmlns(&'a self) -> QName<'a> {
391 QName::new(
392 Prefix::new("xmlns").expect("xmlns is a valid prefix"),
393 LocalName::from(self.clone()),
394 )
395 }
396
397 pub fn is_default(&self) -> bool {
399 self.0.is_empty()
400 }
401}
402
403impl<'a> From<Prefix<'a>> for LocalName<'a> {
404 fn from(value: Prefix<'a>) -> Self {
405 LocalName(value.0)
406 }
407}
408
409impl<'a> From<Option<Prefix<'a>>> for Prefix<'a> {
410 fn from(value: Option<Prefix<'a>>) -> Self {
411 value.unwrap_or_default()
412 }
413}
414
415impl FromStr for Prefix<'_> {
416 type Err = PrefixParseError;
417
418 fn from_str(value: &str) -> Result<Self, Self::Err> {
419 Self::new(value.to_owned())
420 }
421}
422
423impl Display for Prefix<'_> {
424 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425 self.0.fmt(f)
426 }
427}
428
429impl Serialize for Prefix<'_> {
430 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
431 where
432 S: Serializer,
433 {
434 serializer.serialize_text(&self.0)
435 }
436}
437
438impl<'de> Deserialize<'de> for Prefix<'_> {
439 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
440 where
441 D: Deserializer<'de>,
442 {
443 deserializer.deserialize_any(types::string::FromStrVisitor::default())
444 }
445}
446
447#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
452pub struct LocalName<'a>(Cow<'a, str>);
453
454#[derive(Debug, thiserror::Error, PartialEq, Eq)]
456pub enum LocalNameParseError {
457 #[error("Local name has an invalid XML name: {0}")]
459 InvalidXmlName(#[from] name_tokens::InvalidXmlNameError),
460}
461
462impl<'a> LocalName<'a> {
463 pub fn new<T: Into<Cow<'a, str>>>(value: T) -> Result<Self, LocalNameParseError> {
465 let value = value.into();
466
467 name_tokens::is_valid_name(&value)?;
468
469 Ok(Self(value))
470 }
471
472 pub const fn new_dangerous(value: &'a str) -> Self {
477 Self(Cow::Borrowed(value))
478 }
479
480 pub fn into_owned(self) -> LocalName<'static> {
482 LocalName(Cow::Owned(self.0.into_owned()))
483 }
484
485 pub fn as_ref(&self) -> LocalName<'_> {
487 LocalName(Cow::Borrowed(&self.0))
488 }
489}
490
491impl FromStr for LocalName<'_> {
492 type Err = LocalNameParseError;
493 fn from_str(s: &str) -> Result<Self, Self::Err> {
494 Self::new(s.to_owned())
495 }
496}
497
498impl Display for LocalName<'_> {
499 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
500 self.0.fmt(f)
501 }
502}
503
504impl Serialize for LocalName<'_> {
505 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
506 where
507 S: Serializer,
508 {
509 serializer.serialize_text(&self.0)
510 }
511}
512
513impl<'de> Deserialize<'de> for LocalName<'_> {
514 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
515 where
516 D: Deserializer<'de>,
517 {
518 deserializer.deserialize_any(types::string::FromStrVisitor::default())
519 }
520}
521
522#[cfg(test)]
523mod tests {
524 use rstest::rstest;
525
526 use super::*;
527 use std::str::FromStr;
528
529 #[rstest]
530 #[case::basic("prefix")]
531 fn test_prefix(#[case] prefix_text: &str) {
532 let prefix = Prefix::from_str(prefix_text).unwrap();
533 assert_eq!(prefix.to_string(), prefix_text);
534 assert_eq!(prefix.into_owned().to_string(), prefix_text);
535 }
536
537 #[rstest]
538 #[case::empty("", PrefixParseError::InvalidXmlName(InvalidXmlNameError::Empty))]
539 #[case::space("invalid prefix", PrefixParseError::InvalidXmlName(InvalidXmlNameError::InvalidChar { index: 7, character: ' ' }))]
540 fn invalid_prefix_invalid_characters(
541 #[case] prefix: &str,
542 #[case] expected_error: PrefixParseError,
543 ) {
544 let error = Prefix::from_str(prefix).unwrap_err();
545 assert_eq!(error, expected_error);
546 }
547
548 #[rstest]
549 #[case::basic("localName")]
550 fn test_local_name(#[case] local_name_text: &str) {
551 let local_name = LocalName::from_str(local_name_text).unwrap();
552 assert_eq!(local_name.to_string(), local_name_text);
553 assert_eq!(local_name.into_owned().to_string(), local_name_text);
554 }
555
556 #[rstest]
557 #[case::empty("", LocalNameParseError::InvalidXmlName(InvalidXmlNameError::Empty))]
558 #[case::space("invalid localName", LocalNameParseError::InvalidXmlName(InvalidXmlNameError::InvalidChar { index: 7, character: ' ' }))]
559 fn invalid_local_name_invalid_characters(
560 #[case] local_name: &str,
561 #[case] expected_error: LocalNameParseError,
562 ) {
563 let error = LocalName::from_str(local_name).unwrap_err();
564 assert_eq!(error, expected_error);
565 }
566
567 #[rstest]
568 #[case::basic("localName", None)]
569 #[case::with_namespace("localName", Some(XmlNamespace::new("http://example.com").unwrap()))]
570 fn test_expanded_name(#[case] local_name_text: &str, #[case] namespace: Option<XmlNamespace>) {
571 let local_name = LocalName::from_str(local_name_text).unwrap();
572 let expanded_name = ExpandedName::new(local_name.clone(), namespace.clone());
573 assert_eq!(expanded_name.local_name(), &local_name);
574 assert_eq!(expanded_name.namespace(), namespace.as_ref());
575 assert_eq!(expanded_name.to_string(), local_name_text);
576 assert_eq!(expanded_name.into_owned().to_string(), local_name_text);
577 }
578
579 #[rstest]
580 #[case::basic("prefix:localName")]
581 fn test_qname(#[case] qname_text: &str) {
582 let qname = QName::from_str(qname_text).unwrap();
583 assert_eq!(qname.to_string(), qname_text);
584 assert_eq!(qname.into_owned().to_string(), qname_text);
585 }
586
587 #[rstest]
588 #[case::invalid_local_name("prefix:invalid localName", QNameParseError::InvalidLocalName(LocalNameParseError::InvalidXmlName(InvalidXmlNameError::InvalidChar { index: 7, character: ' ' })))]
589 fn invalid_qname_invalid_characters(
590 #[case] qname: &str,
591 #[case] expected_error: QNameParseError,
592 ) {
593 let error = QName::from_str(qname).unwrap_err();
594 assert_eq!(error, expected_error);
595 }
596}