polyxml 0.33.1__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.1 → polyxml-0.33.3}/Cargo.lock +6 -6
  2. {polyxml-0.33.1 → polyxml-0.33.3}/Cargo.toml +1 -1
  3. {polyxml-0.33.1 → polyxml-0.33.3}/PKG-INFO +1 -1
  4. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/src/codegen/go/mod.rs +333 -10
  5. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/src/parser.rs +205 -0
  6. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/src/schema.rs +8 -1
  7. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/src/schema_parser/mod.rs +71 -7
  8. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/src/serializer.rs +105 -0
  9. polyxml-0.33.3/crates/polyxml-core/tests/test_any_element.rs +154 -0
  10. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/test_go_codegen.rs +129 -0
  11. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/test_python_codegen.rs +31 -0
  12. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-python/src/lib.rs +142 -10
  13. {polyxml-0.33.1 → polyxml-0.33.3}/pyproject.toml +1 -1
  14. {polyxml-0.33.1 → polyxml-0.33.3}/python/polyxml/__init__.py +12 -1
  15. {polyxml-0.33.1 → polyxml-0.33.3}/README.md +0 -0
  16. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/Cargo.toml +0 -0
  17. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/README.md +0 -0
  18. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
  19. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/benches/support/tag_dispatch_fixtures.rs +0 -0
  20. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/benches/tag_dispatch.rs +0 -0
  21. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/src/codegen/cpp/mod.rs +0 -0
  22. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/src/codegen/csharp/mod.rs +0 -0
  23. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/src/codegen/java/codec.rs +0 -0
  24. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/src/codegen/java/mod.rs +0 -0
  25. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/src/codegen/java/models.rs +0 -0
  26. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/src/codegen/mod.rs +0 -0
  27. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/src/codegen/python/aot.rs +0 -0
  28. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/src/codegen/python/mod.rs +0 -0
  29. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/src/codegen/rust/mod.rs +0 -0
  30. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/src/codegen/typescript/mod.rs +0 -0
  31. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/src/converters.rs +0 -0
  32. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/src/error.rs +0 -0
  33. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/src/ir/chunker.rs +0 -0
  34. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/src/ir/mod.rs +0 -0
  35. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
  36. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/src/json.rs +0 -0
  37. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/src/lib.rs +0 -0
  38. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/src/transcoder.rs +0 -0
  39. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/src/value.rs +0 -0
  40. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/fixtures/lexical_union.xsd +0 -0
  41. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/fixtures/mixed_content.xsd +0 -0
  42. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/fixtures/ts_mixed_content_inheritance.xsd +0 -0
  43. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/test_adversarial_identifiers.rs +0 -0
  44. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/test_any_attribute.rs +0 -0
  45. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/test_conformance.rs +0 -0
  46. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/test_core.rs +0 -0
  47. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/test_cpp_codegen.rs +0 -0
  48. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/test_csharp_codegen.rs +0 -0
  49. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/test_java_codegen.rs +0 -0
  50. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/test_json.rs +0 -0
  51. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/test_lexical_union_codegen.rs +0 -0
  52. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/test_mixed_content.rs +0 -0
  53. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/test_pattern_codegen.rs +0 -0
  54. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
  55. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/test_rust_codegen.rs +0 -0
  56. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/test_schema_audit.rs +0 -0
  57. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/test_schema_ir.rs +0 -0
  58. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/test_security.rs +0 -0
  59. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/test_transcoder.rs +0 -0
  60. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/test_ts_codegen.rs +0 -0
  61. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/test_unbounded_choice.rs +0 -0
  62. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-core/tests/test_xsi_type.rs +0 -0
  63. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-python/Cargo.toml +0 -0
  64. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-python/README.md +0 -0
  65. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-python/benches/aot_vs_dataclass.py +0 -0
  66. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-python/tests/test_conformance.py +0 -0
  67. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-python/tests/test_generated_models.py +0 -0
  68. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-python/tests/test_json.py +0 -0
  69. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-python/tests/test_mixed_content.py +0 -0
  70. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-python/tests/test_polyxml.py +0 -0
  71. {polyxml-0.33.1 → polyxml-0.33.3}/crates/polyxml-python/tests/test_xsi_type.py +0 -0
  72. {polyxml-0.33.1 → polyxml-0.33.3}/python/polyxml/AGENT_GUIDE.md +0 -0
  73. {polyxml-0.33.1 → polyxml-0.33.3}/python/polyxml/compat/__init__.py +0 -0
  74. {polyxml-0.33.1 → polyxml-0.33.3}/python/polyxml/compat/xsdata.py +0 -0
  75. {polyxml-0.33.1 → 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.1"
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.1"
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.1"
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.1"
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.1"
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.1"
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.1"
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.1
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\"");
@@ -447,6 +446,14 @@ impl GoCodegen {
447
446
  self.emit_struct(out, s, ir);
448
447
  }
449
448
  }
449
+
450
+ let has_any_element = ir.emitted_types().any(|t| match t {
451
+ TypeDef::Struct(s) => s.fields.iter().any(|f| f.kind == FieldKind::Any),
452
+ _ => false,
453
+ });
454
+ if has_any_element {
455
+ out.push_str("// AnyElement represents an unmapped wildcard XML element (xs:any).\ntype AnyElement struct {\n XMLName xml.Name\n Attrs []xml.Attr `xml:\",any,attr\" json:\"attrs,omitempty\"`\n Content string `xml:\",innerxml\" json:\"content,omitempty\"`\n}\n\n");
456
+ }
450
457
  }
451
458
 
452
459
  fn emit_simple_type(&self, out: &mut String, simple: &SimpleTypeDef, ir: &SchemaIR) {
@@ -905,6 +912,11 @@ impl GoCodegen {
905
912
  }
906
913
 
907
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
+
908
920
  let struct_name = type_ident(&s.qname);
909
921
  if let Some(ref doc) = s.documentation {
910
922
  for line in doc.lines() {
@@ -937,9 +949,13 @@ impl GoCodegen {
937
949
  // Struct composition / inheritance if base struct exists
938
950
  let mut simple_content_base: Option<String> = None;
939
951
  if let Some(ref base_qname) = s.base_type {
940
- if matches!(ir.types.get(base_qname), Some(TypeDef::Struct(_))) {
952
+ if let Some(TypeDef::Struct(base_s)) = ir.types.get(base_qname) {
941
953
  let base_name = type_ident(base_qname);
942
- 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
+ }
943
959
  } else if let Some(prim) = PrimitiveType::from_xsd_name(&base_qname.local) {
944
960
  simple_content_base = Some(self.context.map_primitive(prim).to_string());
945
961
  } else if let Some(TypeDef::Simple(st)) = ir.types.get(base_qname) {
@@ -1104,6 +1120,15 @@ impl GoCodegen {
1104
1120
  if f.kind == FieldKind::AnyAttribute {
1105
1121
  return "[]xml.Attr".to_string();
1106
1122
  }
1123
+ if f.kind == FieldKind::Any {
1124
+ if f.cardinality.is_list() {
1125
+ return "[]AnyElement".to_string();
1126
+ } else if f.is_cycle_cut || f.cardinality.is_optional() || f.nillable {
1127
+ return "*AnyElement".to_string();
1128
+ } else {
1129
+ return "AnyElement".to_string();
1130
+ }
1131
+ }
1107
1132
  let base_type = self.context.map_type_ref(&f.type_ref);
1108
1133
 
1109
1134
  if f.cardinality.is_list() {
@@ -1156,6 +1181,15 @@ impl GoCodegen {
1156
1181
  if self.options.backend == GoBackend::Sonic {
1157
1182
  parts.push("sonic:\"-\"".to_string());
1158
1183
  }
1184
+ } else if f.kind == FieldKind::Any {
1185
+ let is_opt = f.cardinality.is_optional() || f.nillable;
1186
+ let tag = if is_opt { "any,omitempty" } else { "any" };
1187
+ if self.options.emit_json_tags {
1188
+ parts.push(format!("json:\"{}\"", tag));
1189
+ }
1190
+ if self.options.backend == GoBackend::Sonic {
1191
+ parts.push(format!("sonic:\"{}\"", tag));
1192
+ }
1159
1193
  } else {
1160
1194
  let is_opt = f.cardinality.is_optional() || f.nillable;
1161
1195
  let raw_json_name = if f.kind == FieldKind::Text {
@@ -1244,6 +1278,264 @@ impl GoCodegen {
1244
1278
  writeln!(out, "}}\n").unwrap();
1245
1279
  }
1246
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
+
1247
1539
  fn emit_facet_checks(
1248
1540
  &self,
1249
1541
  out: &mut String,
@@ -1324,3 +1616,34 @@ impl GoCodegen {
1324
1616
  }
1325
1617
  }
1326
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
+ }