/* Function: CreateLanguageEntry * Creates an entry for the language, adds it to the class menu, and returns it. It will also create the <rootClassMenu> * container if necessary. */ protected MenuEntries.Language CreateLanguageEntry(Languages.Language language) { #if DEBUG if (FindLanguageEntry(language) != null) { throw new Exception("Tried to create a language entry that already existed in the menu."); } #endif if (rootClassMenu == null) { rootClassMenu = new MenuEntries.Container(Hierarchy.Class); rootClassMenu.Title = Engine.Locale.Get("NaturalDocs.Engine", "Menu.Classes"); } MenuEntries.Language languageEntry = new MenuEntries.Language(language, Hierarchy.Class); languageEntry.Parent = rootClassMenu; rootClassMenu.Members.Add(languageEntry); return(languageEntry); }
/* Function: CreateFileSourceEntry * Creates an entry for the file source, adds it to the menu, and returns it. It will also create the <rootFileMenu> * container if necessary. */ protected MenuEntries.FileSource CreateFileSourceEntry(Files.FileSource fileSource) { #if DEBUG if (FindFileSourceEntry(fileSource) != null) { throw new Exception("Tried to create a file source entry that already existed in the menu."); } #endif if (rootFileMenu == null) { rootFileMenu = new MenuEntries.Container(Hierarchy.File); rootFileMenu.Title = Engine.Locale.Get("NaturalDocs.Engine", "Menu.Files"); } MenuEntries.FileSource fileSourceEntry = new MenuEntries.FileSource(fileSource); fileSourceEntry.Parent = rootFileMenu; rootFileMenu.Members.Add(fileSourceEntry); return(fileSourceEntry); }
/* Function: AddFile * Adds a file to the menu tree. */ public void AddFile(Files.File file) { #if DEBUG if (isCondensed) { throw new Exception("Cannot add a file to the menu once it's been condensed."); } #endif // Find which file source owns this file and generate a relative path to it. MenuEntries.FileSource fileSourceEntry = FindOrCreateFileSourceEntryOf(file); Path relativePath = fileSourceEntry.WrappedFileSource.MakeRelative(file.FileName); // Split off the file name and split the rest into individual folder names. string prefix; List <string> pathSegments; relativePath.Split(out prefix, out pathSegments); string fileName = pathSegments[pathSegments.Count - 1]; pathSegments.RemoveAt(pathSegments.Count - 1); // Create the file entry and find out where it goes. Create new folder levels as necessary. MenuEntries.File fileEntry = new MenuEntries.File(file); MenuEntries.Container container = fileSourceEntry; foreach (string pathSegment in pathSegments) { Path pathFromFileSource; if (container == fileSourceEntry) { pathFromFileSource = pathSegment; } else { pathFromFileSource = (container as MenuEntries.Folder).PathFromFileSource + '/' + pathSegment; } MenuEntries.Folder folderEntry = null; foreach (var member in container.Members) { if (member is MenuEntries.Folder && (member as MenuEntries.Folder).PathFromFileSource == pathFromFileSource) { folderEntry = (MenuEntries.Folder)member; break; } } if (folderEntry == null) { folderEntry = new MenuEntries.Folder(pathFromFileSource); folderEntry.Parent = container; container.Members.Add(folderEntry); } container = folderEntry; } fileEntry.Parent = container; container.Members.Add(fileEntry); }
/* Function: AddClass * Adds a class to the menu tree. */ public void AddClass(Symbols.ClassString classString) { #if DEBUG if (isCondensed) { throw new Exception("Cannot add a class to the menu once it's been condensed."); } #endif string[] classSegments = classString.Symbol.SplitSegments(); MenuEntries.Container container; bool ignoreCase; if (classString.Hierarchy == Hierarchy.Class) { MenuEntries.Language languageEntry = FindOrCreateLanguageEntryOf(classString); container = languageEntry; ignoreCase = (languageEntry.WrappedLanguage.CaseSensitive == false); } else if (classString.Hierarchy == Hierarchy.Database) { if (rootDatabaseMenu == null) { rootDatabaseMenu = new MenuEntries.Container(Hierarchy.Database); rootDatabaseMenu.Title = Engine.Locale.Get("NaturalDocs.Engine", "Menu.Database"); } container = rootDatabaseMenu; ignoreCase = true; } else { throw new NotImplementedException(); } // Create the class and find out where it goes. Create new scope containers as necessary. MenuEntries.Class classEntry = new MenuEntries.Class(classString); string scopeSoFar = null; // We only want to walk through the scope levels so we use length - 1 to ignore the last segment, which is the class name. for (int i = 0; i < classSegments.Length - 1; i++) { string classSegment = classSegments[i]; if (scopeSoFar == null) { scopeSoFar = classSegment; } else { scopeSoFar += Symbols.SymbolString.SeparatorChar + classSegment; } MenuEntries.Scope scopeEntry = null; foreach (var member in container.Members) { if (member is MenuEntries.Scope && string.Compare((member as MenuEntries.Scope).WrappedScopeString, scopeSoFar, ignoreCase) == 0) { scopeEntry = (MenuEntries.Scope)member; break; } } if (scopeEntry == null) { scopeEntry = new MenuEntries.Scope(Symbols.SymbolString.FromExportedString(scopeSoFar), classString.Hierarchy); scopeEntry.Parent = container; container.Members.Add(scopeEntry); } container = scopeEntry; } classEntry.Parent = container; container.Members.Add(classEntry); }
/* Function: ConvertToJSON * Converts a Container menu entry to JSON, along with all of its members. This is a recursive function so it will convert * the entire tree inside the container. */ protected JSONMenuEntries.Container ConvertToJSON(MenuEntries.Container menuContainer) { JSONMenuEntries.Container jsonContainer = new JSONMenuEntries.Container(menuContainer); StringBuilder jsonBeforeMembers = new StringBuilder(); jsonBeforeMembers.Append("[2,"); // Title if (menuContainer.CondensedTitles == null) { if (menuContainer.Title != null) { jsonBeforeMembers.Append('"'); jsonBeforeMembers.StringEscapeAndAppend(menuContainer.Title.ToHTML()); jsonBeforeMembers.Append('"'); } // Otherwise leave an empty space before the comma. We don't have to write out "undefined". } else { jsonBeforeMembers.Append("[\""); jsonBeforeMembers.StringEscapeAndAppend(menuContainer.Title.ToHTML()); jsonBeforeMembers.Append('"'); foreach (string condensedTitle in menuContainer.CondensedTitles) { jsonBeforeMembers.Append(",\""); jsonBeforeMembers.StringEscapeAndAppend(condensedTitle.ToHTML()); jsonBeforeMembers.Append('"'); } jsonBeforeMembers.Append(']'); } // Hash path jsonBeforeMembers.Append(','); string hashPath = null; if (menuContainer is MenuEntries.FileSource) { var fileSourceEntry = (MenuEntries.FileSource)menuContainer; hashPath = Paths.SourceFile.FolderHashPath(fileSourceEntry.WrappedFileSource.Number, fileSourceEntry.CondensedPathFromFileSource); } else if (menuContainer is MenuEntries.Folder) { var folderEntry = (MenuEntries.Folder)menuContainer; // Walk up the tree until you find the FileSource MenuEntries.Container parentEntry = menuContainer.Parent; #if DEBUG if (parentEntry == null) { throw new Exception("Parent must be defined when generating JSON for menu folder \"" + (folderEntry.Title ?? "") + "\"."); } #endif while ((parentEntry is MenuEntries.FileSource) == false) { parentEntry = parentEntry.Parent; #if DEBUG if (parentEntry == null) { throw new Exception("Couldn't find a file source among the folder \"" + (folderEntry.Title ?? "") + "\"'s parents when generating JSON."); } #endif } var fileSourceEntry = (MenuEntries.FileSource)parentEntry; hashPath = Paths.SourceFile.FolderHashPath(fileSourceEntry.WrappedFileSource.Number, folderEntry.PathFromFileSource); } else if (menuContainer is MenuEntries.Language) { var languageEntry = (MenuEntries.Language)menuContainer; hashPath = Paths.Class.QualifierHashPath(languageEntry.WrappedLanguage.SimpleIdentifier, languageEntry.CondensedScopeString); } else if (menuContainer is MenuEntries.Scope) { var scopeEntry = (MenuEntries.Scope)menuContainer; if (scopeEntry.Hierarchy == Hierarchy.Class) { // Walk up the tree until you find the language MenuEntries.Container parentEntry = menuContainer.Parent; #if DEBUG if (parentEntry == null) { throw new Exception("Parent must be defined when generating JSON for menu scope \"" + (scopeEntry.Title ?? "") + "\"."); } #endif while ((parentEntry is MenuEntries.Language) == false) { parentEntry = parentEntry.Parent; #if DEBUG if (parentEntry == null) { throw new Exception("Couldn't find a language among the scope \"" + (scopeEntry.Title ?? "") + "\"'s parents when generating JSON."); } #endif } var languageEntry = (MenuEntries.Language)parentEntry; hashPath = Paths.Class.QualifierHashPath(languageEntry.WrappedLanguage.SimpleIdentifier, scopeEntry.WrappedScopeString); } else if (scopeEntry.Hierarchy == Hierarchy.Database) { hashPath = Paths.Database.QualifierHashPath(scopeEntry.WrappedScopeString); } else { throw new NotImplementedException(); } } // If we're at one of the menu roots else if (menuContainer.Parent == null) { if (menuContainer.Hierarchy == Hierarchy.File || menuContainer.Hierarchy == Hierarchy.Class) { // If we're at a root file or class container that is not also a language or file source, it means there are multiple // languages and/or file sources beneath it and thus there is no shared hash path. "CSharpClass:" and "PerlClass:", // "Files:" and "Files2:", etc. hashPath = null; } else if (menuContainer.Hierarchy == Hierarchy.Database) { // If we're at the root database menu and the entry is not also a scope, it means there are multiple scopes beneath it. // However, unlike files and classes, there is still the shared "Database:" hash path. hashPath = Paths.Database.QualifierHashPath(); } else { throw new NotImplementedException(); } } else { throw new NotImplementedException(); } if (hashPath != null) { jsonBeforeMembers.Append('"'); jsonBeforeMembers.StringEscapeAndAppend(hashPath); jsonBeforeMembers.Append('"'); } // Otherwise leave an empty space before the comma. We don't have to write out "undefined". jsonBeforeMembers.Append(','); jsonContainer.JSONBeforeMembers = jsonBeforeMembers.ToString(); jsonContainer.JSONAfterMembers = "]"; jsonContainer.HashPath = hashPath; // Now recurse into members foreach (var member in menuContainer.Members) { jsonContainer.Members.Add(ConvertToJSON(member, jsonContainer.HashPath)); } return(jsonContainer); }