/* Function: GenerateJSON * Generates JSON for all the entries in the passed container. */ protected void GenerateJSON(MenuEntries.Base.Container container) { ContainerExtraData containerExtraData = new ContainerExtraData(container); container.ExtraData = containerExtraData; containerExtraData.GenerateJSON(HTMLBuilder, this); foreach (var member in container.Members) { if (member is MenuEntries.Base.Target) { TargetExtraData targetExtraData = new TargetExtraData((MenuEntries.Base.Target)member); member.ExtraData = targetExtraData; targetExtraData.GenerateJSON(HTMLBuilder, this); } else if (member is MenuEntries.Base.Container) { GenerateJSON((MenuEntries.Base.Container)member); } } }
/* Function: Build * Generates JSON files for all entries in the menu. It returns a <StringTable> mapping the file type strings ("files", * "classes", etc.) to a <IDObjects.NumberSet> representing all the files that were generated. So "files.js", "files2.js", * and "files3.js" would map to "files" -> {1-3}. */ public StringTable <IDObjects.NumberSet> Build() { try { // This will create multiple subdirectories if needed, and will not throw an exception if it already exists. System.IO.Directory.CreateDirectory(HTMLBuilder.Menu_DataFolder); } catch (Exception e) { throw new Exceptions.UserFriendly( Locale.Get("NaturalDocs.Engine", "Error.CouldNotCreateOutputFolder(name, exception)", HTMLBuilder.Menu_DataFolder, e.Message) ); } // Build menu files StringTable <IDObjects.NumberSet> outputFiles = new StringTable <IDObjects.NumberSet>(); if (RootFileMenu != null) { GenerateJSON(RootFileMenu); SegmentMenu(RootFileMenu, "files", ref outputFiles); BuildOutput(RootFileMenu); } if (RootClassMenu != null) { GenerateJSON(RootClassMenu); SegmentMenu(RootClassMenu, "classes", ref outputFiles); BuildOutput(RootClassMenu); } if (RootDatabaseMenu != null) { GenerateJSON(RootDatabaseMenu); SegmentMenu(RootDatabaseMenu, "database", ref outputFiles); BuildOutput(RootDatabaseMenu); } // Build tab information file StringBuilder tabInformation = new StringBuilder("NDMenu.OnTabsLoaded(["); if (!EngineInstance.Config.ShrinkFiles) { tabInformation.Append('\n'); } List <MenuEntries.Base.Container> tabContainers = new List <MenuEntries.Base.Container>(); List <string> tabTypes = new List <string>(); // DEPENDENCY: tabTypes must use the same strings as the NDLocation JavaScript class. // DEPENDENCY: tabTypes must use strings safe for including in CSS names. if (RootFileMenu != null) { tabContainers.Add(RootFileMenu); tabTypes.Add("File"); } if (RootClassMenu != null) { tabContainers.Add(RootClassMenu); tabTypes.Add("Class"); } if (RootDatabaseMenu != null) { tabContainers.Add(RootDatabaseMenu); tabTypes.Add("Database"); } for (int i = 0; i < tabContainers.Count; i++) { ContainerExtraData extraData = (ContainerExtraData)tabContainers[i].ExtraData; if (!EngineInstance.Config.ShrinkFiles) { tabInformation.Append(' ', IndentSpaces); } tabInformation.Append("[\""); tabInformation.Append(tabTypes[i]); tabInformation.Append("\","); if (tabContainers[i].CondensedTitles == null) { tabInformation.Append('"'); tabInformation.StringEscapeAndAppend(tabContainers[i].Title.ToHTML()); tabInformation.Append('"'); } else { tabInformation.Append("[\""); tabInformation.StringEscapeAndAppend(tabContainers[i].Title.ToHTML()); tabInformation.Append('"'); foreach (var condensedTitle in tabContainers[i].CondensedTitles) { tabInformation.Append(",\""); tabInformation.StringEscapeAndAppend(condensedTitle.ToHTML()); tabInformation.Append('"'); } tabInformation.Append(']'); } tabInformation.Append(','); if (extraData.HashPath != null) { tabInformation.Append('"'); tabInformation.StringEscapeAndAppend(extraData.HashPath); tabInformation.Append('"'); } // Otherwise leave an empty spot before the comma. We don't have to write out "undefined". tabInformation.Append(",\""); tabInformation.StringEscapeAndAppend(extraData.DataFileName); tabInformation.Append("\"]"); if (i < tabContainers.Count - 1) { tabInformation.Append(','); } if (!EngineInstance.Config.ShrinkFiles) { tabInformation.Append('\n'); } } if (!EngineInstance.Config.ShrinkFiles) { tabInformation.Append(' ', IndentSpaces); } tabInformation.Append("]);"); System.IO.File.WriteAllText(HTMLBuilder.Menu_DataFile("tabs", 1), tabInformation.ToString()); return(outputFiles); }
/* Function: AppendMembers * A support function for <BuildOutput()>. Appends the output of the container's members to the string, recursively * going through sub-containers as well. This will not include the surrounding brackets, only the comma-separated * member entries. If it finds any sub-containers that start a new data file, it will add them to containersToBuild. */ protected void AppendMembers(MenuEntries.Base.Container container, StringBuilder output, int indent, Stack <MenuEntries.Base.Container> containersToBuild) { for (int i = 0; i < container.Members.Count; i++) { var member = container.Members[i]; if (!EngineInstance.Config.ShrinkFiles) { output.Append(' ', indent * IndentSpaces); } if (member is MenuEntries.Base.Target) { TargetExtraData targetExtraData = (TargetExtraData)member.ExtraData; output.Append(targetExtraData.JSON); } else if (member is MenuEntries.Base.Container) { ContainerExtraData containerExtraData = (ContainerExtraData)member.ExtraData; output.Append(containerExtraData.JSONBeforeMembers); if (containerExtraData.StartsNewDataFile) { output.Append('"'); output.StringEscapeAndAppend(containerExtraData.DataFileName); output.Append('"'); containersToBuild.Push((MenuEntries.Base.Container)member); } else { output.Append('['); if (!EngineInstance.Config.ShrinkFiles) { output.AppendLine(); } AppendMembers((MenuEntries.Base.Container)member, output, indent + 1, containersToBuild); if (!EngineInstance.Config.ShrinkFiles) { output.Append(' ', (indent + 1) * IndentSpaces); } output.Append(']'); } output.Append(containerExtraData.JSONAfterMembers); } #if DEBUG else { throw new Exception("Can't append JSON for menu entry " + member.Title + "."); } #endif if (i < container.Members.Count - 1) { output.Append(','); } if (!EngineInstance.Config.ShrinkFiles) { output.AppendLine(); } } }
/* Function: SegmentMenu * Segments the menu into smaller pieces and generates data file names. */ protected void SegmentMenu(MenuEntries.Base.Container container, string dataFileType, ref StringTable <IDObjects.NumberSet> usedDataFiles) { // Generate the data file name for this container. IDObjects.NumberSet usedDataFileNumbers = usedDataFiles[dataFileType]; if (usedDataFileNumbers == null) { usedDataFileNumbers = new IDObjects.NumberSet(); usedDataFiles.Add(dataFileType, usedDataFileNumbers); } int dataFileNumber = usedDataFileNumbers.LowestAvailable; usedDataFileNumbers.Add(dataFileNumber); ContainerExtraData extraData = (ContainerExtraData)container.ExtraData; extraData.DataFileName = HTMLBuilder.Menu_DataFileNameOnly(dataFileType, dataFileNumber); // The data file has to include all the members in this container no matter what. int containerJSONSize = extraData.JSONBeforeMembers.Length + extraData.JSONAfterMembers.Length + extraData.JSONLengthOfMembers; List <MenuEntries.Base.Container> subContainers = null; foreach (var member in container.Members) { if (member is MenuEntries.Base.Container) { if (subContainers == null) { subContainers = new List <MenuEntries.Base.Container>(); } subContainers.Add((MenuEntries.Base.Container)member); } } // Now start including the contents of subcontainers until we reach the size limit. We're going breadth-first instead of // depth first. List <MenuEntries.Base.Container> nextSubContainers = null; for (;;) { if (subContainers == null || subContainers.Count == 0) { if (nextSubContainers == null || nextSubContainers.Count == 0) { break; } else { subContainers = nextSubContainers; nextSubContainers = null; } } // Add subcontainers to the file in the order from smallest to largest. This prevents one very large container early // in the list from causing all the other ones to be broken out into separate files. // DEPENDENCY: ContainerExtraData.JSONLengthOfMembers must cache its value for this algorithm to be efficient. int smallestSubContainerIndex = 0; int smallestSubContainerSize = (subContainers[0].ExtraData as ContainerExtraData).JSONLengthOfMembers; for (int i = 1; i < subContainers.Count; i++) { if ((subContainers[i].ExtraData as ContainerExtraData).JSONLengthOfMembers < smallestSubContainerSize) { smallestSubContainerIndex = i; smallestSubContainerSize = (subContainers[i].ExtraData as ContainerExtraData).JSONLengthOfMembers; } } containerJSONSize += smallestSubContainerSize; if (containerJSONSize > SegmentLength) { break; } foreach (var member in subContainers[smallestSubContainerIndex].Members) { if (member is MenuEntries.Base.Container) { if (nextSubContainers == null) { nextSubContainers = new List <MenuEntries.Base.Container>(); } nextSubContainers.Add((MenuEntries.Base.Container)member); } } subContainers.RemoveAt(smallestSubContainerIndex); } // Now recurse through any remaining subcontainers so they get their own files. if (subContainers != null) { foreach (var subContainer in subContainers) { SegmentMenu(subContainer, dataFileType, ref usedDataFiles); } } if (nextSubContainers != null) { foreach (var subContainer in nextSubContainers) { SegmentMenu(subContainer, dataFileType, ref usedDataFiles); } } }