public static List <NemmetTag> Parse(string code) { // This returns a List<NemmetTag> because there could be multiple top-level tags ("tag1+tag2+tag3"). We can't assume this HTML fragment will be well-formed. // The top tag on this stack represents the tag to which new tags will be added as children. We put an empty placeholder in there to initialize it. var tagStack = new Stack <NemmetTag>(); tagStack.Push(new NemmetTag("root")); // We'll keep track if we're in a brace or not to determine whether something is an actual operator or just text content var inBraces = false; // Iterate through each character // We add the sibling operator to the end to make sure the last tag gets added (remember, we only process the buffer when we encouter an operator, so we need to make sure there's a final operator on the end) var buffer = new StringBuilder(); foreach (var character in string.Concat(code, SIBLING_OPERATOR)) { // Toggle the braces indicator so we know if something is an operator of just content if (character == OPEN_CURLEY_BRACE || character == CLOSING_CURLEY_BRACE) { inBraces = !inBraces; } // Is this an operator that is NOT contained in a brace? if (string.Concat(SIBLING_OPERATOR, CHILD_OPERATOR, CLIMBUP_OPERATOR, OPEN_PARAN).Contains(character) && !inBraces) { // We have encountered an operator, which means whatever is in the buffer represents a single tag // We need to... // 1. Create this tag // 2. Evaluate the operator to determine where the NEXT tag will go by manipulating the tagStack) // If there's anything in the buffer, process it as a new child of the context tag // (If you're climbing up more than one level at a time ("^^") there might not be anything in the buffer.) NemmetTag tag = null; if (buffer.Length > 0) { tag = new NemmetTag(buffer.ToString()); // If the name is empty, then name this based on the defaults // (I can't do this inside of NemmetTag, because it requires access to the prior tag...) if (string.IsNullOrWhiteSpace(tag.Name)) { tag.Name = NemmetParsingOptions.DefaultTagMap.ContainsKey(tagStack.Peek().Name) ? NemmetParsingOptions.DefaultTagMap[tagStack.Peek().Name] : NemmetParsingOptions.DefaultTag; } // Add this to the top tag on the stack tagStack.Peek().Children.Add(tag); // We empty the buffer so we can start accumulating the next tag buffer.Clear(); } // Now, what do we do with the NEXT tag? // The next tag should be added to the same tag as the last one. if (character == SIBLING_OPERATOR) { // Do nothing. This is just for clarity. } // Climb up. Remove the top tag, to reveal its parent underneath. if (character == CLIMBUP_OPERATOR) { tagStack.Pop(); } // Descend. Add this tag to the top of stack. if (character == CHILD_OPERATOR) { tagStack.Push(tag); } } else { buffer.Append(character); } } // The base tag in the stack was just a placeholder, remember. We want to return the top-level children of that. return(tagStack.Last().Children); }
public static string GetHtml(string code) { return(NemmetTag.Parse(code).Select(x => x.ToHtml()).JoinOn()); }