using System; using System.Collections.Generic; using System.Globalization; using System.Xml; using System.ComponentModel; using System.Diagnostics; using System.Linq; namespace Svg { /// /// Provides the methods required in order to parse and create instances from XML. /// internal class SvgElementFactory { private static List availableElements; private const string svgNS = "http://www.w3.org/2000/svg"; /// /// Gets a list of available types that can be used when creating an . /// private static List 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 = svgTypes.ToList(); } 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 static T CreateDocument(XmlTextReader 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 static SvgElement CreateElement(XmlTextReader reader, SvgDocument document) { if (reader == null) { throw new ArgumentNullException("reader"); } return CreateElement(reader, false, document); } private static SvgElement CreateElement(XmlTextReader 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 == svgNS) { if (elementName == "svg") { createdElement = (fragmentIsDocument) ? new T() : new SvgFragment(); } else { ElementInfo validType = AvailableElements.SingleOrDefault(e => e.ElementName == elementName); if (validType != null) { createdElement = (SvgElement)Activator.CreateInstance(validType.ElementType); } } if (createdElement != null) { SetAttributes(createdElement, reader, document); } } //Trace.TraceInformation("End CreateElement"); return createdElement; } private static void SetAttributes(SvgElement element, XmlTextReader reader, SvgDocument document) { //Trace.TraceInformation("Begin SetAttributes"); string[] styles = null; string[] style = null; int i = 0; while (reader.MoveToNextAttribute()) { // Special treatment for "style" if (reader.LocalName.Equals("style")) { styles = reader.Value.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); for (i = 0; i < styles.Length; i++) { if (!styles[i].Contains(":")) { continue; } style = styles[i].Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries); SetPropertyValue(element, style[0].Trim(), style[1].Trim(), document); } continue; } SetPropertyValue(element, reader.LocalName, reader.Value, document); } //Trace.TraceInformation("End SetAttributes"); } private static Dictionary> _propertyDescriptors = new Dictionary>(); private static void SetPropertyValue(SvgElement element, string attributeName, string attributeValue, SvgDocument document) { var elementType = element.GetType(); PropertyDescriptorCollection properties; 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 { 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() { } } } }