@@ -639,14 +639,73 @@ internal DocsNavigationConfig ParseDocsNavigationConfig(XElement root)
639639 /// <returns>A NavigationConfig instance.</returns>
640640 internal NavigationConfig ParseNavigationConfig ( XElement navigationElement )
641641 {
642- var navConfig = new NavigationConfig
642+ var navConfig = new NavigationConfig ( ) ;
643+
644+ // Parse Tabs
645+ var tabsElement = navigationElement . Element ( "Tabs" ) ;
646+ if ( tabsElement is not null )
643647 {
644- Pages = [ ]
645- } ;
648+ navConfig . Tabs = [ ] ;
649+ foreach ( var tabElement in tabsElement . Elements ( "Tab" ) )
650+ {
651+ var tab = ParseTabConfig ( tabElement ) ;
652+ if ( tab is not null )
653+ {
654+ navConfig . Tabs . Add ( tab ) ;
655+ }
656+ }
657+ }
658+
659+ // Parse Anchors
660+ var anchorsElement = navigationElement . Element ( "Anchors" ) ;
661+ if ( anchorsElement is not null )
662+ {
663+ navConfig . Anchors = [ ] ;
664+ foreach ( var anchorElement in anchorsElement . Elements ( "Anchor" ) )
665+ {
666+ var anchor = ParseAnchorConfig ( anchorElement ) ;
667+ if ( anchor is not null )
668+ {
669+ navConfig . Anchors . Add ( anchor ) ;
670+ }
671+ }
672+ }
673+
674+ // Parse Dropdowns
675+ var dropdownsElement = navigationElement . Element ( "Dropdowns" ) ;
676+ if ( dropdownsElement is not null )
677+ {
678+ navConfig . Dropdowns = [ ] ;
679+ foreach ( var dropdownElement in dropdownsElement . Elements ( "Dropdown" ) )
680+ {
681+ var dropdown = ParseDropdownConfig ( dropdownElement ) ;
682+ if ( dropdown is not null )
683+ {
684+ navConfig . Dropdowns . Add ( dropdown ) ;
685+ }
686+ }
687+ }
688+
689+ // Parse Products
690+ var productsElement = navigationElement . Element ( "Products" ) ;
691+ if ( productsElement is not null )
692+ {
693+ navConfig . Products = [ ] ;
694+ foreach ( var productElement in productsElement . Elements ( "Product" ) )
695+ {
696+ var product = ParseProductConfig ( productElement ) ;
697+ if ( product is not null )
698+ {
699+ navConfig . Products . Add ( product ) ;
700+ }
701+ }
702+ }
646703
704+ // Parse Pages (only if explicitly provided alongside or instead of structured section types)
647705 var pagesElement = navigationElement . Element ( nameof ( NavigationConfig . Pages ) ) ;
648706 if ( pagesElement is not null )
649707 {
708+ navConfig . Pages = [ ] ;
650709 var groupsElement = pagesElement . Element ( "Groups" ) ;
651710 if ( groupsElement is not null )
652711 {
@@ -660,9 +719,7 @@ internal NavigationConfig ParseNavigationConfig(XElement navigationElement)
660719 }
661720 }
662721
663- // Also parse any direct page references
664- var directPages = pagesElement . Elements ( "Page" ) ;
665- foreach ( var pageElement in directPages )
722+ foreach ( var pageElement in pagesElement . Elements ( "Page" ) )
666723 {
667724 var pageName = pageElement . Value ? . Trim ( ) ;
668725 if ( ! string . IsNullOrWhiteSpace ( pageName ) )
@@ -741,6 +798,263 @@ internal NavigationConfig ParseNavigationConfig(XElement navigationElement)
741798 return group ;
742799 }
743800
801+ /// <summary>
802+ /// Parses an Anchor element from the navigation XML.
803+ /// </summary>
804+ /// <param name="anchorElement">The anchor XML element.</param>
805+ /// <returns>An <see cref="AnchorConfig"/> instance, or <see langword="null"/> if parsing fails.</returns>
806+ /// <remarks>
807+ /// Expects a <c>Name</c> attribute on the element. Optional attributes include <c>Href</c> and <c>Icon</c>.
808+ /// Child <c><Pages></c> and <c><Tabs></c> elements are also parsed if present.
809+ /// </remarks>
810+ /// <example>
811+ /// <code>
812+ /// <Anchor Name="API Reference" Href="/api" Icon="code">
813+ /// <Pages>
814+ /// <Groups>
815+ /// <Group Name="Endpoints" Icon="bolt">
816+ /// <Pages>api/index;api/auth</Pages>
817+ /// </Group>
818+ /// </Groups>
819+ /// </Pages>
820+ /// </Anchor>
821+ /// </code>
822+ /// </example>
823+ internal AnchorConfig ? ParseAnchorConfig ( XElement anchorElement )
824+ {
825+ var anchorName = anchorElement . Attribute ( "Name" ) ? . Value ;
826+ if ( string . IsNullOrWhiteSpace ( anchorName ) )
827+ {
828+ Log . LogWarning ( "Anchor element missing Name attribute, skipping" ) ;
829+ return null ;
830+ }
831+
832+ var anchor = new AnchorConfig
833+ {
834+ Anchor = anchorName ,
835+ Href = anchorElement . Attribute ( "Href" ) ? . Value ,
836+ Icon = anchorElement . Attribute ( "Icon" ) ? . Value ,
837+ Pages = [ ]
838+ } ;
839+
840+ ParseNavigationSectionPages ( anchorElement , anchor . Pages ) ;
841+
842+ // Parse nested Tabs within Anchor
843+ var tabsElement = anchorElement . Element ( "Tabs" ) ;
844+ if ( tabsElement is not null )
845+ {
846+ anchor . Tabs = [ ] ;
847+ foreach ( var tabElement in tabsElement . Elements ( "Tab" ) )
848+ {
849+ var tab = ParseTabConfig ( tabElement ) ;
850+ if ( tab is not null )
851+ {
852+ anchor . Tabs . Add ( tab ) ;
853+ }
854+ }
855+ }
856+
857+ return anchor ;
858+ }
859+
860+ /// <summary>
861+ /// Parses a Dropdown element from the navigation XML.
862+ /// </summary>
863+ /// <param name="dropdownElement">The dropdown XML element.</param>
864+ /// <returns>A <see cref="DropdownConfig"/> instance, or <see langword="null"/> if parsing fails.</returns>
865+ /// <remarks>
866+ /// Expects a <c>Name</c> attribute on the element. Optional attributes include <c>Href</c> and <c>Icon</c>.
867+ /// Child <c><Pages></c>, <c><Tabs></c>, and <c><Anchors></c> elements are also parsed if present.
868+ /// </remarks>
869+ /// <example>
870+ /// <code>
871+ /// <Dropdown Name="Products" Icon="grid">
872+ /// <Anchors>
873+ /// <Anchor Name="Core" Icon="star">
874+ /// <Pages><Groups><Group Name="Basics"><Pages>core/index</Pages></Group></Groups></Pages>
875+ /// </Anchor>
876+ /// </Anchors>
877+ /// </Dropdown>
878+ /// </code>
879+ /// </example>
880+ internal DropdownConfig ? ParseDropdownConfig ( XElement dropdownElement )
881+ {
882+ var dropdownName = dropdownElement . Attribute ( "Name" ) ? . Value ;
883+ if ( string . IsNullOrWhiteSpace ( dropdownName ) )
884+ {
885+ Log . LogWarning ( "Dropdown element missing Name attribute, skipping" ) ;
886+ return null ;
887+ }
888+
889+ var dropdown = new DropdownConfig
890+ {
891+ Dropdown = dropdownName ,
892+ Href = dropdownElement . Attribute ( "Href" ) ? . Value ,
893+ Icon = dropdownElement . Attribute ( "Icon" ) ? . Value ,
894+ Pages = [ ]
895+ } ;
896+
897+ ParseNavigationSectionPages ( dropdownElement , dropdown . Pages ) ;
898+
899+ // Parse nested Tabs within Dropdown
900+ var tabsElement = dropdownElement . Element ( "Tabs" ) ;
901+ if ( tabsElement is not null )
902+ {
903+ dropdown . Tabs = [ ] ;
904+ foreach ( var tabElement in tabsElement . Elements ( "Tab" ) )
905+ {
906+ var tab = ParseTabConfig ( tabElement ) ;
907+ if ( tab is not null )
908+ {
909+ dropdown . Tabs . Add ( tab ) ;
910+ }
911+ }
912+ }
913+
914+ // Parse nested Anchors within Dropdown
915+ var anchorsElement = dropdownElement . Element ( "Anchors" ) ;
916+ if ( anchorsElement is not null )
917+ {
918+ dropdown . Anchors = [ ] ;
919+ foreach ( var anchorElement in anchorsElement . Elements ( "Anchor" ) )
920+ {
921+ var anchor = ParseAnchorConfig ( anchorElement ) ;
922+ if ( anchor is not null )
923+ {
924+ dropdown . Anchors . Add ( anchor ) ;
925+ }
926+ }
927+ }
928+
929+ return dropdown ;
930+ }
931+
932+ /// <summary>
933+ /// Parses a Product element from the navigation XML.
934+ /// </summary>
935+ /// <param name="productElement">The product XML element.</param>
936+ /// <returns>A <see cref="ProductConfig"/> instance, or <see langword="null"/> if parsing fails.</returns>
937+ /// <remarks>
938+ /// Expects a <c>Name</c> attribute on the element. Optional attributes include <c>Href</c>, <c>Icon</c>, and <c>Description</c>.
939+ /// Child <c><Pages></c> elements are also parsed if present.
940+ /// </remarks>
941+ /// <example>
942+ /// <code>
943+ /// <Product Name="CloudNimble Core" Href="/core" Icon="box" Description="Core platform features">
944+ /// <Pages>
945+ /// <Groups>
946+ /// <Group Name="Getting Started" Icon="stars">
947+ /// <Pages>core/index;core/quickstart</Pages>
948+ /// </Group>
949+ /// </Groups>
950+ /// </Pages>
951+ /// </Product>
952+ /// </code>
953+ /// </example>
954+ internal ProductConfig ? ParseProductConfig ( XElement productElement )
955+ {
956+ var productName = productElement . Attribute ( "Name" ) ? . Value ;
957+ if ( string . IsNullOrWhiteSpace ( productName ) )
958+ {
959+ Log . LogWarning ( "Product element missing Name attribute, skipping" ) ;
960+ return null ;
961+ }
962+
963+ var product = new ProductConfig
964+ {
965+ Product = productName ,
966+ Href = productElement . Attribute ( "Href" ) ? . Value ,
967+ Icon = productElement . Attribute ( "Icon" ) ? . Value ,
968+ Description = productElement . Attribute ( "Description" ) ? . Value ,
969+ Pages = [ ]
970+ } ;
971+
972+ ParseNavigationSectionPages ( productElement , product . Pages ) ;
973+
974+ return product ;
975+ }
976+
977+ /// <summary>
978+ /// Parses a Tab element from the navigation XML.
979+ /// </summary>
980+ /// <param name="tabElement">The tab XML element.</param>
981+ /// <returns>A <see cref="TabConfig"/> instance, or <see langword="null"/> if parsing fails.</returns>
982+ /// <remarks>
983+ /// Expects a <c>Name</c> attribute on the element. Optional attributes include <c>Href</c> and <c>Icon</c>.
984+ /// Child <c><Pages></c> elements are also parsed if present.
985+ /// </remarks>
986+ /// <example>
987+ /// <code>
988+ /// <Tab Name="Guides" Href="/guides" Icon="book">
989+ /// <Pages>
990+ /// <Groups>
991+ /// <Group Name="Getting Started" Icon="stars">
992+ /// <Pages>guides/index;guides/quickstart</Pages>
993+ /// </Group>
994+ /// </Groups>
995+ /// </Pages>
996+ /// </Tab>
997+ /// </code>
998+ /// </example>
999+ internal TabConfig ? ParseTabConfig ( XElement tabElement )
1000+ {
1001+ var tabName = tabElement . Attribute ( "Name" ) ? . Value ;
1002+ if ( string . IsNullOrWhiteSpace ( tabName ) )
1003+ {
1004+ Log . LogWarning ( "Tab element missing Name attribute, skipping" ) ;
1005+ return null ;
1006+ }
1007+
1008+ var tab = new TabConfig
1009+ {
1010+ Tab = tabName ,
1011+ Href = tabElement . Attribute ( "Href" ) ? . Value ,
1012+ Icon = tabElement . Attribute ( "Icon" ) ? . Value ,
1013+ Pages = [ ]
1014+ } ;
1015+
1016+ ParseNavigationSectionPages ( tabElement , tab . Pages ) ;
1017+
1018+ return tab ;
1019+ }
1020+
1021+ /// <summary>
1022+ /// Parses the <c><Pages></c> child element of a navigation section element and populates the provided
1023+ /// <paramref name="pages"/> list with page strings and nested <see cref="GroupConfig"/> objects.
1024+ /// </summary>
1025+ /// <param name="parentElement">The parent XML element that may contain a <c><Pages></c> child.</param>
1026+ /// <param name="pages">The list to populate with parsed page entries.</param>
1027+ private void ParseNavigationSectionPages ( XElement parentElement , List < object > pages )
1028+ {
1029+ var pagesElement = parentElement . Element ( "Pages" ) ;
1030+ if ( pagesElement is null )
1031+ {
1032+ return ;
1033+ }
1034+
1035+ var groupsElement = pagesElement . Element ( "Groups" ) ;
1036+ if ( groupsElement is not null )
1037+ {
1038+ foreach ( var groupElement in groupsElement . Elements ( "Group" ) )
1039+ {
1040+ var group = ParseGroupConfig ( groupElement ) ;
1041+ if ( group is not null )
1042+ {
1043+ pages . Add ( group ) ;
1044+ }
1045+ }
1046+ }
1047+
1048+ foreach ( var pageElement in pagesElement . Elements ( "Page" ) )
1049+ {
1050+ var pageName = pageElement . Value ? . Trim ( ) ;
1051+ if ( ! string . IsNullOrWhiteSpace ( pageName ) )
1052+ {
1053+ pages . Add ( pageName ) ;
1054+ }
1055+ }
1056+ }
1057+
7441058 /// <summary>
7451059 /// Parses the Integrations element from the MintlifyTemplate XML using attributes.
7461060 /// </summary>
0 commit comments