Beispiel #1
0
        /* Function: BuildDataFiles
         * Generates the output data file for the container.  It must have <JSONContainer.DataFileName> set.  If it finds any
         * sub-containers that also have that set, it will recursively generate files for them as well.
         */
        protected void BuildDataFiles(JSONMenuEntries.Container container)
        {
                        #if DEBUG
            if (container.StartsNewDataFile == false)
            {
                throw new Exception("BuildOutput() can only be called on containers with DataFileName set.");
            }
                        #endif

            Stack <JSONMenuEntries.Container> containersToBuild = new Stack <JSONMenuEntries.Container>();
            containersToBuild.Push(container);

            while (containersToBuild.Count > 0)
            {
                var    containerToBuild = containersToBuild.Pop();
                string fileName         = containerToBuild.DataFileName;

                StringBuilder output = new StringBuilder();
                output.Append("NDMenu.OnSectionLoaded(\"");
                output.StringEscapeAndAppend(fileName);
                output.Append("\",[");

                if (addWhitespace)
                {
                    output.AppendLine();
                }

                AppendMembers(containerToBuild, output, 1, containersToBuild);

                if (addWhitespace)
                {
                    output.Append(' ', IndentWidth);
                }

                output.Append("]);");

                WriteTextFile(Paths.Menu.OutputFolder(Target.OutputFolder) + "/" + fileName, output.ToString());
            }
        }
Beispiel #2
0
        /* Function: AppendMembers
         * A support function for <BuildDataFile()>.  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(JSONMenuEntries.Container container, StringBuilder output, int indent,
                                     Stack <JSONMenuEntries.Container> containersToBuild)
        {
            for (int i = 0; i < container.Members.Count; i++)
            {
                var member = container.Members[i];

                if (addWhitespace)
                {
                    output.Append(' ', indent * IndentWidth);
                }

                if (member is JSONMenuEntries.Container)
                {
                    var memberContainer = (JSONMenuEntries.Container)member;

                    output.Append(memberContainer.JSONBeforeMembers);

                    if (memberContainer.StartsNewDataFile)
                    {
                        output.Append('"');
                        output.StringEscapeAndAppend(memberContainer.DataFileName);
                        output.Append('"');

                        containersToBuild.Push(memberContainer);
                    }
                    else
                    {
                        output.Append('[');

                        if (addWhitespace)
                        {
                            output.AppendLine();
                        }

                        AppendMembers(memberContainer, output, indent + 1, containersToBuild);

                        if (addWhitespace)
                        {
                            output.Append(' ', (indent + 1) * IndentWidth);
                        }

                        output.Append(']');
                    }

                    output.Append(memberContainer.JSONAfterMembers);
                }
                else                 // not a container
                {
                    var memberTarget = (JSONMenuEntries.Target)member;
                    output.Append(memberTarget.JSON);
                }

                if (i < container.Members.Count - 1)
                {
                    output.Append(',');
                }

                if (addWhitespace)
                {
                    output.AppendLine();
                }
            }
        }
Beispiel #3
0
        /* Function: AssignDataFiles
         *
         * Segments the menu into smaller pieces and generates data file names.
         *
         * Parameters:
         *
         *		container - The container to segment.  This will always be assigned a data file name.
         *		usedDataFiles - A table mapping each <Hierarchy> to the data file numbers already in use for it, such as Files -> {1-4}.
         *							   It will be used to determine which numbers are available to assign, and new numbers will be added to it
         *							   as they are assigned by this function.
         */
        protected void AssignDataFiles(JSONMenuEntries.Container container, ref NumberSetTable <Hierarchy> usedDataFiles)
        {
            // Generate the data file name for this container.

            Hierarchy hierarchy = container.MenuEntry.Hierarchy;

            int dataFileNumber = usedDataFiles.LowestAvailable(hierarchy);

            usedDataFiles.Add(hierarchy, dataFileNumber);

            container.DataFileName = Paths.Menu.OutputFile(Target.OutputFolder, hierarchy, dataFileNumber, fileNameOnly: true);


            // The data file has to include all the members in this container no matter what, so we don't check the size against the limit
            // yet.

            int containerJSONSize = container.JSONBeforeMembers.Length + container.JSONAfterMembers.Length +
                                    container.JSONLengthOfMembers;


            // Now find all the subcontainers, which are now candidates for inlining.

            List <JSONMenuEntries.Container> inliningCandidates = null;

            foreach (var member in container.Members)
            {
                if (member is JSONMenuEntries.Container)
                {
                    var containerMember = (JSONMenuEntries.Container)member;

                    if (inliningCandidates == null)
                    {
                        inliningCandidates = new List <JSONMenuEntries.Container>();
                    }

                    inliningCandidates.Add(containerMember);
                }
            }


            // If there's no subcontainers we're done.

            if (inliningCandidates == null)
            {
                return;
            }


            // Go through all our candidates and inline them 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.

            // Keep track of which containers were inlined so we can possibly inline their members as well.
            List <JSONMenuEntries.Container> inlinedContainers = new List <JSONMenuEntries.Container>();

            while (inliningCandidates.Count > 0)
            {
                // Find the smallest of the candidates

                int smallestInliningCandidateIndex = 0;
                int smallestInliningCandidateSize  = inliningCandidates[0].JSONLengthOfMembers;

                for (int i = 1; i < inliningCandidates.Count; i++)
                {
                    if (inliningCandidates[i].JSONLengthOfMembers < smallestInliningCandidateSize)
                    {
                        smallestInliningCandidateIndex = i;
                        smallestInliningCandidateSize  = inliningCandidates[i].JSONLengthOfMembers;
                    }
                }


                // If the smallest candidate fits into the segment length limits, inline it

                if (containerJSONSize + smallestInliningCandidateSize <= SegmentLength)
                {
                    containerJSONSize += smallestInliningCandidateSize;
                    inlinedContainers.Add(inliningCandidates[smallestInliningCandidateIndex]);
                    inliningCandidates.RemoveAt(smallestInliningCandidateIndex);
                }


                // If the smallest candidate doesn't fit, that means it and all the remaining candidates need to get their own files

                else
                {
                    foreach (var inliningCandidate in inliningCandidates)
                    {
                        AssignDataFiles(inliningCandidate, ref usedDataFiles);
                    }

                    inliningCandidates.Clear();
                }


                // If there's no more candidates, go through the list of inlined containers and add their subcontainers to the candidates
                // list.  This allows us to continue inlining for multiple levels as long as we have space for it.

                // This algorithm causes inlining to happen breadth-first instead of depth-first, which we want, but it also allows lower
                // depths to continue to be inlined even if the parent level couldn't be done completely.  It's possible that when there's
                // no room for all the top-level containers a few more lower level ones could still be squeezed in.

                if (inliningCandidates.Count == 0 && inlinedContainers.Count > 0)
                {
                    foreach (var inlinedContainer in inlinedContainers)
                    {
                        foreach (var member in inlinedContainer.Members)
                        {
                            if (member is JSONMenuEntries.Container)
                            {
                                inliningCandidates.Add((JSONMenuEntries.Container)member);
                            }
                        }
                    }

                    inlinedContainers.Clear();
                }
            }
        }
Beispiel #4
0
        /* 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);
        }