/// <summary> /// Finds the sub snippet with the given tagname in the parenting tag node. /// </summary> /// <param name="tagNode">The parenting tag node</param> /// <param name="snippetName">The subtag to search</param> /// <returns>The sub snippet if it is found or <c>null</c> otherwise</returns> /// <exception cref="FormatException">Thrown if the subtag has more than just a single <see cref="TMLTerminalNode"/>.</exception> private SubSnippet FindSubSnippet(TMLTagNode tagNode, string snippetName) { var subnode = tagNode.InnerNodes.Where(n => n is TMLTagNode).Select(n => (TMLTagNode)n).Where(n => n.TagName == snippetName).FirstOrDefault(); if (subnode != null && subnode.InnerNodes.Count > 0) { //only single inner node allowed if (subnode.InnerNodes.Count > 1 || !(subnode.InnerNodes.First() is TMLTerminalNode)) { throw new FormatException("The solution tag may not have nested tags."); } return(ParseTrimmedContent((TMLTerminalNode)subnode.InnerNodes.First())); } return(null); }
/// <summary> /// Constructs the abstract syntax tree from a given text. /// </summary> /// <param name="text">The original text to analyze</param> /// <exception cref="FormatException">Thrown if the number of opening and closing tags to not match or if an unexpected closing tag is encountered.</exception> public TMLSyntaxTree(string text) { //Setup the regex patterns to match opening and closing tags var tagStartRegex = new Regex(@"//<(?<tag>\w+)(?<attributes>[^>]*?)(?<!/)>"); var tagEndRegex = new Regex(@"//</(?<tag>\w+)>"); var attributesRegex = new Regex("\\b(?<key>\\w+)\\b=\"(?<value>.*?)\""); //Find the opening and closing tags var tagStartMatches = tagStartRegex.Matches(text); var tagEndMatches = tagEndRegex.Matches(text); if (tagStartMatches.Count != tagEndMatches.Count) { throw new FormatException($"There are {tagStartMatches.Count} opening tags but {tagEndMatches.Count} closing tags."); } //the character index where the last text portion (i.e. non-tag text) started var lastTextPosition = 0; //the index of the next tag start within tagStartMatches int nextTagStart = 0; //the index of the next tag end within tagEndMatches int nextTagEnd = 0; //stack of currently open tags var tagStack = new Stack <TMLTagNode>(); //go through the matched opening and closing tags in order while (nextTagStart < tagStartMatches.Count || nextTagEnd < tagEndMatches.Count) { //character index of the next opening tag or text.Length if there are no more opening tags int nextStartPosition = (nextTagStart < tagStartMatches.Count ? tagStartMatches[nextTagStart].Index : text.Length); //character index of the next closing tag or text.length if there are no more closing tags int nextEndPosition = (nextTagEnd < tagEndMatches.Count ? tagEndMatches[nextTagEnd].Index : text.Length); //character index of the next opening or closing tag int nextPosition = Math.Min(nextStartPosition, nextEndPosition); //list of children of the tag we are currently in (either the global list or the child list of a node) var childrenOfParentNode = (tagStack.Count == 0 ? Nodes : tagStack.Peek().InnerNodes); //Add text nodes if the next tag is not at the current position if (nextPosition > lastTextPosition) { childrenOfParentNode.Add(new TMLTerminalNode() { BeginIndex = lastTextPosition, Length = nextPosition - lastTextPosition }); } if (nextStartPosition < nextEndPosition) { //The next tag is an opening one var match = tagStartMatches[nextTagStart++]; var tag = new TMLTagNode() { TagName = match.Groups["tag"].Value, OpeningTagBeginIndex = match.Index, OpeningTagLength = match.Length }; //Gather the attributes var attributesString = match.Groups["attributes"].Value; var attributeMatches = attributesRegex.Matches(attributesString); foreach (Match a in attributeMatches) { tag.Attributes[a.Groups["key"].Value] = a.Groups["value"].Value; } //Add the new tag to the parent childrenOfParentNode.Add(tag); //move into this tag tagStack.Push(tag); lastTextPosition = match.Index + match.Length; } else { //The next tag is a closing one var match = tagEndMatches[nextTagEnd++]; var tagname = match.Groups["tag"].Value; if (tagStack.Count == 0) { throw new FormatException($"Encountered a closing tag of {tagname} but expected no closing tag."); } if (tagStack.Peek().TagName != tagname) { throw new FormatException($"Encountered a closing tag of {tagname} but expected a closing tag of {tagStack.Peek().TagName}."); } //leave the tag var tag = tagStack.Pop(); tag.ClosingTagBeginIndex = match.Index; tag.ClosingTagLength = match.Length; lastTextPosition = match.Index + match.Length; } } //Add a text node for everything after the last tag if (text.Length > lastTextPosition) { Nodes.Add(new TMLTerminalNode() { BeginIndex = lastTextPosition, Length = text.Length - lastTextPosition }); } }