SvgElementIdManager.cs 6.62 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;
davescriven's avatar
davescriven committed
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

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)
        {
24
25
26
27
28
            if (id.StartsWith("url("))
            {
                id = id.Substring(4);
                id = id.TrimEnd(')');
            }
davescriven's avatar
davescriven committed
29
            if (id.StartsWith("#"))
30
            {
davescriven's avatar
davescriven committed
31
                id = id.Substring(1);
32
            }
33
34
35
36
37

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

            return element;
davescriven's avatar
davescriven committed
38
39
40
41
42
43
44
45
46
47
48
49
50
        }

        public virtual SvgElement GetElementById(Uri uri)
        {
            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
51
            AddAndForceUniqueID(element, null, false);
52
53
54
55
56
57
58
        }

        /// <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
59
        /// <param name="autoForceUniqueID">Pass true here, if you want the ID to be fixed</param>
60
61
        /// <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
62
        public virtual bool AddAndForceUniqueID(SvgElement element, SvgElement sibling, bool autoForceUniqueID = true, Action<SvgElement, string, string> logElementOldIDNewID = null)
63
64
        {
            var result = false;
davescriven's avatar
davescriven committed
65
66
            if (!string.IsNullOrEmpty(element.ID))
            {
tebjan's avatar
tebjan committed
67
68
                var newID = this.EnsureValidId(element.ID, autoForceUniqueID);
                if (autoForceUniqueID && newID != element.ID)
69
70
71
                {
                    if(logElementOldIDNewID != null)
                        logElementOldIDNewID(element, element.ID, newID);
tebjan's avatar
tebjan committed
72
                    element.ForceUniqueID(newID);
73
74
                    result = true;
                }
davescriven's avatar
davescriven committed
75
76
                this._idValueMap.Add(element.ID, element);
            }
77
78
            
            OnAdded(element);
79
            return result;
davescriven's avatar
davescriven committed
80
81
82
83
84
85
86
87
88
89
90
91
        }

        /// <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);
            }
92
93
            
            OnRemoved(element);
davescriven's avatar
davescriven committed
94
95
96
97
98
99
        }

        /// <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>
100
        /// <param name="autoForceUniqueID">Creates a new unique id <see cref="string"/>.</param>
davescriven's avatar
davescriven committed
101
102
103
104
        /// <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
105
        public string EnsureValidId(string id, bool autoForceUniqueID = false)
davescriven's avatar
davescriven committed
106
        {
107

davescriven's avatar
davescriven committed
108
109
            if (string.IsNullOrEmpty(id))
            {
110
                return id;
davescriven's avatar
davescriven committed
111
112
113
114
            }

            if (char.IsDigit(id[0]))
            {
tebjan's avatar
tebjan committed
115
                if (autoForceUniqueID)
116
117
118
119
                {
                    return EnsureValidId("id" + id, true);
                }
                throw new SvgIDWrongFormatException("ID cannot start with a digit: '" + id + "'.");
davescriven's avatar
davescriven committed
120
121
122
123
            }

            if (this._idValueMap.ContainsKey(id))
            {
tebjan's avatar
tebjan committed
124
                if(autoForceUniqueID)
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
                {
                    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
141
            }
142
143

            return id;
davescriven's avatar
davescriven committed
144
        }
145
        private static readonly Regex regex = new Regex(@"#\d+$");
davescriven's avatar
davescriven committed
146
147
148
149
150
151
152
153
154
155

        /// <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>();
        }
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
        
        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
183
184
    }
}