public static Interactive LoadLocal(Stream stream)
        {
            Interactive  story     = new Interactive();
            BinaryReader binReader = new BinaryReader(stream);

            story.ItemId      = binReader.ReadUInt32();
            story.Title       = binReader.ReadString();
            story.Owner       = binReader.ReadString();
            story.Description = binReader.ReadString();
            story.InfoText    = binReader.ReadString();
            ushort count = binReader.ReadByte();

            for (ushort us = 0; us < count; us++)
            {
                story.RootChapters.Add(binReader.ReadUInt16());
            }
            count = binReader.ReadUInt16();
            for (ushort us = 0; us < count; us++)
            {
                story.Chapters.Add(Chapter.LoadLocal(stream));
            }
            return(story);
        }
        public static async Task <Interactive> LoadWebSkeleton(uint itemId)
        {
            Interactive story = new Interactive(itemId);
            // Get HTML document for main story page.
            HtmlDocument htmlDoc = await WebUtilities.GetHtmlDocumentAsync(ParseParams.IABaseUrl + itemId);

            // Start downloading outline page.
            Task <HtmlDocument> outlineDocTask = WebUtilities.GetHtmlDocumentAsync(ParseParams.IABaseUrl + itemId + ParseParams.IAOutlineUrlSegment);
            // Check page title node to see if a valid ID was given.
            HtmlNode pageTitleNode = htmlDoc.DocumentNode.SelectSingleNode(ParseParams.PageTitleXPath);

            if (pageTitleNode == null)
            {
                throw new Exception("Cannot find page title.");
            }
            else if (pageTitleNode.InnerText.Contains("Item Not Found"))
            {
                throw new Exception("Story not found");
            }

            // Find the HTML node with the text for each field we need, and assign the field value.
            story.Title       = WebUtilities.GetHtmlNodeText(htmlDoc, ParseParams.IATitleXPath);
            story.Owner       = WebUtilities.GetHtmlNodeText(htmlDoc, ParseParams.IAOwnerPath);
            story.Description = WebUtilities.GetHtmlNodeText(htmlDoc, ParseParams.IADescriptionXPath);
            story.InfoText    = WebUtilities.GetHtmlNodeText(htmlDoc, ParseParams.IAInfoTextXPath);

            // Finish getting the outline HTML document.
            HtmlDocument outlineDoc = await outlineDocTask;
            // Check if we got the "Please Login" message.
            HtmlNode loginNode = outlineDoc.DocumentNode.SelectSingleNode(ParseParams.IAOutlineLoginXPath);

            if (loginNode != null && loginNode.InnerText.Contains("Please login"))
            {
                throw new Exception("Not logged in, unable to access outline.");
            }
            // Parse out chapter names and map structure.
            HtmlNode        outlineParentNode = outlineDoc.DocumentNode.SelectSingleNode(ParseParams.IAOutlineParentXPath);
            List <HtmlNode> outlineHtmlNodes  = new List <HtmlNode>(outlineParentNode.ChildNodes);
            int             nodeIndex         = 0;
            HtmlNode        spanNode          = null;
            HtmlNode        bNode             = null;

            while (nodeIndex < outlineHtmlNodes.Count)
            {
                // span element contains choice path string (1-2-1-1-3-...).
                if (outlineHtmlNodes[nodeIndex].Name.Equals("span", StringComparison.OrdinalIgnoreCase))
                {
                    spanNode = outlineHtmlNodes[nodeIndex];
                    // span element must come first.
                    bNode = null;
                }
                // b element contains chapter name.
                else if (outlineHtmlNodes[nodeIndex].Name.Equals("b", StringComparison.OrdinalIgnoreCase))
                {
                    bNode = outlineHtmlNodes[nodeIndex];
                }

                if (spanNode != null && bNode != null)
                {
                    // Get choice path as array of ints.
                    string choicePathStr = spanNode.InnerText;
                    // Remove junk at end.
                    choicePathStr = choicePathStr.Substring(0, choicePathStr.Length - 7);
                    // Convert to int.
                    List <byte> choices = new List <byte>();
                    foreach (string s in choicePathStr.Split('-'))
                    {
                        choices.Add(byte.Parse(s));
                    }

                    string chapterName = WebUtilities.CleanHtmlSymbols(bNode.InnerText);
                    // Remove "#_: " from start of name.
                    chapterName = chapterName.Substring(chapterName.IndexOf(' ') + 1);
                    Chapter chapter = new Chapter(chapterName, choices[choices.Count - 1]);
                    story.AddChapter(chapter, choices);
                    spanNode = null;
                    bNode    = null;
                }
                nodeIndex++;
            }

            return(story);
        }