SvgElementIdManager.cs 6.45 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)
        {
51
            AddAndFixID(element, null, false);
52
53
54
55
56
57
58
59
60
61
        }

        /// <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>
        /// <param name="autoFixID">Pass true here, if you want the ID to be fixed</param>
        /// <param name="logElementOldIDNewID">If not null, the action is called before the id is fixed</param>
        /// <returns>true, if ID was altered</returns>
62
        public virtual bool AddAndFixID(SvgElement element, SvgElement sibling, bool autoFixID = true, Action<SvgElement, string, string> logElementOldIDNewID = null)
63
64
        {
            var result = false;
davescriven's avatar
davescriven committed
65
66
            if (!string.IsNullOrEmpty(element.ID))
            {
67
68
69
70
71
72
73
74
                var newID = this.EnsureValidId(element.ID, autoFixID);
                if (autoFixID && newID != element.ID)
                {
                    if(logElementOldIDNewID != null)
                        logElementOldIDNewID(element, element.ID, newID);
                    element.FixID(newID);
                    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
100
101
102
103
        }

        /// <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>
        /// <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>
104
        public string EnsureValidId(string id, bool autoFixID = false)
davescriven's avatar
davescriven committed
105
        {
106

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

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

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

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

        /// <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>();
        }
155
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
        
        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
182
183
    }
}