-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
XmlUtilities.cs
185 lines (162 loc) · 7.79 KB
/
XmlUtilities.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
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
182
183
184
185
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Xml;
using System.Text.RegularExpressions;
using Microsoft.Build.Construction;
namespace Microsoft.Build.Shared
{
/// <summary>
/// This class contains utility methods for XML manipulation.
/// </summary>
static internal class XmlUtilities
{
/// <summary>
/// This method renames an XML element. Well, actually you can't directly
/// rename an XML element using the DOM, so what you have to do is create
/// a brand new XML element with the new name, and copy over all the attributes
/// and children. This method returns the new XML element object.
/// If the name is the same, does nothing and returns the element passed in.
/// </summary>
/// <param name="oldElement"></param>
/// <param name="newElementName"></param>
/// <param name="xmlNamespace">Can be null if global namespace.</param>
/// <returns>new/renamed element</returns>
internal static XmlElementWithLocation RenameXmlElement(XmlElementWithLocation oldElement, string newElementName, string xmlNamespace)
{
if (String.Equals(oldElement.Name, newElementName, StringComparison.Ordinal) && String.Equals(oldElement.NamespaceURI, xmlNamespace, StringComparison.Ordinal))
{
return oldElement;
}
XmlElementWithLocation newElement = (xmlNamespace == null)
? (XmlElementWithLocation)oldElement.OwnerDocument.CreateElement(newElementName)
: (XmlElementWithLocation)oldElement.OwnerDocument.CreateElement(newElementName, xmlNamespace);
// Copy over all the attributes.
foreach (XmlAttribute oldAttribute in oldElement.Attributes)
{
XmlAttribute newAttribute = (XmlAttribute)oldAttribute.CloneNode(true);
newElement.SetAttributeNode(newAttribute);
}
// Move over all the child nodes - no need to change their identity
while (oldElement.HasChildNodes)
{
// This conveniently updates FirstChild and HasChildNodes on oldElement.
newElement.AppendChild(oldElement.FirstChild);
}
// Add the new element in the same place the old element was.
oldElement.ParentNode?.ReplaceChild(newElement, oldElement);
return newElement;
}
/// <summary>
/// Verifies that a name is valid for the name of an item, property, or piece of metadata.
/// If it isn't, throws an ArgumentException indicating the invalid character.
/// </summary>
/// <remarks>
/// Note that our restrictions are more stringent than the XML Standard's restrictions.
/// </remarks>
/// <throws>ArgumentException</throws>
/// <param name="name">name to validate</param>
internal static void VerifyThrowArgumentValidElementName(string name)
{
ErrorUtilities.VerifyThrowArgumentLength(name, nameof(name));
int firstInvalidCharLocation = LocateFirstInvalidElementNameCharacter(name);
if (-1 != firstInvalidCharLocation)
{
ErrorUtilities.ThrowArgument("OM_NameInvalid", name, name[firstInvalidCharLocation]);
}
}
/// <summary>
/// Verifies that a name is valid for the name of an item, property, or piece of metadata.
/// If it isn't, throws an InvalidProjectException indicating the invalid character.
/// </summary>
/// <remarks>
/// Note that our restrictions are more stringent than the XML Standard's restrictions.
/// </remarks>
internal static void VerifyThrowProjectValidElementName(string name, IElementLocation location)
{
ErrorUtilities.VerifyThrowArgumentLength(name, nameof(name));
int firstInvalidCharLocation = LocateFirstInvalidElementNameCharacter(name);
if (-1 != firstInvalidCharLocation)
{
ProjectErrorUtilities.ThrowInvalidProject(location, "NameInvalid", name, name[firstInvalidCharLocation]);
}
}
/// <summary>
/// Verifies that a name is valid for the name of an item, property, or piece of metadata.
/// If it isn't, throws an InvalidProjectException indicating the invalid character.
/// </summary>
/// <remarks>
/// Note that our restrictions are more stringent than the XML Standard's restrictions.
/// </remarks>
internal static void VerifyThrowProjectValidElementName(XmlElementWithLocation element)
{
string name = element.Name;
int firstInvalidCharLocation = LocateFirstInvalidElementNameCharacter(name);
if (-1 != firstInvalidCharLocation)
{
ProjectErrorUtilities.ThrowInvalidProject(element.Location, "NameInvalid", name, name[firstInvalidCharLocation]);
}
}
/// <summary>
/// Indicates if the given name is valid as the name of an item, property or metadatum.
/// </summary>
/// <remarks>
/// Note that our restrictions are more stringent than those of the XML Standard.
/// </remarks>
/// <param name="name"></param>
/// <returns>true, if name is valid</returns>
internal static bool IsValidElementName(string name)
{
return LocateFirstInvalidElementNameCharacter(name) == -1;
}
/// <summary>
/// Finds the location of the first invalid character, if any, in the name of an
/// item, property, or piece of metadata. Returns the location of the first invalid character, or -1 if there are none.
/// Valid names must match this pattern: [A-Za-z_][A-Za-z_0-9\-.]*
/// Note, this is a subset of all possible valid XmlElement names: we use a subset because we also
/// have to match this same set in our regular expressions, and allowing all valid XmlElement name
/// characters in a regular expression would be impractical.
/// </summary>
/// <remarks>
/// Note that our restrictions are more stringent than the XML Standard's restrictions.
/// PERF: This method has to be as fast as possible, as it's called when any item, property, or piece
/// of metadata is constructed.
/// </remarks>
internal static int LocateFirstInvalidElementNameCharacter(string name)
{
// Check the first character.
// Try capital letters first.
// Optimize slightly for success.
if (!IsValidInitialElementNameCharacter(name[0]))
{
return 0;
}
// Check subsequent characters.
// Try lower case letters first.
// Optimize slightly for success.
for (int i = 1; i < name.Length; i++)
{
if (!IsValidSubsequentElementNameCharacter(name[i]))
{
return i;
}
}
// If we got here, the name was valid.
return -1;
}
internal static bool IsValidInitialElementNameCharacter(char c)
{
return (c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c == '_');
}
internal static bool IsValidSubsequentElementNameCharacter(char c)
{
return (c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') ||
(c == '_') ||
(c == '-');
}
}
}