Skip to content

Commit fee2576

Browse files
committed
Merge tag '0.8.19'
2 parents 15332bd + bfb185e commit fee2576

17 files changed

+119
-44
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "xml-rs"
3-
version = "0.8.18"
3+
version = "0.8.19"
44
authors = ["Vladimir Matveev <vmatveev@citrine.cc>"]
55
license = "MIT"
66
description = "An XML library in pure Rust"

examples/print_events.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::fs::File;
22
use std::io::BufReader;
33
use xml::common::Position;
4-
use xml::reader::*;
4+
use xml::reader::{ParserConfig, XmlEvent};
55

66
fn main() {
77
let file_path = std::env::args_os().nth(1).expect("Please specify a path to an XML file");

examples/rewrite.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//! See <https://lib.rs/crates/svg-hush> for a real-world example.
2+
3+
use xml::EmitterConfig;
4+
use std::fs::File;
5+
use std::io::BufReader;
6+
use std::path::Path;
7+
use xml::reader::{ParserConfig, Result};
8+
9+
fn main() -> Result<(), Box<dyn std::error::Error>> {
10+
let arg = std::env::args_os().nth(1);
11+
let file_path = Path::new(arg.as_deref().unwrap_or("tests/documents/sample_1.xml".as_ref()));
12+
let file = BufReader::new(File::open(file_path)
13+
.map_err(|e| format!("Can't open {}: {e}", file_path.display()))?);
14+
15+
let mut reader = ParserConfig::default()
16+
.ignore_root_level_whitespace(true)
17+
.ignore_comments(false)
18+
.cdata_to_characters(true)
19+
.coalesce_characters(true)
20+
.create_reader(file);
21+
22+
let stdout = std::io::stdout().lock();
23+
24+
let mut writer = EmitterConfig::default()
25+
.create_writer(stdout);
26+
27+
loop {
28+
let reader_event = reader.next()?;
29+
30+
match reader_event {
31+
xml::reader::XmlEvent::EndDocument => break,
32+
xml::reader::XmlEvent::StartElement { name, mut attributes, namespace } => {
33+
let event = xml::writer::XmlEvent::StartElement {
34+
name: name.borrow(),
35+
namespace: namespace.borrow(),
36+
attributes: attributes.iter_mut().map(|attr| {
37+
attr.value = alternating_caps(&attr.value);
38+
attr.borrow()
39+
}).collect(),
40+
};
41+
writer.write(event)?;
42+
},
43+
xml::reader::XmlEvent::Characters(text) => {
44+
let text = alternating_caps(&text);
45+
let event = xml::writer::XmlEvent::Characters(&text);
46+
writer.write(event)?;
47+
},
48+
xml::reader::XmlEvent::Comment(text) => {
49+
let text = alternating_caps(&text);
50+
let event = xml::writer::XmlEvent::Comment(&text);
51+
writer.write(event)?;
52+
},
53+
other => {
54+
if let Some(writer_event) = other.as_writer_event() {
55+
writer.write(writer_event)?;
56+
}
57+
}
58+
}
59+
60+
}
61+
Ok(())
62+
}
63+
64+
fn alternating_caps(text: &str) -> String {
65+
text.chars().enumerate()
66+
.map(|(i, ch)| if i&1==0 { ch.to_ascii_uppercase() } else { ch.to_ascii_lowercase() })
67+
.collect()
68+
}

src/attribute.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
44
use std::fmt;
55

6-
use crate::escape::{Escaped, AttributeEscapes};
6+
use crate::escape::{AttributeEscapes, Escaped};
77
use crate::name::{Name, OwnedName};
88

99
/// A borrowed version of an XML attribute.

src/common.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,15 +112,15 @@ pub fn is_whitespace_str(s: &str) -> bool {
112112
s.chars().all(is_whitespace_char)
113113
}
114114

115-
pub fn is_xml10_char(c: char) -> bool {
115+
#[must_use] pub fn is_xml10_char(c: char) -> bool {
116116
matches!(c, '\u{09}' | '\u{0A}' | '\u{0D}' | '\u{20}'..='\u{D7FF}' | '\u{E000}'..='\u{FFFD}' | '\u{10000}'..)
117117
}
118118

119-
pub fn is_xml11_char(c: char) -> bool {
119+
#[must_use] pub fn is_xml11_char(c: char) -> bool {
120120
matches!(c, '\u{01}'..='\u{D7FF}' | '\u{E000}'..='\u{FFFD}' | '\u{10000}'..)
121121
}
122122

123-
pub fn is_xml11_char_not_restricted(c: char) -> bool {
123+
#[must_use] pub fn is_xml11_char_not_restricted(c: char) -> bool {
124124
is_xml11_char(c) && !matches!(c, '\u{01}'..='\u{08}' | '\u{0B}'..='\u{0C}' | '\u{0E}'..='\u{1F}' | '\u{7F}'..='\u{84}' | '\u{86}'..='\u{9F}')
125125
}
126126

src/escape.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ use std::{borrow::Cow, marker::PhantomData, fmt::{Display, Result, Formatter}};
55
pub(crate) trait Escapes {
66
fn escape(c: u8) -> Option<&'static str>;
77

8-
fn byte_needs_escaping(c: u8) -> bool{
8+
fn byte_needs_escaping(c: u8) -> bool {
99
Self::escape(c).is_some()
1010
}
1111

12-
fn str_needs_escaping(s: &str) -> bool{
12+
fn str_needs_escaping(s: &str) -> bool {
1313
s.bytes().any(|c| Self::escape(c).is_some())
1414
}
1515
}
@@ -22,13 +22,12 @@ pub(crate) struct Escaped<'a, E: Escapes> {
2222
impl<'a, E: Escapes> Escaped<'a, E> {
2323
pub fn new(s: &'a str) -> Self {
2424
Escaped {
25-
_escape_phantom: PhantomData,
25+
_escape_phantom: PhantomData,
2626
to_escape: s,
2727
}
2828
}
2929
}
3030

31-
3231
impl<'a, E: Escapes> Display for Escaped<'a, E> {
3332
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
3433
let mut total_remaining = self.to_escape;
@@ -49,7 +48,7 @@ impl<'a, E: Escapes> Display for Escaped<'a, E> {
4948

5049
total_remaining = &remaining[1..];
5150
}
52-
51+
5352
f.write_str(total_remaining)
5453
}
5554
}
@@ -107,7 +106,7 @@ escapes!(
107106
/// * `"` → `&quot;`
108107
/// * `'` → `&apos;`
109108
/// * `&` → `&amp;`
110-
///
109+
///
111110
/// The following characters are escaped so that attributes are printed on
112111
/// a single line:
113112
/// * `\n` → `&#xA;`
@@ -117,7 +116,8 @@ escapes!(
117116
///
118117
/// Does not perform allocations if the given string does not contain escapable characters.
119118
#[inline]
120-
#[must_use] pub fn escape_str_attribute(s: &str) -> Cow<'_, str> {
119+
#[must_use]
120+
pub fn escape_str_attribute(s: &str) -> Cow<'_, str> {
121121
escape_str::<AttributeEscapes>(s)
122122
}
123123

@@ -133,7 +133,8 @@ escapes!(
133133
///
134134
/// Does not perform allocations if the given string does not contain escapable characters.
135135
#[inline]
136-
#[must_use] pub fn escape_str_pcdata(s: &str) -> Cow<'_, str> {
136+
#[must_use]
137+
pub fn escape_str_pcdata(s: &str) -> Cow<'_, str> {
137138
escape_str::<PcDataEscapes>(s)
138139
}
139140

src/macros.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ macro_rules! gen_setter {
1919
///
2020
/// <small>See [`ParserConfig`][crate::ParserConfig] fields docs for details</small>
2121
#[inline]
22-
pub fn $field(mut self, value: $t) -> Self {
22+
#[must_use] pub fn $field(mut self, value: $t) -> Self {
2323
self.$field = value;
2424
self
2525
}
@@ -29,7 +29,7 @@ macro_rules! gen_setter {
2929
///
3030
/// <small>See [`ParserConfig`][crate::ParserConfig] fields docs for details</small>
3131
#[inline]
32-
pub fn $field(mut self, value: $t) -> Self {
32+
#[must_use] pub fn $field(mut self, value: $t) -> Self {
3333
self.c.$field = value;
3434
self
3535
}

src/namespace.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Contains namespace manipulation types and functions.
22
3+
use std::borrow::Cow;
34
use std::collections::btree_map::Iter as Entries;
45
use std::collections::btree_map::{BTreeMap, Entry};
56
use std::collections::HashSet;
@@ -165,6 +166,12 @@ impl Namespace {
165166
pub fn get<'a, P: ?Sized + AsRef<str>>(&'a self, prefix: &P) -> Option<&'a str> {
166167
self.0.get(prefix.as_ref()).map(|s| &**s)
167168
}
169+
170+
/// Borrowed namespace for the writer
171+
#[must_use]
172+
pub fn borrow(&self) -> Cow<'_, Self> {
173+
Cow::Borrowed(self)
174+
}
168175
}
169176

170177
/// An alias for iterator type for namespace mappings contained in a namespace.

src/reader/events.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
//! Contains `XmlEvent` datatype, instances of which are emitted by the parser.
22
3-
use std::borrow::Cow;
43
use std::fmt;
5-
64
use crate::attribute::OwnedAttribute;
75
use crate::common::XmlVersion;
86
use crate::name::OwnedName;
@@ -207,7 +205,7 @@ impl XmlEvent {
207205
Some(crate::writer::events::XmlEvent::StartElement {
208206
name: name.borrow(),
209207
attributes: attributes.iter().map(|a| a.borrow()).collect(),
210-
namespace: Cow::Borrowed(namespace)
208+
namespace: namespace.borrow(),
211209
}),
212210
XmlEvent::EndElement { ref name } =>
213211
Some(crate::writer::events::XmlEvent::EndElement { name: Some(name.borrow()) }),

src/reader/indexset.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,13 @@ fn indexset() {
8484
}
8585

8686
assert!(s.contains(&OwnedName {
87-
local_name: format!("attr1234"), namespace: None, prefix: None,
87+
local_name: "attr1234".into(), namespace: None, prefix: None,
8888
}));
8989
assert!(s.contains(&OwnedName {
90-
local_name: format!("attr0"), namespace: None, prefix: None,
90+
local_name: "attr0".into(), namespace: None, prefix: None,
9191
}));
9292
assert!(s.contains(&OwnedName {
93-
local_name: format!("attr49999"), namespace: None, prefix: None,
93+
local_name: "attr49999".into(), namespace: None, prefix: None,
9494
}));
9595
}
9696

@@ -100,7 +100,7 @@ struct U64Hasher(u64);
100100
impl Hasher for U64Hasher {
101101
fn finish(&self) -> u64 { self.0 }
102102
fn write(&mut self, slice: &[u8]) {
103-
for &v in slice { self.0 ^= v as u64 } // unused in practice
103+
for &v in slice { self.0 ^= u64::from(v) } // unused in practice
104104
}
105105
fn write_u64(&mut self, i: u64) {
106106
self.0 ^= i;

src/reader/parser.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ impl PullParser {
396396
fn next_pos(&mut self) {
397397
// unfortunately calls to next_pos will never be perfectly balanced with push_pos,
398398
// at very least because parse errors and EOF can happen unexpectedly without a prior push.
399-
if self.pos.len() > 0 {
399+
if !self.pos.is_empty() {
400400
if self.pos.len() > 1 {
401401
self.pos.remove(0);
402402
} else {
@@ -485,7 +485,7 @@ impl PullParser {
485485
let name = this.take_buf();
486486
match name.parse() {
487487
Ok(name) => on_name(this, t, name),
488-
Err(_) => Some(this.error(SyntaxError::InvalidQualifiedName(name.into())))
488+
Err(_) => Some(this.error(SyntaxError::InvalidQualifiedName(name.into()))),
489489
}
490490
};
491491

@@ -515,7 +515,7 @@ impl PullParser {
515515

516516
Token::Character(c) if is_whitespace_char(c) => invoke_callback(self, t),
517517

518-
_ => Some(self.error(SyntaxError::UnexpectedQualifiedName(t)))
518+
_ => Some(self.error(SyntaxError::UnexpectedQualifiedName(t))),
519519
}
520520
}
521521

@@ -527,7 +527,7 @@ impl PullParser {
527527
fn read_attribute_value<F>(&mut self, t: Token, on_value: F) -> Option<Result>
528528
where F: Fn(&mut PullParser, String) -> Option<Result> {
529529
match t {
530-
Token::Character(c) if self.data.quote.is_none() && is_whitespace_char(c) => None, // skip leading whitespace
530+
Token::Character(c) if self.data.quote.is_none() && is_whitespace_char(c) => None, // skip leading whitespace
531531

532532
Token::DoubleQuote | Token::SingleQuote => match self.data.quote {
533533
None => { // Entered attribute value
@@ -558,8 +558,7 @@ impl PullParser {
558558
self.into_state_continue(State::InsideReference)
559559
},
560560

561-
Token::OpeningTagStart =>
562-
Some(self.error(SyntaxError::UnexpectedOpeningTag)),
561+
Token::OpeningTagStart => Some(self.error(SyntaxError::UnexpectedOpeningTag)),
563562

564563
Token::Character(c) if !self.is_valid_xml_char_not_restricted(c) => {
565564
Some(self.error(SyntaxError::InvalidCharacterEntity(c as u32)))
@@ -584,7 +583,7 @@ impl PullParser {
584583

585584
// check whether the name prefix is bound and fix its namespace
586585
match self.nst.get(name.borrow().prefix_repr()) {
587-
Some("") => name.namespace = None, // default namespace
586+
Some("") => name.namespace = None, // default namespace
588587
Some(ns) => name.namespace = Some(ns.into()),
589588
None => return Some(self.error(SyntaxError::UnboundElementPrefix(name.to_string().into())))
590589
}

src/reader/parser/inside_doctype.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ impl PullParser {
3131
_ => None,
3232
},
3333
DoctypeSubstate::String => match t {
34-
Token::SingleQuote if self.data.quote != Some(QuoteToken::SingleQuoteToken) => { None },
35-
Token::DoubleQuote if self.data.quote != Some(QuoteToken::DoubleQuoteToken) => { None },
34+
Token::SingleQuote if self.data.quote != Some(QuoteToken::SingleQuoteToken) => None,
35+
Token::DoubleQuote if self.data.quote != Some(QuoteToken::DoubleQuoteToken) => None,
3636
Token::SingleQuote | Token::DoubleQuote => {
3737
self.data.quote = None;
3838
self.into_state_continue(State::InsideDoctype(DoctypeSubstate::Outside))
@@ -51,12 +51,12 @@ impl PullParser {
5151
None
5252
},
5353
Token::Character(c) if is_whitespace_char(c) => {
54-
match self.buf.as_str() {
54+
let buf = self.take_buf();
55+
match buf.as_str() {
5556
"ENTITY" => self.into_state_continue(State::InsideDoctype(DoctypeSubstate::BeforeEntityName)),
5657
"NOTATION" | "ELEMENT" | "ATTLIST" => self.into_state_continue(State::InsideDoctype(DoctypeSubstate::SkipDeclaration)),
57-
s => Some(self.error(SyntaxError::UnknownMarkupDeclaration(s.into()))),
58+
_ => Some(self.error(SyntaxError::UnknownMarkupDeclaration(buf.into()))),
5859
}
59-
6060
},
6161
_ => Some(self.error(SyntaxError::UnexpectedToken(t))),
6262
},

src/reader/parser/inside_opening_tag.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ impl PullParser {
3131
OpeningTagSubstate::InsideTag => match t {
3232
Token::TagEnd => self.emit_start_element(false),
3333
Token::EmptyTagEnd => self.emit_start_element(true),
34-
Token::Character(c) if is_whitespace_char(c) => None, // skip whitespace
34+
Token::Character(c) if is_whitespace_char(c) => None, // skip whitespace
3535
Token::Character(c) if is_name_start_char(c) => {
3636
if self.buf.len() > self.config.max_name_length {
3737
return Some(self.error(SyntaxError::ExceededConfiguredLimit));
3838
}
3939
self.buf.push(c);
4040
self.into_state_continue(State::InsideOpeningTag(OpeningTagSubstate::InsideAttributeName))
4141
}
42-
_ => Some(self.error(SyntaxError::UnexpectedTokenInOpeningTag(t)))
42+
_ => Some(self.error(SyntaxError::UnexpectedTokenInOpeningTag(t))),
4343
},
4444

4545
OpeningTagSubstate::InsideAttributeName => self.read_qualified_name(t, QualifiedNameTarget::AttributeNameTarget, |this, token, name| {
@@ -108,10 +108,12 @@ impl PullParser {
108108
}),
109109

110110
OpeningTagSubstate::AfterAttributeValue => match t {
111-
Token::Character(c) if is_whitespace_char(c) => self.into_state_continue(State::InsideOpeningTag(OpeningTagSubstate::InsideTag)),
111+
Token::Character(c) if is_whitespace_char(c) => {
112+
self.into_state_continue(State::InsideOpeningTag(OpeningTagSubstate::InsideTag))
113+
},
112114
Token::TagEnd => self.emit_start_element(false),
113115
Token::EmptyTagEnd => self.emit_start_element(true),
114-
_ => Some(self.error(SyntaxError::UnexpectedTokenInOpeningTag(t)))
116+
_ => Some(self.error(SyntaxError::UnexpectedTokenInOpeningTag(t))),
115117
},
116118
}
117119
}

src/reader/parser/outside_tag.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ impl PullParser {
9393
if self.inside_whitespace && self.config.c.trim_whitespace {
9494
None
9595
} else if self.inside_whitespace && !self.config.c.whitespace_to_characters {
96+
debug_assert!(buf.chars().all(|ch| ch.is_whitespace()), "ws={buf:?}");
9697
Some(Ok(XmlEvent::Whitespace(buf)))
9798
} else if self.config.c.trim_whitespace {
9899
Some(Ok(XmlEvent::Characters(buf.trim_matches(is_whitespace_char).into())))
@@ -174,7 +175,7 @@ impl PullParser {
174175
self.into_state(State::OutsideTag, next_event)
175176
},
176177

177-
Token::CommentStart => {
178+
Token::CommentStart => {
178179
let next_event = self.set_encountered(Encountered::Comment);
179180
self.into_state(State::InsideComment, next_event)
180181
}

0 commit comments

Comments
 (0)