/// <summary> /// Causes the <see cref="ITagDefinition"/> to render all of its <see cref="ITagInstance"/>s into XHTML. /// </summary> /// <remarks> /// This method is always called after <see cref="ITagDefinition.RemoveWhitespace"/> is called on any /// <see cref="ITagDefinition"/>. The order in which this method is called across the different /// <see cref="ITagDefinition"/>s is undefined. /// </remarks> /// <param name="context"> /// The <see cref="IRenderContext"/> contains all the <see cref="ITagInstance"/>s, the input text, /// and methods that allow for the rendering of <see cref="ITagInstance"/>s. /// </param> public override void Render(IRenderContext context) { IEnumerable <IOpenTagInstance> tagInstances = context.Tags.Where(t => t.ParentDefinition == this) .OfType <IOpenTagInstance>(); foreach (IOpenTagInstance openTag in tagInstances) { ICloseTagInstance closeTag = openTag.CloseTag; string url = context.GetRenderString(openTag, closeTag); //If the url isn't a real URL, don't create a tag and skip it instead Match urlMatch = RegularExpressions.Url.Match(url); if (urlMatch.Success == false || urlMatch.Index != 0 || urlMatch.Length != url.Length) { continue; } string tag; int width = (int)openTag.Attributes["Width"]; int height = (int)openTag.Attributes["Height"]; tag = String.Format(REPLACEMENT_TAG_FORMAT_STRING, url, width, height); context.RenderTag(openTag, tag, true); //Remove the containing text and the end tag as the open tag is the only tag now int indexAfterOpenTag = openTag.CharRange.StartAt + openTag.CharRange.Length; context.RenderNonTagRange(indexAfterOpenTag, closeTag.CharRange.StartAt - indexAfterOpenTag, String.Empty, false); context.RenderTag(closeTag, String.Empty, false); } context.RegisterRenderCacheability(true); }
/// <summary> /// Causes the <see cref="ITagDefinition"/> to render all of its <see cref="ITagInstance"/>s into XHTML. /// </summary> /// <remarks> /// This method is always called after <see cref="ITagDefinition.RemoveWhitespace"/> is called on any /// <see cref="ITagDefinition"/>. The order in which this method is called across the different /// <see cref="ITagDefinition"/>s is undefined. /// </remarks> /// <param name="context"> /// The <see cref="IRenderContext"/> contains all the <see cref="ITagInstance"/>s, the input text, /// and methods that allow for the rendering of <see cref="ITagInstance"/>s. /// </param> public override void Render(IRenderContext context) { IEnumerable <IOpenTagInstance> tagInstances = context.Tags.Where(t => t.ParentDefinition == this) .OfType <IOpenTagInstance>(); foreach (IOpenTagInstance openTag in tagInstances) { ICloseTagInstance closeTag = openTag.CloseTag; string text = context.GetRenderString(openTag, closeTag); string href = openTag.Attributes.ContainsKey("URL") ? (string)openTag.Attributes["URL"] : text; //If the href isn't a real URL, don't create a tag and skip it instead Match urlMatch = RegularExpressions.Url.Match(href); if (urlMatch.Success == false || urlMatch.Index != 0 || urlMatch.Length != href.Length) { continue; } string tag = String.Format(REPLACEMENT_OPEN_TAG_FORMAT_STRING, WebUtility.HtmlEncode(href)); context.RenderTag(openTag, tag, true); context.RenderTag(closeTag, REPLACEMENT_CLOSE_TAG, true); } context.RegisterRenderCacheability(true); }
/// <summary> /// Causes the <see cref="ITagDefinition"/> to remove any whitespace it sees fit that is around its /// identified <see cref="ITagInstance"/>s. An <see cref="ITagDefinition"/> does not need to remove /// any whitespace, if it doesn't want to. /// </summary> /// <remarks> /// This method is always called before <see cref="ITagDefinition.Render"/> is called on any <see cref="ITagDefinition"/>. /// The order in which this method is called across the different <see cref="ITagDefinition"/>s is undefined. /// </remarks> /// <param name="context"> /// The <see cref="IWhitespaceRemovalContext"/> contains all <see cref="ITagInstance"/>s, the input text, /// and methods that allow for the removal of whitespace. /// </param> public void RemoveWhitespace(IWhitespaceRemovalContext context) { IEnumerable <IOpenTagInstance> tagInstances = context.Tags.Where(t => t.ParentDefinition == this) .OfType <OpenTagInstance>(); foreach (IOpenTagInstance openTag in tagInstances) { ICloseTagInstance closeTag = openTag.CloseTag; IList <ListItemTagInstance> listItemTags = context.GetTagsAfter(openTag) .TakeWhile(t => t != closeTag) .WhereStackDepthIs(0) .OfType <ListItemTagInstance>() .ToList(); //Don't remove whitespace if there's only whitespace in between the open and close tags //because the tag won't be rendered if (listItemTags.Any() == false && _WhitespaceRegex.Match(context.GetRenderString(openTag, closeTag)).Success) { continue; } context.RemoveWhitespaceBeforeTag(openTag, 1); context.RemoveWhitespaceAfterTag(openTag, 1); context.RemoveWhitespaceBeforeTag(openTag.CloseTag, 1); context.RemoveWhitespaceAfterTag(openTag.CloseTag, 2); } }
/// <summary> /// Causes the <see cref="ITagDefinition"/> to render all of its <see cref="ITagInstance"/>s into XHTML. /// </summary> /// <remarks> /// This method is always called after <see cref="ITagDefinition.RemoveWhitespace"/> is called on any /// <see cref="ITagDefinition"/>. The order in which this method is called across the different /// <see cref="ITagDefinition"/>s is undefined. /// </remarks> /// <param name="context"> /// The <see cref="IRenderContext"/> contains all the <see cref="ITagInstance"/>s, the input text, /// and methods that allow for the rendering of <see cref="ITagInstance"/>s. /// </param> public void Render(IRenderContext context) { IEnumerable <IOpenTagInstance> tagInstances = context.Tags.Where(t => t.ParentDefinition == this) .OfType <OpenTagInstance>(); foreach (IOpenTagInstance openTag in tagInstances) { ICloseTagInstance closeTag = openTag.CloseTag; IList <ListItemTagInstance> listItemTags = context.GetTagsAfter(openTag) .TakeWhile(t => t != closeTag) .WhereStackDepthIs(0) .OfType <ListItemTagInstance>() .ToList(); string replacementOpenTag; string replacementCloseTag; ChooseReplacementTags(openTag, out replacementOpenTag, out replacementCloseTag); if (listItemTags.Any() == false) { //Render only if there's not only whitespace in between the open and close tags if (_WhitespaceRegex.Match(context.GetRenderString(openTag, closeTag)).Success == false) { context.RenderTag(openTag, replacementOpenTag + REPLACEMENT_LIST_ITEM_OPEN_TAG, true); context.RenderTag(closeTag, REPLACEMENT_LIST_ITEM_CLOSE_TAG + replacementCloseTag, true); } } else { //If there's content between the open tag and the first list item tag, wrap that in a li tag too ListItemTagInstance firstListItemTag = listItemTags.First(); if (_WhitespaceRegex.Match(context.GetRenderString(openTag, firstListItemTag)).Success) { context.RenderTag(openTag, replacementOpenTag, true); context.RenderNonTagRange(openTag.CharRange.StartAt + openTag.CharRange.Length, firstListItemTag.CharRange.StartAt - (openTag.CharRange.StartAt + openTag.CharRange.Length), String.Empty, false); context.RenderTag(firstListItemTag, REPLACEMENT_LIST_ITEM_OPEN_TAG, true); } else { context.RenderTag(openTag, replacementOpenTag + REPLACEMENT_LIST_ITEM_OPEN_TAG, true); context.RenderTag(firstListItemTag, REPLACEMENT_LIST_ITEM_CLOSE_TAG + REPLACEMENT_LIST_ITEM_OPEN_TAG, true); } foreach (ListItemTagInstance listItemTagInstance in listItemTags.Skip(1)) { context.RenderTag(listItemTagInstance, REPLACEMENT_LIST_ITEM_CLOSE_TAG + REPLACEMENT_LIST_ITEM_OPEN_TAG, true); } context.RenderTag(closeTag, REPLACEMENT_LIST_ITEM_CLOSE_TAG + replacementCloseTag, true); } } context.RegisterRenderCacheability(true); }
/// <summary> /// Causes the <see cref="ITagDefinition"/> to remove any whitespace it sees fit that is around its /// identified <see cref="ITagInstance"/>s. An <see cref="ITagDefinition"/> does not need to remove any /// whitespace, if it doesn't want to. /// </summary> /// <remarks> /// <para> /// This method is always called before <see cref="ITagDefinition.Render"/> is called on any /// <see cref="ITagDefinition"/>. The order in which this method is called across the different /// <see cref="ITagDefinition"/>s is undefined. /// </para> /// <para> /// This method does nothing in its default implementation. Inheritors should override this method /// if they want to perform whitespace removal. /// </para> /// </remarks> /// <param name="context"> /// The <see cref="IWhitespaceRemovalContext"/> contains all <see cref="ITagInstance"/>s, the input text, /// and methods that allow for the removal of whitespace. /// </param> public override void RemoveWhitespace(IWhitespaceRemovalContext context) { IEnumerable <IOpenTagInstance> tagInstances = context.Tags.Where(t => t.ParentDefinition == this) .OfType <IOpenTagInstance>(); foreach (IOpenTagInstance openTag in tagInstances) { ICloseTagInstance closeTag = openTag.CloseTag; context.RemoveWhitespaceBeforeTag(openTag, 1); context.RemoveWhitespaceAfterTag(openTag, 1); context.RemoveWhitespaceBeforeTag(closeTag, 1); context.RemoveWhitespaceAfterTag(closeTag, 2); } }
/// <summary> /// Causes the <see cref="ITagDefinition"/> to render all of its <see cref="ITagInstance"/>s into XHTML. /// </summary> /// <remarks> /// This method is always called after <see cref="ITagDefinition.RemoveWhitespace"/> is called on any /// <see cref="ITagDefinition"/>. The order in which this method is called across the different /// <see cref="ITagDefinition"/>s is undefined. /// </remarks> /// <param name="context"> /// The <see cref="IRenderContext"/> contains all the <see cref="ITagInstance"/>s, the input text, /// and methods that allow for the rendering of <see cref="ITagInstance"/>s. /// </param> public override void Render(IRenderContext context) { IEnumerable <IOpenTagInstance> tagInstances = context.Tags.Where(t => t.ParentDefinition == this) .OfType <IOpenTagInstance>(); foreach (IOpenTagInstance openTag in tagInstances) { ICloseTagInstance closeTag = openTag.CloseTag; string replacementOpenTag = REPLACEMENT_OPEN_TAG; if (openTag.Attributes.ContainsKey("Username")) { replacementOpenTag += String.Format(AUTHOR_TAG_FORMAT_STRING, WebUtility.HtmlEncode((string)openTag.Attributes["Username"])); } context.RenderTag(openTag, replacementOpenTag, true); //Wrap all content that is not inside an block element tag in P tags ITagInstance afterTag = openTag; while (afterTag != null) { //Find the next block element tag nested inside openTag IOpenTagInstance untilTag = context.GetTagsAfter(afterTag) .TakeWhile(t => t != closeTag) //Take until the close tag .WhereStackDepthIs(0) //Only tags immediately nested inside openTag .Where(t => t.RendersToInlineElement == false) .OfType <IOpenTagInstance>() .FirstOrDefault(); if (untilTag != null) { WrapTextBetweenTagsInPTags(afterTag, untilTag, context); afterTag = untilTag.CloseTag; } else { WrapTextBetweenTagsInPTags(afterTag, closeTag, context); afterTag = null; } } context.RenderTag(closeTag, REPLACEMENT_CLOSE_TAG, true); } context.RegisterRenderCacheability(true); }
/// <summary> /// Validates the BBCode tags and ensures they respect the rules they define. For example, some tags /// disallow other tags inside them. This is where those rules are enforced and errant tags /// (<see cref="ITagInstance"/>s) are removed. /// </summary> /// <param name="context">The <see cref="ValidationContext"/></param> private void ValidateAndRepairSyntax(ValidationContext context) { for (int i = 0; i < context.Tags.Count; i++) { ITagInstance tagInstance = context.Tags[i]; bool vetoed = CheckForPreviousTagOverlap(context, i); if (vetoed == false && tagInstance is IOpenTagInstance) { IOpenTagInstance openTagInstance = (IOpenTagInstance)tagInstance; //If any other tag vetoes this one, OR this tag vetoes itself vetoed = context.OpenTagStack.Any(t => t.CheckForVetoAgainstAnotherTag(openTagInstance, context)) || openTagInstance.CheckForSelfVeto(context); if (vetoed == false) { context.OpenTagStack.Push(openTagInstance); } } if (vetoed == false && tagInstance is ICloseTagInstance) { //If we find a close tag without any open tag, OR //if the current innermost tag (top of the stack) is not the open tag for this close tag vetoed = context.OpenTagStack.Any() == false || context.OpenTagStack.Peek().ParentDefinition != tagInstance.ParentDefinition; if (vetoed == false) { IOpenTagInstance openTagInstance = context.OpenTagStack.Pop(); openTagInstance.CloseTag = (ICloseTagInstance)tagInstance; } } if (vetoed) { context.Tags.RemoveAt(i); i--; } } //Close any tags that were left open while (context.OpenTagStack.Any()) { IOpenTagInstance openTagInstance = context.OpenTagStack.Peek(); string tagText; ICloseTagInstance closeTag = openTagInstance.ParentDefinition.MakeCloseTagFor(openTagInstance, context.InputString.Length, out tagText); if (closeTag.CheckIfValidClose(context)) { context.InputString.Append(tagText); context.Tags.Add(closeTag); openTagInstance.CloseTag = closeTag; } else { context.Tags.Remove(openTagInstance); } context.OpenTagStack.Pop(); } }