use crate::shared::error::*;
use crate::shared::syntax::*;
use crate::shared::text::is_xml_name;
use std::convert::TryFrom;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::result::Result as StdResult;
use std::str::{from_utf8, FromStr};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Name {
pub(crate) namespace_uri: Option<String>,
pub(crate) prefix: Option<String>,
pub(crate) local_name: String,
}
impl Display for Name {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match &self.prefix {
Some(prefix) => write!(f, "{}{}{}", prefix, XML_NS_SEPARATOR, self.local_name),
None => write!(f, "{}", self.local_name),
}
}
}
impl FromStr for Name {
type Err = Error;
fn from_str(value: &str) -> StdResult<Self, Self::Err> {
if value.is_empty() {
Err(Error::Syntax)
} else {
let parts = value
.split(XML_NS_SEPARATOR)
.map(|s| s.to_string())
.collect::<Vec<String>>();
match parts.len() {
1 => Name::new(Name::check_part(parts.get(0).unwrap())?, None, None),
2 => Name::new(
Name::check_part(parts.get(1).unwrap())?,
Some(Name::check_part(parts.get(0).unwrap())?),
None,
),
_ => Err(Error::Syntax),
}
}
}
}
impl TryFrom<&[u8]> for Name {
type Error = Error;
fn try_from(value: &[u8]) -> StdResult<Self, Self::Error> {
match from_utf8(value) {
Ok(str) => Self::from_str(str),
Err(e) => {
error!("Could not convert from UTF-8, error {:?}", e);
Err(Error::InvalidCharacter)
}
}
}
}
impl Name {
pub fn new_ns(namespace_uri: &str, qualified_name: &str) -> Result<Self> {
let mut parsed = Name::from_str(qualified_name)?;
parsed.namespace_uri = Some(Self::check_namespace_uri(
namespace_uri,
&parsed.prefix,
&parsed.local_name,
)?);
Ok(parsed)
}
fn new(
local_name: String,
prefix: Option<String>,
namespace_uri: Option<String>,
) -> Result<Self> {
if local_name.is_empty() {
warn!("local_name may not be empty");
return Err(Error::Syntax);
}
if let Some(prefix) = &prefix {
if prefix.is_empty() {
warn!("prefix may not be empty");
return Err(Error::Syntax);
}
}
if let Some(namespace_uri) = &namespace_uri {
if namespace_uri.is_empty() {
warn!("namespace_uri may not be empty");
return Err(Error::Syntax);
}
}
Ok(Self {
namespace_uri,
prefix,
local_name,
})
}
fn check_part(part: &str) -> Result<String> {
if part.is_empty() {
Err(Error::Syntax)
} else if is_xml_name(part) {
Ok(part.to_string())
} else {
Err(Error::InvalidCharacter)
}
}
fn check_namespace_uri(
namespace_uri: &str,
prefix: &Option<String>,
local: &str,
) -> Result<String> {
if namespace_uri.is_empty() {
Err(Error::Syntax)
} else {
if let Some(prefix) = prefix {
if (prefix == XML_NS_ATTRIBUTE && namespace_uri != XML_NS_URI)
|| (prefix == XMLNS_NS_ATTRIBUTE && namespace_uri != XMLNS_NS_URI)
{
return Err(Error::Namespace);
}
}
if (local == XML_NS_ATTRIBUTE && namespace_uri != XML_NS_URI)
|| (local == XMLNS_NS_ATTRIBUTE && namespace_uri != XMLNS_NS_URI)
{
Err(Error::Namespace)
} else {
Ok(namespace_uri.to_string())
}
}
}
pub fn for_cdata() -> Self {
Self {
namespace_uri: None,
prefix: None,
local_name: XML_NAME_CDATA.to_string(),
}
}
pub fn for_comment() -> Self {
Self {
namespace_uri: None,
prefix: None,
local_name: XML_NAME_COMMENT.to_string(),
}
}
pub fn for_document() -> Self {
Self {
namespace_uri: None,
prefix: None,
local_name: XML_NAME_DOCUMENT.to_string(),
}
}
pub fn for_document_fragment() -> Self {
Self {
namespace_uri: None,
prefix: None,
local_name: XML_NAME_DOCUMENT_FRAGMENT.to_string(),
}
}
pub fn for_text() -> Self {
Self {
namespace_uri: None,
prefix: None,
local_name: XML_NAME_TEXT.to_string(),
}
}
pub fn for_public_id() -> Self {
Self {
namespace_uri: None,
prefix: None,
local_name: XML_DOCTYPE_PUBLIC.to_string(),
}
}
pub fn for_system_id() -> Self {
Self {
namespace_uri: None,
prefix: None,
local_name: XML_DOCTYPE_SYSTEM.to_string(),
}
}
#[allow(dead_code)]
pub(crate) fn for_null() -> Self {
Self {
namespace_uri: None,
prefix: None,
local_name: "null".to_string(),
}
}
pub fn is_namespace_attribute(&self) -> bool {
let xmlns_ns = Some(XMLNS_NS_URI.to_string());
let xmlns_attribute = XMLNS_NS_ATTRIBUTE.to_string();
self.namespace_uri == xmlns_ns
&& ((self.local_name == xmlns_attribute && self.prefix == None)
|| self.prefix == Some(xmlns_attribute))
}
pub fn for_namespace(prefix: Option<&str>) -> Self {
let xmlns_ns = Some(XMLNS_NS_URI.to_string());
let xmlns_attribute = XMLNS_NS_ATTRIBUTE.to_string();
match prefix {
None => Self::new(xmlns_attribute, None, xmlns_ns).unwrap(),
Some(prefix) => Self::new(prefix.to_string(), Some(xmlns_attribute), xmlns_ns).unwrap(),
}
}
pub fn is_id_attribute(&self, lax: bool) -> bool {
let id_attribute = XML_NS_ATTR_ID.to_string();
if lax {
self.local_name == id_attribute
} else {
let xml_ns = XML_NS_URI.to_string();
let xml_prefix = XML_NS_ATTRIBUTE.to_string();
self.local_name == id_attribute
&& (self.namespace_uri == Some(xml_ns) || self.prefix == Some(xml_prefix))
}
}
pub fn for_xml_id() -> Self {
Self {
namespace_uri: Some(XML_NS_URI.to_string()),
prefix: Some(XML_NS_ATTRIBUTE.to_string()),
local_name: XML_NS_ATTR_ID.to_string(),
}
}
pub fn namespace_uri(&self) -> &Option<String> {
&self.namespace_uri
}
pub fn local_name(&self) -> &String {
&self.local_name
}
pub fn prefix(&self) -> &Option<String> {
&self.prefix
}
pub fn set_prefix(&mut self, new_prefix: Option<&str>) -> Result<()> {
self.prefix = new_prefix.map(String::from);
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::shared::error::Error;
use crate::shared::name::Name;
use crate::shared::syntax::{XMLNS_NS_URI, XML_NS_URI};
use std::str::FromStr;
#[test]
fn test_parse_invalid_chars() {
for c in " \t\r\n\u{0}!?".chars() {
let name = Name::from_str(&format!("he{}lo", c));
assert!(name.is_err());
}
}
#[test]
fn test_parse_local() {
let name = Name::from_str("hello").unwrap();
assert_eq!(name.local_name, "hello".to_string());
assert!(name.prefix().is_none());
assert!(name.namespace_uri().is_none());
}
#[test]
fn test_parse_qualified() {
let name = Name::from_str("x:hello").unwrap();
assert_eq!(name.local_name, "hello".to_string());
assert_eq!(name.prefix(), &Some("x".to_string()));
assert!(name.namespace_uri().is_none());
}
#[test]
fn test_parse_namespaced() {
let name = Name::new_ns("http://example.org/schema/x", "x:hello").unwrap();
assert_eq!(name.local_name, "hello".to_string());
assert_eq!(name.prefix(), &Some("x".to_string()));
assert_eq!(
name.namespace_uri(),
&Some("http://example.org/schema/x".to_string())
);
}
#[test]
fn test_error_on_empty() {
let name = Name::from_str("");
assert_eq!(name.err().unwrap(), Error::Syntax);
let name = Name::from_str(":name");
assert_eq!(name.err().unwrap(), Error::Syntax);
let name = Name::from_str("prefix:");
assert_eq!(name.err().unwrap(), Error::Syntax);
let name = Name::new_ns("", "prefix:name");
assert_eq!(name.err().unwrap(), Error::Syntax);
}
#[test]
fn test_xml_ns_names() {
const RDF_NS: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
let name = Name::new_ns(XML_NS_URI, "xml:id");
assert!(name.is_ok());
let name = name.unwrap();
assert!(name.is_id_attribute(true));
assert!(name.is_id_attribute(false));
let name = Name::new_ns(RDF_NS, "xml:id");
assert_eq!(name.err().unwrap(), Error::Namespace);
let name = Name::from_str("another:id");
assert!(name.is_ok());
let name = name.unwrap();
assert!(name.is_id_attribute(true));
assert!(!name.is_id_attribute(false));
let name = Name::from_str("x:hello");
assert!(name.is_ok());
let name = name.unwrap();
assert!(!name.is_id_attribute(true));
assert!(!name.is_id_attribute(false));
}
#[test]
fn test_xmlns_ns_names() {
const RDF_NS: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
let name = Name::new_ns(XMLNS_NS_URI, "xmlns");
assert!(name.is_ok());
assert!(name.unwrap().is_namespace_attribute());
let name = Name::new_ns(XMLNS_NS_URI, "xmlns:p");
assert!(name.is_ok());
assert!(name.unwrap().is_namespace_attribute());
let name = Name::new_ns(RDF_NS, "xmlns");
assert_eq!(name.err().unwrap(), Error::Namespace);
let name = Name::new_ns(RDF_NS, "xmlns:rdf");
assert_eq!(name.err().unwrap(), Error::Namespace);
let name = Name::from_str("x:hello").unwrap();
assert!(!name.is_namespace_attribute());
}
}