SvgElementFactory.cs 11.6 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
        private static List<ElementInfo> availableElements;
Tibor Peluch's avatar
Tibor Peluch committed
17
        private const string svgNS = "http://www.w3.org/2000/svg";
18

19
20
21
        /// <summary>
        /// Gets a list of available types that can be used when creating an <see cref="SvgElement"/>.
        /// </summary>
22
23
24
25
26
27
28
29
        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
30
                                   && t.IsSubclassOf(typeof(SvgElement))
31
32
33
34
35
36
37
38
39
                                   select new ElementInfo { ElementName = ((SvgElementAttribute)t.GetCustomAttributes(typeof(SvgElementAttribute), true)[0]).ElementName, ElementType = t };

                    availableElements = svgTypes.ToList();
                }

                return availableElements;
            }
        }

40
41
42
43
44
45
        /// <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>
46
        public static T CreateDocument<T>(XmlTextReader reader) where T : SvgDocument, new()
davescriven's avatar
davescriven committed
47
        {
48
49
50
51
52
53
54
55
56
57
            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.");
            }

58
            return (T)CreateElement<T>(reader, true, null);
davescriven's avatar
davescriven committed
59
60
        }

61
62
63
64
65
66
        /// <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
67
68
        public static SvgElement CreateElement(XmlTextReader reader, SvgDocument document)
        {
69
70
71
72
73
            if (reader == null)
            {
                throw new ArgumentNullException("reader");
            }

74
            return CreateElement<SvgDocument>(reader, false, document);
davescriven's avatar
davescriven committed
75
76
        }

77
        private static SvgElement CreateElement<T>(XmlTextReader reader, bool fragmentIsDocument, SvgDocument document)  where T : SvgDocument, new()
davescriven's avatar
davescriven committed
78
79
80
        {
            SvgElement createdElement = null;
            string elementName = reader.LocalName;
Tibor Peluch's avatar
Tibor Peluch committed
81
            string elementNS = reader.NamespaceURI;
davescriven's avatar
davescriven committed
82

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

Tibor Peluch's avatar
Tibor Peluch committed
85
            if (elementNS == svgNS)
davescriven's avatar
davescriven committed
86
            {
Tibor Peluch's avatar
Tibor Peluch committed
87
                if (elementName == "svg")
88
                {
Tibor Peluch's avatar
Tibor Peluch committed
89
90
91
92
93
94
95
                    createdElement = (fragmentIsDocument) ? new T() : new SvgFragment();
                }
                else
                {
                    ElementInfo validType = AvailableElements.SingleOrDefault(e => e.ElementName == elementName);
                    if (validType != null)
                    {
96
97
98
99
100
                        createdElement = (SvgElement) Activator.CreateInstance(validType.ElementType);
                    }
                    else
                    {
                        createdElement = new SvgUnknownElement(elementName);
Tibor Peluch's avatar
Tibor Peluch committed
101
                    }
102
                }
davescriven's avatar
davescriven committed
103

Tibor Peluch's avatar
Tibor Peluch committed
104
105
106
107
                if (createdElement != null)
                {
                    SetAttributes(createdElement, reader, document);
                }
108
            }
109
110
111
112
113
114
            else
            {
                // All non svg element (html, ...)
                createdElement = new NonSvgElement(elementName);
                SetAttributes(createdElement, reader, document);
            }
115

116
            //Trace.TraceInformation("End CreateElement");
davescriven's avatar
davescriven committed
117
118
119
120
121
122

            return createdElement;
        }

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

davescriven's avatar
davescriven committed
125
126
127
128
129
130
131
            string[] styles = null;
            string[] style = null;
            int i = 0;

            while (reader.MoveToNextAttribute())
            {
                // Special treatment for "style"
132
                if (reader.LocalName.Equals("style") && !(element is NonSvgElement))
davescriven's avatar
davescriven committed
133
134
135
136
137
138
139
140
141
142
143
144
145
146
                {
                    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);
                    }

Mark Johnson's avatar
Mark Johnson committed
147
148
149
150
151
					//defaults for text can come from the document
					if (element.ElementName == "text")
					{
						if (!styles.Contains("font-size") && document.CustomAttributes.ContainsKey("font-size") && document.CustomAttributes["font-size"] != null)
						{
152
							SetPropertyValue(element, "font-size", document.CustomAttributes["font-size"], document);
Mark Johnson's avatar
Mark Johnson committed
153
						}
154
						if (!styles.Contains("font-family") && document.CustomAttributes.ContainsKey("font-family") && document.CustomAttributes["font-family"] != null)
Mark Johnson's avatar
Mark Johnson committed
155
156
157
158
159
						{
							SetPropertyValue(element, "font-family", document.CustomAttributes["font-family"], document);
						}
						
					}
160
                    continue; 
davescriven's avatar
davescriven committed
161
162
163
164
                }

                SetPropertyValue(element, reader.LocalName, reader.Value, document);
            }
165

166
            //Trace.TraceInformation("End SetAttributes");
davescriven's avatar
davescriven committed
167
168
        }

ddpruitt's avatar
ddpruitt committed
169
        private static Dictionary<Type, Dictionary<string, PropertyDescriptorCollection>> _propertyDescriptors = new Dictionary<Type, Dictionary<string, PropertyDescriptorCollection>>();
170
        private static object syncLock = new object();
ddpruitt's avatar
ddpruitt committed
171

davescriven's avatar
davescriven committed
172
173
        private static void SetPropertyValue(SvgElement element, string attributeName, string attributeValue, SvgDocument document)
        {
ddpruitt's avatar
ddpruitt committed
174
175
176
            var elementType = element.GetType();

            PropertyDescriptorCollection properties;
177
            lock (syncLock)
ddpruitt's avatar
ddpruitt committed
178
            {
179
                if (_propertyDescriptors.Keys.Contains(elementType))
ddpruitt's avatar
ddpruitt committed
180
                {
181
182
183
184
185
186
187
188
189
                    if (_propertyDescriptors[elementType].Keys.Contains(attributeName))
                    {
                        properties = _propertyDescriptors[elementType][attributeName];
                    }
                    else
                    {
                        properties = TypeDescriptor.GetProperties(elementType, new[] { new SvgAttributeAttribute(attributeName) });
                        _propertyDescriptors[elementType].Add(attributeName, properties);
                    }
ddpruitt's avatar
ddpruitt committed
190
191
192
193
                }
                else
                {
                    properties = TypeDescriptor.GetProperties(elementType, new[] { new SvgAttributeAttribute(attributeName) });
194
                    _propertyDescriptors.Add(elementType, new Dictionary<string, PropertyDescriptorCollection>());
ddpruitt's avatar
ddpruitt committed
195

196
197
                    _propertyDescriptors[elementType].Add(attributeName, properties);
                } 
ddpruitt's avatar
ddpruitt committed
198
            }
davescriven's avatar
davescriven committed
199
200
201

            if (properties.Count > 0)
            {
202
                PropertyDescriptor descriptor = properties[0];
davescriven's avatar
davescriven committed
203
204
205

                try
                {
Mark Johnson's avatar
Mark Johnson committed
206
207
208
209
210
211
212
213
					if (attributeName == "opacity" && attributeValue == "undefined")
					{
						attributeValue = "1";
					}

					descriptor.SetValue(element, descriptor.Converter.ConvertFrom(document, CultureInfo.InvariantCulture, attributeValue));
					

davescriven's avatar
davescriven committed
214
215
216
217
218
219
                }
                catch
                {
                    Trace.TraceWarning(string.Format("Attribute '{0}' cannot be set - type '{1}' cannot convert from string '{2}'.", attributeName, descriptor.PropertyType.FullName, attributeValue));
                }
            }
220
221
            else
            {
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
                //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;
                }
243
            }
davescriven's avatar
davescriven committed
244
        }
245

246
247
248
        /// <summary>
        /// Contains information about a type inheriting from <see cref="SvgElement"/>.
        /// </summary>
249
250
        [DebuggerDisplay("{ElementName}, {ElementType}")]
        internal sealed class ElementInfo
251
        {
252
253
254
            /// <summary>
            /// Gets the SVG name of the <see cref="SvgElement"/>.
            /// </summary>
255
            public string ElementName { get; set; }
256
257
258
            /// <summary>
            /// Gets the <see cref="Type"/> of the <see cref="SvgElement"/> subclass.
            /// </summary>
259
260
            public Type ElementType { get; set; }

261
262
263
264
265
            /// <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>
266
267
268
269
270
            public ElementInfo(string elementName, Type elementType)
            {
                this.ElementName = elementName;
                this.ElementType = elementType;
            }
271
272
273
274
275
276
277

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