SvgElementFactory.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Xml;
  5. using System.ComponentModel;
  6. using System.Diagnostics;
  7. using System.Linq;
  8. using ExCSS;
  9. #pragma warning disable
  10. namespace Svg
  11. {
  12. /// <summary>
  13. /// Provides the methods required in order to parse and create <see cref="SvgElement"/> instances from XML.
  14. /// </summary>
  15. internal class SvgElementFactory
  16. {
  17. private Dictionary<string, ElementInfo> availableElements;
  18. private Parser cssParser = new Parser();
  19. /// <summary>
  20. /// Gets a list of available types that can be used when creating an <see cref="SvgElement"/>.
  21. /// </summary>
  22. public Dictionary<string, ElementInfo> AvailableElements
  23. {
  24. get
  25. {
  26. if (availableElements == null)
  27. {
  28. var svgTypes = from t in typeof(SvgDocument).Assembly.GetExportedTypes()
  29. where t.GetCustomAttributes(typeof(SvgElementAttribute), true).Length > 0
  30. && t.IsSubclassOf(typeof(SvgElement))
  31. select new ElementInfo { ElementName = ((SvgElementAttribute)t.GetCustomAttributes(typeof(SvgElementAttribute), true)[0]).ElementName, ElementType = t };
  32. availableElements = (from t in svgTypes
  33. where t.ElementName != "svg"
  34. group t by t.ElementName into types
  35. select types).ToDictionary(e => e.Key, e => e.SingleOrDefault());
  36. }
  37. return availableElements;
  38. }
  39. }
  40. /// <summary>
  41. /// Creates an <see cref="SvgDocument"/> from the current node in the specified <see cref="XmlTextReader"/>.
  42. /// </summary>
  43. /// <param name="reader">The <see cref="XmlTextReader"/> containing the node to parse into an <see cref="SvgDocument"/>.</param>
  44. /// <exception cref="ArgumentNullException">The <paramref name="reader"/> parameter cannot be <c>null</c>.</exception>
  45. /// <exception cref="InvalidOperationException">The CreateDocument method can only be used to parse root &lt;svg&gt; elements.</exception>
  46. public T CreateDocument<T>(XmlReader reader) where T : SvgDocument, new()
  47. {
  48. if (reader == null)
  49. {
  50. throw new ArgumentNullException("reader");
  51. }
  52. if (reader.LocalName != "svg")
  53. {
  54. throw new InvalidOperationException("The CreateDocument method can only be used to parse root <svg> elements.");
  55. }
  56. return (T)CreateElement<T>(reader, true, null);
  57. }
  58. /// <summary>
  59. /// Creates an <see cref="SvgElement"/> from the current node in the specified <see cref="XmlTextReader"/>.
  60. /// </summary>
  61. /// <param name="reader">The <see cref="XmlTextReader"/> containing the node to parse into a subclass of <see cref="SvgElement"/>.</param>
  62. /// <param name="document">The <see cref="SvgDocument"/> that the created element belongs to.</param>
  63. /// <exception cref="ArgumentNullException">The <paramref name="reader"/> and <paramref name="document"/> parameters cannot be <c>null</c>.</exception>
  64. public SvgElement CreateElement(XmlReader reader, SvgDocument document)
  65. {
  66. if (reader == null)
  67. {
  68. throw new ArgumentNullException("reader");
  69. }
  70. return CreateElement<SvgDocument>(reader, false, document);
  71. }
  72. private SvgElement CreateElement<T>(XmlReader reader, bool fragmentIsDocument, SvgDocument document) where T : SvgDocument, new()
  73. {
  74. SvgElement createdElement = null;
  75. string elementName = reader.LocalName;
  76. string elementNS = reader.NamespaceURI;
  77. //Trace.TraceInformation("Begin CreateElement: {0}", elementName);
  78. if (elementNS == SvgAttributeAttribute.SvgNamespace || string.IsNullOrEmpty(elementNS))
  79. {
  80. if (elementName == "svg")
  81. {
  82. createdElement = (fragmentIsDocument) ? new T() : new SvgFragment();
  83. }
  84. else
  85. {
  86. ElementInfo validType = null;
  87. if (AvailableElements.TryGetValue(elementName, out validType))
  88. {
  89. createdElement = (SvgElement)Activator.CreateInstance(validType.ElementType);
  90. }
  91. else
  92. {
  93. createdElement = new SvgUnknownElement(elementName);
  94. }
  95. }
  96. if (createdElement != null)
  97. {
  98. SetAttributes(createdElement, reader, document);
  99. }
  100. }
  101. else
  102. {
  103. // All non svg element (html, ...)
  104. createdElement = new NonSvgElement(elementName);
  105. SetAttributes(createdElement, reader, document);
  106. }
  107. //Trace.TraceInformation("End CreateElement");
  108. return createdElement;
  109. }
  110. private void SetAttributes(SvgElement element, XmlReader reader, SvgDocument document)
  111. {
  112. //Trace.TraceInformation("Begin SetAttributes");
  113. //string[] styles = null;
  114. //string[] style = null;
  115. //int i = 0;
  116. while (reader.MoveToNextAttribute())
  117. {
  118. if (reader.LocalName.Equals("style") && !(element is NonSvgElement))
  119. {
  120. var inlineSheet = cssParser.Parse("#a{" + reader.Value + "}");
  121. foreach (var rule in inlineSheet.StyleRules)
  122. {
  123. foreach (var decl in rule.Declarations)
  124. {
  125. element.AddStyle(decl.Name, decl.Term.ToString(), SvgElement.StyleSpecificity_InlineStyle);
  126. }
  127. }
  128. }
  129. else if (IsStyleAttribute(reader.LocalName))
  130. {
  131. element.AddStyle(reader.LocalName, reader.Value, SvgElement.StyleSpecificity_PresAttribute);
  132. }
  133. else
  134. {
  135. SetPropertyValue(element, reader.LocalName, reader.Value, document);
  136. }
  137. }
  138. //Trace.TraceInformation("End SetAttributes");
  139. }
  140. private static bool IsStyleAttribute(string name)
  141. {
  142. switch (name)
  143. {
  144. case "alignment-baseline":
  145. case "baseline-shift":
  146. case "clip":
  147. case "clip-path":
  148. case "clip-rule":
  149. case "color":
  150. case "color-interpolation":
  151. case "color-interpolation-filters":
  152. case "color-profile":
  153. case "color-rendering":
  154. case "cursor":
  155. case "direction":
  156. case "display":
  157. case "dominant-baseline":
  158. case "enable-background":
  159. case "fill":
  160. case "fill-opacity":
  161. case "fill-rule":
  162. case "filter":
  163. case "flood-color":
  164. case "flood-opacity":
  165. case "font":
  166. case "font-family":
  167. case "font-size":
  168. case "font-size-adjust":
  169. case "font-stretch":
  170. case "font-style":
  171. case "font-variant":
  172. case "font-weight":
  173. case "glyph-orientation-horizontal":
  174. case "glyph-orientation-vertical":
  175. case "image-rendering":
  176. case "kerning":
  177. case "letter-spacing":
  178. case "lighting-color":
  179. case "marker":
  180. case "marker-end":
  181. case "marker-mid":
  182. case "marker-start":
  183. case "mask":
  184. case "opacity":
  185. case "overflow":
  186. case "pointer-events":
  187. case "shape-rendering":
  188. case "stop-color":
  189. case "stop-opacity":
  190. case "stroke":
  191. case "stroke-dasharray":
  192. case "stroke-dashoffset":
  193. case "stroke-linecap":
  194. case "stroke-linejoin":
  195. case "stroke-miterlimit":
  196. case "stroke-opacity":
  197. case "stroke-width":
  198. case "text-anchor":
  199. case "text-decoration":
  200. case "text-rendering":
  201. case "unicode-bidi":
  202. case "visibility":
  203. case "word-spacing":
  204. case "writing-mode":
  205. return true;
  206. }
  207. return false;
  208. }
  209. private static Dictionary<Type, Dictionary<string, PropertyDescriptorCollection>> _propertyDescriptors = new Dictionary<Type, Dictionary<string, PropertyDescriptorCollection>>();
  210. private static object syncLock = new object();
  211. internal static void SetPropertyValue(SvgElement element, string attributeName, string attributeValue, SvgDocument document)
  212. {
  213. var elementType = element.GetType();
  214. PropertyDescriptorCollection properties;
  215. lock (syncLock)
  216. {
  217. if (_propertyDescriptors.Keys.Contains(elementType))
  218. {
  219. if (_propertyDescriptors[elementType].Keys.Contains(attributeName))
  220. {
  221. properties = _propertyDescriptors[elementType][attributeName];
  222. }
  223. else
  224. {
  225. properties = TypeDescriptor.GetProperties(elementType, new[] { new SvgAttributeAttribute(attributeName) });
  226. _propertyDescriptors[elementType].Add(attributeName, properties);
  227. }
  228. }
  229. else
  230. {
  231. properties = TypeDescriptor.GetProperties(elementType, new[] { new SvgAttributeAttribute(attributeName) });
  232. _propertyDescriptors.Add(elementType, new Dictionary<string, PropertyDescriptorCollection>());
  233. _propertyDescriptors[elementType].Add(attributeName, properties);
  234. }
  235. }
  236. if (properties.Count > 0)
  237. {
  238. PropertyDescriptor descriptor = properties[0];
  239. try
  240. {
  241. if (attributeName == "opacity" && attributeValue == "undefined")
  242. {
  243. attributeValue = "1";
  244. }
  245. descriptor.SetValue(element, descriptor.Converter.ConvertFrom(document, CultureInfo.InvariantCulture, attributeValue));
  246. }
  247. catch
  248. {
  249. Trace.TraceWarning(string.Format("Attribute '{0}' cannot be set - type '{1}' cannot convert from string '{2}'.", attributeName, descriptor.PropertyType.FullName, attributeValue));
  250. }
  251. }
  252. else
  253. {
  254. //check for namespace declaration in svg element
  255. if (string.Equals(element.ElementName, "svg", StringComparison.OrdinalIgnoreCase))
  256. {
  257. if (string.Equals(attributeName, "xmlns", StringComparison.OrdinalIgnoreCase)
  258. || string.Equals(attributeName, "xlink", StringComparison.OrdinalIgnoreCase)
  259. || string.Equals(attributeName, "xmlns:xlink", StringComparison.OrdinalIgnoreCase)
  260. || string.Equals(attributeName, "version", StringComparison.OrdinalIgnoreCase))
  261. {
  262. //nothing to do
  263. }
  264. else
  265. {
  266. //attribute is not a svg attribute, store it in custom attributes
  267. element.CustomAttributes[attributeName] = attributeValue;
  268. }
  269. }
  270. else
  271. {
  272. //attribute is not a svg attribute, store it in custom attributes
  273. element.CustomAttributes[attributeName] = attributeValue;
  274. }
  275. }
  276. }
  277. /// <summary>
  278. /// Contains information about a type inheriting from <see cref="SvgElement"/>.
  279. /// </summary>
  280. [DebuggerDisplay("{ElementName}, {ElementType}")]
  281. internal sealed class ElementInfo
  282. {
  283. /// <summary>
  284. /// Gets the SVG name of the <see cref="SvgElement"/>.
  285. /// </summary>
  286. public string ElementName { get; set; }
  287. /// <summary>
  288. /// Gets the <see cref="Type"/> of the <see cref="SvgElement"/> subclass.
  289. /// </summary>
  290. public Type ElementType { get; set; }
  291. /// <summary>
  292. /// Initializes a new instance of the <see cref="ElementInfo"/> struct.
  293. /// </summary>
  294. /// <param name="elementName">Name of the element.</param>
  295. /// <param name="elementType">Type of the element.</param>
  296. public ElementInfo(string elementName, Type elementType)
  297. {
  298. this.ElementName = elementName;
  299. this.ElementType = elementType;
  300. }
  301. /// <summary>
  302. /// Initializes a new instance of the <see cref="ElementInfo"/> class.
  303. /// </summary>
  304. public ElementInfo()
  305. {
  306. }
  307. }
  308. }
  309. }
  310. #pragma warning restore