SvgElementIdManager.cs 7.97 KB
Newer Older
davescriven's avatar
davescriven committed
1
2
3
4
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
5
using System.Text.RegularExpressions;
Eric Domke's avatar
Eric Domke committed
6
7
using System.Net;
using System.IO;
davescriven's avatar
davescriven committed
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

namespace Svg
{
    /// <summary>
    /// Provides methods to ensure element ID's are valid and unique.
    /// </summary>
    public class SvgElementIdManager
    {
        private SvgDocument _document;
        private Dictionary<string, SvgElement> _idValueMap;

        /// <summary>
        /// Retrieves the <see cref="SvgElement"/> with the specified ID.
        /// </summary>
        /// <param name="id">A <see cref="string"/> containing the ID of the element to find.</param>
        /// <returns>An <see cref="SvgElement"/> of one exists with the specified ID; otherwise false.</returns>
        public virtual SvgElement GetElementById(string id)
        {
26
27
28
29
30
            if (id.StartsWith("url("))
            {
                id = id.Substring(4);
                id = id.TrimEnd(')');
            }
davescriven's avatar
davescriven committed
31
            if (id.StartsWith("#"))
32
            {
davescriven's avatar
davescriven committed
33
                id = id.Substring(1);
34
            }
35
36
37
38
39

            SvgElement element = null;
            this._idValueMap.TryGetValue(id, out element);

            return element;
davescriven's avatar
davescriven committed
40
41
42
43
        }

        public virtual SvgElement GetElementById(Uri uri)
        {
Eric Domke's avatar
Eric Domke committed
44
            if (uri.ToString().StartsWith("url(")) uri = new Uri(uri.ToString().Substring(4).TrimEnd(')'), UriKind.Relative);
Eric Domke's avatar
Eric Domke committed
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
            if (!uri.IsAbsoluteUri && this._document.BaseUri != null && !uri.ToString().StartsWith("#"))
            {
                var fullUri = new Uri(this._document.BaseUri, uri);
                var hash = fullUri.OriginalString.Substring(fullUri.OriginalString.LastIndexOf('#'));
                SvgDocument doc;
                switch (fullUri.Scheme.ToLowerInvariant())
                {
                    case "file":
                        doc = SvgDocument.Open<SvgDocument>(fullUri.LocalPath.Substring(0, fullUri.LocalPath.Length - hash.Length));
                        return doc.IdManager.GetElementById(hash);
                    case "http":
                    case "https":
                        var httpRequest = WebRequest.Create(uri);
                        using (WebResponse webResponse = httpRequest.GetResponse())
                        {
                            doc = SvgDocument.Open<SvgDocument>(webResponse.GetResponseStream());
                            return doc.IdManager.GetElementById(hash);
                        }
                    default:
                        throw new NotSupportedException();
                }

            }
davescriven's avatar
davescriven committed
68
69
70
71
72
73
74
75
76
            return this.GetElementById(uri.ToString());
        }

        /// <summary>
        /// Adds the specified <see cref="SvgElement"/> for ID management.
        /// </summary>
        /// <param name="element">The <see cref="SvgElement"/> to be managed.</param>
        public virtual void Add(SvgElement element)
        {
tebjan's avatar
tebjan committed
77
            AddAndForceUniqueID(element, null, false);
78
79
80
81
82
83
84
        }

        /// <summary>
        /// Adds the specified <see cref="SvgElement"/> for ID management. 
        /// And can auto fix the ID if it already exists or it starts with a number.
        /// </summary>
        /// <param name="element">The <see cref="SvgElement"/> to be managed.</param>
tebjan's avatar
tebjan committed
85
        /// <param name="autoForceUniqueID">Pass true here, if you want the ID to be fixed</param>
86
87
        /// <param name="logElementOldIDNewID">If not null, the action is called before the id is fixed</param>
        /// <returns>true, if ID was altered</returns>
tebjan's avatar
tebjan committed
88
        public virtual bool AddAndForceUniqueID(SvgElement element, SvgElement sibling, bool autoForceUniqueID = true, Action<SvgElement, string, string> logElementOldIDNewID = null)
89
90
        {
            var result = false;
davescriven's avatar
davescriven committed
91
92
            if (!string.IsNullOrEmpty(element.ID))
            {
tebjan's avatar
tebjan committed
93
94
                var newID = this.EnsureValidId(element.ID, autoForceUniqueID);
                if (autoForceUniqueID && newID != element.ID)
95
96
97
                {
                    if(logElementOldIDNewID != null)
                        logElementOldIDNewID(element, element.ID, newID);
tebjan's avatar
tebjan committed
98
                    element.ForceUniqueID(newID);
99
100
                    result = true;
                }
davescriven's avatar
davescriven committed
101
102
                this._idValueMap.Add(element.ID, element);
            }
103
104
            
            OnAdded(element);
105
            return result;
davescriven's avatar
davescriven committed
106
107
108
109
110
111
112
113
114
115
116
117
        }

        /// <summary>
        /// Removed the specified <see cref="SvgElement"/> from ID management.
        /// </summary>
        /// <param name="element">The <see cref="SvgElement"/> to be removed from ID management.</param>
        public virtual void Remove(SvgElement element)
        {
            if (!string.IsNullOrEmpty(element.ID))
            {
                this._idValueMap.Remove(element.ID);
            }
118
119
            
            OnRemoved(element);
davescriven's avatar
davescriven committed
120
121
122
123
124
125
        }

        /// <summary>
        /// Ensures that the specified ID is valid within the containing <see cref="SvgDocument"/>.
        /// </summary>
        /// <param name="id">A <see cref="string"/> containing the ID to validate.</param>
126
        /// <param name="autoForceUniqueID">Creates a new unique id <see cref="string"/>.</param>
davescriven's avatar
davescriven committed
127
128
129
130
        /// <exception cref="SvgException">
        /// <para>The ID cannot start with a digit.</para>
        /// <para>An element with the same ID already exists within the containing <see cref="SvgDocument"/>.</para>
        /// </exception>
tebjan's avatar
tebjan committed
131
        public string EnsureValidId(string id, bool autoForceUniqueID = false)
davescriven's avatar
davescriven committed
132
        {
133

davescriven's avatar
davescriven committed
134
135
            if (string.IsNullOrEmpty(id))
            {
136
                return id;
davescriven's avatar
davescriven committed
137
138
139
140
            }

            if (char.IsDigit(id[0]))
            {
tebjan's avatar
tebjan committed
141
                if (autoForceUniqueID)
142
143
144
145
                {
                    return EnsureValidId("id" + id, true);
                }
                throw new SvgIDWrongFormatException("ID cannot start with a digit: '" + id + "'.");
davescriven's avatar
davescriven committed
146
147
148
149
            }

            if (this._idValueMap.ContainsKey(id))
            {
tebjan's avatar
tebjan committed
150
                if(autoForceUniqueID)
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
                {
                    var match = regex.Match(id);

                    int number;
                    if (match.Success && int.TryParse(match.Value.Substring(1), out number))
                    {
                        id = regex.Replace(id, "#" + (number + 1));
                    }
                    else
                    {
                        id += "#1";
                    }

                    return EnsureValidId(id, true);
                }
                throw new SvgIDExistsException("An element with the same ID already exists: '" + id + "'.");
davescriven's avatar
davescriven committed
167
            }
168
169

            return id;
davescriven's avatar
davescriven committed
170
        }
171
        private static readonly Regex regex = new Regex(@"#\d+$");
davescriven's avatar
davescriven committed
172
173
174
175
176
177
178
179
180
181

        /// <summary>
        /// Initialises a new instance of an <see cref="SvgElementIdManager"/>.
        /// </summary>
        /// <param name="document">The <see cref="SvgDocument"/> containing the <see cref="SvgElement"/>s to manage.</param>
        public SvgElementIdManager(SvgDocument document)
        {
            this._document = document;
            this._idValueMap = new Dictionary<string, SvgElement>();
        }
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
        
        public event EventHandler<SvgElementEventArgs> ElementAdded;
        public event EventHandler<SvgElementEventArgs> ElementRemoved;
        
        protected void OnAdded(SvgElement element)
        {
        	var handler = ElementAdded;
        	if(handler != null)
        	{
        		handler(this._document, new SvgElementEventArgs{ Element = element });
        	}
        }
        
        protected void OnRemoved(SvgElement element)
        {
        	var handler = ElementRemoved;
        	if(handler != null)
        	{
        		handler(this._document, new SvgElementEventArgs{ Element = element });
        	}
        }
        
    }
    
    public class SvgElementEventArgs : EventArgs
    {
    	public SvgElement Element;
davescriven's avatar
davescriven committed
209
210
    }
}