/// <summary>
        /// Loads and parses quest markup data to QuestDocument.
        /// No additional logical structure checks, use "validate" method for it.
        /// </summary>
        /// <param name="markup">Markup source of quest data.</param>
        public static QuestDocument LoadMarkup(string markup)
        {
            if (string.IsNullOrEmpty(markup))
            {
                throw new Exception("Invalid markup data");
            }
            var stream = new QuestStream(markup);
            var doc    = new QuestDocument();

            while (!stream.Eof())
            {
                var    lineId = stream.pointer;
                string name;
                var    page = ParsePage(stream, out name);
                if (doc.pages.ContainsKey(name))
                {
                    throw new Exception(
                              string.Format("Error at {0} line : page with name \"{1}\" already declared before", lineId, name));
                }
                doc.pages[name] = page;
                if (string.IsNullOrEmpty(doc.entry))
                {
                    doc.entry = name;
                }
            }
            doc.ResetProgress();
            return(doc);
        }
 static void ParsePageLogics(QuestPage page, QuestStream stream)
 {
     while (true)
     {
         var line = stream.Next();
         if (line.Length == 0)
         {
             break;
         }
         if (line[0] != '{')
         {
             stream.Previous(); break;
         }
         var matching = Regex.Match(line, "\\{(.+?)\\}");
         if (!matching.Success || string.IsNullOrEmpty(matching.Groups[1].Value))
         {
             throw new Exception(string.Format("Invalid logic at line: {0}", stream.pointer));
         }
         var cond = ParseLogic(QuestLogicFilter.State, matching.Groups[1].Value, stream.pointer);
         if (cond != null)
         {
             if (page.logics == null)
             {
                 page.logics = new List <QuestLogic> ();
             }
             page.logics.Add(cond);
         }
     }
 }
 static void ParsePageTexts(QuestPage page, QuestStream stream)
 {
     _pageTextsBuffer.Length = 0;
     while (true)
     {
         var line = stream.Next();
         if (line.Length == 0)
         {
             break;
         }
         if (line[0] == '*' || line[0] == '-')
         {
             stream.Previous(); break;
         }
         if (_pageTextsBuffer.Length > 0)
         {
             _pageTextsBuffer.Append(" ");
         }
         _pageTextsBuffer.Append(line);
     }
     if (_pageTextsBuffer.Length == 0)
     {
         throw new Exception(string.Format("Invalid page texts at line: {0}", stream.pointer));
     }
     page.texts.AddRange(Regex.Split(_pageTextsBuffer.ToString(), "\\s?\\[br\\]\\s?"));
 }
        static void ParsePageChoices(QuestPage page, QuestStream stream)
        {
            var lineId = stream.pointer;

            while (true)
            {
                var line = stream.Next();
                if (line.Length == 0)
                {
                    break;
                }
                if (line[0] != '*')
                {
                    stream.Previous(); break;
                }
                var matching = Regex.Match(line, "\\*(.*?)->\\s*?(\\b.+)");
                if (!matching.Success)
                {
                    throw new Exception(string.Format("Invalid choice syntax at line: {0}", lineId));
                }
                lineId = stream.pointer;
                var choice = new QuestChoice();
                choice.link = matching.Groups[2].Value.ToLowerInvariant();
                var rawChoiceText = matching.Groups[1].Value;
                if (!string.IsNullOrEmpty(rawChoiceText))
                {
                    var matchingCond = Regex.Match(rawChoiceText, "\\{(.*?)\\}(.+)");
                    if (!matchingCond.Success)
                    {
                        choice.text = rawChoiceText.Trim();
                    }
                    else
                    {
                        choice.condition = ParseLogic(QuestLogicFilter.Expression, matchingCond.Groups[1].Value, lineId);
                        choice.text      = matchingCond.Groups[2].Value.Trim();
                    }
                }
                page.choices.Add(choice);
            }
            if (page.choices.Count == 0)
            {
                throw new Exception(string.Format("No choices at line: {0}", lineId));
            }
            if (page.choices.Count == 1 && page.choices[0].condition != null)
            {
                throw new Exception(string.Format("Auto choice cant use condition at line: {0}", lineId));
            }
        }
        static QuestPage ParsePage(QuestStream stream, out string pageName)
        {
            var line     = stream.Next();
            var matching = Regex.Match(line, "->\\s*?(\\b.+)");

            if (!matching.Success)
            {
                throw new Exception(string.Format("Invalid page header at line: {0}", stream.pointer));
            }
            var name = matching.Groups[1].Value.ToLowerInvariant();

            if (name == "end")
            {
                throw new Exception(string.Format("Invalid page name at line: {0}", stream.pointer));
            }
            var page = new QuestPage();

            ParsePageLogics(page, stream);
            ParsePageTexts(page, stream);
            ParsePageChoices(page, stream);
            pageName = name;
            return(page);
        }