1#!/usr/bin/env python 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 15from __future__ import print_function 16 17import os 18import sys 19 20from xml.dom.minidom import parse 21from xml.dom.minidom import Node 22 23__author__ = 'nsylvain (Nicolas Sylvain)' 24 25try: 26 cmp 27except NameError: 28 def cmp(x, y): 29 return (x > y) - (x < y) 30 31REPLACEMENTS = dict() 32ARGUMENTS = None 33 34 35class CmpTuple(object): 36 """Compare function between 2 tuple.""" 37 def __call__(self, x, y): 38 return cmp(x[0], y[0]) 39 40 41class CmpNode(object): 42 """Compare function between 2 xml nodes.""" 43 44 def __call__(self, x, y): 45 def get_string(node): 46 node_string = "node" 47 node_string += node.nodeName 48 if node.nodeValue: 49 node_string += node.nodeValue 50 51 if node.attributes: 52 # We first sort by name, if present. 53 node_string += node.getAttribute("Name") 54 55 all_nodes = [] 56 for (name, value) in node.attributes.items(): 57 all_nodes.append((name, value)) 58 59 all_nodes.sort(CmpTuple()) 60 for (name, value) in all_nodes: 61 node_string += name 62 node_string += value 63 64 return node_string 65 66 return cmp(get_string(x), get_string(y)) 67 68 69def PrettyPrintNode(node, indent=0): 70 if node.nodeType == Node.TEXT_NODE: 71 if node.data.strip(): 72 print('%s%s' % (' '*indent, node.data.strip())) 73 return 74 75 if node.childNodes: 76 node.normalize() 77 # Get the number of attributes 78 attr_count = 0 79 if node.attributes: 80 attr_count = node.attributes.length 81 82 # Print the main tag 83 if attr_count == 0: 84 print('%s<%s>' % (' '*indent, node.nodeName)) 85 else: 86 print('%s<%s' % (' '*indent, node.nodeName)) 87 88 all_attributes = [] 89 for (name, value) in node.attributes.items(): 90 all_attributes.append((name, value)) 91 all_attributes.sort(CmpTuple()) 92 for (name, value) in all_attributes: 93 print('%s %s="%s"' % (' '*indent, name, value)) 94 print('%s>' % (' '*indent)) 95 if node.nodeValue: 96 print('%s %s' % (' '*indent, node.nodeValue)) 97 98 for sub_node in node.childNodes: 99 PrettyPrintNode(sub_node, indent=indent+2) 100 print('%s</%s>' % (' '*indent, node.nodeName)) 101 102 103def FlattenFilter(node): 104 """Returns a list of all the node and sub nodes.""" 105 node_list = [] 106 107 if (node.attributes and 108 node.getAttribute('Name') == '_excluded_files'): 109 # We don't add the "_excluded_files" filter. 110 return [] 111 112 for current in node.childNodes: 113 if current.nodeName == 'Filter': 114 node_list.extend(FlattenFilter(current)) 115 else: 116 node_list.append(current) 117 118 return node_list 119 120 121def FixFilenames(filenames, current_directory): 122 new_list = [] 123 for filename in filenames: 124 if filename: 125 for key in REPLACEMENTS: 126 filename = filename.replace(key, REPLACEMENTS[key]) 127 os.chdir(current_directory) 128 filename = filename.strip('"\' ') 129 if filename.startswith('$'): 130 new_list.append(filename) 131 else: 132 new_list.append(os.path.abspath(filename)) 133 return new_list 134 135 136def AbsoluteNode(node): 137 """Makes all the properties we know about in this node absolute.""" 138 if node.attributes: 139 for (name, value) in node.attributes.items(): 140 if name in ['InheritedPropertySheets', 'RelativePath', 141 'AdditionalIncludeDirectories', 142 'IntermediateDirectory', 'OutputDirectory', 143 'AdditionalLibraryDirectories']: 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 extranous 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 196 # Sort the list. 197 node_array.sort(CmpNode()) 198 199 # Insert the nodes in the correct order. 200 for new_node in node_array: 201 # But don't append empty tool node. 202 if new_node.nodeName == 'Tool': 203 if new_node.attributes and new_node.attributes.length == 1: 204 # This one was empty. 205 continue 206 if new_node.nodeName == 'UserMacro': 207 continue 208 node.appendChild(new_node) 209 210 211def GetConfiguationNodes(vcproj): 212 #TODO(nsylvain): Find a better way to navigate the xml. 213 nodes = [] 214 for node in vcproj.childNodes: 215 if node.nodeName == "Configurations": 216 for sub_node in node.childNodes: 217 if sub_node.nodeName == "Configuration": 218 nodes.append(sub_node) 219 220 return nodes 221 222 223def GetChildrenVsprops(filename): 224 dom = parse(filename) 225 if dom.documentElement.attributes: 226 vsprops = dom.documentElement.getAttribute('InheritedPropertySheets') 227 return FixFilenames(vsprops.split(';'), os.path.dirname(filename)) 228 return [] 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(('Usage: %s "c:\\path\\to\\vcproj.vcproj" [key1=value1] ' 295 '[key2=value2]' % argv[0])) 296 return 1 297 298 # Parse the keys 299 for i in range(2, len(argv)): 300 (key, value) = argv[i].split('=') 301 REPLACEMENTS[key] = value 302 303 # Open the vcproj and parse the xml. 304 dom = parse(argv[1]) 305 306 # First thing we need to do is find the Configuration Node and merge them 307 # with the vsprops they include. 308 for configuration_node in GetConfiguationNodes(dom.documentElement): 309 # Get the property sheets associated with this configuration. 310 vsprops = configuration_node.getAttribute('InheritedPropertySheets') 311 312 # Fix the filenames to be absolute. 313 vsprops_list = FixFilenames(vsprops.strip().split(';'), 314 os.path.dirname(argv[1])) 315 316 # Extend the list of vsprops with all vsprops contained in the current 317 # vsprops. 318 for current_vsprops in vsprops_list: 319 vsprops_list.extend(GetChildrenVsprops(current_vsprops)) 320 321 # Now that we have all the vsprops, we need to merge them. 322 for current_vsprops in vsprops_list: 323 MergeProperties(configuration_node, 324 parse(current_vsprops).documentElement) 325 326 # Now that everything is merged, we need to cleanup the xml. 327 CleanupVcproj(dom.documentElement) 328 329 # Finally, we use the prett xml function to print the vcproj back to the 330 # user. 331 #print dom.toprettyxml(newl="\n") 332 PrettyPrintNode(dom.documentElement) 333 return 0 334 335 336if __name__ == '__main__': 337 sys.exit(main(sys.argv)) 338