1#![allow(clippy::needless_return)]
3
4use sxd_document_no_unsafe::dom::{Element, ChildOfElement, Attribute};
5use sxd_document_no_unsafe::{as_str, as_qname};
6
7pub fn mml_to_string(e: Element) -> String {
19 return format_element(e, 0);
20}
21
22pub fn format_element(e: Element, indent: usize) -> String {
25 let namespace = "";
31 let mut answer = format!("{:in$}<{ns}{name}{attrs}>", " ", in=2*indent, ns=namespace, name=as_qname!(e.name()).local_part(), attrs=format_attrs(&e.attributes()));
32 let children = e.children();
33 let has_element = children.iter().find(|&&c| matches!(c, ChildOfElement::Element(_x)));
34 if has_element.is_none() {
35 let content = children.iter().fold(String::new(), |mut acc, c| {
37 if let ChildOfElement::Text(t) = c {
38 acc.push_str(as_str!(t.text()));
39 }
40 acc
41 });
42 return format!("{}{}</{}{}>\n", answer, handle_special_chars(&content), namespace, as_qname!(e.name()).local_part());
43 } else {
49 answer += "\n"; for c in e.children() {
52 if let ChildOfElement::Element(e) = c {
53 answer += &format_element(e, indent+1);
54 }
55 }
56 }
57 return answer + &format!("{:in$}</{ns}{name}>\n", " ", in=2*indent, ns=namespace, name=as_qname!(e.name()).local_part());
58
59 }
61
62pub fn format_attrs(attrs: &[Attribute]) -> String {
64 let mut result = String::new();
65 for attr in attrs {
66 result += format!(" {}='{}'", as_qname!(attr.name()).local_part(), handle_special_chars(as_str!(attr.value()))).as_str();
67 }
68 result
69}
70
71fn handle_special_chars(text: &str) -> String {
72 let mut s = String::with_capacity(text.len());
74 for ch in text.chars() {
75 match ch {
76 '"' => s.push_str("""),
77 '&' => s.push_str("&"),
78 '\'' => s.push_str("'"),
79 '<' => s.push_str("<"),
80 '>' => s.push_str(">"),
81 '\u{2061}' => s.push_str("⁡"),
82 '\u{2062}' => s.push_str("⁢"),
83 '\u{2063}' => s.push_str("⁣"),
84 '\u{2064}' => s.push_str("⁤"),
85 _ => s.push(ch),
86 }
87 }
88 s
89}
90
91
92pub fn yaml_to_string(yaml: &Yaml, indent: usize) -> String {
111 let mut result = String::new();
112 {
113 let mut emitter = YamlEmitter::new(&mut result);
114 emitter.compact(true);
115 emitter.emit_node(yaml).unwrap(); }
117 if indent == 0 {
118 return result;
119 }
120 let indent_str = format!("{:in$}", " ", in=2*indent);
121 result = result.replace('\n',&("\n".to_string() + &indent_str)); return indent_str + result.trim_end(); }
124
125fn is_scalar(v: &Yaml) -> bool {
131 return !matches!(v, Yaml::Hash(_) | Yaml::Array(_));
132}
133
134fn is_complex(v: &Yaml) -> bool {
135 return match v {
136 Yaml::Hash(h) => {
137 return match h.len() {
138 0 => false,
139 1 => {
140 let (key,val) = h.iter().next().unwrap();
141 return !(is_scalar(key) && is_scalar(val))
142 },
143 _ => true,
144 }
145 },
146 Yaml::Array(v) => {
147 return match v.len() {
148 0 => false,
149 1 => {
150 let hash = v[0].as_hash();
151 if let Some(hash) = hash {
152 return match hash.len() {
153 0 => false,
154 1 => {
155 let (key, val) = hash.iter().next().unwrap();
156 return !(is_scalar(key) && is_scalar(val));
157 },
158 _ => true,
159 }
160 } else {
161 return !is_scalar(&v[0]);
162 }
163 },
164 _ => true,
165 }
166 },
167 _ => false,
168 }
169}
170
171use std::error::Error;
172use std::fmt::{self, Display};
173use yaml_rust::{Yaml, yaml::Hash};
174
175#[derive(Copy, Clone, Debug)]
178#[allow(dead_code)] enum EmitError {
180 FmtError(fmt::Error),
181 BadHashmapKey,
182}
183
184impl Error for EmitError {
185 fn cause(&self) -> Option<&dyn Error> {
186 None
187 }
188}
189
190impl Display for EmitError {
191 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
192 match *self {
193 EmitError::FmtError(ref err) => Display::fmt(err, formatter),
194 EmitError::BadHashmapKey => formatter.write_str("bad hashmap key"),
195 }
196 }
197}
198
199impl From<fmt::Error> for EmitError {
200 fn from(f: fmt::Error) -> Self {
201 EmitError::FmtError(f)
202 }
203}
204
205struct YamlEmitter<'a> {
206 writer: &'a mut dyn fmt::Write,
207 best_indent: usize,
208 compact: bool,
209
210 level: isize,
211}
212
213type EmitResult = Result<(), EmitError>;
214
215fn escape_str(wr: &mut dyn fmt::Write, v: &str) -> Result<(), fmt::Error> {
217 wr.write_str("\"")?;
218
219 let mut start = 0;
220
221 for (i, byte) in v.bytes().enumerate() {
222 let escaped = match byte {
223 b'"' => "\\\"",
224 b'\\' => "\\\\",
225 b'\x00' => "\\u0000",
226 b'\x01' => "\\u0001",
227 b'\x02' => "\\u0002",
228 b'\x03' => "\\u0003",
229 b'\x04' => "\\u0004",
230 b'\x05' => "\\u0005",
231 b'\x06' => "\\u0006",
232 b'\x07' => "\\u0007",
233 b'\x08' => "\\b",
234 b'\t' => "\\t",
235 b'\n' => "\\n",
236 b'\x0b' => "\\u000b",
237 b'\x0c' => "\\f",
238 b'\r' => "\\r",
239 b'\x0e' => "\\u000e",
240 b'\x0f' => "\\u000f",
241 b'\x10' => "\\u0010",
242 b'\x11' => "\\u0011",
243 b'\x12' => "\\u0012",
244 b'\x13' => "\\u0013",
245 b'\x14' => "\\u0014",
246 b'\x15' => "\\u0015",
247 b'\x16' => "\\u0016",
248 b'\x17' => "\\u0017",
249 b'\x18' => "\\u0018",
250 b'\x19' => "\\u0019",
251 b'\x1a' => "\\u001a",
252 b'\x1b' => "\\u001b",
253 b'\x1c' => "\\u001c",
254 b'\x1d' => "\\u001d",
255 b'\x1e' => "\\u001e",
256 b'\x1f' => "\\u001f",
257 b'\x7f' => "\\u007f",
258 _ => continue,
259 };
260
261 if start < i {
262 wr.write_str(&v[start..i])?;
263 }
264
265 wr.write_str(escaped)?;
266
267 start = i + 1;
268 }
269
270 if start != v.len() {
271 wr.write_str(&v[start..])?;
272 }
273
274 wr.write_str("\"")?;
275 Ok(())
276}
277
278impl<'a> YamlEmitter<'a> {
279 pub fn new(writer: &'a mut dyn fmt::Write) -> YamlEmitter<'a> {
280 YamlEmitter {
281 writer,
282 best_indent: 2,
283 compact: true,
284 level: -1,
285 }
286 }
287
288 pub fn compact(&mut self, compact: bool) {
297 self.compact = compact;
298 }
299
300 #[allow(dead_code)] pub fn is_compact(&self) -> bool {
303 self.compact
304 }
305
306 fn write_indent(&mut self) -> EmitResult {
314 if self.level <= 0 {
315 return Ok(());
316 }
317 for _ in 0..self.level {
318 for _ in 0..self.best_indent {
319 write!(self.writer, " ")?;
320 }
321 }
322 Ok(())
323 }
324
325 fn emit_node(&mut self, node: &Yaml) -> EmitResult {
326 match *node {
327 Yaml::Array(ref v) => self.emit_array(v),
328 Yaml::Hash(ref h) => self.emit_hash(h),
329 Yaml::String(ref v) => {
330 if need_quotes(v) {
331 escape_str(self.writer, v)?;
332 } else {
333 write!(self.writer, "{v}")?;
334 }
335 Ok(())
336 }
337 Yaml::Boolean(v) => {
338 if v {
339 self.writer.write_str("true")?;
340 } else {
341 self.writer.write_str("false")?;
342 }
343 Ok(())
344 }
345 Yaml::Integer(v) => {
346 write!(self.writer, "{v}")?;
347 Ok(())
348 }
349 Yaml::Real(ref v) => {
350 write!(self.writer, "{v}")?;
351 Ok(())
352 }
353 Yaml::Null | Yaml::BadValue => {
354 write!(self.writer, "~")?;
355 Ok(())
356 }
357 _ => Ok(()),
359 }
360 }
361
362 fn emit_array(&mut self, v: &[Yaml]) -> EmitResult {
363 if v.is_empty() {
364 write!(self.writer, "[]")?;
365 } else if v.len() == 1 && !is_complex(&v[0]) {
366 write!(self.writer, "[")?;
368 self.emit_val(true, &v[0])?;
369 write!(self.writer, "]")?;
370 } else {
371 self.level += 1;
372
373 for (cnt, x) in v.iter().enumerate() {
374 if cnt > 0 {
375 writeln!(self.writer)?;
376 self.write_indent()?;
377 }
378 write!(self.writer, "- ")?;
379 self.emit_val(true, x)?;
380 }
381 self.level -= 1;
382 }
383 return Ok(());
384 }
385
386 fn emit_hash(&mut self, h: &Hash) -> EmitResult {
387 if h.is_empty() {
388 self.writer.write_str("{}")?;
389 } else {
390 self.level += 1;
392 for (cnt, (k, v)) in h.iter().enumerate() {
393 if cnt > 0 {
399 writeln!(self.writer)?;
400 self.write_indent()?;
401 }
402 if !is_scalar(k) {
403 write!(self.writer, "? ")?;
404 self.emit_val(true, k)?;
405 writeln!(self.writer)?;
406 self.write_indent()?;
407 write!(self.writer, ": ")?;
408 self.emit_val(true, v)?;
409 } else {
410 self.emit_node(k)?;
411 write!(self.writer, ": ")?;
412
413 let complex_value = is_complex(v);
415 if !complex_value && v.as_hash().is_some() {
416 write!(self.writer, "{{")?;
417 }
418 self.emit_val(!complex_value, v)?;
420 if !complex_value && v.as_hash().is_some() {
421 write!(self.writer, "}}")?;
422 }
423 }
424 }
425 self.level -= 1;
426 }
427 Ok(())
428 }
429
430 fn emit_val(&mut self, inline: bool, val: &Yaml) -> EmitResult {
436 match *val {
437 Yaml::Array(ref v) => {
438 if !((inline && self.compact) || v.is_empty()) {
439 writeln!(self.writer)?;
440 self.level += 1;
441 self.write_indent()?;
442 self.level -= 1;
443 }
444 self.emit_array(v)
445 }
446 Yaml::Hash(ref h) => {
447 if !((inline && self.compact) || h.is_empty()) {
448 writeln!(self.writer)?;
449 self.level += 1;
450 self.write_indent()?;
451 self.level -= 1;
452 }
453 self.emit_hash(h)
454 }
455 _ => {
456 self.emit_node(val)
458 }
459 }
460 }
461}
462
463fn need_quotes(string: &str) -> bool {
478 fn need_quotes_spaces(string: &str) -> bool {
479 string.starts_with(' ') || string.ends_with(' ')
480 }
481
482 string.is_empty()
483 || need_quotes_spaces(string)
484 || string.starts_with(['&', '*', '?', '|', '-', '<', '>', '=', '!', '%', '@'])
485 || string.contains(|character: char| matches!(character,
486 ':'
487 | '{'
488 | '}'
489 | '['
490 | ']'
491 | ','
492 | '#'
493 | '`'
494 | '\"'
495 | '\''
496 | '\\'
497 | '\0'..='\x06'
498 | '\t'
499 | '\n'
500 | '\r'
501 | '\x0e'..='\x1a'
502 | '\x1c'..='\x1f') )
503 || [
504 "yes", "Yes", "YES", "no", "No", "NO", "True", "TRUE", "true", "False", "FALSE",
509 "false", "on", "On", "ON", "off", "Off", "OFF",
510 "null", "Null", "NULL", "~",
512 ]
513 .contains(&string)
514 || string.starts_with('.')
515 || string.starts_with("0x")
516 || string.parse::<i64>().is_ok()
517 || string.parse::<f64>().is_ok()
518}
519
520#[cfg(test)]
521mod tests {
522 use super::*;
523 use sxd_document_no_unsafe::dom::{ChildOfElement, ChildOfRoot};
524 use sxd_document_no_unsafe::parser;
525
526 fn first_element(package: &sxd_document_no_unsafe::Package) -> Element<'_> {
528 let doc = package.as_document();
529 for child in doc.root().children() {
530 if let ChildOfRoot::Element(e) = child {
531 return e;
532 }
533 }
534 panic!("No root element found");
535 }
536
537 #[test]
538 fn handle_special_chars_escapes() {
541 let input = "& < > \" ' \u{2061} \u{2062} \u{2063} \u{2064} x";
542 let expected = "& < > " ' ⁡ ⁢ ⁣ ⁤ x";
543 assert_eq!(handle_special_chars(input), expected);
544 }
545
546 #[test]
547 fn format_element_leaf_text() {
549 let package = parser::parse("<math><mi>&</mi></math>").unwrap();
550 let math = first_element(&package);
551 let mi = math
552 .children()
553 .iter()
554 .find_map(|c| match c {
555 ChildOfElement::Element(e) => Some(*e),
556 _ => None,
557 })
558 .unwrap();
559 assert_eq!(format_element(mi, 0), " <mi>&</mi>\n");
560 }
561
562 #[test]
563 fn format_element_nested() {
565 let package = parser::parse("<math><mi>x</mi><mo>+</mo></math>").unwrap();
566 let math = first_element(&package);
567 let rendered = format_element(math, 0);
568 assert!(rendered.starts_with(" <math>\n"));
569 assert!(rendered.contains("\n <mi>x</mi>\n"));
570 assert!(rendered.contains("\n <mo>+</mo>\n"));
571 assert!(rendered.ends_with("</math>\n"));
572 }
573
574 #[test]
575 fn format_attrs_escapes() {
577 let package = parser::parse("<math a=\"&\" b=\"<\"></math>").unwrap();
578 let math = first_element(&package);
579 let rendered = format_attrs(&math.attributes());
580 assert!(rendered.contains(" a='&'"));
581 assert!(rendered.contains(" b='<'"));
582 }
583
584 #[test]
585 fn format_element_non_bmp_character_literal() {
587 let package = parser::parse("<math><mi>𝞪</mi></math>").unwrap();
588 let math = first_element(&package);
589 let mi = math
590 .children()
591 .iter()
592 .find_map(|c| match c {
593 ChildOfElement::Element(e) => Some(*e),
594 _ => None,
595 })
596 .unwrap();
597 let rendered = format_element(mi, 0);
598 assert!(rendered.contains("𝞪"));
599 }
600
601 #[test]
602 fn format_element_non_bmp_character_numeric() {
604 let package = parser::parse("<math><mi>𝞪</mi></math>").unwrap();
605 let math = first_element(&package);
606 let mi = math
607 .children()
608 .iter()
609 .find_map(|c| match c {
610 ChildOfElement::Element(e) => Some(*e),
611 _ => None,
612 })
613 .unwrap();
614 let rendered = format_element(mi, 0);
615 assert!(rendered.contains("𝞪"));
616 }
617
618 #[test]
619 fn xpath_non_bmp_literal() {
621 use sxd_xpath_no_unsafe::{Factory, Value};
622
623 let package = parser::parse("<math><mi>𝞪</mi></math>").unwrap();
624 let xpath = Factory::new().build("string(/math/mi)").unwrap();
625 let context = sxd_xpath_no_unsafe::Context::new();
626
627 let value = xpath.evaluate(&context, first_element(&package)).unwrap();
628 match value {
629 Value::String(s) => assert_eq!(s, "𝞪"),
630 _ => panic!("Expected string value from xpath"),
631 }
632 }
633
634 #[test]
635 fn xpath_non_bmp_numeric() {
637 use sxd_xpath_no_unsafe::{Factory, Value};
638
639 let package = parser::parse("<math><mi>𝞪</mi></math>").unwrap();
640 let xpath = Factory::new().build("string(/math/mi)").unwrap();
641 let context = sxd_xpath_no_unsafe::Context::new();
642
643 let value = xpath.evaluate(&context, first_element(&package)).unwrap();
644 match value {
645 Value::String(s) => assert_eq!(s, "𝞪"),
646 _ => panic!("Expected string value from xpath"),
647 }
648 }
649
650 #[test]
651 fn xpath_non_bmp_namespace_literal() {
653 use sxd_xpath_no_unsafe::{Factory, Value};
654
655 let xml = "<math xmlns=\"http://www.w3.org/1998/Math/MathML\"><mi>𝞪</mi></math>";
656 let package = parser::parse(xml).unwrap();
657 let xpath = Factory::new()
658 .build("string(/m:math/m:mi)")
659 .unwrap();
660 let mut context = sxd_xpath_no_unsafe::Context::new();
661 context.set_namespace("m", "http://www.w3.org/1998/Math/MathML");
662
663 let value = xpath.evaluate(&context, first_element(&package)).unwrap();
664 match value {
665 Value::String(s) => assert_eq!(s, "𝞪"),
666 _ => panic!("Expected string value from xpath"),
667 }
668 }
669
670 #[test]
671 fn xpath_non_bmp_namespace_numeric() {
673 use sxd_xpath_no_unsafe::{Factory, Value};
674
675 let xml = "<math xmlns=\"http://www.w3.org/1998/Math/MathML\"><mi>𝞪</mi></math>";
676 let package = parser::parse(xml).unwrap();
677 let xpath = Factory::new()
678 .build("string(/m:math/m:mi)")
679 .unwrap();
680 let mut context = sxd_xpath_no_unsafe::Context::new();
681 context.set_namespace("m", "http://www.w3.org/1998/Math/MathML");
682
683 let value = xpath.evaluate(&context, first_element(&package)).unwrap();
684 match value {
685 Value::String(s) => assert_eq!(s, "𝞪"),
686 _ => panic!("Expected string value from xpath"),
687 }
688 }
689
690 #[test]
691 fn xpath_non_bmp_text_nodeset() {
693 use sxd_xpath_no_unsafe::{Factory, Value};
694
695 let xml = "<math xmlns=\"http://www.w3.org/1998/Math/MathML\"><mi>𝞪</mi></math>";
696 let package = parser::parse(xml).unwrap();
697 let xpath = Factory::new().build("/m:math/m:mi/text()").unwrap();
698 let mut context = sxd_xpath_no_unsafe::Context::new();
699 context.set_namespace("m", "http://www.w3.org/1998/Math/MathML");
700
701 let value = xpath.evaluate(&context, first_element(&package)).unwrap();
702 match value {
703 Value::Nodeset(nodes) => {
704 let ordered = nodes.document_order();
705 let node = ordered.first().expect("Expected one text node");
706 let text = node.text().expect("Expected text node");
707 assert_eq!(text.text(), "𝞪");
708 assert_eq!(ordered.len(), 1);
709 }
710 _ => panic!("Expected nodeset value from xpath"),
711 }
712 }
713}