using System;
using System.Collections.Generic;
using System.Globalization;
using System.Xml;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using ExCSS;
namespace Svg
{
///
/// Provides the methods required in order to parse and create instances from XML.
///
internal class SvgElementFactory
{
private Dictionary availableElements;
private Parser cssParser = new Parser();
///
/// Gets a list of available types that can be used when creating an .
///
public Dictionary AvailableElements
{
get
{
if (availableElements == null)
{
var svgTypes = from t in typeof(SvgDocument).Assembly.GetExportedTypes()
where t.GetCustomAttributes(typeof(SvgElementAttribute), true).Length > 0
&& t.IsSubclassOf(typeof(SvgElement))
select new ElementInfo { ElementName = ((SvgElementAttribute)t.GetCustomAttributes(typeof(SvgElementAttribute), true)[0]).ElementName, ElementType = t };
availableElements = (from t in svgTypes
where t.ElementName != "svg"
group t by t.ElementName into types
select types).ToDictionary(e => e.Key, e => e.SingleOrDefault());
}
return availableElements;
}
}
///
/// Creates an from the current node in the specified .
///
/// The containing the node to parse into an .
/// The parameter cannot be null .
/// The CreateDocument method can only be used to parse root <svg> elements.
public T CreateDocument(XmlReader reader) where T : SvgDocument, new()
{
if (reader == null)
{
throw new ArgumentNullException("reader");
}
if (reader.LocalName != "svg")
{
throw new InvalidOperationException("The CreateDocument method can only be used to parse root elements.");
}
return (T)CreateElement(reader, true, null);
}
///
/// Creates an from the current node in the specified .
///
/// The containing the node to parse into a subclass of .
/// The that the created element belongs to.
/// The and parameters cannot be null .
public SvgElement CreateElement(XmlReader reader, SvgDocument document)
{
if (reader == null)
{
throw new ArgumentNullException("reader");
}
return CreateElement(reader, false, document);
}
private SvgElement CreateElement(XmlReader reader, bool fragmentIsDocument, SvgDocument document) where T : SvgDocument, new()
{
SvgElement createdElement = null;
string elementName = reader.LocalName;
string elementNS = reader.NamespaceURI;
//Trace.TraceInformation("Begin CreateElement: {0}", elementName);
if (elementNS == SvgAttributeAttribute.SvgNamespace || string.IsNullOrEmpty(elementNS))
{
if (elementName == "svg")
{
createdElement = (fragmentIsDocument) ? new T() : new SvgFragment();
}
else
{
ElementInfo validType = null;
if (AvailableElements.TryGetValue(elementName, out validType))
{
createdElement = (SvgElement) Activator.CreateInstance(validType.ElementType);
}
else
{
createdElement = new SvgUnknownElement(elementName);
}
}
if (createdElement != null)
{
SetAttributes(createdElement, reader, document);
}
}
else
{
// All non svg element (html, ...)
createdElement = new NonSvgElement(elementName);
SetAttributes(createdElement, reader, document);
}
//Trace.TraceInformation("End CreateElement");
return createdElement;
}
private void SetAttributes(SvgElement element, XmlReader reader, SvgDocument document)
{
//Trace.TraceInformation("Begin SetAttributes");
//string[] styles = null;
//string[] style = null;
//int i = 0;
while (reader.MoveToNextAttribute())
{
if (reader.LocalName.Equals("style") && !(element is NonSvgElement))
{
var inlineSheet = cssParser.Parse("#a{" + reader.Value + "}");
foreach (var rule in inlineSheet.StyleRules)
{
foreach (var decl in rule.Declarations)
{
element.AddStyle(decl.Name, decl.Term.ToString(), SvgElement.StyleSpecificity_InlineStyle);
}
}
}
else if (IsStyleAttribute(reader.LocalName))
{
element.AddStyle(reader.LocalName, reader.Value, SvgElement.StyleSpecificity_PresAttribute);
}
else
{
SetPropertyValue(element, reader.LocalName, reader.Value, document);
}
}
//Trace.TraceInformation("End SetAttributes");
}
private static bool IsStyleAttribute(string name)
{
switch (name)
{
case "alignment-baseline":
case "baseline-shift":
case "clip":
case "clip-path":
case "clip-rule":
case "color":
case "color-interpolation":
case "color-interpolation-filters":
case "color-profile":
case "color-rendering":
case "cursor":
case "direction":
case "display":
case "dominant-baseline":
case "enable-background":
case "fill":
case "fill-opacity":
case "fill-rule":
case "filter":
case "flood-color":
case "flood-opacity":
case "font":
case "font-family":
case "font-size":
case "font-size-adjust":
case "font-stretch":
case "font-style":
case "font-variant":
case "font-weight":
case "glyph-orientation-horizontal":
case "glyph-orientation-vertical":
case "image-rendering":
case "kerning":
case "letter-spacing":
case "lighting-color":
case "marker":
case "marker-end":
case "marker-mid":
case "marker-start":
case "mask":
case "opacity":
case "overflow":
case "pointer-events":
case "shape-rendering":
case "stop-color":
case "stop-opacity":
case "stroke":
case "stroke-dasharray":
case "stroke-dashoffset":
case "stroke-linecap":
case "stroke-linejoin":
case "stroke-miterlimit":
case "stroke-opacity":
case "stroke-width":
case "text-anchor":
case "text-decoration":
case "text-rendering":
case "unicode-bidi":
case "visibility":
case "word-spacing":
case "writing-mode":
return true;
}
return false;
}
private static Dictionary> _propertyDescriptors = new Dictionary>();
private static object syncLock = new object();
internal static void SetPropertyValue(SvgElement element, string attributeName, string attributeValue, SvgDocument document)
{
var elementType = element.GetType();
PropertyDescriptorCollection properties;
lock (syncLock)
{
if (_propertyDescriptors.Keys.Contains(elementType))
{
if (_propertyDescriptors[elementType].Keys.Contains(attributeName))
{
properties = _propertyDescriptors[elementType][attributeName];
}
else
{
properties = TypeDescriptor.GetProperties(elementType, new[] { new SvgAttributeAttribute(attributeName) });
_propertyDescriptors[elementType].Add(attributeName, properties);
}
}
else
{
properties = TypeDescriptor.GetProperties(elementType, new[] { new SvgAttributeAttribute(attributeName) });
_propertyDescriptors.Add(elementType, new Dictionary());
_propertyDescriptors[elementType].Add(attributeName, properties);
}
}
if (properties.Count > 0)
{
PropertyDescriptor descriptor = properties[0];
try
{
if (attributeName == "opacity" && attributeValue == "undefined")
{
attributeValue = "1";
}
descriptor.SetValue(element, descriptor.Converter.ConvertFrom(document, CultureInfo.InvariantCulture, attributeValue));
}
catch
{
Trace.TraceWarning(string.Format("Attribute '{0}' cannot be set - type '{1}' cannot convert from string '{2}'.", attributeName, descriptor.PropertyType.FullName, attributeValue));
}
}
else
{
//check for namespace declaration in svg element
if (string.Equals(element.ElementName, "svg", StringComparison.OrdinalIgnoreCase))
{
if (string.Equals(attributeName, "xmlns", StringComparison.OrdinalIgnoreCase)
|| string.Equals(attributeName, "xlink", StringComparison.OrdinalIgnoreCase)
|| string.Equals(attributeName, "xmlns:xlink", StringComparison.OrdinalIgnoreCase)
|| string.Equals(attributeName, "version", StringComparison.OrdinalIgnoreCase))
{
//nothing to do
}
else
{
//attribute is not a svg attribute, store it in custom attributes
element.CustomAttributes[attributeName] = attributeValue;
}
}
else
{
//attribute is not a svg attribute, store it in custom attributes
element.CustomAttributes[attributeName] = attributeValue;
}
}
}
///
/// Contains information about a type inheriting from .
///
[DebuggerDisplay("{ElementName}, {ElementType}")]
internal sealed class ElementInfo
{
///
/// Gets the SVG name of the .
///
public string ElementName { get; set; }
///
/// Gets the of the subclass.
///
public Type ElementType { get; set; }
///
/// Initializes a new instance of the struct.
///
/// Name of the element.
/// Type of the element.
public ElementInfo(string elementName, Type elementType)
{
this.ElementName = elementName;
this.ElementType = elementType;
}
///
/// Initializes a new instance of the class.
///
public ElementInfo()
{
}
}
}
}