@@ -513,3 +513,200 @@ fn need_quotes(string: &str) -> bool {
513513 || string. parse :: < i64 > ( ) . is_ok ( )
514514 || string. parse :: < f64 > ( ) . is_ok ( )
515515}
516+
517+ #[ cfg( test) ]
518+ mod tests {
519+ use super :: * ;
520+ use sxd_document:: dom:: { ChildOfElement , ChildOfRoot } ;
521+ use sxd_document:: parser;
522+
523+ /// helper function
524+ fn first_element ( package : & sxd_document:: Package ) -> Element < ' _ > {
525+ let doc = package. as_document ( ) ;
526+ for child in doc. root ( ) . children ( ) {
527+ if let ChildOfRoot :: Element ( e) = child {
528+ return e;
529+ }
530+ }
531+ panic ! ( "No root element found" ) ;
532+ }
533+
534+ #[ test]
535+ /// Escapes XML entities and invisible characters for safe display.
536+ /// Tests the method on a few hardcoded characters.
537+ fn handle_special_chars_escapes ( ) {
538+ let input = "& < > \" ' \u{2061} \u{2062} \u{2063} \u{2064} x" ;
539+ let expected = "& < > " ' ⁡ ⁢ ⁣ ⁤ x" ;
540+ assert_eq ! ( handle_special_chars( input) , expected) ;
541+ }
542+
543+ #[ test]
544+ /// Formats a leaf element as a single line with escaped text.
545+ fn format_element_leaf_text ( ) {
546+ let package = parser:: parse ( "<math><mi>&</mi></math>" ) . unwrap ( ) ;
547+ let math = first_element ( & package) ;
548+ let mi = math
549+ . children ( )
550+ . iter ( )
551+ . find_map ( |c| match c {
552+ ChildOfElement :: Element ( e) => Some ( * e) ,
553+ _ => None ,
554+ } )
555+ . unwrap ( ) ;
556+ assert_eq ! ( format_element( mi, 0 ) , " <mi>&</mi>\n " ) ;
557+ }
558+
559+ #[ test]
560+ /// Formats a nested element with indentation and newlines.
561+ fn format_element_nested ( ) {
562+ let package = parser:: parse ( "<math><mi>x</mi><mo>+</mo></math>" ) . unwrap ( ) ;
563+ let math = first_element ( & package) ;
564+ let rendered = format_element ( math, 0 ) ;
565+ assert ! ( rendered. starts_with( " <math>\n " ) ) ;
566+ assert ! ( rendered. contains( "\n <mi>x</mi>\n " ) ) ;
567+ assert ! ( rendered. contains( "\n <mo>+</mo>\n " ) ) ;
568+ assert ! ( rendered. ends_with( "</math>\n " ) ) ;
569+ }
570+
571+ #[ test]
572+ /// Escapes special characters in attribute values.
573+ fn format_attrs_escapes ( ) {
574+ let package = parser:: parse ( "<math a=\" &\" b=\" <\" ></math>" ) . unwrap ( ) ;
575+ let math = first_element ( & package) ;
576+ let rendered = format_attrs ( & math. attributes ( ) ) ;
577+ assert ! ( rendered. contains( " a='&'" ) ) ;
578+ assert ! ( rendered. contains( " b='<'" ) ) ;
579+ }
580+
581+ #[ test]
582+ /// Preserves non-BMP characters from a literal XML form.
583+ fn format_element_non_bmp_character_literal ( ) {
584+ let package = parser:: parse ( "<math><mi>𝞪</mi></math>" ) . unwrap ( ) ;
585+ let math = first_element ( & package) ;
586+ let mi = math
587+ . children ( )
588+ . iter ( )
589+ . find_map ( |c| match c {
590+ ChildOfElement :: Element ( e) => Some ( * e) ,
591+ _ => None ,
592+ } )
593+ . unwrap ( ) ;
594+ let rendered = format_element ( mi, 0 ) ;
595+ assert ! ( rendered. contains( "𝞪" ) ) ;
596+ }
597+
598+ #[ test]
599+ /// Preserves non-BMP characters from a numeric XML form.
600+ fn format_element_non_bmp_character_numeric ( ) {
601+ let package = parser:: parse ( "<math><mi>𝞪</mi></math>" ) . unwrap ( ) ;
602+ let math = first_element ( & package) ;
603+ let mi = math
604+ . children ( )
605+ . iter ( )
606+ . find_map ( |c| match c {
607+ ChildOfElement :: Element ( e) => Some ( * e) ,
608+ _ => None ,
609+ } )
610+ . unwrap ( ) ;
611+ let rendered = format_element ( mi, 0 ) ;
612+ assert ! ( rendered. contains( "𝞪" ) ) ;
613+ }
614+
615+ #[ test]
616+ /// Evaluates non-BMP literal text through sxd_xpath.
617+ fn xpath_non_bmp_literal ( ) {
618+ use sxd_xpath:: { Factory , Value } ;
619+
620+ let package = parser:: parse ( "<math><mi>𝞪</mi></math>" ) . unwrap ( ) ;
621+ let xpath = Factory :: new ( ) . build ( "string(/math/mi)" ) . unwrap ( ) . unwrap ( ) ;
622+ let context = sxd_xpath:: Context :: new ( ) ;
623+
624+ let value = xpath. evaluate ( & context, first_element ( & package) ) . unwrap ( ) ;
625+ match value {
626+ Value :: String ( s) => assert_eq ! ( s, "𝞪" ) ,
627+ _ => panic ! ( "Expected string value from xpath" ) ,
628+ }
629+ }
630+
631+ #[ test]
632+ /// Evaluates non-BMP numeric text through sxd_xpath.
633+ fn xpath_non_bmp_numeric ( ) {
634+ use sxd_xpath:: { Factory , Value } ;
635+
636+ let package = parser:: parse ( "<math><mi>𝞪</mi></math>" ) . unwrap ( ) ;
637+ let xpath = Factory :: new ( ) . build ( "string(/math/mi)" ) . unwrap ( ) . unwrap ( ) ;
638+ let context = sxd_xpath:: Context :: new ( ) ;
639+
640+ let value = xpath. evaluate ( & context, first_element ( & package) ) . unwrap ( ) ;
641+ match value {
642+ Value :: String ( s) => assert_eq ! ( s, "𝞪" ) ,
643+ _ => panic ! ( "Expected string value from xpath" ) ,
644+ }
645+ }
646+
647+ #[ test]
648+ /// Evaluates non-BMP literal text with a MathML namespace-qualified XPath.
649+ fn xpath_non_bmp_namespace_literal ( ) {
650+ use sxd_xpath:: { Factory , Value } ;
651+
652+ let xml = "<math xmlns=\" http://www.w3.org/1998/Math/MathML\" ><mi>𝞪</mi></math>" ;
653+ let package = parser:: parse ( xml) . unwrap ( ) ;
654+ let xpath = Factory :: new ( )
655+ . build ( "string(/m:math/m:mi)" )
656+ . unwrap ( )
657+ . unwrap ( ) ;
658+ let mut context = sxd_xpath:: Context :: new ( ) ;
659+ context. set_namespace ( "m" , "http://www.w3.org/1998/Math/MathML" ) ;
660+
661+ let value = xpath. evaluate ( & context, first_element ( & package) ) . unwrap ( ) ;
662+ match value {
663+ Value :: String ( s) => assert_eq ! ( s, "𝞪" ) ,
664+ _ => panic ! ( "Expected string value from xpath" ) ,
665+ }
666+ }
667+
668+ #[ test]
669+ /// Evaluates non-BMP numeric text with a MathML namespace-qualified XPath.
670+ fn xpath_non_bmp_namespace_numeric ( ) {
671+ use sxd_xpath:: { Factory , Value } ;
672+
673+ let xml = "<math xmlns=\" http://www.w3.org/1998/Math/MathML\" ><mi>𝞪</mi></math>" ;
674+ let package = parser:: parse ( xml) . unwrap ( ) ;
675+ let xpath = Factory :: new ( )
676+ . build ( "string(/m:math/m:mi)" )
677+ . unwrap ( )
678+ . unwrap ( ) ;
679+ let mut context = sxd_xpath:: Context :: new ( ) ;
680+ context. set_namespace ( "m" , "http://www.w3.org/1998/Math/MathML" ) ;
681+
682+ let value = xpath. evaluate ( & context, first_element ( & package) ) . unwrap ( ) ;
683+ match value {
684+ Value :: String ( s) => assert_eq ! ( s, "𝞪" ) ,
685+ _ => panic ! ( "Expected string value from xpath" ) ,
686+ }
687+ }
688+
689+ #[ test]
690+ /// Extracts a text node via XPath (nodeset result) and verifies the non-BMP character survives.
691+ fn xpath_non_bmp_text_nodeset ( ) {
692+ use sxd_xpath:: { Factory , Value } ;
693+
694+ let xml = "<math xmlns=\" http://www.w3.org/1998/Math/MathML\" ><mi>𝞪</mi></math>" ;
695+ let package = parser:: parse ( xml) . unwrap ( ) ;
696+ let xpath = Factory :: new ( ) . build ( "/m:math/m:mi/text()" ) . unwrap ( ) . unwrap ( ) ;
697+ let mut context = sxd_xpath:: Context :: new ( ) ;
698+ context. set_namespace ( "m" , "http://www.w3.org/1998/Math/MathML" ) ;
699+
700+ let value = xpath. evaluate ( & context, first_element ( & package) ) . unwrap ( ) ;
701+ match value {
702+ Value :: Nodeset ( nodes) => {
703+ let ordered = nodes. document_order ( ) ;
704+ let node = ordered. first ( ) . expect ( "Expected one text node" ) ;
705+ let text = node. text ( ) . expect ( "Expected text node" ) ;
706+ assert_eq ! ( text. text( ) , "𝞪" ) ;
707+ assert_eq ! ( ordered. len( ) , 1 ) ;
708+ }
709+ _ => panic ! ( "Expected nodeset value from xpath" ) ,
710+ }
711+ }
712+ }
0 commit comments