Life, the Universe, and Software EngineeringLife, the Universe, and Software Engineering

Displaying SharePoint Enhanced Rich Text Formatted (RTF) Text fields in Silverlight

Main Image

Silverlight can be used to enhance the user experience for SharePoint users. Most data stored in SharePoint can be displayed directly by existing Silverlight controls and/or the controls available in the Silverlight Toolkit available on Codeplex.  The exception to this is the displaying of rich text formatted text field data.  RTF formatted text is stored in a simplified HTML format in SharePoint there current is no easy way to correctly display this type of text data in Silverlight… until now.

The primary mechanism for displaying text data in Silverlight is the TextBlock control.  The TextBlock control can be used for displaying text runs of changing fonts and decorations, such as bolding and underlines.  This means we can used the TextBlock control to represent “most” of the text data in a SharePoint RTF field, however, the simplified HTML used to represent RTF formated text in SharePoint can also contain hyper-links and images.  To correct represent all aspects of SharePoint RTF data we actual needed to create several different Silverlight controls.

To correctly represent RTF text data we will need to parse the HTML mark-up from in each RTF text field and translate each HTML mark-up tag into an appropriate Silverlight control.  HTML data that conforms to HTML standards also conforms to generic XML standards, so you would assume that RTF text field data could also be parsed using the built-in XML document parser.  Unfortunately the HTML fragments used by SharePoint do not conform to the latest HTML standard.  RTF text data contains many tags that are not closed.  For example,  a line break tag that conforms to the latest HTML standard looks like this “<br/>” , note the closing slash, which as pre the XML standard indicates that the br tag is closed, with no contained text or child tags.  The SharePoint RTF editor uses the HTML 1.0 version of this tag “<br>”  with no closing slash.  In fact, if you attempt to use the HTML 4.0 compliant tag “<br/>” the SharePoint text box editor will helpfully convert it back into the HTML 1.0 version “<br>”.  For HTML browsers this works just fine as they are able to easily read both HTML 1.0 and HTML 4.0 tags, however, the XML parser requires that all tags fully conform to the XML standard, which requires that all open tags have a corresponding close tag, or that the opening tag contain a closing slash like our “<br/>” example.  This of course, means that the built-in XML parse cannot be used to parse SharePoint RTF text data.

Previous attempts at creating Silverlight controls that can handle HTML data have relied on the browsers ability to parse.  This approach does work, however, in practise, the mechanism to do this appears to be very slow. 

The approach taken here is to create an HTML parser to parse the simplified HTML used by the RTF text fields.  The HTML parser will be constructed in three layers as follows:

 

Architecture

 

HtmlTextBlock Parser Layer #1

The first step in parsing text is to create the ability to read the text one character at a time, provide the ability to “look ahead” one or more character, and keep track of where the parser is in the text stream, in terms of line number and column number, in case an error or warning needs to be reported to the user.  The StreamReader class is a convenient standard class for access text data one character at a time.

In Siliverlight the following code appears to be the best way to initialize the stream reader:

privateStreamReader m_streamReader;MemoryStream stream = newMemoryStream(UTF8Encoding.UTF8.GetBytes(text),false);

m_streamReader = newStreamReader(stream, UTF8Encoding.UTF8);

 

 Once the steam reader is initialized I like to wrap this layer of a text parser in a simple set of methods:

 

char nextChar() a method to return one character a time and to track the character position it terms of line number and column number.  This method also has the ability to indicated when the end of stream has been reached by returning the “character”  EOF_CHAR or ‘\0’.

void pushChar(char c)

a method to push one character back into the character stream to provide the ability for the next level of the parser to "look ahead" one or more characters.

The code for these two functions can be seen below:

        #region Parser Layer #1
        private int m_lineNumber;
        private int m_columnNumber;
        private Stack m_charStack = new Stack();
        private char EOF_CHAR = '\0';
        /// 
        /// return one character a time and to track the character position it terms of line number and column
        /// 
        /// 
        private char nextChar()
        {
            char c;
            if (m_charStack.Count &gt; 0)
            {
                c = m_charStack.Pop();
            }
            else if (m_streamReader.EndOfStream)
            {
                return EOF_CHAR;
            }
            else
            {
                c = (char)m_streamReader.Read();
            }
            if (c == '\n')
            {
                m_lineNumber++;
                m_previousLineLength = m_columnNumber;
                m_columnNumber = 0;
            }
            else
            {
                m_columnNumber++;
            }
            return c;
        }
        private int m_previousLineLength = 0;
        /// 
        /// Push one charcater back into the character stream, and adjust current character position accordingly
        /// 
        /// <param name="c" />
        private void pushChar(char c)
        {
            if (c == '\n')
            {
                m_lineNumber--;
                m_columnNumber = m_previousLineLength;
                m_previousLineLength = 80; //Supports stepping back over one line only
            }
            else
            {
                m_columnNumber--;
            }
            m_charStack.Push(c);
        }

        #endregion

Note one of the tricks used in the pushChar() method is the use of the private variable m_previousLineLength. This allows pushChar to correctly restore that correct column across one carriage return.  The method could be made more generic to allow more that one carriage return to be pushed back into the character stream, but this is not really neccsary for the type of parsing we are going to do here.

HtmlTextBlock Parser layer #2

 The next layer of our parser will be responsible for parsing the HTML tags and HTML entities such as “&amp;” etc.  I have wrapped this layer in three methods:

 

publicHtmlNodeNextNode() - read and remove the next node from the node stream.

publicHtmlNode PeekNode() - read the next node in the node stream, but do not remove it.

publicvoid PushNode(HtmlNode node)- Push an HtmlNode back into the node stream (in last-in-first-out order).

 

The HtmlNode class has just a few important properties.  The UML class diagram for the HtmlNode class is as follows:

HtmlNode

Type represents the type node returned, possible values are Element, EndElement, Text, Whitespace, and EOF. 

LocalName is the local name of the tag (the tag name, not including the namespace).

Prefix is the UML prefix associated with the name.  The prefix represents the short hand name of the namespace.

Value is the text value of the node, if applicable.

NormalizedValue the XML normalized version of the text value of the node, if applicable. (All whitespace is reduced to a single space).

The code for this layer can be seen here:

 

        #region Parser Layer #2
        private Stack m_nodeStack = new Stack();

        /// 
        /// Push an HtmlNode back into the node stream (in last-in-first-out order).
        /// 
        /// <param name="node" />
        public void PushNode(HtmlNode node)
        {
            m_nodeStack.Push(node);
        }
        /// 
        /// Return the next HtmlNode but do not remove it from the node stream
        /// 
        /// 
        public HtmlNode PeekNode()
        {
            HtmlNode node;
            if (m_nodeStack.Count &gt; 0)
            {
                node = m_nodeStack.Peek();
            }
            else
            {
                node = parseNode();
                m_nodeStack.Push(node);
            }
            return node;
        }
        /// 
        /// Return the next HtmlHode
        /// 
        /// 
        public HtmlNode NextNode()
        {
            if (m_nodeStack.Count &gt; 0)
            {
                return m_nodeStack.Pop();
            }
            else
            {
                return parseNode();
            }
        }
        private enum ParseModes
        {
            Whitespace = 0x01,
            Text = 0x02,
            Element = 0x04,
            Name = 0x08,
            Value = 0x10,
            QuotedValue = 0x30,

        }
        private ParseModes m_parseMode;
        private string parseEntity()
        {
            StringBuilder entity = new StringBuilder("&amp;");
            char c;
            while ((c = nextChar()) != EOF_CHAR)
            {
                if (char.IsLetterOrDigit(c) || c == '-' || c == '_')
                {
                    entity.Append(c);
                }
                else if (c == ';')
                {
                    //end of entity
                    entity.Append(c);
                    return entity.ToString();
                }
                else
                {

                    //Not an entity

                    break; 
                }
            }
            //not an entity

            for (int i = entity.Length - 1; i &gt; 0; i--)
            {
                pushChar(entity[i]);
            }
            return "&amp;";
        }
        private HtmlNode m_currentElement; //Holds turn current element node during the processing of the entire element, and it's attributes.
        private HtmlNode parseNode()
        {
            char openQuoteChar = 'x';
            StringBuilder value = new StringBuilder();
            StringBuilder localName = new StringBuilder();
            StringBuilder prefix = new StringBuilder();
            HtmlNodeType nodeType = HtmlNodeType.Unknown;
            char c;
            while ((c = nextChar()) != EOF_CHAR)
            {
                switch (c)
                {
                    case '&lt;':
                        switch (m_parseMode)
                        {
                            case ParseModes.Element:
                            case ParseModes.Name:
                            case ParseModes.Value:
                            case ParseModes.QuotedValue:
                                pushChar(c);
                                handleUnexpectedCharacter(c);
                                return new HtmlNode(nodeType, prefix.ToString(), localName.ToString(), value.ToString());
                            case ParseModes.Text:
                                if (value.Length &gt; 0)
                                {
                                    pushChar(c);
                                    return new HtmlNode(HtmlNodeType.Text, value.ToString());
                                }
                                m_parseMode = ParseModes.Element;
                                nodeType = HtmlNodeType.Element;
                                continue;
                            case ParseModes.Whitespace:
                                if (value.Length &gt; 0)
                                {
                                    pushChar(c);
                                    return new HtmlNode(HtmlNodeType.Whitespace, value.ToString());
                                }
                                m_parseMode = ParseModes.Element;
                                nodeType = HtmlNodeType.Element;
                                continue;
                        }
                        handleUnexpectedCharacter(c);
                        continue;
                    case '&gt;':
                        switch (m_parseMode)
                        {
                            case ParseModes.Element:
                                m_parseMode = ParseModes.Whitespace;
                                m_currentElement = null;
                                if (nodeType != HtmlNodeType.Unknown)
                                {
                                    return new HtmlNode(nodeType, prefix.ToString(), localName.ToString(), value.ToString());
                                }
                                continue;
                            case ParseModes.Name:
                                m_parseMode = ParseModes.Whitespace;
                                m_currentElement = null;
                                if (nodeType == HtmlNodeType.Unknown)
                                {
                                    return new HtmlNode(HtmlNodeType.Attribute, prefix.ToString(), localName.ToString());
                                }
                                else
                                {
                                    return new HtmlNode(nodeType, prefix.ToString(), localName.ToString());
                                }
                            case ParseModes.Value:
                                m_parseMode = ParseModes.Whitespace;
                                m_currentElement = null;
                                return new HtmlNode(HtmlNodeType.Attribute, prefix.ToString(), localName.ToString(), value.ToString());
                            case ParseModes.QuotedValue:
                                value.Append(c);
                                continue;
                            case ParseModes.Text:
                                if (value.Length &gt; 0)
                                {
                                    pushChar(c);
                                    return new HtmlNode(HtmlNodeType.Text, value.ToString());
                                }
                                m_parseMode = ParseModes.Element;
                                nodeType = HtmlNodeType.Element;
                                continue;
                            case ParseModes.Whitespace:
                                if (value.Length &gt; 0)
                                {
                                    pushChar(c);
                                    return new HtmlNode(HtmlNodeType.Whitespace, value.ToString());
                                }
                                m_parseMode = ParseModes.Text;
                                nodeType = HtmlNodeType.Text;
                                value.Append(c);
                                continue;
                        }
                        handleUnexpectedCharacter(c);
                        continue;
                    case '/':
                        switch (m_parseMode)
                        {
                            case ParseModes.Element:
                                if (prefix.Length == 0 || localName.Length == 0)
                                {
                                    if (nodeType == HtmlNodeType.Element)
                                    {
                                        nodeType = HtmlNodeType.EndElement;
                                        continue;
                                    }
                                }
                                else
                                {
                                    char next = nextChar();
                                    if (next == '&gt;')
                                    {
                                        m_parseMode = ParseModes.Whitespace;
                                        if (m_currentElement == null)
                                        {
                                            handleError("Implied EndElement node with no corresponding Element node", null);
                                        }
                                        else
                                        {
                                            //Create the implied EndElement node
                                            HtmlNode closeTag = new HtmlNode(HtmlNodeType.EndElement, m_currentElement.Prefix, m_currentElement.LocalName);
                                            if (nodeType == HtmlNodeType.Unknown)
                                            {

                                                return closeTag;
                                            }
                                            else
                                            {
                                                PushNode(closeTag);
                                                return new HtmlNode(nodeType, prefix.ToString(), localName.ToString(), value.ToString());
                                            }
                                        }
                                    }
                                }
                                handleUnexpectedCharacter(c);
                                continue;
                            case ParseModes.Name:
                                pushChar(c);
                                m_parseMode = ParseModes.Element;
                                return new HtmlNode(nodeType, prefix.ToString(), localName.ToString());
                            case ParseModes.QuotedValue:
                                break;
                            case ParseModes.Value:
                                pushChar(c);
                                m_parseMode = ParseModes.Element;
                                return new HtmlNode(nodeType, prefix.ToString(), localName.ToString(), value.ToString());
                        }
                        break;
                    case '"':

                    case '\'':
                        switch (m_parseMode)
                        {
                            case ParseModes.Text:
                            case ParseModes.Whitespace:
                                break;
                            case ParseModes.Value:
                                if (value.Length == 0)
                                {
                                    openQuoteChar = c;
                                    m_parseMode = ParseModes.QuotedValue;
                                    continue;
                                }
                                else
                                {
                                    m_parseMode = ParseModes.Element;
                                    pushChar(c);
                                    return new HtmlNode(HtmlNodeType.Attribute, prefix.ToString(), localName.ToString(), value.ToString());
                                }
                            case ParseModes.QuotedValue:
                                if (c == openQuoteChar)
                                {
                                    m_parseMode = ParseModes.Element;
                                    return new HtmlNode(HtmlNodeType.Attribute, prefix.ToString(), localName.ToString(), value.ToString());
                                }
                                else
                                {
                                    value.Append(c);
                                    continue;
                                }
                            case ParseModes.Name:
                            case ParseModes.Element:
                                handleUnexpectedCharacter(c);
                                continue;
                        }
                        break;
                    case '&amp;':
                        switch (m_parseMode)
                        {
                            case ParseModes.Whitespace:
                                if (value.Length &gt; 0)
                                {
                                    pushChar(c);
                                    return new HtmlNode(HtmlNodeType.Whitespace, value.ToString());
                                }
                                m_parseMode = ParseModes.Text;
                                value.Append(parseEntity());
                                continue;
                            case ParseModes.Text:
                            case ParseModes.Value:
                            case ParseModes.QuotedValue:
                                value.Append(parseEntity());
                                continue;
                        }
                        handleUnexpectedCharacter(c);
                        continue;
                    case '.':
                    case '-':
                        switch (m_parseMode)
                        {
                            case ParseModes.Whitespace:
                                if (value.Length &gt; 0)
                                {
                                    pushChar(c);
                                    return new HtmlNode(HtmlNodeType.Whitespace, value.ToString());
                                }
                                m_parseMode = ParseModes.Text;
                                value.Append(c);
                                continue;
                            case ParseModes.Text:
                            case ParseModes.Value:
                            case ParseModes.QuotedValue:
                                value.Append(c);
                                continue;
                        }
                        handleUnexpectedCharacter(c);
                        continue;
                    case ';':
                        break;

                    case ':':
                        switch (m_parseMode)
                        {
                            case ParseModes.Text:
                            case ParseModes.Whitespace:
                            case ParseModes.QuotedValue:
                                value.Append(c);
                                continue;
                            case ParseModes.Name:
                                if (prefix.Length &gt; 0)
                                {
                                    break;
                                }
                                else if (localName.Length &gt; 0)
                                {
                                    prefix.Append(localName.ToString());
                                    localName.Length = 0;
                                    continue;
                                }
                                break;
                        }
                        handleUnexpectedCharacter(c);
                        continue;
                    case '\t':
                    case ' ':
                    case '\r':
                    case '\n':
                        switch (m_parseMode)
                        {
                            case ParseModes.Text:
                            case ParseModes.Whitespace:
                            case ParseModes.QuotedValue:
                                value.Append(c);
                                continue;
                            case ParseModes.Value:
                                m_parseMode = ParseModes.Element;
                                if (localName.Length &gt; 0)
                                {
                                    return new HtmlNode(HtmlNodeType.Attribute, prefix.ToString(), localName.ToString(), value.ToString());
                                }
                                continue;
                            case ParseModes.Element:
                                continue;
                            case ParseModes.Name:
                                m_parseMode = ParseModes.Element;
                                if (nodeType == HtmlNodeType.Element || nodeType == HtmlNodeType.EndElement)
                                {
                                    m_currentElement = new HtmlNode(nodeType, prefix.ToString(), localName.ToString());
                                    return m_currentElement;
                                }
                                continue;
                        }
                        break;
                    case '=':
                        switch (m_parseMode)
                        {
                            case ParseModes.Name:
                                m_parseMode = ParseModes.Value;
                                continue;
                            case ParseModes.Text:
                            case ParseModes.Whitespace:
                            case ParseModes.QuotedValue:
                                break;
                            case ParseModes.Value:
                                if (value.Length &gt; 0)
                                {
                                    return new HtmlNode(HtmlNodeType.Attribute, prefix.ToString(), localName.ToString(), value.ToString());
                                }
                                m_parseMode = ParseModes.Element;
                                continue;
                            case ParseModes.Element:
                                handleUnexpectedCharacter(c);
                                continue;
                        }
                        break;
                }
                if (char.IsLetterOrDigit(c) || c == '_')
                {
                    switch (m_parseMode)
                    {
                        case ParseModes.Element:
                            localName.Append(c);
                            if (nodeType == HtmlNodeType.Unknown)
                            {
                                nodeType = HtmlNodeType.Attribute;
                            }
                            m_parseMode = ParseModes.Name;
                            break;
                        case ParseModes.Name:
                            localName.Append(c);
                            break;
                        case ParseModes.Value:
                        case ParseModes.QuotedValue:
                        case ParseModes.Text:
                            value.Append(c);
                            break;
                        case ParseModes.Whitespace:
                            if (value.Length &gt; 0)
                            {
                                pushChar(c);
                                return new HtmlNode(HtmlNodeType.Whitespace, value.ToString());
                            }
                            if (nodeType == HtmlNodeType.Unknown)
                            {
                                nodeType = HtmlNodeType.Text;
                            }
                            m_parseMode = ParseModes.Text;
                            value.Append(c);
                            break;
                    }
                }
                else
                {
                    //Some kind of sepparator

                    switch (m_parseMode)
                    {
                        case ParseModes.Element:
                            handleUnexpectedCharacter(c);
                            break;
                        case ParseModes.Name:
                            handleUnexpectedCharacter(c);
                            m_parseMode = ParseModes.Element;
                            break;
                        case ParseModes.Value:
                            pushChar(c);
                            m_parseMode = ParseModes.Element;
                            return new HtmlNode(HtmlNodeType.Attribute, prefix.ToString(), localName.ToString(), value.ToString());
                        case ParseModes.QuotedValue:
                        case ParseModes.Text:
                            value.Append(c);
                            break;
                        case ParseModes.Whitespace:
                            if (value.Length &gt; 0)
                            {
                                pushChar(c);
                                return new HtmlNode(HtmlNodeType.Whitespace, value.ToString());
                            }
                            m_parseMode = ParseModes.Text;
                            value.Append(c);
                            break;
                    }
                }
            }
            if (value.Length &gt; 0)
            {
                return new HtmlNode(nodeType, value.ToString());
            }
            else
            {
                return new HtmlNode(HtmlNodeType.EOF);
            }
        }
        #endregion

HtmlTextBlock Parser layer #3

The third layer of the parser reads the HTML node stream and converts it into a set of Silverlight controls.   Parsing HTML nodes into corresponding Silverlight controls is relatively straight forward if you don’t have to worry about HTML style such as padding, margins, font size and font decorations.   To simplify the parsing and displaying the correct style in Silverlight I decided to create a specialized HtmlStyle class to manage style and a set of custom Silverlight controls to mirror the functionality of each HTML tag.  The following UML class diagram represents the HtmlStyle class:

 

The main functionality of the HtmlStyle class is to replicate the cascading effect of HTML CSS style definitions.  So HtmlStyle instance are created in a parent-child hierarchy and as HTML tags are encountered the latest HtmlStyle is managed by pushing and popping HtmlStyle objects on and off of a stack.  Each attribute or property of the HtmlStyle class either has an assigned value or the value is retrieved from it’s parent HtmlStyle.

 The following table shows the HTML tag and the corresponding Silverlight control used to render it, as well as the base Silverlight class that it is derived from:

 

 

HTML TagRendering Controlbase classNotes
<a> HtmlAnchor HyperlinkButton  
<b> HtmlStyle n/a An HtmlStyle object is created with FontWieght set to Bold. 
<blockquote> HtmlBlockQuote HtmlDiv  
<br> HtmlLineBreak Control  
<div> HtmlDiv Panel  
<em> HtmlStyle n/a An HtmlStyle object is created with FontStyle set to Italic. 
<font> HtmlStyle n/a An HtmlStyle object is created with all appropriate style properties set.  If the style defines a background, and additional HtmlDiv element is created to allow background to show up correctly. 
<i> HtmlStyle n/a An HtmlStyle object is created with FontStyle set to Italic. 
<img> HtmlImg Canvas  
<p> HtmlAnchor HyperlinkButton  
<strong> HtmlStyle n/a An HtmlStyle object is created with FontWieght set to ExtraBold. 
<u> HtmlStyle n/a An HtmlStyle object is created with TextDecorations set to Underline. 
<ol> HtmlOrderedList HtmlList  
<ul> HtmlUnorderedList HtmlList  
<li> HtmlListItem StackPanel  
  HtmlList StackPanel This is the generic base class for HtmlOrderedList and HtmlListItem.
whitespace and text TextBlock / Run n/a All whitespace and text is represented using standard Silverlight TextBlock and Run classes.

 

The most interesting part of the code for level #3 can be seen here:

 

        private Stack m_elementStack = new Stack();
        UIElement m_current = null;
        TextBlock m_currentTextBlock = null;
        private void AddInline(HtmlStyle style, Inline value)
        {
            if (m_currentTextBlock == null)
            {
                TextBlock textBlock = new TextBlock();
                style.Set(textBlock);
                AddElement(textBlock);
            }
            m_currentTextBlock.Inlines.Add(value);
        }
        private void AddElement(UIElement element)
        {
            m_currentTextBlock = null;
            while (!(m_current is Panel || m_current is ContentControl))
            {
                PopElement();
            }
            if (m_current is Panel)
            {
                Panel panel = m_current as Panel;
                panel.Children.Add(element);
            }
            else if (m_current is ContentControl)
            {
                ContentControl contentControl = m_current as ContentControl;
                if (contentControl.Content == null)
                {
                    contentControl.Content = new HtmlDiv();
                }
                if (contentControl.Content is HtmlDiv)
                {
                    HtmlDiv htmlDiv = contentControl.Content as HtmlDiv;
                    htmlDiv.Children.Add(element);
                }
            }
            m_elementStack.Push(m_current);
            m_current = element;
            if (m_current is TextBlock)
            {
                m_currentTextBlock = m_current as TextBlock;
            }
        }
        private void PopElement()
        {
            m_current = m_elementStack.Pop();
            if (m_currentTextBlock != null)
            {
                m_current = m_elementStack.Pop();
                m_currentTextBlock = null;
            }
        }
        private void parseAllStyleAttributes(HtmlParser parser, HtmlStyle style)
        {
            HtmlNode node;
            while ((node = parser.NextNode()).Type == HtmlNodeType.Attribute)
            {
                Debug.WriteLine("Attribute = " + node);
                parseStyleAttribute(node,style);
            }
            parser.PushNode(node);
        }
        private void parseStyleAttribute(HtmlNode node, HtmlStyle style)
        {
            string attributeName = node.LocalName.ToUpper();
            switch (attributeName)
            {
                case ATTRIBUTE_ALIGN:
                    style.SetAlign(node.Value);
                    break;
                case ATTRIBUTE_VALIGN:
                    style.SetVAlign(node.Value);
                    break;
                case ATTRIBUTE_SIZE:
                    style.SetFontSize(node.Value);
                    break;
                case ATTRIBUTE_STYLE:
                    style.SetStyle(node.Value);
                    break;
                case ATTRIBUTE_COLOR:
                    style.SetColor(node.Value);
                    break;
            }
        }
        private void parseText(string text)
        {
            this.Children.Clear();
            m_elementStack.Clear();
            HtmlDiv root = new HtmlDiv();
            m_current = root;
            m_currentTextBlock = null;

            if (text != null)
            {
                HtmlStyle style = new HtmlStyle();
                HtmlParser parser = new HtmlParser(text);
                HtmlNode node;
                while ((node = parser.NextNode()).Type != HtmlNodeType.EOF)
                {
                    Debug.WriteLine("HtmlNode = " + node);
                    string elementName = null;
                    switch (node.Type)
                    {
                        case HtmlNodeType.Element:
                            elementName = node.LocalName.ToUpper();
                            switch (elementName)
                            {
                                case ELEMENT_A:
                                    style = new HtmlStyle(style);
                                    style.TextDecorations = System.Windows.TextDecorations.Underline;
                                    style.Foreground = new SolidColorBrush { Color = Colors.Blue };
                                    HtmlAnchor newAnchor = new HtmlAnchor();
                                    newAnchor.HtmlStyle = style;
                                    while ((node = parser.NextNode()).Type == HtmlNodeType.Attribute)
                                    {
                                        Debug.WriteLine("Attribute = " + node);
                                        string attributeName = node.LocalName.ToUpper();
                                        switch (attributeName)
                                        {
                                            case ATTRIBUTE_HREF:
                                                newAnchor.NavigateUri = new Uri(node.Value, UriKind.RelativeOrAbsolute);
                                                break;
                                            default:
                                                parseStyleAttribute(node, style);
                                                break;
                                        }
                                    }
                                    parser.PushNode(node);
                                    AddElement(newAnchor);
                                    break;
                                case ELEMENT_B:
                                    style = new HtmlStyle(style);
                                    style.FontWeight = FontWeights.Bold;
                                    break;
                                case ELEMENT_BLOCKQUOTE:
                                    style = new HtmlStyle(style);
                                    parseAllStyleAttributes(parser, style);
                                    HtmlBlockQuote newBlockQuote = new HtmlBlockQuote();
                                    newBlockQuote.HtmlStyle = style;
                                    AddElement(newBlockQuote);
                                    break;
                                case ELEMENT_BR:
                                    AddElement(new HtmlLineBreak());
                                    PopElement();
                                    break;
                                case ELEMENT_DIV:
                                    style = new HtmlStyle(style);
                                    parseAllStyleAttributes(parser, style);
                                    HtmlDiv newDiv = new HtmlDiv();
                                    newDiv.HtmlStyle = style;
                                    AddElement(newDiv);
                                    break;
                                case ELEMENT_EM:
                                    style = new HtmlStyle(style);
                                    style.FontStyle = FontStyles.Italic;
                                    break;
                                case ELEMENT_I:
                                    style = new HtmlStyle(style);
                                    style.FontStyle = FontStyles.Italic;
                                    break;
                                case ELEMENT_FONT:
                                    style = new HtmlStyle(style);
                                    parseAllStyleAttributes(parser, style);
                                    if (style.DefinesBackground)
                                    {
                                        //Can only support background color with a div
                                        HtmlDiv backgroundDiv = new HtmlDiv();
                                        backgroundDiv.HtmlStyle = style;
                                        AddElement(backgroundDiv);
                                    }
                                    break;
                                case ELEMENT_IMG:
                                    style = new HtmlStyle(style);
                                    HtmlImg newHtmlImg = new HtmlImg();
                                    while ((node = parser.NextNode()).Type == HtmlNodeType.Attribute)
                                    {
                                        Debug.WriteLine("Attribute = " + node);
                                        string attributeName = node.LocalName.ToUpper();
                                        switch (attributeName)
                                        {
                                            case ATTRIBUTE_SRC:
                                                newHtmlImg.ImageSrc = new Uri(node.Value, UriKind.RelativeOrAbsolute);
                                                break;
                                            default:
                                                parseStyleAttribute(node, style);
                                                break;
                                        }
                                    }
                                    parser.PushNode(node);
                                    newHtmlImg.HtmlStyle = style;
                                    AddElement(newHtmlImg);
                                    break;
                                case ELEMENT_LI:
                                    style = new HtmlStyle(style);
                                    HtmlListItem newListItem = new HtmlListItem();
                                    newListItem.HtmlStyle = style;
                                    AddElement(newListItem);
                                    break;
                                case ELEMENT_OL:
                                    style = new HtmlStyle(style);
                                    HtmlOrderedList newOrderedList = new HtmlOrderedList();
                                    newOrderedList.HtmlStyle = style;
                                    AddElement(newOrderedList);
                                    break;
                                case ELEMENT_UL:
                                    style = new HtmlStyle(style);
                                    HtmlUnorderedList newUnorderedList = new HtmlUnorderedList();
                                    newUnorderedList.HtmlStyle = style;
                                    AddElement(newUnorderedList);
                                    break;
                                case ELEMENT_P:
                                    style = new HtmlStyle(style);
                                    parseAllStyleAttributes(parser, style);
                                    HtmlParagraph paragraph = new HtmlParagraph();
                                    AddElement(paragraph);
                                    break;
                                case ELEMENT_STRONG:
                                    style = new HtmlStyle(style);
                                    style.FontWeight = FontWeights.ExtraBold;
                                    break;
                                case ELEMENT_U:
                                    style = new HtmlStyle(style);
                                    style.TextDecorations = TextDecorations.Underline;
                                    break;
                                default:
                                    Debug.WriteLine("Unimplemented Element = " + node);
                                    break;
                            }
                            break;
                        case HtmlNodeType.EndElement:
                            elementName = node.LocalName.ToUpper();
                            switch (elementName)
                            {
                                case ELEMENT_A:
                                case ELEMENT_BLOCKQUOTE:
                                case ELEMENT_DIV:
                                case ELEMENT_IMG:
                                case ELEMENT_LI:
                                case ELEMENT_OL:
                                case ELEMENT_P:
                                case ELEMENT_UL:
                                    style = style.Parent;
                                    PopElement();
                                    break;
                                case ELEMENT_B:
                                    style = style.Parent;
                                    break;
                                case ELEMENT_BR:
                                    AddElement(new HtmlLineBreak());
                                    PopElement();
                                    break;
                                case ELEMENT_EM:
                                    style = style.Parent;
                                    break;
                                case ELEMENT_FONT:
                                    if (style.DefinesBackground)
                                    {
                                        PopElement();
                                    }
                                    style = style.Parent;
                                    break;
                                case ELEMENT_I:
                                    style = style.Parent;
                                    break;
                                case ELEMENT_STRONG:
                                    style = style.Parent;
                                    break;
                                case ELEMENT_U:
                                    style = style.Parent;
                                    break;
                                default:
                                    Debug.WriteLine("Unimplemented EndElement = " + node);
                                    break;
                            }
                            break;
                        case HtmlNodeType.Whitespace:
                            break;
                        case HtmlNodeType.Text:
                            Run run = new Run();
                            run.Text = s_entityResolver.ResolveAllEntities(node.NormalizedValue);
                            style.Set(run);
                            AddInline(style,run);
                            break;
                    }
                }
            }
            if (root != null)
            {
                this.Children.Add(root);
            }
        }
To test this control I created a SharePoint list with an enhanced rich text field.  Here is what the column looked like on my development SharePoint site:
SharepointRTFEditor
If you look at the raw HTML generated for this column it look like this:
Rtfexample
Now here is what it looked like in an instantiation of the HtmlTextBlock control in Silverlight:
DisplayExample
Note that above UI is a little crude, the HtmlTextBlock control was used to format only the the middle part of the above image.
The complete source for the HtmlTextBlock control can be found here: 

DaisleyHarrison.Silverlight.HtmlTextBlock.v2.zip (774.80 kb)

Authors

All Categories

Archive