SvgElementIdManager.cs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Text.RegularExpressions;
  6. using System.Net;
  7. using System.IO;
  8. #pragma warning disable
  9. namespace Svg
  10. {
  11. /// <summary>
  12. /// Provides methods to ensure element ID's are valid and unique.
  13. /// </summary>
  14. public class SvgElementIdManager
  15. {
  16. private SvgDocument _document;
  17. private Dictionary<string, SvgElement> _idValueMap;
  18. /// <summary>
  19. /// Retrieves the <see cref="SvgElement"/> with the specified ID.
  20. /// </summary>
  21. /// <param name="id">A <see cref="string"/> containing the ID of the element to find.</param>
  22. /// <returns>An <see cref="SvgElement"/> of one exists with the specified ID; otherwise false.</returns>
  23. public virtual SvgElement GetElementById(string id)
  24. {
  25. if (id.StartsWith("url("))
  26. {
  27. id = id.Substring(4);
  28. id = id.TrimEnd(')');
  29. }
  30. if (id.StartsWith("#"))
  31. {
  32. id = id.Substring(1);
  33. }
  34. SvgElement element = null;
  35. this._idValueMap.TryGetValue(id, out element);
  36. return element;
  37. }
  38. public virtual SvgElement GetElementById(Uri uri)
  39. {
  40. System.Net.ServicePointManager.SecurityProtocol = (SecurityProtocolType)(0xc0 | 0x300 | 0xc00);
  41. if (uri.ToString().StartsWith("url(")) uri = new Uri(uri.ToString().Substring(4).TrimEnd(')'), UriKind.Relative);
  42. if (!uri.IsAbsoluteUri && this._document.BaseUri != null && !uri.ToString().StartsWith("#"))
  43. {
  44. var fullUri = new Uri(this._document.BaseUri, uri);
  45. var hash = fullUri.OriginalString.Substring(fullUri.OriginalString.LastIndexOf('#'));
  46. SvgDocument doc;
  47. switch (fullUri.Scheme.ToLowerInvariant())
  48. {
  49. case "file":
  50. doc = SvgDocument.Open<SvgDocument>(fullUri.LocalPath.Substring(0, fullUri.LocalPath.Length - hash.Length));
  51. return doc.IdManager.GetElementById(hash);
  52. case "http":
  53. case "https":
  54. var httpRequest = WebRequest.Create(uri);
  55. using (WebResponse webResponse = httpRequest.GetResponse())
  56. {
  57. doc = SvgDocument.Open<SvgDocument>(webResponse.GetResponseStream());
  58. return doc.IdManager.GetElementById(hash);
  59. }
  60. default:
  61. throw new NotSupportedException();
  62. }
  63. }
  64. return this.GetElementById(uri.ToString());
  65. }
  66. /// <summary>
  67. /// Adds the specified <see cref="SvgElement"/> for ID management.
  68. /// </summary>
  69. /// <param name="element">The <see cref="SvgElement"/> to be managed.</param>
  70. public virtual void Add(SvgElement element)
  71. {
  72. AddAndForceUniqueID(element, null, false);
  73. }
  74. /// <summary>
  75. /// Adds the specified <see cref="SvgElement"/> for ID management.
  76. /// And can auto fix the ID if it already exists or it starts with a number.
  77. /// </summary>
  78. /// <param name="element">The <see cref="SvgElement"/> to be managed.</param>
  79. /// <param name="autoForceUniqueID">Pass true here, if you want the ID to be fixed</param>
  80. /// <param name="logElementOldIDNewID">If not null, the action is called before the id is fixed</param>
  81. /// <returns>true, if ID was altered</returns>
  82. public virtual bool AddAndForceUniqueID(SvgElement element, SvgElement sibling, bool autoForceUniqueID = true, Action<SvgElement, string, string> logElementOldIDNewID = null)
  83. {
  84. var result = false;
  85. if (!string.IsNullOrEmpty(element.ID))
  86. {
  87. var newID = this.EnsureValidId(element.ID, autoForceUniqueID);
  88. if (autoForceUniqueID && newID != element.ID)
  89. {
  90. if (logElementOldIDNewID != null)
  91. logElementOldIDNewID(element, element.ID, newID);
  92. element.ForceUniqueID(newID);
  93. result = true;
  94. }
  95. this._idValueMap.Add(element.ID, element);
  96. }
  97. OnAdded(element);
  98. return result;
  99. }
  100. /// <summary>
  101. /// Removed the specified <see cref="SvgElement"/> from ID management.
  102. /// </summary>
  103. /// <param name="element">The <see cref="SvgElement"/> to be removed from ID management.</param>
  104. public virtual void Remove(SvgElement element)
  105. {
  106. if (!string.IsNullOrEmpty(element.ID))
  107. {
  108. this._idValueMap.Remove(element.ID);
  109. }
  110. OnRemoved(element);
  111. }
  112. /// <summary>
  113. /// Ensures that the specified ID is valid within the containing <see cref="SvgDocument"/>.
  114. /// </summary>
  115. /// <param name="id">A <see cref="string"/> containing the ID to validate.</param>
  116. /// <param name="autoForceUniqueID">Creates a new unique id <see cref="string"/>.</param>
  117. /// <exception cref="SvgException">
  118. /// <para>The ID cannot start with a digit.</para>
  119. /// <para>An element with the same ID already exists within the containing <see cref="SvgDocument"/>.</para>
  120. /// </exception>
  121. public string EnsureValidId(string id, bool autoForceUniqueID = false)
  122. {
  123. if (string.IsNullOrEmpty(id))
  124. {
  125. return id;
  126. }
  127. if (char.IsDigit(id[0]))
  128. {
  129. if (autoForceUniqueID)
  130. {
  131. return EnsureValidId("id" + id, true);
  132. }
  133. throw new SvgIDWrongFormatException("ID cannot start with a digit: '" + id + "'.");
  134. }
  135. if (this._idValueMap.ContainsKey(id))
  136. {
  137. if (autoForceUniqueID)
  138. {
  139. var match = regex.Match(id);
  140. int number;
  141. if (match.Success && int.TryParse(match.Value.Substring(1), out number))
  142. {
  143. id = regex.Replace(id, "#" + (number + 1));
  144. }
  145. else
  146. {
  147. id += "#1";
  148. }
  149. return EnsureValidId(id, true);
  150. }
  151. throw new SvgIDExistsException("An element with the same ID already exists: '" + id + "'.");
  152. }
  153. return id;
  154. }
  155. private static readonly Regex regex = new Regex(@"#\d+$");
  156. /// <summary>
  157. /// Initialises a new instance of an <see cref="SvgElementIdManager"/>.
  158. /// </summary>
  159. /// <param name="document">The <see cref="SvgDocument"/> containing the <see cref="SvgElement"/>s to manage.</param>
  160. public SvgElementIdManager(SvgDocument document)
  161. {
  162. this._document = document;
  163. this._idValueMap = new Dictionary<string, SvgElement>();
  164. }
  165. public event EventHandler<SvgElementEventArgs> ElementAdded;
  166. public event EventHandler<SvgElementEventArgs> ElementRemoved;
  167. protected void OnAdded(SvgElement element)
  168. {
  169. var handler = ElementAdded;
  170. if (handler != null)
  171. {
  172. handler(this._document, new SvgElementEventArgs { Element = element });
  173. }
  174. }
  175. protected void OnRemoved(SvgElement element)
  176. {
  177. var handler = ElementRemoved;
  178. if (handler != null)
  179. {
  180. handler(this._document, new SvgElementEventArgs { Element = element });
  181. }
  182. }
  183. }
  184. public class SvgElementEventArgs : EventArgs
  185. {
  186. public SvgElement Element;
  187. }
  188. }
  189. #pragma warning restore