3
# Copyright (c) 2009 Google Inc. All rights reserved.
4
# Use of this source code is governed by a BSD-style license that can be
5
# found in the LICENSE file.
7
"""Make the format of a vcproj really pretty.
9
This script normalize and sort an xml. It also fetches all the properties
10
inside linked vsprops and include them explicitly in the vcproj.
12
It outputs the resulting xml to stdout.
15
__author__ = 'nsylvain (Nicolas Sylvain)'
20
from xml.dom.minidom import parse
21
from xml.dom.minidom import Node
27
"""Compare function between 2 tuple."""
28
def __call__(self, x, y):
31
return cmp(key1, key2)
34
"""Compare function between 2 xml nodes."""
36
def get_string(self, node):
38
node_string += node.nodeName
40
node_string += node.nodeValue
43
# We first sort by name, if present.
44
node_string += node.getAttribute("Name")
47
for (name, value) in node.attributes.items():
48
all_nodes.append((name, value))
50
all_nodes.sort(CmpTuple())
51
for (name, value) in all_nodes:
57
def __call__(self, x, y):
58
return cmp(self.get_string(x), self.get_string(y))
60
def PrettyPrintNode(node, indent=0):
61
if node.nodeType == Node.TEXT_NODE:
63
print '%s%s' % (' '*indent, node.data.strip())
68
# Get the number of attributes
71
attr_count = node.attributes.length
75
print '%s<%s>' % (' '*indent, node.nodeName)
77
print '%s<%s' % (' '*indent, node.nodeName)
80
for (name, value) in node.attributes.items():
81
all_attributes.append((name, value))
82
all_attributes.sort(CmpTuple())
83
for (name, value) in all_attributes:
84
print '%s %s="%s"' % (' '*indent, name, value)
85
print '%s>' % (' '*indent)
87
print '%s %s' % (' '*indent, node.nodeValue)
89
for sub_node in node.childNodes:
90
PrettyPrintNode(sub_node, indent=indent+2)
91
print '%s</%s>' % (' '*indent, node.nodeName)
93
def FlattenFilter(node):
94
"""Returns a list of all the node and sub nodes."""
97
if (node.attributes and
98
node.getAttribute('Name') == '_excluded_files'):
99
# We don't add the "_excluded_files" filter.
102
for current in node.childNodes:
103
if current.nodeName == 'Filter':
104
node_list.extend(FlattenFilter(current))
106
node_list.append(current)
110
def FixFilenames(filenames, current_directory):
112
for filename in filenames:
114
for key in REPLACEMENTS:
115
filename = filename.replace(key, REPLACEMENTS[key])
116
os.chdir(current_directory)
117
filename = filename.strip('"\' ')
118
if filename.startswith('$'):
119
new_list.append(filename)
121
new_list.append(os.path.abspath(filename))
124
def AbsoluteNode(node):
125
# Make all the properties we know about in this node absolute.
127
for (name, value) in node.attributes.items():
128
if name in ['InheritedPropertySheets', 'RelativePath',
129
'AdditionalIncludeDirectories',
130
'IntermediateDirectory', 'OutputDirectory',
131
'AdditionalLibraryDirectories']:
132
# We want to fix up these paths
133
path_list = value.split(';')
134
new_list = FixFilenames(path_list, os.path.dirname(ARGUMENTS[1]))
135
node.setAttribute(name, ';'.join(new_list))
137
node.removeAttribute(name)
139
def CleanupVcproj(node):
140
# For each sub node, we call recursively this function.
141
for sub_node in node.childNodes:
142
AbsoluteNode(sub_node)
143
CleanupVcproj(sub_node)
145
# Normalize the node, and remove all extranous whitespaces.
146
for sub_node in node.childNodes:
147
if sub_node.nodeType == Node.TEXT_NODE:
148
sub_node.data = sub_node.data.replace("\r", "")
149
sub_node.data = sub_node.data.replace("\n", "")
150
sub_node.data = sub_node.data.rstrip()
152
# Fix all the semicolon separated attributes to be sorted, and we also
155
for (name, value) in node.attributes.items():
156
sorted_list = sorted(value.split(';'))
158
[unique_list.append(i) for i in sorted_list if not unique_list.count(i)]
159
node.setAttribute(name, ';'.join(unique_list))
161
node.removeAttribute(name)
166
# For each node, take a copy, and remove it from the list.
168
while node.childNodes and node.childNodes[0]:
169
# Take a copy of the node and remove it from the list.
170
current = node.childNodes[0]
171
node.removeChild(current)
173
# If the child is a filter, we want to append all its children
175
if current.nodeName == 'Filter':
176
node_array.extend(FlattenFilter(current))
178
node_array.append(current)
182
node_array.sort(CmpNode())
184
# Insert the nodes in the correct order.
185
for new_node in node_array:
186
# But don't append empty tool node.
187
if new_node.nodeName == 'Tool':
188
if new_node.attributes and new_node.attributes.length == 1:
189
# This one was empty.
191
if new_node.nodeName == 'UserMacro':
193
node.appendChild(new_node)
195
def GetConfiguationNodes(vcproj):
196
#TODO(nsylvain): Find a better way to navigate the xml.
198
for node in vcproj.childNodes:
199
if node.nodeName == "Configurations":
200
for sub_node in node.childNodes:
201
if sub_node.nodeName == "Configuration":
202
nodes.append(sub_node)
206
def GetChildrenVsprops(filename):
207
dom = parse(filename)
208
if dom.documentElement.attributes:
209
vsprops = dom.documentElement.getAttribute('InheritedPropertySheets')
210
return FixFilenames(vsprops.split(';'), os.path.dirname(filename))
213
def SeekToNode(node1, child2):
214
# A text node does not have properties.
215
if child2.nodeType == Node.TEXT_NODE:
218
# Get the name of the current node.
219
current_name = child2.getAttribute("Name")
221
# There is no name. We don't know how to merge.
224
# Look through all the nodes to find a match.
225
for sub_node in node1.childNodes:
226
if sub_node.nodeName == child2.nodeName:
227
name = sub_node.getAttribute("Name")
228
if name == current_name:
231
# No match. We give up.
234
def MergeAttributes(node1, node2):
235
# No attributes to merge?
236
if not node2.attributes:
239
for (name, value2) in node2.attributes.items():
240
# Don't merge the 'Name' attribute.
243
value1 = node1.getAttribute(name)
245
# The attribute exist in the main node. If it's equal, we leave it
246
# untouched, otherwise we concatenate it.
248
node1.setAttribute(name, ';'.join([value1, value2]))
250
# The attribute does nto exist in the main node. We append this one.
251
node1.setAttribute(name, value2)
253
# If the attribute was a property sheet attributes, we remove it, since
255
if name == 'InheritedPropertySheets':
256
node1.removeAttribute(name)
258
def MergeProperties(node1, node2):
259
MergeAttributes(node1, node2)
260
for child2 in node2.childNodes:
261
child1 = SeekToNode(node1, child2)
263
MergeProperties(child1, child2)
265
node1.appendChild(child2.cloneNode(True))
271
"""Main function of this vcproj prettifier."""
273
# check if we have exactly 1 parameter.
275
print ('Usage: %s "c:\\path\\to\\vcproj.vcproj" [key1=value1] '
276
'[key2=value2]' % argv[0])
280
for i in range(2, len(argv)):
281
(key, value) = argv[i].split('=')
282
REPLACEMENTS[key] = value
284
# Open the vcproj and parse the xml.
287
# First thing we need to do is find the Configuration Node and merge them
288
# with the vsprops they include.
289
for configuration_node in GetConfiguationNodes(dom.documentElement):
290
# Get the property sheets associated with this configuration.
291
vsprops = configuration_node.getAttribute('InheritedPropertySheets')
293
# Fix the filenames to be absolute.
294
vsprops_list = FixFilenames(vsprops.strip().split(';'),
295
os.path.dirname(argv[1]))
297
# Extend the list of vsprops with all vsprops contained in the current
299
for current_vsprops in vsprops_list:
300
vsprops_list.extend(GetChildrenVsprops(current_vsprops))
302
# Now that we have all the vsprops, we need to merge them.
303
for current_vsprops in vsprops_list:
304
MergeProperties(configuration_node,
305
parse(current_vsprops).documentElement)
307
# Now that everything is merged, we need to cleanup the xml.
308
CleanupVcproj(dom.documentElement)
310
# Finally, we use the prett xml function to print the vcproj back to the
312
#print dom.toprettyxml(newl="\n")
313
PrettyPrintNode(dom.documentElement)
315
if __name__ == '__main__':