Example #1
0
 public static string FormatContentAsHTML(string content)
 {
     HtmlFormatter formatter = new HtmlFormatter();
     StringWriter writer = new StringWriter();
     HtmlFormatterOptions options = new HtmlFormatterOptions(' ',0,80,HtmlFormatterCase.LowerCase,HtmlFormatterCase.LowerCase,false);
     formatter.Format( content, writer, options );
     return writer.GetStringBuilder().ToString();
 }
Example #2
0
 public static string FormatContentAsXHTML(string content, string rootTag)
 {
     // first format
     HtmlFormatter formatter = new HtmlFormatter();
     StringWriter writer = new StringWriter();
     HtmlFormatterOptions options = new HtmlFormatterOptions(' ',0,80,true);
     string contentRoot = "<" + rootTag + " xmlns=\"http://www.w3.org/1999/xhtml\">"+content+"</" + rootTag + ">";
     formatter.Format( contentRoot, writer, options );
     return writer.GetStringBuilder().ToString();
 }
Example #3
0
        public void Format(string input, TextWriter output, HtmlFormatterOptions options)
        {
            // Determine if we are outputting xhtml
            bool makeXhtml = options.MakeXhtml;

            // Save the max line length
            int maxLineLength = options.MaxLineLength;

            // Make the indent string
            string indentString = new String(options.IndentChar, options.IndentSize);

            char[] chars = input.ToCharArray();
            Stack tagStack = new Stack();
            Stack writerStack = new Stack();

            // The previous begin or end tag that was seen
            FormatInfo previousTag = null;

            // The current begin or end tag that was seen
            FormatInfo currentTag = null;

            // The text between previousTag and currentTag
            string text = String.Empty;

            // True if we've seen whitespace at the end of the last text outputted
            bool sawWhiteSpace = false;

            // True if the last tag seen was self-terminated with a '/>'
            bool sawSelfTerminatingTag = false;

            // True if we saw a tag that we decided not to render
            bool ignoredLastTag = false;

            // Put the initial writer on the stack
            HtmlWriter writer = new HtmlWriter(output, indentString, maxLineLength);
            writerStack.Push(writer);

            Token t = HtmlTokenizer.GetFirstToken(chars);
            Token lastToken = t;

            while (t != null)
            {
                writer = (HtmlWriter)writerStack.Peek();
                switch (t.Type)
                {
                    case Token.AttrName:
                        if (makeXhtml)
                        {
                            string attrName = String.Empty;
                            if (!previousTag.tagInfo.IsXml)
                            {
                                // Need to lowercase the HTML attribute names for XHTML
                                attrName = t.Text.ToLower();
                            }
                            else
                            {
                                attrName = t.Text;
                            }
                            writer.Write(attrName);

                            // If we are trying to be compliant XHTML, don't allow attribute minimization
                            Token nextToken = HtmlTokenizer.GetNextToken(t);
                            if (nextToken.Type != Token.EqualsChar)
                            {
                                writer.Write("=\"" + attrName + "\"");
                            }
                        }
                        else
                        {
                            // Convert the case of the attribute if the tag isn't xml
                            if (!previousTag.tagInfo.IsXml)
                            {
                                if (options.AttributeCasing == HtmlFormatterCase.UpperCase)
                                {
                                    writer.Write(t.Text.ToUpper());
                                }
                                else if (options.AttributeCasing == HtmlFormatterCase.LowerCase)
                                {
                                    writer.Write(t.Text.ToLower());
                                }
                                else
                                {
                                    writer.Write(t.Text);
                                }
                            }
                            else
                            {
                                writer.Write(t.Text);
                            }
                        }
                        break;
                    case Token.AttrVal:
                        if (makeXhtml && (lastToken.Type != Token.DoubleQuote) && (lastToken.Type != Token.SingleQuote))
                        {
                            // If the attribute value isn't quoted, double quote it, replacing the inner double quotes
                            writer.Write('\"');
                            writer.Write(t.Text.Replace("\"", "&quot;"));
                            writer.Write('\"');
                        }
                        else
                        {
                            writer.Write(t.Text);
                        }
                        break;
                    case Token.CloseBracket:
                        if (makeXhtml)
                        {
                            if (ignoredLastTag)
                            {
                                // Don't render the close bracket if we ignored the last tag
                                ignoredLastTag = false;
                            }
                            else
                            {
                                if (sawSelfTerminatingTag && (!previousTag.tagInfo.IsComment))
                                {
                                    // If we saw a self terminating tag, that doesn't have the forward slash, put it in (except for comments)
                                    writer.Write(" />");
                                }
                                else
                                {
                                    // If we are just closing a normal tag, just put in a normal close bracket
                                    writer.Write('>');
                                }
                            }
                        }
                        else
                        {
                            // If there's no XHTML to be made, just put in a normal close bracket
                            writer.Write('>');
                        }
                        break;
                    case Token.DoubleQuote:
                        writer.Write('\"');
                        break;
                    case Token.Empty:
                        break;
                    case Token.EqualsChar:
                        writer.Write('=');
                        break;
                    case Token.Error:
                        if (lastToken.Type == Token.OpenBracket)
                        {
                            // Since we aren't outputting open brackets right away, we might have to output one now
                            writer.Write('<');
                        }
                        writer.Write(t.Text);
                        break;
                    case Token.ForwardSlash:
                    case Token.OpenBracket:
                        // Just push these symbols on the stack for now... output them when we write the tag
                        break;
                    case Token.SelfTerminating:
                        previousTag.isEndTag = true;
                        if (!previousTag.tagInfo.NoEndTag)
                        {
                            // If the tag that is self-terminating is normally not a self-closed tag
                            // then we've placed an entry on the stack for it.  Since it's self terminating, we now need
                            // to pop that item off of the tag stack
                            tagStack.Pop();

                            // If it was a self-closed Xml tag, then we also need to clean up the writerStack
                            if (previousTag.tagInfo.IsXml)
                            {
                                HtmlWriter oldWriter = (HtmlWriter)writerStack.Pop();
                                writer = (HtmlWriter)writerStack.Peek();

                                // Since a self-closed xml tag can't have any text content, we can just write out the formatted contents
                                writer.Write(oldWriter.Content);
                            }
                        }
                        if ((lastToken.Type == Token.Whitespace) && (lastToken.Text.Length > 0))
                        {
                            writer.Write("/>");
                        }
                        else
                        {
                            writer.Write(" />");
                        }
                        break;
                    case Token.SingleQuote:
                        writer.Write('\'');
                        break;
                    case Token.XmlDirective:
                        writer.WriteLineIfNotOnNewLine();
                        writer.Write('<');
                        writer.Write(t.Text);
                        writer.Write('>');
                        writer.WriteLineIfNotOnNewLine();
                        ignoredLastTag = true;
                        break;
                    case Token.TagName:
                    case Token.Comment:
                    case Token.InlineServerScript:
                        string tagName;

                        // Reset the self terminating tag flag
                        sawSelfTerminatingTag = false;

                        // Create or get the proper tagInfo, depending on the type of token
                        TagInfo info;
                        if (t.Type == Token.Comment)
                        {
                            // Handle comment tags
                            tagName = t.Text;
                            info = new TagInfo(t.Text, commentTag);
                        }
                        else if (t.Type == Token.InlineServerScript)
                        {
                            // Handle server-side script tags
                            string script = t.Text.Trim();
                            script = script.Substring(1);
                            tagName = script;
                            if (script.StartsWith("%@"))
                            {
                                // Directives are block tags
                                info = new TagInfo(script, directiveTag);
                            }
                            else
                            {
                                // Other server side script tags aren't
                                info = new TagInfo(script, otherServerSideScriptTag);
                            }
                        }
                        else
                        {
                            // Otherwise, this is a normal tag, and try to get a TagInfo for it
                            tagName = t.Text;
                            info = tagTable[tagName] as TagInfo;
                            if (info == null)
                            {
                                // if we couldn't find one, create a copy of the unknownTag with a new tagname
                                if (tagName.IndexOf(':') > -1)
                                {
                                    // If it is a prefixed tag, it's probably unknown XML
                                    info = new TagInfo(tagName, unknownXmlTag);
                                }
                                else if (writer is XmlWriter)
                                {
                                    info = new TagInfo(tagName, nestedXmlTag);
                                }
                                else
                                {
                                    // If it is a not prefixed, it's probably an unknown HTML tag
                                    info = new TagInfo(tagName, unknownHtmlTag);
                                }
                            }
                            else
                            {
                                // If it's not an unknown tag, converting to the desired case (and leave as is for PreserveCase)
                                if ((options.ElementCasing == HtmlFormatterCase.LowerCase) || makeXhtml)
                                {
                                    tagName = info.TagName;
                                }
                                else if (options.ElementCasing == HtmlFormatterCase.UpperCase)
                                {
                                    tagName = info.TagName.ToUpper();
                                }
                            }
                        }

                        if (previousTag == null)
                        {
                            // Special case for the first tag seen
                            previousTag = new FormatInfo(info, false);
                            // Since this is the first tag, set it's indent to 0
                            previousTag.indent = 0;

                            // Push it on the stack
                            tagStack.Push(previousTag);
                            // And output the preceeding text
                            writer.Write(text);

                            if (info.IsXml)
                            {
                                // When we encounter an xml block, create a new writer to contain the inner content of the xml
                                HtmlWriter newWriter = new XmlWriter(writer.Indent, info.TagName, indentString, maxLineLength);
                                writerStack.Push(newWriter);
                                writer = newWriter;
                            }

                            if (lastToken.Type == Token.ForwardSlash)
                            {
                                // If this is an end tag, output the proper prefix
                                writer.Write("</");
                            }
                            else
                            {
                                writer.Write('<');
                            }
                            // Write the name
                            writer.Write(tagName);
                            // Indicate that we've written out the last text block
                            text = String.Empty;
                        }
                        else
                        {
                            // Put the new tag in the next spot
                            currentTag = new FormatInfo(info, (lastToken.Type == Token.ForwardSlash));

                            WhiteSpaceType whiteSpaceType;
                            if (previousTag.isEndTag)
                            {
                                // If the previous tag is an end tag, we need to check the following whitespace
                                whiteSpaceType = previousTag.tagInfo.FollowingWhiteSpaceType;
                            }
                            else
                            {
                                // otherwise check the initial inner whitespace
                                whiteSpaceType = previousTag.tagInfo.InnerWhiteSpaceType;
                            }

                            // Flag that indicates if the previous tag (before the text) is an inline tag
                            bool inline = previousTag.tagInfo.IsInline;
                            bool emptyXml = false;
                            bool firstOrLastUnknownXmlText = false;

                            if (writer is XmlWriter)
                            {
                                // if we're in an xml block
                                XmlWriter xmlWriter = (XmlWriter)writer;

                                if (xmlWriter.IsUnknownXml)
                                {
                                    // Special case for unknown XML tags
                                    // Determine if this is the first or last xml text in an unknown xml tag, so we know to preserve the text content here
                                    firstOrLastUnknownXmlText = (((previousTag.isBeginTag) && (previousTag.tagInfo.TagName.ToLower() == xmlWriter.TagName.ToLower())) ||
                                        ((currentTag.isEndTag) && (currentTag.tagInfo.TagName.ToLower() == xmlWriter.TagName.ToLower()))) &&
                                        (!FormattedTextWriter.IsWhiteSpace(text));
                                }

                                if (previousTag.isBeginTag)
                                {
                                    if (FormattedTextWriter.IsWhiteSpace(text))
                                    {
                                        if ((xmlWriter.IsUnknownXml) && (currentTag.isEndTag) &&
                                            (previousTag.tagInfo.TagName.ToLower() == currentTag.tagInfo.TagName.ToLower()))
                                        {
                                            // Special case for unknown XML tags:
                                            // If the previous tag is an open tag and the next tag is the corresponding close tag and the text is only whitespace, also
                                            // treat the tag as inline, so the begin and end tag appear on the same line
                                            inline = true;
                                            emptyXml = true;
                                            // Empty the text since we want the open and close tag to be touching
                                            text = "";
                                        }
                                    }
                                    else
                                    {
                                        if (!xmlWriter.IsUnknownXml)
                                        {
                                            // If there is non-whitespace text and we're in a normal Xml block, then remember that there was text
                                            xmlWriter.ContainsText = true;
                                        }
                                    }
                                }
                            }

                            // Flag that indicates if we want to preserve whitespace in the front of the text
                            bool frontWhitespace = true;

                            if ((previousTag.isBeginTag) && (previousTag.tagInfo.PreserveContent))
                            {
                                // If the previous tag is a begin tag and we're preserving the content as-is, just write out the text
                                writer.Write(text);
                            }
                            else
                            {
                                if (whiteSpaceType == WhiteSpaceType.NotSignificant)
                                {
                                    // If the whitespace is not significant in this location
                                    if (!inline && !firstOrLastUnknownXmlText)
                                    {
                                        // If the previous tag is not an inline tag, write out a new line
                                        writer.WriteLineIfNotOnNewLine();
                                        // Since we've written out a newline, we no longer need to preserve front whitespace
                                        frontWhitespace = false;
                                    }
                                }
                                else if (whiteSpaceType == WhiteSpaceType.Significant)
                                {
                                    // If the whitespace in this location is significant
                                    if (FormattedTextWriter.HasFrontWhiteSpace(text))
                                    {
                                        // If there is whitespace in the front, that means we can insert more whitespace without
                                        // changing rendering behavior
                                        if (!inline && !firstOrLastUnknownXmlText)
                                        {
                                            // Only insert a new line if the tag isn't inline
                                            writer.WriteLineIfNotOnNewLine();
                                            frontWhitespace = false;
                                        }
                                    }
                                }
                                else if (whiteSpaceType == WhiteSpaceType.CarryThrough)
                                {
                                    // If the whitespace in this location is carry through (meaning whitespace at the end of the previous
                                    // text block eats up any whitespace in this location
                                    if ((sawWhiteSpace) || (FormattedTextWriter.HasFrontWhiteSpace(text)))
                                    {
                                        // If the last text block ended in whitspace or if there is already whitespace in this location
                                        // we can add a new line
                                        if (!inline && !firstOrLastUnknownXmlText)
                                        {
                                            // Only add it if the previous tag isn't inline
                                            writer.WriteLineIfNotOnNewLine();
                                            frontWhitespace = false;
                                        }
                                    }
                                }

                                if (previousTag.isBeginTag)
                                {
                                    // If the previous tag is a begin tag
                                    if (!previousTag.tagInfo.NoIndent && !inline)
                                    {
                                        // Indent if desired
                                        writer.Indent++;
                                    }
                                }

                                // Special case for unknown XML tags:
                                if (firstOrLastUnknownXmlText)
                                {
                                    writer.Write(text);
                                }
                                else
                                {
                                    writer.WriteLiteral(text, frontWhitespace);
                                }
                            }

                            if (currentTag.isEndTag)
                            {
                                // If the currentTag is an end tag
                                if (!currentTag.tagInfo.NoEndTag)
                                {
                                    // Figure out where the corresponding begin tag is
                                    ArrayList popped = new ArrayList();
                                    FormatInfo formatInfo = null;

                                    bool foundOpenTag = false;

                                    bool allowPartial = false;
                                    if ((currentTag.tagInfo.Flags & FormattingFlags.AllowPartialTags) != 0)
                                    {
                                        // Once we've exited a tag that allows partial tags, clear the flag
                                        allowPartial = true;
                                    }

                                    // Start popping off the tag stack if there are tags on the stack
                                    if (tagStack.Count > 0)
                                    {
                                        // Keep popping until we find the right tag, remember what we've popped off
                                        formatInfo = (FormatInfo)tagStack.Pop();
                                        popped.Add(formatInfo);
                                        while ((tagStack.Count > 0) && (formatInfo.tagInfo.TagName.ToLower() != currentTag.tagInfo.TagName.ToLower()))
                                        {
                                            if ((formatInfo.tagInfo.Flags & FormattingFlags.AllowPartialTags) != 0)
                                            {
                                                // Special case for tags that allow partial tags inside of them.
                                                allowPartial = true;
                                                break;
                                            }
                                            formatInfo = (FormatInfo)tagStack.Pop();
                                            popped.Add(formatInfo);
                                        }

                                        if (formatInfo.tagInfo.TagName.ToLower() != currentTag.tagInfo.TagName.ToLower())
                                        {
                                            // If we didn't find the corresponding open tag, push everything back on
                                            for (int i = popped.Count - 1; i >= 0; i--)
                                            {
                                                tagStack.Push(popped[i]);
                                            }
                                        }
                                        else
                                        {
                                            foundOpenTag = true;
                                            for (int i = 0; i < popped.Count - 1; i++)
                                            {
                                                FormatInfo fInfo = (FormatInfo)popped[i];
                                                if (fInfo.tagInfo.IsXml)
                                                {
                                                    // If we have an xml tag that was unclosed, we need to clean up the xml stack
                                                    if (writerStack.Count > 1)
                                                    {
                                                        HtmlWriter oldWriter = (HtmlWriter)writerStack.Pop();
                                                        writer = (HtmlWriter)writerStack.Peek();
                                                        // Write out the contents of the old writer
                                                        writer.Write(oldWriter.Content);
                                                    }
                                                }

                                                if (!fInfo.tagInfo.NoEndTag)
                                                {
                                                    writer.WriteLineIfNotOnNewLine();
                                                    writer.Indent = fInfo.indent;
                                                    if ((makeXhtml) && (!allowPartial))
                                                    {
                                                        // If we're trying to be XHTML compliant, close unclosed child tags
                                                        // Don't close if we are under a tag that allows partial tags
                                                        writer.Write("</"+fInfo.tagInfo.TagName+">");
                                                    }
                                                }
                                            }

                                            // Set the indent to the indent of the corresponding open tag
                                            writer.Indent = formatInfo.indent;
                                        }
                                    }
                                    if (foundOpenTag || allowPartial)
                                    {
                                        // Only write out the close tag if there was a corresponding open tag or we are under
                                        // a tag that allows partial tags
                                        if ((!emptyXml) &&
                                            (!firstOrLastUnknownXmlText) &&
                                            (!currentTag.tagInfo.IsInline) &&
                                            (!currentTag.tagInfo.PreserveContent) &&
                                            (  FormattedTextWriter.IsWhiteSpace(text) ||
                                            FormattedTextWriter.HasBackWhiteSpace(text) ||
                                            (currentTag.tagInfo.FollowingWhiteSpaceType == WhiteSpaceType.NotSignificant)
                                            ) &&
                                            (  !(currentTag.tagInfo is TDTagInfo) ||
                                            FormattedTextWriter.HasBackWhiteSpace(text)
                                            )
                                            )
                                        {
                                            // Insert a newline before the next tag, if allowed
                                            writer.WriteLineIfNotOnNewLine();
                                        }
                                        // Write out the end tag prefix
                                        writer.Write("</");
                                        // Finally, write out the tag name
                                        writer.Write(tagName);
                                    }
                                    else
                                    {
                                        ignoredLastTag = true;
                                    }

                                    if (currentTag.tagInfo.IsXml)
                                    {
                                        // If we have an xml tag that was unclosed, we need to clean up the xml stack
                                        if (writerStack.Count > 1)
                                        {
                                            HtmlWriter oldWriter = (HtmlWriter)writerStack.Pop();
                                            writer = (HtmlWriter)writerStack.Peek();
                                            // Write out the contents of the old writer
                                            writer.Write(oldWriter.Content);
                                        }
                                    }
                                }
                                else
                                {
                                    ignoredLastTag = true;
                                }
                            }
                            else
                            {
                                // If the currentTag is a begin tag
                                bool done = false;
                                // Close implicitClosure tags
                                while (!done && (tagStack.Count > 0))
                                {
                                    // Peek at the top of the stack to see the last unclosed tag
                                    FormatInfo fInfo = (FormatInfo)tagStack.Peek();
                                    // If the currentTag can't be a child of that tag, then we need to close that tag
                                    done = fInfo.tagInfo.CanContainTag(currentTag.tagInfo);
                                    if (!done)
                                    {
                                        // Pop it off and write a close tag for it
                                        // REVIEW: Will XML tags always be able to contained in any tag?  If not we should be cleaning up the writerStack as well...
                                        tagStack.Pop();
                                        writer.Indent = fInfo.indent;
                                        // If we're trying to be XHTML compliant, write in the end tags
                                        if (makeXhtml)
                                        {
                                            if (!fInfo.tagInfo.IsInline)
                                            {
                                                // Only insert a newline if we are allowed to
                                                writer.WriteLineIfNotOnNewLine();
                                            }
                                            writer.Write("</"+fInfo.tagInfo.TagName+">");
                                        }
                                    }
                                }

                                // Remember the indent so we can properly indent the corresponding close tag for this open tag
                                currentTag.indent = writer.Indent;

                                if ((!firstOrLastUnknownXmlText) &&
                                    (!currentTag.tagInfo.IsInline) &&
                                    (!currentTag.tagInfo.PreserveContent) &&
                                    ( (FormattedTextWriter.IsWhiteSpace(text) || FormattedTextWriter.HasBackWhiteSpace(text)) ||
                                    (  (text.Length == 0) &&
                                    (  ((previousTag.isBeginTag) && (previousTag.tagInfo.InnerWhiteSpaceType == WhiteSpaceType.NotSignificant)) ||
                                    ((previousTag.isEndTag) && (previousTag.tagInfo.FollowingWhiteSpaceType == WhiteSpaceType.NotSignificant))
                                    )
                                    )
                                    )
                                    )
                                {
                                    // Insert a newline before the currentTag if we are allowed to
                                    writer.WriteLineIfNotOnNewLine();
                                }

                                if (!currentTag.tagInfo.NoEndTag)
                                {
                                    // Only push tags with close tags onto the stack
                                    tagStack.Push(currentTag);
                                }
                                else
                                {
                                    // If this tag doesn't have a close tag, remember that it is self terminating
                                    sawSelfTerminatingTag = true;
                                }

                                if (currentTag.tagInfo.IsXml)
                                {
                                    // When we encounter an xml block, create a new writer to contain the inner content of the xml
                                    HtmlWriter newWriter = new XmlWriter(writer.Indent, currentTag.tagInfo.TagName, indentString, maxLineLength);
                                    writerStack.Push(newWriter);
                                    writer = newWriter;
                                }

                                writer.Write('<');
                                // Finally, write out the tag name
                                writer.Write(tagName);
                            }

                            // Remember if the text ended in whitespace
                            sawWhiteSpace = FormattedTextWriter.HasBackWhiteSpace(text);

                            // Clear out the text, since we have already outputted it
                            text = String.Empty;

                            previousTag = currentTag;
                        }
                        break;
                    case Token.ServerScriptBlock:
                    case Token.ClientScriptBlock:
                    case Token.Style:
                    case Token.TextToken:
                        // Remember all these types of tokens as text so we can output them between the tags
                        if (makeXhtml)
                        {
                            // UNDONE: Need to implement this in the tokenizer, etc...
                            text += t.Text.Replace("&nbsp;", "&#160;");
                        }
                        else
                        {
                            text += t.Text;
                        }
                        break;
                    case Token.Whitespace:
                        if (t.Text.Length > 0)
                        {
                            writer.Write(' ');
                        }
                        break;
                    default:
                        Debug.Fail("Invalid token type!");
                        break;
                }
                // Remember what the last token was
                lastToken = t;

                // Get the next token
                t = HtmlTokenizer.GetNextToken(t);
            }

            if (text.Length > 0)
            {
                // Write out the last text if there is any
                writer.Write(text);
            }

            while (writerStack.Count > 1)
            {
                // If we haven't cleared out the writer stack, do it
                HtmlWriter oldWriter = (HtmlWriter)writerStack.Pop();
                writer = (HtmlWriter)writerStack.Peek();
                writer.Write(oldWriter.Content);
            }

            // Flush the writer original
            writer.Flush();
        }