1use std::fmt;
31
32use crate::engine::{
33 encode_loop, is_invalid_for_xml, write_markup, InvalidCharPolicy, MarkupConfig,
34};
35
36pub fn for_xml(input: &str) -> String {
48 crate::html::for_html(input)
49}
50
51pub fn write_xml<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
55 crate::html::write_html(out, input)
56}
57
58pub fn for_xml_content(input: &str) -> String {
71 crate::html::for_html_content(input)
72}
73
74pub fn write_xml_content<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
78 crate::html::write_html_content(out, input)
79}
80
81pub fn for_xml_attribute(input: &str) -> String {
94 crate::html::for_html_attribute(input)
95}
96
97pub fn write_xml_attribute<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
101 crate::html::write_html_attribute(out, input)
102}
103
104pub fn for_xml_comment(input: &str) -> String {
129 let mut out = String::with_capacity(input.len());
130 write_xml_comment(&mut out, input).expect("writing to string cannot fail");
131 out
132}
133
134pub fn write_xml_comment<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
138 let mut last_was_hyphen = false;
139 encode_loop(
140 out,
141 input,
142 |c| c == '-' || is_invalid_for_xml(c),
143 |out, c, next| {
144 if c != '-' {
145 last_was_hyphen = false;
146 out.write_char(' ')
147 } else if last_was_hyphen {
148 last_was_hyphen = false;
149 out.write_char('~')
150 } else if next.is_none() {
151 out.write_char('~')
152 } else {
153 last_was_hyphen = next == Some('-');
154 out.write_char('-')
155 }
156 },
157 )
158}
159
160pub fn for_cdata(input: &str) -> String {
187 let mut out = String::with_capacity(input.len());
188 write_cdata(&mut out, input).expect("writing to string cannot fail");
189 out
190}
191
192pub fn write_cdata<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
196 let mut bracket_count: u32 = 0;
197 encode_loop(
198 out,
199 input,
200 |c| c == ']' || c == '>' || is_invalid_for_xml(c),
201 |out, c, next| {
202 if c == ']' {
203 if next.is_none() {
204 return out.write_str("]]]><![CDATA[");
206 }
207 bracket_count += 1;
208 if next != Some(']') && next != Some('>') {
209 bracket_count = 0;
210 }
211 out.write_char(']')
212 } else if c == '>' {
213 let split = bracket_count >= 2;
214 bracket_count = 0;
215 if split {
216 out.write_str("]]><![CDATA[>")
217 } else {
218 out.write_char('>')
219 }
220 } else {
221 bracket_count = 0;
222 out.write_char(' ')
223 }
224 },
225 )
226}
227
228const XML11_FULL: MarkupConfig = MarkupConfig {
229 encode_gt: true,
230 encode_quotes: true,
231 invalid: InvalidCharPolicy::Xml11Reference,
232};
233
234const XML11_CONTENT: MarkupConfig = MarkupConfig {
235 encode_gt: true,
236 encode_quotes: false,
237 invalid: InvalidCharPolicy::Xml11Reference,
238};
239
240const XML11_ATTRIBUTE: MarkupConfig = MarkupConfig {
241 encode_gt: false,
242 encode_quotes: true,
243 invalid: InvalidCharPolicy::Xml11Reference,
244};
245
246pub fn for_xml11(input: &str) -> String {
267 let mut out = String::with_capacity(input.len());
268 write_xml11(&mut out, input).expect("writing to string cannot fail");
269 out
270}
271
272pub fn write_xml11<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
276 write_markup(out, input, &XML11_FULL)
277}
278
279pub fn for_xml11_content(input: &str) -> String {
293 let mut out = String::with_capacity(input.len());
294 write_xml11_content(&mut out, input).expect("writing to string cannot fail");
295 out
296}
297
298pub fn write_xml11_content<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
302 write_markup(out, input, &XML11_CONTENT)
303}
304
305pub fn for_xml11_attribute(input: &str) -> String {
319 let mut out = String::with_capacity(input.len());
320 write_xml11_attribute(&mut out, input).expect("writing to string cannot fail");
321 out
322}
323
324pub fn write_xml11_attribute<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
328 write_markup(out, input, &XML11_ATTRIBUTE)
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334
335 #[test]
338 fn xml_aliases_match_html() {
339 let input = r#"<b attr="val">&</b>"#;
340 assert_eq!(for_xml(input), crate::html::for_html(input));
341 assert_eq!(for_xml_content(input), crate::html::for_html_content(input));
342 assert_eq!(
343 for_xml_attribute(input),
344 crate::html::for_html_attribute(input)
345 );
346 }
347
348 #[test]
351 fn comment_passthrough() {
352 assert_eq!(for_xml_comment("safe text"), "safe text");
353 assert_eq!(for_xml_comment(""), "");
354 }
355
356 #[test]
357 fn comment_double_hyphen() {
358 assert_eq!(for_xml_comment("a--b"), "a-~b");
359 assert_eq!(for_xml_comment("--"), "-~");
360 assert_eq!(for_xml_comment("---"), "-~~");
361 assert_eq!(for_xml_comment("----"), "-~-~");
362 assert_eq!(for_xml_comment("a--b--c"), "a-~b-~c");
363 }
364
365 #[test]
366 fn comment_trailing_hyphen() {
367 assert_eq!(for_xml_comment("trailing-"), "trailing~");
368 assert_eq!(for_xml_comment("-"), "~");
369 }
370
371 #[test]
372 fn comment_hyphen_not_paired_across_run() {
373 assert_eq!(for_xml_comment("-a-b"), "-a-b");
374 assert_eq!(for_xml_comment("a-b-c"), "a-b-c");
375 }
376
377 #[test]
378 fn comment_replaces_invalid_xml() {
379 assert_eq!(for_xml_comment("a\x01b"), "a b");
380 assert_eq!(for_xml_comment("a\x7Fb"), "a b");
381 }
382
383 #[test]
384 fn comment_preserves_non_ascii() {
385 assert_eq!(for_xml_comment("café"), "café");
386 }
387
388 #[test]
389 fn comment_writer_variant() {
390 let mut out = String::new();
391 write_xml_comment(&mut out, "a--b").unwrap();
392 assert_eq!(out, "a-~b");
393 }
394
395 #[test]
398 fn cdata_passthrough() {
399 assert_eq!(for_cdata("safe text"), "safe text");
400 assert_eq!(for_cdata(""), "");
401 }
402
403 #[test]
404 fn cdata_splits_closing_delimiter() {
405 assert_eq!(for_cdata("a]]>b"), "a]]]]><![CDATA[>b");
406 }
407
408 #[test]
409 fn cdata_double_split() {
410 assert_eq!(for_cdata("a]]>b]]>c"), "a]]]]><![CDATA[>b]]]]><![CDATA[>c");
411 }
412
413 #[test]
414 fn cdata_brackets_without_gt() {
415 assert_eq!(for_cdata("]]a"), "]]a");
416 assert_eq!(for_cdata("a]b"), "a]b");
417 }
418
419 #[test]
420 fn cdata_splits_trailing_bracket() {
421 assert_eq!(for_cdata("]"), "]]]><![CDATA[");
422 assert_eq!(for_cdata("]]"), "]]]]><![CDATA[");
423 assert_eq!(for_cdata("]]]"), "]]]]]><![CDATA[");
424 assert_eq!(for_cdata("a]"), "a]]]><![CDATA[");
425 }
426
427 #[test]
428 fn cdata_writes_cannot_form_a_delimiter_across_the_boundary() {
429 let mut out = String::new();
430 write_cdata(&mut out, "a]]").unwrap();
431 write_cdata(&mut out, ">b").unwrap();
432 assert_eq!(out, "a]]]]><![CDATA[>b");
433
434 let mut out = String::new();
435 write_cdata(&mut out, "a]").unwrap();
436 write_cdata(&mut out, "]>b").unwrap();
437 assert_eq!(out, "a]]]><![CDATA[]>b");
438 }
439
440 #[test]
441 fn cdata_bracket_run_reset_before_gt() {
442 assert_eq!(for_cdata("]]a>"), "]]a>");
443 assert_eq!(for_cdata("]] >"), "]] >");
444 }
445
446 #[test]
447 fn cdata_extra_brackets() {
448 assert_eq!(for_cdata("]]]>"), "]]]]]><![CDATA[>");
450 }
451
452 #[test]
453 fn cdata_replaces_invalid_xml() {
454 assert_eq!(for_cdata("a\x01b"), "a b");
455 }
456
457 #[test]
458 fn cdata_single_bracket_gt() {
459 assert_eq!(for_cdata("]>"), "]>");
461 }
462
463 #[test]
464 fn cdata_writer_variant() {
465 let mut out = String::new();
466 write_cdata(&mut out, "a]]>b").unwrap();
467 assert_eq!(out, "a]]]]><![CDATA[>b");
468 }
469
470 #[test]
473 fn xml11_encodes_entities() {
474 assert_eq!(for_xml11("<&>\"'"), "<&>"'");
475 }
476
477 #[test]
478 fn xml11_controls_as_references() {
479 assert_eq!(for_xml11("a\x01b"), "ab");
481 assert_eq!(for_xml11("a\x08b"), "ab");
482 assert_eq!(for_xml11("a\x0Bb"), "ab");
483 assert_eq!(for_xml11("a\x1Fb"), "ab");
484 }
485
486 #[test]
487 fn xml11_nel_passes_through() {
488 assert_eq!(for_xml11("a\u{0085}b"), "a\u{0085}b");
490 }
491
492 #[test]
493 fn xml11_del_and_c1_as_references() {
494 assert_eq!(for_xml11("a\x7Fb"), "ab");
495 assert_eq!(for_xml11("a\u{0080}b"), "a€b");
496 assert_eq!(for_xml11("a\u{009F}b"), "aŸb");
497 }
498
499 #[test]
500 fn xml11_nul_replaced_with_space() {
501 assert_eq!(for_xml11("a\x00b"), "a b");
502 }
503
504 #[test]
505 fn xml11_nonchars_replaced_with_space() {
506 assert_eq!(for_xml11("a\u{FDD0}b"), "a b");
507 }
508
509 #[test]
510 fn xml11_preserves_tab_lf_cr() {
511 assert_eq!(for_xml11("a\tb\nc\rd"), "a\tb\nc\rd");
512 }
513
514 #[test]
515 fn xml11_content_no_quotes() {
516 assert_eq!(for_xml11_content(r#"a"b'c"#), r#"a"b'c"#);
517 assert_eq!(for_xml11_content("a\x01b"), "ab");
518 }
519
520 #[test]
521 fn xml11_attribute_no_gt() {
522 assert_eq!(for_xml11_attribute("a>b"), "a>b");
523 assert_eq!(for_xml11_attribute("a\x01b"), "ab");
524 }
525}