use std::fmt;
use crate::engine::{
encode_loop, is_invalid_for_xml, write_markup, InvalidCharPolicy, MarkupConfig,
};
pub fn for_xml(input: &str) -> String {
crate::html::for_html(input)
}
pub fn write_xml<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
crate::html::write_html(out, input)
}
pub fn for_xml_content(input: &str) -> String {
crate::html::for_html_content(input)
}
pub fn write_xml_content<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
crate::html::write_html_content(out, input)
}
pub fn for_xml_attribute(input: &str) -> String {
crate::html::for_html_attribute(input)
}
pub fn write_xml_attribute<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
crate::html::write_html_attribute(out, input)
}
pub fn for_xml_comment(input: &str) -> String {
let mut out = String::with_capacity(input.len());
write_xml_comment(&mut out, input).expect("writing to string cannot fail");
out
}
pub fn write_xml_comment<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
let mut last_was_hyphen = false;
encode_loop(
out,
input,
|c| c == '-' || is_invalid_for_xml(c),
|out, c, next| {
if c != '-' {
last_was_hyphen = false;
out.write_char(' ')
} else if last_was_hyphen {
last_was_hyphen = false;
out.write_char('~')
} else if next.is_none() {
out.write_char('~')
} else {
last_was_hyphen = next == Some('-');
out.write_char('-')
}
},
)
}
pub fn for_cdata(input: &str) -> String {
let mut out = String::with_capacity(input.len());
write_cdata(&mut out, input).expect("writing to string cannot fail");
out
}
pub fn write_cdata<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
let mut bracket_count: u32 = 0;
encode_loop(
out,
input,
|c| c == ']' || c == '>' || is_invalid_for_xml(c),
|out, c, next| {
if c == ']' {
if next.is_none() {
return out.write_str("]]]><![CDATA[");
}
bracket_count += 1;
if next != Some(']') && next != Some('>') {
bracket_count = 0;
}
out.write_char(']')
} else if c == '>' {
let split = bracket_count >= 2;
bracket_count = 0;
if split {
out.write_str("]]><![CDATA[>")
} else {
out.write_char('>')
}
} else {
bracket_count = 0;
out.write_char(' ')
}
},
)
}
const XML11_FULL: MarkupConfig = MarkupConfig {
encode_gt: true,
encode_quotes: true,
invalid: InvalidCharPolicy::Xml11Reference,
};
const XML11_CONTENT: MarkupConfig = MarkupConfig {
encode_gt: true,
encode_quotes: false,
invalid: InvalidCharPolicy::Xml11Reference,
};
const XML11_ATTRIBUTE: MarkupConfig = MarkupConfig {
encode_gt: false,
encode_quotes: true,
invalid: InvalidCharPolicy::Xml11Reference,
};
pub fn for_xml11(input: &str) -> String {
let mut out = String::with_capacity(input.len());
write_xml11(&mut out, input).expect("writing to string cannot fail");
out
}
pub fn write_xml11<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
write_markup(out, input, &XML11_FULL)
}
pub fn for_xml11_content(input: &str) -> String {
let mut out = String::with_capacity(input.len());
write_xml11_content(&mut out, input).expect("writing to string cannot fail");
out
}
pub fn write_xml11_content<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
write_markup(out, input, &XML11_CONTENT)
}
pub fn for_xml11_attribute(input: &str) -> String {
let mut out = String::with_capacity(input.len());
write_xml11_attribute(&mut out, input).expect("writing to string cannot fail");
out
}
pub fn write_xml11_attribute<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
write_markup(out, input, &XML11_ATTRIBUTE)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn xml_aliases_match_html() {
let input = r#"<b attr="val">&</b>"#;
assert_eq!(for_xml(input), crate::html::for_html(input));
assert_eq!(for_xml_content(input), crate::html::for_html_content(input));
assert_eq!(
for_xml_attribute(input),
crate::html::for_html_attribute(input)
);
}
#[test]
fn comment_passthrough() {
assert_eq!(for_xml_comment("safe text"), "safe text");
assert_eq!(for_xml_comment(""), "");
}
#[test]
fn comment_double_hyphen() {
assert_eq!(for_xml_comment("a--b"), "a-~b");
assert_eq!(for_xml_comment("--"), "-~");
assert_eq!(for_xml_comment("---"), "-~~");
assert_eq!(for_xml_comment("----"), "-~-~");
assert_eq!(for_xml_comment("a--b--c"), "a-~b-~c");
}
#[test]
fn comment_trailing_hyphen() {
assert_eq!(for_xml_comment("trailing-"), "trailing~");
assert_eq!(for_xml_comment("-"), "~");
}
#[test]
fn comment_hyphen_not_paired_across_run() {
assert_eq!(for_xml_comment("-a-b"), "-a-b");
assert_eq!(for_xml_comment("a-b-c"), "a-b-c");
}
#[test]
fn comment_replaces_invalid_xml() {
assert_eq!(for_xml_comment("a\x01b"), "a b");
assert_eq!(for_xml_comment("a\x7Fb"), "a b");
}
#[test]
fn comment_preserves_non_ascii() {
assert_eq!(for_xml_comment("café"), "café");
}
#[test]
fn comment_writer_variant() {
let mut out = String::new();
write_xml_comment(&mut out, "a--b").unwrap();
assert_eq!(out, "a-~b");
}
#[test]
fn cdata_passthrough() {
assert_eq!(for_cdata("safe text"), "safe text");
assert_eq!(for_cdata(""), "");
}
#[test]
fn cdata_splits_closing_delimiter() {
assert_eq!(for_cdata("a]]>b"), "a]]]]><![CDATA[>b");
}
#[test]
fn cdata_double_split() {
assert_eq!(for_cdata("a]]>b]]>c"), "a]]]]><![CDATA[>b]]]]><![CDATA[>c");
}
#[test]
fn cdata_brackets_without_gt() {
assert_eq!(for_cdata("]]a"), "]]a");
assert_eq!(for_cdata("a]b"), "a]b");
}
#[test]
fn cdata_splits_trailing_bracket() {
assert_eq!(for_cdata("]"), "]]]><![CDATA[");
assert_eq!(for_cdata("]]"), "]]]]><![CDATA[");
assert_eq!(for_cdata("]]]"), "]]]]]><![CDATA[");
assert_eq!(for_cdata("a]"), "a]]]><![CDATA[");
}
#[test]
fn cdata_writes_cannot_form_a_delimiter_across_the_boundary() {
let mut out = String::new();
write_cdata(&mut out, "a]]").unwrap();
write_cdata(&mut out, ">b").unwrap();
assert_eq!(out, "a]]]]><![CDATA[>b");
let mut out = String::new();
write_cdata(&mut out, "a]").unwrap();
write_cdata(&mut out, "]>b").unwrap();
assert_eq!(out, "a]]]><![CDATA[]>b");
}
#[test]
fn cdata_bracket_run_reset_before_gt() {
assert_eq!(for_cdata("]]a>"), "]]a>");
assert_eq!(for_cdata("]] >"), "]] >");
}
#[test]
fn cdata_extra_brackets() {
assert_eq!(for_cdata("]]]>"), "]]]]]><![CDATA[>");
}
#[test]
fn cdata_replaces_invalid_xml() {
assert_eq!(for_cdata("a\x01b"), "a b");
}
#[test]
fn cdata_single_bracket_gt() {
assert_eq!(for_cdata("]>"), "]>");
}
#[test]
fn cdata_writer_variant() {
let mut out = String::new();
write_cdata(&mut out, "a]]>b").unwrap();
assert_eq!(out, "a]]]]><![CDATA[>b");
}
#[test]
fn xml11_encodes_entities() {
assert_eq!(for_xml11("<&>\"'"), "<&>"'");
}
#[test]
fn xml11_controls_as_references() {
assert_eq!(for_xml11("a\x01b"), "ab");
assert_eq!(for_xml11("a\x08b"), "ab");
assert_eq!(for_xml11("a\x0Bb"), "ab");
assert_eq!(for_xml11("a\x1Fb"), "ab");
}
#[test]
fn xml11_nel_passes_through() {
assert_eq!(for_xml11("a\u{0085}b"), "a\u{0085}b");
}
#[test]
fn xml11_del_and_c1_as_references() {
assert_eq!(for_xml11("a\x7Fb"), "ab");
assert_eq!(for_xml11("a\u{0080}b"), "a€b");
assert_eq!(for_xml11("a\u{009F}b"), "aŸb");
}
#[test]
fn xml11_nul_replaced_with_space() {
assert_eq!(for_xml11("a\x00b"), "a b");
}
#[test]
fn xml11_nonchars_replaced_with_space() {
assert_eq!(for_xml11("a\u{FDD0}b"), "a b");
}
#[test]
fn xml11_preserves_tab_lf_cr() {
assert_eq!(for_xml11("a\tb\nc\rd"), "a\tb\nc\rd");
}
#[test]
fn xml11_content_no_quotes() {
assert_eq!(for_xml11_content(r#"a"b'c"#), r#"a"b'c"#);
assert_eq!(for_xml11_content("a\x01b"), "ab");
}
#[test]
fn xml11_attribute_no_gt() {
assert_eq!(for_xml11_attribute("a>b"), "a>b");
assert_eq!(for_xml11_attribute("a\x01b"), "ab");
}
}