270 lines
7.9 KiB
C#
270 lines
7.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
|
|
namespace ToolBuddy.ThirdParty.VectorGraphics
|
|
{
|
|
internal class SVGPropertySheet : Dictionary<string, string> { }
|
|
|
|
internal class SVGStyleSheet
|
|
{
|
|
private List<KeyValuePair<string, SVGPropertySheet>> m_Selectors = new List<KeyValuePair<string, SVGPropertySheet>>();
|
|
|
|
public SVGPropertySheet this[string key]
|
|
{
|
|
get
|
|
{
|
|
int i = m_Selectors.FindIndex(x => x.Key == key);
|
|
if (i != -1)
|
|
return m_Selectors[i].Value;
|
|
return null;
|
|
}
|
|
set
|
|
{
|
|
var v = new KeyValuePair<string, SVGPropertySheet>(key, value);
|
|
int i = m_Selectors.FindIndex(x => x.Key == key);
|
|
if (i != -1)
|
|
m_Selectors[i] = v;
|
|
m_Selectors.Add(v);
|
|
}
|
|
}
|
|
|
|
public IEnumerable<string> selectors
|
|
{
|
|
get { return m_Selectors.Select(x => x.Key); }
|
|
}
|
|
|
|
public int Count
|
|
{
|
|
get { return m_Selectors.Count; }
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
m_Selectors.Clear();
|
|
}
|
|
};
|
|
|
|
internal static class SVGStyleSheetUtils
|
|
{
|
|
public static SVGStyleSheet Parse(string cssText)
|
|
{
|
|
var result = new SVGStyleSheet();
|
|
var tokens = Tokenize(cssText);
|
|
|
|
var sheet = new SVGStyleSheet();
|
|
while (ParseSelector(tokens, sheet))
|
|
{
|
|
foreach (var sel in sheet.selectors)
|
|
{
|
|
if (result.selectors.Contains(sel))
|
|
CombineProperties(result[sel], sheet[sel]);
|
|
else
|
|
result[sel] = sheet[sel];
|
|
}
|
|
sheet.Clear();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public static SVGPropertySheet ParseInline(string cssText)
|
|
{
|
|
var tokens = Tokenize(cssText);
|
|
var props = new SVGPropertySheet();
|
|
ParseProperties(tokens, props);
|
|
return props;
|
|
}
|
|
|
|
private static bool ParseSelector(List<string> tokens, SVGStyleSheet sheet)
|
|
{
|
|
if (tokens.Count == 0)
|
|
return false;
|
|
|
|
var newSheet = new SVGStyleSheet();
|
|
while (true)
|
|
{
|
|
var selectorName = PopToken(tokens);
|
|
newSheet[selectorName] = new SVGPropertySheet();
|
|
|
|
while (PeekToken(tokens) == ",")
|
|
PopToken(tokens);
|
|
|
|
if (PeekToken(tokens) == "" || PeekToken(tokens) == "{")
|
|
break;
|
|
}
|
|
|
|
var sep = PopToken(tokens);
|
|
if (sep != "{")
|
|
{
|
|
Debug.LogError("Invalid CSS selector opening bracket: \"" + sep + "\"");
|
|
return false;
|
|
}
|
|
|
|
var props = new SVGPropertySheet();
|
|
ParseProperties(tokens, props);
|
|
|
|
// Transfer properties to the new selectors
|
|
foreach (var key in newSheet.selectors)
|
|
sheet[key] = CopyProperties(props);
|
|
|
|
sep = PopToken(tokens);
|
|
if (sep != "}")
|
|
{
|
|
Debug.LogError("Invalid CSS selector closing bracket: \"" + sep + "\"");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static void CombineProperties(SVGPropertySheet first, SVGPropertySheet second)
|
|
{
|
|
foreach (var key in second.Keys)
|
|
first[key] = second[key];
|
|
}
|
|
|
|
private static SVGPropertySheet CopyProperties(SVGPropertySheet props)
|
|
{
|
|
var newProps = new SVGPropertySheet();
|
|
foreach (var v in props)
|
|
newProps[v.Key] = v.Value;
|
|
return newProps;
|
|
}
|
|
|
|
private static bool ParseProperties(List<string> tokens, SVGPropertySheet props)
|
|
{
|
|
string name;
|
|
string value;
|
|
while (ParseProperty(tokens, out name, out value))
|
|
{
|
|
props[name] = value;
|
|
while (PeekToken(tokens) == ";")
|
|
PopToken(tokens);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static bool ParseProperty(List<string> tokens, out string name, out string value)
|
|
{
|
|
name = null;
|
|
value = null;
|
|
|
|
if (PeekToken(tokens) == "" || PeekToken(tokens) == "}")
|
|
return false;
|
|
|
|
name = PopToken(tokens);
|
|
|
|
var sep = PopToken(tokens);
|
|
if (sep != ":")
|
|
{
|
|
Debug.LogError("Invalid CSS property separator: \"" + sep + "\"");
|
|
return false;
|
|
}
|
|
|
|
value = "";
|
|
while (PeekToken(tokens) != "" && PeekToken(tokens) != ";" && PeekToken(tokens) != "}")
|
|
{
|
|
value = (value == "") ? PopToken(tokens) : value + " " + PopToken(tokens);
|
|
if (PeekToken(tokens) == "(")
|
|
value += ParseParenValue(tokens);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static string ParseParenValue(List<string> tokens)
|
|
{
|
|
var opening = PopToken(tokens);
|
|
if (opening != "(")
|
|
{
|
|
Debug.LogError("Invaid CSS value opening");
|
|
return "";
|
|
}
|
|
|
|
var value = opening;
|
|
while (PeekToken(tokens) != "" && PeekToken(tokens) != ")")
|
|
value += PopToken(tokens);
|
|
|
|
if (PeekToken(tokens) != ")")
|
|
{
|
|
Debug.LogError("Invaid CSS value closing");
|
|
return "";
|
|
}
|
|
|
|
value += PopToken(tokens);
|
|
return value;
|
|
}
|
|
|
|
/// <summary>Breaks a CSS input into tokens</summary>
|
|
/// <param name="cssText">The CSS text</param>
|
|
/// <returns>A list of tokens</returns>
|
|
public static List<string> Tokenize(string cssText)
|
|
{
|
|
var tokens = new List<string>();
|
|
|
|
cssText = cssText.Replace(System.Environment.NewLine, ""); // Remove newlines
|
|
cssText = Regex.Replace(cssText, @"/\*.*?\*/", ""); // Remove CSS comments
|
|
cssText = Regex.Replace(cssText, @"<!--.*?-->", ""); // Remove XML comments
|
|
|
|
int from = 0;
|
|
int to = 0;
|
|
|
|
while (from < cssText.Length)
|
|
{
|
|
while (from < cssText.Length && IsWhitespace(cssText[from]))
|
|
++from;
|
|
|
|
to = from;
|
|
|
|
while (to < cssText.Length && !IsSeparator(cssText[to]))
|
|
++to;
|
|
|
|
if (from == to)
|
|
{
|
|
if (from < cssText.Length)
|
|
tokens.Add(cssText[from].ToString());
|
|
++to;
|
|
}
|
|
else
|
|
{
|
|
tokens.Add(cssText.Substring(from, to-from));
|
|
}
|
|
|
|
from = to;
|
|
}
|
|
|
|
return tokens;
|
|
}
|
|
|
|
private static string PeekToken(List<string> tokens)
|
|
{
|
|
if (tokens.Count == 0)
|
|
return "";
|
|
return tokens[0];
|
|
}
|
|
|
|
private static string PopToken(List<string> tokens)
|
|
{
|
|
if (tokens.Count == 0)
|
|
return "";
|
|
var tok = tokens[0];
|
|
tokens.RemoveAt(0);
|
|
return tok;
|
|
}
|
|
|
|
private static bool IsSeparator(char ch)
|
|
{
|
|
return IsWhitespace(ch) || ch == ';' || ch == ':' || ch == '{' || ch == '}' || ch == '(' || ch == ')' || ch == ',';
|
|
}
|
|
|
|
private static bool IsWhitespace(char ch)
|
|
{
|
|
return ch == ' ' || ch == '\n' || ch == '\t';
|
|
}
|
|
}
|
|
} // namespace
|