SvgElementFactory.cs 9 KB
Newer Older
davescriven's avatar
davescriven committed
1
2
3
4
5
6
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Xml;
using System.ComponentModel;
using System.Diagnostics;
7
using System.Linq;
davescriven's avatar
davescriven committed
8
9
10

namespace Svg
{
11
12
13
    /// <summary>
    /// Provides the methods required in order to parse and create <see cref="SvgElement"/> instances from XML.
    /// </summary>
davescriven's avatar
davescriven committed
14
15
    internal class SvgElementFactory
    {
16
17
        private static List<ElementInfo> availableElements;

18
19
20
        /// <summary>
        /// Gets a list of available types that can be used when creating an <see cref="SvgElement"/>.
        /// </summary>
21
22
23
24
25
26
27
28
        private static List<ElementInfo> AvailableElements
        {
            get
            {
                if (availableElements == null)
                {
                    var svgTypes = from t in typeof(SvgDocument).Assembly.GetExportedTypes()
                                   where t.GetCustomAttributes(typeof(SvgElementAttribute), true).Length > 0
29
                                   && t.IsSubclassOf(typeof(SvgElement))
30
31
32
33
34
35
36
37
38
                                   select new ElementInfo { ElementName = ((SvgElementAttribute)t.GetCustomAttributes(typeof(SvgElementAttribute), true)[0]).ElementName, ElementType = t };

                    availableElements = svgTypes.ToList();
                }

                return availableElements;
            }
        }

39
40
41
42
43
44
        /// <summary>
        /// Creates an <see cref="SvgDocument"/> from the current node in the specified <see cref="XmlTextReader"/>.
        /// </summary>
        /// <param name="reader">The <see cref="XmlTextReader"/> containing the node to parse into an <see cref="SvgDocument"/>.</param>
        /// <exception cref="ArgumentNullException">The <paramref name="reader"/> parameter cannot be <c>null</c>.</exception>
        /// <exception cref="InvalidOperationException">The CreateDocument method can only be used to parse root &lt;svg&gt; elements.</exception>
davescriven's avatar
davescriven committed
45
46
        public static SvgDocument CreateDocument(XmlTextReader reader)
        {
47
48
49
50
51
52
53
54
55
56
            if (reader == null)
            {
                throw new ArgumentNullException("reader");
            }

            if (reader.LocalName != "svg")
            {
                throw new InvalidOperationException("The CreateDocument method can only be used to parse root <svg> elements.");
            }

davescriven's avatar
davescriven committed
57
58
59
            return (SvgDocument)CreateElement(reader, true, null);
        }

60
61
62
63
64
65
        /// <summary>
        /// Creates an <see cref="SvgElement"/> from the current node in the specified <see cref="XmlTextReader"/>.
        /// </summary>
        /// <param name="reader">The <see cref="XmlTextReader"/> containing the node to parse into a subclass of <see cref="SvgElement"/>.</param>
        /// <param name="document">The <see cref="SvgDocument"/> that the created element belongs to.</param>
        /// <exception cref="ArgumentNullException">The <paramref name="reader"/> and <paramref name="document"/> parameters cannot be <c>null</c>.</exception>
davescriven's avatar
davescriven committed
66
67
        public static SvgElement CreateElement(XmlTextReader reader, SvgDocument document)
        {
68
69
70
71
72
73
74
75
76
77
            if (reader == null)
            {
                throw new ArgumentNullException("reader");
            }

            if (document == null)
            {
                throw new ArgumentNullException("document");
            }

davescriven's avatar
davescriven committed
78
79
80
81
82
83
84
85
            return CreateElement(reader, false, document);
        }

        private static SvgElement CreateElement(XmlTextReader reader, bool fragmentIsDocument, SvgDocument document)
        {
            SvgElement createdElement = null;
            string elementName = reader.LocalName;

86
            //Trace.TraceInformation("Begin CreateElement: {0}", elementName);
87

88
            if (elementName == "svg")
davescriven's avatar
davescriven committed
89
            {
90
91
92
93
                createdElement = (fragmentIsDocument) ? new SvgDocument() : new SvgFragment();
            }
            else
            {
94
95
                ElementInfo validType = AvailableElements.SingleOrDefault(e => e.ElementName == elementName);
                if (validType != null)
96
                {
97
                    createdElement = (SvgElement)Activator.CreateInstance(validType.ElementType);
98
                }
davescriven's avatar
davescriven committed
99
100
            }

101
102
103
104
105
106
            if (createdElement != null)
            {
                createdElement.ElementName = elementName;
                SetAttributes(createdElement, reader, document);
            }

107
            //Trace.TraceInformation("End CreateElement");
davescriven's avatar
davescriven committed
108
109
110
111
112
113

            return createdElement;
        }

        private static void SetAttributes(SvgElement element, XmlTextReader reader, SvgDocument document)
        {
114
            //Trace.TraceInformation("Begin SetAttributes");
115

davescriven's avatar
davescriven committed
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
            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);
            }
143

144
            //Trace.TraceInformation("End SetAttributes");
davescriven's avatar
davescriven committed
145
146
        }

ddpruitt's avatar
ddpruitt committed
147
148
        private static Dictionary<Type, Dictionary<string, PropertyDescriptorCollection>> _propertyDescriptors = new Dictionary<Type, Dictionary<string, PropertyDescriptorCollection>>();

davescriven's avatar
davescriven committed
149
150
        private static void SetPropertyValue(SvgElement element, string attributeName, string attributeValue, SvgDocument document)
        {
ddpruitt's avatar
ddpruitt committed
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
            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(element.GetType(), new[] { new SvgAttributeAttribute(attributeName) });
                _propertyDescriptors.Add(elementType, new Dictionary<string, PropertyDescriptorCollection>());

                _propertyDescriptors[elementType].Add(attributeName, properties);
            }
davescriven's avatar
davescriven committed
173
174
175

            if (properties.Count > 0)
            {
176
                PropertyDescriptor descriptor = properties[0];
davescriven's avatar
davescriven committed
177
178
179

                try
                {
180
                    descriptor.SetValue(element, descriptor.Converter.ConvertFrom(document, CultureInfo.InvariantCulture, attributeValue));
davescriven's avatar
davescriven committed
181
182
183
184
185
186
187
                }
                catch
                {
                    Trace.TraceWarning(string.Format("Attribute '{0}' cannot be set - type '{1}' cannot convert from string '{2}'.", attributeName, descriptor.PropertyType.FullName, attributeValue));
                }
            }
        }
188

189
190
191
        /// <summary>
        /// Contains information about a type inheriting from <see cref="SvgElement"/>.
        /// </summary>
192
193
        [DebuggerDisplay("{ElementName}, {ElementType}")]
        internal sealed class ElementInfo
194
        {
195
196
197
            /// <summary>
            /// Gets the SVG name of the <see cref="SvgElement"/>.
            /// </summary>
198
            public string ElementName { get; set; }
199
200
201
            /// <summary>
            /// Gets the <see cref="Type"/> of the <see cref="SvgElement"/> subclass.
            /// </summary>
202
203
            public Type ElementType { get; set; }

204
205
206
207
208
            /// <summary>
            /// Initializes a new instance of the <see cref="ElementInfo"/> struct.
            /// </summary>
            /// <param name="elementName">Name of the element.</param>
            /// <param name="elementType">Type of the element.</param>
209
210
211
212
213
            public ElementInfo(string elementName, Type elementType)
            {
                this.ElementName = elementName;
                this.ElementType = elementType;
            }
214
215
216
217
218
219
220

            /// <summary>
            /// Initializes a new instance of the <see cref="ElementInfo"/> class.
            /// </summary>
            public ElementInfo()
            {
            }
221
        }
davescriven's avatar
davescriven committed
222
223
    }
}