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() { } } } }