SvgElementIdManager.cs 7.84 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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
            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
67
68
69
70
71
72
73
74
75
            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
76
            AddAndForceUniqueID(element, null, false);
77
78
79
80
81
82
83
        }

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

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

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

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

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

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

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

        /// <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>();
        }
181
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
        
        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
208
209
    }
}