• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2
3# Copyright (c) 2012 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.
6
7"""Make the format of a vcproj really pretty.
8
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.
11
12   It outputs the resulting xml to stdout.
13"""
14
15
16import os
17import sys
18
19from xml.dom.minidom import parse
20from xml.dom.minidom import Node
21
22__author__ = "nsylvain (Nicolas Sylvain)"
23ARGUMENTS = None
24REPLACEMENTS = dict()
25
26
27def cmp(x, y):
28    return (x > y) - (x < y)
29
30
31class CmpTuple:
32    """Compare function between 2 tuple."""
33
34    def __call__(self, x, y):
35        return cmp(x[0], y[0])
36
37
38class CmpNode:
39    """Compare function between 2 xml nodes."""
40
41    def __call__(self, x, y):
42        def get_string(node):
43            node_string = "node"
44            node_string += node.nodeName
45            if node.nodeValue:
46                node_string += node.nodeValue
47
48            if node.attributes:
49                # We first sort by name, if present.
50                node_string += node.getAttribute("Name")
51
52                all_nodes = []
53                for (name, value) in node.attributes.items():
54                    all_nodes.append((name, value))
55
56                all_nodes.sort(CmpTuple())
57                for (name, value) in all_nodes:
58                    node_string += name
59                    node_string += value
60
61            return node_string
62
63        return cmp(get_string(x), get_string(y))
64
65
66def PrettyPrintNode(node, indent=0):
67    if node.nodeType == Node.TEXT_NODE:
68        if node.data.strip():
69            print("{}{}".format(" " * indent, node.data.strip()))
70        return
71
72    if node.childNodes:
73        node.normalize()
74    # Get the number of attributes
75    attr_count = 0
76    if node.attributes:
77        attr_count = node.attributes.length
78
79    # Print the main tag
80    if attr_count == 0:
81        print("{}<{}>".format(" " * indent, node.nodeName))
82    else:
83        print("{}<{}".format(" " * indent, node.nodeName))
84
85        all_attributes = []
86        for (name, value) in node.attributes.items():
87            all_attributes.append((name, value))
88            all_attributes.sort(CmpTuple())
89        for (name, value) in all_attributes:
90            print('{}  {}="{}"'.format(" " * indent, name, value))
91        print("%s>" % (" " * indent))
92    if node.nodeValue:
93        print("{}  {}".format(" " * indent, node.nodeValue))
94
95    for sub_node in node.childNodes:
96        PrettyPrintNode(sub_node, indent=indent + 2)
97    print("{}</{}>".format(" " * indent, node.nodeName))
98
99
100def FlattenFilter(node):
101    """Returns a list of all the node and sub nodes."""
102    node_list = []
103
104    if node.attributes and node.getAttribute("Name") == "_excluded_files":
105        # We don't add the "_excluded_files" filter.
106        return []
107
108    for current in node.childNodes:
109        if current.nodeName == "Filter":
110            node_list.extend(FlattenFilter(current))
111        else:
112            node_list.append(current)
113
114    return node_list
115
116
117def FixFilenames(filenames, current_directory):
118    new_list = []
119    for filename in filenames:
120        if filename:
121            for key in REPLACEMENTS:
122                filename = filename.replace(key, REPLACEMENTS[key])
123            os.chdir(current_directory)
124            filename = filename.strip("\"' ")
125            if filename.startswith("$"):
126                new_list.append(filename)
127            else:
128                new_list.append(os.path.abspath(filename))
129    return new_list
130
131
132def AbsoluteNode(node):
133    """Makes all the properties we know about in this node absolute."""
134    if node.attributes:
135        for (name, value) in node.attributes.items():
136            if name in [
137                "InheritedPropertySheets",
138                "RelativePath",
139                "AdditionalIncludeDirectories",
140                "IntermediateDirectory",
141                "OutputDirectory",
142                "AdditionalLibraryDirectories",
143            ]:
144                # We want to fix up these paths
145                path_list = value.split(";")
146                new_list = FixFilenames(path_list, os.path.dirname(ARGUMENTS[1]))
147                node.setAttribute(name, ";".join(new_list))
148            if not value:
149                node.removeAttribute(name)
150
151
152def CleanupVcproj(node):
153    """For each sub node, we call recursively this function."""
154    for sub_node in node.childNodes:
155        AbsoluteNode(sub_node)
156        CleanupVcproj(sub_node)
157
158    # Normalize the node, and remove all extraneous whitespaces.
159    for sub_node in node.childNodes:
160        if sub_node.nodeType == Node.TEXT_NODE:
161            sub_node.data = sub_node.data.replace("\r", "")
162            sub_node.data = sub_node.data.replace("\n", "")
163            sub_node.data = sub_node.data.rstrip()
164
165    # Fix all the semicolon separated attributes to be sorted, and we also
166    # remove the dups.
167    if node.attributes:
168        for (name, value) in node.attributes.items():
169            sorted_list = sorted(value.split(";"))
170            unique_list = []
171            for i in sorted_list:
172                if not unique_list.count(i):
173                    unique_list.append(i)
174            node.setAttribute(name, ";".join(unique_list))
175            if not value:
176                node.removeAttribute(name)
177
178    if node.childNodes:
179        node.normalize()
180
181    # For each node, take a copy, and remove it from the list.
182    node_array = []
183    while node.childNodes and node.childNodes[0]:
184        # Take a copy of the node and remove it from the list.
185        current = node.childNodes[0]
186        node.removeChild(current)
187
188        # If the child is a filter, we want to append all its children
189        # to this same list.
190        if current.nodeName == "Filter":
191            node_array.extend(FlattenFilter(current))
192        else:
193            node_array.append(current)
194
195    # Sort the list.
196    node_array.sort(CmpNode())
197
198    # Insert the nodes in the correct order.
199    for new_node in node_array:
200        # But don't append empty tool node.
201        if new_node.nodeName == "Tool":
202            if new_node.attributes and new_node.attributes.length == 1:
203                # This one was empty.
204                continue
205        if new_node.nodeName == "UserMacro":
206            continue
207        node.appendChild(new_node)
208
209
210def GetConfiguationNodes(vcproj):
211    # TODO(nsylvain): Find a better way to navigate the xml.
212    nodes = []
213    for node in vcproj.childNodes:
214        if node.nodeName == "Configurations":
215            for sub_node in node.childNodes:
216                if sub_node.nodeName == "Configuration":
217                    nodes.append(sub_node)
218
219    return nodes
220
221
222def GetChildrenVsprops(filename):
223    dom = parse(filename)
224    if dom.documentElement.attributes:
225        vsprops = dom.documentElement.getAttribute("InheritedPropertySheets")
226        return FixFilenames(vsprops.split(";"), os.path.dirname(filename))
227    return []
228
229
230def SeekToNode(node1, child2):
231    # A text node does not have properties.
232    if child2.nodeType == Node.TEXT_NODE:
233        return None
234
235    # Get the name of the current node.
236    current_name = child2.getAttribute("Name")
237    if not current_name:
238        # There is no name. We don't know how to merge.
239        return None
240
241    # Look through all the nodes to find a match.
242    for sub_node in node1.childNodes:
243        if sub_node.nodeName == child2.nodeName:
244            name = sub_node.getAttribute("Name")
245            if name == current_name:
246                return sub_node
247
248    # No match. We give up.
249    return None
250
251
252def MergeAttributes(node1, node2):
253    # No attributes to merge?
254    if not node2.attributes:
255        return
256
257    for (name, value2) in node2.attributes.items():
258        # Don't merge the 'Name' attribute.
259        if name == "Name":
260            continue
261        value1 = node1.getAttribute(name)
262        if value1:
263            # The attribute exist in the main node. If it's equal, we leave it
264            # untouched, otherwise we concatenate it.
265            if value1 != value2:
266                node1.setAttribute(name, ";".join([value1, value2]))
267        else:
268            # The attribute does not exist in the main node. We append this one.
269            node1.setAttribute(name, value2)
270
271        # If the attribute was a property sheet attributes, we remove it, since
272        # they are useless.
273        if name == "InheritedPropertySheets":
274            node1.removeAttribute(name)
275
276
277def MergeProperties(node1, node2):
278    MergeAttributes(node1, node2)
279    for child2 in node2.childNodes:
280        child1 = SeekToNode(node1, child2)
281        if child1:
282            MergeProperties(child1, child2)
283        else:
284            node1.appendChild(child2.cloneNode(True))
285
286
287def main(argv):
288    """Main function of this vcproj prettifier."""
289    global ARGUMENTS
290    ARGUMENTS = argv
291
292    # check if we have exactly 1 parameter.
293    if len(argv) < 2:
294        print(
295            'Usage: %s "c:\\path\\to\\vcproj.vcproj" [key1=value1] '
296            "[key2=value2]" % argv[0]
297        )
298        return 1
299
300    # Parse the keys
301    for i in range(2, len(argv)):
302        (key, value) = argv[i].split("=")
303        REPLACEMENTS[key] = value
304
305    # Open the vcproj and parse the xml.
306    dom = parse(argv[1])
307
308    # First thing we need to do is find the Configuration Node and merge them
309    # with the vsprops they include.
310    for configuration_node in GetConfiguationNodes(dom.documentElement):
311        # Get the property sheets associated with this configuration.
312        vsprops = configuration_node.getAttribute("InheritedPropertySheets")
313
314        # Fix the filenames to be absolute.
315        vsprops_list = FixFilenames(
316            vsprops.strip().split(";"), os.path.dirname(argv[1])
317        )
318
319        # Extend the list of vsprops with all vsprops contained in the current
320        # vsprops.
321        for current_vsprops in vsprops_list:
322            vsprops_list.extend(GetChildrenVsprops(current_vsprops))
323
324        # Now that we have all the vsprops, we need to merge them.
325        for current_vsprops in vsprops_list:
326            MergeProperties(configuration_node, parse(current_vsprops).documentElement)
327
328    # Now that everything is merged, we need to cleanup the xml.
329    CleanupVcproj(dom.documentElement)
330
331    # Finally, we use the prett xml function to print the vcproj back to the
332    # user.
333    # print dom.toprettyxml(newl="\n")
334    PrettyPrintNode(dom.documentElement)
335    return 0
336
337
338if __name__ == "__main__":
339    sys.exit(main(sys.argv))
340