minidom: forcing a namespace on Element. Stop requiring prefixes.
Below is what I think I did.
A few changes:
- Change prefixes to be something less important in the API.
- Rework the Element struct to force a namespace. In XMPP everything is
namespaced.
- Remove parent ref on what was previously NamespaceSet and is now
Prefixes.
More specifically this means `Element::new` has changed to require
`Element`'s new new properties as parameters. `Element::builder` and
`Element::bare` now require a namespace unconditionally.
`Element::prefix` has been removed.
This new API is based on the fact that prefixes are non-essential
(really just an implementation detail) and shouldn't be visible to the
user. It is possible nonetheless to set custom prefixes for
compatibility reasons with `ElementBuilder::prefix`. **A prefix is
firstly mapped to a namespace, and then attached to an element**, there
cannot be a prefix without a namespace.
Prefix inheritance is used if possible but for the case with no
prefix ("xmlns") to be reused, we only check custom prefixes declared on
the tag itself and not ascendants. If it's already used then we generate
prefixes (ns0, ns1, ..) checking on what has been declared on all
ascendants (plus of course those already set on the current tag).
Example API:
```rust
let mut elem = ElementBuilder("stream", "http://etherx.jabber.org/streams")
.prefix(Some(String::from("stream")), "http://etherx.jabber.org/streams)
.prefix(None, "jabber:client")
.attr(..)
.build();
assert_eq!(elem.ns(), String::from("http://etherx.jabber.org/streams"));
```
See also the few tests added in src/tests.
TODO: Fix inconsistencies wrt. "prefix:name" format provided as a name
when creating an Element with `Element::new` or `Element::bare`.
`Element::builder` already handles this as it should, splitting name and
prefix.
TODO: Change `Element::name` method to `Element::local_name` to make it
more explicit.
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
parent
0b680a18e5
commit
f151306fbe
9 changed files with 709 additions and 379 deletions
184
minidom-rs/src/parser.rs
Normal file
184
minidom-rs/src/parser.rs
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
// Copyright (c) 2020 Maxime “pep” Buquet <pep@bouah.net>
|
||||
// Copyright (c) 2020 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! Provides a `Parser` type, which takes bytes and returns Elements. It also keeps a hold of
|
||||
//! ascendant elements to be able to handle namespaces properly.
|
||||
|
||||
use crate::element::Element;
|
||||
use crate::error::{Error, ParserError, Result};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use quick_xml::Reader as EventReader;
|
||||
use std::cell::RefCell;
|
||||
use std::str;
|
||||
|
||||
/// Parser
|
||||
#[derive(Debug)]
|
||||
pub struct Parser {
|
||||
buffer: RefCell<BytesMut>,
|
||||
state: ParserState,
|
||||
}
|
||||
|
||||
/// Describes the state of the parser.
|
||||
///
|
||||
/// This parser will only accept one-level documents. The root element is kept for convenience, to
|
||||
/// be able to pass namespaces down to children who are themselves children.
|
||||
#[derive(Debug)]
|
||||
pub enum ParserState {
|
||||
/// Not enough data has been processed to find the first element.
|
||||
Empty,
|
||||
|
||||
/// The normal state. the root element has been identified and children are processed.
|
||||
Root {
|
||||
/// Root element. Kept for future reference
|
||||
root: Element,
|
||||
|
||||
/// Child element
|
||||
child: Option<Element>,
|
||||
|
||||
/// XXX: Weird flag to say if we've already sent what we could send or if there's more to
|
||||
/// send. This Variant needs to be changed.
|
||||
sent: bool,
|
||||
},
|
||||
|
||||
/// Something was passed in the buffer that made the parser get into an error state.
|
||||
Error,
|
||||
|
||||
/// The root element has been closed. No feed-ing can happen past this point.
|
||||
Closed,
|
||||
}
|
||||
|
||||
/// Result of polling the parser
|
||||
#[derive(Debug)]
|
||||
pub enum ParserResult {
|
||||
/// Buffer is not empty but needs more data
|
||||
Partial,
|
||||
|
||||
/// An Element has been generated from the buffer.
|
||||
Single(Element),
|
||||
}
|
||||
|
||||
/*
|
||||
/// Split <stream:stream> and parse it.
|
||||
fn split_stream_stream_stream_features(string: String) -> (Element, Element) {
|
||||
let mut stuff = string.splitn(2, '>');
|
||||
let stream_opening_str = stuff.next().unwrap().to_string() + "/>";
|
||||
let rest = stuff.next().unwrap().to_string();
|
||||
let stream_opening: Element = stream_opening_str.parse().unwrap();
|
||||
let rest: Element = rest.parse().unwrap();
|
||||
println!("opening: {}", String::from(&stream_opening));
|
||||
println!("features: {}", String::from(&rest));
|
||||
(stream_opening, rest)
|
||||
}
|
||||
*/
|
||||
|
||||
fn maybe_split_prolog(string: &str) -> &str {
|
||||
if string.starts_with("<?xml") {
|
||||
let mut stuff = string.splitn(2, '>');
|
||||
stuff.next();
|
||||
stuff.next().unwrap()
|
||||
} else {
|
||||
string
|
||||
}
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
/// Creates a new Parser
|
||||
pub fn new() -> Parser {
|
||||
Parser {
|
||||
buffer: RefCell::new(BytesMut::new()),
|
||||
state: ParserState::Empty,
|
||||
}
|
||||
}
|
||||
|
||||
/// Feed bytes to the parser.
|
||||
pub fn feed(&mut self, bytes: BytesMut) -> Result<()> {
|
||||
self.buffer.borrow_mut().unsplit(bytes);
|
||||
let state = match self.state {
|
||||
ParserState::Empty => {
|
||||
// TODO: Try splitting xml prolog and stream header
|
||||
let foo = self.buffer.borrow();
|
||||
let header = maybe_split_prolog(str::from_utf8(foo.as_ref())?);
|
||||
println!("FOO: header: {:?}", header);
|
||||
let mut reader = EventReader::from_str(header);
|
||||
let root = Element::from_reader(&mut reader);
|
||||
match root {
|
||||
Ok(root) => {
|
||||
println!("FOO: elem: {:?}", root);
|
||||
ParserState::Root {
|
||||
root,
|
||||
child: None,
|
||||
sent: false,
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("FOO: err: {:?}", e);
|
||||
ParserState::Empty
|
||||
}
|
||||
}
|
||||
}
|
||||
ParserState::Closed => return Err(Error::ParserError(ParserError::Closed)),
|
||||
_ => ParserState::Empty,
|
||||
};
|
||||
|
||||
self.state = state;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns Elements to the application.
|
||||
pub fn poll(&mut self) -> Result<Option<ParserResult>> {
|
||||
match &self.state {
|
||||
ParserState::Empty if self.buffer.borrow().len() != 0 => {
|
||||
Ok(Some(ParserResult::Partial))
|
||||
}
|
||||
ParserState::Empty | ParserState::Closed | ParserState::Error => Ok(None),
|
||||
ParserState::Root {
|
||||
root, child: None, ..
|
||||
} => Ok(Some(ParserResult::Single(root.clone()))),
|
||||
ParserState::Root {
|
||||
child: Some(child), ..
|
||||
} => Ok(Some(ParserResult::Single(child.clone()))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets the parser
|
||||
pub fn reset(&mut self) {
|
||||
*self = Parser::new();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bytes::{BufMut, BytesMut};
|
||||
|
||||
#[test]
|
||||
fn test_prolog() {
|
||||
let mut parser = Parser::new();
|
||||
let mut buf = BytesMut::new();
|
||||
buf.put(&b"<?xml version='1.0'?>"[..]);
|
||||
buf.put(&b"<stream:stream xmlns='jabber:client' xml:lang='en' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' to='foo.bar'>"[..]);
|
||||
match parser.feed(buf) {
|
||||
Ok(_) => (),
|
||||
_ => panic!(),
|
||||
}
|
||||
|
||||
let elem = Element::builder("stream:stream", "http://etherx.jabber.org/streams")
|
||||
.prefix_ns(None, "jabber:client")
|
||||
.attr("xml:lang", "en")
|
||||
.attr("version", "1.0")
|
||||
.attr("to", "foo.bar")
|
||||
.build();
|
||||
|
||||
println!("BAR: elem: {:?}", elem);
|
||||
|
||||
match parser.poll() {
|
||||
Ok(Some(ParserResult::Single(e))) => assert_eq!(e, elem),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue