255 lines
8.9 KiB
C#
255 lines
8.9 KiB
C#
// // Copyright (c) Microsoft. All rights reserved.
|
|
// // Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Text;
|
|
using System.Xml;
|
|
|
|
namespace HtmlToXamlDemo
|
|
{
|
|
internal class CssStylesheet
|
|
{
|
|
private List<StyleDefinition> _styleDefinitions;
|
|
// Constructor
|
|
public CssStylesheet(XmlElement htmlElement)
|
|
{
|
|
if (htmlElement != null)
|
|
{
|
|
DiscoverStyleDefinitions(htmlElement);
|
|
}
|
|
}
|
|
|
|
// Recursively traverses an html tree, discovers STYLE elements and creates a style definition table
|
|
// for further cascading style application
|
|
public void DiscoverStyleDefinitions(XmlElement htmlElement)
|
|
{
|
|
if (htmlElement.LocalName.ToLower() == "link")
|
|
{
|
|
return;
|
|
// Add LINK elements processing for included stylesheets
|
|
// <LINK href="http://sc.msn.com/global/css/ptnr/orange.css" type=text/css \r\nrel=stylesheet>
|
|
}
|
|
|
|
if (htmlElement.LocalName.ToLower() != "style")
|
|
{
|
|
// This is not a STYLE element. Recurse into it
|
|
for (var htmlChildNode = htmlElement.FirstChild;
|
|
htmlChildNode != null;
|
|
htmlChildNode = htmlChildNode.NextSibling)
|
|
{
|
|
if (htmlChildNode is XmlElement)
|
|
{
|
|
DiscoverStyleDefinitions((XmlElement) htmlChildNode);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Add style definitions from this style.
|
|
|
|
// Collect all text from this style definition
|
|
var stylesheetBuffer = new StringBuilder();
|
|
|
|
for (var htmlChildNode = htmlElement.FirstChild;
|
|
htmlChildNode != null;
|
|
htmlChildNode = htmlChildNode.NextSibling)
|
|
{
|
|
if (htmlChildNode is XmlText || htmlChildNode is XmlComment)
|
|
{
|
|
stylesheetBuffer.Append(RemoveComments(htmlChildNode.Value));
|
|
}
|
|
}
|
|
|
|
// CssStylesheet has the following syntactical structure:
|
|
// @import declaration;
|
|
// selector { definition }
|
|
// where "selector" is one of: ".classname", "tagname"
|
|
// It can contain comments in the following form: /*...*/
|
|
|
|
var nextCharacterIndex = 0;
|
|
while (nextCharacterIndex < stylesheetBuffer.Length)
|
|
{
|
|
// Extract selector
|
|
var selectorStart = nextCharacterIndex;
|
|
while (nextCharacterIndex < stylesheetBuffer.Length && stylesheetBuffer[nextCharacterIndex] != '{')
|
|
{
|
|
// Skip declaration directive starting from @
|
|
if (stylesheetBuffer[nextCharacterIndex] == '@')
|
|
{
|
|
while (nextCharacterIndex < stylesheetBuffer.Length &&
|
|
stylesheetBuffer[nextCharacterIndex] != ';')
|
|
{
|
|
nextCharacterIndex++;
|
|
}
|
|
selectorStart = nextCharacterIndex + 1;
|
|
}
|
|
nextCharacterIndex++;
|
|
}
|
|
|
|
if (nextCharacterIndex < stylesheetBuffer.Length)
|
|
{
|
|
// Extract definition
|
|
var definitionStart = nextCharacterIndex;
|
|
while (nextCharacterIndex < stylesheetBuffer.Length && stylesheetBuffer[nextCharacterIndex] != '}')
|
|
{
|
|
nextCharacterIndex++;
|
|
}
|
|
|
|
// Define a style
|
|
if (nextCharacterIndex - definitionStart > 2)
|
|
{
|
|
AddStyleDefinition(
|
|
stylesheetBuffer.ToString(selectorStart, definitionStart - selectorStart),
|
|
stylesheetBuffer.ToString(definitionStart + 1, nextCharacterIndex - definitionStart - 2));
|
|
}
|
|
|
|
// Skip closing brace
|
|
if (nextCharacterIndex < stylesheetBuffer.Length)
|
|
{
|
|
Debug.Assert(stylesheetBuffer[nextCharacterIndex] == '}');
|
|
nextCharacterIndex++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Returns a string with all c-style comments replaced by spaces
|
|
private string RemoveComments(string text)
|
|
{
|
|
var commentStart = text.IndexOf("/*", StringComparison.Ordinal);
|
|
if (commentStart < 0)
|
|
{
|
|
return text;
|
|
}
|
|
|
|
var commentEnd = text.IndexOf("*/", commentStart + 2, StringComparison.Ordinal);
|
|
if (commentEnd < 0)
|
|
{
|
|
return text.Substring(0, commentStart);
|
|
}
|
|
|
|
return text.Substring(0, commentStart) + " " + RemoveComments(text.Substring(commentEnd + 2));
|
|
}
|
|
|
|
public void AddStyleDefinition(string selector, string definition)
|
|
{
|
|
// Notrmalize parameter values
|
|
selector = selector.Trim().ToLower();
|
|
definition = definition.Trim().ToLower();
|
|
if (selector.Length == 0 || definition.Length == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_styleDefinitions == null)
|
|
{
|
|
_styleDefinitions = new List<StyleDefinition>();
|
|
}
|
|
|
|
var simpleSelectors = selector.Split(',');
|
|
|
|
foreach (string t in simpleSelectors)
|
|
{
|
|
var simpleSelector = t.Trim();
|
|
if (simpleSelector.Length > 0)
|
|
{
|
|
_styleDefinitions.Add(new StyleDefinition(simpleSelector, definition));
|
|
}
|
|
}
|
|
}
|
|
|
|
public string GetStyle(string elementName, List<XmlElement> sourceContext)
|
|
{
|
|
Debug.Assert(sourceContext.Count > 0);
|
|
Debug.Assert(elementName == sourceContext[sourceContext.Count - 1].LocalName);
|
|
|
|
// Add id processing for style selectors
|
|
if (_styleDefinitions != null)
|
|
{
|
|
for (var i = _styleDefinitions.Count - 1; i >= 0; i--)
|
|
{
|
|
var selector = _styleDefinitions[i].Selector;
|
|
|
|
var selectorLevels = selector.Split(' ');
|
|
|
|
var indexInSelector = selectorLevels.Length - 1;
|
|
var indexInContext = sourceContext.Count - 1;
|
|
var selectorLevel = selectorLevels[indexInSelector].Trim();
|
|
|
|
if (MatchSelectorLevel(selectorLevel, sourceContext[sourceContext.Count - 1]))
|
|
{
|
|
return _styleDefinitions[i].Definition;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private bool MatchSelectorLevel(string selectorLevel, XmlElement xmlElement)
|
|
{
|
|
if (selectorLevel.Length == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var indexOfDot = selectorLevel.IndexOf('.');
|
|
var indexOfPound = selectorLevel.IndexOf('#');
|
|
|
|
string selectorClass = null;
|
|
string selectorId = null;
|
|
string selectorTag = null;
|
|
if (indexOfDot >= 0)
|
|
{
|
|
if (indexOfDot > 0)
|
|
{
|
|
selectorTag = selectorLevel.Substring(0, indexOfDot);
|
|
}
|
|
selectorClass = selectorLevel.Substring(indexOfDot + 1);
|
|
}
|
|
else if (indexOfPound >= 0)
|
|
{
|
|
if (indexOfPound > 0)
|
|
{
|
|
selectorTag = selectorLevel.Substring(0, indexOfPound);
|
|
}
|
|
selectorId = selectorLevel.Substring(indexOfPound + 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;
|
|
}
|
|
|
|
private class StyleDefinition
|
|
{
|
|
public readonly string Definition;
|
|
public readonly string Selector;
|
|
|
|
public StyleDefinition(string selector, string definition)
|
|
{
|
|
Selector = selector;
|
|
Definition = definition;
|
|
}
|
|
}
|
|
}
|
|
} |