mod tests;
use std::borrow::Cow;
use std::io::{Cursor, Write};
use std::str;
pub(crate) const XML_WRITE_ERROR: &str = "Couldn't write to xml file";
const UNICODE_ESCAPE_LENGTH: usize = 7;
pub(crate) fn xml_declaration<W: Write>(mut writer: W) {
writer
.write_all(b"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n")
.expect(XML_WRITE_ERROR);
}
pub(crate) fn xml_start_tag_only<W: Write>(writer: &mut W, tag: &str) {
write!(writer, "<{tag}>").expect(XML_WRITE_ERROR);
}
pub(crate) fn xml_start_tag<W, T>(writer: &mut W, tag: &str, attributes: &[T])
where
W: Write,
T: IntoAttribute,
{
write!(writer, "<{tag}").expect(XML_WRITE_ERROR);
for attribute in attributes {
attribute.write_to(writer);
}
writer.write_all(b">").expect(XML_WRITE_ERROR);
}
pub(crate) fn xml_end_tag<W: Write>(writer: &mut W, tag: &str) {
write!(writer, "</{tag}>").expect(XML_WRITE_ERROR);
}
pub(crate) fn xml_empty_tag_only<W: Write>(writer: &mut W, tag: &str) {
write!(writer, "<{tag}/>").expect(XML_WRITE_ERROR);
}
pub(crate) fn xml_empty_tag<W, T>(writer: &mut W, tag: &str, attributes: &[T])
where
W: Write,
T: IntoAttribute,
{
write!(writer, "<{tag}").expect(XML_WRITE_ERROR);
for attribute in attributes {
attribute.write_to(writer);
}
writer.write_all(b"/>").expect(XML_WRITE_ERROR);
}
pub(crate) fn xml_data_element_only<W: Write>(writer: &mut W, tag: &str, data: &str) {
write!(writer, "<{}>{}</{}>", tag, escape_xml_data(data), tag).expect(XML_WRITE_ERROR);
}
pub(crate) fn xml_data_element<W, T>(writer: &mut W, tag: &str, data: &str, attributes: &[T])
where
W: Write,
T: IntoAttribute,
{
write!(writer, "<{tag}").expect(XML_WRITE_ERROR);
for attribute in attributes {
attribute.write_to(writer);
}
write!(writer, ">{}</{}>", escape_xml_data(data), tag).expect(XML_WRITE_ERROR);
}
pub(crate) fn xml_si_element<W: Write>(writer: &mut W, string: &str, preserve_whitespace: bool) {
if preserve_whitespace {
write!(
writer,
r#"<si><t xml:space="preserve">{}</t></si>"#,
escape_xml_data(&escape_xml_escapes(string))
)
.expect(XML_WRITE_ERROR);
} else {
write!(
writer,
"<si><t>{}</t></si>",
escape_xml_data(&escape_xml_escapes(string))
)
.expect(XML_WRITE_ERROR);
}
}
pub(crate) fn xml_rich_si_element<W: Write>(writer: &mut W, string: &str) {
write!(writer, "<si>{string}</si>").expect(XML_WRITE_ERROR);
}
pub(crate) fn xml_theme<W: Write>(writer: &mut W, theme: &str) {
writeln!(writer, "{theme}").expect(XML_WRITE_ERROR);
}
pub(crate) fn xml_raw_string<W: Write>(writer: &mut W, data: &str) {
writer.write_all(data.as_bytes()).expect(XML_WRITE_ERROR);
}
pub(crate) fn escape_attributes(attribute: &str) -> Cow<'_, str> {
escape_string(attribute, match_attribute_html_char)
}
pub(crate) fn escape_xml_data(data: &str) -> Cow<'_, str> {
escape_string(data, match_xml_char)
}
pub(crate) fn escape_url(data: &str) -> Cow<'_, str> {
escape_string(data, match_url_char)
}
#[cfg(test)]
pub(crate) fn cursor_to_str(cursor: &Cursor<Vec<u8>>) -> &str {
str::from_utf8(cursor.get_ref()).unwrap()
}
pub(crate) fn cursor_to_string(cursor: &Cursor<Vec<u8>>) -> String {
str::from_utf8(cursor.get_ref()).unwrap().to_string()
}
pub(crate) fn reset(cursor: &mut Cursor<Vec<u8>>) {
cursor.get_mut().clear();
cursor.set_position(0);
}
fn match_attribute_html_char(ch: char) -> Option<&'static str> {
match ch {
'&' => Some("&"),
'"' => Some("""),
'<' => Some("<"),
'>' => Some(">"),
'\n' => Some("
"),
_ => None,
}
}
fn match_xml_char(ch: char) -> Option<&'static str> {
match ch {
'&' => Some("&"),
'<' => Some("<"),
'>' => Some(">"),
'\x00' => Some("_x0000_"),
'\x01' => Some("_x0001_"),
'\x02' => Some("_x0002_"),
'\x03' => Some("_x0003_"),
'\x04' => Some("_x0004_"),
'\x05' => Some("_x0005_"),
'\x06' => Some("_x0006_"),
'\x07' => Some("_x0007_"),
'\x08' => Some("_x0008_"),
'\x0B' => Some("_x000B_"),
'\x0C' => Some("_x000C_"),
'\x0D' => Some("_x000D_"),
'\x0E' => Some("_x000E_"),
'\x0F' => Some("_x000F_"),
'\x10' => Some("_x0010_"),
'\x11' => Some("_x0011_"),
'\x12' => Some("_x0012_"),
'\x13' => Some("_x0013_"),
'\x14' => Some("_x0014_"),
'\x15' => Some("_x0015_"),
'\x16' => Some("_x0016_"),
'\x17' => Some("_x0017_"),
'\x18' => Some("_x0018_"),
'\x19' => Some("_x0019_"),
'\x1A' => Some("_x001A_"),
'\x1B' => Some("_x001B_"),
'\x1C' => Some("_x001C_"),
'\x1D' => Some("_x001D_"),
'\x1E' => Some("_x001E_"),
'\x1F' => Some("_x001F_"),
'\u{FFFE}' => Some("_xFFFE_"),
'\u{FFFF}' => Some("_xFFFF_"),
_ => None,
}
}
fn match_url_char(ch: char) -> Option<&'static str> {
match ch {
'%' => Some("%25"),
'"' => Some("%22"),
' ' => Some("%20"),
'<' => Some("%3c"),
'>' => Some("%3e"),
'[' => Some("%5b"),
']' => Some("%5d"),
'^' => Some("%5e"),
'`' => Some("%60"),
'{' => Some("%7b"),
'}' => Some("%7d"),
_ => None,
}
}
fn escape_string<F>(original: &str, char_handler: F) -> Cow<'_, str>
where
F: FnOnce(char) -> Option<&'static str> + Copy,
{
for (i, ch) in original.char_indices() {
if char_handler(ch).is_some() {
let mut escaped_string = original[..i].to_string();
let remaining = &original[i..];
escaped_string.reserve(remaining.len());
for ch in remaining.chars() {
match char_handler(ch) {
Some(escaped_char) => escaped_string.push_str(escaped_char),
None => escaped_string.push(ch),
}
}
return Cow::Owned(escaped_string);
}
}
Cow::Borrowed(original)
}
pub(crate) fn escape_xml_escapes(original: &str) -> Cow<'_, str> {
if !original.contains("_x") {
return Cow::Borrowed(original);
}
let string_end = original.len();
let mut escaped_string = original.to_string();
for (index, _) in original.rmatch_indices("_x") {
if index + UNICODE_ESCAPE_LENGTH > string_end {
continue;
}
if !original.is_char_boundary(index + (UNICODE_ESCAPE_LENGTH - 1)) {
continue;
}
if !original[index + UNICODE_ESCAPE_LENGTH - 1..].starts_with('_') {
continue;
}
if original[index + 2..index + (UNICODE_ESCAPE_LENGTH - 1)]
.chars()
.all(|c| c.is_ascii_hexdigit())
{
escaped_string.replace_range(index..index, "_x005F");
}
}
if escaped_string == original {
return Cow::Borrowed(original);
}
Cow::Owned(escaped_string)
}
pub(crate) trait IntoAttribute {
fn write_to<W>(&self, writer: &mut W)
where
W: Write;
}
impl IntoAttribute for (&str, &str) {
fn write_to<W>(&self, writer: &mut W)
where
W: Write,
{
write!(writer, r#" {}="{}""#, self.0, escape_attributes(self.1)).expect(XML_WRITE_ERROR);
}
}
impl IntoAttribute for (&str, String) {
fn write_to<W>(&self, writer: &mut W)
where
W: Write,
{
write!(writer, r#" {}="{}""#, self.0, escape_attributes(&self.1)).expect(XML_WRITE_ERROR);
}
}