728x90
반응형
728x170
■ HTML을 XAML로 변환하는 방법을 보여준다.
▶ 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="<p>This is a test of HTML <b>Bold</b> and <i>Italic</i></p><p>
<a href="http://www.microsoft.com" >The Link</a></p>" />
<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
반응형
그리드형(광고전용)
'C# > WPF' 카테고리의 다른 글
[C#/WPF] UWP API 사용하기 (0) | 2021.04.26 |
---|---|
[C#/WPF] TOTP(Time-based One-Time Password) 사용하기 (0) | 2021.04.26 |
[C#/WPF] Dispatcher 클래스 : Invoke 메소드를 사용해 UI 업데이트 하기 (0) | 2021.04.22 |
[C#/WPF] 관리자 권한으로 실행하기 (0) | 2021.04.03 |
[C#/WPF] 가상 키보드 사용하기 (0) | 2021.03.27 |
[C#/WPF] LogicalTreeHelper 클래스 : GetChildren 정적 메소드를 사용해 논리적 자식 열거 가능형 구하기 (0) | 2021.03.20 |
[C#/WPF] ListBox 엘리먼트 : 마우스 진입시 애니메이션 사용하기 (0) | 2021.03.07 |
[C#/WPF] ListBox 클래스 : 항목 선택시 슬라이딩 리스트 박스 사용하기 (0) | 2021.03.07 |
[C#/WPF] 누겟 설치 : VirtualDesktop.WPF (0) | 2021.03.06 |
[C#/WPF] SystemParameters 클래스 : VirtualScreenWidth/VirtualScreenHeight 정적 속성을 사용해 듀얼 모니터 전체 화면 윈도우 표시하기 (0) | 2021.03.06 |
댓글을 달아 주세요