@@ -135,12 +135,28 @@ struct C14nContext<'a> {
135135
136136impl < ' a > C14nContext < ' a > {
137137 fn new ( doc : & ' a Document , options : & ' a C14nOptions ) -> Self {
138+ // Seed the rendered namespace stack with the implicit `xml` prefix
139+ // binding. The XML Namespaces spec reserves `xml` as always bound to
140+ // `http://www.w3.org/XML/1998/namespace`. Canonical XML 1.0 §2.3
141+ // ("Processing Model") requires that this binding is never emitted:
142+ // "omit namespace node with local name xml, which defines the
143+ // xml prefix, if its string value is
144+ // http://www.w3.org/XML/1998/namespace"
145+ // Exclusive C14N §3 defines itself as a variant of Canonical XML and
146+ // does not restate this rule — it inherits it. Pre-populating the
147+ // binding here makes the dedup check in `compute_ns_declarations`
148+ // filter it out automatically when an element uses `xml:lang`,
149+ // `xml:space`, or `xml:base`.
150+ let mut initial_bindings = NsBinding :: new ( ) ;
151+ initial_bindings. insert (
152+ "xml" . to_string ( ) ,
153+ "http://www.w3.org/XML/1998/namespace" . to_string ( ) ,
154+ ) ;
138155 Self {
139156 doc,
140157 options,
141158 output : String :: new ( ) ,
142- // Start with the implicit xml namespace binding.
143- rendered_ns_stack : vec ! [ NsBinding :: new( ) ] ,
159+ rendered_ns_stack : vec ! [ initial_bindings] ,
144160 }
145161 }
146162
@@ -1018,4 +1034,70 @@ mod tests {
10181034 let result = canonicalize ( & doc, & C14nOptions :: default ( ) ) ;
10191035 assert_eq ! ( result, "<?before?>\n <root></root>\n <?after?>" ) ;
10201036 }
1037+
1038+ #[ test]
1039+ fn test_c14n_inclusive_xml_prefix_not_emitted ( ) {
1040+ // XML Namespaces reserves the `xml` prefix as implicitly bound to
1041+ // `http://www.w3.org/XML/1998/namespace`. Canonical XML §2.3 requires
1042+ // that this binding is never emitted as an `xmlns:xml` declaration.
1043+ let result = c14n ( "<root xml:lang=\" en\" >hello</root>" ) ;
1044+ assert_eq ! ( result, "<root xml:lang=\" en\" >hello</root>" ) ;
1045+ assert ! (
1046+ !result. contains( "xmlns:xml" ) ,
1047+ "implicit xml namespace should not be emitted, got: {result}"
1048+ ) ;
1049+ }
1050+
1051+ #[ test]
1052+ fn test_c14n_exclusive_xml_prefix_not_emitted_on_root ( ) {
1053+ // Exclusive C14N §2.4 treats the `xml` prefix the same as inclusive:
1054+ // it is always implicitly bound and must never be emitted.
1055+ let doc = Document :: parse_str ( "<root xml:lang=\" en\" >hello</root>" ) . unwrap ( ) ;
1056+ let result = canonicalize (
1057+ & doc,
1058+ & C14nOptions {
1059+ with_comments : false ,
1060+ exclusive : true ,
1061+ inclusive_prefixes : vec ! [ ] ,
1062+ } ,
1063+ ) ;
1064+ assert_eq ! ( result, "<root xml:lang=\" en\" >hello</root>" ) ;
1065+ assert ! ( !result. contains( "xmlns:xml" ) ) ;
1066+ }
1067+
1068+ #[ test]
1069+ fn test_c14n_exclusive_xml_prefix_not_emitted_on_subtree ( ) {
1070+ // When canonicalizing a subtree whose ancestor carries `xml:lang`,
1071+ // the subtree must not spuriously declare `xmlns:xml` just because
1072+ // an `xml:` attribute appears in scope.
1073+ let doc = Document :: parse_str (
1074+ "<root xmlns=\" http://example.com\" xml:lang=\" en\" >\
1075+ <child xml:space=\" preserve\" >hi</child></root>",
1076+ )
1077+ . unwrap ( ) ;
1078+ let root = doc. root_element ( ) . unwrap ( ) ;
1079+ let child = doc
1080+ . children ( root)
1081+ . find ( |& n| doc. node_name ( n) == Some ( "child" ) )
1082+ . unwrap ( ) ;
1083+
1084+ let result = canonicalize_subtree (
1085+ & doc,
1086+ child,
1087+ & C14nOptions {
1088+ with_comments : false ,
1089+ exclusive : true ,
1090+ inclusive_prefixes : vec ! [ ] ,
1091+ } ,
1092+ ) ;
1093+
1094+ assert ! (
1095+ !result. contains( "xmlns:xml" ) ,
1096+ "implicit xml namespace should not be emitted in exclusive C14N, got: {result}"
1097+ ) ;
1098+ // The default namespace from the root IS visibly utilized by the
1099+ // child element's unprefixed name, so it should appear.
1100+ assert ! ( result. contains( "xmlns=\" http://example.com\" " ) ) ;
1101+ assert ! ( result. contains( "xml:space=\" preserve\" " ) ) ;
1102+ }
10211103}
0 commit comments