use std::char;
use std::str;
use std::ops::{Deref, DerefMut};
use {
ByteStream,
StreamError,
StrSpan,
XmlByteExt,
XmlCharExt,
};
type Result<T> = ::std::result::Result<T, StreamError>;
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum Reference<'a> {
Entity(&'a str),
Char(char),
}
#[derive(Clone, Copy, PartialEq)]
pub struct Stream<'a> {
d: ByteStream<'a>,
}
impl<'a> Deref for Stream<'a> {
type Target = ByteStream<'a>;
fn deref(&self) -> &Self::Target {
&self.d
}
}
impl<'a> DerefMut for Stream<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.d
}
}
impl<'a> From<ByteStream<'a>> for Stream<'a> {
fn from(d: ByteStream<'a>) -> Self {
Stream { d }
}
}
impl<'a> From<&'a str> for Stream<'a> {
fn from(text: &'a str) -> Self {
ByteStream::new(text.into()).into()
}
}
impl<'a> From<StrSpan<'a>> for Stream<'a> {
fn from(span: StrSpan<'a>) -> Self {
ByteStream::new(span).into()
}
}
impl<'a> Stream<'a> {
#[inline]
pub fn skip_spaces(&mut self) {
while !self.at_end() && self.curr_byte_unchecked().is_xml_space() {
self.advance(1);
}
}
#[inline]
pub fn starts_with_space(&self) -> bool {
!self.at_end() && self.curr_byte_unchecked().is_xml_space()
}
pub fn consume_spaces(&mut self) -> Result<()> {
if self.at_end() {
return Err(StreamError::UnexpectedEndOfStream);
}
if !self.starts_with_space() {
let c = self.curr_byte_unchecked() as char;
let pos = self.gen_text_pos();
return Err(StreamError::InvalidSpace(c, pos));
}
self.skip_spaces();
Ok(())
}
pub fn try_consume_reference(&mut self) -> Option<Reference<'a>> {
let start = self.pos();
let mut s = self.clone();
match s.consume_reference() {
Ok(r) => {
self.advance(s.pos() - start);
Some(r)
}
Err(_) => {
None
}
}
}
pub fn consume_reference(&mut self) -> Result<Reference<'a>> {
self._consume_reference().map_err(|_| StreamError::InvalidReference)
}
fn _consume_reference(&mut self) -> Result<Reference<'a>> {
if !self.try_consume_byte(b'&') {
return Err(StreamError::InvalidReference);
}
let reference = if self.try_consume_byte(b'#') {
let (value, radix) = if self.try_consume_byte(b'x') {
let value = self.consume_bytes(|_, c| c.is_xml_hex_digit()).as_str();
(value, 16)
} else {
let value = self.consume_bytes(|_, c| c.is_xml_digit()).as_str();
(value, 10)
};
let n = u32::from_str_radix(value, radix).map_err(|_| StreamError::InvalidReference)?;
let c = char::from_u32(n).unwrap_or('\u{FFFD}');
if !c.is_xml_char() {
return Err(StreamError::InvalidReference);
}
Reference::Char(c)
} else {
let name = self.consume_name()?;
match name.as_str() {
"quot" => Reference::Char('"'),
"amp" => Reference::Char('&'),
"apos" => Reference::Char('\''),
"lt" => Reference::Char('<'),
"gt" => Reference::Char('>'),
_ => Reference::Entity(name.as_str()),
}
};
self.consume_byte(b';')?;
Ok(reference)
}
pub fn consume_name(&mut self) -> Result<StrSpan<'a>> {
let start = self.pos();
self.skip_name()?;
let name = self.slice_back(start);
if name.is_empty() {
return Err(StreamError::InvalidName);
}
Ok(name)
}
pub fn skip_name(&mut self) -> Result<()> {
let mut iter = self.chars();
if let Some(c) = iter.next() {
if c.is_xml_name_start() {
self.advance(c.len_utf8());
} else {
return Err(StreamError::InvalidName);
}
}
for c in iter {
if c.is_xml_name() {
self.advance(c.len_utf8());
} else {
break;
}
}
Ok(())
}
pub fn consume_qname(&mut self) -> Result<(StrSpan<'a>, StrSpan<'a>)> {
let start = self.pos();
let mut splitter = None;
for c in self.chars() {
if c == ':' {
splitter = Some(self.pos());
self.advance(1);
} else if c.is_xml_name() {
self.advance(c.len_utf8());
} else {
break;
}
}
let (prefix, local) = if let Some(splitter) = splitter {
let prefix = self.span().slice_region(start, splitter);
let local = self.slice_back(splitter + 1);
(prefix, local)
} else {
let local = self.slice_back(start);
("".into(), local)
};
if local.is_empty() {
return Err(StreamError::InvalidName);
}
Ok((prefix, local))
}
pub fn consume_eq(&mut self) -> Result<()> {
self.skip_spaces();
self.consume_byte(b'=')?;
self.skip_spaces();
Ok(())
}
pub fn consume_quote(&mut self) -> Result<u8> {
let c = self.curr_byte()?;
if c == b'\'' || c == b'"' {
self.advance(1);
Ok(c)
} else {
Err(StreamError::InvalidQuote(c as char, self.gen_text_pos()))
}
}
}