polyxml 0.33.2__tar.gz → 0.33.3__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.3}/Cargo.lock +6 -6
  2. {polyxml-0.33.2 → polyxml-0.33.3}/Cargo.toml +1 -1
  3. {polyxml-0.33.2 → polyxml-0.33.3}/PKG-INFO +1 -1
  4. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/src/codegen/go/mod.rs +307 -10
  5. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/test_go_codegen.rs +129 -0
  6. {polyxml-0.33.2 → polyxml-0.33.3}/pyproject.toml +1 -1
  7. {polyxml-0.33.2 → polyxml-0.33.3}/README.md +0 -0
  8. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/Cargo.toml +0 -0
  9. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/README.md +0 -0
  10. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
  11. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/benches/support/tag_dispatch_fixtures.rs +0 -0
  12. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/benches/tag_dispatch.rs +0 -0
  13. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/src/codegen/cpp/mod.rs +0 -0
  14. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/src/codegen/csharp/mod.rs +0 -0
  15. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/src/codegen/java/codec.rs +0 -0
  16. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/src/codegen/java/mod.rs +0 -0
  17. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/src/codegen/java/models.rs +0 -0
  18. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/src/codegen/mod.rs +0 -0
  19. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/src/codegen/python/aot.rs +0 -0
  20. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/src/codegen/python/mod.rs +0 -0
  21. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/src/codegen/rust/mod.rs +0 -0
  22. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/src/codegen/typescript/mod.rs +0 -0
  23. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/src/converters.rs +0 -0
  24. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/src/error.rs +0 -0
  25. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/src/ir/chunker.rs +0 -0
  26. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/src/ir/mod.rs +0 -0
  27. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
  28. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/src/json.rs +0 -0
  29. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/src/lib.rs +0 -0
  30. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/src/parser.rs +0 -0
  31. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/src/schema.rs +0 -0
  32. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/src/schema_parser/mod.rs +0 -0
  33. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/src/serializer.rs +0 -0
  34. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/src/transcoder.rs +0 -0
  35. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/src/value.rs +0 -0
  36. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/fixtures/lexical_union.xsd +0 -0
  37. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/fixtures/mixed_content.xsd +0 -0
  38. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/fixtures/ts_mixed_content_inheritance.xsd +0 -0
  39. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/test_adversarial_identifiers.rs +0 -0
  40. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/test_any_attribute.rs +0 -0
  41. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/test_any_element.rs +0 -0
  42. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/test_conformance.rs +0 -0
  43. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/test_core.rs +0 -0
  44. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/test_cpp_codegen.rs +0 -0
  45. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/test_csharp_codegen.rs +0 -0
  46. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/test_java_codegen.rs +0 -0
  47. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/test_json.rs +0 -0
  48. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/test_lexical_union_codegen.rs +0 -0
  49. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/test_mixed_content.rs +0 -0
  50. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/test_pattern_codegen.rs +0 -0
  51. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/test_python_codegen.rs +0 -0
  52. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
  53. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/test_rust_codegen.rs +0 -0
  54. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/test_schema_audit.rs +0 -0
  55. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/test_schema_ir.rs +0 -0
  56. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/test_security.rs +0 -0
  57. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/test_transcoder.rs +0 -0
  58. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/test_ts_codegen.rs +0 -0
  59. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/test_unbounded_choice.rs +0 -0
  60. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-core/tests/test_xsi_type.rs +0 -0
  61. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-python/Cargo.toml +0 -0
  62. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-python/README.md +0 -0
  63. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-python/benches/aot_vs_dataclass.py +0 -0
  64. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-python/src/lib.rs +0 -0
  65. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-python/tests/test_conformance.py +0 -0
  66. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-python/tests/test_generated_models.py +0 -0
  67. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-python/tests/test_json.py +0 -0
  68. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-python/tests/test_mixed_content.py +0 -0
  69. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-python/tests/test_polyxml.py +0 -0
  70. {polyxml-0.33.2 → polyxml-0.33.3}/crates/polyxml-python/tests/test_xsi_type.py +0 -0
  71. {polyxml-0.33.2 → polyxml-0.33.3}/python/polyxml/AGENT_GUIDE.md +0 -0
  72. {polyxml-0.33.2 → polyxml-0.33.3}/python/polyxml/__init__.py +0 -0
  73. {polyxml-0.33.2 → polyxml-0.33.3}/python/polyxml/compat/__init__.py +0 -0
  74. {polyxml-0.33.2 → polyxml-0.33.3}/python/polyxml/compat/xsdata.py +0 -0
  75. {polyxml-0.33.2 → polyxml-0.33.3}/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.3"
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.3"
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.3"
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.3"
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.3"
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.3"
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.3"
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.3
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\"");
@@ -913,6 +912,11 @@ impl GoCodegen {
913
912
  }
914
913
 
915
914
  fn emit_struct(&self, out: &mut String, s: &StructDef, ir: &SchemaIR) {
915
+ if s.is_abstract {
916
+ self.emit_abstract_struct(out, s, ir);
917
+ return;
918
+ }
919
+
916
920
  let struct_name = type_ident(&s.qname);
917
921
  if let Some(ref doc) = s.documentation {
918
922
  for line in doc.lines() {
@@ -945,9 +949,13 @@ impl GoCodegen {
945
949
  // Struct composition / inheritance if base struct exists
946
950
  let mut simple_content_base: Option<String> = None;
947
951
  if let Some(ref base_qname) = s.base_type {
948
- if matches!(ir.types.get(base_qname), Some(TypeDef::Struct(_))) {
952
+ if let Some(TypeDef::Struct(base_s)) = ir.types.get(base_qname) {
949
953
  let base_name = type_ident(base_qname);
950
- writeln!(out, " {}", base_name).unwrap();
954
+ if base_s.is_abstract {
955
+ writeln!(out, " {}Base", base_name).unwrap();
956
+ } else {
957
+ writeln!(out, " {}", base_name).unwrap();
958
+ }
951
959
  } else if let Some(prim) = PrimitiveType::from_xsd_name(&base_qname.local) {
952
960
  simple_content_base = Some(self.context.map_primitive(prim).to_string());
953
961
  } else if let Some(TypeDef::Simple(st)) = ir.types.get(base_qname) {
@@ -1270,6 +1278,264 @@ impl GoCodegen {
1270
1278
  writeln!(out, "}}\n").unwrap();
1271
1279
  }
1272
1280
 
1281
+ fn emit_abstract_struct(&self, out: &mut String, s: &StructDef, ir: &SchemaIR) {
1282
+ let struct_name = type_ident(&s.qname);
1283
+ let base_struct_name = format!("{}Base", struct_name);
1284
+
1285
+ if let Some(ref doc) = s.documentation {
1286
+ for line in doc.lines() {
1287
+ writeln!(out, "// {}", line).unwrap();
1288
+ }
1289
+ }
1290
+
1291
+ // 1. Base struct definition holding declared fields of the abstract type
1292
+ writeln!(out, "type {} struct {{", base_struct_name).unwrap();
1293
+ if let Some(ref base_qname) = s.base_type {
1294
+ if let Some(TypeDef::Struct(base_s)) = ir.types.get(base_qname) {
1295
+ let base_name = type_ident(base_qname);
1296
+ if base_s.is_abstract {
1297
+ writeln!(out, " {}Base", base_name).unwrap();
1298
+ } else {
1299
+ writeln!(out, " {}", base_name).unwrap();
1300
+ }
1301
+ }
1302
+ }
1303
+
1304
+ let mut seen_fields = HashSet::new();
1305
+ let field_names: Vec<String> = s
1306
+ .fields
1307
+ .iter()
1308
+ .map(|f| self.unique_field_name(&f.name, &mut seen_fields))
1309
+ .collect();
1310
+
1311
+ let mut seen_json = HashSet::new();
1312
+ for (f, field_name) in s.fields.iter().zip(&field_names) {
1313
+ let field_type = self.resolve_field_type(f);
1314
+ let tag = self.build_field_struct_tags(f, &mut seen_json);
1315
+ writeln!(out, " {} {}{}", field_name, field_type, tag).unwrap();
1316
+ }
1317
+ writeln!(out, "}}\n").unwrap();
1318
+
1319
+ writeln!(
1320
+ out,
1321
+ "func (s {}) Validate() error {{\n return nil\n}}\n",
1322
+ base_struct_name
1323
+ )
1324
+ .unwrap();
1325
+
1326
+ // 2. Discover concrete derivations
1327
+ let derivations = concrete_derivations(s, ir);
1328
+
1329
+ // 3. Dispatch wrapper struct
1330
+ writeln!(out, "type {} struct {{", struct_name).unwrap();
1331
+ if self.options.emit_xml_tags {
1332
+ let mut xml_tags = Vec::new();
1333
+ if self.options.emit_json_tags {
1334
+ xml_tags.push("json:\"-\"");
1335
+ }
1336
+ if self.options.backend == GoBackend::Sonic {
1337
+ xml_tags.push("sonic:\"-\"");
1338
+ }
1339
+ if xml_tags.is_empty() {
1340
+ writeln!(out, " XMLName xml.Name").unwrap();
1341
+ } else {
1342
+ writeln!(out, " XMLName xml.Name `{}`", xml_tags.join(" ")).unwrap();
1343
+ }
1344
+ }
1345
+ writeln!(out, " {}", base_struct_name).unwrap();
1346
+
1347
+ let mut seen_derives = HashSet::new();
1348
+ let derive_fields: Vec<(String, String, String)> = derivations
1349
+ .iter()
1350
+ .map(|d| {
1351
+ let type_name = type_ident(&d.qname);
1352
+ let field_name = self.unique_field_name(&type_name, &mut seen_derives);
1353
+ (d.qname.local.clone(), field_name, type_name)
1354
+ })
1355
+ .collect();
1356
+
1357
+ for (_local, field_name, type_name) in &derive_fields {
1358
+ let json_name = AsLowerCamelCase(field_name).to_string();
1359
+ writeln!(
1360
+ out,
1361
+ " {} *{} `xml:\"-\" json:\"{},omitempty\"`",
1362
+ field_name, type_name, json_name
1363
+ )
1364
+ .unwrap();
1365
+ }
1366
+ writeln!(out, "}}\n").unwrap();
1367
+
1368
+ // 4. Methods on dispatch wrapper
1369
+ writeln!(out, "func (s {}) Selected() string {{", struct_name).unwrap();
1370
+ for (local, field_name, _) in &derive_fields {
1371
+ writeln!(
1372
+ out,
1373
+ " if s.{} != nil {{ return {:?} }}",
1374
+ field_name, local
1375
+ )
1376
+ .unwrap();
1377
+ }
1378
+ writeln!(out, " return \"\"\n}}\n").unwrap();
1379
+
1380
+ writeln!(out, "func (s {}) Value() any {{", struct_name).unwrap();
1381
+ for (_, field_name, _) in &derive_fields {
1382
+ writeln!(
1383
+ out,
1384
+ " if s.{} != nil {{ return s.{} }}",
1385
+ field_name, field_name
1386
+ )
1387
+ .unwrap();
1388
+ }
1389
+ writeln!(out, " return nil\n}}\n").unwrap();
1390
+
1391
+ // UnmarshalXML
1392
+ writeln!(
1393
+ out,
1394
+ "func (s *{}) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {{",
1395
+ struct_name
1396
+ )
1397
+ .unwrap();
1398
+ writeln!(out, " s.XMLName = start.Name").unwrap();
1399
+ writeln!(out, " var typeAttr *xml.Attr").unwrap();
1400
+ writeln!(out, " for _, attr := range start.Attr {{").unwrap();
1401
+ 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();
1402
+ writeln!(out, " typeAttr = &attr").unwrap();
1403
+ writeln!(out, " break").unwrap();
1404
+ writeln!(out, " }}").unwrap();
1405
+ writeln!(out, " }}").unwrap();
1406
+ writeln!(out, " if typeAttr == nil {{").unwrap();
1407
+ writeln!(
1408
+ out,
1409
+ " return fmt.Errorf(\"abstract type '{}' requires xsi:type naming a concrete derivation\")",
1410
+ struct_name
1411
+ )
1412
+ .unwrap();
1413
+ writeln!(out, " }}").unwrap();
1414
+
1415
+ if derive_fields.is_empty() {
1416
+ writeln!(
1417
+ out,
1418
+ " return fmt.Errorf(\"abstract type '{}' has no registered derivations; deserialize the concrete type directly (escape hatch)\")",
1419
+ struct_name
1420
+ )
1421
+ .unwrap();
1422
+ } else {
1423
+ writeln!(out, " localType := typeAttr.Value").unwrap();
1424
+ writeln!(
1425
+ out,
1426
+ " if idx := strings.Index(localType, \":\"); idx != -1 {{"
1427
+ )
1428
+ .unwrap();
1429
+ writeln!(out, " localType = localType[idx+1:]").unwrap();
1430
+ writeln!(out, " }}").unwrap();
1431
+ writeln!(out, " switch localType {{").unwrap();
1432
+ for (local, field_name, type_name) in &derive_fields {
1433
+ writeln!(out, " case {:?}:", local).unwrap();
1434
+ writeln!(out, " var concrete {}", type_name).unwrap();
1435
+ writeln!(
1436
+ out,
1437
+ " if err := d.DecodeElement(&concrete, &start); err != nil {{ return err }}"
1438
+ )
1439
+ .unwrap();
1440
+ writeln!(out, " s.{} = &concrete", field_name).unwrap();
1441
+ writeln!(
1442
+ out,
1443
+ " s.{} = concrete.{}",
1444
+ base_struct_name, base_struct_name
1445
+ )
1446
+ .unwrap();
1447
+ writeln!(out, " return nil").unwrap();
1448
+ }
1449
+ let known = derive_fields
1450
+ .iter()
1451
+ .map(|(local, _, _)| local.as_str())
1452
+ .collect::<Vec<_>>()
1453
+ .join(", ");
1454
+ writeln!(out, " default:").unwrap();
1455
+ writeln!(
1456
+ out,
1457
+ " return fmt.Errorf(\"xsi:type=%q does not match any known derivation of '{}' (known: {})\", typeAttr.Value)",
1458
+ struct_name, known
1459
+ )
1460
+ .unwrap();
1461
+ writeln!(out, " }}").unwrap();
1462
+ }
1463
+ writeln!(out, "}}\n").unwrap();
1464
+
1465
+ // MarshalXML
1466
+ writeln!(
1467
+ out,
1468
+ "func (s {}) MarshalXML(e *xml.Encoder, start xml.StartElement) error {{",
1469
+ struct_name
1470
+ )
1471
+ .unwrap();
1472
+ writeln!(
1473
+ out,
1474
+ " if s.XMLName.Local != \"\" {{ start.Name = s.XMLName }}"
1475
+ )
1476
+ .unwrap();
1477
+ for (local, field_name, _) in &derive_fields {
1478
+ writeln!(out, " if s.{} != nil {{", field_name).unwrap();
1479
+ writeln!(out, " hasXsiNs := false\n hasType := false").unwrap();
1480
+ writeln!(out, " for _, attr := range start.Attr {{").unwrap();
1481
+ writeln!(out, " if attr.Name.Local == \"xmlns:xsi\" || (attr.Name.Local == \"xsi\" && attr.Name.Space == \"xmlns\") {{ hasXsiNs = true }}").unwrap();
1482
+ 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();
1483
+ writeln!(out, " }}").unwrap();
1484
+ 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();
1485
+ writeln!(out, " if !hasType {{ start.Attr = append(start.Attr, xml.Attr{{Name: xml.Name{{Local: \"xsi:type\"}}, Value: {:?}}}) }}", local).unwrap();
1486
+ writeln!(
1487
+ out,
1488
+ " return e.EncodeElement(s.{}, start)",
1489
+ field_name
1490
+ )
1491
+ .unwrap();
1492
+ writeln!(out, " }}").unwrap();
1493
+ }
1494
+ if derive_fields.is_empty() {
1495
+ writeln!(
1496
+ out,
1497
+ " return fmt.Errorf(\"abstract type '{}' has no registered derivations\")",
1498
+ struct_name
1499
+ )
1500
+ .unwrap();
1501
+ } else {
1502
+ writeln!(
1503
+ out,
1504
+ " return fmt.Errorf(\"cannot marshal abstract type '{}' without a concrete derivation\")",
1505
+ struct_name
1506
+ )
1507
+ .unwrap();
1508
+ }
1509
+ writeln!(out, "}}\n").unwrap();
1510
+
1511
+ // Validate()
1512
+ writeln!(out, "func (s {}) Validate() error {{", struct_name).unwrap();
1513
+ for (_, field_name, _) in &derive_fields {
1514
+ writeln!(
1515
+ out,
1516
+ " if s.{} != nil {{ return s.{}.Validate() }}",
1517
+ field_name, field_name
1518
+ )
1519
+ .unwrap();
1520
+ }
1521
+ if derive_fields.is_empty() {
1522
+ writeln!(
1523
+ out,
1524
+ " return fmt.Errorf(\"abstract type '{}' has no registered derivations\")",
1525
+ struct_name
1526
+ )
1527
+ .unwrap();
1528
+ } else {
1529
+ writeln!(
1530
+ out,
1531
+ " return fmt.Errorf(\"abstract type '{}' requires a concrete derivation\")",
1532
+ struct_name
1533
+ )
1534
+ .unwrap();
1535
+ }
1536
+ writeln!(out, "}}\n").unwrap();
1537
+ }
1538
+
1273
1539
  fn emit_facet_checks(
1274
1540
  &self,
1275
1541
  out: &mut String,
@@ -1350,3 +1616,34 @@ impl GoCodegen {
1350
1616
  }
1351
1617
  }
1352
1618
  }
1619
+
1620
+ fn derives_from(ir: &SchemaIR, d: &StructDef, base: &QName) -> bool {
1621
+ let mut seen: HashSet<QName> = HashSet::new();
1622
+ let mut cur = d.base_type.as_ref();
1623
+ while let Some(q) = cur {
1624
+ if q == base {
1625
+ return true;
1626
+ }
1627
+ if !seen.insert(q.clone()) {
1628
+ break;
1629
+ }
1630
+ cur = match ir.types.get(q) {
1631
+ Some(TypeDef::Struct(b)) => b.base_type.as_ref(),
1632
+ _ => None,
1633
+ };
1634
+ }
1635
+ false
1636
+ }
1637
+
1638
+ fn concrete_derivations<'a>(s: &'a StructDef, ir: &'a SchemaIR) -> Vec<&'a StructDef> {
1639
+ let mut result = Vec::new();
1640
+ for type_def in ir.types.values() {
1641
+ if let TypeDef::Struct(d) = type_def {
1642
+ if !d.is_abstract && d.qname != s.qname && derives_from(ir, d, &s.qname) {
1643
+ result.push(d);
1644
+ }
1645
+ }
1646
+ }
1647
+ result.sort_by(|a, b| a.qname.local.cmp(&b.qname.local));
1648
+ result
1649
+ }
@@ -837,3 +837,132 @@ 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
+ }
@@ -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.3"
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