@@ -769,3 +769,163 @@ fn test_niso_yes_no_enumeration() {
769769 assert ! ( NisoFacts :: YES_NO_VALUES . contains( & "yes" ) ) ;
770770 assert ! ( NisoFacts :: YES_NO_VALUES . contains( & "no" ) ) ;
771771}
772+
773+ // =============================================================================
774+ // xs:redefine Support Tests
775+ // =============================================================================
776+
777+ /// Test that xs:redefine is parsed correctly - the schema should have redefines recorded
778+ #[ test]
779+ #[ ignore = "Requires complete schema resolution - run with: cargo test -- --ignored" ]
780+ fn test_dita_redefine_support ( ) {
781+ let ( _temp_dir, base_path) = extract_bundle_to_temp :: < Dita12 > ( ) ;
782+
783+ // Find basetopic.xsd which uses xs:redefine for commonElementGrp.xsd
784+ if let Some ( basetopic_path) = walkdir_find ( & base_path, "basetopic.xsd" ) {
785+ eprintln ! ( "Found basetopic.xsd at: {}" , basetopic_path. display( ) ) ;
786+
787+ let schema = XsdSchema :: from_file ( & basetopic_path)
788+ . expect ( "Should parse basetopic.xsd" ) ;
789+
790+ // Check that redefines were recorded
791+ eprintln ! ( "Schema has {} redefines" , schema. redefines. len( ) ) ;
792+
793+ // basetopic.xsd should have at least one xs:redefine (for commonElementGrp.xsd)
794+ // Note: The exact number depends on how many xs:redefine elements are in the schema chain
795+ if !schema. redefines . is_empty ( ) {
796+ eprintln ! ( "✓ xs:redefine elements were parsed successfully" ) ;
797+ for ( i, redefine) in schema. redefines . iter ( ) . enumerate ( ) {
798+ eprintln ! ( " Redefine {}: {} redefinitions from {}" ,
799+ i, redefine. redefinitions. len( ) , redefine. location) ;
800+ }
801+ }
802+
803+ // Check that the title group exists in global maps - this comes from commonElementGrp.xsd
804+ // via xs:redefine in basetopic.xsd
805+ let title_group_exists = schema. maps . global_maps . groups . keys ( )
806+ . any ( |qname| qname. local_name == "title" ) ;
807+ eprintln ! ( "title group exists: {}" , title_group_exists) ;
808+
809+ // Check for other expected groups from commonElementGrp.xsd
810+ let expected_groups = [ "title" , "ph" , "keyword" , "xref" , "data" , "data-about" ] ;
811+ for group_name in expected_groups {
812+ let exists = schema. maps . global_maps . groups . keys ( )
813+ . any ( |qname| qname. local_name == group_name) ;
814+ eprintln ! ( " Group '{}': {}" , group_name, if exists { "✓" } else { "✗" } ) ;
815+ }
816+ } else {
817+ eprintln ! ( "basetopic.xsd not found - checking for topic.xsd" ) ;
818+ if let Some ( topic_path) = walkdir_find ( & base_path, "topic.xsd" ) {
819+ eprintln ! ( "Found topic.xsd at: {}" , topic_path. display( ) ) ;
820+ // topic.xsd includes basetopic.xsd which uses xs:redefine
821+ let schema = XsdSchema :: from_file ( & topic_path)
822+ . expect ( "Should parse topic.xsd" ) ;
823+ eprintln ! ( "Schema has {} redefines" , schema. redefines. len( ) ) ;
824+ }
825+ }
826+ }
827+
828+ /// Test that TOPIC_CHILDREN facts match the parsed schema content
829+ /// This specifically tests xs:redefine support since 'title' comes from redefined commonElementGrp
830+ #[ test]
831+ #[ ignore = "Requires complete schema resolution - run with: cargo test -- --ignored" ]
832+ fn test_dita_topic_children_from_redefine ( ) {
833+ use bundle_facts:: DitaFacts ;
834+
835+ let ( _temp_dir, base_path) = extract_bundle_to_temp :: < Dita12 > ( ) ;
836+
837+ // Find topic.xsd
838+ if let Some ( topic_path) = walkdir_find ( & base_path, "topic.xsd" ) {
839+ let schema = XsdSchema :: from_file ( & topic_path)
840+ . expect ( "Should parse topic.xsd" ) ;
841+
842+ // Find the 'topic' element
843+ let topic_element = schema. maps . global_maps . elements . iter ( )
844+ . find ( |( qname, _) | qname. local_name == "topic" ) ;
845+
846+ if let Some ( ( _, topic_elem) ) = topic_element {
847+ eprintln ! ( "Found topic element" ) ;
848+
849+ // Get child element names from the type
850+ let child_names: Vec < String > = collect_element_names_from_element ( topic_elem, & schema) ;
851+
852+ eprintln ! ( "Topic children found: {:?}" , child_names) ;
853+
854+ // Assert that xs:redefine-dependent children exist
855+ // 'title' is the key one - it comes from commonElementGrp.xsd via xs:redefine
856+ for ( expected_child, min_occurs, _max_occurs) in DitaFacts :: TOPIC_CHILDREN {
857+ let found = child_names. iter ( ) . any ( |n| n == expected_child) ;
858+ if * min_occurs > 0 {
859+ assert ! (
860+ found,
861+ "Required child '{}' should be present in topic (from xs:redefine)" ,
862+ expected_child
863+ ) ;
864+ }
865+ eprintln ! ( " Child '{}': {}" , expected_child, if found { "✓" } else { "✗" } ) ;
866+ }
867+ } else {
868+ eprintln ! ( "topic element not found in schema" ) ;
869+ }
870+ }
871+ }
872+
873+ /// Helper to collect element names from an element's content model
874+ fn collect_element_names_from_element (
875+ element : & xmlschema:: validators:: XsdElement ,
876+ _schema : & XsdSchema ,
877+ ) -> Vec < String > {
878+ use xmlschema:: validators:: ElementType ;
879+
880+ let mut names = Vec :: new ( ) ;
881+
882+ match & element. element_type {
883+ ElementType :: Complex ( ct) => {
884+ collect_element_names_from_complex_type ( ct, & mut names) ;
885+ }
886+ ElementType :: Simple ( _) | ElementType :: Any => {
887+ // Simple types and any don't have child elements
888+ }
889+ }
890+
891+ names
892+ }
893+
894+ /// Helper to collect element names from a complex type's content model
895+ fn collect_element_names_from_complex_type (
896+ ct : & xmlschema:: validators:: XsdComplexType ,
897+ names : & mut Vec < String > ,
898+ ) {
899+ use xmlschema:: validators:: ComplexContent ;
900+
901+ // Check content model - it's either a group or simple content
902+ match & ct. content {
903+ ComplexContent :: Group ( group) => {
904+ for particle in & group. particles {
905+ collect_element_names_from_particle ( particle, names) ;
906+ }
907+ }
908+ ComplexContent :: Simple ( _) => {
909+ // Simple content has no child elements
910+ }
911+ }
912+ }
913+
914+ /// Helper to collect element names from a particle
915+ fn collect_element_names_from_particle (
916+ particle : & GroupParticle ,
917+ names : & mut Vec < String > ,
918+ ) {
919+ match particle {
920+ GroupParticle :: Element ( elem) => {
921+ names. push ( elem. name . local_name . clone ( ) ) ;
922+ }
923+ GroupParticle :: Group ( group) => {
924+ // Recursively collect from nested group (sequence, choice, etc.)
925+ for p in & group. particles {
926+ collect_element_names_from_particle ( p, names) ;
927+ }
928+ }
929+ GroupParticle :: Any ( _) => { }
930+ }
931+ }
0 commit comments