Compare commits

...

4 Commits

Author SHA1 Message Date
cel 🌸 c1e6f7e918 implement element writing 2024-11-20 16:43:34 +00:00
cel 🌸 49c8d52f0d make namespaces optional 2024-11-20 15:46:24 +00:00
cel 🌸 a3dc4e1475 WIP: write start tag of element 2024-11-20 15:10:36 +00:00
cel 🌸 0175a2d365 implement write end tag 2024-11-19 16:46:51 +00:00
7 changed files with 463 additions and 89 deletions

12
Cargo.lock generated
View File

@ -17,6 +17,17 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "async-recursion"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@ -272,6 +283,7 @@ dependencies = [
name = "peanuts" name = "peanuts"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-recursion",
"circular", "circular",
"futures", "futures",
"nom", "nom",

View File

@ -6,6 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
async-recursion = "1.1.1"
circular = { version = "0.3.0", path = "../circular" } circular = { version = "0.3.0", path = "../circular" }
futures = "0.3.30" futures = "0.3.30"
nom = "7.1.3" nom = "7.1.3"

View File

@ -1,10 +1,14 @@
// elements resemble a final tree, including inherited namespace information // elements resemble a final tree, including inherited namespace information
use std::collections::{HashMap, HashSet}; use std::{
collections::{HashMap, HashSet},
convert::Infallible,
str::FromStr,
};
use crate::{ use crate::{
error::Error, error::Error,
xml::{self, Attribute}, xml::{self, parsers_complete::Parser, Attribute},
}; };
// when are namespaces names chosen then if they are automatically calculated // when are namespaces names chosen then if they are automatically calculated
@ -18,7 +22,7 @@ pub struct NamespaceDeclaration {
// names are qualified, they contain a reference to the namespace (held within the reader/writer) // names are qualified, they contain a reference to the namespace (held within the reader/writer)
#[derive(PartialEq, Eq, Hash, Clone, Debug)] #[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct Name { pub struct Name {
pub namespace: String, pub namespace: Option<String>,
pub local_name: String, pub local_name: String,
} }
@ -50,6 +54,33 @@ pub struct Element {
pub content: Vec<Content>, pub content: Vec<Content>,
} }
pub fn escape_str(s: &str) -> String {
let mut string = String::new();
for str in s.split_inclusive(|c| c == '<' || c == '&' || c == '>') {
if let Some(str) = str.strip_suffix('<') {
if !str.is_empty() {
string.push_str(str)
}
string.push_str("&lt;");
} else if let Some(str) = str.strip_suffix('&') {
if !str.is_empty() {
string.push_str(str)
}
string.push_str("&amp;");
} else if let Some(str) = str.strip_suffix('>') {
if !str.is_empty() {
string.push_str(str)
}
string.push_str("&gt;");
} else {
if !str.is_empty() {
string.push_str(str)
}
}
}
string
}
// impl<'s> TryFrom<xml::Element<'s>> for Element<'s> { // impl<'s> TryFrom<xml::Element<'s>> for Element<'s> {
// type Error = Error; // type Error = Error;

View File

@ -16,6 +16,7 @@ pub enum Error {
MismatchedEndTag(Name, Name), MismatchedEndTag(Name, Name),
NotInElement(String), NotInElement(String),
ExtraData(String), ExtraData(String),
UndeclaredNamespace(String),
} }
impl From<std::io::Error> for Error { impl From<std::io::Error> for Error {

View File

@ -48,7 +48,7 @@ where
Ok(self.inner.read_buf(&mut self.buffer).await?) Ok(self.inner.read_buf(&mut self.buffer).await?)
} }
async fn read_prolog<'s>(&'s mut self) -> Result<()> { pub async fn read_prolog<'s>(&'s mut self) -> Result<()> {
loop { loop {
self.read_buf().await?; self.read_buf().await?;
let input = str::from_utf8(self.buffer.data())?; let input = str::from_utf8(self.buffer.data())?;
@ -68,7 +68,7 @@ where
} }
} }
async fn read_start_tag<'s>(&'s mut self) -> Result<Element> { pub async fn read_start_tag<'s>(&'s mut self) -> Result<Element> {
loop { loop {
self.read_buf().await?; self.read_buf().await?;
let input = str::from_utf8(self.buffer.data())?; let input = str::from_utf8(self.buffer.data())?;
@ -93,7 +93,7 @@ where
} }
} }
async fn read_end_tag<'s>(&'s mut self) -> Result<()> { pub async fn read_end_tag<'s>(&'s mut self) -> Result<()> {
loop { loop {
self.read_buf().await?; self.read_buf().await?;
let input = str::from_utf8(self.buffer.data())?; let input = str::from_utf8(self.buffer.data())?;
@ -118,7 +118,7 @@ where
} }
} }
async fn read_element<'s>(&'s mut self) -> Result<Element> { pub async fn read_element<'s>(&'s mut self) -> Result<Element> {
loop { loop {
self.read_buf().await?; self.read_buf().await?;
let input = str::from_utf8(self.buffer.data())?; let input = str::from_utf8(self.buffer.data())?;
@ -140,7 +140,7 @@ where
} }
} }
async fn read_content<'s>(&'s mut self) -> Result<Content> { pub async fn read_content<'s>(&'s mut self) -> Result<Content> {
let mut last_char = false; let mut last_char = false;
let mut text = String::new(); let mut text = String::new();
loop { loop {
@ -312,7 +312,7 @@ impl<R> Reader<R> {
.chain(element_namespace_declarations.iter()) .chain(element_namespace_declarations.iter())
.collect(); .collect();
// element name and default attribute namespace // element name
let element_namespace_declaration; let element_namespace_declaration;
let element_local_name = s_tag.name.local_part().to_string(); let element_local_name = s_tag.name.local_part().to_string();
@ -330,10 +330,8 @@ impl<R> Reader<R> {
} }
} }
let element_default_namespace = element_namespace_declaration let element_default_namespace =
.ok_or_else(|| Error::UnqualifiedNamespace(s_tag.name.to_string()))? element_namespace_declaration.map(|decl| decl.namespace.clone());
.namespace
.clone();
let element_name = Name { let element_name = Name {
namespace: element_default_namespace, namespace: element_default_namespace,
@ -361,20 +359,24 @@ impl<R> Reader<R> {
namespace_declaration.prefix.as_deref() == Some(prefix) namespace_declaration.prefix.as_deref() == Some(prefix)
}); });
} }
None => attribute_namespace_declaration = element_namespace_declaration, None => attribute_namespace_declaration = None,
} }
let name;
if let Some(namespace_declaration) = attribute_namespace_declaration { if let Some(namespace_declaration) = attribute_namespace_declaration {
let name = Name { name = Name {
namespace: namespace_declaration.namespace.clone(), namespace: Some(namespace_declaration.namespace.clone()),
local_name: attribute_local_name, local_name: attribute_local_name,
}; };
let value = value.process()?;
// check for duplicate attribute
if let Some(_value) = attributes.insert(name, value) {
return Err(Error::DuplicateAttribute(q_name.to_string()));
}
} else { } else {
return Err(Error::UnqualifiedNamespace(q_name.to_string())); name = Name {
namespace: None,
local_name: attribute_local_name,
};
}
let value = value.process()?;
// check for duplicate attribute
if let Some(_value) = attributes.insert(name, value) {
return Err(Error::DuplicateAttribute(q_name.to_string()));
} }
} }
@ -415,8 +417,7 @@ impl<R> Reader<R> {
} }
let e_tag_namespace = e_tag_namespace_declaration let e_tag_namespace = e_tag_namespace_declaration
.ok_or_else(|| Error::UnqualifiedNamespace(xml_e_tag.name.to_string()))? .map(|decl| decl.namespace.clone())
.namespace
.clone(); .clone();
let e_tag_name = Name { let e_tag_name = Name {
@ -512,10 +513,8 @@ impl<R> Reader<R> {
} }
} }
let element_default_namespace = element_namespace_declaration let element_default_namespace =
.ok_or_else(|| Error::UnqualifiedNamespace(xml_name.to_string()))? element_namespace_declaration.map(|decl| decl.namespace.clone());
.namespace
.clone();
let element_name = Name { let element_name = Name {
namespace: element_default_namespace, namespace: element_default_namespace,
@ -541,10 +540,7 @@ impl<R> Reader<R> {
} }
} }
let e_tag_namespace = e_tag_namespace_declaration let e_tag_namespace = e_tag_namespace_declaration.map(|decl| decl.namespace.clone());
.ok_or_else(|| Error::UnqualifiedNamespace(xml_name.to_string()))?
.namespace
.clone();
let e_tag_name = Name { let e_tag_name = Name {
namespace: e_tag_namespace, namespace: e_tag_namespace,
@ -577,20 +573,24 @@ impl<R> Reader<R> {
namespace_declaration.prefix.as_deref() == Some(prefix) namespace_declaration.prefix.as_deref() == Some(prefix)
}); });
} }
None => attribute_namespace_declaration = element_namespace_declaration, None => attribute_namespace_declaration = None,
} }
let name;
if let Some(namespace_declaration) = attribute_namespace_declaration { if let Some(namespace_declaration) = attribute_namespace_declaration {
let name = Name { name = Name {
namespace: namespace_declaration.namespace.clone(), namespace: Some(namespace_declaration.namespace.clone()),
local_name: attribute_local_name, local_name: attribute_local_name,
}; };
let value = value.process()?;
// check for duplicate attribute
if let Some(_value) = attributes.insert(name, value) {
return Err(Error::DuplicateAttribute(q_name.to_string()));
}
} else { } else {
return Err(Error::UnqualifiedNamespace(q_name.to_string())); name = Name {
namespace: None,
local_name: attribute_local_name,
};
}
let value = value.process()?;
// check for duplicate attribute
if let Some(_value) = attributes.insert(name, value) {
return Err(Error::DuplicateAttribute(q_name.to_string()));
} }
} }
@ -674,19 +674,21 @@ impl<R: AsyncRead + Unpin> Stream for Reader<R> {
} }
#[cfg(test)] #[cfg(test)]
mod test { pub(crate) mod test {
use futures::{sink::Buffer, StreamExt}; use futures::{sink::Buffer, StreamExt};
use tokio::io::AsyncRead; use tokio::io::AsyncRead;
use crate::element::Element;
use super::Reader; use super::Reader;
struct MockAsyncReader<'s> { pub struct MockAsyncReader<'s> {
put: bool, put: bool,
data: &'s str, data: &'s str,
} }
impl<'s> MockAsyncReader<'s> { impl<'s> MockAsyncReader<'s> {
fn new(data: &'s str) -> Self { pub fn new(data: &'s str) -> Self {
Self { put: false, data } Self { put: false, data }
} }
} }
@ -705,7 +707,7 @@ mod test {
} }
} }
const TEST_DOC: &'static str = "<xs:schema pub const TEST_DOC: &'static str = "<xs:schema
xmlns:xs='http://www.w3.org/2001/XMLSchema' xmlns:xs='http://www.w3.org/2001/XMLSchema'
targetNamespace='http://etherx.jabber.org/streams' targetNamespace='http://etherx.jabber.org/streams'
xmlns='http://etherx.jabber.org/streams' xmlns='http://etherx.jabber.org/streams'

View File

@ -1,53 +1,275 @@
use std::collections::HashSet; use std::{collections::HashSet, str::FromStr};
use async_recursion::async_recursion;
use futures::Sink; use futures::Sink;
use tokio::io::AsyncWrite; use tokio::io::{AsyncWrite, AsyncWriteExt};
use crate::{ use crate::{
element::{Element, Name, NamespaceDeclaration}, element::{escape_str, Content, Element, Name, NamespaceDeclaration},
error::Error, error::Error,
xml::{self, composers::Composer, parsers_complete::Parser, ETag}, xml::{self, composers::Composer, parsers_complete::Parser, ETag},
Result,
}; };
// pub struct Writer<W, C = Composer> { // pub struct Writer<W, C = Composer> {
pub struct Writer<W> { pub struct Writer<W> {
inner: W, inner: W,
depth: Vec<Name>, depth: Vec<Name>,
namespaces: Vec<HashSet<NamespaceDeclaration>>, namespace_declarations: Vec<HashSet<NamespaceDeclaration>>,
} }
impl<W: AsyncWrite + Unpin> Writer<W> { impl<W> Writer<W> {
pub async fn write(&mut self, element: Element) -> Result<(), Error> { pub fn new(writer: W) -> Self {
todo!() Self {
inner: writer,
depth: Vec::new(),
namespace_declarations: Vec::new(),
}
}
}
impl<W: AsyncWrite + Unpin + Send> Writer<W> {
#[async_recursion]
pub async fn write_element(&mut self, element: &Element) -> Result<()> {
if element.content.is_empty() {
self.write_empty(element).await?;
} else {
self.write_start(element).await?;
for content in &element.content {
self.write_content(content).await?;
}
self.write_end().await?;
}
Ok(())
} }
pub async fn write_start(&mut self, element: Element) -> Result<(), Error> { pub async fn write_empty(&mut self, element: &Element) -> Result<()> {
todo!() let namespace_declarations_stack: Vec<_> = self
.namespace_declarations
.iter()
.flatten()
.chain(&element.namespace_declarations)
.collect();
let prefix;
if let Some(namespace) = &element.name.namespace {
let name_namespace_declaration = namespace_declarations_stack
.iter()
.rfind(|namespace_declaration| namespace_declaration.namespace == *namespace)
.ok_or(Error::UndeclaredNamespace(namespace.clone()))?;
prefix = name_namespace_declaration.prefix.as_ref();
} else {
prefix = None
}
let name;
if let Some(prefix) = &prefix {
name = xml::QName::PrefixedName(xml::PrefixedName {
prefix: xml::Prefix::parse_full(prefix)?,
local_part: xml::LocalPart::parse_full(&element.name.local_name)?,
})
} else {
name = xml::QName::UnprefixedName(xml::UnprefixedName::parse_full(
&element.name.local_name,
)?)
}
let mut attributes = Vec::new();
for namespace_declaration in &element.namespace_declarations {
let ns_name = namespace_declaration
.prefix
.as_ref()
.map(|prefix| -> Result<_> {
Ok(xml::NSAttName::PrefixedAttName(xml::PrefixedAttName(
xml::NCName::parse_full(&prefix)?,
)))
})
.unwrap_or(Ok(xml::NSAttName::DefaultAttName))?;
let value = xml::AttValue::from(namespace_declaration.namespace.as_str());
let xml_attribute = xml::Attribute::NamespaceDeclaration { ns_name, value };
attributes.push(xml_attribute);
}
for (name, value) in &element.attributes {
let prefix;
if let Some(namespace) = &name.namespace {
let name_namespace_declaration = namespace_declarations_stack
.iter()
.rfind(|namespace_declaration| namespace_declaration.namespace == *namespace)
.ok_or(Error::UndeclaredNamespace(namespace.clone()))?;
prefix = name_namespace_declaration.prefix.as_ref();
} else {
prefix = None
}
let att_name;
if let Some(prefix) = &prefix {
att_name = xml::QName::PrefixedName(xml::PrefixedName {
prefix: xml::Prefix::parse_full(prefix)?,
local_part: xml::LocalPart::parse_full(&element.name.local_name)?,
})
} else {
att_name = xml::QName::UnprefixedName(xml::UnprefixedName::parse_full(
&element.name.local_name,
)?)
}
let value = xml::AttValue::from(value.as_str());
let xml_attribute = xml::Attribute::Attribute {
name: att_name,
value,
};
attributes.push(xml_attribute);
}
let tag = xml::EmptyElemTag { name, attributes };
tag.write(&mut self.inner).await?;
Ok(())
} }
pub async fn write_end(&mut self) -> Result<(), Error> { pub async fn write_start(&mut self, element: &Element) -> Result<()> {
todo!() let namespace_declarations_stack: Vec<_> = self
// let e_tag; .namespace_declarations
// if let Some(name) = self.depth.pop() { .iter()
// if let Some(prefix) = name.namespace.prefix { .flatten()
// e_tag = xml::ETag { .chain(&element.namespace_declarations)
// name: xml::QName::PrefixedName(xml::PrefixedName { .collect();
// prefix: xml::Prefix::parse_full(&prefix)?,
// local_part: xml::LocalPart::parse_full(&name.name)?, let prefix;
// }), if let Some(namespace) = &element.name.namespace {
// }; let name_namespace_declaration = namespace_declarations_stack
// e_tag.write(&mut self.inner).await?; .iter()
// Ok(()) .rfind(|namespace_declaration| namespace_declaration.namespace == *namespace)
// } else { .ok_or(Error::UndeclaredNamespace(namespace.clone()))?;
// e_tag = xml::ETag { prefix = name_namespace_declaration.prefix.as_ref();
// name: xml::QName::UnprefixedName(xml::UnprefixedName::parse_full(&name.name)?), } else {
// }; prefix = None
// e_tag.write(&mut self.inner).await?; }
// Ok(())
// } let name;
// } else { if let Some(prefix) = &prefix {
// return Err(Error::NotInElement("".to_string())); name = xml::QName::PrefixedName(xml::PrefixedName {
// } prefix: xml::Prefix::parse_full(prefix)?,
local_part: xml::LocalPart::parse_full(&element.name.local_name)?,
})
} else {
name = xml::QName::UnprefixedName(xml::UnprefixedName::parse_full(
&element.name.local_name,
)?)
}
let mut attributes = Vec::new();
for namespace_declaration in &element.namespace_declarations {
let ns_name = namespace_declaration
.prefix
.as_ref()
.map(|prefix| -> Result<_> {
Ok(xml::NSAttName::PrefixedAttName(xml::PrefixedAttName(
xml::NCName::parse_full(&prefix)?,
)))
})
.unwrap_or(Ok(xml::NSAttName::DefaultAttName))?;
let value = xml::AttValue::from(namespace_declaration.namespace.as_str());
let xml_attribute = xml::Attribute::NamespaceDeclaration { ns_name, value };
attributes.push(xml_attribute);
}
for (name, value) in &element.attributes {
let prefix;
if let Some(namespace) = &name.namespace {
let name_namespace_declaration = namespace_declarations_stack
.iter()
.rfind(|namespace_declaration| namespace_declaration.namespace == *namespace)
.ok_or(Error::UndeclaredNamespace(namespace.clone()))?;
prefix = name_namespace_declaration.prefix.as_ref();
} else {
prefix = None
}
let att_name;
if let Some(prefix) = &prefix {
att_name = xml::QName::PrefixedName(xml::PrefixedName {
prefix: xml::Prefix::parse_full(prefix)?,
local_part: xml::LocalPart::parse_full(&element.name.local_name)?,
})
} else {
att_name = xml::QName::UnprefixedName(xml::UnprefixedName::parse_full(
&element.name.local_name,
)?)
}
let value = xml::AttValue::from(value.as_str());
let xml_attribute = xml::Attribute::Attribute {
name: att_name,
value,
};
attributes.push(xml_attribute);
}
let s_tag = xml::STag { name, attributes };
s_tag.write(&mut self.inner).await?;
self.depth.push(element.name.clone());
self.namespace_declarations
.push(element.namespace_declarations.clone());
Ok(())
}
pub async fn write_content(&mut self, content: &Content) -> Result<()> {
match content {
Content::Element(element) => self.write_element(element).await?,
Content::Text(text) => self.inner.write_all(escape_str(text).as_bytes()).await?,
// TODO: comments and PI
Content::PI => {}
Content::Comment(_) => {}
}
Ok(())
}
pub async fn write_end(&mut self) -> Result<()> {
if let Some(name) = &self.depth.pop() {
let e_tag;
let namespace_declarations_stack: Vec<_> =
self.namespace_declarations.iter().flatten().collect();
let prefix;
if let Some(namespace) = &name.namespace {
let name_namespace_declaration = namespace_declarations_stack
.iter()
.rfind(|namespace_declaration| namespace_declaration.namespace == *namespace)
.ok_or(Error::UndeclaredNamespace(namespace.clone()))?;
prefix = name_namespace_declaration.prefix.as_ref();
} else {
prefix = None
}
if let Some(prefix) = &prefix {
e_tag = xml::ETag {
name: xml::QName::PrefixedName(xml::PrefixedName {
prefix: xml::Prefix::parse_full(prefix)?,
local_part: xml::LocalPart::parse_full(&name.local_name)?,
}),
};
} else {
e_tag = xml::ETag {
name: xml::QName::UnprefixedName(xml::UnprefixedName::parse_full(
&name.local_name,
)?),
};
}
e_tag.write(&mut self.inner).await?;
self.namespace_declarations.pop();
Ok(())
} else {
return Err(Error::NotInElement("".to_string()));
}
} }
} }
@ -57,25 +279,43 @@ impl<W: AsyncWrite, E: Into<Element>> Sink<E> for Writer<W> {
fn poll_ready( fn poll_ready(
self: std::pin::Pin<&mut Self>, self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>, cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> { ) -> std::task::Poll<Result<()>> {
todo!() todo!()
} }
fn start_send(self: std::pin::Pin<&mut Self>, item: E) -> Result<(), Self::Error> { fn start_send(self: std::pin::Pin<&mut Self>, item: E) -> Result<()> {
todo!() todo!()
} }
fn poll_flush( fn poll_flush(
self: std::pin::Pin<&mut Self>, self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>, cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> { ) -> std::task::Poll<Result<()>> {
todo!() todo!()
} }
fn poll_close( fn poll_close(
self: std::pin::Pin<&mut Self>, self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>, cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> { ) -> std::task::Poll<Result<()>> {
todo!() todo!()
} }
} }
#[cfg(test)]
mod test {
use crate::{
reader::{test::*, Reader},
writer::Writer,
};
#[tokio::test]
async fn test_element_write() {
let mock = MockAsyncReader::new(TEST_DOC);
let mut reader = Reader::new(mock);
let element = reader.read_element().await.unwrap();
let stdout = tokio::io::stdout();
let mut writer = Writer::new(stdout);
writer.write_element(&element).await.unwrap();
}
}

View File

@ -1,4 +1,6 @@
use std::{char, ops::Deref}; use std::{char, convert::Infallible, ops::Deref, str::FromStr};
use parsers_complete::Parser;
use crate::error::Error; use crate::error::Error;
@ -15,7 +17,7 @@ pub enum NSAttName<'s> {
/// [2] PrefixedAttName ::= 'xmlns:' NCName /// [2] PrefixedAttName ::= 'xmlns:' NCName
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct PrefixedAttName<'s>(NCName<'s>); pub struct PrefixedAttName<'s>(pub NCName<'s>);
impl<'s> Deref for PrefixedAttName<'s> { impl<'s> Deref for PrefixedAttName<'s> {
type Target = NCName<'s>; type Target = NCName<'s>;
@ -228,14 +230,14 @@ pub enum EntityValue<'s> {
SingleQuoted(Vec<EntityValueData<'s>>), SingleQuoted(Vec<EntityValueData<'s>>),
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum AttValueData<'s> { pub enum AttValueData<'s> {
String(&'s str), String(&'s str),
Reference(Reference<'s>), Reference(Reference<'s>),
} }
/// [10] AttValue ::= '"' ([^<&"] | Reference)* '"' /// [10] AttValue ::= '"' ([^<&"] | Reference)* '"'
/// | "'" ([^<&'] | Reference)* "'" /// | "'" ([^<&'] | Reference)* "'"
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum AttValue<'s> { pub enum AttValue<'s> {
DoubleQuoted(Vec<AttValueData<'s>>), DoubleQuoted(Vec<AttValueData<'s>>),
SingleQuoted(Vec<AttValueData<'s>>), SingleQuoted(Vec<AttValueData<'s>>),
@ -259,6 +261,34 @@ impl<'s> AttValue<'s> {
} }
} }
impl<'s> From<&'s str> for AttValue<'s> {
fn from(s: &'s str) -> AttValue<'s> {
let mut data = Vec::new();
for str in s.split_inclusive(|c| c == '<' || c == '"') {
if let Some(str) = str.strip_suffix('<') {
if !str.is_empty() {
data.push(AttValueData::String(str))
}
data.push(AttValueData::Reference(Reference::EntityRef(EntityRef(
Name::parse_full("lt").unwrap(),
))))
} else if let Some(str) = str.strip_suffix('"') {
if !str.is_empty() {
data.push(AttValueData::String(str))
}
data.push(AttValueData::Reference(Reference::EntityRef(EntityRef(
Name::parse_full("quot").unwrap(),
))))
} else {
if !str.is_empty() {
data.push(AttValueData::String(str))
}
}
}
AttValue::DoubleQuoted(data)
}
}
/// [11] SystemLiteral ::= ('"' [^"]* '"') | ("'" [^']* "'") /// [11] SystemLiteral ::= ('"' [^"]* '"') | ("'" [^']* "'")
#[derive(Debug)] #[derive(Debug)]
pub enum SystemLiteral<'s> { pub enum SystemLiteral<'s> {
@ -673,7 +703,7 @@ pub struct IgnoreSectContents<'s> {
pub struct Ignore<'s>(&'s str); pub struct Ignore<'s>(&'s str);
/// [66] CharRef ::= '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';' /// [66] CharRef ::= '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';'
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum CharRef<'s> { pub enum CharRef<'s> {
Decimal(&'s str), Decimal(&'s str),
Hexadecimal(&'s str), Hexadecimal(&'s str),
@ -706,7 +736,7 @@ impl<'s> CharRef<'s> {
} }
/// [67] Reference ::= EntityRef | CharRef /// [67] Reference ::= EntityRef | CharRef
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum Reference<'s> { pub enum Reference<'s> {
EntityRef(EntityRef<'s>), EntityRef(EntityRef<'s>),
CharRef(CharRef<'s>), CharRef(CharRef<'s>),
@ -729,8 +759,8 @@ impl<'s> Reference<'s> {
} }
/// [68] EntityRef ::= '&' Name ';' /// [68] EntityRef ::= '&' Name ';'
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct EntityRef<'s>(Name<'s>); pub struct EntityRef<'s>(pub(crate) Name<'s>);
impl<'s> Deref for EntityRef<'s> { impl<'s> Deref for EntityRef<'s> {
type Target = Name<'s>; type Target = Name<'s>;
@ -835,3 +865,60 @@ pub struct NotationDecl<'s> {
/// [83] PublicID ::= 'PUBLIC' S PubidLiteral /// [83] PublicID ::= 'PUBLIC' S PubidLiteral
#[derive(Debug)] #[derive(Debug)]
pub struct PublicID<'s>(PubidLiteral<'s>); pub struct PublicID<'s>(PubidLiteral<'s>);
#[cfg(test)]
mod test {
use super::{AttValue, AttValueData, EntityRef, Name, Reference};
#[test]
fn att_value_from_str() {
assert_eq!(
AttValue::from("hsdaflaskdf<laksdf<abdsf"),
AttValue::DoubleQuoted(vec![
AttValueData::String("hsdaflaskdf"),
AttValueData::Reference(Reference::EntityRef(EntityRef(Name("lt")))),
AttValueData::String("laksdf"),
AttValueData::Reference(Reference::EntityRef(EntityRef(Name("lt")))),
AttValueData::String("abdsf"),
])
);
assert_eq!(
AttValue::from("hsdaflaskdf<laksdf\"abdsf"),
AttValue::DoubleQuoted(vec![
AttValueData::String("hsdaflaskdf"),
AttValueData::Reference(Reference::EntityRef(EntityRef(Name("lt")))),
AttValueData::String("laksdf"),
AttValueData::Reference(Reference::EntityRef(EntityRef(Name("quot")))),
AttValueData::String("abdsf"),
])
);
assert_eq!(
AttValue::from("hsdaflaskdf<laksdf\""),
AttValue::DoubleQuoted(vec![
AttValueData::String("hsdaflaskdf"),
AttValueData::Reference(Reference::EntityRef(EntityRef(Name("lt")))),
AttValueData::String("laksdf"),
AttValueData::Reference(Reference::EntityRef(EntityRef(Name("quot")))),
])
);
assert_eq!(
AttValue::from("hsdaflaskdf\"<<laksdf\""),
AttValue::DoubleQuoted(vec![
AttValueData::String("hsdaflaskdf"),
AttValueData::Reference(Reference::EntityRef(EntityRef(Name("quot")))),
AttValueData::Reference(Reference::EntityRef(EntityRef(Name("lt")))),
AttValueData::Reference(Reference::EntityRef(EntityRef(Name("lt")))),
AttValueData::String("laksdf"),
AttValueData::Reference(Reference::EntityRef(EntityRef(Name("quot")))),
])
);
assert_eq!(
AttValue::from("<<\""),
AttValue::DoubleQuoted(vec![
AttValueData::Reference(Reference::EntityRef(EntityRef(Name("lt")))),
AttValueData::Reference(Reference::EntityRef(EntityRef(Name("lt")))),
AttValueData::Reference(Reference::EntityRef(EntityRef(Name("quot")))),
])
);
}
}