public static IList<ParsedChunk> ParseChunk(string input) { #region init // init var toReturn = new List<ParsedChunk>(); var openTags = new Stack<BbTag>(); var tags = new Queue<BbTag>(); var processedQueue = new Queue<BbTag>(); var finder = new BbFinder(input); // find all tags while (!finder.HasReachedEnd) { var next = finder.Next(); if (next != null) tags.Enqueue(next); } // return original input if we've no valid bbcode tags if (tags.All(x => x.Type == BbCodeType.None)) return new[] {AsChunk(input)}; #endregion #region handle unbalanced tags var unbalancedTags = (from t in tags where t.Type != BbCodeType.None group t by t.Type into g select new {Type = g.Key, Tags = g}) .Where(x => x.Tags.Count()%2 == 1); foreach (var tagGroup in unbalancedTags.ToList()) tagGroup.Tags.First().Type = BbCodeType.None; #endregion while (tags.Count > 0) { // get the next tag to process var tag = tags.Dequeue(); var addToQueue = true; #region add as child of last tag // check if we're in the context of another open tag if (openTags.Count > 0) { var lastOpen = openTags.Peek(); // check if we're the closing for the last open tag if (lastOpen.Type == tag.Type && tag.IsClosing) { lastOpen.ClosingTag = tag; openTags.Pop(); #region handle noparse if (lastOpen.Type == BbCodeType.NoParse) { lastOpen.Children = lastOpen.Children ?? new List<BbTag>(); lastOpen.Children.Add(new BbTag { Type = BbCodeType.None, End = tag.Start, Start = lastOpen.End }); } #endregion } else { if (lastOpen.Type != BbCodeType.NoParse) { // if not, we have to be a child of it lastOpen.Children = lastOpen.Children ?? new List<BbTag>(); lastOpen.Children.Add(tag); } addToQueue = false; } } #endregion // we don't need to continue processing closing tags if (tag.IsClosing) continue; // tell the system we're in the context of this tag now if (tag.Type != BbCodeType.None) // text content can't have children openTags.Push(tag); // if we're added as a child to another tag, don't process independently of parent if (addToQueue) processedQueue.Enqueue(tag); } // if in the process of removing improper tags we end up with no bbcode, // return original if (processedQueue.All(x => x.Type == BbCodeType.None)) return new[] {AsChunk(input)}; toReturn.AddRange(processedQueue.Select(x => FromTag(x, input))); return toReturn; }
public static IList <ParsedChunk> ParseChunk(string input) { #region init // init var toReturn = new List <ParsedChunk>(); var openTags = new Stack <BbTag>(); var tags = new Queue <BbTag>(); var processedQueue = new Queue <BbTag>(); var finder = new BbFinder(input); // find all tags while (!finder.HasReachedEnd) { var next = finder.Next(); if (next != null) { tags.Enqueue(next); } } // return original input if we've no valid bbcode tags if (tags.All(x => x.Type == BbCodeType.None)) { return new[] { AsChunk(input) } } ; #endregion while (tags.Count > 0) { // get the next tag to process var tag = tags.Dequeue(); var addToQueue = true; #region add as child of last tag // check if we're in the context of another open tag if (openTags.Count > 0) { var lastOpen = openTags.Peek(); var lastMatching = openTags.FirstOrDefault(x => tag.IsClosing && x.Type == tag.Type); // check if we're closing any previous tags if (lastMatching != null) { lastMatching.ClosingTag = tag; // keep going through our opened tag stack until we find the one // we're closing do { lastOpen = openTags.Pop(); // if we end up with a tag that isn't the one we're closing, // it must not have been closed correctly, e.g // [i] [b] [/i] // we'll treat that '[b]' as text if (lastOpen != lastMatching) { lastOpen.Type = BbCodeType.None; } } while (lastOpen != lastMatching); #region handle noparse if (lastMatching.Type == BbCodeType.NoParse) { lastMatching.Children = lastMatching.Children ?? new List <BbTag>(); lastMatching.Children.Add(new BbTag { Type = BbCodeType.None, End = tag.Start, Start = lastMatching.End }); } #endregion } else { if (openTags.All(x => x.Type != BbCodeType.NoParse)) { // if not, we have to be a child of it lastOpen.Children = lastOpen.Children ?? new List <BbTag>(); lastOpen.Children.Add(tag); } // any matching closing tags would be caught in the if part of this // branch, this is an invalid tag, treat as text if (tag.IsClosing) { tag.Type = BbCodeType.None; } addToQueue = false; } } #endregion // we don't need to continue processing closing tags if (tag.IsClosing) { continue; } // tell the system we're in the context of this tag now // though ignore children of 'text' and 'hr' if (tag.Type != BbCodeType.None && tag.Type != BbCodeType.HorizontalRule) { openTags.Push(tag); } // if we're added as a child to another tag, don't process independently of parent if (addToQueue) { processedQueue.Enqueue(tag); } } // these tags haven't been closed, so treat them as invalid foreach (var openTag in openTags) { openTag.Type = BbCodeType.None; } // if we have no bbcode present, just return the text as-is if (processedQueue.All(x => x.Type == BbCodeType.None && x.Children == null)) { return new[] { AsChunk(input) } } ; toReturn.AddRange(processedQueue.Select(x => FromTag(x, input))); return(toReturn); }
public static IList<ParsedChunk> ParseChunk(string input) { #region init // init var toReturn = new List<ParsedChunk>(); var openTags = new Stack<BbTag>(); var tags = new Queue<BbTag>(); var processedQueue = new Queue<BbTag>(); var finder = new BbFinder(input); // find all tags while (!finder.HasReachedEnd) { var next = finder.Next(); if (next != null) tags.Enqueue(next); } // return original input if we've no valid bbcode tags if (tags.All(x => x.Type == BbCodeType.None)) return new[] {AsChunk(input)}; #endregion while (tags.Count > 0) { // get the next tag to process var tag = tags.Dequeue(); var addToQueue = true; #region add as child of last tag // check if we're in the context of another open tag if (openTags.Count > 0) { var lastOpen = openTags.Peek(); var lastMatching = openTags.FirstOrDefault(x => tag.IsClosing && x.Type == tag.Type); // check if we're closing any previous tags if (lastMatching != null) { lastMatching.ClosingTag = tag; // keep going through our opened tag stack until we find the one // we're closing do { lastOpen = openTags.Pop(); // if we end up with a tag that isn't the one we're closing, // it must not have been closed correctly, e.g // [i] [b] [/i] // we'll treat that '[b]' as text if (lastOpen != lastMatching) lastOpen.Type = BbCodeType.None; } while (lastOpen != lastMatching); #region handle noparse if (lastMatching.Type == BbCodeType.NoParse) { lastMatching.Children = lastMatching.Children ?? new List<BbTag>(); lastMatching.Children.Add(new BbTag { Type = BbCodeType.None, End = tag.Start, Start = lastMatching.End }); } #endregion } else { if (openTags.All(x => x.Type != BbCodeType.NoParse)) { // if not, we have to be a child of it lastOpen.Children = lastOpen.Children ?? new List<BbTag>(); lastOpen.Children.Add(tag); } // any matching closing tags would be caught in the if part of this // branch, this is an invalid tag, treat as text if (tag.IsClosing) tag.Type = BbCodeType.None; addToQueue = false; } } #endregion // we don't need to continue processing closing tags if (tag.IsClosing) continue; // tell the system we're in the context of this tag now // though ignore children of 'text' and 'hr' if (tag.Type != BbCodeType.None && tag.Type != BbCodeType.HorizontalRule) openTags.Push(tag); // if we're added as a child to another tag, don't process independently of parent if (addToQueue) processedQueue.Enqueue(tag); } // these tags haven't been closed, so treat them as invalid foreach (var openTag in openTags) openTag.Type = BbCodeType.None; // if we have no bbcode present, just return the text as-is if (processedQueue.All(x => x.Type == BbCodeType.None && x.Children == null)) return new[] {AsChunk(input)}; toReturn.AddRange(processedQueue.Select(x => FromTag(x, input))); return toReturn; }