SvgElementIdManager.cs 8.02 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>
85
        /// <param name="sibling">Not used.</param>
tebjan's avatar
tebjan committed
86
        /// <param name="autoForceUniqueID">Pass true here, if you want the ID to be fixed</param>
87
88
        /// <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
89
        public virtual bool AddAndForceUniqueID(SvgElement element, SvgElement sibling, bool autoForceUniqueID = true, Action<SvgElement, string, string> logElementOldIDNewID = null)
90
91
        {
            var result = false;
davescriven's avatar
davescriven committed
92
93
            if (!string.IsNullOrEmpty(element.ID))
            {
tebjan's avatar
tebjan committed
94
95
                var newID = this.EnsureValidId(element.ID, autoForceUniqueID);
                if (autoForceUniqueID && newID != element.ID)
96
97
98
                {
                    if(logElementOldIDNewID != null)
                        logElementOldIDNewID(element, element.ID, newID);
tebjan's avatar
tebjan committed
99
                    element.ForceUniqueID(newID);
100
101
                    result = true;
                }
davescriven's avatar
davescriven committed
102
103
                this._idValueMap.Add(element.ID, element);
            }
104
105
            
            OnAdded(element);
106
            return result;
davescriven's avatar
davescriven committed
107
108
109
110
111
112
113
114
115
116
117
118
        }

        /// <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);
            }
119
120
            
            OnRemoved(element);
davescriven's avatar
davescriven committed
121
122
123
124
125
126
        }

        /// <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>
127
        /// <param name="autoForceUniqueID">Creates a new unique id <see cref="string"/>.</param>
davescriven's avatar
davescriven committed
128
129
130
131
        /// <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
132
        public string EnsureValidId(string id, bool autoForceUniqueID = false)
davescriven's avatar
davescriven committed
133
        {
134

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

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

            if (this._idValueMap.ContainsKey(id))
            {
tebjan's avatar
tebjan committed
151
                if(autoForceUniqueID)
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
                {
                    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
168
            }
169
170

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

        /// <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>();
        }
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
209
        
        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
210
211
    }
}