첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
728x90
반응형
728x170

■ HTML을 XAML로 변환하는 방법을 보여준다.

TestProject.zip
0.04MB

▶ CSSStyleSheet.cs

using System.Collections.Generic;
using System.Text;
using System.Xml;

namespace TestProject
{
    /// <summary>
    /// CSS 스타일 시트
    /// </summary>
    internal class CSSStyleSheet
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Class
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 스타일 정의 - StyleDefinition

        /// <summary>
        /// 스타일 정의
        /// </summary>
        private class StyleDefinition
        {
            //////////////////////////////////////////////////////////////////////////////////////////////////// Field
            ////////////////////////////////////////////////////////////////////////////////////////// Public

            #region Field

            /// <summary>
            /// 셀렉터
            /// </summary>
            public string Selector;

            /// <summary>
            /// 정의
            /// </summary>
            public string Definition;

            #endregion

            //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
            ////////////////////////////////////////////////////////////////////////////////////////// Public

            #region 생성자 - StyleDefinition(selector, definition)

            /// <summary>
            /// 생성자
            /// </summary>
            /// <param name="selector">셀렉터</param>
            /// <param name="definition">정의</param>
            public StyleDefinition(string selector, string definition)
            {
                Selector   = selector;
                Definition = definition;
            }

            #endregion
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 스타일 정의 리스트
        /// </summary>
        private List<StyleDefinition> _styleDefinitionList;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - CSSStyleSheet(htmlElement)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="htmlElement">HTML 엘리먼트</param>
        public CSSStyleSheet(XmlElement htmlElement)
        {
            if(htmlElement != null)
            {
                DiscoverStyleDefinitions(htmlElement);
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 스타일 정의 추가하기 - AddStyleDefinition(selector, definition)

        /// <summary>
        /// 스타일 정의 추가하기
        /// </summary>
        /// <param name="selector">셀렉터</param>
        /// <param name="definition">정의</param>
        public void AddStyleDefinition(string selector, string definition)
        {
            selector = selector.Trim().ToLower();

            definition = definition.Trim().ToLower();

            if(selector.Length == 0 || definition.Length == 0)
            {
                return;
            }

            if(_styleDefinitionList == null)
            {
                _styleDefinitionList = new List<StyleDefinition>();
            }

            string[] simpleSelectorArray = selector.Split(',');

            for(int i = 0; i < simpleSelectorArray.Length; i++)
            {
                string simpleSelector = simpleSelectorArray[i].Trim();

                if(simpleSelector.Length > 0)
                {
                    _styleDefinitionList.Add(new StyleDefinition(simpleSelector, definition));
                }
            }
        }

        #endregion
        #region 스타일 정의 발견하기 - DiscoverStyleDefinitions(htmlElement)

        /// <summary>
        /// 스타일 정의 발견하기
        /// </summary>
        /// <param name="htmlElement">HTML 엘리먼트</param>
        public void DiscoverStyleDefinitions(XmlElement htmlElement)
        {
            if(htmlElement.LocalName.ToLower() == "link")
            {
                return;
            }

            if(htmlElement.LocalName.ToLower() != "style")
            {
                for(XmlNode childNode = htmlElement.FirstChild; childNode != null; childNode = childNode.NextSibling)
                {
                    if(childNode is XmlElement)
                    {
                        this.DiscoverStyleDefinitions((XmlElement)childNode);
                    }
                }

                return;
            }

            StringBuilder stringBuilder = new StringBuilder();

            for(XmlNode childNode = htmlElement.FirstChild; childNode != null; childNode = childNode.NextSibling)
            {
                if(childNode is XmlText || childNode is XmlComment)
                {
                    stringBuilder.Append(RemoveComments(childNode.Value));
                }
            }

            int nextCharacterIndex = 0;

            while(nextCharacterIndex < stringBuilder.Length)
            {
                int selectorStart = nextCharacterIndex;

                while(nextCharacterIndex < stringBuilder.Length && stringBuilder[nextCharacterIndex] != '{')
                {
                    if(stringBuilder[nextCharacterIndex] == '@')
                    {
                        while(nextCharacterIndex < stringBuilder.Length && stringBuilder[nextCharacterIndex] != ';')
                        {
                            nextCharacterIndex++;
                        }

                        selectorStart = nextCharacterIndex + 1;
                    }

                    nextCharacterIndex++;
                }

                if(nextCharacterIndex < stringBuilder.Length)
                {
                    int definitionStart = nextCharacterIndex;

                    while(nextCharacterIndex < stringBuilder.Length && stringBuilder[nextCharacterIndex] != '}')
                    {
                        nextCharacterIndex++;
                    }

                    if(nextCharacterIndex - definitionStart > 2)
                    {
                        AddStyleDefinition
                        (
                            stringBuilder.ToString(selectorStart, definitionStart - selectorStart),
                            stringBuilder.ToString(definitionStart + 1, nextCharacterIndex - definitionStart - 2)
                        );
                    }

                    if(nextCharacterIndex < stringBuilder.Length)
                    {
                        nextCharacterIndex++;
                    }
                }
            }
        }

        #endregion
        #region 스타일 구하기 - GetStyle(elementName, sourceElementList)

        /// <summary>
        /// 스타일 구하기
        /// </summary>
        /// <param name="elementName">엘리먼트명</param>
        /// <param name="sourceElementList">소스 엘리먼트 리스트</param>
        /// <returns>스타일</returns>
        public string GetStyle(string elementName, List<XmlElement> sourceElementList)
        {
            if(_styleDefinitionList != null)
            {
                for(int i = _styleDefinitionList.Count - 1; i >= 0; i--)
                {
                    string selector = _styleDefinitionList[i].Selector;

                    string[] selectorLevelArray = selector.Split(' ');

                    int indexInSelector = selectorLevelArray.Length - 1;

                    int indexInContext = sourceElementList.Count - 1;

                    string selectorLevel = selectorLevelArray[indexInSelector].Trim();

                    if(MatchSelectorLevel(selectorLevel, sourceElementList[sourceElementList.Count - 1]))
                    {
                        return _styleDefinitionList[i].Definition;
                    }
                }
            }

            return null;
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 주석 제거하기 - RemoveComments(text)

        /// <summary>
        /// 주석 제거하기
        /// </summary>
        /// <param name="text">텍스트</param>
        /// <returns>주석 제거 텍스트</returns>
        private string RemoveComments(string text)
        {
            int commentStart = text.IndexOf("/*");

            if(commentStart < 0)
            {
                return text;
            }

            int commentEnd = text.IndexOf("*/", commentStart + 2);

            if(commentEnd < 0)
            {
                return text.Substring(0, commentStart);
            }

            return text.Substring(0, commentStart) + " " + RemoveComments(text.Substring(commentEnd + 2));
        }

        #endregion
        #region 셀렉터 레벨 매칭하기 - MatchSelectorLevel(selectorLevel, xmlElement)

        /// <summary>
        /// 셀렉터 레벨 매칭하기
        /// </summary>
        /// <param name="selectorLevel">셀렉터 레벨</param>
        /// <param name="xmlElement">XML 엘리먼트</param>
        /// <returns>처리 결과</returns>
        private bool MatchSelectorLevel(string selectorLevel, XmlElement xmlElement)
        {
            if(selectorLevel.Length == 0)
            {
                return false;
            }

            int dotindex   = selectorLevel.IndexOf('.');
            int poindIndex = selectorLevel.IndexOf('#');

            string selectorClass = null;
            string selectorID    = null;
            string selectorTag   = null;

            if(dotindex >= 0)
            {
                if(dotindex > 0)
                {
                    selectorTag = selectorLevel.Substring(0, dotindex);
                }

                selectorClass = selectorLevel.Substring(dotindex + 1);
            }
            else if(poindIndex >= 0)
            {
                if(poindIndex > 0)
                {
                    selectorTag = selectorLevel.Substring(0, poindIndex);
                }

                selectorID = selectorLevel.Substring(poindIndex + 1);
            }
            else
            {
                selectorTag = selectorLevel;
            }

            if(selectorTag != null && selectorTag != xmlElement.LocalName)
            {
                return false;
            }

            if(selectorID != null && HTMLToXAMLConverter.GetAttribute(xmlElement, "id") != selectorID)
            {
                return false;
            }

            if(selectorClass != null && HTMLToXAMLConverter.GetAttribute(xmlElement, "class") != selectorClass)
            {
                return false;
            }

            return true;
        }

        #endregion
    }
}

 

▶ HTMLCSSParser.cs

using System.Collections;
using System.Collections.Generic;
using System.Xml;

namespace TestProject
{
    /// <summary>
    /// HTML CSS 구문 분석기
    /// </summary>
    internal static class HTMLCSSParser
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 폰트 일반 패밀리 배열
        /// </summary>
        private static readonly string[] _fontGenericFamilyArray = new string[] { "serif", "sans-serif", "monospace", "cursive", "fantasy" };

        /// <summary>
        /// 폰트 스타일 배열
        /// </summary>
        private static readonly string[] _fontStyleArray = new string[] { "normal", "italic", "oblique" };

        /// <summary>
        /// 폰트 변형 배열
        /// </summary>
        private static readonly string[] _fontVariantArray = new string[] { "normal", "small-caps" };

        /// <summary>
        /// 폰트 가중치 배열
        /// </summary>
        private static readonly string[] _fontWeightArray = new string[] { "normal", "bold", "bolder", "lighter", "100", "200", "300", "400", "500", "600", "700", "800", "900" };

        /// <summary>
        /// 폰트 절대 크기 배열
        /// </summary>
        private static readonly string[] _fontAbsoluteSizeArray = new string[] { "xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large" };

        /// <summary>
        /// 폰트 상대 크기 배열
        /// </summary>
        private static readonly string[] _fontRelativeSizeArray = new string[] { "larger", "smaller" };

        /// <summary>
        /// 폰트 크기 단위 배열
        /// </summary>
        private static readonly string[] _fontSizeUnitArray = new string[] { "px", "mm", "cm", "in", "pt", "pc", "em", "ex", "%" };

        /// <summary>
        /// 색상 배열
        /// </summary>
        private static readonly string[] _colorArray = new string[]
        {
            "aliceblue"    , "antiquewhite"    , "aqua"         , "aquamarine"    , "azure"         , "beige"         , "bisque"              , "black"            , "blanchedalmond" , "blue"           ,
            "blueviolet"   , "brown"           , "burlywood"    , "cadetblue"     , "chartreuse"    , "chocolate"     , "coral"               , "cornflowerblue"   , "cornsilk"       , "crimson"        ,
            "cyan"         , "darkblue"        , "darkcyan"     , "darkgoldenrod" , "darkgray"      , "darkgreen"     , "darkkhaki"           , "darkmagenta"      , "darkolivegreen" , "darkorange"     ,
            "darkorchid"   , "darkred"         , "darksalmon"   , "darkseagreen"  , "darkslateblue" , "darkslategray" , "darkturquoise"       , "darkviolet"       , "deeppink"       , "deepskyblue"    ,
            "dimgray"      , "dodgerblue"      , "firebrick"    , "floralwhite"   , "forestgreen"   , "fuchsia"       , "gainsboro"           , "ghostwhite"       , "gold"           , "goldenrod"      ,
            "gray"         , "green"           , "greenyellow"  , "honeydew"      , "hotpink"       , "indianred"     , "indigo"              , "ivory"            , "khaki"          , "lavender"       ,
            "lavenderblush", "lawngreen"       , "lemonchiffon" , "lightblue"     , "lightcoral"    , "lightcyan"     , "lightgoldenrodyellow", "lightgreen"       , "lightgrey"      , "lightpink"      ,
            "lightsalmon"  , "lightseagreen"   , "lightskyblue" , "lightslategray", "lightsteelblue", "lightyellow"   , "lime"                , "limegreen"        , "linen"          , "magenta"        ,
            "maroon"       , "mediumaquamarine", "mediumblue"   , "mediumorchid"  , "mediumpurple"  , "mediumseagreen", "mediumslateblue"     , "mediumspringgreen", "mediumturquoise", "mediumvioletred",
            "midnightblue" , "mintcream"       , "mistyrose"    , "moccasin"      , "navajowhite"   , "navy"          , "oldlace"             , "olive"            , "olivedrab"      , "orange"         ,
            "orangered"    , "orchid"          , "palegoldenrod", "palegreen"     , "paleturquoise" , "palevioletred" , "papayawhip"          , "peachpuff"        , "peru"           , "pink"           ,
            "plum"         , "powderblue"      , "purple"       , "red"           , "rosybrown"     , "royalblue"     , "saddlebrown"         , "salmon"           , "sandybrown"     , "seagreen"       ,
            "seashell"     , "sienna"          , "silver"       , "skyblue"       , "slateblue"     , "slategray"     , "snow"                , "springgreen"      , "steelblue"      , "tan"            ,
            "teal"         , "thistle"         , "tomato"       , "turquoise"     , "violet"        , "wheat"         , "white"               , "whitesmoke"       , "yellow"         , "yellowgreen"
        };

        /// <summary>
        /// 시스템 색상 배열
        /// </summary>
        private static readonly string[] _systemColorArray = new string[]
        {
            "activeborder"    , "activecaption", "appworkspace"   , "background"       , "buttonface"         , "buttonhighlight", "buttonshadow", "buttontext", "captiontext", "graytext" ,
            "highlight"       , "highlighttext", "inactiveborder" , "inactivecaption"  , "inactivecaptiontext", "infobackground" , "infotext"    , "menu"      , "menutext"   , "scrollbar",
            "threeddarkshadow", "threedface"   , "threedhighlight", "threedlightshadow", "threedshadow"       , "window"         , "windowframe" , "windowtext"
        };

        /// <summary>
        /// 텍스트 장식 배열
        /// </summary>
        private static readonly string[] _textDecorationArray = new string[] { "none", "underline", "overline", "line-through", "blink" };

        /// <summary>
        /// 텍스트 변환 배열
        /// </summary>
        private static readonly string[] _textTransformArray = new string[] { "none", "capitalize", "uppercase", "lowercase" };

        /// <summary>
        /// 텍스트 정렬 배열
        /// </summary>
        private static readonly string[] _textAlignmentArray = new string[] { "left", "right", "center", "justify" };

        /// <summary>
        /// 리스트 스타일 타입 배열
        /// </summary>
        private static readonly string[] _listStyleTypeArray = new string[]
        {
            "disc", "circle", "square", "decimal", "lower-roman", "upper-roman", "lower-alpha", "upper-alpha", "none"
        };

        /// <summary>
        /// 리스트 스타일 위치 배열
        /// </summary>
        private static readonly string[] _listStylePositionArray = new string[] { "inside", "outside" };

        /// <summary>
        /// 수직 정렬 배열
        /// </summary>
        private static readonly string[] _verticalAlignmentArray = new string[]
        {
            "baseline", "sub", "super", "top", "text-top", "middle", "bottom", "text-bottom"
        };

        /// <summary>
        /// 테두리 스타일 배열
        /// </summary>
        private static readonly string[] _borderStyleArray = new string[]
        {
            "none", "dotted", "dashed", "solid", "double", "groove", "ridge", "inset", "outset"
        };

        /// <summary>
        /// 플로트 배열
        /// </summary>
        private static readonly string[] _floatArray = new string[] { "left", "right", "none" };

        /// <summary>
        /// 클리어 배열
        /// </summary>
        private static readonly string[] _clearArray = new string[] { "none", "left", "right", "both" };

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Internal

        #region CSS 어트리뷰트에서 엘리먼트 속성 구하기 - GetElementPropertiesFromCSSAttributes(htmlElement, elementName, styleSheet, localPropertyTable, sourceElementList)

        /// <summary>
        /// CSS 어트리뷰트에서 엘리먼트 속성 구하기
        /// </summary>
        /// <param name="htmlElement">HTML 엘리먼트</param>
        /// <param name="elementName">엘리먼트명</param>
        /// <param name="styleSheet">스타일 시트</param>
        /// <param name="localPropertyTable">로컬 속성 테이블</param>
        /// <param name="sourceElementList">소스 엘리먼트</param>
        internal static void GetElementPropertiesFromCSSAttributes
        (
            XmlElement       htmlElement,
            string           elementName,
            CSSStyleSheet    styleSheet,
            Hashtable        localPropertyTable,
            List<XmlElement> sourceElementList
        )
        {
            string styleFromStyleSheet = styleSheet.GetStyle(elementName, sourceElementList);

            string styleInline = HTMLToXAMLConverter.GetAttribute(htmlElement, "style");

            string style = styleFromStyleSheet != null ? styleFromStyleSheet : null;

            if(styleInline != null)
            {
                style = style == null ? styleInline : (style + ";" + styleInline);
            }

            if(style != null)
            {
                string[] styleValueArray = style.Split(';');

                for(int i = 0; i < styleValueArray.Length; i++)
                {
                    string[] styleNameValueArray;

                    styleNameValueArray = styleValueArray[i].Split(':');

                    if(styleNameValueArray.Length == 2)
                    {
                        string styleName = styleNameValueArray[0].Trim().ToLower();

                        string styleValue = HTMLToXAMLConverter.UnQuote(styleNameValueArray[1].Trim()).ToLower();

                        int nextIndex = 0;

                        switch(styleName)
                        {
                            case "font" :

                                ParseCSSFont(styleValue, localPropertyTable);

                                break;

                            case "font-family" :

                                ParseCSSFontFamily(styleValue, ref nextIndex, localPropertyTable);

                                break;

                            case "font-size" :

                                ParseCSSSize(styleValue, ref nextIndex, localPropertyTable, "font-size", true);

                                break;

                            case "font-style" :

                                ParseCSSFontStyle(styleValue, ref nextIndex, localPropertyTable);

                                break;

                            case "font-weight" :

                                ParseCSSFontWeight(styleValue, ref nextIndex, localPropertyTable);

                                break;

                            case "font-variant" :

                                ParseCSSFontVariant(styleValue, ref nextIndex, localPropertyTable);

                                break;

                            case "line-height" :

                                ParseCSSSize(styleValue, ref nextIndex, localPropertyTable, "line-height", true);

                                break;

                            case "word-spacing" :

                                break;

                            case "letter-spacing" :

                                break;

                            case "color" :

                                ParseCSSColor(styleValue, ref nextIndex, localPropertyTable, "color");

                                break;

                            case "text-decoration" :

                                ParseCSSTextDecoration(styleValue, ref nextIndex, localPropertyTable);

                                break;

                            case "text-transform" :

                                ParseCSSTextTransform(styleValue, ref nextIndex, localPropertyTable);

                                break;

                            case "background-color" :

                                ParseCSSColor(styleValue, ref nextIndex, localPropertyTable, "background-color");

                                break;

                            case "background" :

                                ParseCSSBackground(styleValue, ref nextIndex, localPropertyTable);

                                break;

                            case "text-align" :

                                ParseCSSTextAlignment(styleValue, ref nextIndex, localPropertyTable);

                                break;

                            case "vertical-align" :

                                ParseCSSVerticalAlignment(styleValue, ref nextIndex, localPropertyTable);

                                break;

                            case "text-indent" :

                                ParseCSSSize(styleValue, ref nextIndex, localPropertyTable, "text-indent", false);

                                break;

                            case "width"  :
                            case "height" :

                                ParseCSSSize(styleValue, ref nextIndex, localPropertyTable, styleName, true);

                                break;

                            case "margin" :

                                ParseCSSRectangleProperty(styleValue, ref nextIndex, localPropertyTable, styleName);

                                break;

                            case "margin-top"    :
                            case "margin-right"  :
                            case "margin-bottom" :
                            case "margin-left"   :

                                ParseCSSSize(styleValue, ref nextIndex, localPropertyTable, styleName, true);

                                break;

                            case "padding" :

                                ParseCSSRectangleProperty(styleValue, ref nextIndex, localPropertyTable, styleName);

                                break;

                            case "padding-top"    :
                            case "padding-right"  :
                            case "padding-bottom" :
                            case "padding-left"   :

                                ParseCSSSize(styleValue, ref nextIndex, localPropertyTable, styleName, true);

                                break;

                            case "border" :

                                ParseCSSBorder(styleValue, ref nextIndex, localPropertyTable);

                                break;

                            case "border-style":
                            case "border-width":
                            case "border-color":

                                ParseCSSRectangleProperty(styleValue, ref nextIndex, localPropertyTable, styleName);

                                break;

                            case "border-top"    :
                            case "border-right"  :
                            case "border-left"   :
                            case "border-bottom" :

                                break;

                            case "border-top-style"    :
                            case "border-right-style"  :
                            case "border-left-style"   :
                            case "border-bottom-style" :
                            case "border-top-color"    :
                            case "border-right-color"  :
                            case "border-left-color"   :
                            case "border-bottom-color" :
                            case "border-top-width"    :
                            case "border-right-width"  :
                            case "border-left-width"   :
                            case "border-bottom-width" :

                                break;

                            case "display" :

                                break;

                            case "float" :

                                ParseCSSFloat(styleValue, ref nextIndex, localPropertyTable);

                                break;

                            case "clear" :

                                ParseCSSClear(styleValue, ref nextIndex, localPropertyTable);

                                break;

                            default :

                                break;
                        }
                    }
                }
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////// Private

        #region 공백 문자 구문 분석하기 - ParseWhiteSpace(styleValue, nextIndex)

        /// <summary>
        /// 공백 문자 구문 분석하기
        /// </summary>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        private static void ParseWhiteSpace(string styleValue, ref int nextIndex)
        {
            while(nextIndex < styleValue.Length && char.IsWhiteSpace(styleValue[nextIndex]))
            {
                nextIndex++;
            }
        }

        #endregion
        #region 단어 구문 분석하기 - ParseWord(word, styleValue, nextIndex)

        /// <summary>
        /// 단어 구문 분석하기
        /// </summary>
        /// <param name="word">단어</param>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        /// <returns>처리 결과</returns>
        private static bool ParseWord(string word, string styleValue, ref int nextIndex)
        {
            ParseWhiteSpace(styleValue, ref nextIndex);

            for(int i = 0; i < word.Length; i++)
            {
                if(!(nextIndex + i < styleValue.Length && word[i] == styleValue[nextIndex + i]))
                {
                    return false;
                }
            }

            if(nextIndex + word.Length < styleValue.Length && char.IsLetterOrDigit(styleValue[nextIndex + word.Length]))
            {
                return false;
            }

            nextIndex += word.Length;

            return true;
        }

        #endregion
        #region 단어 열거 구문 분석하기 - ParseWordEnumeration(wordArray, styleValue, nextIndex)

        /// <summary>
        /// 단어 열거 구문 분석하기
        /// </summary>
        /// <param name="wordArray">단어 배열</param>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        /// <returns>단어</returns>
        private static string ParseWordEnumeration(string[] wordArray, string styleValue, ref int nextIndex)
        {
            for(int i = 0; i < wordArray.Length; i++)
            {
                if(ParseWord(wordArray[i], styleValue, ref nextIndex))
                {
                    return wordArray[i];
                }
            }

            return null;
        }

        #endregion
        #region 단어 열거 구문 분석하기 - ParseWordEnumeration(wordArray, styleValue, nextIndex, localPropertyTable, attributeName)

        /// <summary>
        /// 단어 열거 구문 분석하기
        /// </summary>
        /// <param name="wordArray">단어 배열</param>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        /// <param name="localPropertyTable">로컬 속성 테이블</param>
        /// <param name="attributeName">어트리뷰트명</param>
        private static void ParseWordEnumeration(string[] wordArray, string styleValue, ref int nextIndex, Hashtable localPropertyTable, string attributeName)
        {
            string attributeValue = ParseWordEnumeration(wordArray, styleValue, ref nextIndex);

            if(attributeValue != null)
            {
                localPropertyTable[attributeName] = attributeValue;
            }
        }

        #endregion

        #region CSS 폰트 스타일 구문 분석하기 - ParseCSSFontStyle(styleValue, nextIndex, localPropertyTable)

        /// <summary>
        /// CSS 폰트 스타일 구문 분석하기
        /// </summary>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        /// <param name="localPropertyTable">로컬 속성 테이블</param>
        private static void ParseCSSFontStyle(string styleValue, ref int nextIndex, Hashtable localPropertyTable)
        {
            ParseWordEnumeration(_fontStyleArray, styleValue, ref nextIndex, localPropertyTable, "font-style");
        }

        #endregion
        #region CSS 폰트 변형 구문 분석하기 - ParseCSSFontVariant(styleValue, nextIndex, localPropertyTable)

        /// <summary>
        /// CSS 폰트 변형 구문 분석하기
        /// </summary>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        /// <param name="localPropertyTable">로컬 속성 테이블</param>
        private static void ParseCSSFontVariant(string styleValue, ref int nextIndex, Hashtable localPropertyTable)
        {
            ParseWordEnumeration(_fontVariantArray, styleValue, ref nextIndex, localPropertyTable, "font-variant");
        }

        #endregion
        #region CSS 폰트 가중치 구문 분석하기 - ParseCSSFontWeight(styleValue, nextIndex, localPropertyTable)

        /// <summary>
        /// CSS 폰트 가중치 구문 분석하기
        /// </summary>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        /// <param name="localPropertyTable">로컬 속성 테이블</param>
        private static void ParseCSSFontWeight(string styleValue, ref int nextIndex, Hashtable localPropertyTable)
        {
            ParseWordEnumeration(_fontWeightArray, styleValue, ref nextIndex, localPropertyTable, "font-weight");
        }

        #endregion
        #region CSS 크기 구문 분석하기 - ParseCSSSize(styleValue, nextIndex, mustBeNonNegative)

        /// <summary>
        /// CSS 크기 구문 분석하기
        /// </summary>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        /// <param name="mustBeNonNegative">비-음수 필수 여부</param>
        /// <returns>CSS 크기</returns>
        private static string ParseCSSSize(string styleValue, ref int nextIndex, bool mustBeNonNegative)
        {
            ParseWhiteSpace(styleValue, ref nextIndex);

            int startIndex = nextIndex;

            if(nextIndex < styleValue.Length && styleValue[nextIndex] == '-')
            {
                nextIndex++;
            }

            if(nextIndex < styleValue.Length && char.IsDigit(styleValue[nextIndex]))
            {
                while (nextIndex < styleValue.Length && (char.IsDigit(styleValue[nextIndex]) || styleValue[nextIndex] == '.'))
                {
                    nextIndex++;
                }

                string number = styleValue.Substring(startIndex, nextIndex - startIndex);

                string unit = ParseWordEnumeration(_fontSizeUnitArray, styleValue, ref nextIndex);

                if(unit == null)
                {
                    unit = "px";
                }

                if(mustBeNonNegative && styleValue[startIndex] == '-')
                {
                    return "0";
                }
                else
                {
                    return number + unit;
                }
            }

            return null;
        }

        #endregion
        #region CSS 크기 구문 분석하기 - ParseCSSSize(styleValue, nextIndex, localValueTable, propertyName, mustBeNonNegative)

        /// <summary>
        /// CSS 크기 구문 분석하기
        /// </summary>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        /// <param name="localValueTable">로컬 값 테이블</param>
        /// <param name="propertyName">속성명</param>
        /// <param name="mustBeNonNegative">비-음수 필수 여부</param>
        private static void ParseCSSSize(string styleValue, ref int nextIndex, Hashtable localValueTable, string propertyName, bool mustBeNonNegative)
        {
            string length = ParseCSSSize(styleValue, ref nextIndex, mustBeNonNegative);

            if(length != null)
            {
                localValueTable[propertyName] = length;
            }
        }

        #endregion
        #region CSS 폰트 패밀리 구문 분석하기 - ParseCSSFontFamily(styleValue, nextIndex, localPropertyTable)

        /// <summary>
        /// CSS 폰트 패밀리 구문 분석하기
        /// </summary>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        /// <param name="localPropertyTable">로컬 속성 테이블</param>
        private static void ParseCSSFontFamily(string styleValue, ref int nextIndex, Hashtable localPropertyTable)
        {
            string fontFamilyList = null;

            while(nextIndex < styleValue.Length)
            {
                string fontFamily = ParseWordEnumeration(_fontGenericFamilyArray, styleValue, ref nextIndex);

                if(fontFamily == null)
                {
                    if(nextIndex < styleValue.Length && (styleValue[nextIndex] == '"' || styleValue[nextIndex] == '\''))
                    {
                        char quote = styleValue[nextIndex];

                        nextIndex++;

                        int startIndex = nextIndex;

                        while(nextIndex < styleValue.Length && styleValue[nextIndex] != quote)
                        {
                            nextIndex++;
                        }

                        fontFamily = '"' + styleValue.Substring(startIndex, nextIndex - startIndex) + '"';
                    }

                    if(fontFamily == null)
                    {
                        int startIndex = nextIndex;

                        while(nextIndex < styleValue.Length && styleValue[nextIndex] != ',' && styleValue[nextIndex] != ';')
                        {
                            nextIndex++;
                        }

                        if(nextIndex > startIndex)
                        {
                            fontFamily = styleValue.Substring(startIndex, nextIndex - startIndex).Trim();

                            if(fontFamily.Length == 0)
                            {
                                fontFamily = null;
                            }
                        }
                    }
                }

                ParseWhiteSpace(styleValue, ref nextIndex);

                if(nextIndex < styleValue.Length && styleValue[nextIndex] == ',')
                {
                    nextIndex++;
                }

                if(fontFamily != null)
                {
                    if(fontFamilyList == null && fontFamily.Length > 0)
                    {
                        if(fontFamily[0] == '"' || fontFamily[0] == '\'')
                        {
                            fontFamily = fontFamily.Substring(1, fontFamily.Length - 2);
                        }

                        fontFamilyList = fontFamily;
                    }
                }
                else
                {
                    break;
                }
            }

            if(fontFamilyList != null)
            {
                localPropertyTable["font-family"] = fontFamilyList;
            }
        }

        #endregion
        #region CSS 폰트 구문 분석하기 - ParseCSSFont(styleValue, localPropertyTable)

        /// <summary>
        /// CSS 폰트 구문 분석하기
        /// </summary>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="localPropertyTable">로컬 속성 테이블</param>
        private static void ParseCSSFont(string styleValue, Hashtable localPropertyTable)
        {
            int nextIndex = 0;

            ParseCSSFontStyle(styleValue, ref nextIndex, localPropertyTable);

            ParseCSSFontVariant(styleValue, ref nextIndex, localPropertyTable);

            ParseCSSFontWeight(styleValue, ref nextIndex, localPropertyTable);

            ParseCSSSize(styleValue, ref nextIndex, localPropertyTable, "font-size", true);

            ParseWhiteSpace(styleValue, ref nextIndex);

            if(nextIndex < styleValue.Length && styleValue[nextIndex] == '/')
            {
                nextIndex++;

                ParseCSSSize(styleValue, ref nextIndex, localPropertyTable, "line-height", true);
            }

            ParseCSSFontFamily(styleValue, ref nextIndex, localPropertyTable);
        }

        #endregion
        #region CSS 색상 구문 분석하기 - ParseCSSColor(styleValue, nextIndex)

        /// <summary>
        /// CSS 색상 구문 분석하기
        /// </summary>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        /// <returns>CSS 색상</returns>
        private static string ParseCSSColor(string styleValue, ref int nextIndex)
        {
            ParseWhiteSpace(styleValue, ref nextIndex);

            string color = null;

            if(nextIndex < styleValue.Length)
            {
                int startIndex = nextIndex;

                char character = styleValue[nextIndex];

                if(character == '#')
                {
                    nextIndex++;

                    while(nextIndex < styleValue.Length)
                    {
                        character = char.ToUpper(styleValue[nextIndex]);

                        if(!('0' <= character && character <= '9' || 'A' <= character && character <= 'F'))
                        {
                            break;
                        }

                        nextIndex++;
                    }

                    if(nextIndex > startIndex + 1)
                    {
                        color = styleValue.Substring(startIndex, nextIndex - startIndex);
                    }
                }
                else if(styleValue.Substring(nextIndex, 3).ToLower() == "rbg")
                {
                    while(nextIndex < styleValue.Length && styleValue[nextIndex] != ')')
                    {
                        nextIndex++;
                    }

                    if(nextIndex < styleValue.Length)
                    {
                        nextIndex++;
                    }

                    color = "gray";
                }
                else if(char.IsLetter(character))
                {
                    color = ParseWordEnumeration(_colorArray, styleValue, ref nextIndex);

                    if(color == null)
                    {
                        color = ParseWordEnumeration(_systemColorArray, styleValue, ref nextIndex);

                        if(color != null)
                        {
                            color = "black";
                        }
                    }
                }
            }

            return color;
        }

        #endregion
        #region CSS 색상 구문 분석하기 - ParseCSSColor(styleValue, nextIndex, localValueTable, propertyName)

        /// <summary>
        /// CSS 색상 구문 분석하기
        /// </summary>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        /// <param name="localValueTable">로컬 값 테이블</param>
        /// <param name="propertyName">속성명</param>
        private static void ParseCSSColor(string styleValue, ref int nextIndex, Hashtable localValueTable, string propertyName)
        {
            string color = ParseCSSColor(styleValue, ref nextIndex);

            if(color != null)
            {
                localValueTable[propertyName] = color;
            }
        }

        #endregion
        #region CSS 텍스트 장식 구문 분석하기 - ParseCSSTextDecoration(styleValue, nextIndex, localPropertyTable)

        /// <summary>
        /// CSS 텍스트 장식 구문 분석하기
        /// </summary>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        /// <param name="localPropertyTable">로컬 속성 테이블</param>
        private static void ParseCSSTextDecoration(string styleValue, ref int nextIndex, Hashtable localPropertyTable)
        {
            for(int i = 1; i < _textDecorationArray.Length; i++)
            {
                localPropertyTable["text-decoration-" + _textDecorationArray[i]] = "false";
            }

            while(nextIndex < styleValue.Length)
            {
                string decoration = ParseWordEnumeration(_textDecorationArray, styleValue, ref nextIndex);

                if(decoration == null || decoration == "none")
                {
                    break;
                }

                localPropertyTable["text-decoration-" + decoration] = "true";
            }
        }

        #endregion
        #region CSS 텍스트 변환 구문 분석하기 - ParseCSSTextTransform(styleValue, nextIndex, localPropertyTable)

        /// <summary>
        /// CSS 텍스트 변환 구문 분석하기
        /// </summary>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        /// <param name="localPropertyTable">로컬 속성 테이블</param>
        private static void ParseCSSTextTransform(string styleValue, ref int nextIndex, Hashtable localPropertyTable)
        {
            ParseWordEnumeration(_textTransformArray, styleValue, ref nextIndex, localPropertyTable, "text-transform");
        }

        #endregion
        #region CSS 배경 구문 분석하기 - ParseCSSBackground(styleValue, nextIndex, localValueTable)

        /// <summary>
        /// CSS 배경 구문 분석하기
        /// </summary>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        /// <param name="localValueTable">로컬 값 테이블</param>
        private static void ParseCSSBackground(string styleValue, ref int nextIndex, Hashtable localValueTable)
        {
        }

        #endregion
        #region CSS 텍스트 정렬 구문 분석하기 - ParseCSSTextAlignment(styleValue, nextIndex, localPropertyTable)

        /// <summary>
        /// CSS 텍스트 정렬 구문 분석하기
        /// </summary>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        /// <param name="localPropertyTable">로컬 속성 테이블</param>
        private static void ParseCSSTextAlignment(string styleValue, ref int nextIndex, Hashtable localPropertyTable)
        {
            ParseWordEnumeration(_textAlignmentArray, styleValue, ref nextIndex, localPropertyTable, "text-align");
        }

        #endregion
        #region CSS 수직 정렬 구문 분석하기 - ParseCSSVerticalAlignment(styleValue, nextIndex, localPropertyTable)

        /// <summary>
        /// CSS 수직 정렬 구문 분석하기
        /// </summary>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        /// <param name="localPropertyTable">로컬 속성 테이블</param>
        private static void ParseCSSVerticalAlignment(string styleValue, ref int nextIndex, Hashtable localPropertyTable)
        {
            ParseWordEnumeration(_verticalAlignmentArray, styleValue, ref nextIndex, localPropertyTable, "vertical-align");
        }

        #endregion
        #region CSS 리스트 스타일 타입 구문 분석하기 - ParseCSSListStyleType(styleValue, nextIndex)

        /// <summary>
        /// CSS 리스트 스타일 타입 구문 분석하기
        /// </summary>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        /// <returns>CSS 리스트 스타일 타입</returns>
        private static string ParseCSSListStyleType(string styleValue, ref int nextIndex)
        {
            return ParseWordEnumeration(_listStyleTypeArray, styleValue, ref nextIndex);
        }

        #endregion
        #region CSS 리스트 스타일 위치 구문 분석하기 - ParseCSSListStylePosition(styleValue, nextIndex)

        /// <summary>
        /// CSS 리스트 스타일 위치 구문 분석하기
        /// </summary>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        /// <returns>CSS 리스트 스타일 위치</returns>
        private static string ParseCSSListStylePosition(string styleValue, ref int nextIndex)
        {
            return ParseWordEnumeration(_listStylePositionArray, styleValue, ref nextIndex);
        }

        #endregion
        #region CSS 리스트 스타일 이미지 구문 분석하기 - ParseCSSListStyleImage(styleValue, nextIndex)

        /// <summary>
        /// CSS 리스트 스타일 이미지 구문 분석하기
        /// </summary>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        /// <returns>CSS 리스트 스타일 이미지</returns>
        private static string ParseCSSListStyleImage(string styleValue, ref int nextIndex)
        {
            return null;
        }

        #endregion
        #region CSS 리스트 스타일 구문 분석하기 - ParseCSSListStyle(styleValue, localPropertyTable)

        /// <summary>
        /// CSS 리스트 스타일 구문 분석하기
        /// </summary>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="localPropertyTable">로컬 속성 테이블</param>
        private static void ParseCSSListStyle(string styleValue, Hashtable localPropertyTable)
        {
            int nextIndex = 0;

            while(nextIndex < styleValue.Length)
            {
                string listStyleType = ParseCSSListStyleType(styleValue, ref nextIndex);

                if(listStyleType != null)
                {
                    localPropertyTable["list-style-type"] = listStyleType;
                }
                else
                {
                    string listStylePosition = ParseCSSListStylePosition(styleValue, ref nextIndex);

                    if(listStylePosition != null)
                    {
                        localPropertyTable["list-style-position"] = listStylePosition;
                    }
                    else
                    {
                        string listStyleImage = ParseCSSListStyleImage(styleValue, ref nextIndex);

                        if(listStyleImage != null)
                        {
                            localPropertyTable["list-style-image"] = listStyleImage;
                        }
                        else
                        {
                            break;
                        }
                    }
                }
            }
        }

        #endregion
        #region CSS 테두리 스타일 구문 분석하기 - ParseCSSBorderStyle(styleValue, nextIndex)

        /// <summary>
        /// CSS 테두리 스타일 구문 분석하기
        /// </summary>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        /// <returns>CSS 테두리 스타일</returns>
        private static string ParseCSSBorderStyle(string styleValue, ref int nextIndex)
        {
            return ParseWordEnumeration(_borderStyleArray, styleValue, ref nextIndex);
        }

        #endregion
        #region CSS 사각형 속성 구문 분석하기 - ParseCSSRectangleProperty(styleValue, nextIndex, localPropertyTable, propertyName)

        /// <summary>
        /// CSS 사각형 속성 구문 분석하기
        /// </summary>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        /// <param name="localPropertyTable">로컬 속성 테이블</param>
        /// <param name="propertyName">속성명</param>
        /// <returns>처리 결과</returns>
        private static bool ParseCSSRectangleProperty(string styleValue, ref int nextIndex, Hashtable localPropertyTable, string propertyName)
        {
            string value = propertyName == "border-color" ? ParseCSSColor(styleValue, ref nextIndex) :
                           propertyName == "border-style" ? ParseCSSBorderStyle(styleValue, ref nextIndex) : ParseCSSSize(styleValue, ref nextIndex, true);

            if(value != null)
            {
                localPropertyTable[propertyName + "-top"   ] = value;
                localPropertyTable[propertyName + "-bottom"] = value;
                localPropertyTable[propertyName + "-right" ] = value;
                localPropertyTable[propertyName + "-left"  ] = value;

                value = propertyName == "border-color" ? ParseCSSColor(styleValue, ref nextIndex) :
                        propertyName == "border-style" ? ParseCSSBorderStyle(styleValue, ref nextIndex) : ParseCSSSize(styleValue, ref nextIndex, true);

                if(value != null)
                {
                    localPropertyTable[propertyName + "-right"] = value;
                    localPropertyTable[propertyName + "-left" ] = value;

                    value = propertyName == "border-color" ? ParseCSSColor(styleValue, ref nextIndex) :
                            propertyName == "border-style" ? ParseCSSBorderStyle(styleValue, ref nextIndex) : ParseCSSSize(styleValue, ref nextIndex, true);

                    if(value != null)
                    {
                        localPropertyTable[propertyName + "-bottom"] = value;

                        value = propertyName == "border-color" ? ParseCSSColor(styleValue, ref nextIndex) :
                                propertyName == "border-style" ? ParseCSSBorderStyle(styleValue, ref nextIndex) : ParseCSSSize(styleValue, ref nextIndex, true);

                        if(value != null)
                        {
                            localPropertyTable[propertyName + "-left"] = value;
                        }
                    }
                }

                return true;
            }

            return false;
        }

        #endregion
        #region CSS 테두리 구문 분석하기 - ParseCSSBorder(styleValue, nextIndex, localPropertyTable)

        /// <summary>
        /// CSS 테두리 구문 분석하기
        /// </summary>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        /// <param name="localPropertyTable">로컬 속성 테이블</param>
        private static void ParseCSSBorder(string styleValue, ref int nextIndex, Hashtable localPropertyTable)
        {
            while
            (
                ParseCSSRectangleProperty(styleValue, ref nextIndex, localPropertyTable, "border-width") ||
                ParseCSSRectangleProperty(styleValue, ref nextIndex, localPropertyTable, "border-style") ||
                ParseCSSRectangleProperty(styleValue, ref nextIndex, localPropertyTable, "border-color")
            )
            {
            }
        }

        #endregion
        #region CSS 플로트 구문 분석하기 - ParseCSSFloat(styleValue, nextIndex, localPropertyTable)

        /// <summary>
        /// CSS 플로트 구문 분석하기
        /// </summary>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        /// <param name="localPropertyTable">로컬 속성 테이블</param>
        private static void ParseCSSFloat(string styleValue, ref int nextIndex, Hashtable localPropertyTable)
        {
            ParseWordEnumeration(_floatArray, styleValue, ref nextIndex, localPropertyTable, "float");
        }

        #endregion
        #region CSS 클리어 구문 분석하기 - ParseCSSClear(styleValue, nextIndex, localPropertyTable)

        /// <summary>
        /// CSS 클리어 구문 분석하기
        /// </summary>
        /// <param name="styleValue">스타일 값</param>
        /// <param name="nextIndex">다음 인덱스</param>
        /// <param name="localPropertyTable">로컬 속성 테이블</param>
        private static void ParseCSSClear(string styleValue, ref int nextIndex, Hashtable localPropertyTable)
        {
            ParseWordEnumeration(_clearArray, styleValue, ref nextIndex, localPropertyTable, "clear");
        }

        #endregion
    }
}

 

▶ HTMLLexicalAnalyzer.cs

using System;
using System.IO;
using System.Text;

namespace TestProject
{
    /// <summary>
    /// HTML 어휘 분석기
    /// </summary>
    internal class HTMLLexicalAnalyzer
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 입력 문자열 리더기
        /// </summary>
        private StringReader inputStringReader;

        /// <summary>
        /// 다음 문자 코드
        /// </summary>
        private int nextCharacterCode;

        /// <summary>
        /// 다음 문자
        /// </summary>
        private char nextCharacter;

        /// <summary>
        /// 전방 문자 코드
        /// </summary>
        private int lookAheadCharacterCode;

        /// <summary>
        /// 전방 문자
        /// </summary>
        private char lookAheadCharacter;

        /// <summary>
        /// 이전 문자
        /// </summary>
        private char previousCharacter;

        /// <summary>
        /// 다음 공백 문자 무시 여부
        /// </summary>
        private bool ignoreNextWhitespace;

        /// <summary>
        /// 다음 문자 개체 여부
        /// </summary>
        private bool isNextCharacterEntity;

        /// <summary>
        /// 다음 토큰 문자열 빌더
        /// </summary>
        private StringBuilder nextTokenStringBuilder;

        /// <summary>
        /// 다음 토큰 타입
        /// </summary>
        private HTMLTokenType nextTokenType;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Internal

        #region 다음 토큰 타입 - NextTokenType

        /// <summary>
        /// 다음 토큰 타입
        /// </summary>
        internal HTMLTokenType NextTokenType
        {
            get
            {
                return this.nextTokenType;
            }
        }

        #endregion
        #region 다음 토큰 - NextToken

        /// <summary>
        /// 다음 토큰
        /// </summary>
        internal string NextToken
        {
            get
            {
                return this.nextTokenStringBuilder.ToString();
            }
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 다음 문자 - NextCharacter

        /// <summary>
        /// 다음 문자
        /// </summary>
        private char NextCharacter
        {
            get
            {
                return this.nextCharacter;
            }
        }

        #endregion
        #region 스트림 끝 위치 여부 - IsAtEndOfStream

        /// <summary>
        /// 스트림 끝 위치 여부
        /// </summary>
        private bool IsAtEndOfStream
        {
            get
            {
                return this.nextCharacterCode == -1;
            }
        }

        #endregion
        #region 태그 시작 위치 여부 - IsAtTagStart

        /// <summary>
        /// 태그 시작 위치 여부
        /// </summary>
        private bool IsAtTagStart
        {
            get
            {
                return this.nextCharacter == '<'                                                       &&
                       (this.lookAheadCharacter == '/' || IsGoodForNameStart(this.lookAheadCharacter)) &&
                       !this.isNextCharacterEntity;
            }
        }

        #endregion
        #region 태그 종료 위치 여부 - IsAtTagEnd

        /// <summary>
        /// 태그 종료 위치 여부
        /// </summary>
        private bool IsAtTagEnd
        {
            get
            {
                return (this.nextCharacter == '>' || (this.nextCharacter == '/' && this.lookAheadCharacter == '>')) &&
                       !this.isNextCharacterEntity;
            }
        }

        #endregion
        #region 지시자 시작 위치 여부 - IsAtDirectiveStart

        /// <summary>
        /// 지시자 시작 위치 여부
        /// </summary>
        private bool IsAtDirectiveStart
        {
            get
            {
                return (this.nextCharacter == '<' && this.lookAheadCharacter == '!' && !IsNextCharacterEntity);
            }
        }

        #endregion
        #region 다음 문자 개체 여부 - IsNextCharacterEntity

        /// <summary>
        /// 다음 문자 개체 여부
        /// </summary>
        private bool IsNextCharacterEntity
        {
            get
            {
                return this.isNextCharacterEntity;
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - HTMLLexicalAnalyzer(inputText)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="inputText">입력 텍스트</param>
        internal HTMLLexicalAnalyzer(string inputText)
        {
            this.inputStringReader      = new StringReader(inputText);
            this.nextCharacterCode      = 0;
            this.nextCharacter          = ' ';
            this.lookAheadCharacterCode = this.inputStringReader.Read();
            this.lookAheadCharacter     = (char)this.lookAheadCharacterCode;
            this.previousCharacter      = ' ';
            this.ignoreNextWhitespace   = true;
            this.nextTokenStringBuilder = new StringBuilder(100);
            this.nextTokenType          = HTMLTokenType.Text;

            GetNextCharacter();
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Internal

        #region 다음 컨텐트 토큰 구하기 - GetNextContentToken()

        /// <summary>
        /// 다음 컨텐트 토큰 구하기
        /// </summary>
        internal void GetNextContentToken()
        {
            this.nextTokenStringBuilder.Length = 0;

            if(IsAtEndOfStream)
            {
                this.nextTokenType = HTMLTokenType.EOF;

                return;
            }

            if(IsAtTagStart)
            {
                GetNextCharacter();

                if(NextCharacter == '/')
                {
                    this.nextTokenStringBuilder.Append("</");

                    this.nextTokenType = HTMLTokenType.ClosingTagStart;

                    GetNextCharacter();

                    this.ignoreNextWhitespace = false;
                }
                else
                {
                    this.nextTokenType = HTMLTokenType.OpeningTagStart;

                    this.nextTokenStringBuilder.Append("<");

                    this.ignoreNextWhitespace = true;
                }
            }
            else if(IsAtDirectiveStart)
            {
                GetNextCharacter();

                if(this.lookAheadCharacter == '[')
                {
                    ReadDynamicContent();
                }
                else if(this.lookAheadCharacter == '-')
                {
                    ReadComment();
                }
                else
                {
                    ReadUnknownDirective();
                }
            }
            else
            {
                this.nextTokenType = HTMLTokenType.Text;

                while(!IsAtTagStart && !IsAtEndOfStream && !IsAtDirectiveStart)
                {
                    if(NextCharacter == '<' && !IsNextCharacterEntity && this.lookAheadCharacter == '?')
                    {
                        SkipProcessingDirective();
                    }
                    else
                    {
                        if(NextCharacter <= ' ')
                        {
                            if(!this.ignoreNextWhitespace)
                            {
                                this.nextTokenStringBuilder.Append(' ');
                            }

                            this.ignoreNextWhitespace = true;
                        }
                        else
                        {
                            this.nextTokenStringBuilder.Append(NextCharacter);

                            this.ignoreNextWhitespace = false;
                        }

                        GetNextCharacter();
                    }
                }
            }
        }

        #endregion
        #region 다음 태그 토큰 구하기 - GetNextTagToken()

        /// <summary>
        /// 다음 태그 토큰 구하기
        /// </summary>
        internal void GetNextTagToken()
        {
            this.nextTokenStringBuilder.Length = 0;

            if(IsAtEndOfStream)
            {
                this.nextTokenType = HTMLTokenType.EOF;

                return;
            }

            SkipWhiteSpace();

            if(NextCharacter == '>' && !IsNextCharacterEntity)
            {
                this.nextTokenType = HTMLTokenType.TagEnd;

                this.nextTokenStringBuilder.Append('>');

                GetNextCharacter();
            }
            else if(NextCharacter == '/' && this.lookAheadCharacter == '>')
            {
                this.nextTokenType = HTMLTokenType.EmptyTagEnd;

                this.nextTokenStringBuilder.Append("/>");

                GetNextCharacter();
                GetNextCharacter();

                this.ignoreNextWhitespace = false;
            }
            else if(IsGoodForNameStart(this.NextCharacter))
            {
                this.nextTokenType = HTMLTokenType.Name;

                while(IsGoodForName(NextCharacter) && !IsAtEndOfStream)
                {
                    this.nextTokenStringBuilder.Append(NextCharacter);

                    GetNextCharacter();
                }
            }
            else
            {
                this.nextTokenType = HTMLTokenType.Atom;

                this.nextTokenStringBuilder.Append(NextCharacter);

                GetNextCharacter();
            }
        }

        #endregion
        #region 다음 등호 토큰 구하기 - GetNextEqualSignToken()

        /// <summary>
        /// 다음 등호 토큰 구하기
        /// </summary>
        internal void GetNextEqualSignToken()
        {
            this.nextTokenStringBuilder.Length = 0;

            this.nextTokenStringBuilder.Append('=');

            this.nextTokenType = HTMLTokenType.EqualSign;

            SkipWhiteSpace();

            if(NextCharacter == '=')
            {
                GetNextCharacter();
            }
        }

        #endregion
        #region 다음 원자 토큰 구하기 - GetNextAtomToken()

        /// <summary>
        /// 다음 원자 토큰 구하기
        /// </summary>
        internal void GetNextAtomToken()
        {
            this.nextTokenStringBuilder.Length = 0;

            SkipWhiteSpace();

            this.nextTokenType = HTMLTokenType.Atom;

            if((NextCharacter == '\'' || NextCharacter == '"') && !IsNextCharacterEntity)
            {
                char startingQuote = this.NextCharacter;

                GetNextCharacter();

                while(!(NextCharacter == startingQuote && !IsNextCharacterEntity) && !IsAtEndOfStream)
                {
                    this.nextTokenStringBuilder.Append(NextCharacter);

                    GetNextCharacter();
                }

                if(NextCharacter == startingQuote)
                {
                    GetNextCharacter();
                }
            }
            else
            {
                while(!IsAtEndOfStream && !char.IsWhiteSpace(NextCharacter) && NextCharacter != '>')
                {
                    this.nextTokenStringBuilder.Append(NextCharacter);

                    GetNextCharacter();
                }
            }
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 전방 문자 읽기 - ReadLookAheadCharacter()

        /// <summary>
        /// 전방 문자 읽기
        /// </summary>
        private void ReadLookAheadCharacter()
        {
            if(this.lookAheadCharacterCode != -1)
            {
                this.lookAheadCharacterCode = this.inputStringReader.Read();
                this.lookAheadCharacter     = (char)this.lookAheadCharacterCode;
            }
        }

        #endregion
        #region 다음 문자 구하기 - GetNextCharacter()

        /// <summary>
        /// 다음 문자 구하기
        /// </summary>
        private void GetNextCharacter()
        {
            if(this.nextCharacterCode == -1)
            {
                throw new InvalidOperationException("GetNextCharacter method called at the end of a stream");
            }

            this.previousCharacter     = this.nextCharacter;
            this.nextCharacter         = this.lookAheadCharacter;
            this.nextCharacterCode     = this.lookAheadCharacterCode;
            this.isNextCharacterEntity = false;

            ReadLookAheadCharacter();

            if(this.nextCharacter == '&')
            {
                if(this.lookAheadCharacter == '#')
                {
                    int entityCode = 0;

                    ReadLookAheadCharacter();

                    for(int i = 0; i < 7 && char.IsDigit(this.lookAheadCharacter); i++)
                    {
                        entityCode = 10 * entityCode + (this.lookAheadCharacterCode - (int)'0');

                        ReadLookAheadCharacter();
                    }

                    if(this.lookAheadCharacter == ';')
                    {
                        ReadLookAheadCharacter();

                        this.nextCharacterCode = entityCode;

                        this.nextCharacter = (char)this.nextCharacterCode;

                        this.isNextCharacterEntity = true;
                    }
                    else
                    {
                        this.nextCharacter     = this.lookAheadCharacter;
                        this.nextCharacterCode = this.lookAheadCharacterCode;

                        ReadLookAheadCharacter();

                        this.isNextCharacterEntity = false;
                    }
                }
                else if(char.IsLetter(this.lookAheadCharacter))
                {
                    string entity = string.Empty;

                    for(int i = 0; i < 10 && (char.IsLetter(this.lookAheadCharacter) || char.IsDigit(this.lookAheadCharacter)); i++)
                    {
                        entity += this.lookAheadCharacter;

                        ReadLookAheadCharacter();
                    }

                    if(this.lookAheadCharacter == ';')
                    {
                        ReadLookAheadCharacter();

                        if(HTMLSchema.IsEntity(entity))
                        {
                            this.nextCharacter         = HTMLSchema.GetEntityCharacterValue(entity);
                            this.nextCharacterCode     = (int)this.nextCharacter;
                            this.isNextCharacterEntity = true;
                        }
                        else
                        {
                            this.nextCharacter     = this.lookAheadCharacter;
                            this.nextCharacterCode = this.lookAheadCharacterCode;

                            ReadLookAheadCharacter();

                            this.isNextCharacterEntity = false;
                        }
                    }
                    else
                    {
                        this.nextCharacter = this.lookAheadCharacter;

                        ReadLookAheadCharacter();

                        this.isNextCharacterEntity = false;
                    }
                }
            }
        }

        #endregion
        #region 동적 컨텐트 읽기 - ReadDynamicContent()

        /// <summary>
        /// 동적 컨텐트 읽기
        /// </summary>
        private void ReadDynamicContent()
        {
            this.nextTokenType = HTMLTokenType.Text;

            this.nextTokenStringBuilder.Length = 0;

            GetNextCharacter();
            GetNextCharacter();

            while(!(this.nextCharacter == ']' && this.lookAheadCharacter == '>') && !IsAtEndOfStream)
            {
                GetNextCharacter();
            }

            if(!IsAtEndOfStream)
            {
                GetNextCharacter();

                GetNextCharacter();
            }
        }

        #endregion
        #region 주석 읽기 - ReadComment()

        /// <summary>
        /// 주석 읽기
        /// </summary>
        private void ReadComment()
        {
            this.nextTokenType = HTMLTokenType.Comment;

            this.nextTokenStringBuilder.Length = 0;

            GetNextCharacter();
            GetNextCharacter();
            GetNextCharacter();

            while(true)
            {
                while(!IsAtEndOfStream && !(this.nextCharacter == '-' && this.lookAheadCharacter == '-' || this.nextCharacter == '!' && this.lookAheadCharacter == '>'))
                {
                    this.nextTokenStringBuilder.Append(NextCharacter);

                    GetNextCharacter();
                }

                GetNextCharacter();

                if(this.previousCharacter == '-' && this.nextCharacter == '-' && this.lookAheadCharacter == '>')
                {
                    GetNextCharacter();

                    break;
                }
                else if(this.previousCharacter == '!' && this.nextCharacter == '>')
                {
                    break;
                }
                else
                {
                    this.nextTokenStringBuilder.Append(this.previousCharacter);

                    continue;
                }
            }

            if(this.nextCharacter == '>')
            {
                GetNextCharacter();
            }
        }

        #endregion
        #region 알 수 없는 지시자 읽기 - ReadUnknownDirective()

        /// <summary>
        /// 알 수 없는 지시자 읽기
        /// </summary>
        private void ReadUnknownDirective()
        {
            this.nextTokenType = HTMLTokenType.Text;

            this.nextTokenStringBuilder.Length = 0;

            GetNextCharacter();

            while(!(this.nextCharacter == '>' && !IsNextCharacterEntity) && !IsAtEndOfStream)
            {
                GetNextCharacter();
            }

            if(!IsAtEndOfStream)
            {
                GetNextCharacter();
            }
        }

        #endregion
        #region 처리중 지시자 건너뛰기 - SkipProcessingDirective()

        /// <summary>
        /// 처리중 지시자 건너뛰기
        /// </summary>
        private void SkipProcessingDirective()
        {
            GetNextCharacter();
            GetNextCharacter();

            while(!((this.nextCharacter == '?' || this.nextCharacter == '/') && this.lookAheadCharacter == '>') && !IsAtEndOfStream)
            {
                GetNextCharacter();
            }

            if(!IsAtEndOfStream)
            {
                GetNextCharacter();

                GetNextCharacter();
            }
        }

        #endregion
        #region 공백 문자 건너뛰기 - SkipWhiteSpace()

        /// <summary>
        /// 공백 문자 건너뛰기
        /// </summary>
        private void SkipWhiteSpace()
        {
            while(true)
            {
                if(this.nextCharacter == '<' && (this.lookAheadCharacter == '?' || this.lookAheadCharacter == '!'))
                {
                    GetNextCharacter();

                    if(this.lookAheadCharacter == '[')
                    {
                        while(!IsAtEndOfStream && !(this.previousCharacter == ']' && this.nextCharacter == ']' && this.lookAheadCharacter == '>'))
                        {
                            GetNextCharacter();
                        }

                        if(this.nextCharacter == '>')
                        {
                            GetNextCharacter();
                        }
                    }
                    else
                    {
                        while(!IsAtEndOfStream && this.nextCharacter != '>')
                        {
                            GetNextCharacter();
                        }

                        if(this.nextCharacter == '>')
                        {
                            GetNextCharacter();
                        }
                    }
                }

                if(!char.IsWhiteSpace(this.NextCharacter))
                {
                    break;
                }

                GetNextCharacter();
            }
        }

        #endregion
        #region 명칭 시작 적합 여부 구하기 - IsGoodForNameStart(character)

        /// <summary>
        /// 명칭 시작 적합 여부 구하기
        /// </summary>
        /// <param name="character">문자</param>
        /// <returns>명칭 시작 적합 여부</returns>
        private bool IsGoodForNameStart(char character)
        {
            return character == '_' || char.IsLetter(character);
        }

        #endregion
        #region 문자 결합 여부 구하기 - IsCombiningCharacter(character)

        /// <summary>
        /// 문자 결합 여부 구하기
        /// </summary>
        /// <param name="character">문자</param>
        /// <returns>문자 결합 여부</returns>
        private bool IsCombiningCharacter(char character)
        {
            return false;
        }

        #endregion
        #region 연장기 여부 구하기 - IsExtender(character)

        /// <summary>
        /// 연장기 여부 구하기
        /// </summary>
        /// <param name="character">문자</param>
        /// <returns>연장기 여부</returns>
        private bool IsExtender(char character)
        {
            return false;
        }

        #endregion
        #region 명칭 적합 여부 구하기 - IsGoodForName(character)

        /// <summary>
        /// 명칭 적합 여부 구하기
        /// </summary>
        /// <param name="character">문자</param>
        /// <returns>명칭 적합 여부</returns>
        private bool IsGoodForName(char character)
        {
            return this.IsGoodForNameStart(character) ||
                   character == '.'                   ||
                   character == '-'                   ||
                   character == ':'                   ||
                   char.IsDigit(character)            ||
                   IsCombiningCharacter(character)    ||
                   IsExtender(character);
        }

        #endregion
    }
}

 

▶ HTMLParser.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;

namespace TestProject
{
    /// <summary>
    /// HTML 구문 분석기
    /// </summary>
    internal class HTMLParser
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Internal

        #region Field

        /// <summary>
        /// XHTML_NAMESPACE
        /// </summary>
        internal const string XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml";

        /// <summary>
        /// HTML_HEADER
        /// </summary>
        internal const string HTML_HEADER = "Version:1.0\r\nStartHTML:{0:D10}\r\nEndHTML:{1:D10}\r\nStartFragment:{2:D10}\r\nEndFragment:{3:D10}\r\nStartSelection:{4:D10}\r\nEndSelection:{5:D10}\r\n";

        /// <summary>
        /// HTML_START_FRAGMENT_COMMENT
        /// </summary>
        internal const string HTML_START_FRAGMENT_COMMENT = "<!--StartFragment-->";

        /// <summary>
        /// HTML_END_FRAGMENT_COMMENT
        /// </summary>
        internal const string HTML_END_FRAGMENT_COMMENT = "<!--EndFragment-->";

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 분석기
        /// </summary>
        private HTMLLexicalAnalyzer analyzer;

        /// <summary>
        /// 문서
        /// </summary>
        private XmlDocument document;

        /// <summary>
        /// 개발 엘리먼트 스택
        /// </summary>
        private Stack<XmlElement> openedElementStack;

        /// <summary>
        /// 보류 인라인 엘리먼트 스택
        /// </summary>
        private Stack<XmlElement> pendingInlineElementStack;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 생성자 - HTMLParser(inputString)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="inputString">입력 문자열</param>
        private HTMLParser(string inputString)
        {
            this.document = new XmlDocument();

            this.openedElementStack = new Stack<XmlElement>();

            this.pendingInlineElementStack = new Stack<XmlElement>();

            this.analyzer = new HTMLLexicalAnalyzer(inputString);

            this.analyzer.GetNextContentToken();
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Internal

        #region HTML 구문 분석하기 - ParseHTML(html)

        /// <summary>
        /// HTML 구문 분석하기
        /// </summary>
        /// <param name="html">HTML</param>
        /// <returns>XML 엘리먼트</returns>
        internal static XmlElement ParseHTML(string html)
        {
            HTMLParser parser = new HTMLParser(html);

            XmlElement htmlRootElement = parser.ParseHTMLContent();

            return htmlRootElement;
        }

        #endregion
        #region 클립보드 데이터에서 HTML 추출하기 - ExtractHTMLFromClipboardData(htmlDataString)

        /// <summary>
        /// 클립보드 데이터에서 HTML 추출하기
        /// </summary>
        /// <param name="htmlDataString">HTML 데이터 문자열</param>
        /// <returns>HTML</returns>
        internal static string ExtractHTMLFromClipboardData(string htmlDataString)
        {
            int startHTMLIndex = htmlDataString.IndexOf("StartHTML:");

            if(startHTMLIndex < 0)
            {
                return "ERROR: Urecognized html header";
            }

            startHTMLIndex = int.Parse(htmlDataString.Substring(startHTMLIndex + "StartHTML:".Length, "0123456789".Length));

            if(startHTMLIndex < 0 || startHTMLIndex > htmlDataString.Length)
            {
                return "ERROR: Urecognized html header";
            }

            int endHTMLIndex = htmlDataString.IndexOf("EndHTML:");

            if(endHTMLIndex < 0)
            {
                return "ERROR: Urecognized html header";
            }

            endHTMLIndex = int.Parse(htmlDataString.Substring(endHTMLIndex + "EndHTML:".Length, "0123456789".Length));

            if(endHTMLIndex > htmlDataString.Length)
            {
                endHTMLIndex = htmlDataString.Length;
            }

            return htmlDataString.Substring(startHTMLIndex, endHTMLIndex - startHTMLIndex);
        }

        #endregion
        #region HTML 클립보드 헤더 추가하기 - AddHTMLClipboardHeader(htmlString)

        /// <summary>
        /// HTML 클립보드 헤더 추가하기
        /// </summary>
        /// <param name="htmlString">HTML 문자열</param>
        /// <returns>처리 결과</returns>
        internal static string AddHTMLClipboardHeader(string htmlString)
        {
            StringBuilder stringBuilder = new StringBuilder();

            int startHTML     = HTML_HEADER.Length + 6 * ("0123456789".Length - "{0:D10}".Length);
            int endHTML       = startHTML + htmlString.Length;
            int startFragment = htmlString.IndexOf(HTML_START_FRAGMENT_COMMENT, 0);

            if(startFragment >= 0)
            {
                startFragment = startHTML + startFragment + HTML_START_FRAGMENT_COMMENT.Length;
            }
            else
            {
                startFragment = startHTML;
            }

            int endFragment = htmlString.IndexOf(HTML_END_FRAGMENT_COMMENT, 0);

            if(endFragment >= 0)
            {
                endFragment = startHTML + endFragment;
            }
            else
            {
                endFragment = endHTML;
            }

            stringBuilder.AppendFormat
            (
                HTML_HEADER,
                startHTML,
                endHTML,
                startFragment,
                endFragment,
                startFragment,
                endFragment
            );

            stringBuilder.Append(htmlString);

            return stringBuilder.ToString();
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 불변 ASSERT 처리하기 - InvariantAssert(condition, message)

        /// <summary>
        /// 불변 ASSERT 처리하기
        /// </summary>
        /// <param name="condition">조건</param>
        /// <param name="message">메시지</param>
        private void InvariantAssert(bool condition, string message)
        {
            if(!condition)
            {
                throw new Exception("Assertion error: " + message);
            }
        }

        #endregion
        #region 구조화 엘리먼트 열기 - OpenStructuringElement(htmlElement)

        /// <summary>
        /// 구조화 엘리먼트 열기
        /// </summary>
        /// <param name="htmlElement">HTML 엘리먼트</param>
        private void OpenStructuringElement(XmlElement htmlElement)
        {
            if(HTMLSchema.IsBlockElement(htmlElement.LocalName))
            {
                while(this.openedElementStack.Count > 0 && HTMLSchema.IsInlineElement(this.openedElementStack.Peek().LocalName))
                {
                    XmlElement htmlInlineElement = this.openedElementStack.Pop();

                    InvariantAssert(this.openedElementStack.Count > 0, "OpenStructuringElement: stack of opened elements cannot become empty here");

                    this.pendingInlineElementStack.Push(CreateElementCopy(htmlInlineElement));
                }
            }

            if(this.openedElementStack.Count > 0)
            {
                XmlElement htmlParent = this.openedElementStack.Peek();

                if(HTMLSchema.ClosesOnNextElementStart(htmlParent.LocalName, htmlElement.LocalName))
                {
                    this.openedElementStack.Pop();

                    htmlParent = this.openedElementStack.Count > 0 ? this.openedElementStack.Peek() : null;
                }

                if(htmlParent != null)
                {
                    htmlParent.AppendChild(htmlElement);
                }
            }

            this.openedElementStack.Push(htmlElement);
        }

        #endregion
        #region 어트리뷰트 구문 분석하기 - ParseAttributes(xmlElement)

        /// <summary>
        /// 어트리뷰트 구문 분석하기
        /// </summary>
        /// <param name="xmlElement">XML 엘리먼트</param>
        private void ParseAttributes(XmlElement xmlElement)
        {
            while
            (
                this.analyzer.NextTokenType != HTMLTokenType.EOF    &&
                this.analyzer.NextTokenType != HTMLTokenType.TagEnd &&
                this.analyzer.NextTokenType != HTMLTokenType.EmptyTagEnd
            )
            {
                if(this.analyzer.NextTokenType == HTMLTokenType.Name)
                {
                    string attributeName = this.analyzer.NextToken;

                    this.analyzer.GetNextEqualSignToken();

                    this.analyzer.GetNextAtomToken();

                    string attributeValue = this.analyzer.NextToken;

                    xmlElement.SetAttribute(attributeName, attributeValue);
                }

                this.analyzer.GetNextTagToken();
            }
        }

        #endregion
        #region 빈 엘리먼트 추가하기 - AddEmptyElement(htmlEmptyElement)

        /// <summary>
        /// 빈 엘리먼트 추가하기
        /// </summary>
        /// <param name="htmlEmptyElement">HTML 빈 엘리먼트</param>
        private void AddEmptyElement(XmlElement htmlEmptyElement)
        {
            InvariantAssert
            (
                this.openedElementStack.Count > 0,
                "AddEmptyElement: Stack of opened elements cannot be empty, as we have at least one artificial root element"
            );

            XmlElement htmlParent = this.openedElementStack.Peek();

            htmlParent.AppendChild(htmlEmptyElement);
        }

        #endregion
        #region 인라인 엘리먼트 열기 - OpenInlineElement(htmlInlineElement)

        /// <summary>
        /// 인라인 엘리먼트 열기
        /// </summary>
        /// <param name="htmlInlineElement">HTML 인라인 엘리먼트</param>
        private void OpenInlineElement(XmlElement htmlInlineElement)
        {
            this.pendingInlineElementStack.Push(htmlInlineElement);
        }

        #endregion
        #region 엘리먼트 개방 여부 구하기 - IsElementOpened(htmlElementName)

        /// <summary>
        /// 엘리먼트 개방 여부 구하기
        /// </summary>
        /// <param name="htmlElementName">HTML 엘리먼트명</param>
        /// <returns>엘리먼트 개방 여부</returns>
        private bool IsElementOpened(string htmlElementName)
        {
            foreach(XmlElement openedElement in this.openedElementStack)
            {
                if(openedElement.LocalName == htmlElementName)
                {
                    return true;
                }
            }

            return false;
        }

        #endregion
        #region 복사 엘리먼트 생성하기 - CreateElementCopy(htmlElement)

        /// <summary>
        /// 복사 엘리먼트 생성하기
        /// </summary>
        /// <param name="htmlElement">HTML 엘리먼트</param>
        /// <returns>복사 생성 엘리먼트</returns>
        private XmlElement CreateElementCopy(XmlElement htmlElement)
        {
            XmlElement htmlElementCopy = this.document.CreateElement(htmlElement.LocalName, XHTML_NAMESPACE);

            for(int i = 0; i < htmlElement.Attributes.Count; i++)
            {
                XmlAttribute attribute = htmlElement.Attributes[i];

                htmlElementCopy.SetAttribute(attribute.Name, attribute.Value);
            }

            return htmlElementCopy;
        }

        #endregion
        #region 엘리먼트 닫기 - CloseElement(htmlElementName)

        /// <summary>
        /// 엘리먼트 닫기
        /// </summary>
        /// <param name="htmlElementName">HTML 엘리먼트명</param>
        private void CloseElement(string htmlElementName)
        {
            InvariantAssert
            (
                this.openedElementStack.Count > 0,
                "CloseElement: Stack of opened elements cannot be empty, as we have at least one artificial root element"
            );

            if(this.pendingInlineElementStack.Count > 0 && this.pendingInlineElementStack.Peek().LocalName == htmlElementName)
            {
                XmlElement htmlInlineElement = this.pendingInlineElementStack.Pop();

                InvariantAssert
                (
                    this.openedElementStack.Count > 0,
                    "CloseElement: Stack of opened elements cannot be empty, as we have at least one artificial root element"
                );

                XmlElement htmlParent = this.openedElementStack.Peek();

                htmlParent.AppendChild(htmlInlineElement);

                return;
            }
            else if(IsElementOpened(htmlElementName))
            {
                while(this.openedElementStack.Count > 1)
                {
                    XmlElement htmlOpenedElement = this.openedElementStack.Pop();

                    if(htmlOpenedElement.LocalName == htmlElementName)
                    {
                        return;
                    }

                    if(HTMLSchema.IsInlineElement(htmlOpenedElement.LocalName))
                    {
                        this.pendingInlineElementStack.Push(CreateElementCopy(htmlOpenedElement));
                    }
                }
            }

            return;
        }

        #endregion
        #region 보류 인라인 엘리먼트 열기 - OpenPendingInlineElements()

        /// <summary>
        /// 보류 인라인 엘리먼트 열기
        /// </summary>
        private void OpenPendingInlineElements()
        {
            if(this.pendingInlineElementStack.Count > 0)
            {
                XmlElement htmlInlineElement = this.pendingInlineElementStack.Pop();

                OpenPendingInlineElements();

                InvariantAssert
                (
                    this.openedElementStack.Count > 0,
                    "OpenPendingInlineElements: Stack of opened elements cannot be empty, as we have at least one artificial root element"
                );

                XmlElement htmlParent = this.openedElementStack.Peek();

                htmlParent.AppendChild(htmlInlineElement);

                this.openedElementStack.Push(htmlInlineElement);
            }
        }

        #endregion
        #region 텍스트 컨텐트 추가하기 - AddTextContent(textContent)

        /// <summary>
        /// 텍스트 컨텐트 추가하기
        /// </summary>
        /// <param name="textContent">텍스트 컨텐트</param>
        private void AddTextContent(string textContent)
        {
            OpenPendingInlineElements();

            InvariantAssert
            (
                this.openedElementStack.Count > 0,
                "AddTextContent: Stack of opened elements cannot be empty, as we have at least one artificial root element"
            );

            XmlElement htmlParent = this.openedElementStack.Peek();

            XmlText textNode = this.document.CreateTextNode(textContent);

            htmlParent.AppendChild(textNode);
        }

        #endregion
        #region 주석 추가하기 - AddComment(comment)

        /// <summary>
        /// 주석 추가하기
        /// </summary>
        /// <param name="comment">주석</param>
        private void AddComment(string comment)
        {
            OpenPendingInlineElements();

            InvariantAssert
            (
                this.openedElementStack.Count > 0,
                "AddComment: Stack of opened elements cannot be empty, as we have at least one artificial root element"
            );

            XmlElement htmlParent = this.openedElementStack.Peek();

            XmlComment xmlComment = this.document.CreateComment(comment);

            htmlParent.AppendChild(xmlComment);
        }

        #endregion
        #region HTML 컨텐트 구문 분석하기 - ParseHTMLContent()

        /// <summary>
        /// HTML 컨텐트 구문 분석하기
        /// </summary>
        /// <returns>XML 엘리먼트</returns>
        private XmlElement ParseHTMLContent()
        {
            XmlElement htmlRootElement = this.document.CreateElement("html", XHTML_NAMESPACE);

            OpenStructuringElement(htmlRootElement);

            while(this.analyzer.NextTokenType != HTMLTokenType.EOF)
            {
                if(this.analyzer.NextTokenType == HTMLTokenType.OpeningTagStart)
                {
                    this.analyzer.GetNextTagToken();

                    if(this.analyzer.NextTokenType == HTMLTokenType.Name)
                    {
                        string htmlElementName = this.analyzer.NextToken.ToLower();

                        this.analyzer.GetNextTagToken();

                        XmlElement htmlElement = this.document.CreateElement(htmlElementName, XHTML_NAMESPACE);

                        ParseAttributes(htmlElement);

                        if(this.analyzer.NextTokenType == HTMLTokenType.EmptyTagEnd || HTMLSchema.IsEmptyElement(htmlElementName))
                        {
                            AddEmptyElement(htmlElement);
                        }
                        else if(HTMLSchema.IsInlineElement(htmlElementName))
                        {
                            OpenInlineElement(htmlElement);
                        }
                        else if(HTMLSchema.IsBlockElement(htmlElementName) || HTMLSchema.IsKnownOpenableElement(htmlElementName))
                        {
                            OpenStructuringElement(htmlElement);
                        }
                    }
                }
                else if(this.analyzer.NextTokenType == HTMLTokenType.ClosingTagStart)
                {
                    this.analyzer.GetNextTagToken();

                    if(this.analyzer.NextTokenType == HTMLTokenType.Name)
                    {
                        string htmlElementName = this.analyzer.NextToken.ToLower();

                        this.analyzer.GetNextTagToken();

                        CloseElement(htmlElementName);
                    }
                }
                else if(this.analyzer.NextTokenType == HTMLTokenType.Text)
                {
                    AddTextContent(this.analyzer.NextToken);
                }
                else if (this.analyzer.NextTokenType == HTMLTokenType.Comment)
                {
                    AddComment(this.analyzer.NextToken);
                }

                this.analyzer.GetNextContentToken();
            }

            if
            (
                htmlRootElement.FirstChild is XmlElement                &&
                htmlRootElement.FirstChild == htmlRootElement.LastChild &&
                htmlRootElement.FirstChild.LocalName.ToLower() == "html"
            )
            {
                htmlRootElement = (XmlElement)htmlRootElement.FirstChild;
            }

            return htmlRootElement;
        }

        #endregion
    }
}

 

▶ HTMLSchema.cs

using System.Collections;

namespace TestProject
{
    /// <summary>
    /// HTML 스키마
    /// </summary>
    internal class HTMLSchema
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// HTML 인라인 엘리먼트 리스트
        /// </summary>
        private static ArrayList _htmlInlineElementList;

        /// <summary>
        /// HTML 블럭 엘리먼트 리스트
        /// </summary>
        private static ArrayList _htmlBlockElementList;

        /// <summary>
        /// HTML 다른 개방 가능한 엘리먼트 리스트
        /// </summary>
        private static ArrayList _htmlOtherOpenableElementList;

        /// <summary>
        /// HTML 빈 엘리먼트 리스트
        /// </summary>
        private static ArrayList _htmlEmptyElementList;

        /// <summary>
        /// 부모 엘리먼트 종료에서 폐쇄시 HTML 엘리먼트 리스트
        /// </summary>
        private static ArrayList _htmlElementListClosingOnParentElementEnd;

        /// <summary>
        /// 컬럼 그룹 폐쇄시 HTML 엘리먼트 리스트
        /// </summary>
        private static ArrayList _htmlElementListClosingColumnGroup;

        /// <summary>
        /// DD 폐쇄시 HTML 엘리먼트 리스트
        /// </summary>
        private static ArrayList _htmlElementListClosingDD;

        /// <summary>
        /// DT 폐쇄시 HTML 엘리먼트 리스트
        /// </summary>
        private static ArrayList _htmlElementListClosingDT;

        /// <summary>
        /// LI 폐쇄시 HTML 엘리먼트 리스트
        /// </summary>
        private static ArrayList _htmlElementListClosingLI;

        /// <summary>
        /// 테이블 바디 폐쇄시 HTML 엘리먼트 리스트
        /// </summary>
        private static ArrayList _htmlElementListClosingTableBody;

        /// <summary>
        /// TD 폐쇄시 HTML 엘리먼트 리스트
        /// </summary>
        private static ArrayList _htmlElementListClosingTD;

        /// <summary>
        /// 테이블 FOOTER 폐쇄시 HTML 엘리먼트 리스트
        /// </summary>
        private static ArrayList _htmlElementListClosingTableFoot;

        /// <summary>
        /// 테이블 헤드 폐쇄시 HTML 엘리먼트 리스트
        /// </summary>
        private static ArrayList _htmlElementListClosingTableHead;

        /// <summary>
        /// 테이블 헤더 폐쇄시 HTML 엘리먼트 리스트
        /// </summary>
        private static ArrayList _htmlElementListClosingTableHeader;

        /// <summary>
        /// 테이블 행 페쇄시 HTML 엘리먼트 리스트
        /// </summary>
        private static ArrayList _htmlElementListClosingTableRow;

        /// <summary>
        /// HTML 문자 개체 테이블
        /// </summary>
        private static Hashtable _htmlCharacterEntityTable;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Static

        #region 생성자 - HTMLSchema()

        /// <summary>
        /// 생성자
        /// </summary>
        static HTMLSchema()
        {
            InitializeInlineElementList();

            InitializeBlockElementList();

            InitializeOtherOpenableElementList();

            InitializeEmptyElementList();

            InitializeElementListClosingParentElementEnd();

            InitializeElementListClosingNewElementStart();

            InitializeHTMLCharacterEntityTable();
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Internal

        #region 빈 엘리먼트 여부 구하기 - IsEmptyElement(xmlElementName)

        /// <summary>
        /// 빈 엘리먼트 여부 구하기
        /// </summary>
        /// <param name="xmlElementName">XML 엘리먼트명</param>
        /// <returns>빈 엘리먼트 여부</returns>
        internal static bool IsEmptyElement(string xmlElementName)
        {
            return _htmlEmptyElementList.Contains(xmlElementName.ToLower());
        }

        #endregion
        #region 블럭 엘리먼트 여부 구하기 - IsBlockElement(xmlElementName)

        /// <summary>
        /// 블럭 엘리먼트 여부 구하기
        /// </summary>
        /// <param name="xmlElementName">XML 엘리먼트명</param>
        /// <returns>블럭 엘리먼트 여부</returns>
        internal static bool IsBlockElement(string xmlElementName)
        {
            return _htmlBlockElementList.Contains(xmlElementName);
        }

        #endregion
        #region 인라인 엘리먼트 여부 구하기 - IsInlineElement(xmlElementName)

        /// <summary>
        /// 인라인 엘리먼트 여부 구하기
        /// </summary>
        /// <param name="xmlElementName">XML 엘리먼트명</param>
        /// <returns>인라인 엘리먼트 여부</returns>
        internal static bool IsInlineElement(string xmlElementName)
        {
            return _htmlInlineElementList.Contains(xmlElementName);
        }

        #endregion
        #region 알 수 있는 개발 가능 엘리먼트 여부 구하기 - IsKnownOpenableElement(xmlElementName)

        /// <summary>
        /// 알 수 있는 개발 가능 엘리먼트 여부 구하기
        /// </summary>
        /// <param name="xmlElementName">XML 엘리먼트명</param>
        /// <returns>알 수 있는 개발 가능 엘리먼트 여부</returns>
        internal static bool IsKnownOpenableElement(string xmlElementName)
        {
            return _htmlOtherOpenableElementList.Contains(xmlElementName);
        }

        #endregion
        #region 부모 엘리먼트 종료에서 닫힘 여부 구하기 - ClosesOnParentElementEnd(string xmlElementName)

        /// <summary>
        /// 부모 엘리먼트 종료에서 닫힘 여부 구하기
        /// </summary>
        /// <param name="xmlElementName">XML 엘리먼트명</param>
        /// <returns>부모 엘리먼트 종료에서 닫힘 여부</returns>
        internal static bool ClosesOnParentElementEnd(string xmlElementName)
        {
            return _htmlElementListClosingOnParentElementEnd.Contains(xmlElementName.ToLower());
        }

        #endregion
        #region 다음 엘리먼트 시작에서 닫힘 여부 구하기 - ClosesOnNextElementStart(currentElementName, nextElementName)

        /// <summary>
        /// 다음 엘리먼트 시작에서 닫힘 여부 구하기
        /// </summary>
        /// <param name="currentElementName">현재 엘리먼트명</param>
        /// <param name="nextElementName">다음 엘리먼트명</param>
        /// <returns>다음 엘리먼트 시작에서 닫힘 여부</returns>
        internal static bool ClosesOnNextElementStart(string currentElementName, string nextElementName)
        {
            switch(currentElementName)
            {
                case "colgroup" :

                    return _htmlElementListClosingColumnGroup.Contains(nextElementName) && HTMLSchema.IsBlockElement(nextElementName);

                case "dd" :

                    return _htmlElementListClosingDD.Contains(nextElementName) && HTMLSchema.IsBlockElement(nextElementName);

                case "dt" :

                    return _htmlElementListClosingDT.Contains(nextElementName) && HTMLSchema.IsBlockElement(nextElementName);

                case "li" :

                    return _htmlElementListClosingLI.Contains(nextElementName);

                case "p" :

                    return HTMLSchema.IsBlockElement(nextElementName);

                case "tbody" :

                    return _htmlElementListClosingTableBody.Contains(nextElementName);

                case "tfoot" :

                    return _htmlElementListClosingTableFoot.Contains(nextElementName);

                case "thead" :

                    return _htmlElementListClosingTableHead.Contains(nextElementName);

                case "tr" :

                    return _htmlElementListClosingTableRow.Contains(nextElementName);

                case "td" :

                    return _htmlElementListClosingTD.Contains(nextElementName);

                case "th" :

                    return _htmlElementListClosingTableHeader.Contains(nextElementName);
            }

            return false;
        }

        #endregion
        #region 개체 여부 구하기 - IsEntity(entityName)

        /// <summary>
        /// 개체 여부 구하기
        /// </summary>
        /// <param name="entityName">개체명</param>
        /// <returns>개체 여부</returns>
        internal static bool IsEntity(string entityName)
        {
            if(_htmlCharacterEntityTable.Contains(entityName))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        #endregion
        #region 개체 문자 값 구하기 - GetEntityCharacterValue(entityName)

        /// <summary>
        /// 개체 문자 값 구하기
        /// </summary>
        /// <param name="entityName">개체명</param>
        /// <returns>개체 문자 값</returns>
        internal static char GetEntityCharacterValue(string entityName)
        {
            if(_htmlCharacterEntityTable.Contains(entityName))
            {
                return (char)_htmlCharacterEntityTable[entityName];
            }
            else
            {
                return (char)0;
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////// Private

        #region 인라인 엘리먼트 리스트 초기화하기 - InitializeInlineElementList()

        /// <summary>
        /// 인라인 엘리먼트 리스트 초기화하기
        /// </summary>
        private static void InitializeInlineElementList()
        {
            _htmlInlineElementList = new ArrayList();

            _htmlInlineElementList.Add("a"      );
            _htmlInlineElementList.Add("abbr"   );
            _htmlInlineElementList.Add("acronym");
            _htmlInlineElementList.Add("address");
            _htmlInlineElementList.Add("b"      );
            _htmlInlineElementList.Add("bdo"    );
            _htmlInlineElementList.Add("big"    );
            _htmlInlineElementList.Add("button" );
            _htmlInlineElementList.Add("code"   );
            _htmlInlineElementList.Add("del"    );
            _htmlInlineElementList.Add("dfn"    );
            _htmlInlineElementList.Add("em"     );
            _htmlInlineElementList.Add("font"   );
            _htmlInlineElementList.Add("i"      );
            _htmlInlineElementList.Add("ins"    );
            _htmlInlineElementList.Add("kbd"    );
            _htmlInlineElementList.Add("label"  );
            _htmlInlineElementList.Add("legend" );
            _htmlInlineElementList.Add("q"      );
            _htmlInlineElementList.Add("s"      );
            _htmlInlineElementList.Add("samp"   );
            _htmlInlineElementList.Add("small"  );
            _htmlInlineElementList.Add("span"   );
            _htmlInlineElementList.Add("strike" );
            _htmlInlineElementList.Add("strong" );
            _htmlInlineElementList.Add("sub"    );
            _htmlInlineElementList.Add("sup"    );
            _htmlInlineElementList.Add("u"      );
            _htmlInlineElementList.Add("var"    );
        }

        #endregion
        #region 블럭 엘리먼트 리스트 초기화하기 - InitializeBlockElementList()

        /// <summary>
        /// 블럭 엘리먼트 리스트 초기화하기
        /// </summary>
        private static void InitializeBlockElementList()
        {
            _htmlBlockElementList = new ArrayList();

            _htmlBlockElementList.Add("blockquote");
            _htmlBlockElementList.Add("body"      );
            _htmlBlockElementList.Add("caption"   );
            _htmlBlockElementList.Add("center"    );
            _htmlBlockElementList.Add("cite"      );
            _htmlBlockElementList.Add("dd"        );
            _htmlBlockElementList.Add("dir"       );
            _htmlBlockElementList.Add("div"       );
            _htmlBlockElementList.Add("dl"        );
            _htmlBlockElementList.Add("dt"        );
            _htmlBlockElementList.Add("form"      );
            _htmlBlockElementList.Add("h1"        );
            _htmlBlockElementList.Add("h2"        );
            _htmlBlockElementList.Add("h3"        );
            _htmlBlockElementList.Add("h4"        );
            _htmlBlockElementList.Add("h5"        );
            _htmlBlockElementList.Add("h6"        );
            _htmlBlockElementList.Add("html"      );
            _htmlBlockElementList.Add("li"        );
            _htmlBlockElementList.Add("menu"      );
            _htmlBlockElementList.Add("ol"        );
            _htmlBlockElementList.Add("p"         );
            _htmlBlockElementList.Add("pre"       );
            _htmlBlockElementList.Add("table"     );
            _htmlBlockElementList.Add("tbody"     );
            _htmlBlockElementList.Add("td"        );
            _htmlBlockElementList.Add("textarea"  );
            _htmlBlockElementList.Add("tfoot"     );
            _htmlBlockElementList.Add("th"        );
            _htmlBlockElementList.Add("thead"     );
            _htmlBlockElementList.Add("tr"        );
            _htmlBlockElementList.Add("tt"        );
            _htmlBlockElementList.Add("ul"        );
        }

        #endregion
        #region 다른 개방 가능한 엘리먼트 리스트 초기화하기 - InitializeOtherOpenableElementList()

        /// <summary>
        /// 다른 개방 가능한 엘리먼트 리스트 초기화하기
        /// </summary>
        private static void InitializeOtherOpenableElementList()
        {
            _htmlOtherOpenableElementList = new ArrayList();

            _htmlOtherOpenableElementList.Add("applet"  );
            _htmlOtherOpenableElementList.Add("base"    );
            _htmlOtherOpenableElementList.Add("basefont");
            _htmlOtherOpenableElementList.Add("colgroup");
            _htmlOtherOpenableElementList.Add("fieldset");
            _htmlOtherOpenableElementList.Add("frameset");
            _htmlOtherOpenableElementList.Add("head"    );
            _htmlOtherOpenableElementList.Add("iframe"  );
            _htmlOtherOpenableElementList.Add("map"     );
            _htmlOtherOpenableElementList.Add("noframes");
            _htmlOtherOpenableElementList.Add("noscript");
            _htmlOtherOpenableElementList.Add("object"  );
            _htmlOtherOpenableElementList.Add("optgroup");
            _htmlOtherOpenableElementList.Add("option"  );
            _htmlOtherOpenableElementList.Add("script"  );
            _htmlOtherOpenableElementList.Add("select"  );
            _htmlOtherOpenableElementList.Add("style"   );
            _htmlOtherOpenableElementList.Add("title"   );
        }

        #endregion
        #region 빈 엘리먼트 리스트 초기화하기 - InitializeEmptyElementList()

        /// <summary>
        /// 빈 엘리먼트 리스트 초기화하기
        /// </summary>
        private static void InitializeEmptyElementList()
        {
            _htmlEmptyElementList = new ArrayList();

            _htmlEmptyElementList.Add("area"    );
            _htmlEmptyElementList.Add("base"    );
            _htmlEmptyElementList.Add("basefont");
            _htmlEmptyElementList.Add("br"      );
            _htmlEmptyElementList.Add("col"     );
            _htmlEmptyElementList.Add("frame"   );
            _htmlEmptyElementList.Add("hr"      );
            _htmlEmptyElementList.Add("img"     );
            _htmlEmptyElementList.Add("input"   );
            _htmlEmptyElementList.Add("isindex" );
            _htmlEmptyElementList.Add("link"    );
            _htmlEmptyElementList.Add("meta"    );
            _htmlEmptyElementList.Add("param"   );
        }

        #endregion
        #region 부모 엘리먼트 종료 폐쇄시 엘리먼트 리스트 초기화하기 - InitializeElementListClosingParentElementEnd()

        /// <summary>
        /// 부모 엘리먼트 종료 폐쇄시 엘리먼트 리스트 초기화하기
        /// </summary>
        private static void InitializeElementListClosingParentElementEnd()
        {
            _htmlElementListClosingOnParentElementEnd = new ArrayList();

            _htmlElementListClosingOnParentElementEnd.Add("body"    );
            _htmlElementListClosingOnParentElementEnd.Add("colgroup");
            _htmlElementListClosingOnParentElementEnd.Add("dd"      );
            _htmlElementListClosingOnParentElementEnd.Add("dt"      );
            _htmlElementListClosingOnParentElementEnd.Add("head"    );
            _htmlElementListClosingOnParentElementEnd.Add("html"    );
            _htmlElementListClosingOnParentElementEnd.Add("li"      );
            _htmlElementListClosingOnParentElementEnd.Add("p"       );
            _htmlElementListClosingOnParentElementEnd.Add("tbody"   );
            _htmlElementListClosingOnParentElementEnd.Add("td"      );
            _htmlElementListClosingOnParentElementEnd.Add("tfoot"   );
            _htmlElementListClosingOnParentElementEnd.Add("thead"   );
            _htmlElementListClosingOnParentElementEnd.Add("th"      );
            _htmlElementListClosingOnParentElementEnd.Add("tr"      );
        }

        #endregion
        #region 신규 엘리먼트 시작 폐쇄시 엘리먼트 리스트 초기화하기 - InitializeElementListClosingNewElementStart()

        /// <summary>
        /// 신규 엘리먼트 시작 폐쇄시 엘리먼트 리스트 초기화하기
        /// </summary>
        private static void InitializeElementListClosingNewElementStart()
        {
            _htmlElementListClosingColumnGroup = new ArrayList();

            _htmlElementListClosingColumnGroup.Add("colgroup");
            _htmlElementListClosingColumnGroup.Add("tr"      );
            _htmlElementListClosingColumnGroup.Add("thead"   );
            _htmlElementListClosingColumnGroup.Add("tfoot"   );
            _htmlElementListClosingColumnGroup.Add("tbody"   );

            _htmlElementListClosingDD = new ArrayList();

            _htmlElementListClosingDD.Add("dd");
            _htmlElementListClosingDD.Add("dt");

            _htmlElementListClosingDT = new ArrayList();

            _htmlElementListClosingDD.Add("dd");
            _htmlElementListClosingDD.Add("dt");

            _htmlElementListClosingLI = new ArrayList();

            _htmlElementListClosingLI.Add("li");

            _htmlElementListClosingTableBody = new ArrayList();

            _htmlElementListClosingTableBody.Add("tbody");
            _htmlElementListClosingTableBody.Add("thead");
            _htmlElementListClosingTableBody.Add("tfoot");

            _htmlElementListClosingTableRow = new ArrayList();

            _htmlElementListClosingTableRow.Add("thead");
            _htmlElementListClosingTableRow.Add("tfoot");
            _htmlElementListClosingTableRow.Add("tbody");
            _htmlElementListClosingTableRow.Add("tr"   );

            _htmlElementListClosingTD = new ArrayList();

            _htmlElementListClosingTD.Add("td"   );
            _htmlElementListClosingTD.Add("th"   );
            _htmlElementListClosingTD.Add("tr"   );
            _htmlElementListClosingTD.Add("tbody");
            _htmlElementListClosingTD.Add("tfoot");
            _htmlElementListClosingTD.Add("thead");

            _htmlElementListClosingTableHeader = new ArrayList();

            _htmlElementListClosingTableHeader.Add("td"   );
            _htmlElementListClosingTableHeader.Add("th"   );
            _htmlElementListClosingTableHeader.Add("tr"   );
            _htmlElementListClosingTableHeader.Add("tbody");
            _htmlElementListClosingTableHeader.Add("tfoot");
            _htmlElementListClosingTableHeader.Add("thead");

            _htmlElementListClosingTableHead = new ArrayList();

            _htmlElementListClosingTableHead.Add("tbody");
            _htmlElementListClosingTableHead.Add("tfoot");

            _htmlElementListClosingTableFoot = new ArrayList();

            _htmlElementListClosingTableFoot.Add("tbody");
            _htmlElementListClosingTableFoot.Add("thead");
        }

        #endregion
        #region HTML 문자 개체 테이블 초기화하기 - InitializeHTMLCharacterEntityTable()

        /// <summary>
        /// HTML 문자 개체 테이블 초기화하기
        /// </summary>
        private static void InitializeHTMLCharacterEntityTable()
        {
            _htmlCharacterEntityTable = new Hashtable();

            _htmlCharacterEntityTable["Aacute"  ] = (char)193;
            _htmlCharacterEntityTable["aacute"  ] = (char)225;
            _htmlCharacterEntityTable["Acirc"   ] = (char)194;
            _htmlCharacterEntityTable["acirc"   ] = (char)226;
            _htmlCharacterEntityTable["acute"   ] = (char)180;
            _htmlCharacterEntityTable["AElig"   ] = (char)198;
            _htmlCharacterEntityTable["aelig"   ] = (char)230;
            _htmlCharacterEntityTable["Agrave"  ] = (char)192;
            _htmlCharacterEntityTable["agrave"  ] = (char)224;
            _htmlCharacterEntityTable["alefsym" ] = (char)8501;
            _htmlCharacterEntityTable["Alpha"   ] = (char)913;
            _htmlCharacterEntityTable["alpha"   ] = (char)945;
            _htmlCharacterEntityTable["amp"     ] = (char)38;
            _htmlCharacterEntityTable["and"     ] = (char)8743;
            _htmlCharacterEntityTable["ang"     ] = (char)8736;
            _htmlCharacterEntityTable["Aring"   ] = (char)197;
            _htmlCharacterEntityTable["aring"   ] = (char)229;
            _htmlCharacterEntityTable["asymp"   ] = (char)8776;
            _htmlCharacterEntityTable["Atilde"  ] = (char)195;
            _htmlCharacterEntityTable["atilde"  ] = (char)227;
            _htmlCharacterEntityTable["Auml"    ] = (char)196;
            _htmlCharacterEntityTable["auml"    ] = (char)228;
            _htmlCharacterEntityTable["bdquo"   ] = (char)8222;
            _htmlCharacterEntityTable["Beta"    ] = (char)914;
            _htmlCharacterEntityTable["beta"    ] = (char)946;
            _htmlCharacterEntityTable["brvbar"  ] = (char)166;
            _htmlCharacterEntityTable["bull"    ] = (char)8226;
            _htmlCharacterEntityTable["cap"     ] = (char)8745;
            _htmlCharacterEntityTable["Ccedil"  ] = (char)199;
            _htmlCharacterEntityTable["ccedil"  ] = (char)231;
            _htmlCharacterEntityTable["cent"    ] = (char)162;
            _htmlCharacterEntityTable["Chi"     ] = (char)935;
            _htmlCharacterEntityTable["chi"     ] = (char)967;
            _htmlCharacterEntityTable["circ"    ] = (char)710;
            _htmlCharacterEntityTable["clubs"   ] = (char)9827;
            _htmlCharacterEntityTable["cong"    ] = (char)8773;
            _htmlCharacterEntityTable["copy"    ] = (char)169;
            _htmlCharacterEntityTable["crarr"   ] = (char)8629;
            _htmlCharacterEntityTable["cup"     ] = (char)8746;
            _htmlCharacterEntityTable["curren"  ] = (char)164;
            _htmlCharacterEntityTable["dagger"  ] = (char)8224;
            _htmlCharacterEntityTable["Dagger"  ] = (char)8225;
            _htmlCharacterEntityTable["darr"    ] = (char)8595;
            _htmlCharacterEntityTable["dArr"    ] = (char)8659;
            _htmlCharacterEntityTable["deg"     ] = (char)176;
            _htmlCharacterEntityTable["Delta"   ] = (char)916;
            _htmlCharacterEntityTable["delta"   ] = (char)948;
            _htmlCharacterEntityTable["diams"   ] = (char)9830;
            _htmlCharacterEntityTable["divide"  ] = (char)247;
            _htmlCharacterEntityTable["Eacute"  ] = (char)201;
            _htmlCharacterEntityTable["eacute"  ] = (char)233;
            _htmlCharacterEntityTable["Ecirc"   ] = (char)202;
            _htmlCharacterEntityTable["ecirc"   ] = (char)234;
            _htmlCharacterEntityTable["Egrave"  ] = (char)200;
            _htmlCharacterEntityTable["egrave"  ] = (char)232;
            _htmlCharacterEntityTable["empty"   ] = (char)8709;
            _htmlCharacterEntityTable["emsp"    ] = (char)8195;
            _htmlCharacterEntityTable["ensp"    ] = (char)8194;
            _htmlCharacterEntityTable["Epsilon" ] = (char)917;
            _htmlCharacterEntityTable["epsilon" ] = (char)949;
            _htmlCharacterEntityTable["equiv"   ] = (char)8801;
            _htmlCharacterEntityTable["Eta"     ] = (char)919;
            _htmlCharacterEntityTable["eta"     ] = (char)951;
            _htmlCharacterEntityTable["ETH"     ] = (char)208;
            _htmlCharacterEntityTable["eth"     ] = (char)240;
            _htmlCharacterEntityTable["Euml"    ] = (char)203;
            _htmlCharacterEntityTable["euml"    ] = (char)235;
            _htmlCharacterEntityTable["euro"    ] = (char)8364;
            _htmlCharacterEntityTable["exist"   ] = (char)8707;
            _htmlCharacterEntityTable["fnof"    ] = (char)402;
            _htmlCharacterEntityTable["forall"  ] = (char)8704;
            _htmlCharacterEntityTable["frac12"  ] = (char)189;
            _htmlCharacterEntityTable["frac14"  ] = (char)188;
            _htmlCharacterEntityTable["frac34"  ] = (char)190;
            _htmlCharacterEntityTable["frasl"   ] = (char)8260;
            _htmlCharacterEntityTable["Gamma"   ] = (char)915;
            _htmlCharacterEntityTable["gamma"   ] = (char)947;
            _htmlCharacterEntityTable["ge"      ] = (char)8805;
            _htmlCharacterEntityTable["gt"      ] = (char)62;
            _htmlCharacterEntityTable["harr"    ] = (char)8596;
            _htmlCharacterEntityTable["hArr"    ] = (char)8660;
            _htmlCharacterEntityTable["hearts"  ] = (char)9829;
            _htmlCharacterEntityTable["hellip"  ] = (char)8230;
            _htmlCharacterEntityTable["Iacute"  ] = (char)205;
            _htmlCharacterEntityTable["iacute"  ] = (char)237;
            _htmlCharacterEntityTable["Icirc"   ] = (char)206;
            _htmlCharacterEntityTable["icirc"   ] = (char)238;
            _htmlCharacterEntityTable["iexcl"   ] = (char)161;
            _htmlCharacterEntityTable["Igrave"  ] = (char)204;
            _htmlCharacterEntityTable["igrave"  ] = (char)236;
            _htmlCharacterEntityTable["image"   ] = (char)8465;
            _htmlCharacterEntityTable["infin"   ] = (char)8734;
            _htmlCharacterEntityTable["int"     ] = (char)8747;
            _htmlCharacterEntityTable["Iota"    ] = (char)921;
            _htmlCharacterEntityTable["iota"    ] = (char)953;
            _htmlCharacterEntityTable["iquest"  ] = (char)191;
            _htmlCharacterEntityTable["isin"    ] = (char)8712;
            _htmlCharacterEntityTable["Iuml"    ] = (char)207;
            _htmlCharacterEntityTable["iuml"    ] = (char)239;
            _htmlCharacterEntityTable["Kappa"   ] = (char)922;
            _htmlCharacterEntityTable["kappa"   ] = (char)954;
            _htmlCharacterEntityTable["Lambda"  ] = (char)923;
            _htmlCharacterEntityTable["lambda"  ] = (char)955;
            _htmlCharacterEntityTable["lang"    ] = (char)9001;
            _htmlCharacterEntityTable["laquo"   ] = (char)171;
            _htmlCharacterEntityTable["larr"    ] = (char)8592;
            _htmlCharacterEntityTable["lArr"    ] = (char)8656;
            _htmlCharacterEntityTable["lceil"   ] = (char)8968;
            _htmlCharacterEntityTable["ldquo"   ] = (char)8220;
            _htmlCharacterEntityTable["le"      ] = (char)8804;
            _htmlCharacterEntityTable["lfloor"  ] = (char)8970;
            _htmlCharacterEntityTable["lowast"  ] = (char)8727;
            _htmlCharacterEntityTable["loz"     ] = (char)9674;
            _htmlCharacterEntityTable["lrm"     ] = (char)8206;
            _htmlCharacterEntityTable["lsaquo"  ] = (char)8249;
            _htmlCharacterEntityTable["lsquo"   ] = (char)8216;
            _htmlCharacterEntityTable["lt"      ] = (char)60;
            _htmlCharacterEntityTable["macr"    ] = (char)175;
            _htmlCharacterEntityTable["mdash"   ] = (char)8212;
            _htmlCharacterEntityTable["micro"   ] = (char)181;
            _htmlCharacterEntityTable["middot"  ] = (char)183;
            _htmlCharacterEntityTable["minus"   ] = (char)8722;
            _htmlCharacterEntityTable["Mu"      ] = (char)924;
            _htmlCharacterEntityTable["mu"      ] = (char)956;
            _htmlCharacterEntityTable["nabla"   ] = (char)8711;
            _htmlCharacterEntityTable["nbsp"    ] = (char)160;
            _htmlCharacterEntityTable["ndash"   ] = (char)8211;
            _htmlCharacterEntityTable["ne"      ] = (char)8800;
            _htmlCharacterEntityTable["ni"      ] = (char)8715;
            _htmlCharacterEntityTable["not"     ] = (char)172;
            _htmlCharacterEntityTable["notin"   ] = (char)8713;
            _htmlCharacterEntityTable["nsub"    ] = (char)8836;
            _htmlCharacterEntityTable["Ntilde"  ] = (char)209;
            _htmlCharacterEntityTable["ntilde"  ] = (char)241;
            _htmlCharacterEntityTable["Nu"      ] = (char)925;
            _htmlCharacterEntityTable["nu"      ] = (char)957;
            _htmlCharacterEntityTable["Oacute"  ] = (char)211;
            _htmlCharacterEntityTable["ocirc"   ] = (char)244;
            _htmlCharacterEntityTable["OElig"   ] = (char)338;
            _htmlCharacterEntityTable["oelig"   ] = (char)339;
            _htmlCharacterEntityTable["Ograve"  ] = (char)210;
            _htmlCharacterEntityTable["ograve"  ] = (char)242;
            _htmlCharacterEntityTable["oline"   ] = (char)8254;
            _htmlCharacterEntityTable["Omega"   ] = (char)937;
            _htmlCharacterEntityTable["omega"   ] = (char)969;
            _htmlCharacterEntityTable["Omicron" ] = (char)927;
            _htmlCharacterEntityTable["omicron" ] = (char)959;
            _htmlCharacterEntityTable["oplus"   ] = (char)8853;
            _htmlCharacterEntityTable["or"      ] = (char)8744;
            _htmlCharacterEntityTable["ordf"    ] = (char)170;
            _htmlCharacterEntityTable["ordm"    ] = (char)186;
            _htmlCharacterEntityTable["Oslash"  ] = (char)216;
            _htmlCharacterEntityTable["oslash"  ] = (char)248;
            _htmlCharacterEntityTable["Otilde"  ] = (char)213;
            _htmlCharacterEntityTable["otilde"  ] = (char)245;
            _htmlCharacterEntityTable["otimes"  ] = (char)8855;
            _htmlCharacterEntityTable["Ouml"    ] = (char)214;
            _htmlCharacterEntityTable["ouml"    ] = (char)246;
            _htmlCharacterEntityTable["para"    ] = (char)182;
            _htmlCharacterEntityTable["part"    ] = (char)8706;
            _htmlCharacterEntityTable["permil"  ] = (char)8240;
            _htmlCharacterEntityTable["perp"    ] = (char)8869;
            _htmlCharacterEntityTable["Phi"     ] = (char)934;
            _htmlCharacterEntityTable["phi"     ] = (char)966;
            _htmlCharacterEntityTable["pi"      ] = (char)960;
            _htmlCharacterEntityTable["piv"     ] = (char)982;
            _htmlCharacterEntityTable["plusmn"  ] = (char)177;
            _htmlCharacterEntityTable["pound"   ] = (char)163;
            _htmlCharacterEntityTable["prime"   ] = (char)8242;
            _htmlCharacterEntityTable["Prime"   ] = (char)8243;
            _htmlCharacterEntityTable["prod"    ] = (char)8719;
            _htmlCharacterEntityTable["prop"    ] = (char)8733;
            _htmlCharacterEntityTable["Psi"     ] = (char)936;
            _htmlCharacterEntityTable["psi"     ] = (char)968;
            _htmlCharacterEntityTable["quot"    ] = (char)34;
            _htmlCharacterEntityTable["radic"   ] = (char)8730;
            _htmlCharacterEntityTable["rang"    ] = (char)9002;
            _htmlCharacterEntityTable["raquo"   ] = (char)187;
            _htmlCharacterEntityTable["rarr"    ] = (char)8594;
            _htmlCharacterEntityTable["rArr"    ] = (char)8658;
            _htmlCharacterEntityTable["rceil"   ] = (char)8969;
            _htmlCharacterEntityTable["rdquo"   ] = (char)8221;
            _htmlCharacterEntityTable["real"    ] = (char)8476;
            _htmlCharacterEntityTable["reg"     ] = (char)174;
            _htmlCharacterEntityTable["rfloor"  ] = (char)8971;
            _htmlCharacterEntityTable["Rho"     ] = (char)929;
            _htmlCharacterEntityTable["rho"     ] = (char)961;
            _htmlCharacterEntityTable["rlm"     ] = (char)8207;
            _htmlCharacterEntityTable["rsaquo"  ] = (char)8250;
            _htmlCharacterEntityTable["rsquo"   ] = (char)8217;
            _htmlCharacterEntityTable["sbquo"   ] = (char)8218;
            _htmlCharacterEntityTable["Scaron"  ] = (char)352;
            _htmlCharacterEntityTable["scaron"  ] = (char)353;
            _htmlCharacterEntityTable["sdot"    ] = (char)8901;
            _htmlCharacterEntityTable["sect"    ] = (char)167;
            _htmlCharacterEntityTable["shy"     ] = (char)173;
            _htmlCharacterEntityTable["Sigma"   ] = (char)931;
            _htmlCharacterEntityTable["sigma"   ] = (char)963;
            _htmlCharacterEntityTable["sigmaf"  ] = (char)962;
            _htmlCharacterEntityTable["sim"     ] = (char)8764;
            _htmlCharacterEntityTable["spades"  ] = (char)9824;
            _htmlCharacterEntityTable["sub"     ] = (char)8834;
            _htmlCharacterEntityTable["sube"    ] = (char)8838;
            _htmlCharacterEntityTable["sum"     ] = (char)8721;
            _htmlCharacterEntityTable["sup"     ] = (char)8835;
            _htmlCharacterEntityTable["sup1"    ] = (char)185;
            _htmlCharacterEntityTable["sup2"    ] = (char)178;
            _htmlCharacterEntityTable["sup3"    ] = (char)179;
            _htmlCharacterEntityTable["supe"    ] = (char)8839;
            _htmlCharacterEntityTable["szlig"   ] = (char)223;
            _htmlCharacterEntityTable["Tau"     ] = (char)932;
            _htmlCharacterEntityTable["tau"     ] = (char)964;
            _htmlCharacterEntityTable["there4"  ] = (char)8756;
            _htmlCharacterEntityTable["Theta"   ] = (char)920;
            _htmlCharacterEntityTable["theta"   ] = (char)952;
            _htmlCharacterEntityTable["thetasym"] = (char)977;
            _htmlCharacterEntityTable["thinsp"  ] = (char)8201;
            _htmlCharacterEntityTable["THORN"   ] = (char)222;
            _htmlCharacterEntityTable["thorn"   ] = (char)254;
            _htmlCharacterEntityTable["tilde"   ] = (char)732;
            _htmlCharacterEntityTable["times"   ] = (char)215;
            _htmlCharacterEntityTable["trade"   ] = (char)8482;
            _htmlCharacterEntityTable["Uacute"  ] = (char)218;
            _htmlCharacterEntityTable["uacute"  ] = (char)250;
            _htmlCharacterEntityTable["uarr"    ] = (char)8593;
            _htmlCharacterEntityTable["uArr"    ] = (char)8657;
            _htmlCharacterEntityTable["Ucirc"   ] = (char)219;
            _htmlCharacterEntityTable["ucirc"   ] = (char)251;
            _htmlCharacterEntityTable["Ugrave"  ] = (char)217;
            _htmlCharacterEntityTable["ugrave"  ] = (char)249;
            _htmlCharacterEntityTable["uml"     ] = (char)168;
            _htmlCharacterEntityTable["upsih"   ] = (char)978;
            _htmlCharacterEntityTable["Upsilon" ] = (char)933;
            _htmlCharacterEntityTable["upsilon" ] = (char)965;
            _htmlCharacterEntityTable["Uuml"    ] = (char)220;
            _htmlCharacterEntityTable["uuml"    ] = (char)252;
            _htmlCharacterEntityTable["weierp"  ] = (char)8472;
            _htmlCharacterEntityTable["Xi"      ] = (char)926;
            _htmlCharacterEntityTable["xi"      ] = (char)958;
            _htmlCharacterEntityTable["Yacute"  ] = (char)221;
            _htmlCharacterEntityTable["yacute"  ] = (char)253;
            _htmlCharacterEntityTable["yen"     ] = (char)165;
            _htmlCharacterEntityTable["Yuml"    ] = (char)376;
            _htmlCharacterEntityTable["yuml"    ] = (char)255;
            _htmlCharacterEntityTable["Zeta"    ] = (char)918;
            _htmlCharacterEntityTable["zeta"    ] = (char)950;
            _htmlCharacterEntityTable["zwj"     ] = (char)8205;
            _htmlCharacterEntityTable["zwnj"    ] = (char)8204;
       }

       #endregion
    }
}

 

▶ HTMLTokenType.cs

namespace TestProject
{
    /// <summary>
    /// HTML 토큰 타입
    /// </summary>
    internal enum HTMLTokenType
    {
        /// <summary>
        /// 개발 태그 시작
        /// </summary>
        OpeningTagStart,

        /// <summary>
        /// 폐쇄 태그 시작
        /// </summary>
        ClosingTagStart,

        /// <summary>
        /// 태그 종료
        /// </summary>
        TagEnd,

        /// <summary>
        /// 빈 태그 종료
        /// </summary>
        EmptyTagEnd,

        /// <summary>
        /// 등호
        /// </summary>
        EqualSign,

        /// <summary>
        /// 명칭
        /// </summary>
        Name,

        /// <summary>
        /// 원자
        /// </summary>
        Atom,

        /// <summary>
        /// 텍스트
        /// </summary>
        Text,

        /// <summary>
        /// 주석
        /// </summary>
        Comment,

        /// <summary>
        /// 파일 끝
        /// </summary>
        EOF
    }
}

 

▶ HTMLToXAMLConverter.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Documents;
using System.Xml;

namespace TestProject
{
    /// <summary>
    /// HTML→XAML 변환자
    /// </summary>
    public static class HTMLToXAMLConverter
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// XAML 네임스페이스
        /// </summary>
        private static string _xamlNamespace = "http://schemas.microsoft.com/winfx/2006/xaml/presentation";

        /// <summary>
        /// 인라인 프래그먼 부모 엘리먼트
        /// </summary>
        private static XmlElement _inlineFragmentParentElement;

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Public

        #region Field

        /// <summary>
        /// XAML_FLOWDOCUMENT
        /// </summary>
        public const string XAML_FLOWDOCUMENT = "FlowDocument";

        /// <summary>
        /// XAML_RUN
        /// </summary>
        public const string XAML_RUN = "Run";

        /// <summary>
        /// XAML_SPAN
        /// </summary>
        public const string XAML_SPAN = "Span";

        /// <summary>
        /// XAML_HYPERLINK
        /// </summary>
        public const string XAML_HYPERLINK = "Hyperlink";

        /// <summary>
        /// XAML_HYPERLINK_NAVIGATE_URI
        /// </summary>
        public const string XAML_HYPERLINK_NAVIGATE_URI = "NavigateUri";

        /// <summary>
        /// XAML_HYPERLINK_TARGET_NAME
        /// </summary>
        public const string XAML_HYPERLINK_TARGET_NAME = "TargetName";

        /// <summary>
        /// XAML_SECTION
        /// </summary>
        public const string XAML_SECTION = "Section";

        /// <summary>
        /// XAML_LIST
        /// </summary>
        public const string XAML_LIST = "List";

        /// <summary>
        /// XAML_LIST_MARKER_STYLE
        /// </summary>
        public const string XAML_LIST_MARKER_STYLE = "MarkerStyle";

        /// <summary>
        /// XAML_LIST_MARKER_STYLE_NONE
        /// </summary>
        public const string XAML_LIST_MARKER_STYLE_NONE = "None";

        /// <summary>
        /// XAML_LIST_MARKER_STYLE_DECIMAL
        /// </summary>
        public const string XAML_LIST_MARKER_STYLE_DECIMAL = "Decimal";

        /// <summary>
        /// XAML_LIST_MARKER_STYLE_DISC
        /// </summary>
        public const string XAML_LIST_MARKER_STYLE_DISC = "Disc";

        /// <summary>
        /// XAML_LIST_MARKER_STYLE_CIRCLE
        /// </summary>
        public const string XAML_LIST_MARKER_STYLE_CIRCLE = "Circle";

        /// <summary>
        /// XAML_LIST_MARKER_STYLE_SQUARE
        /// </summary>
        public const string XAML_LIST_MARKER_STYLE_SQUARE = "Square";

        /// <summary>
        /// XAML_LIST_MARKER_STYLE_BOX
        /// </summary>
        public const string XAML_LIST_MARKER_STYLE_BOX = "Box";

        /// <summary>
        /// XAML_LIST_MARKER_STYLE_LOWER_LATIN
        /// </summary>
        public const string XAML_LIST_MARKER_STYLE_LOWER_LATIN = "LowerLatin";

        /// <summary>
        /// XAML_LIST_MARKER_STYLE_UPPER_LATIN
        /// </summary>
        public const string XAML_LIST_MARKER_STYLE_UPPER_LATIN = "UpperLatin";

        /// <summary>
        /// XAML_LIST_MARKER_STYLE_LOWER_ROMAN
        /// </summary>
        public const string XAML_LIST_MARKER_STYLE_LOWER_ROMAN = "LowerRoman";

        /// <summary>
        /// XAML_LIST_MARKER_STYLE_UPPER_ROMAN
        /// </summary>
        public const string XAML_LIST_MARKER_STYLE_UPPER_ROMAN = "UpperRoman";

        /// <summary>
        /// XAML_LIST_ITEM
        /// </summary>
        public const string XAML_LIST_ITEM = "ListItem";

        /// <summary>
        /// XAML_LINE_BREAK
        /// </summary>
        public const string XAML_LINE_BREAK = "LineBreak";

        /// <summary>
        /// XAML_PARAGRAPH
        /// </summary>
        public const string XAML_PARAGRAPH = "Paragraph";

        /// <summary>
        /// XAML_MARGIN
        /// </summary>
        public const string XAML_MARGIN = "Margin";

        /// <summary>
        /// XAML_PADDING
        /// </summary>
        public const string XAML_PADDING = "Padding";

        /// <summary>
        /// XAML_BORDER_BRUSH
        /// </summary>
        public const string XAML_BORDER_BRUSH = "BorderBrush";

        /// <summary>
        /// XAML_BORDER_THICKNESS
        /// </summary>
        public const string XAML_BORDER_THICKNESS = "BorderThickness";

        /// <summary>
        /// XAML_TABLE
        /// </summary>
        public const string XAML_TABLE = "Table";

        /// <summary>
        /// XAML_TABLE_COLUMN
        /// </summary>
        public const string XAML_TABLE_COLUMN = "TableColumn";

        /// <summary>
        /// XAML_TABLE_ROW_GROUP
        /// </summary>
        public const string XAML_TABLE_ROW_GROUP = "TableRowGroup";

        /// <summary>
        /// XAML_TABLE_ROW
        /// </summary>
        public const string XAML_TABLE_ROW = "TableRow";

        /// <summary>
        /// XAML_TABLE_CELL
        /// </summary>
        public const string XAML_TABLE_CELL = "TableCell";

        /// <summary>
        /// XAML_TABLE_CELL_BORDER_THICKNESS
        /// </summary>
        public const string XAML_TABLE_CELL_BORDER_THICKNESS = "BorderThickness";

        /// <summary>
        /// XAML_TABLE_CELL_BORDER_BRUSH
        /// </summary>
        public const string XAML_TABLE_CELL_BORDER_BRUSH = "BorderBrush";

        /// <summary>
        /// XAML_TABLE_CELL_COLUMN_SPAN
        /// </summary>
        public const string XAML_TABLE_CELL_COLUMN_SPAN = "ColumnSpan";

        /// <summary>
        /// XAML_TABLE_CELL_ROW_SPAN
        /// </summary>
        public const string XAML_TABLE_CELL_ROW_SPAN = "RowSpan";

        /// <summary>
        /// XAML_WIDTH
        /// </summary>
        public const string XAML_WIDTH = "Width";

        /// <summary>
        /// XAML_BRUSHES_BLACK
        /// </summary>
        public const string XAML_BRUSHES_BLACK = "Black";

        /// <summary>
        /// XAML_FONT_FAMILY
        /// </summary>
        public const string XAML_FONT_FAMILY = "FontFamily";

        /// <summary>
        /// XAML_FONT_SIZE
        /// </summary>
        public const string XAML_FONT_SIZE = "FontSize";

        /// <summary>
        /// XAML_FONT_SIZE_XX_LARGE
        /// </summary>
        public const string XAML_FONT_SIZE_XX_LARGE = "22pt";

        /// <summary>
        /// XAML_FONT_SIZE_X_LARGE
        /// </summary>
        public const string XAML_FONT_SIZE_X_LARGE = "20pt";

        /// <summary>
        /// XAML_FONT_SIZE_LARGE
        /// </summary>
        public const string XAML_FONT_SIZE_LARGE = "18pt";

        /// <summary>
        /// XAML_FONT_SIZE_MEDIUM
        /// </summary>
        public const string XAML_FONT_SIZE_MEDIUM = "16pt";

        /// <summary>
        /// XAML_FONT_SIZE_SMALL
        /// </summary>
        public const string XAML_FONT_SIZE_SMALL = "12pt";

        /// <summary>
        /// XAML_FONT_SIZE_X_SMALL
        /// </summary>
        public const string XAML_FONT_SIZE_X_SMALL = "10pt";

        /// <summary>
        /// XAML_FONT_SIZE_XX_SMALL
        /// </summary>
        public const string XAML_FONT_SIZE_XX_SMALL = "8pt";

        /// <summary>
        /// XAML_FONT_WEIGHT
        /// </summary>
        public const string XAML_FONT_WEIGHT = "FontWeight";

        /// <summary>
        /// XAML_FONT_WEIGHT_BOLD
        /// </summary>
        public const string XAML_FONT_WEIGHT_BOLD = "Bold";

        /// <summary>
        /// XAML_FONT_STYLE
        /// </summary>
        public const string XAML_FONT_STYLE = "FontStyle";

        /// <summary>
        /// XAML_FOREGROUND
        /// </summary>
        public const string XAML_FOREGROUND = "Foreground";

        /// <summary>
        /// XAML_BACKGROUND
        /// </summary>
        public const string XAML_BACKGROUND = "Background";

        /// <summary>
        /// XAML_TEXT_DECORATIONS
        /// </summary>
        public const string XAML_TEXT_DECORATIONS = "TextDecorations";

        /// <summary>
        /// XAML_TEXT_DECORATIONS_UNDERLINE
        /// </summary>
        public const string XAML_TEXT_DECORATIONS_UNDERLINE = "Underline";

        /// <summary>
        /// XAML_TEXT_INDENT
        /// </summary>
        public const string XAML_TEXT_INDENT = "TextIndent";

        /// <summary>
        /// XAML_TEXT_ALIGNMENT
        /// </summary>
        public const string XAML_TEXT_ALIGNMENT = "TextAlignment";

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region HTML에서 XAML로 변환하기 - ConvertHTMLToXAML(html, asFlowDocument)

        /// <summary>
        /// HTML에서 XAML로 변환하기
        /// </summary>
        /// <param name="html">HTML</param>
        /// <param name="asFlowDocument">플로우 문서 여부</param>
        /// <returns>XAML</returns>
        public static string ConvertHTMLToXAML(string html, bool asFlowDocument)
        {
            XmlElement htmlElement = HTMLParser.ParseHTML(html);

            string rootElementName = asFlowDocument ? HTMLToXAMLConverter.XAML_FLOWDOCUMENT : HTMLToXAMLConverter.XAML_SECTION;

            XmlDocument xamlDocument = new XmlDocument();

            XmlElement xamlFlowDocumentElement = xamlDocument.CreateElement(null, rootElementName, _xamlNamespace);

            CSSStyleSheet styleSheet = new CSSStyleSheet(htmlElement);

            List<XmlElement> sourceElementList = new List<XmlElement>(10);

            _inlineFragmentParentElement = null;

            AddBlock(xamlFlowDocumentElement, htmlElement, new Hashtable(), styleSheet, sourceElementList);

            if(!asFlowDocument)
            {
                xamlFlowDocumentElement = ExtractInlineFragment(xamlFlowDocumentElement);
            }

            xamlFlowDocumentElement.SetAttribute("xml:space", "preserve");

            string xaml = xamlFlowDocumentElement.OuterXml;

            return xaml;
        }

        #endregion
        #region 어트리뷰트 구하기 - GetAttribute(element, attributeName)

        /// <summary>
        /// 어트리뷰트 구하기
        /// </summary>
        /// <param name="element">엘리먼트</param>
        /// <param name="attributeName">어트리뷰트명</param>
        /// <returns>어트리뷰트</returns>
        public static string GetAttribute(XmlElement element, string attributeName)
        {
            attributeName = attributeName.ToLower();

            for(int i = 0; i < element.Attributes.Count; i++)
            {
                if(element.Attributes[i].Name.ToLower() == attributeName)
                {
                    return element.Attributes[i].Value;
                }
            }

            return null;
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Internal

        #region 인용 부호 제거하기 - UnQuote(value)

        /// <summary>
        /// 인용 부호 제거하기
        /// </summary>
        /// <param name="value">값</param>
        /// <returns>인용 부호 제거 값</returns>
        internal static string UnQuote(string value)
        {
            if(value.StartsWith("\"") && value.EndsWith("\"") || value.StartsWith("'") && value.EndsWith("'"))
            {
                value = value.Substring(1, value.Length - 2).Trim();
            }

            return value;
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 인라인 프래그먼 부모 정의하기 - DefineInlineFragmentParent(htmlComment, xamlParentElement)

        /// <summary>
        /// 인라인 프래그먼 부모 정의하기
        /// </summary>
        /// <param name="htmlComment">HTML 주석</param>
        /// <param name="xamlParentElement">XAML 부모 엘리먼트</param>
        private static void DefineInlineFragmentParent(XmlComment htmlComment, XmlElement xamlParentElement)
        {
            if(htmlComment.Value == "StartFragment")
            {
                _inlineFragmentParentElement = xamlParentElement;
            }
            else if(htmlComment.Value == "EndFragment")
            {
                if(_inlineFragmentParentElement == null && xamlParentElement != null)
                {
                    _inlineFragmentParentElement = xamlParentElement;
                }
            }
        }

        #endregion
        #region 텍스트 RUN 추가하기 - AddTextRun(xamlElement, textData)

        /// <summary>
        /// 텍스트 RUN 추가하기
        /// </summary>
        /// <param name="xamlElement">XAML 엘리먼트</param>
        /// <param name="textData">텍스트 데이터</param>
        private static void AddTextRun(XmlElement xamlElement, string textData)
        {
            for(int i = 0; i < textData.Length; i++)
            {
                if(char.IsControl(textData[i]))
                {
                    textData = textData.Remove(i--, 1);
                }
            }

            textData = textData.Replace((char)160, ' ');

            if(textData.Length > 0)
            {
                xamlElement.AppendChild(xamlElement.OwnerDocument.CreateTextNode(textData));
            }
        }

        #endregion
        #region 엘리먼트 속성 구하기 - GetElementProperties(htmlElement, inheritedPropertyTable, localPropertyTable, styleSheet, sourceElementList)

        /// <summary>
        /// 엘리먼트 속성 구하기
        /// </summary>
        /// <param name="htmlElement">HTML 엘리먼트</param>
        /// <param name="inheritedPropertyTable">상속 속성 테이블</param>
        /// <param name="localPropertyTable">로컬 속성 테이블</param>
        /// <param name="styleSheet">스타일 시트</param>
        /// <param name="sourceElementList">소스 엘리먼트 리스트</param>
        /// <returns>엘리먼트 속성</returns>
        private static Hashtable GetElementProperties
        (
            XmlElement       htmlElement,
            Hashtable        inheritedPropertyTable,
            out Hashtable    localPropertyTable,
            CSSStyleSheet    styleSheet,
            List<XmlElement> sourceElementList
        )
        {
            Hashtable currentPropertyTable = new Hashtable();

            IDictionaryEnumerator propertyEnumerator = inheritedPropertyTable.GetEnumerator();

            while(propertyEnumerator.MoveNext())
            {
                currentPropertyTable[propertyEnumerator.Key] = propertyEnumerator.Value;
            }

            string elementName      = htmlElement.LocalName.ToLower();
            string elementNamespace = htmlElement.NamespaceURI;

            localPropertyTable = new Hashtable();

            switch(elementName)
            {
                case "i"      :
                case "italic" :
                case "em"     :

                    localPropertyTable["font-style"] = "italic";

                    break;

                case "b"      :
                case "bold"   :
                case "strong" :
                case "dfn"    :

                    localPropertyTable["font-weight"] = "bold";

                    break;

                case "u"         :
                case "underline" :

                    localPropertyTable["text-decoration-underline"] = "true";

                    break;

                case "font" :

                    string attributeValue = GetAttribute(htmlElement, "face");

                    if(attributeValue != null)
                    {
                        localPropertyTable["font-family"] = attributeValue;
                    }

                    attributeValue = GetAttribute(htmlElement, "size");

                    if(attributeValue != null)
                    {
                        double fontSize = double.Parse(attributeValue) * (12.0 / 3.0);

                        if(fontSize < 1.0)
                        {
                            fontSize = 1.0;
                        }
                        else if(fontSize > 1000.0)
                        {
                            fontSize = 1000.0;
                        }

                        localPropertyTable["font-size"] = fontSize.ToString();
                    }

                    attributeValue = GetAttribute(htmlElement, "color");

                    if(attributeValue != null)
                    {
                        localPropertyTable["color"] = attributeValue;
                    }

                    break;

                case "samp" :

                    localPropertyTable["font-family"] = "Courier New";
                    localPropertyTable["font-size"  ] = XAML_FONT_SIZE_XX_SMALL;
                    localPropertyTable["text-align" ] = "Left";

                    break;

                case "sub" :

                    break;

                case "sup" :

                    break;

                case "a" :

                    break;

                case "acronym" :

                    break;

                case "p" :

                    break;

                case "div" :

                    break;

                case "pre" :

                    localPropertyTable["font-family"] = "Courier New";
                    localPropertyTable["font-size"  ] = XAML_FONT_SIZE_XX_SMALL;
                    localPropertyTable["text-align" ] = "Left";

                    break;

                case "blockquote" :

                    localPropertyTable["margin-left"] = "16";

                    break;

                case "h1" :

                    localPropertyTable["font-size"] = XAML_FONT_SIZE_XX_LARGE;

                    break;

                case "h2" :

                    localPropertyTable["font-size"] = XAML_FONT_SIZE_X_LARGE;

                    break;

                case "h3" :

                    localPropertyTable["font-size"] = XAML_FONT_SIZE_LARGE;

                    break;

                case "h4" :

                    localPropertyTable["font-size"] = XAML_FONT_SIZE_MEDIUM;

                    break;

                case "h5" :

                    localPropertyTable["font-size"] = XAML_FONT_SIZE_SMALL;

                    break;

                case "h6" :

                    localPropertyTable["font-size"] = XAML_FONT_SIZE_X_SMALL;

                    break;

                case "ul" :

                    localPropertyTable["list-style-type"] = "disc";

                    break;

                case "ol" :

                    localPropertyTable["list-style-type"] = "decimal";

                    break;

                case "table" :
                case "body"  :
                case "html"  :

                    break;
            }

            HTMLCSSParser.GetElementPropertiesFromCSSAttributes
            (
                htmlElement,
                elementName,
                styleSheet,
                localPropertyTable,
                sourceElementList
            );

            propertyEnumerator = localPropertyTable.GetEnumerator();

            while(propertyEnumerator.MoveNext())
            {
                currentPropertyTable[propertyEnumerator.Key] = propertyEnumerator.Value;
            }

            return currentPropertyTable;
        }

        #endregion
        #region 속성 값 설정하기 - SetPropertyValue(xamlElement, property, stringValue)

        /// <summary>
        /// 속성 값 설정하기
        /// </summary>
        /// <param name="xamlElement">XAML 엘리먼트</param>
        /// <param name="property">속성</param>
        /// <param name="stringValue">문자열 값</param>
        private static void SetPropertyValue(XmlElement xamlElement, DependencyProperty property, string stringValue)
        {
            TypeConverter typeConverter = TypeDescriptor.GetConverter(property.PropertyType);

            try
            {
                object convertedValue = typeConverter.ConvertFromInvariantString(stringValue);

                if(convertedValue != null)
                {
                    xamlElement.SetAttribute(property.Name, stringValue);
                }
            }
            catch(Exception)
            {
            }
        }

        #endregion
        #region 두께 속성 작성하기 - ComposeThicknessProperty(xamlElement, propertyName, left, right, top, bottom)

        /// <summary>
        /// 두께 속성 작성하기
        /// </summary>
        /// <param name="xamlElement">XAML 엘리먼트</param>
        /// <param name="propertyName">속성명</param>
        /// <param name="left">왼쪽</param>
        /// <param name="right">오른쪽</param>
        /// <param name="top">위쪽</param>
        /// <param name="bottom">아래쪽</param>
        private static void ComposeThicknessProperty
        (
            XmlElement xamlElement,
            string     propertyName,
            string     left,
            string     right,
            string     top,
            string     bottom
        )
        {
            string thickness;

            if(left[0] == '0' || left[0] == '-')
            {
                left = "0";
            }

            if(right[0] == '0' || right[0] == '-')
            {
                right = "0";
            }

            if(top[0] == '0' || top[0] == '-')
            {
                top = "0";
            }

            if(bottom[0] == '0' || bottom[0] == '-')
            {
                bottom = "0";
            }

            if(left == right && top == bottom)
            {
                if(left == top)
                {
                    thickness = left;
                }
                else
                {
                    thickness = left + "," + top;
                }
            }
            else
            {
                thickness = left + "," + top + "," + right + "," + bottom;
            }

            xamlElement.SetAttribute(propertyName, thickness);
        }

        #endregion
        #region 로컬 속성 테이블 적용하기 - ApplyLocalPropertyTable(xamlElement, localPropertyTable, isBlock)

        /// <summary>
        /// 로컬 속성 테이블 적용하기
        /// </summary>
        /// <param name="xamlElement">XAML 엘리먼트</param>
        /// <param name="localPropertyTable">로컬 속성 테이블</param>
        /// <param name="isBlock">블럭 여부</param>
        private static void ApplyLocalPropertyTable(XmlElement xamlElement, Hashtable localPropertyTable, bool isBlock)
        {
            bool   marginSet             = false;
            string marginTop             = "0";
            string marginBottom          = "0";
            string marginLeft            = "0";
            string marginRight           = "0";
            bool   paddingSet            = false;
            string paddingTop            = "0";
            string paddingBottom         = "0";
            string paddingLeft           = "0";
            string paddingRight          = "0";
            string borderColor           = null;
            bool   borderThicknessSet    = false;
            string borderThicknessTop    = "0";
            string borderThicknessBottom = "0";
            string borderThicknessLeft   = "0";
            string borderThicknessRight  = "0";

            IDictionaryEnumerator propertyEnumerator = localPropertyTable.GetEnumerator();

            while(propertyEnumerator.MoveNext())
            {
                switch((string)propertyEnumerator.Key)
                {
                    case "font-family" :

                        xamlElement.SetAttribute(XAML_FONT_FAMILY, (string)propertyEnumerator.Value);

                        break;

                    case "font-style" :

                        xamlElement.SetAttribute(XAML_FONT_STYLE, (string)propertyEnumerator.Value);

                        break;

                    case "font-variant" :

                        break;

                    case "font-weight" :

                        xamlElement.SetAttribute(XAML_FONT_WEIGHT, (string)propertyEnumerator.Value);

                        break;

                    case "font-size" :

                        xamlElement.SetAttribute(XAML_FONT_SIZE, (string)propertyEnumerator.Value);

                        break;

                    case "color" :

                        SetPropertyValue(xamlElement, TextElement.ForegroundProperty, (string)propertyEnumerator.Value);

                        break;

                    case "background-color" :

                        SetPropertyValue(xamlElement, TextElement.BackgroundProperty, (string)propertyEnumerator.Value);

                        break;

                    case "text-decoration-underline" :

                        if(!isBlock)
                        {
                            if((string)propertyEnumerator.Value == "true")
                            {
                                xamlElement.SetAttribute(XAML_TEXT_DECORATIONS, XAML_TEXT_DECORATIONS_UNDERLINE);
                            }
                        }

                        break;

                    case "text-decoration-none"         :
                    case "text-decoration-overline"     :
                    case "text-decoration-line-through" :
                    case "text-decoration-blink"        :

                        break;

                    case "text-transform" :

                        break;

                    case "text-indent" :

                        if(isBlock)
                        {
                            xamlElement.SetAttribute(XAML_TEXT_INDENT, (string)propertyEnumerator.Value);
                        }

                        break;

                    case "text-align" :

                        if(isBlock)
                        {
                            xamlElement.SetAttribute(XAML_TEXT_ALIGNMENT, (string)propertyEnumerator.Value);
                        }

                        break;

                    case "width"  :
                    case "height" :

                        break;

                    case "margin-top" :

                        marginSet = true;
                        marginTop = (string)propertyEnumerator.Value;

                        break;

                    case "margin-right" :

                        marginSet   = true;
                        marginRight = (string)propertyEnumerator.Value;

                        break;

                    case "margin-bottom" :

                        marginSet    = true;
                        marginBottom = (string)propertyEnumerator.Value;

                        break;

                    case "margin-left" :

                        marginSet  = true;
                        marginLeft = (string)propertyEnumerator.Value;

                        break;

                    case "padding-top" :

                        paddingSet = true;
                        paddingTop = (string)propertyEnumerator.Value;

                        break;

                    case "padding-right" :

                        paddingSet   = true;
                        paddingRight = (string)propertyEnumerator.Value;

                        break;

                    case "padding-bottom" :

                        paddingSet    = true;
                        paddingBottom = (string)propertyEnumerator.Value;

                        break;

                    case "padding-left" :

                        paddingSet  = true;
                        paddingLeft = (string)propertyEnumerator.Value;

                        break;

                    case "border-color-top" :

                        borderColor = (string)propertyEnumerator.Value;

                        break;

                    case "border-color-right" :

                        borderColor = (string)propertyEnumerator.Value;

                        break;

                    case "border-color-bottom" :

                        borderColor = (string)propertyEnumerator.Value;

                        break;

                    case "border-color-left" :

                        borderColor = (string)propertyEnumerator.Value;

                        break;

                    case "border-style-top"    :
                    case "border-style-right"  :
                    case "border-style-bottom" :
                    case "border-style-left"   :

                        break;

                    case "border-width-top" :

                        borderThicknessSet = true;
                        borderThicknessTop = (string)propertyEnumerator.Value;

                        break;

                    case "border-width-right" :

                        borderThicknessSet   = true;
                        borderThicknessRight = (string)propertyEnumerator.Value;

                        break;

                    case "border-width-bottom" :

                        borderThicknessSet    = true;
                        borderThicknessBottom = (string)propertyEnumerator.Value;

                        break;

                    case "border-width-left" :

                        borderThicknessSet  = true;
                        borderThicknessLeft = (string)propertyEnumerator.Value;

                        break;

                    case "list-style-type" :

                        if(xamlElement.LocalName == XAML_LIST)
                        {
                            string markerStyle;

                            switch(((string)propertyEnumerator.Value).ToLower())
                            {
                                case "disc"        : markerStyle = HTMLToXAMLConverter.XAML_LIST_MARKER_STYLE_DISC;        break;
                                case "circle"      : markerStyle = HTMLToXAMLConverter.XAML_LIST_MARKER_STYLE_CIRCLE;      break;
                                case "none"        : markerStyle = HTMLToXAMLConverter.XAML_LIST_MARKER_STYLE_NONE;        break;
                                case "square"      : markerStyle = HTMLToXAMLConverter.XAML_LIST_MARKER_STYLE_SQUARE;      break;
                                case "box"         : markerStyle = HTMLToXAMLConverter.XAML_LIST_MARKER_STYLE_BOX;         break;
                                case "lower-latin" : markerStyle = HTMLToXAMLConverter.XAML_LIST_MARKER_STYLE_LOWER_LATIN; break;
                                case "upper-latin" : markerStyle = HTMLToXAMLConverter.XAML_LIST_MARKER_STYLE_UPPER_LATIN; break;
                                case "lower-roman" : markerStyle = HTMLToXAMLConverter.XAML_LIST_MARKER_STYLE_LOWER_ROMAN; break;
                                case "upper-roman" : markerStyle = HTMLToXAMLConverter.XAML_LIST_MARKER_STYLE_UPPER_ROMAN; break;
                                case "decimal"     : markerStyle = HTMLToXAMLConverter.XAML_LIST_MARKER_STYLE_DECIMAL;     break;
                                default            : markerStyle = HTMLToXAMLConverter.XAML_LIST_MARKER_STYLE_DISC;        break;
                            }

                            xamlElement.SetAttribute(HTMLToXAMLConverter.XAML_LIST_MARKER_STYLE, markerStyle);
                        }

                        break;

                    case "float" :
                    case "clear" :

                        break;

                    case "display" :

                        break;
                }
            }

            if(isBlock)
            {
                if(marginSet)
                {
                    ComposeThicknessProperty(xamlElement, XAML_MARGIN, marginLeft, marginRight, marginTop, marginBottom);
                }

                if(paddingSet)
                {
                    ComposeThicknessProperty(xamlElement, XAML_PADDING, paddingLeft, paddingRight, paddingTop, paddingBottom);
                }

                if(borderColor != null)
                {
                    xamlElement.SetAttribute(XAML_BORDER_BRUSH, borderColor);
                }

                if(borderThicknessSet)
                {
                    ComposeThicknessProperty
                    (
                        xamlElement,
                        XAML_BORDER_THICKNESS,
                        borderThicknessLeft,
                        borderThicknessRight,
                        borderThicknessTop,
                        borderThicknessBottom
                    );
                }
            }
        }

        #endregion
        #region SPAN 또는 RUN 추가하기 - AddSpanOrRun(xamlParentElement, htmlElement, inheritedPropertyTable, styleSheet, sourceElementList)

        /// <summary>
        /// SPAN 또는 RUN 추가하기
        /// </summary>
        /// <param name="xamlParentElement">XAML 부모 엘리먼트</param>
        /// <param name="htmlElement">HTML 엘리먼트</param>
        /// <param name="inheritedPropertyTable">상속 속성 테이블</param>
        /// <param name="styleSheet">스타일 시트</param>
        /// <param name="sourceElementList">소스 엘리먼트 리스트</param>
        private static void AddSpanOrRun
        (
            XmlElement       xamlParentElement,
            XmlElement       htmlElement,
            Hashtable        inheritedPropertyTable,
            CSSStyleSheet    styleSheet,
            List<XmlElement> sourceElementList
        )
        {
            bool elementHasChildren = false;

            for(XmlNode htmlNode = htmlElement.FirstChild; htmlNode != null; htmlNode = htmlNode.NextSibling)
            {
                if(htmlNode is XmlElement)
                {
                    string htmlChildName = ((XmlElement)htmlNode).LocalName.ToLower();

                    if
                    (
                        HTMLSchema.IsInlineElement(htmlChildName) ||
                        HTMLSchema.IsBlockElement(htmlChildName)  ||
                        htmlChildName == "img"                    ||
                        htmlChildName == "br"                     ||
                        htmlChildName == "hr"
                    )
                    {
                        elementHasChildren = true;

                        break;
                    }
                }
            }

            string xamlElementName = elementHasChildren ? HTMLToXAMLConverter.XAML_SPAN : HTMLToXAMLConverter.XAML_RUN;

            Hashtable localPropertyTable;
            Hashtable currentPropertyTable = GetElementProperties
            (
                htmlElement,
                inheritedPropertyTable,
                out localPropertyTable,
                styleSheet,
                sourceElementList
            );

            XmlElement xamlElement = xamlParentElement.OwnerDocument.CreateElement(null, xamlElementName, _xamlNamespace);

            ApplyLocalPropertyTable(xamlElement, localPropertyTable, false);

            for(XmlNode htmlChildNode = htmlElement.FirstChild; htmlChildNode != null; htmlChildNode = htmlChildNode.NextSibling)
            {
                AddInline(xamlElement, htmlChildNode, currentPropertyTable, styleSheet, sourceElementList);
            }

            xamlParentElement.AppendChild(xamlElement);
        }

        #endregion
        #region 하이퍼링크 추가하기 - AddHyperlink(xamlParentElement, htmlElement, inheritedPropertyTable, styleSheet, sourceElementList)

        /// <summary>
        /// 하이퍼링크 추가하기
        /// </summary>
        /// <param name="xamlParentElement">XAML 부모 엘리먼트</param>
        /// <param name="htmlElement">HTML 엘리먼트</param>
        /// <param name="inheritedPropertyTable">상속 속성 테이블</param>
        /// <param name="styleSheet">스타일 시트</param>
        /// <param name="sourceElementList">소스 엘리먼트 리스트</param>
        private static void AddHyperlink
        (
            XmlElement       xamlParentElement,
            XmlElement       htmlElement,
            Hashtable        inheritedPropertyTable,
            CSSStyleSheet    styleSheet,
            List<XmlElement> sourceElementList
        )
        {
            string href = GetAttribute(htmlElement, "href");

            if(href == null)
            {
                AddSpanOrRun(xamlParentElement, htmlElement, inheritedPropertyTable, styleSheet, sourceElementList);
            }
            else
            {
                Hashtable localPropertyTable;
                Hashtable currentPropertyTable = GetElementProperties
                (
                    htmlElement,
                    inheritedPropertyTable,
                    out localPropertyTable,
                    styleSheet,
                    sourceElementList
                );

                XmlElement xamlElement = xamlParentElement.OwnerDocument.CreateElement(null, HTMLToXAMLConverter.XAML_HYPERLINK, _xamlNamespace);

                ApplyLocalPropertyTable(xamlElement, localPropertyTable, false);

                string[] hrefPartArray = href.Split(new char[] { '#' });

                if(hrefPartArray.Length > 0 && hrefPartArray[0].Trim().Length > 0)
                {
                    xamlElement.SetAttribute(HTMLToXAMLConverter.XAML_HYPERLINK_NAVIGATE_URI, hrefPartArray[0].Trim());
                }

                if(hrefPartArray.Length == 2 && hrefPartArray[1].Trim().Length > 0)
                {
                    xamlElement.SetAttribute(HTMLToXAMLConverter.XAML_HYPERLINK_TARGET_NAME, hrefPartArray[1].Trim());
                }

                for(XmlNode htmlChildNode = htmlElement.FirstChild; htmlChildNode != null; htmlChildNode = htmlChildNode.NextSibling)
                {
                    AddInline(xamlElement, htmlChildNode, currentPropertyTable, styleSheet, sourceElementList);
                }

                xamlParentElement.AppendChild(xamlElement);
            }
        }

        #endregion
        #region 이미지 추가하기 - AddImage(xamlParentElement, htmlElement, inheritedPropertyTable, styleSheet, sourceElementList)

        /// <summary>
        /// 이미지 추가하기
        /// </summary>
        /// <param name="xamlParentElement">XAML 부모 엘리먼트</param>
        /// <param name="htmlElement">HTML 엘리먼트</param>
        /// <param name="inheritedPropertyTable">상속 속성 테이블</param>
        /// <param name="styleSheet">스타일 시트</param>
        /// <param name="sourceElementList">소스 엘리먼트 리스트</param>
        private static void AddImage
        (
            XmlElement       xamlParentElement,
            XmlElement       htmlElement,
            Hashtable        inheritedPropertyTable,
            CSSStyleSheet    styleSheet,
            List<XmlElement> sourceElementList
        )
        {
        }

        #endregion
        #region 라인 브레이크 추가하기 - AddLineBreak(xamlParentElement, htmlElementName)

        /// <summary>
        /// 라인 브레이크 추가하기
        /// </summary>
        /// <param name="xamlParentElement">XAML 부모 엘리먼트</param>
        /// <param name="htmlElementName">HTML 엘리먼트명</param>
        private static void AddLineBreak(XmlElement xamlParentElement, string htmlElementName)
        {
            XmlElement xamlLineBreak = xamlParentElement.OwnerDocument.CreateElement(null, HTMLToXAMLConverter.XAML_LINE_BREAK, _xamlNamespace);

            xamlParentElement.AppendChild(xamlLineBreak);

            if(htmlElementName == "hr")
            {
                XmlText xamlHorizontalLine = xamlParentElement.OwnerDocument.CreateTextNode("----------------------");

                xamlParentElement.AppendChild(xamlHorizontalLine);

                xamlLineBreak = xamlParentElement.OwnerDocument.CreateElement(null, HTMLToXAMLConverter.XAML_LINE_BREAK, _xamlNamespace);

                xamlParentElement.AppendChild(xamlLineBreak);
            }
        }

        #endregion
        #region 인라인 추가하기 - AddInline(xamlParentElement, htmlNode, inheritedPropertyTable, styleSheet, sourceElementList)

        /// <summary>
        /// 인라인 추가하기
        /// </summary>
        /// <param name="xamlParentElement">XAML 부모 엘리먼트</param>
        /// <param name="htmlNode">HTML 노드</param>
        /// <param name="inheritedPropertyTable">상속 속성 테이블</param>
        /// <param name="styleSheet">스타일 시트</param>
        /// <param name="sourceElementList">소스 엘리먼트 리스트</param>
        private static void AddInline
        (
            XmlElement       xamlParentElement,
            XmlNode          htmlNode,
            Hashtable        inheritedPropertyTable,
            CSSStyleSheet    styleSheet,
            List<XmlElement> sourceElementList
        )
        {
            if(htmlNode is XmlComment)
            {
                DefineInlineFragmentParent((XmlComment)htmlNode, xamlParentElement);
            }
            else if(htmlNode is XmlText)
            {
                AddTextRun(xamlParentElement, htmlNode.Value);
            }
            else if(htmlNode is XmlElement)
            {
                XmlElement htmlElement = (XmlElement)htmlNode;

                if(htmlElement.NamespaceURI != HTMLParser.XHTML_NAMESPACE)
                {
                    return;
                }

                string htmlElementName = htmlElement.LocalName.ToLower();

                sourceElementList.Add(htmlElement);

                switch(htmlElementName)
                {
                    case "a" :

                        AddHyperlink(xamlParentElement, htmlElement, inheritedPropertyTable, styleSheet, sourceElementList);

                        break;

                    case "img" :

                        AddImage(xamlParentElement, htmlElement, inheritedPropertyTable, styleSheet, sourceElementList);

                        break;

                    case "br" :
                    case "hr" :

                        AddLineBreak(xamlParentElement, htmlElementName);

                        break;

                    default :

                        if(HTMLSchema.IsInlineElement(htmlElementName) || HTMLSchema.IsBlockElement(htmlElementName))
                        {
                            AddSpanOrRun(xamlParentElement, htmlElement, inheritedPropertyTable, styleSheet, sourceElementList);
                        }

                        break;
                }

                sourceElementList.RemoveAt(sourceElementList.Count - 1);
            }
        }

        #endregion
        #region 암묵적 문단 추가하기 - AddImplicitParagraph(xamlParentElement, htmlNode, inheritedPropertyTable, styleSheet, sourceElementList)

        /// <summary>
        /// 암묵적 문단 추가하기
        /// </summary>
        /// <param name="xamlParentElement">XAML 부모 엘리먼트</param>
        /// <param name="htmlNode">HTML 노드</param>
        /// <param name="inheritedPropertyTable">상속 속성 테이블</param>
        /// <param name="styleSheet">스타일 시트</param>
        /// <param name="sourceElementList">소스 엘리먼트 리스트</param>
        /// <returns>암묵적 문단</returns>
        private static XmlNode AddImplicitParagraph
        (
            XmlElement       xamlParentElement,
            XmlNode          htmlNode,
            Hashtable        inheritedPropertyTable,
            CSSStyleSheet    styleSheet,
            List<XmlElement> sourceElementList
        )
        {
            XmlElement xamlParagraphElement = xamlParentElement.OwnerDocument.CreateElement(null, HTMLToXAMLConverter.XAML_PARAGRAPH, _xamlNamespace);

            XmlNode lastNodeProcessed = null;

            while(htmlNode != null)
            {
                if(htmlNode is XmlComment)
                {
                    DefineInlineFragmentParent((XmlComment)htmlNode, null);
                }
                else if(htmlNode is XmlText)
                {
                    if(htmlNode.Value.Trim().Length > 0)
                    {
                        AddTextRun(xamlParagraphElement, htmlNode.Value);
                    }
                }
                else if(htmlNode is XmlElement)
                {
                    string htmlChildName = ((XmlElement)htmlNode).LocalName.ToLower();

                    if(HTMLSchema.IsBlockElement(htmlChildName))
                    {
                        break;
                    }
                    else
                    {
                        AddInline(xamlParagraphElement, (XmlElement)htmlNode, inheritedPropertyTable, styleSheet, sourceElementList);
                    }
                }

                lastNodeProcessed = htmlNode;

                htmlNode = htmlNode.NextSibling;
            }

            if(xamlParagraphElement.FirstChild != null)
            {
                xamlParentElement.AppendChild(xamlParagraphElement);
            }

            return lastNodeProcessed;
        }

        #endregion
        #region 문단 추가하기 - AddParagraph(xamlParentElement, htmlElement, inheritedPropertyTable, styleSheet, sourceElementList)

        /// <summary>
        /// 문단 추가하기
        /// </summary>
        /// <param name="xamlParentElement">XAML 부모 엘리먼트</param>
        /// <param name="htmlElement">HTML 엘리먼트</param>
        /// <param name="inheritedPropertyTable">상속 속성 테이블</param>
        /// <param name="styleSheet">스타일 시트</param>
        /// <param name="sourceElementList">소스 엘리먼트 리스트</param>
        private static void AddParagraph
        (
            XmlElement       xamlParentElement,
            XmlElement       htmlElement,
            Hashtable        inheritedPropertyTable,
            CSSStyleSheet    styleSheet,
            List<XmlElement> sourceElementList
        )
        {
            Hashtable localPropertyTable;
            Hashtable currentPropertyTable = GetElementProperties
            (
                htmlElement,
                inheritedPropertyTable,
                out localPropertyTable,
                styleSheet,
                sourceElementList
            );

            XmlElement xamlElement = xamlParentElement.OwnerDocument.CreateElement(null, HTMLToXAMLConverter.XAML_PARAGRAPH, _xamlNamespace);

            ApplyLocalPropertyTable(xamlElement, localPropertyTable, true);

            for(XmlNode htmlChildNode = htmlElement.FirstChild; htmlChildNode != null; htmlChildNode = htmlChildNode.NextSibling)
            {
                AddInline(xamlElement, htmlChildNode, currentPropertyTable, styleSheet, sourceElementList);
            }

            xamlParentElement.AppendChild(xamlElement);
        }

        #endregion
        #region 섹션 추가하기 - AddSection(xamlParentElement, htmlElement, inheritedPropertyTable, styleSheet, sourceElementList)

        /// <summary>
        /// 섹션 추가하기
        /// </summary>
        /// <param name="xamlParentElement">XAML 부모 엘리먼트</param>
        /// <param name="htmlElement">HTML 엘리먼트</param>
        /// <param name="inheritedPropertyTable">상속 속성 테이블</param>
        /// <param name="styleSheet">스타일 시트</param>
        /// <param name="sourceElementList">소스 엘리먼트 리스트</param>
        private static void AddSection
        (
            XmlElement       xamlParentElement,
            XmlElement       htmlElement,
            Hashtable        inheritedPropertyTable,
            CSSStyleSheet    styleSheet,
            List<XmlElement> sourceElementList
        )
        {
            bool htmlElementContainsBlocks = false;

            for(XmlNode htmlChildNode = htmlElement.FirstChild; htmlChildNode != null; htmlChildNode = htmlChildNode.NextSibling)
            {
                if(htmlChildNode is XmlElement)
                {
                    string htmlChildName = ((XmlElement)htmlChildNode).LocalName.ToLower();

                    if(HTMLSchema.IsBlockElement(htmlChildName))
                    {
                        htmlElementContainsBlocks = true;

                        break;
                    }
                }
            }

            if(!htmlElementContainsBlocks)
            {
                AddParagraph(xamlParentElement, htmlElement, inheritedPropertyTable, styleSheet, sourceElementList);
            }
            else
            {
                Hashtable localPropertyTable;
                Hashtable currentPropertyTable = GetElementProperties
                (
                    htmlElement,
                    inheritedPropertyTable,
                    out localPropertyTable,
                    styleSheet,
                    sourceElementList
                );

                XmlElement xamlElement = xamlParentElement.OwnerDocument.CreateElement(null, HTMLToXAMLConverter.XAML_SECTION, _xamlNamespace);

                ApplyLocalPropertyTable(xamlElement, localPropertyTable, true);

                if(!xamlElement.HasAttributes)
                {
                    xamlElement = xamlParentElement;
                }

                for(XmlNode htmlChildNode = htmlElement.FirstChild; htmlChildNode != null; htmlChildNode = htmlChildNode != null ? htmlChildNode.NextSibling : null)
                {
                    htmlChildNode = AddBlock(xamlElement, htmlChildNode, currentPropertyTable, styleSheet, sourceElementList);
                }

                if(xamlElement != xamlParentElement)
                {
                    xamlParentElement.AppendChild(xamlElement);
                }
            }
        }

        #endregion
        #region 리스트 항목 추가하기 - AddListItem(xamlListElement, htmlLIElement, inheritedPropertyTable, styleSheet, sourceElementList)

        /// <summary>
        /// 리스트 항목 추가하기
        /// </summary>
        /// <param name="xamlListElement">XAML 리스트 엘리먼트</param>
        /// <param name="htmlListItemElement">HTML 리스트 항목 엘리먼트</param>
        /// <param name="inheritedPropertyTable">상속 속성 테이블</param>
        /// <param name="styleSheet">스타일 시트</param>
        /// <param name="sourceElementList">소스 엘리먼트 리스트</param>
        private static void AddListItem
        (
            XmlElement       xamlListElement,
            XmlElement       htmlListItemElement,
            Hashtable        inheritedPropertyTable,
            CSSStyleSheet    styleSheet,
            List<XmlElement> sourceElementList
        )
        {
            Hashtable localPropertyTable;
            Hashtable currentPropertyTable = GetElementProperties
            (
                htmlListItemElement,
                inheritedPropertyTable,
                out localPropertyTable,
                styleSheet,
                sourceElementList
            );

            XmlElement xamlListItemElement = xamlListElement.OwnerDocument.CreateElement(null, XAML_LIST_ITEM, _xamlNamespace);

            for(XmlNode htmlChildNode = htmlListItemElement.FirstChild; htmlChildNode != null; htmlChildNode = htmlChildNode != null ? htmlChildNode.NextSibling : null)
            {
                htmlChildNode = AddBlock(xamlListItemElement, htmlChildNode, currentPropertyTable, styleSheet, sourceElementList);
            }

            xamlListElement.AppendChild(xamlListItemElement);
        }

        #endregion
        #region 리스트 추가하기 - AddList(xamlParentElement, htmlListElement, inheritedPropertyTable, styleSheet, sourceElementList)

        /// <summary>
        /// 리스트 추가하기
        /// </summary>
        /// <param name="xamlParentElement">XAML 부모 엘리먼트</param>
        /// <param name="htmlListElement">HTML 리스트 엘리먼트</param>
        /// <param name="inheritedPropertyTable">상속 속성 테이블</param>
        /// <param name="styleSheet">스타일 시트</param>
        /// <param name="sourceElementList">소스 엘리먼트 리스트</param>
        private static void AddList
        (
            XmlElement       xamlParentElement,
            XmlElement       htmlListElement,
            Hashtable        inheritedPropertyTable,
            CSSStyleSheet    styleSheet,
            List<XmlElement> sourceElementList
        )
        {
            string htmlListElementName = htmlListElement.LocalName.ToLower();

            Hashtable localPropertyTable;
            Hashtable currentPropertyTable = GetElementProperties
            (
                htmlListElement,
                inheritedPropertyTable,
                out localPropertyTable,
                styleSheet,
                sourceElementList
            );

            XmlElement xamlListElement = xamlParentElement.OwnerDocument.CreateElement(null, XAML_LIST, _xamlNamespace);

            if(htmlListElementName == "ol")
            {
                xamlListElement.SetAttribute(HTMLToXAMLConverter.XAML_LIST_MARKER_STYLE, XAML_LIST_MARKER_STYLE_DECIMAL);
            }
            else
            {
                xamlListElement.SetAttribute(HTMLToXAMLConverter.XAML_LIST_MARKER_STYLE, XAML_LIST_MARKER_STYLE_DISC);
            }

            ApplyLocalPropertyTable(xamlListElement, localPropertyTable, true);

            for(XmlNode htmlChildNode = htmlListElement.FirstChild; htmlChildNode != null; htmlChildNode = htmlChildNode.NextSibling)
            {
                if(htmlChildNode is XmlElement && htmlChildNode.LocalName.ToLower() == "li")
                {
                    sourceElementList.Add((XmlElement)htmlChildNode);

                    AddListItem(xamlListElement, (XmlElement)htmlChildNode, currentPropertyTable, styleSheet, sourceElementList);

                    sourceElementList.RemoveAt(sourceElementList.Count - 1);
                }
            }

            if(xamlListElement.HasChildNodes)
            {
                xamlParentElement.AppendChild(xamlListElement);
            }
        }

        #endregion
        #region 고아 리스트 항목 추가하기 - AddOrphanListItems(xamlParentElement, htmlLIElement, inheritedPropertyTable, styleSheet, sourceElementList)

        /// <summary>
        /// 고아 리스트 항목 추가하기
        /// </summary>
        /// <param name="xamlParentElement">XAML 부모 엘리먼트</param>
        /// <param name="htmlListItemElement">HTML 리스트 항목 엘리먼트</param>
        /// <param name="inheritedPropertyTable">상속 속성 테이블</param>
        /// <param name="styleSheet">스타일 시트</param>
        /// <param name="sourceElementList">소스 엘리먼트 리스트</param>
        /// <returns>XML 엘리먼트</returns>
        private static XmlElement AddOrphanListItems
        (
            XmlElement       xamlParentElement,
            XmlElement       htmlListItemElement,
            Hashtable        inheritedPropertyTable,
            CSSStyleSheet    styleSheet,
            List<XmlElement> sourceElementList
        )
        {
            XmlElement lastProcessedListItemElement = null;

            XmlNode xamlListItemElementPreviousSibling = xamlParentElement.LastChild;

            XmlElement xamlListElement;

            if(xamlListItemElementPreviousSibling != null && xamlListItemElementPreviousSibling.LocalName == XAML_LIST)
            {
                xamlListElement = (XmlElement)xamlListItemElementPreviousSibling;
            }
            else
            {
                xamlListElement = xamlParentElement.OwnerDocument.CreateElement(null, XAML_LIST, _xamlNamespace);

                xamlParentElement.AppendChild(xamlListElement);
            }

            XmlNode htmlChildNode = htmlListItemElement;

            string htmlChildNodeName = htmlChildNode == null ? null : htmlChildNode.LocalName.ToLower();

            while(htmlChildNode != null && htmlChildNodeName == "li")
            {
                AddListItem(xamlListElement, (XmlElement)htmlChildNode, inheritedPropertyTable, styleSheet, sourceElementList);

                lastProcessedListItemElement = (XmlElement)htmlChildNode;

                htmlChildNode = htmlChildNode.NextSibling;

                htmlChildNodeName = htmlChildNode == null ? null : htmlChildNode.LocalName.ToLower();
            }

            return lastProcessedListItemElement;
        }

        #endregion
        #region 단일 셀 테이블에서 셀 구하기 - GetCellFromSingleCellTable(htmlTableElement)

        /// <summary>
        /// 단일 셀 테이블에서 셀 구하기
        /// </summary>
        /// <param name="htmlTableElement">HTML 테이블 엘리먼트</param>
        /// <returns>XML 엘리먼트</returns>
        private static XmlElement GetCellFromSingleCellTable(XmlElement htmlTableElement)
        {
            XmlElement singleCell = null;

            for(XmlNode tableChild = htmlTableElement.FirstChild; tableChild != null; tableChild = tableChild.NextSibling)
            {
                string elementName = tableChild.LocalName.ToLower();

                if(elementName == "tbody" || elementName == "thead" || elementName == "tfoot")
                {
                    if(singleCell != null)
                    {
                        return null;
                    }

                    for(XmlNode tbodyChild = tableChild.FirstChild; tbodyChild != null; tbodyChild = tbodyChild.NextSibling)
                    {
                        if(tbodyChild.LocalName.ToLower() == "tr")
                        {
                            if(singleCell != null)
                            {
                                return null;
                            }

                            for(XmlNode trChild = tbodyChild.FirstChild; trChild != null; trChild = trChild.NextSibling)
                            {
                                string cellName = trChild.LocalName.ToLower();

                                if(cellName == "td" || cellName == "th")
                                {
                                    if(singleCell != null)
                                    {
                                        return null;
                                    }

                                    singleCell = (XmlElement)trChild;
                                }
                            }
                        }
                    }
                }
                else if(tableChild.LocalName.ToLower() == "tr")
                {
                    if(singleCell != null)
                    {
                        return null;
                    }

                    for(XmlNode trChild = tableChild.FirstChild; trChild != null; trChild = trChild.NextSibling)
                    {
                        string cellName = trChild.LocalName.ToLower();

                        if(cellName == "td" || cellName == "th")
                        {
                            if(singleCell != null)
                            {
                                return null;
                            }

                            singleCell = (XmlElement)trChild;
                        }
                    }
                }
            }

            return singleCell;
        }

        #endregion
        #region 활성 행 SPAN 리스트 지우기 - ClearActiveRowSpanList(activeRowSpanList)

        /// <summary>
        /// 활성 행 SPAN 리스트 지우기
        /// </summary>
        /// <param name="activeRowSpanList">활성 행 SPAN 리스트</param>
        private static void ClearActiveRowSpanList(ArrayList activeRowSpanList)
        {
            for(int columnIndex = 0; columnIndex < activeRowSpanList.Count; columnIndex++)
            {
                activeRowSpanList[columnIndex] = 0;
            }
        }

        #endregion
        #region 컬럼 시작 리스트 오름차순 순서 확인하기 - VerifyColumnStartListAscendingOrder(columnStartList)

        /// <summary>
        /// 컬럼 시작 리스트 오름차순 순서 확인하기
        /// </summary>
        /// <param name="columnStartList">컬럼 시작 리스트</param>
        private static void VerifyColumnStartListAscendingOrder(ArrayList columnStartList)
        {
            double columnStart;

            columnStart = -0.01;

            for(int columnIndex = 0; columnIndex < columnStartList.Count; columnIndex++)
            {
                columnStart = (double)columnStartList[columnIndex];
            }
        }

        #endregion
        #region CSS 어트리뷰트 구하기 - GetCSSAttribute(cssStyle, attributeName)

        /// <summary>
        /// CSS 어트리뷰트 구하기
        /// </summary>
        /// <param name="cssStyle">CSS 스타일</param>
        /// <param name="attributeName">어트리뷰트명</param>
        /// <returns>CSS 어트리뷰트</returns>
        private static string GetCSSAttribute(string cssStyle, string attributeName)
        {
            if(cssStyle != null)
            {
                string[] styleValueArray;

                attributeName = attributeName.ToLower();

                styleValueArray = cssStyle.Split(';');

                for(int styleValueIndex = 0; styleValueIndex < styleValueArray.Length; styleValueIndex++)
                {
                    string[] styleNameValueArray;

                    styleNameValueArray = styleValueArray[styleValueIndex].Split(':');

                    if(styleNameValueArray.Length == 2)
                    {
                        if(styleNameValueArray[0].Trim().ToLower() == attributeName)
                        {
                            return styleNameValueArray[1].Trim();
                        }
                    }
                }
            }

            return null;
        }

        #endregion
        #region 길이 값 획득 시도하기 - TryGetLengthValue(lengthString, length)

        /// <summary>
        /// 길이 값 획득 시도하기
        /// </summary>
        /// <param name="lengthString">길이 문자열</param>
        /// <param name="length">길이</param>
        /// <returns>길이 값</returns>
        private static bool TryGetLengthValue(string lengthString, out double length)
        {
            length = double.NaN;

            if(lengthString != null)
            {
                lengthString = lengthString.Trim().ToLower();

                if(lengthString.EndsWith("pt"))
                {
                    lengthString = lengthString.Substring(0, lengthString.Length - 2);

                    if(double.TryParse(lengthString, out length))
                    {
                        length = (length * 96.0) / 72.0;
                    }
                    else
                    {
                        length = double.NaN;
                    }
                }
                else if(lengthString.EndsWith("px"))
                {
                    lengthString = lengthString.Substring(0, lengthString.Length - 2);

                    if(!double.TryParse(lengthString, out length))
                    {
                        length = double.NaN;
                    }
                }
                else
                {
                    if(!double.TryParse(lengthString, out length))
                    {
                        length = double.NaN;
                    }
                }
            }

            return !double.IsNaN(length);
        }

        #endregion
        #region 컬럼 너비 구하기 - GetColumnWidth(htmlTDElement)

        /// <summary>
        /// 컬럼 너비 구하기
        /// </summary>
        /// <param name="htmlTDElement">HTML TD 엘리먼트</param>
        /// <returns>컬럼 너비</returns>
        private static double GetColumnWidth(XmlElement htmlTDElement)
        {
            string columnWidthAsString;
            double columnWidth;

            columnWidthAsString = null;
            columnWidth         = -1;

            columnWidthAsString = GetAttribute(htmlTDElement, "width");

            if(columnWidthAsString == null)
            {
                columnWidthAsString = GetCSSAttribute(GetAttribute(htmlTDElement, "style"), "width");
            }

            if(!TryGetLengthValue(columnWidthAsString, out columnWidth) || columnWidth == 0)
            {
                columnWidth = -1;
            }

            return columnWidth;
        }

        #endregion
        #region 행 SPAN 구하기 - GetRowSpan(htmlTDElement)

        /// <summary>
        /// 행 SPAN 구하기
        /// </summary>
        /// <param name="htmlTDElement">HTML TD 엘리먼트</param>
        /// <returns>행 SPAN</returns>
        private static int GetRowSpan(XmlElement htmlTDElement)
        {
            string rowSpanAsString;
            int    rowSpan;

            rowSpanAsString = GetAttribute((XmlElement)htmlTDElement, "rowspan");

            if(rowSpanAsString != null)
            {
                if(!int.TryParse(rowSpanAsString, out rowSpan))
                {
                    rowSpan = 1;
                }
            }
            else
            {
                rowSpan = 1;
            }

            return rowSpan;
        }

        #endregion
        #region 다음 컬럼 인덱스 구하기 - GetNextColumnIndex(columnIndex, columnWidth, columnStartList, activeRowSpanList)

        /// <summary>
        /// 다음 컬럼 인덱스 구하기
        /// </summary>
        /// <param name="columnIndex">컬럼 인덱스</param>
        /// <param name="columnWidth">컬럼 너비</param>
        /// <param name="columnStartList">컬럼 시작 리스트</param>
        /// <param name="activeRowSpanList">활성 행 SPAN 리스트</param>
        /// <returns>다음 컬럼 인덱스</returns>
        private static int GetNextColumnIndex(int columnIndex, double columnWidth, ArrayList columnStartList, ArrayList activeRowSpanList)
        {
            double columnStart;
            int    spannedColumnIndex;

            columnStart = (double)columnStartList[columnIndex];

            spannedColumnIndex = columnIndex + 1;

            while(spannedColumnIndex < columnStartList.Count && (double)columnStartList[spannedColumnIndex] < columnStart + columnWidth && spannedColumnIndex != -1)
            {
                if((int)activeRowSpanList[spannedColumnIndex] > 0)
                {
                    spannedColumnIndex = -1;
                }
                else
                {
                    spannedColumnIndex++;
                }
            }

            return spannedColumnIndex;
        }

        #endregion
        #region 테이블 행 구조 분석하기 - AnalyzeTableRowStructure(htmlTRElement, columnStartList, activeRowSpanList, tableWidth, styleSheet)

        /// <summary>
        /// 테이블 행 구조 분석하기
        /// </summary>
        /// <param name="htmlTableRowElement">HTML 테이블 행 엘리먼트</param>
        /// <param name="columnStartList">컬럼 시작 리스트</param>
        /// <param name="activeRowSpanList">활성 행 SPAN 리스트</param>
        /// <param name="tableWidth">테이블 너비</param>
        /// <param name="styleSheet">스타일 시트</param>
        /// <returns>처리 결과</returns>
        private static double AnalyzeTableRowStructure
        (
            XmlElement    htmlTableRowElement,
            ArrayList     columnStartList,
            ArrayList     activeRowSpanList,
            double        tableWidth,
            CSSStyleSheet styleSheet
        )
        {
            double columnWidth;

            if(!htmlTableRowElement.HasChildNodes)
            {
                return 0;
            }

            bool columnWidthsAvailable = true;

            double columnStart = 0;

            XmlNode htmlChildNode = htmlTableRowElement.FirstChild;

            int columnIndex = 0;

            double trWidth = 0;

            if(columnIndex < activeRowSpanList.Count)
            {
                if((double)columnStartList[columnIndex] == columnStart)
                {
                    while(columnIndex < activeRowSpanList.Count && (int)activeRowSpanList[columnIndex] > 0)
                    {
                        activeRowSpanList[columnIndex] = (int)activeRowSpanList[columnIndex] - 1;

                        columnIndex++;

                        columnStart = (double)columnStartList[columnIndex];
                    }
                }
            }

            while(htmlChildNode != null && columnWidthsAvailable)
            {
                VerifyColumnStartListAscendingOrder(columnStartList);

                switch(htmlChildNode.LocalName.ToLower())
                {
                    case "td" :

                        if(columnIndex < columnStartList.Count)
                        {
                            if(columnStart < (double)columnStartList[columnIndex])
                            {
                                columnStartList.Insert(columnIndex, columnStart);

                                activeRowSpanList.Insert(columnIndex, 0);
                            }
                        }
                        else
                        {
                            columnStartList.Add(columnStart);

                            activeRowSpanList.Add(0);
                        }

                        columnWidth = GetColumnWidth((XmlElement)htmlChildNode);

                        if(columnWidth != -1)
                        {
                            int nextColumnIndex;

                            int rowSpan = GetRowSpan((XmlElement)htmlChildNode);

                            nextColumnIndex = GetNextColumnIndex(columnIndex, columnWidth, columnStartList, activeRowSpanList);

                            if(nextColumnIndex != -1)
                            {
                                for(int spannedColumnIndex = columnIndex; spannedColumnIndex < nextColumnIndex; spannedColumnIndex++)
                                {
                                    activeRowSpanList[spannedColumnIndex] = rowSpan - 1;
                                }

                                columnIndex = nextColumnIndex;

                                columnStart = columnStart + columnWidth;

                                if(columnIndex < activeRowSpanList.Count)
                                {
                                    if((double)columnStartList[columnIndex] == columnStart)
                                    {
                                        while(columnIndex < activeRowSpanList.Count && (int)activeRowSpanList[columnIndex] > 0)
                                        {
                                            activeRowSpanList[columnIndex] = (int)activeRowSpanList[columnIndex] - 1;

                                            columnIndex++;

                                            columnStart = (double)columnStartList[columnIndex];
                                        }
                                    }
                                }
                            }
                            else
                            {
                                columnWidthsAvailable = false;
                            }
                        }
                        else
                        {
                            columnWidthsAvailable = false;
                        }

                        break;

                    default :

                        break;
                }

                htmlChildNode = htmlChildNode.NextSibling;
            }

            if(columnWidthsAvailable)
            {
                trWidth = columnStart;
            }
            else
            {
                trWidth = 0;
            }

            return trWidth;
        }

        #endregion
        #region 테이블 바디 구조 분석하기 - AnalyzeTableBodyStructure(htmlTableBodyElement, columnStartList, activeRowSpanList, tableWidth, styleSheet)

        /// <summary>
        /// 테이블 바디 구조 분석하기
        /// </summary>
        /// <param name="htmlTableBodyElement">HTML 테이블 바디 엘리먼트</param>
        /// <param name="columnStartList">컬럼 시작 리스트</param>
        /// <param name="activeRowSpanList">활성 행 SPAN 리스트</param>
        /// <param name="tableWidth">테이블 너비</param>
        /// <param name="styleSheet">스타일 시트</param>
        /// <returns>처리 결과</returns>
        private static double AnalyzeTableBodyStructure
        (
            XmlElement    htmlTableBodyElement,
            ArrayList     columnStartList,
            ArrayList     activeRowSpanList,
            double        tableWidth,
            CSSStyleSheet styleSheet
        )
        {
            double tableBodyWidth = 0;

            bool columnWidthsAvailable = true;

            if(!htmlTableBodyElement.HasChildNodes)
            {
                return tableBodyWidth;
            }

            ClearActiveRowSpanList(activeRowSpanList);

            XmlNode htmlChildNode = htmlTableBodyElement.FirstChild;

            while(htmlChildNode != null && columnWidthsAvailable)
            {
                switch(htmlChildNode.LocalName.ToLower())
                {
                    case "tr" :

                        double trWidth = AnalyzeTableRowStructure
                        (
                            (XmlElement)htmlChildNode,
                            columnStartList,
                            activeRowSpanList,
                            tableBodyWidth,
                            styleSheet
                        );

                        if(trWidth > tableBodyWidth)
                        {
                            tableBodyWidth = trWidth;
                        }

                        break;

                    case "td" :

                        columnWidthsAvailable = false;

                        break;

                    default :

                        break;
                }

                htmlChildNode = htmlChildNode.NextSibling;
            }

            ClearActiveRowSpanList(activeRowSpanList);

            return columnWidthsAvailable ? tableBodyWidth : 0;
        }

        #endregion
        #region 테이블 구조 분석하기 - AnalyzeTableStructure(htmlTableElement, styleSheet)

        /// <summary>
        /// 테이블 구조 분석하기
        /// </summary>
        /// <param name="htmlTableElement">HTML 테이블 엘리먼트</param>
        /// <param name="styleSheet">스타일 시트</param>
        /// <returns>배열 리스트</returns>
        private static ArrayList AnalyzeTableStructure(XmlElement htmlTableElement, CSSStyleSheet styleSheet)
        {
            if(!htmlTableElement.HasChildNodes)
            {
                return null;
            }

            bool columnWidthsAvailable = true;

            ArrayList columnStartList   = new ArrayList();
            ArrayList activeRowSpanList = new ArrayList();

            XmlNode htmlChildNode = htmlTableElement.FirstChild;

            double tableWidth = 0;

            while(htmlChildNode != null && columnWidthsAvailable)
            {
                switch(htmlChildNode.LocalName.ToLower())
                {
                    case "tbody" :

                        double tbodyWidth = AnalyzeTableBodyStructure
                        (
                            (XmlElement)htmlChildNode,
                            columnStartList,
                            activeRowSpanList,
                            tableWidth,
                            styleSheet
                        );

                        if(tbodyWidth > tableWidth)
                        {
                            tableWidth = tbodyWidth;
                        }
                        else if(tbodyWidth == 0)
                        {
                            columnWidthsAvailable = false;
                        }

                        break;

                    case "tr" :

                        double trWidth = AnalyzeTableRowStructure
                        (
                            (XmlElement)htmlChildNode,
                            columnStartList,
                            activeRowSpanList,
                            tableWidth,
                            styleSheet
                        );

                        if(trWidth > tableWidth)
                        {
                            tableWidth = trWidth;
                        }
                        else if(trWidth == 0)
                        {
                            columnWidthsAvailable = false;
                        }

                        break;

                    case "td" :

                        columnWidthsAvailable = false;

                        break;

                    default :

                        break;
                }

                htmlChildNode = htmlChildNode.NextSibling;
            }

            if(columnWidthsAvailable)
            {
                columnStartList.Add(tableWidth);

                VerifyColumnStartListAscendingOrder(columnStartList);
            }
            else
            {
                columnStartList = null;
            }

            return columnStartList;
        }

        #endregion
        #region 테이블 컬럼 추가하기 - AddTableColumn(xamlTableElement, htmlColumnElement, inheritedPropertyTable, styleSheet, sourceElementList)

        /// <summary>
        /// 테이블 컬럼 추가하기
        /// </summary>
        /// <param name="xamlTableElement">XAML 테이블 엘리먼트</param>
        /// <param name="htmlColumnElement">HTML 컬럼 엘리먼트</param>
        /// <param name="inheritedPropertyTable">상속 속성 테이블</param>
        /// <param name="styleSheet">스타일 시트</param>
        /// <param name="sourceElementList">소스 엘리먼트 리스트</param>
        private static void AddTableColumn
        (
            XmlElement       xamlTableElement,
            XmlElement       htmlColumnElement,
            Hashtable        inheritedPropertyTable,
            CSSStyleSheet    styleSheet,
            List<XmlElement> sourceElementList
        )
        {
            Hashtable localPropertyTable;
            Hashtable currentPropertyTable = GetElementProperties
            (
                htmlColumnElement,
                inheritedPropertyTable,
                out localPropertyTable,
                styleSheet,
                sourceElementList
            );

            XmlElement xamlTableColumnElement = xamlTableElement.OwnerDocument.CreateElement(null, XAML_TABLE_COLUMN, _xamlNamespace);

            xamlTableElement.AppendChild(xamlTableColumnElement);
        }

        #endregion
        #region 테이블 컬럼 그룹 추가하기 - AddTableColumnGroup(xamlTableElement, htmlColumnGroupElement, inheritedPropertyTable, styleSheet, sourceElementList)

        /// <summary>
        /// 테이블 컬럼 그룹 추가하기
        /// </summary>
        /// <param name="xamlTableElement">XAML 테이블 엘리먼트</param>
        /// <param name="htmlColumnGroupElement">HTML 컬럼 그룹 엘리먼트</param>
        /// <param name="inheritedPropertyTable">상속 속성 테이블</param>
        /// <param name="styleSheet">스타일 시트</param>
        /// <param name="sourceElementList">소스 엘리먼트 리스트</param>
        private static void AddTableColumnGroup
        (
            XmlElement       xamlTableElement,
            XmlElement       htmlColumnGroupElement,
            Hashtable        inheritedPropertyTable,
            CSSStyleSheet    styleSheet,
            List<XmlElement> sourceElementList
        )
        {
            Hashtable localPropertyTable;
            Hashtable currentPropertyTable = GetElementProperties
            (
                htmlColumnGroupElement,
                inheritedPropertyTable,
                out localPropertyTable,
                styleSheet,
                sourceElementList
            );

            for(XmlNode htmlNode = htmlColumnGroupElement.FirstChild; htmlNode != null; htmlNode = htmlNode.NextSibling)
            {
                if(htmlNode is XmlElement && htmlNode.LocalName.ToLower() == "col")
                {
                    AddTableColumn(xamlTableElement, (XmlElement)htmlNode, currentPropertyTable, styleSheet, sourceElementList);
                }
            }
        }

        #endregion
        #region 컬럼 정보 추가하기 - AddColumnInformation(htmlTableElement, xamlTableElement, columnStartAllRowList, currentPropertyTable, styleSheet, sourceElementList)

        /// <summary>
        /// 컬럼 정보 추가하기
        /// </summary>
        /// <param name="htmlTableElement">HTML 테이블 엘리먼트</param>
        /// <param name="xamlTableElement">XAML 테이블 엘리먼트</param>
        /// <param name="columnStartAllRowList">컬럼 시작 모든 행 리스트</param>
        /// <param name="currentPropertyTable">현재 속성 테이블</param>
        /// <param name="styleSheet">스타일 시트</param>
        /// <param name="sourceElementList">소스 엘리먼트 리스트</param>
        private static void AddColumnInformation
        (
            XmlElement       htmlTableElement,
            XmlElement       xamlTableElement,
            ArrayList        columnStartAllRowList,
            Hashtable        currentPropertyTable,
            CSSStyleSheet    styleSheet,
            List<XmlElement> sourceElementList
        )
        {
            if(columnStartAllRowList != null)
            {
                for(int columnIndex = 0; columnIndex < columnStartAllRowList.Count - 1; columnIndex++)
                {
                    XmlElement xamlColumnElement;

                    xamlColumnElement = xamlTableElement.OwnerDocument.CreateElement(null, XAML_TABLE_COLUMN, _xamlNamespace);

                    xamlColumnElement.SetAttribute
                    (
                        XAML_WIDTH,
                        ((double)columnStartAllRowList[columnIndex + 1] - (double)columnStartAllRowList[columnIndex]).ToString()
                    );

                    xamlTableElement.AppendChild(xamlColumnElement);
                }
            }
            else
            {
                for(XmlNode htmlChildNode = htmlTableElement.FirstChild; htmlChildNode != null; htmlChildNode = htmlChildNode.NextSibling)
                {
                    if(htmlChildNode.LocalName.ToLower() == "colgroup")
                    {
                        AddTableColumnGroup(xamlTableElement, (XmlElement)htmlChildNode, currentPropertyTable, styleSheet, sourceElementList);
                    }
                    else if(htmlChildNode.LocalName.ToLower() == "col")
                    {
                        AddTableColumn(xamlTableElement, (XmlElement)htmlChildNode, currentPropertyTable, styleSheet, sourceElementList);
                    }
                    else if(htmlChildNode is XmlElement)
                    {
                        break;
                    }
                }
            }
        }

        #endregion
        #region 활성 행 SPAN 리스트 초기화하기 - InitializeActiveRowSpanList(activeRowSpanList, count)

        /// <summary>
        /// 활성 행 SPAN 리스트 초기화하기
        /// </summary>
        /// <param name="activeRowSpanList">활성 행 SPAN 리스트</param>
        /// <param name="count">카운트</param>
        private static void InitializeActiveRowSpanList(ArrayList activeRowSpanList, int count)
        {
            for(int columnIndex = 0; columnIndex < count; columnIndex++)
            {
                activeRowSpanList.Add(0);
            }
        }

        #endregion
        #region 테이블 셀 엘리먼트에 속성 적용하기 - ApplyPropertiesToTableCellElement(htmlChildNode, xamlTableCellElement)

        /// <summary>
        /// 테이블 셀 엘리먼트에 속성 적용하기
        /// </summary>
        /// <param name="htmlChildNode">HTML 자식 노드</param>
        /// <param name="xamlTableCellElement">XAML 테이블 셀 엘리먼트</param>
        private static void ApplyPropertiesToTableCellElement(XmlElement htmlChildNode, XmlElement xamlTableCellElement)
        {
            xamlTableCellElement.SetAttribute(XAML_TABLE_CELL_BORDER_THICKNESS, "1,1,1,1");

            xamlTableCellElement.SetAttribute(XAML_TABLE_CELL_BORDER_BRUSH, XAML_BRUSHES_BLACK);

            string rowSpanString = GetAttribute((XmlElement)htmlChildNode, "rowspan");

            if(rowSpanString != null)
            {
                xamlTableCellElement.SetAttribute(XAML_TABLE_CELL_ROW_SPAN, rowSpanString);
            }
        }

        #endregion
        #region 컬럼 SPAN 계산하기 - CalculateColumnSpan(columnIndex, columnWidth, columnStartList)

        /// <summary>
        /// 컬럼 SPAN 계산하기
        /// </summary>
        /// <param name="columnIndex">컬럼 인덱스</param>
        /// <param name="columnWidth">컬럼 너비</param>
        /// <param name="columnStartList">컬럼 시작 리스트</param>
        /// <returns>컬럼 SPAN</returns>
        private static int CalculateColumnSpan(int columnIndex, double columnWidth, ArrayList columnStartList)
        {
            double columnSpanningValue;
            int    columnSpanningIndex;
            int    columnSpan;
            double subsidaryColumnWidth;

            columnSpanningIndex  = columnIndex;
            columnSpanningValue  = 0;
            columnSpan           = 0;
            subsidaryColumnWidth = 0;

            while(columnSpanningValue < columnWidth && columnSpanningIndex < columnStartList.Count - 1)
            {
                subsidaryColumnWidth = (double)columnStartList[columnSpanningIndex + 1] - (double)columnStartList[columnSpanningIndex];

                columnSpanningValue += subsidaryColumnWidth;

                columnSpanningIndex++;
            }

            columnSpan = columnSpanningIndex - columnIndex;

            return columnSpan;
        }

        #endregion
        #region 데이터 셀에 데이터 추가하기 - AddDataToTableCell(xamlTableCellElement, htmlDataStartNode, currentPropertyTable, styleSheet, sourceElementList)

        /// <summary>
        /// 데이터 셀에 데이터 추가하기
        /// </summary>
        /// <param name="xamlTableCellElement">XAML 테이블 셀 엘리먼트</param>
        /// <param name="htmlDataStartNode">HTML 데이터 시작 노드</param>
        /// <param name="currentPropertyTable">현재 속성 테이블</param>
        /// <param name="styleSheet">스타일 시트</param>
        /// <param name="sourceElementList">소스 엘리먼트 리스트</param>
        private static void AddDataToTableCell
        (
            XmlElement       xamlTableCellElement,
            XmlNode          htmlDataStartNode,
            Hashtable        currentPropertyTable,
            CSSStyleSheet    styleSheet,
            List<XmlElement> sourceElementList
        )
        {
            for(XmlNode htmlChildNode = htmlDataStartNode; htmlChildNode != null; htmlChildNode = htmlChildNode != null ? htmlChildNode.NextSibling : null)
            {
                htmlChildNode = AddBlock(xamlTableCellElement, htmlChildNode, currentPropertyTable, styleSheet, sourceElementList);
            }
        }

        #endregion
        #region 테이블 행에 테이블 셀 추가하기 - AddTableCellsToTableRow(xamlTableRowElement, htmlTDStartNode, currentPropertyTable, columnStartList,
            activeRowSpanList, styleSheet, sourceElementList)

        /// <summary>
        /// 테이블 행에 테이블 셀 추가하기
        /// </summary>
        /// <param name="xamlTableRowElement">XAML 테이블 행 엘리먼트</param>
        /// <param name="htmlTDStartNode">HTML TD 시작 노드</param>
        /// <param name="currentPropertyTable">현재 속성 테이블</param>
        /// <param name="columnStartList">컬럼 시작 리스트</param>
        /// <param name="activeRowSpanList">활성 행 SPAN 리스트</param>
        /// <param name="styleSheet">스타일 시트</param>
        /// <param name="sourceElementList">소스 엘리먼트 리스트</param>
        /// <returns>XML 노드</returns>
        private static XmlNode AddTableCellsToTableRow
        (
            XmlElement       xamlTableRowElement,
            XmlNode          htmlTDStartNode,
            Hashtable        currentPropertyTable,
            ArrayList        columnStartList,
            ArrayList        activeRowSpanList,
            CSSStyleSheet    styleSheet,
            List<XmlElement> sourceElementList
        )
        {
            XmlNode htmlChildNode = htmlTDStartNode;
            double  columnStart   = 0;
            double  columnWidth   = 0;
            int     columnIndex   = 0;
            int     columnSpan    = 0;

            while
            (
                htmlChildNode != null                        &&
                htmlChildNode.LocalName.ToLower() != "tr"    &&
                htmlChildNode.LocalName.ToLower() != "tbody" &&
                htmlChildNode.LocalName.ToLower() != "thead" &&
                htmlChildNode.LocalName.ToLower() != "tfoot"
            )
            {
                if(htmlChildNode.LocalName.ToLower() == "td" || htmlChildNode.LocalName.ToLower() == "th")
                {
                    XmlElement xamlTableCellElement = xamlTableRowElement.OwnerDocument.CreateElement(null, XAML_TABLE_CELL, _xamlNamespace);

                    sourceElementList.Add((XmlElement)htmlChildNode);

                    Hashtable tdElementLocalPropertyTable;
                    Hashtable tdElementCurrentPropertyTable = GetElementProperties
                    (
                        (XmlElement)htmlChildNode,
                        currentPropertyTable,
                        out tdElementLocalPropertyTable,
                        styleSheet,
                        sourceElementList
                    );

                    ApplyPropertiesToTableCellElement((XmlElement)htmlChildNode, xamlTableCellElement);

                    if(columnStartList != null)
                    {
                        while(columnIndex < activeRowSpanList.Count && (int)activeRowSpanList[columnIndex] > 0)
                        {
                            activeRowSpanList[columnIndex] = (int)activeRowSpanList[columnIndex] - 1;

                            columnIndex++;
                        }

                        columnStart = (double)columnStartList[columnIndex];
                        columnWidth = GetColumnWidth((XmlElement)htmlChildNode);
                        columnSpan  = CalculateColumnSpan(columnIndex, columnWidth, columnStartList);
                        int rowSpan = GetRowSpan((XmlElement)htmlChildNode);

                        xamlTableCellElement.SetAttribute(XAML_TABLE_CELL_COLUMN_SPAN, columnSpan.ToString());

                        for(int spannedColumnIndex = columnIndex; spannedColumnIndex < columnIndex + columnSpan; spannedColumnIndex++)
                        {
                            activeRowSpanList[spannedColumnIndex] = (rowSpan - 1);
                        }

                        columnIndex = columnIndex + columnSpan;
                    }

                    AddDataToTableCell
                    (
                        xamlTableCellElement,
                        htmlChildNode.FirstChild,
                        tdElementCurrentPropertyTable,
                        styleSheet,
                        sourceElementList
                    );

                    if(xamlTableCellElement.HasChildNodes)
                    {
                        xamlTableRowElement.AppendChild(xamlTableCellElement);
                    }

                    sourceElementList.RemoveAt(sourceElementList.Count - 1);

                    htmlChildNode = htmlChildNode.NextSibling;
                }
                else
                {
                    htmlChildNode = htmlChildNode.NextSibling;
                }
            }

            return htmlChildNode;
        }

        #endregion
        #region 테이블 바디에 테이블 행 추가하기 - AddTableRowsToTableBody(xamlTableBodyElement, htmlTableRowStartNode, currentPropertyTable, columnStartList, styleSheet, sourceElementList)

        /// <summary>
        /// 테이블 바디에 테이블 행 추가하기
        /// </summary>
        /// <param name="xamlTableBodyElement">XAML 테이블 바디 엘리먼트</param>
        /// <param name="htmlTableRowStartNode">HTML 테이블 행 시작 노드</param>
        /// <param name="currentPropertyTable">현재 속성 테이블</param>
        /// <param name="columnStartList">컬럼 시작 리스트</param>
        /// <param name="styleSheet">스타일 시트</param>
        /// <param name="sourceElementList">소스 엘리먼트 리스트</param>
        /// <returns>XML 노드</returns>
        private static XmlNode AddTableRowsToTableBody
        (
            XmlElement       xamlTableBodyElement,
            XmlNode          htmlTableRowStartNode,
            Hashtable        currentPropertyTable,
            ArrayList        columnStartList,
            CSSStyleSheet    styleSheet,
            List<XmlElement> sourceElementList
        )
        {
            XmlNode htmlChildNode = htmlTableRowStartNode;

            ArrayList activeRowSpanList = null;

            if(columnStartList != null)
            {
                activeRowSpanList = new ArrayList();

                InitializeActiveRowSpanList(activeRowSpanList, columnStartList.Count);
            }

            while(htmlChildNode != null && htmlChildNode.LocalName.ToLower() != "tbody")
            {
                if(htmlChildNode.LocalName.ToLower() == "tr")
                {
                    XmlElement xamlTableRowElement = xamlTableBodyElement.OwnerDocument.CreateElement(null, XAML_TABLE_ROW, _xamlNamespace);

                    sourceElementList.Add((XmlElement)htmlChildNode);

                    Hashtable trElementLocalPropertyTable;
                    Hashtable trElementCurrentPropertyTable = GetElementProperties
                    (
                        (XmlElement)htmlChildNode,
                        currentPropertyTable,
                        out trElementLocalPropertyTable,
                        styleSheet,
                        sourceElementList
                    );

                    AddTableCellsToTableRow
                    (
                        xamlTableRowElement,
                        htmlChildNode.FirstChild,
                        trElementCurrentPropertyTable,
                        columnStartList,
                        activeRowSpanList,
                        styleSheet,
                        sourceElementList
                    );

                    if(xamlTableRowElement.HasChildNodes)
                    {
                        xamlTableBodyElement.AppendChild(xamlTableRowElement);
                    }

                    sourceElementList.RemoveAt(sourceElementList.Count - 1);

                    htmlChildNode = htmlChildNode.NextSibling;
                }
                else if(htmlChildNode.LocalName.ToLower() == "td")
                {
                    XmlElement xamlTableRowElement = xamlTableBodyElement.OwnerDocument.CreateElement(null, XAML_TABLE_ROW, _xamlNamespace);

                    htmlChildNode = AddTableCellsToTableRow
                    (
                        xamlTableRowElement,
                        htmlChildNode,
                        currentPropertyTable,
                        columnStartList,
                        activeRowSpanList,
                        styleSheet,
                        sourceElementList
                    );

                    if(xamlTableRowElement.HasChildNodes)
                    {
                        xamlTableBodyElement.AppendChild(xamlTableRowElement);
                    }
                }
                else
                {
                    htmlChildNode = htmlChildNode.NextSibling;
                }
            }

            return htmlChildNode;
        }

        #endregion
        #region 테이블 추가하기 - AddTable(xamlParentElement, htmlTableElement, inheritedPropertyTable, styleSheet, sourceElementList)

        /// <summary>
        /// 테이블 추가하기
        /// </summary>
        /// <param name="xamlParentElement">XAML 부모 엘리먼트</param>
        /// <param name="htmlTableElement">HTML 테이블 엘리먼트</param>
        /// <param name="inheritedPropertyTable">상속 속성 테이블</param>
        /// <param name="styleSheet">스타일 시트</param>
        /// <param name="sourceElementList">소스 엘리먼트 리스트</param>
        private static void AddTable
        (
            XmlElement       xamlParentElement,
            XmlElement       htmlTableElement,
            Hashtable        inheritedPropertyTable,
            CSSStyleSheet    styleSheet,
            List<XmlElement> sourceElementList
        )
        {
            Hashtable localPropertyTable;
            Hashtable currentPropertyTable = GetElementProperties
            (
                htmlTableElement,
                inheritedPropertyTable,
                out localPropertyTable,
                styleSheet,
                sourceElementList
            );

            XmlElement singleCell = GetCellFromSingleCellTable(htmlTableElement);

            if(singleCell != null)
            {
                sourceElementList.Add(singleCell);

                for(XmlNode htmlChildNode = singleCell.FirstChild; htmlChildNode != null; htmlChildNode = htmlChildNode != null ? htmlChildNode.NextSibling : null)
                {
                    htmlChildNode = AddBlock(xamlParentElement, htmlChildNode, currentPropertyTable, styleSheet, sourceElementList);
                }

                sourceElementList.RemoveAt(sourceElementList.Count - 1);
            }
            else
            {
                XmlElement xamlTableElement = xamlParentElement.OwnerDocument.CreateElement(null, XAML_TABLE, _xamlNamespace);

                ArrayList columnStartList = AnalyzeTableStructure(htmlTableElement, styleSheet);

                AddColumnInformation(htmlTableElement, xamlTableElement, columnStartList, currentPropertyTable, styleSheet, sourceElementList);

                XmlNode htmlChildNode = htmlTableElement.FirstChild;

                while(htmlChildNode != null)
                {
                    string htmlChildName = htmlChildNode.LocalName.ToLower();

                    if(htmlChildName == "tbody" || htmlChildName == "thead" || htmlChildName == "tfoot")
                    {
                        XmlElement xamlTableBodyElement = xamlTableElement.OwnerDocument.CreateElement(null, XAML_TABLE_ROW_GROUP, _xamlNamespace);

                        xamlTableElement.AppendChild(xamlTableBodyElement);

                        sourceElementList.Add((XmlElement)htmlChildNode);

                        Hashtable tbodyElementLocalPropertyTable;
                        Hashtable tbodyElementCurrentPropertyTable = GetElementProperties
                        (
                            (XmlElement)htmlChildNode,
                            currentPropertyTable,
                            out tbodyElementLocalPropertyTable,
                            styleSheet,
                            sourceElementList
                        );

                        AddTableRowsToTableBody
                        (
                            xamlTableBodyElement,
                            htmlChildNode.FirstChild,
                            tbodyElementCurrentPropertyTable,
                            columnStartList,
                            styleSheet,
                            sourceElementList
                        );

                        if(xamlTableBodyElement.HasChildNodes)
                        {
                            xamlTableElement.AppendChild(xamlTableBodyElement);
                        }

                        sourceElementList.RemoveAt(sourceElementList.Count - 1);

                        htmlChildNode = htmlChildNode.NextSibling;
                    }
                    else if(htmlChildName == "tr")
                    {
                        XmlElement xamlTableBodyElement = xamlTableElement.OwnerDocument.CreateElement(null, XAML_TABLE_ROW_GROUP, _xamlNamespace);

                        htmlChildNode = AddTableRowsToTableBody
                        (
                            xamlTableBodyElement,
                            htmlChildNode,
                            currentPropertyTable,
                            columnStartList,
                            styleSheet,
                            sourceElementList
                        );

                        if(xamlTableBodyElement.HasChildNodes)
                        {
                            xamlTableElement.AppendChild(xamlTableBodyElement);
                        }
                    }
                    else
                    {
                        htmlChildNode = htmlChildNode.NextSibling;
                    }
                }

                if(xamlTableElement.HasChildNodes)
                {
                    xamlParentElement.AppendChild(xamlTableElement);
                }
            }
        }

        #endregion
        #region 블럭 추가하기 - AddBlock(xamlParentElement, htmlNode, inheritedPropertyTable, styleSheet, sourceElementList)

        /// <summary>
        /// 블럭 추가하기
        /// </summary>
        /// <param name="xamlParentElement">XAML 부모 엘리먼트</param>
        /// <param name="htmlNode">HTML 노드</param>
        /// <param name="inheritedPropertyTable">상속 속성 테이블</param>
        /// <param name="styleSheet">스타일 시트</param>
        /// <param name="sourceElementList">소스 엘리먼트 리스트</param>
        /// <returns>블럭</returns>
        private static XmlNode AddBlock
        (
            XmlElement       xamlParentElement,
            XmlNode          htmlNode,
            Hashtable        inheritedPropertyTable,
            CSSStyleSheet    styleSheet,
            List<XmlElement> sourceElementList
        )
        {
            if(htmlNode is XmlComment)
            {
                DefineInlineFragmentParent((XmlComment)htmlNode, null);
            }
            else if(htmlNode is XmlText)
            {
                htmlNode = AddImplicitParagraph
                (
                    xamlParentElement,
                    htmlNode,
                    inheritedPropertyTable,
                    styleSheet,
                    sourceElementList
                );
            }
            else if(htmlNode is XmlElement)
            {
                XmlElement htmlElement = (XmlElement)htmlNode;

                string htmlElementName      = htmlElement.LocalName;
                string htmlElementNamespace = htmlElement.NamespaceURI;

                if(htmlElementNamespace != HTMLParser.XHTML_NAMESPACE)
                {
                    return htmlElement;
                }

                sourceElementList.Add(htmlElement);

                htmlElementName = htmlElementName.ToLower();

                switch(htmlElementName)
                {
                    case "html"       :
                    case "body"       :
                    case "div"        :
                    case "form"       :
                    case "pre"        :
                    case "blockquote" :
                    case "caption"    :
                    case "center"     :
                    case "cite"       :

                        AddSection(xamlParentElement, htmlElement, inheritedPropertyTable, styleSheet, sourceElementList);

                        break;

                    case "p"        :
                    case "h1"       :
                    case "h2"       :
                    case "h3"       :
                    case "h4"       :
                    case "h5"       :
                    case "h6"       :
                    case "nsrtitle" :
                    case "textarea" :
                    case "dd"       :
                    case "dl"       :
                    case "dt"       :
                    case "tt"       :

                        AddParagraph(xamlParentElement, htmlElement, inheritedPropertyTable, styleSheet, sourceElementList);

                        break;

                    case "ol"   :
                    case "ul"   :
                    case "dir"  :
                    case "menu" :

                        AddList(xamlParentElement, htmlElement, inheritedPropertyTable, styleSheet, sourceElementList);

                        break;

                    case "li" :

                        htmlNode = AddOrphanListItems
                        (
                            xamlParentElement,
                            htmlElement,
                            inheritedPropertyTable,
                            styleSheet,
                            sourceElementList
                        );

                        break;

                    case "img" :

                        AddImage(xamlParentElement, htmlElement, inheritedPropertyTable, styleSheet, sourceElementList);

                        break;

                    case "table" :

                        AddTable(xamlParentElement, htmlElement, inheritedPropertyTable, styleSheet, sourceElementList);

                        break;

                    case "tbody" :
                    case "tfoot" :
                    case "thead" :
                    case "tr"    :
                    case "td"    :
                    case "th"    :

                        goto default;

                    case "style"  :
                    case "meta"   :
                    case "head"   :
                    case "title"  :
                    case "script" :

                        break;

                    default :

                        htmlNode = AddImplicitParagraph
                        (
                            xamlParentElement,
                            htmlElement,
                            inheritedPropertyTable,
                            styleSheet,
                            sourceElementList
                        );

                        break;
                }

                sourceElementList.RemoveAt(sourceElementList.Count - 1);
            }

            return htmlNode;
        }

        #endregion
        #region 인라인 프래그먼트 추출하기 - ExtractInlineFragment(xamlFlowDocumentElement)

        /// <summary>
        /// 인라인 프래그먼트 추출하기
        /// </summary>
        /// <param name="xamlFlowDocumentElement">XAML 플로우 문서 엘리먼트</param>
        /// <returns>XML 엘리먼트</returns>
        private static XmlElement ExtractInlineFragment(XmlElement xamlFlowDocumentElement)
        {
            if(_inlineFragmentParentElement != null)
            {
                if(_inlineFragmentParentElement.LocalName == HTMLToXAMLConverter.XAML_SPAN)
                {
                    xamlFlowDocumentElement = _inlineFragmentParentElement;
                }
                else
                {
                    xamlFlowDocumentElement = xamlFlowDocumentElement.OwnerDocument.CreateElement(null, HTMLToXAMLConverter.XAML_SPAN, _xamlNamespace);

                    while (_inlineFragmentParentElement.FirstChild != null)
                    {
                        XmlNode copyNode = _inlineFragmentParentElement.FirstChild;

                        _inlineFragmentParentElement.RemoveChild(copyNode);

                        xamlFlowDocumentElement.AppendChild(copyNode);
                    }
                }
            }

            return xamlFlowDocumentElement;
        }

        #endregion
    }
}

 

▶ XAMLToHTMLConverter.cs

using System;
using System.IO;
using System.Text;
using System.Xml;

namespace TestProject
{
    /// <summary>
    /// XAML→HTML 변환자
    /// </summary>
    internal static class XAMLToHTMLConverter
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Internal

        #region XAML을 HTML로 변환하기 - ConvertXAMLToHTML(xaml)

        /// <summary>
        /// XAML을 HTML로 변환하기
        /// </summary>
        /// <param name="xaml">XAML</param>
        /// <returns>HTML</returns>
        internal static string ConvertXAMLToHTML(string xaml)
        {
            XmlTextReader reader;
            StringBuilder stringBuilder;
            XmlTextWriter writer;

            reader = new XmlTextReader(new StringReader(xaml));

            stringBuilder = new StringBuilder(100);

            writer = new XmlTextWriter(new StringWriter(stringBuilder));

            if(!WriteFlowDocument(reader, writer))
            {
                return string.Empty;
            }

            string html = stringBuilder.ToString();

            return html;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////// Private

        #region 다음 토큰 읽기 - ReadNextToken(reader)

        /// <summary>
        /// 다음 토큰 읽기
        /// </summary>
        /// <param name="reader">리더기</param>
        /// <returns>처리 결과</returns>
        private static bool ReadNextToken(XmlReader reader)
        {
            while(reader.Read())
            {
                switch(reader.NodeType)
                {
                    case XmlNodeType.Element               :
                    case XmlNodeType.EndElement            :
                    case XmlNodeType.None                  :
                    case XmlNodeType.CDATA                 :
                    case XmlNodeType.Text                  :
                    case XmlNodeType.SignificantWhitespace :

                        return true;

                    case XmlNodeType.Whitespace :

                        if(reader.XmlSpace == XmlSpace.Preserve)
                        {
                            return true;
                        }

                        break;

                    case XmlNodeType.EndEntity       :
                    case XmlNodeType.EntityReference :

                        break;

                    case XmlNodeType.Comment :

                        return true;

                    case XmlNodeType.ProcessingInstruction :
                    case XmlNodeType.DocumentType          :
                    case XmlNodeType.XmlDeclaration        :
                    default                                :

                        break;
                }
            }

            return false;
        }

        #endregion
        #region XAML 색상 구문 분석하기 - ParseXAMLColor(color)

        /// <summary>
        /// XAML 색상 구문 분석하기
        /// </summary>
        /// <param name="color">색상</param>
        /// <returns>XAML 색상</returns>
        private static string ParseXAMLColor(string color)
        {
            if(color.StartsWith("#"))
            {
                color = "#" + color.Substring(3);
            }

            return color;
        }

        #endregion
        #region XAML 두께 구문 분석하기 - ParseXAMLThickness(thickness)

        /// <summary>
        /// XAML 두께 구문 분석하기
        /// </summary>
        /// <param name="thickness">두께</param>
        /// <returns>XAML 두께</returns>
        private static string ParseXAMLThickness(string thickness)
        {
            string[] valueArray = thickness.Split(',');

            for(int i = 0; i < valueArray.Length; i++)
            {
                double value;

                if(double.TryParse(valueArray[i], out value))
                {
                    valueArray[i] = Math.Ceiling(value).ToString();
                }
                else
                {
                    valueArray[i] = "1";
                }
            }

            string cssThickness;

            switch(valueArray.Length)
            {
                case 1 :

                    cssThickness = thickness;

                    break;

                case 2 :

                    cssThickness = valueArray[1] + " " + valueArray[0];

                    break;

                case 4 :

                    cssThickness = valueArray[1] + " " + valueArray[2] + " " + valueArray[3] + " " + valueArray[0];

                    break;

                default :

                    cssThickness = valueArray[0];

                    break;
            }

            return cssThickness;
        }

        #endregion
        #region 형식 적용 속성 쓰기 - WriteFormattingProperties(reader, writer, inlineStyleStringBuilder)

        /// <summary>
        /// 형식 적용 속성 쓰기
        /// </summary>
        /// <param name="reader">리더기</param>
        /// <param name="writer">작성기</param>
        /// <param name="inlineStyleStringBuilder">인라인 스타일 문자열 빌더</param>
        private static void WriteFormattingProperties(XmlTextReader reader, XmlTextWriter writer, StringBuilder inlineStyleStringBuilder)
        {
            inlineStyleStringBuilder.Remove(0, inlineStyleStringBuilder.Length);

            if(!reader.HasAttributes)
            {
                return;
            }

            bool borderSet = false;

            while(reader.MoveToNextAttribute())
            {
                string css = null;

                switch(reader.Name)
                {
                    case "Background" :

                        css = "background-color:" + ParseXAMLColor(reader.Value) + ";";

                        break;

                    case "FontFamily" :

                        css = "font-family:" + reader.Value + ";";

                        break;

                    case "FontStyle" :

                        css = "font-style:" + reader.Value.ToLower() + ";";

                        break;

                    case "FontWeight" :

                        css = "font-weight:" + reader.Value.ToLower() + ";";

                        break;

                    case "FontStretch" :

                        break;

                    case "FontSize" :

                        css = "font-size:" + reader.Value + ";";

                        break;

                    case "Foreground" :

                        css = "color:" + ParseXAMLColor(reader.Value) + ";";

                        break;

                    case "TextDecorations" :

                        css = "text-decoration:underline;";

                        break;

                    case "TextEffects" :

                        break;

                    case "Emphasis" :

                        break;

                    case "StandardLigatures" :

                        break;

                    case "Variants" :

                        break;

                    case "Capitals" :

                        break;

                    case "Fraction" :

                        break;

                    case "Padding" :

                        css = "padding:" + ParseXAMLThickness(reader.Value) + ";";

                        break;

                    case "Margin" :

                        css = "margin:" + ParseXAMLThickness(reader.Value) + ";";

                        break;

                    case "BorderThickness" :

                        css = "border-width:" + ParseXAMLThickness(reader.Value) + ";";

                        borderSet = true;

                        break;

                    case "BorderBrush" :

                        css = "border-color:" + ParseXAMLColor(reader.Value) + ";";

                        borderSet = true;

                        break;

                    case "LineHeight" :

                        break;

                    case "TextIndent" :

                        css = "text-indent:" + reader.Value + ";";

                        break;

                    case "TextAlignment" :

                        css = "text-align:" + reader.Value + ";";

                        break;

                    case "IsKeptTogether" :

                        break;

                    case "IsKeptWithNext" :

                        break;

                    case "ColumnBreakBefore" :

                        break;

                    case "PageBreakBefore" :

                        break;

                    case "FlowDirection" :

                        break;

                    case "Width" :

                        css = "width:" + reader.Value + ";";

                        break;

                    case "ColumnSpan" :

                        writer.WriteAttributeString("COLSPAN", reader.Value);

                        break;

                    case "RowSpan" :

                        writer.WriteAttributeString("ROWSPAN", reader.Value);

                        break;
                }

                if(css != null)
                {
                    inlineStyleStringBuilder.Append(css);
                }
            }

            if(borderSet)
            {
                inlineStyleStringBuilder.Append("border-style:solid;mso-element:para-border-div;");
            }

            reader.MoveToElement();
        }

        #endregion
        #region 복합 속성 추가하기 - AddComplexProperty(reader, inlineStyleStringBuilder)

        /// <summary>
        /// 복합 속성 추가하기
        /// </summary>
        /// <param name="reader">리더기</param>
        /// <param name="inlineStyleStringBuilder">인라인 스타일 문자열 빌더</param>
        private static void AddComplexProperty(XmlTextReader reader, StringBuilder inlineStyleStringBuilder)
        {
            if(inlineStyleStringBuilder != null && reader.Name.EndsWith(".TextDecorations"))
            {
                inlineStyleStringBuilder.Append("text-decoration:underline;");
            }

            WriteElementContent(reader, null, null);
        }

        #endregion
        #region 엘리먼트 쓰기 - WriteElement(reader, writer, inlineStyleStringBuilder)

        /// <summary>
        /// 엘리먼트 쓰기
        /// </summary>
        /// <param name="reader">리더기</param>
        /// <param name="writer">작성기</param>
        /// <param name="inlineStyleStringBuilder">인라인 스타일 문자열 빌더</param>
        private static void WriteElement(XmlTextReader reader, XmlTextWriter writer, StringBuilder inlineStyleStringBuilder)
        {
            if(writer == null)
            {
                WriteElementContent(reader, null, null);
            }
            else
            {
                string htmlElementName = null;

                switch(reader.Name)
                {
                    case "Run"  :
                    case "Span" :

                        htmlElementName = "SPAN";

                        break;

                    case "InlineUIContainer" :

                        htmlElementName = "SPAN";

                        break;

                    case "Bold" :

                        htmlElementName = "B";

                        break;

                    case "Italic" :

                        htmlElementName = "I";

                        break;

                    case "Paragraph" :

                        htmlElementName = "P";

                        break;

                    case "BlockUIContainer" :

                        htmlElementName = "DIV";

                        break;

                    case "Section" :

                        htmlElementName = "DIV";

                        break;

                    case "Table" :

                        htmlElementName = "TABLE";

                        break;

                    case "TableColumn" :

                        htmlElementName = "COL";

                        break;

                    case "TableRowGroup" :

                        htmlElementName = "TBODY";

                        break;

                    case "TableRow" :

                        htmlElementName = "TR";

                        break;

                    case "TableCell" :

                        htmlElementName = "TD";

                        break;

                    case "List" :

                        string marker = reader.GetAttribute("MarkerStyle");

                        if(marker == null || marker == "None" || marker == "Disc" || marker == "Circle" || marker == "Square" || marker == "Box")
                        {
                            htmlElementName = "UL";
                        }
                        else
                        {
                            htmlElementName = "OL";
                        }

                        break;

                    case "ListItem" :

                        htmlElementName = "LI";

                        break;

                    default :

                        htmlElementName = null;

                        break;
                }

                if(writer != null && htmlElementName != null)
                {
                    writer.WriteStartElement(htmlElementName);

                    WriteFormattingProperties(reader, writer, inlineStyleStringBuilder);

                    WriteElementContent(reader, writer, inlineStyleStringBuilder);

                    writer.WriteEndElement();
                }
                else
                {
                    WriteElementContent(reader, null, null);
                }
            }
        }

        #endregion
        #region 엘리먼트 내용 쓰기 - WriteElementContent(reader, writer, inlineStyleStringBuilder)

        /// <summary>
        /// 엘리먼트 내용 쓰기
        /// </summary>
        /// <param name="reader">리더기</param>
        /// <param name="writer">작성기</param>
        /// <param name="inlineStyleStringBuilder">인라인 스타일 문자열 빌더</param>
        private static void WriteElementContent(XmlTextReader reader, XmlTextWriter writer, StringBuilder inlineStyleStringBuilder)
        {
            bool elementContentStarted = false;

            if(reader.IsEmptyElement)
            {
                if(writer != null && !elementContentStarted && inlineStyleStringBuilder.Length > 0)
                {
                    writer.WriteAttributeString("STYLE", inlineStyleStringBuilder.ToString());

                    inlineStyleStringBuilder.Remove(0, inlineStyleStringBuilder.Length);
                }

                elementContentStarted = true;
            }
            else
            {
                while(ReadNextToken(reader) && reader.NodeType != XmlNodeType.EndElement)
                {
                    switch(reader.NodeType)
                    {
                        case XmlNodeType.Element :

                            if(reader.Name.Contains("."))
                            {
                                AddComplexProperty(reader, inlineStyleStringBuilder);
                            }
                            else
                            {
                                if(writer != null && !elementContentStarted && inlineStyleStringBuilder.Length > 0)
                                {
                                    writer.WriteAttributeString("STYLE", inlineStyleStringBuilder.ToString());

                                    inlineStyleStringBuilder.Remove(0, inlineStyleStringBuilder.Length);
                                }

                                elementContentStarted = true;

                                WriteElement(reader, writer, inlineStyleStringBuilder);
                            }

                            break;

                        case XmlNodeType.Comment :

                            if(writer != null)
                            {
                                if(!elementContentStarted && inlineStyleStringBuilder.Length > 0)
                                {
                                    writer.WriteAttributeString("STYLE", inlineStyleStringBuilder.ToString());
                                }

                                writer.WriteComment(reader.Value);
                            }

                            elementContentStarted = true;

                            break;

                        case XmlNodeType.CDATA                 :
                        case XmlNodeType.Text                  :
                        case XmlNodeType.SignificantWhitespace :

                            if(writer != null)
                            {
                                if(!elementContentStarted && inlineStyleStringBuilder.Length > 0)
                                {
                                    writer.WriteAttributeString("STYLE", inlineStyleStringBuilder.ToString());
                                }

                                writer.WriteString(reader.Value);
                            }

                            elementContentStarted = true;

                            break;
                    }
                }
            }
        }

        #endregion
        #region 플로우 문서 쓰기 - WriteFlowDocument(reader, writer)

        /// <summary>
        /// 플로우 문서 쓰기
        /// </summary>
        /// <param name="reader">리더기</param>
        /// <param name="writer">작성기</param>
        /// <returns>처리 결과</returns>
        private static bool WriteFlowDocument(XmlTextReader reader, XmlTextWriter writer)
        {
            if(!ReadNextToken(reader))
            {
                return false;
            }

            if(reader.NodeType != XmlNodeType.Element || reader.Name != "FlowDocument")
            {
                return false;
            }

            StringBuilder stringBuilder = new StringBuilder();

            writer.WriteStartElement("HTML");
            writer.WriteStartElement("BODY");

            WriteFormattingProperties(reader, writer, stringBuilder);

            WriteElementContent(reader, writer, stringBuilder);

            writer.WriteEndElement();
            writer.WriteEndElement();

            return true;
        }

        #endregion
    }
}

 

▶ HTMLRichTextBoxBehavior.cs

using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Markup;
using System.Windows.Navigation;

namespace TestProject
{
    /// <summary>
    /// HTML 리치 텍스트 박스 동작
    /// </summary>
    public class HTMLRichTextBoxBehavior : DependencyObject
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Dependency Property
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 텍스트 속성 - TextProperty

        /// <summary>
        /// 텍스트 속성
        /// </summary>
        public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached
        (
            "Text",
            typeof(string),
            typeof(HTMLRichTextBoxBehavior),
            new UIPropertyMetadata(null, TextPropertyChangedCallback)
        );

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 텍스트 구하기 - GetText(richTextBox)

        /// <summary>
        /// 텍스트 구하기
        /// </summary>
        /// <param name="richTextBox">리치 텍스트 박스</param>
        /// <returns>텍스트</returns>
        public static string GetText(RichTextBox richTextBox)
        {
            return (string)richTextBox.GetValue(TextProperty);
        }

        #endregion
        #region 텍스트 설정하기 - SetText(richTextBox, value)

        /// <summary>
        /// 텍스트 설정하기
        /// </summary>
        /// <param name="richTextBox">리치 텍스트 박스</param>
        /// <param name="value">값</param>
        public static void SetText(RichTextBox richTextBox, string value)
        {
            richTextBox.SetValue(TextProperty, value);
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////// Private
        ////////////////////////////////////////////////////////////////////// Event

        #region 하이퍼링크 탐색 요청시 처리하기 - hyperlink_RequestNavigate(sender, e)

        /// <summary>
        /// 하이퍼링크 탐색 요청시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private static void hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
        {
            Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));

            e.Handled = true;
        }

        #endregion

        ////////////////////////////////////////////////////////////////////// Function

        #region 텍스트 속성 변경시 콜백 처리하기 - TextPropertyChangedCallback(d, e)

        /// <summary>
        /// 텍스트 속성 변경시 콜백 처리하기
        /// </summary>
        /// <param name="d">의존 객체</param>
        /// <param name="e">이벤트 인자</param>
        private static void TextPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            RichTextBox richTextBox = d as RichTextBox;

            string text = (e.NewValue ?? string.Empty).ToString();

            string xaml = HTMLToXAMLConverter.ConvertHTMLToXAML(text, true);

            FlowDocument document = XamlReader.Parse(xaml) as FlowDocument;

            AddHyperlinkEventHandler(document);

            richTextBox.Document = document;
        }

        #endregion

        #region 논리적 자식 열거 가능형 구하기 - GetLogicalChildEnumerable(parent)

        /// <summary>
        /// 논리적 자식 열거 가능형 구하기
        /// </summary>
        /// <param name="parent">부모</param>
        /// <returns>비주얼 자식 열거 가능형</returns>
        private static IEnumerable<DependencyObject> GetLogicalChildEnumerable(DependencyObject parent)
        {
            foreach(DependencyObject child in LogicalTreeHelper.GetChildren(parent).OfType<DependencyObject>())
            {
                yield return child;

                foreach(DependencyObject grandChild in GetLogicalChildEnumerable(child))
                {
                    yield return grandChild;
                }
            }
        }

        #endregion
        #region 하이퍼링크 이벤트 핸들러 추가하기 - AddHyperlinkEventHandler(document)

        /// <summary>
        /// 하이퍼링크 이벤트 핸들러 추가하기
        /// </summary>
        /// <param name="document">문서</param>
        private static void AddHyperlinkEventHandler(FlowDocument document)
        {
            if(document == null)
            {
                return;
            }

            GetLogicalChildEnumerable(document).OfType<Hyperlink>().ToList().ForEach(hyperlink => hyperlink.RequestNavigate += hyperlink_RequestNavigate);
        }

        #endregion
    }
}

 

▶ WebBrowserBehavior.cs

using System.Windows;
using System.Windows.Controls;

namespace TestProject
{
    /// <summary>
    /// 웹 브라우저 동작
    /// </summary>
    public class WebBrowserBehavior
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Dependency Property
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 바디 속성 - BodyProperty

        /// <summary>
        /// 바디 속성
        /// </summary>
        public static readonly DependencyProperty BodyProperty = DependencyProperty.RegisterAttached
        (
            "Body",
            typeof(string),
            typeof(WebBrowserBehavior),
            new PropertyMetadata(BodyPropertyChangedCallback)
        );

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 바디 구하기 - GetBody(d)

        /// <summary>
        /// 바디 구하기
        /// </summary>
        /// <param name="d">의존 객체</param>
        /// <returns>바디</returns>
        public static string GetBody(DependencyObject d)
        {
            return (string)d.GetValue(BodyProperty);
        }

        #endregion
        #region 바디 설정하기 - SetBody(d, value)

        /// <summary>
        /// 바디 설정하기
        /// </summary>
        /// <param name="d">의존 객체</param>
        /// <param name="value">값</param>
        public static void SetBody(DependencyObject d, string value)
        {
            d.SetValue(BodyProperty, value);
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////// Private

        #region 바디 속성 변경시 콜백 처리하기 - BodyPropertyChangedCallback(d, e)

        /// <summary>
        /// 바디 속성 변경시 콜백 처리하기
        /// </summary>
        /// <param name="d">의존 객체</param>
        /// <param name="e">이벤트 인자</param>
        private static void BodyPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            WebBrowser webBrowser = d as WebBrowser;

            webBrowser?.NavigateToString((string)e.NewValue);
        }

        #endregion
    }
}

 

▶ MainWindow.xaml

<Window x:Class="TestProject.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:TestProject"
    Width="800"
    Height="600"
    Title="HTML을 XAML로 변환하기"
    FontFamily="나눔고딕코딩"
    FontSize="16">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <TextBox Name="textBox" Grid.Row="0"
            HorizontalAlignment="Stretch"
            VerticalAlignment="Stretch"
            TextWrapping="Wrap"
            Text="&lt;p&gt;This is a test of HTML &lt;b&gt;Bold&lt;/b&gt; and &lt;i&gt;Italic&lt;/i&gt;&lt;/p&gt;&lt;p&gt;
                  &lt;a href=&quot;http://www.microsoft.com&quot; &gt;The Link&lt;/a&gt;&lt;/p&gt;" />
        <RichTextBox Grid.Row="1"
            Margin="0 10 0 0"
            local:HTMLRichTextBoxBehavior.Text="{Binding ElementName=textBox, Path=Text}"
            IsDocumentEnabled="True"
            IsReadOnly="True" />
        <Border Grid.Row="2"
            Margin="0 10 0 0"
            BorderThickness="1"
            BorderBrush="#abadb3"
            SnapsToDevicePixels="True">
            <WebBrowser
                local:WebBrowserBehavior.Body="{Binding ElementName=textBox, Path=Text}" />
        </Border>
    </Grid>
</Window>
728x90
반응형
그리드형(광고전용)
Posted by icodebroker

댓글을 달아 주세요