Files
JRCookbook/JRCookbookBusiness/Converters/CssStylesheet.cs
2026-03-07 19:22:22 -06:00

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;
}
}
}
}