polyxml 0.33.2__tar.gz → 0.33.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. {polyxml-0.33.2 → polyxml-0.33.4}/Cargo.lock +6 -6
  2. {polyxml-0.33.2 → polyxml-0.33.4}/Cargo.toml +1 -1
  3. {polyxml-0.33.2 → polyxml-0.33.4}/PKG-INFO +1 -1
  4. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/src/codegen/go/mod.rs +349 -17
  5. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/src/schema_parser/mod.rs +41 -16
  6. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/test_go_codegen.rs +241 -0
  7. {polyxml-0.33.2 → polyxml-0.33.4}/pyproject.toml +1 -1
  8. {polyxml-0.33.2 → polyxml-0.33.4}/README.md +0 -0
  9. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/Cargo.toml +0 -0
  10. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/README.md +0 -0
  11. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
  12. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/benches/support/tag_dispatch_fixtures.rs +0 -0
  13. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/benches/tag_dispatch.rs +0 -0
  14. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/src/codegen/cpp/mod.rs +0 -0
  15. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/src/codegen/csharp/mod.rs +0 -0
  16. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/src/codegen/java/codec.rs +0 -0
  17. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/src/codegen/java/mod.rs +0 -0
  18. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/src/codegen/java/models.rs +0 -0
  19. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/src/codegen/mod.rs +0 -0
  20. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/src/codegen/python/aot.rs +0 -0
  21. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/src/codegen/python/mod.rs +0 -0
  22. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/src/codegen/rust/mod.rs +0 -0
  23. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/src/codegen/typescript/mod.rs +0 -0
  24. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/src/converters.rs +0 -0
  25. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/src/error.rs +0 -0
  26. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/src/ir/chunker.rs +0 -0
  27. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/src/ir/mod.rs +0 -0
  28. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
  29. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/src/json.rs +0 -0
  30. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/src/lib.rs +0 -0
  31. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/src/parser.rs +0 -0
  32. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/src/schema.rs +0 -0
  33. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/src/serializer.rs +0 -0
  34. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/src/transcoder.rs +0 -0
  35. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/src/value.rs +0 -0
  36. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/fixtures/lexical_union.xsd +0 -0
  37. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/fixtures/mixed_content.xsd +0 -0
  38. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/fixtures/ts_mixed_content_inheritance.xsd +0 -0
  39. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/test_adversarial_identifiers.rs +0 -0
  40. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/test_any_attribute.rs +0 -0
  41. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/test_any_element.rs +0 -0
  42. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/test_conformance.rs +0 -0
  43. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/test_core.rs +0 -0
  44. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/test_cpp_codegen.rs +0 -0
  45. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/test_csharp_codegen.rs +0 -0
  46. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/test_java_codegen.rs +0 -0
  47. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/test_json.rs +0 -0
  48. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/test_lexical_union_codegen.rs +0 -0
  49. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/test_mixed_content.rs +0 -0
  50. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/test_pattern_codegen.rs +0 -0
  51. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/test_python_codegen.rs +0 -0
  52. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
  53. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/test_rust_codegen.rs +0 -0
  54. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/test_schema_audit.rs +0 -0
  55. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/test_schema_ir.rs +0 -0
  56. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/test_security.rs +0 -0
  57. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/test_transcoder.rs +0 -0
  58. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/test_ts_codegen.rs +0 -0
  59. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/test_unbounded_choice.rs +0 -0
  60. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-core/tests/test_xsi_type.rs +0 -0
  61. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-python/Cargo.toml +0 -0
  62. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-python/README.md +0 -0
  63. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-python/benches/aot_vs_dataclass.py +0 -0
  64. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-python/src/lib.rs +0 -0
  65. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-python/tests/test_conformance.py +0 -0
  66. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-python/tests/test_generated_models.py +0 -0
  67. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-python/tests/test_json.py +0 -0
  68. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-python/tests/test_mixed_content.py +0 -0
  69. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-python/tests/test_polyxml.py +0 -0
  70. {polyxml-0.33.2 → polyxml-0.33.4}/crates/polyxml-python/tests/test_xsi_type.py +0 -0
  71. {polyxml-0.33.2 → polyxml-0.33.4}/python/polyxml/AGENT_GUIDE.md +0 -0
  72. {polyxml-0.33.2 → polyxml-0.33.4}/python/polyxml/__init__.py +0 -0
  73. {polyxml-0.33.2 → polyxml-0.33.4}/python/polyxml/compat/__init__.py +0 -0
  74. {polyxml-0.33.2 → polyxml-0.33.4}/python/polyxml/compat/xsdata.py +0 -0
  75. {polyxml-0.33.2 → polyxml-0.33.4}/python/polyxml/py.typed +0 -0
@@ -608,7 +608,7 @@ dependencies = [
608
608
 
609
609
  [[package]]
610
610
  name = "polyxml"
611
- version = "0.33.2"
611
+ version = "0.33.4"
612
612
  dependencies = [
613
613
  "criterion",
614
614
  "heck",
@@ -629,14 +629,14 @@ dependencies = [
629
629
 
630
630
  [[package]]
631
631
  name = "polyxml-c"
632
- version = "0.33.2"
632
+ version = "0.33.4"
633
633
  dependencies = [
634
634
  "polyxml",
635
635
  ]
636
636
 
637
637
  [[package]]
638
638
  name = "polyxml-cli"
639
- version = "0.33.2"
639
+ version = "0.33.4"
640
640
  dependencies = [
641
641
  "clap",
642
642
  "glob",
@@ -650,7 +650,7 @@ dependencies = [
650
650
 
651
651
  [[package]]
652
652
  name = "polyxml-js"
653
- version = "0.33.2"
653
+ version = "0.33.4"
654
654
  dependencies = [
655
655
  "napi",
656
656
  "napi-build",
@@ -660,7 +660,7 @@ dependencies = [
660
660
 
661
661
  [[package]]
662
662
  name = "polyxml-python"
663
- version = "0.33.2"
663
+ version = "0.33.4"
664
664
  dependencies = [
665
665
  "lexical-core",
666
666
  "polyxml",
@@ -670,7 +670,7 @@ dependencies = [
670
670
 
671
671
  [[package]]
672
672
  name = "polyxml-wasm"
673
- version = "0.33.2"
673
+ version = "0.33.4"
674
674
  dependencies = [
675
675
  "polyxml",
676
676
  "quick-xml",
@@ -3,7 +3,7 @@ resolver = "2"
3
3
  members = ["crates/polyxml-core", "crates/polyxml-python"]
4
4
 
5
5
  [workspace.package]
6
- version = "0.33.2"
6
+ version = "0.33.4"
7
7
  edition = "2021"
8
8
  rust-version = "1.80"
9
9
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polyxml
3
- Version: 0.33.2
3
+ Version: 0.33.4
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -1,7 +1,7 @@
1
1
  use std::collections::HashSet;
2
2
  use std::fmt::Write as FmtWrite;
3
3
 
4
- use heck::{AsPascalCase, AsSnakeCase};
4
+ use heck::{AsLowerCamelCase, AsPascalCase, AsSnakeCase};
5
5
  use serde::{Deserialize, Serialize};
6
6
 
7
7
  use crate::codegen::{
@@ -361,7 +361,7 @@ impl GoCodegen {
361
361
  {
362
362
  imports.push("\"bytes\"");
363
363
  }
364
- if has_fmt {
364
+ if has_fmt || body.contains("fmt.") {
365
365
  imports.push("\"fmt\"");
366
366
  }
367
367
  if has_io {
@@ -374,13 +374,12 @@ impl GoCodegen {
374
374
  .types
375
375
  .values()
376
376
  .any(|def| matches!(def, TypeDef::Union(u) if u.is_lexical()))
377
+ && body.contains("strconv.")
377
378
  {
378
- if body.contains("strconv.") {
379
- imports.push("\"strconv\"");
380
- }
381
- if body.contains("strings.") {
382
- imports.push("\"strings\"");
383
- }
379
+ imports.push("\"strconv\"");
380
+ }
381
+ if body.contains("strings.") {
382
+ imports.push("\"strings\"");
384
383
  }
385
384
  if has_time {
386
385
  imports.push("\"time\"");
@@ -587,7 +586,11 @@ impl GoCodegen {
587
586
  let mapped_type = self.context.map_type_ref(&branch.type_ref);
588
587
  let mut tag_parts = Vec::new();
589
588
  if self.options.emit_xml_tags {
590
- tag_parts.push(format!("xml:\"{},omitempty\"", branch.xml_name));
589
+ let branch_xml = match &branch.namespace {
590
+ Some(ns) if !ns.is_empty() => format!("{} {}", ns, branch.xml_name),
591
+ _ => branch.xml_name.clone(),
592
+ };
593
+ tag_parts.push(format!("xml:\"{},omitempty\"", branch_xml));
591
594
  }
592
595
  if self.options.emit_json_tags {
593
596
  tag_parts.push(format!("json:\"{},omitempty\"", branch.xml_name));
@@ -913,6 +916,11 @@ impl GoCodegen {
913
916
  }
914
917
 
915
918
  fn emit_struct(&self, out: &mut String, s: &StructDef, ir: &SchemaIR) {
919
+ if s.is_abstract {
920
+ self.emit_abstract_struct(out, s, ir);
921
+ return;
922
+ }
923
+
916
924
  let struct_name = type_ident(&s.qname);
917
925
  if let Some(ref doc) = s.documentation {
918
926
  for line in doc.lines() {
@@ -945,9 +953,13 @@ impl GoCodegen {
945
953
  // Struct composition / inheritance if base struct exists
946
954
  let mut simple_content_base: Option<String> = None;
947
955
  if let Some(ref base_qname) = s.base_type {
948
- if matches!(ir.types.get(base_qname), Some(TypeDef::Struct(_))) {
956
+ if let Some(TypeDef::Struct(base_s)) = ir.types.get(base_qname) {
949
957
  let base_name = type_ident(base_qname);
950
- writeln!(out, " {}", base_name).unwrap();
958
+ if base_s.is_abstract {
959
+ writeln!(out, " {}Base", base_name).unwrap();
960
+ } else {
961
+ writeln!(out, " {}", base_name).unwrap();
962
+ }
951
963
  } else if let Some(prim) = PrimitiveType::from_xsd_name(&base_qname.local) {
952
964
  simple_content_base = Some(self.context.map_primitive(prim).to_string());
953
965
  } else if let Some(TypeDef::Simple(st)) = ir.types.get(base_qname) {
@@ -1018,7 +1030,7 @@ impl GoCodegen {
1018
1030
  }
1019
1031
 
1020
1032
  let field_type = self.resolve_field_type(f);
1021
- let tag = self.build_field_struct_tags(f, &mut seen_json);
1033
+ let tag = self.build_field_struct_tags(f, s, ir, &mut seen_json);
1022
1034
  writeln!(out, " {} {}{}", field_name, field_type, tag).unwrap();
1023
1035
  }
1024
1036
 
@@ -1136,29 +1148,44 @@ impl GoCodegen {
1136
1148
  }
1137
1149
  }
1138
1150
 
1139
- fn build_field_struct_tags(&self, f: &FieldDef, seen_json: &mut HashSet<String>) -> String {
1151
+ fn build_field_struct_tags(
1152
+ &self,
1153
+ f: &FieldDef,
1154
+ s: &StructDef,
1155
+ ir: &SchemaIR,
1156
+ seen_json: &mut HashSet<String>,
1157
+ ) -> String {
1140
1158
  let mut parts = Vec::new();
1141
1159
 
1142
1160
  if self.options.emit_xml_tags {
1143
1161
  let is_opt = f.cardinality.is_optional() || f.nillable;
1144
1162
  let xml_val = match f.kind {
1145
1163
  FieldKind::Attribute => {
1164
+ let attr_name = match &f.namespace {
1165
+ Some(ns) if !ns.is_empty() => format!("{} {}", ns, f.xml_name),
1166
+ _ => f.xml_name.clone(),
1167
+ };
1146
1168
  if is_opt {
1147
- format!("{},attr,omitempty", f.xml_name)
1169
+ format!("{},attr,omitempty", attr_name)
1148
1170
  } else {
1149
- format!("{},attr", f.xml_name)
1171
+ format!("{},attr", attr_name)
1150
1172
  }
1151
1173
  }
1152
1174
  FieldKind::Text => ",chardata".to_string(),
1153
1175
  FieldKind::Any => ",any".to_string(),
1154
1176
  FieldKind::AnyAttribute => ",any,attr".to_string(),
1155
1177
  FieldKind::Element => {
1178
+ let elem_name = if is_namespaced_ref(f, s, ir) {
1179
+ format!("{} {}", f.namespace.as_ref().unwrap(), f.xml_name)
1180
+ } else {
1181
+ f.xml_name.clone()
1182
+ };
1156
1183
  if f.xml_name.is_empty() {
1157
1184
  ",any".to_string()
1158
1185
  } else if is_opt {
1159
- format!("{},omitempty", f.xml_name)
1186
+ format!("{},omitempty", elem_name)
1160
1187
  } else {
1161
- f.xml_name.clone()
1188
+ elem_name
1162
1189
  }
1163
1190
  }
1164
1191
  };
@@ -1270,6 +1297,264 @@ impl GoCodegen {
1270
1297
  writeln!(out, "}}\n").unwrap();
1271
1298
  }
1272
1299
 
1300
+ fn emit_abstract_struct(&self, out: &mut String, s: &StructDef, ir: &SchemaIR) {
1301
+ let struct_name = type_ident(&s.qname);
1302
+ let base_struct_name = format!("{}Base", struct_name);
1303
+
1304
+ if let Some(ref doc) = s.documentation {
1305
+ for line in doc.lines() {
1306
+ writeln!(out, "// {}", line).unwrap();
1307
+ }
1308
+ }
1309
+
1310
+ // 1. Base struct definition holding declared fields of the abstract type
1311
+ writeln!(out, "type {} struct {{", base_struct_name).unwrap();
1312
+ if let Some(ref base_qname) = s.base_type {
1313
+ if let Some(TypeDef::Struct(base_s)) = ir.types.get(base_qname) {
1314
+ let base_name = type_ident(base_qname);
1315
+ if base_s.is_abstract {
1316
+ writeln!(out, " {}Base", base_name).unwrap();
1317
+ } else {
1318
+ writeln!(out, " {}", base_name).unwrap();
1319
+ }
1320
+ }
1321
+ }
1322
+
1323
+ let mut seen_fields = HashSet::new();
1324
+ let field_names: Vec<String> = s
1325
+ .fields
1326
+ .iter()
1327
+ .map(|f| self.unique_field_name(&f.name, &mut seen_fields))
1328
+ .collect();
1329
+
1330
+ let mut seen_json = HashSet::new();
1331
+ for (f, field_name) in s.fields.iter().zip(&field_names) {
1332
+ let field_type = self.resolve_field_type(f);
1333
+ let tag = self.build_field_struct_tags(f, s, ir, &mut seen_json);
1334
+ writeln!(out, " {} {}{}", field_name, field_type, tag).unwrap();
1335
+ }
1336
+ writeln!(out, "}}\n").unwrap();
1337
+
1338
+ writeln!(
1339
+ out,
1340
+ "func (s {}) Validate() error {{\n return nil\n}}\n",
1341
+ base_struct_name
1342
+ )
1343
+ .unwrap();
1344
+
1345
+ // 2. Discover concrete derivations
1346
+ let derivations = concrete_derivations(s, ir);
1347
+
1348
+ // 3. Dispatch wrapper struct
1349
+ writeln!(out, "type {} struct {{", struct_name).unwrap();
1350
+ if self.options.emit_xml_tags {
1351
+ let mut xml_tags = Vec::new();
1352
+ if self.options.emit_json_tags {
1353
+ xml_tags.push("json:\"-\"");
1354
+ }
1355
+ if self.options.backend == GoBackend::Sonic {
1356
+ xml_tags.push("sonic:\"-\"");
1357
+ }
1358
+ if xml_tags.is_empty() {
1359
+ writeln!(out, " XMLName xml.Name").unwrap();
1360
+ } else {
1361
+ writeln!(out, " XMLName xml.Name `{}`", xml_tags.join(" ")).unwrap();
1362
+ }
1363
+ }
1364
+ writeln!(out, " {}", base_struct_name).unwrap();
1365
+
1366
+ let mut seen_derives = HashSet::new();
1367
+ let derive_fields: Vec<(String, String, String)> = derivations
1368
+ .iter()
1369
+ .map(|d| {
1370
+ let type_name = type_ident(&d.qname);
1371
+ let field_name = self.unique_field_name(&type_name, &mut seen_derives);
1372
+ (d.qname.local.clone(), field_name, type_name)
1373
+ })
1374
+ .collect();
1375
+
1376
+ for (_local, field_name, type_name) in &derive_fields {
1377
+ let json_name = AsLowerCamelCase(field_name).to_string();
1378
+ writeln!(
1379
+ out,
1380
+ " {} *{} `xml:\"-\" json:\"{},omitempty\"`",
1381
+ field_name, type_name, json_name
1382
+ )
1383
+ .unwrap();
1384
+ }
1385
+ writeln!(out, "}}\n").unwrap();
1386
+
1387
+ // 4. Methods on dispatch wrapper
1388
+ writeln!(out, "func (s {}) Selected() string {{", struct_name).unwrap();
1389
+ for (local, field_name, _) in &derive_fields {
1390
+ writeln!(
1391
+ out,
1392
+ " if s.{} != nil {{ return {:?} }}",
1393
+ field_name, local
1394
+ )
1395
+ .unwrap();
1396
+ }
1397
+ writeln!(out, " return \"\"\n}}\n").unwrap();
1398
+
1399
+ writeln!(out, "func (s {}) Value() any {{", struct_name).unwrap();
1400
+ for (_, field_name, _) in &derive_fields {
1401
+ writeln!(
1402
+ out,
1403
+ " if s.{} != nil {{ return s.{} }}",
1404
+ field_name, field_name
1405
+ )
1406
+ .unwrap();
1407
+ }
1408
+ writeln!(out, " return nil\n}}\n").unwrap();
1409
+
1410
+ // UnmarshalXML
1411
+ writeln!(
1412
+ out,
1413
+ "func (s *{}) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {{",
1414
+ struct_name
1415
+ )
1416
+ .unwrap();
1417
+ writeln!(out, " s.XMLName = start.Name").unwrap();
1418
+ writeln!(out, " var typeAttr *xml.Attr").unwrap();
1419
+ writeln!(out, " for _, attr := range start.Attr {{").unwrap();
1420
+ writeln!(out, " if (attr.Name.Local == \"type\" && (attr.Name.Space == \"http://www.w3.org/2001/XMLSchema-instance\" || attr.Name.Space == \"xsi\")) || attr.Name.Local == \"xsi:type\" {{").unwrap();
1421
+ writeln!(out, " typeAttr = &attr").unwrap();
1422
+ writeln!(out, " break").unwrap();
1423
+ writeln!(out, " }}").unwrap();
1424
+ writeln!(out, " }}").unwrap();
1425
+ writeln!(out, " if typeAttr == nil {{").unwrap();
1426
+ writeln!(
1427
+ out,
1428
+ " return fmt.Errorf(\"abstract type '{}' requires xsi:type naming a concrete derivation\")",
1429
+ struct_name
1430
+ )
1431
+ .unwrap();
1432
+ writeln!(out, " }}").unwrap();
1433
+
1434
+ if derive_fields.is_empty() {
1435
+ writeln!(
1436
+ out,
1437
+ " return fmt.Errorf(\"abstract type '{}' has no registered derivations; deserialize the concrete type directly (escape hatch)\")",
1438
+ struct_name
1439
+ )
1440
+ .unwrap();
1441
+ } else {
1442
+ writeln!(out, " localType := typeAttr.Value").unwrap();
1443
+ writeln!(
1444
+ out,
1445
+ " if idx := strings.Index(localType, \":\"); idx != -1 {{"
1446
+ )
1447
+ .unwrap();
1448
+ writeln!(out, " localType = localType[idx+1:]").unwrap();
1449
+ writeln!(out, " }}").unwrap();
1450
+ writeln!(out, " switch localType {{").unwrap();
1451
+ for (local, field_name, type_name) in &derive_fields {
1452
+ writeln!(out, " case {:?}:", local).unwrap();
1453
+ writeln!(out, " var concrete {}", type_name).unwrap();
1454
+ writeln!(
1455
+ out,
1456
+ " if err := d.DecodeElement(&concrete, &start); err != nil {{ return err }}"
1457
+ )
1458
+ .unwrap();
1459
+ writeln!(out, " s.{} = &concrete", field_name).unwrap();
1460
+ writeln!(
1461
+ out,
1462
+ " s.{} = concrete.{}",
1463
+ base_struct_name, base_struct_name
1464
+ )
1465
+ .unwrap();
1466
+ writeln!(out, " return nil").unwrap();
1467
+ }
1468
+ let known = derive_fields
1469
+ .iter()
1470
+ .map(|(local, _, _)| local.as_str())
1471
+ .collect::<Vec<_>>()
1472
+ .join(", ");
1473
+ writeln!(out, " default:").unwrap();
1474
+ writeln!(
1475
+ out,
1476
+ " return fmt.Errorf(\"xsi:type=%q does not match any known derivation of '{}' (known: {})\", typeAttr.Value)",
1477
+ struct_name, known
1478
+ )
1479
+ .unwrap();
1480
+ writeln!(out, " }}").unwrap();
1481
+ }
1482
+ writeln!(out, "}}\n").unwrap();
1483
+
1484
+ // MarshalXML
1485
+ writeln!(
1486
+ out,
1487
+ "func (s {}) MarshalXML(e *xml.Encoder, start xml.StartElement) error {{",
1488
+ struct_name
1489
+ )
1490
+ .unwrap();
1491
+ writeln!(
1492
+ out,
1493
+ " if s.XMLName.Local != \"\" {{ start.Name = s.XMLName }}"
1494
+ )
1495
+ .unwrap();
1496
+ for (local, field_name, _) in &derive_fields {
1497
+ writeln!(out, " if s.{} != nil {{", field_name).unwrap();
1498
+ writeln!(out, " hasXsiNs := false\n hasType := false").unwrap();
1499
+ writeln!(out, " for _, attr := range start.Attr {{").unwrap();
1500
+ writeln!(out, " if attr.Name.Local == \"xmlns:xsi\" || (attr.Name.Local == \"xsi\" && attr.Name.Space == \"xmlns\") {{ hasXsiNs = true }}").unwrap();
1501
+ writeln!(out, " if attr.Name.Local == \"xsi:type\" || (attr.Name.Local == \"type\" && attr.Name.Space == \"http://www.w3.org/2001/XMLSchema-instance\") {{ hasType = true }}").unwrap();
1502
+ writeln!(out, " }}").unwrap();
1503
+ writeln!(out, " if !hasXsiNs {{ start.Attr = append(start.Attr, xml.Attr{{Name: xml.Name{{Local: \"xmlns:xsi\"}}, Value: \"http://www.w3.org/2001/XMLSchema-instance\"}}) }}").unwrap();
1504
+ writeln!(out, " if !hasType {{ start.Attr = append(start.Attr, xml.Attr{{Name: xml.Name{{Local: \"xsi:type\"}}, Value: {:?}}}) }}", local).unwrap();
1505
+ writeln!(
1506
+ out,
1507
+ " return e.EncodeElement(s.{}, start)",
1508
+ field_name
1509
+ )
1510
+ .unwrap();
1511
+ writeln!(out, " }}").unwrap();
1512
+ }
1513
+ if derive_fields.is_empty() {
1514
+ writeln!(
1515
+ out,
1516
+ " return fmt.Errorf(\"abstract type '{}' has no registered derivations\")",
1517
+ struct_name
1518
+ )
1519
+ .unwrap();
1520
+ } else {
1521
+ writeln!(
1522
+ out,
1523
+ " return fmt.Errorf(\"cannot marshal abstract type '{}' without a concrete derivation\")",
1524
+ struct_name
1525
+ )
1526
+ .unwrap();
1527
+ }
1528
+ writeln!(out, "}}\n").unwrap();
1529
+
1530
+ // Validate()
1531
+ writeln!(out, "func (s {}) Validate() error {{", struct_name).unwrap();
1532
+ for (_, field_name, _) in &derive_fields {
1533
+ writeln!(
1534
+ out,
1535
+ " if s.{} != nil {{ return s.{}.Validate() }}",
1536
+ field_name, field_name
1537
+ )
1538
+ .unwrap();
1539
+ }
1540
+ if derive_fields.is_empty() {
1541
+ writeln!(
1542
+ out,
1543
+ " return fmt.Errorf(\"abstract type '{}' has no registered derivations\")",
1544
+ struct_name
1545
+ )
1546
+ .unwrap();
1547
+ } else {
1548
+ writeln!(
1549
+ out,
1550
+ " return fmt.Errorf(\"abstract type '{}' requires a concrete derivation\")",
1551
+ struct_name
1552
+ )
1553
+ .unwrap();
1554
+ }
1555
+ writeln!(out, "}}\n").unwrap();
1556
+ }
1557
+
1273
1558
  fn emit_facet_checks(
1274
1559
  &self,
1275
1560
  out: &mut String,
@@ -1350,3 +1635,50 @@ impl GoCodegen {
1350
1635
  }
1351
1636
  }
1352
1637
  }
1638
+
1639
+ fn derives_from(ir: &SchemaIR, d: &StructDef, base: &QName) -> bool {
1640
+ let mut seen: HashSet<QName> = HashSet::new();
1641
+ let mut cur = d.base_type.as_ref();
1642
+ while let Some(q) = cur {
1643
+ if q == base {
1644
+ return true;
1645
+ }
1646
+ if !seen.insert(q.clone()) {
1647
+ break;
1648
+ }
1649
+ cur = match ir.types.get(q) {
1650
+ Some(TypeDef::Struct(b)) => b.base_type.as_ref(),
1651
+ _ => None,
1652
+ };
1653
+ }
1654
+ false
1655
+ }
1656
+
1657
+ fn concrete_derivations<'a>(s: &'a StructDef, ir: &'a SchemaIR) -> Vec<&'a StructDef> {
1658
+ let mut result = Vec::new();
1659
+ for type_def in ir.types.values() {
1660
+ if let TypeDef::Struct(d) = type_def {
1661
+ if !d.is_abstract && d.qname != s.qname && derives_from(ir, d, &s.qname) {
1662
+ result.push(d);
1663
+ }
1664
+ }
1665
+ }
1666
+ result.sort_by(|a, b| a.qname.local.cmp(&b.qname.local));
1667
+ result
1668
+ }
1669
+
1670
+ fn is_namespaced_ref(f: &FieldDef, s: &StructDef, ir: &SchemaIR) -> bool {
1671
+ let Some(ref ns) = f.namespace else {
1672
+ return false;
1673
+ };
1674
+ if ns.is_empty() {
1675
+ return false;
1676
+ }
1677
+ // If field namespace is different from struct namespace, it's definitely an external ref
1678
+ if s.qname.namespace.as_deref() != Some(ns.as_str()) {
1679
+ return true;
1680
+ }
1681
+ // If it matches a global element definition, it's an element ref
1682
+ let q = QName::new(Some(ns.as_str()), &f.xml_name);
1683
+ ir.elements.contains_key(&q)
1684
+ }
@@ -1805,16 +1805,29 @@ fn parse_element_field(
1805
1805
  in_choice: bool,
1806
1806
  in_unbounded_compositor: bool,
1807
1807
  ) -> Option<FieldDef> {
1808
- let name = get_attr_value(e, "name")
1809
- .or_else(|| get_attr_value(e, "ref").map(|r| strip_prefix(&r).to_string()))?;
1810
-
1811
- let xml_name = get_attr_value(e, "name")
1812
- .or_else(|| get_attr_value(e, "ref"))
1813
- .unwrap_or_else(|| name.clone());
1808
+ let ref_attr = get_attr_value(e, "ref");
1809
+ let name_attr = get_attr_value(e, "name");
1810
+
1811
+ let (name, xml_name, namespace) = if let Some(ref r) = ref_attr {
1812
+ let qname = resolve_qname(r, target_ns, prefixes);
1813
+ let field_name = name_attr.unwrap_or_else(|| qname.local.clone());
1814
+ let xml_name = qname.local;
1815
+ let namespace = qname.namespace;
1816
+ (field_name, xml_name, namespace)
1817
+ } else {
1818
+ let n = name_attr?;
1819
+ let xml_name = n.clone();
1820
+ let namespace = target_ns.map(Into::into);
1821
+ (n, xml_name, namespace)
1822
+ };
1814
1823
 
1815
1824
  let type_ref = get_attr_value(e, "type")
1816
1825
  .map(|t| resolve_type_ref(&t, target_ns, prefixes))
1817
- .or_else(|| get_attr_value(e, "ref").map(|r| resolve_type_ref(&r, target_ns, prefixes)))
1826
+ .or_else(|| {
1827
+ ref_attr
1828
+ .as_ref()
1829
+ .map(|r| resolve_type_ref(r, target_ns, prefixes))
1830
+ })
1818
1831
  .unwrap_or(TypeRef::Primitive(PrimitiveType::String));
1819
1832
 
1820
1833
  let min_occurs = if in_choice {
@@ -1845,7 +1858,7 @@ fn parse_element_field(
1845
1858
  Some(FieldDef {
1846
1859
  name: sanitize_field_name(&name),
1847
1860
  xml_name,
1848
- namespace: target_ns.map(Into::into),
1861
+ namespace,
1849
1862
  kind: FieldKind::Element,
1850
1863
  type_ref,
1851
1864
  cardinality: Cardinality {
@@ -1866,16 +1879,28 @@ fn parse_attribute_field(
1866
1879
  target_ns: Option<&str>,
1867
1880
  prefixes: &HashMap<String, String>,
1868
1881
  ) -> Option<FieldDef> {
1869
- let name = get_attr_value(e, "name")
1870
- .or_else(|| get_attr_value(e, "ref").map(|r| strip_prefix(&r).to_string()))?;
1871
-
1872
- let xml_name = get_attr_value(e, "name")
1873
- .or_else(|| get_attr_value(e, "ref"))
1874
- .unwrap_or_else(|| name.clone());
1882
+ let ref_attr = get_attr_value(e, "ref");
1883
+ let name_attr = get_attr_value(e, "name");
1884
+
1885
+ let (name, xml_name, namespace) = if let Some(ref r) = ref_attr {
1886
+ let qname = resolve_qname(r, target_ns, prefixes);
1887
+ let field_name = name_attr.unwrap_or_else(|| qname.local.clone());
1888
+ let xml_name = qname.local;
1889
+ let namespace = qname.namespace;
1890
+ (field_name, xml_name, namespace)
1891
+ } else {
1892
+ let n = name_attr?;
1893
+ let xml_name = n.clone();
1894
+ (n, xml_name, None)
1895
+ };
1875
1896
 
1876
1897
  let type_ref = get_attr_value(e, "type")
1877
1898
  .map(|t| resolve_type_ref(&t, target_ns, prefixes))
1878
- .or_else(|| get_attr_value(e, "ref").map(|r| resolve_type_ref(&r, target_ns, prefixes)))
1899
+ .or_else(|| {
1900
+ ref_attr
1901
+ .as_ref()
1902
+ .map(|r| resolve_type_ref(r, target_ns, prefixes))
1903
+ })
1879
1904
  .unwrap_or(TypeRef::Primitive(PrimitiveType::String));
1880
1905
 
1881
1906
  let is_required = get_attr_value(e, "use")
@@ -1894,7 +1919,7 @@ fn parse_attribute_field(
1894
1919
  Some(FieldDef {
1895
1920
  name: sanitize_field_name(&name),
1896
1921
  xml_name,
1897
- namespace: None, // Attributes are unqualified by default unless form="qualified"
1922
+ namespace,
1898
1923
  kind: FieldKind::Attribute,
1899
1924
  type_ref,
1900
1925
  cardinality,
@@ -837,3 +837,244 @@ fn test_go_any_attribute_codegen() {
837
837
 
838
838
  assert!(code.contains("AnyAttribute []xml.Attr `xml:\",any,attr\" json:\"-\"`"));
839
839
  }
840
+
841
+ #[test]
842
+ fn test_go_abstract_xsi_type_dispatch() {
843
+ let xsd = r#"<?xml version="1.0" encoding="UTF-8"?>
844
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:audit:abstract" xmlns:t="urn:audit:abstract" elementFormDefault="qualified">
845
+ <xs:complexType name="AbstractDocument" abstract="true">
846
+ <xs:sequence>
847
+ <xs:element name="Id" type="xs:string"/>
848
+ </xs:sequence>
849
+ </xs:complexType>
850
+
851
+ <xs:complexType name="InvoiceDocument">
852
+ <xs:complexContent>
853
+ <xs:extension base="t:AbstractDocument">
854
+ <xs:sequence>
855
+ <xs:element name="Amount" type="xs:decimal"/>
856
+ </xs:sequence>
857
+ </xs:extension>
858
+ </xs:complexContent>
859
+ </xs:complexType>
860
+
861
+ <xs:complexType name="ReceiptDocument">
862
+ <xs:complexContent>
863
+ <xs:extension base="t:AbstractDocument">
864
+ <xs:sequence>
865
+ <xs:element name="Store" type="xs:string"/>
866
+ </xs:sequence>
867
+ </xs:extension>
868
+ </xs:complexContent>
869
+ </xs:complexType>
870
+
871
+ <xs:element name="Document" type="t:AbstractDocument"/>
872
+ </xs:schema>"#;
873
+
874
+ let ir = XsdParser::new().parse_str(xsd).expect("parse failed");
875
+ let codegen = GoCodegen::new(GoOptions::default());
876
+ let code = codegen.generate_module(&ir);
877
+
878
+ assert!(code.contains("type AbstractDocumentBase struct"));
879
+ assert!(code.contains("type AbstractDocument struct"));
880
+ assert!(code.contains("InvoiceDocument *InvoiceDocument"));
881
+ assert!(code.contains("ReceiptDocument *ReceiptDocument"));
882
+ assert!(code.contains("func (s AbstractDocument) Selected() string"));
883
+ assert!(code.contains("func (s AbstractDocument) Value() any"));
884
+ assert!(code.contains("func (s *AbstractDocument) UnmarshalXML"));
885
+ assert!(code.contains("func (s AbstractDocument) MarshalXML"));
886
+
887
+ if Command::new("go").arg("version").output().is_err() {
888
+ return;
889
+ }
890
+
891
+ let dir = tempdir().unwrap();
892
+ fs::write(
893
+ dir.path().join("go.mod"),
894
+ "module abstract_test\n\ngo 1.22\n",
895
+ )
896
+ .unwrap();
897
+ fs::write(dir.path().join("models.go"), &code).unwrap();
898
+ fs::write(
899
+ dir.path().join("models_test.go"),
900
+ r#"package models
901
+
902
+ import (
903
+ "encoding/xml"
904
+ "strings"
905
+ "testing"
906
+ )
907
+
908
+ func TestAbstractDocumentRoundTrip(t *testing.T) {
909
+ inputXml := `<Document xmlns="urn:audit:abstract" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InvoiceDocument"><Id>INV-001</Id><Amount>199.99</Amount></Document>`
910
+ var doc Document
911
+ if err := xml.Unmarshal([]byte(inputXml), &doc); err != nil {
912
+ t.Fatalf("Unmarshal failed: %v", err)
913
+ }
914
+
915
+ if doc.Selected() != "InvoiceDocument" {
916
+ t.Fatalf("expected Selected() to be InvoiceDocument, got %q", doc.Selected())
917
+ }
918
+ if doc.InvoiceDocument == nil {
919
+ t.Fatalf("InvoiceDocument is nil")
920
+ }
921
+ if doc.InvoiceDocument.ID != "INV-001" {
922
+ t.Fatalf("expected ID 'INV-001', got %q", doc.InvoiceDocument.ID)
923
+ }
924
+ if doc.InvoiceDocument.Amount != 199.99 {
925
+ t.Fatalf("expected Amount 199.99, got %v", doc.InvoiceDocument.Amount)
926
+ }
927
+
928
+ marshaled, err := xml.Marshal(&doc)
929
+ if err != nil {
930
+ t.Fatalf("Marshal failed: %v", err)
931
+ }
932
+ marshaledStr := string(marshaled)
933
+ if !strings.Contains(marshaledStr, `xsi:type="InvoiceDocument"`) {
934
+ t.Fatalf("marshaled XML missing xsi:type: %s", marshaledStr)
935
+ }
936
+ if !strings.Contains(marshaledStr, `Amount`) {
937
+ t.Fatalf("marshaled XML missing Amount element: %s", marshaledStr)
938
+ }
939
+
940
+ // Missing xsi:type should fail
941
+ var missingDoc Document
942
+ if err := xml.Unmarshal([]byte(`<Document xmlns="urn:audit:abstract"><Id>INV-001</Id></Document>`), &missingDoc); err == nil {
943
+ t.Fatalf("expected error unmarshaling abstract type without xsi:type, got nil")
944
+ }
945
+
946
+ // Unknown xsi:type should fail
947
+ var unknownDoc Document
948
+ if err := xml.Unmarshal([]byte(`<Document xmlns="urn:audit:abstract" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="UnknownDoc"><Id>INV-001</Id></Document>`), &unknownDoc); err == nil {
949
+ t.Fatalf("expected error unmarshaling unknown xsi:type, got nil")
950
+ }
951
+ }
952
+ "#,
953
+ )
954
+ .unwrap();
955
+
956
+ let output = Command::new("go")
957
+ .args(["test", "-v", "./..."])
958
+ .current_dir(dir.path())
959
+ .output()
960
+ .expect("go test failed to execute");
961
+
962
+ assert!(
963
+ output.status.success(),
964
+ "go test failed: stdout:\n{}\nstderr:\n{}",
965
+ String::from_utf8_lossy(&output.stdout),
966
+ String::from_utf8_lossy(&output.stderr)
967
+ );
968
+ }
969
+
970
+ #[test]
971
+ fn test_go_namespaced_element_ref_matching() {
972
+ let schema = r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
973
+ xmlns:t="urn:audit:ref" targetNamespace="urn:audit:ref"
974
+ elementFormDefault="qualified">
975
+ <xs:element name="Child" type="xs:string"/>
976
+ <xs:element name="Container"><xs:complexType><xs:sequence>
977
+ <xs:element ref="t:Child"/>
978
+ </xs:sequence></xs:complexType></xs:element>
979
+ </xs:schema>"#;
980
+
981
+ let ir = XsdParser::new().parse_str(schema).unwrap();
982
+ let codegen = GoCodegen::new(GoOptions::default());
983
+ let code = codegen.generate_module(&ir);
984
+
985
+ // Code must NOT contain schema prefix t:Child
986
+ assert!(
987
+ !code.contains("\"t:Child\""),
988
+ "Go codegen must not emit schema lexical prefix in tags: {code}"
989
+ );
990
+ // Code must emit expanded namespace QName
991
+ assert!(
992
+ code.contains("`xml:\"urn:audit:ref Child\" json:\"Child\"`"),
993
+ "Go codegen must emit expanded QName and clean JSON name in tags: {code}"
994
+ );
995
+
996
+ if Command::new("go").arg("version").output().is_err() {
997
+ return;
998
+ }
999
+
1000
+ let dir = tempdir().unwrap();
1001
+ fs::write(dir.path().join("go.mod"), "module ref_test\n\ngo 1.22\n").unwrap();
1002
+ fs::write(dir.path().join("models.go"), &code).unwrap();
1003
+ fs::write(
1004
+ dir.path().join("models_test.go"),
1005
+ r#"package models
1006
+
1007
+ import (
1008
+ "encoding/xml"
1009
+ "strings"
1010
+ "testing"
1011
+ )
1012
+
1013
+ func TestNamespacedElementRef(t *testing.T) {
1014
+ // 1. Default namespace instance
1015
+ xml1 := `<Container xmlns="urn:audit:ref"><Child>abc</Child></Container>`
1016
+ var c1 Container
1017
+ if err := xml.Unmarshal([]byte(xml1), &c1); err != nil {
1018
+ t.Fatalf("unmarshal xml1 failed: %v", err)
1019
+ }
1020
+ if c1.Child != "abc" {
1021
+ t.Fatalf("expected Child 'abc', got %q", c1.Child)
1022
+ }
1023
+
1024
+ // 2. Prefixed namespace instance (schema prefix t)
1025
+ xml2 := `<t:Container xmlns:t="urn:audit:ref"><t:Child>abc</t:Child></t:Container>`
1026
+ var c2 Container
1027
+ if err := xml.Unmarshal([]byte(xml2), &c2); err != nil {
1028
+ t.Fatalf("unmarshal xml2 failed: %v", err)
1029
+ }
1030
+ if c2.Child != "abc" {
1031
+ t.Fatalf("expected Child 'abc', got %q", c2.Child)
1032
+ }
1033
+
1034
+ // 3. Alternate prefix instance (prefix p)
1035
+ xml3 := `<p:Container xmlns:p="urn:audit:ref"><p:Child>abc</p:Child></p:Container>`
1036
+ var c3 Container
1037
+ if err := xml.Unmarshal([]byte(xml3), &c3); err != nil {
1038
+ t.Fatalf("unmarshal xml3 failed: %v", err)
1039
+ }
1040
+ if c3.Child != "abc" {
1041
+ t.Fatalf("expected Child 'abc', got %q", c3.Child)
1042
+ }
1043
+
1044
+ // 4. Foreign namespace URI with same local child name -> must NOT populate Child
1045
+ xml4 := `<Container xmlns="urn:audit:ref"><Child xmlns="urn:audit:other">abc</Child></Container>`
1046
+ var c4 Container
1047
+ if err := xml.Unmarshal([]byte(xml4), &c4); err != nil {
1048
+ t.Fatalf("unmarshal xml4 failed: %v", err)
1049
+ }
1050
+ if c4.Child != "" {
1051
+ t.Fatalf("expected Child to remain empty for foreign namespace, got %q", c4.Child)
1052
+ }
1053
+
1054
+ // 5. Roundtrip serialization preserves namespace
1055
+ data, err := xml.Marshal(c1)
1056
+ if err != nil {
1057
+ t.Fatalf("marshal failed: %v", err)
1058
+ }
1059
+ dataStr := string(data)
1060
+ if !strings.Contains(dataStr, "Child") || !strings.Contains(dataStr, "urn:audit:ref") {
1061
+ t.Fatalf("marshaled XML missing Child or namespace: %s", dataStr)
1062
+ }
1063
+ }
1064
+ "#,
1065
+ )
1066
+ .unwrap();
1067
+
1068
+ let output = Command::new("go")
1069
+ .args(["test", "-v", "./..."])
1070
+ .current_dir(dir.path())
1071
+ .output()
1072
+ .expect("go test failed to execute");
1073
+
1074
+ assert!(
1075
+ output.status.success(),
1076
+ "go test failed: stdout:\n{}\nstderr:\n{}",
1077
+ String::from_utf8_lossy(&output.stdout),
1078
+ String::from_utf8_lossy(&output.stderr)
1079
+ );
1080
+ }
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "polyxml"
7
- version = "0.33.2"
7
+ version = "0.33.4"
8
8
  description = "High-performance, polyglot XML data-binding engine built in Rust"
9
9
  authors = [
10
10
  { name = "Bailey Nguyen", email = "bailey.tan.nguyen@gmail.com" }
File without changes