SvgElementFactory.cs 12.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;
Eric Domke's avatar
Eric Domke committed
8
using ExCSS;
davescriven's avatar
davescriven committed
9
10
11

namespace Svg
{
12
13
14
    /// <summary>
    /// Provides the methods required in order to parse and create <see cref="SvgElement"/> instances from XML.
    /// </summary>
davescriven's avatar
davescriven committed
15
16
    internal class SvgElementFactory
    {
17
        private static List<ElementInfo> availableElements;
Tibor Peluch's avatar
Tibor Peluch committed
18
        private const string svgNS = "http://www.w3.org/2000/svg";
Eric Domke's avatar
Eric Domke committed
19
        private static Parser cssParser = new Parser();
20

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

                    availableElements = svgTypes.ToList();
                }

                return availableElements;
            }
        }

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

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

63
64
65
66
67
68
        /// <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>
Eric Domke's avatar
Eric Domke committed
69
        public static SvgElement CreateElement(XmlReader reader, SvgDocument document)
davescriven's avatar
davescriven committed
70
        {
71
72
73
74
75
            if (reader == null)
            {
                throw new ArgumentNullException("reader");
            }

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

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

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

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

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

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

            return createdElement;
        }

Eric Domke's avatar
Eric Domke committed
123
        private static void SetAttributes(SvgElement element, XmlReader reader, SvgDocument document)
davescriven's avatar
davescriven committed
124
        {
125
            //Trace.TraceInformation("Begin SetAttributes");
126

Eric Domke's avatar
Eric Domke committed
127
128
129
            //string[] styles = null;
            //string[] style = null;
            //int i = 0;
davescriven's avatar
davescriven committed
130
131
132

            while (reader.MoveToNextAttribute())
            {
Eric Domke's avatar
Eric Domke committed
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
                //// Special treatment for "style"
                //if (reader.LocalName.Equals("style") && !(element is NonSvgElement))
                //{
                //    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);
                //    }

                //    //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)
                //        {
                //            SetPropertyValue(element, "font-size", document.CustomAttributes["font-size"], document);
                //        }
                //        if (!styles.Contains("font-family") && document.CustomAttributes.ContainsKey("font-family") && document.CustomAttributes["font-family"] != null)
                //        {
                //            SetPropertyValue(element, "font-family", document.CustomAttributes["font-family"], document);
                //        }
						
                //    }
                //    continue; 
                //}

                //SetPropertyValue(element, reader.LocalName, reader.Value, document);
davescriven's avatar
davescriven committed
166

Eric Domke's avatar
Eric Domke committed
167
168
169
170
                if (reader.LocalName.Equals("style") && !(element is NonSvgElement)) 
                {
                    var inlineSheet = cssParser.Parse("#a{" + reader.Value + "}");
                    foreach (var rule in inlineSheet.StyleRules)
davescriven's avatar
davescriven committed
171
                    {
Eric Domke's avatar
Eric Domke committed
172
                        foreach (var decl in rule.Declarations)
davescriven's avatar
davescriven committed
173
                        {
Eric Domke's avatar
Eric Domke committed
174
                            element.AddStyle(decl.Name, decl.Term.ToString(), 1 << 16);
davescriven's avatar
davescriven committed
175
176
177
                        }
                    }
                }
Eric Domke's avatar
Eric Domke committed
178
179
180
181
                else 
                {
                    element.AddStyle(reader.LocalName, reader.Value, 2 << 16);
                }
davescriven's avatar
davescriven committed
182
            }
183

184
            //Trace.TraceInformation("End SetAttributes");
davescriven's avatar
davescriven committed
185
186
        }

ddpruitt's avatar
ddpruitt committed
187
        private static Dictionary<Type, Dictionary<string, PropertyDescriptorCollection>> _propertyDescriptors = new Dictionary<Type, Dictionary<string, PropertyDescriptorCollection>>();
188
        private static object syncLock = new object();
ddpruitt's avatar
ddpruitt committed
189

Eric Domke's avatar
Eric Domke committed
190
        internal static void SetPropertyValue(SvgElement element, string attributeName, string attributeValue, SvgDocument document)
davescriven's avatar
davescriven committed
191
        {
ddpruitt's avatar
ddpruitt committed
192
193
194
            var elementType = element.GetType();

            PropertyDescriptorCollection properties;
195
            lock (syncLock)
ddpruitt's avatar
ddpruitt committed
196
            {
197
                if (_propertyDescriptors.Keys.Contains(elementType))
ddpruitt's avatar
ddpruitt committed
198
                {
199
200
201
202
203
204
205
206
207
                    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
208
209
210
211
                }
                else
                {
                    properties = TypeDescriptor.GetProperties(elementType, new[] { new SvgAttributeAttribute(attributeName) });
212
                    _propertyDescriptors.Add(elementType, new Dictionary<string, PropertyDescriptorCollection>());
ddpruitt's avatar
ddpruitt committed
213

214
215
                    _propertyDescriptors[elementType].Add(attributeName, properties);
                } 
ddpruitt's avatar
ddpruitt committed
216
            }
davescriven's avatar
davescriven committed
217
218
219

            if (properties.Count > 0)
            {
220
                PropertyDescriptor descriptor = properties[0];
davescriven's avatar
davescriven committed
221
222
223

                try
                {
Mark Johnson's avatar
Mark Johnson committed
224
225
226
227
228
229
230
231
					if (attributeName == "opacity" && attributeValue == "undefined")
					{
						attributeValue = "1";
					}

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

davescriven's avatar
davescriven committed
232
233
234
235
236
237
                }
                catch
                {
                    Trace.TraceWarning(string.Format("Attribute '{0}' cannot be set - type '{1}' cannot convert from string '{2}'.", attributeName, descriptor.PropertyType.FullName, attributeValue));
                }
            }
238
239
            else
            {
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
                //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;
                }
261
            }
davescriven's avatar
davescriven committed
262
        }
263

264
265
266
        /// <summary>
        /// Contains information about a type inheriting from <see cref="SvgElement"/>.
        /// </summary>
267
268
        [DebuggerDisplay("{ElementName}, {ElementType}")]
        internal sealed class ElementInfo
269
        {
270
271
272
            /// <summary>
            /// Gets the SVG name of the <see cref="SvgElement"/>.
            /// </summary>
273
            public string ElementName { get; set; }
274
275
276
            /// <summary>
            /// Gets the <see cref="Type"/> of the <see cref="SvgElement"/> subclass.
            /// </summary>
277
278
            public Type ElementType { get; set; }

279
280
281
282
283
            /// <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>
284
285
286
287
288
            public ElementInfo(string elementName, Type elementType)
            {
                this.ElementName = elementName;
                this.ElementType = elementType;
            }
289
290
291
292
293
294
295

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