# This python script parses the spec files from MSBuild to create
# mappings from compiler options to IDE XML specifications.  For
# more information see here:

#  http://blogs.msdn.com/vcblog/archive/2008/12/16/msbuild-task.aspx
#  "${PROGRAMFILES}/MSBuild/Microsoft.Cpp/v4.0/1033/cl.xml"
#  "${PROGRAMFILES}/MSBuild/Microsoft.Cpp/v4.0/1033/lib.xml"
#  "${PROGRAMFILES}/MSBuild/Microsoft.Cpp/v4.0/1033/link.xml"
#  "${PROGRAMFILES}/MSBuild/Microsoft.Cpp/v4.0/V110/1033/cl.xml"
#  "${PROGRAMFILES}/MSBuild/Microsoft.Cpp/v4.0/V110/1033/lib.xml"
#  "${PROGRAMFILES}/MSBuild/Microsoft.Cpp/v4.0/V110/1033/link.xml"
#  "${PROGRAMFILES}/MSBuild/Microsoft.Cpp/v4.0/v120/1033/cl.xml"
#  "${PROGRAMFILES}/MSBuild/Microsoft.Cpp/v4.0/v120/1033/lib.xml"
#  "${PROGRAMFILES}/MSBuild/Microsoft.Cpp/v4.0/v120/1033/link.xml"
#  "${PROGRAMFILES}/MSBuild/Microsoft.Cpp/v4.0/V140/1033/cl.xml"
#  "${PROGRAMFILES}/MSBuild/Microsoft.Cpp/v4.0/V140/1033/lib.xml"
#  "${PROGRAMFILES}/MSBuild/Microsoft.Cpp/v4.0/V140/1033/link.xml"
#
#  BoolProperty  <Name>true|false</Name>
#   simple example:
#     <BoolProperty ReverseSwitch="Oy-" Name="OmitFramePointers"
#      Category="Optimization" Switch="Oy">
#   <BoolProperty.DisplayName>  <BoolProperty.Description>
# <CLCompile>
#     <OmitFramePointers>true</OmitFramePointers>
#  </ClCompile>
#
#  argument means it might be this: /MP3
#   example with argument:
#   <BoolProperty Name="MultiProcessorCompilation" Category="General" Switch="MP">
#      <BoolProperty.DisplayName>
#        <sys:String>Multi-processor Compilation</sys:String>
#      </BoolProperty.DisplayName>
#      <BoolProperty.Description>
#        <sys:String>Multi-processor Compilation</sys:String>
#      </BoolProperty.Description>
#      <Argument Property="ProcessorNumber" IsRequired="false" />
#    </BoolProperty>
# <CLCompile>
#   <MultiProcessorCompilation>true</MultiProcessorCompilation>
#   <ProcessorNumber>4</ProcessorNumber>
#  </ClCompile>
#  IntProperty
#     not used AFIT
#  <IntProperty Name="ProcessorNumber" Category="General" Visible="false">


#  per config options example
#    <EnableFiberSafeOptimizations Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</EnableFiberSafeOptimizations>
#
#  EnumProperty
#   <EnumProperty Name="Optimization" Category="Optimization">
#      <EnumProperty.DisplayName>
#       <sys:String>Optimization</sys:String>
#     </EnumProperty.DisplayName>
#     <EnumProperty.Description>
#       <sys:String>Select option for code optimization; choose Custom to use specific optimization options.     (/Od, /O1, /O2, /Ox)</sys:String>
#     </EnumProperty.Description>
#      <EnumValue Name="MaxSpeed" Switch="O2">
#       <EnumValue.DisplayName>
#         <sys:String>Maximize Speed</sys:String>
#       </EnumValue.DisplayName>
#       <EnumValue.Description>
#         <sys:String>Equivalent to /Og /Oi /Ot /Oy /Ob2 /Gs /GF /Gy</sys:String>
#       </EnumValue.Description>
#     </EnumValue>
#     <EnumValue Name="MinSpace" Switch="O1">
#       <EnumValue.DisplayName>
#         <sys:String>Minimize Size</sys:String>
#       </EnumValue.DisplayName>
#       <EnumValue.Description>
#         <sys:String>Equivalent to /Og /Os /Oy /Ob2 /Gs /GF /Gy</sys:String>
#       </EnumValue.Description>
#     </EnumValue>
#     example for O2 would be this:
#     <Optimization>MaxSpeed</Optimization>
#     example for O1 would be this:
#     <Optimization>MinSpace</Optimization>
#
#  StringListProperty
#   <StringListProperty Name="PreprocessorDefinitions" Category="Preprocessor" Switch="D ">
#     <StringListProperty.DisplayName>
#       <sys:String>Preprocessor Definitions</sys:String>
#     </StringListProperty.DisplayName>
#     <StringListProperty.Description>
#       <sys:String>Defines a preprocessing symbols for your source file.</sys:String>
#     </StringListProperty.Description>
#   </StringListProperty>

#   <StringListProperty Subtype="folder" Name="AdditionalIncludeDirectories" Category="General" Switch="I">
#     <StringListProperty.DisplayName>
#       <sys:String>Additional Include Directories</sys:String>
#     </StringListProperty.DisplayName>
#     <StringListProperty.Description>
#       <sys:String>Specifies one or more directories to add to the include path; separate with semi-colons if more than one.     (/I[path])</sys:String>
#     </StringListProperty.Description>
#   </StringListProperty>
#  StringProperty

# Example add bill include:

#   <AdditionalIncludeDirectories>..\..\..\..\..\..\bill;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>


import sys
from xml.dom.minidom import parse, parseString

def getText(node):
    nodelist = node.childNodes
    rc = ""
    for child in nodelist:
        if child.nodeType == child.TEXT_NODE:
            rc = rc + child.data
    return rc

def print_tree(document, spaces=""):
  for i in range(len(document.childNodes)):
    if document.childNodes[i].nodeType == document.childNodes[i].ELEMENT_NODE:
      print spaces+str(document.childNodes[i].nodeName )
    print_tree(document.childNodes[i],spaces+"----")
  pass

###########################################################################################
#Data structure that stores a property of MSBuild
class Property:
  #type = type of MSBuild property (ex. if the property is EnumProperty type should be "Enum")
  #attributeNames = a list of any attributes that this property could have (ex. if this was a EnumProperty it should be ["Name","Category"])
  #document = the dom file that's root node is the Property node (ex. if you were parsing a BoolProperty the root node should be something like <BoolProperty Name="RegisterOutput" Category="General" IncludeInCommandLine="false">
  def __init__(self,type,attributeNames,document=None):
    self.suffix_type = "Property"
    self.prefix_type = type
    self.attributeNames = attributeNames
    self.attributes = {}
    self.DisplayName = ""
    self.Description = ""
    self.argumentProperty = ""
    self.argumentIsRequired = ""
    self.values = []
    if document is not None:
      self.populate(document)
    pass

  #document = the dom file that's root node is the Property node (ex. if you were parsing a BoolProperty the root node should be something like <BoolProperty Name="RegisterOutput" Category="General" IncludeInCommandLine="false">
  #spaces = do not use
  def populate(self,document, spaces = ""):
    if document.nodeName == self.prefix_type+self.suffix_type:
      for i in self.attributeNames:
        self.attributes[i] = document.getAttribute(i)
    for i in range(len(document.childNodes)):
      child = document.childNodes[i]
      if child.nodeType == child.ELEMENT_NODE:
        if child.nodeName == self.prefix_type+self.suffix_type+".DisplayName":
          self.DisplayName = getText(child.childNodes[1])
        if child.nodeName == self.prefix_type+self.suffix_type+".Description":
          self.Description = getText(child.childNodes[1])
        if child.nodeName == "Argument":
          self.argumentProperty = child.getAttribute("Property")
          self.argumentIsRequired = child.getAttribute("IsRequired")
        if child.nodeName == self.prefix_type+"Value":
          va = Property(self.prefix_type,["Name","DisplayName","Switch"])
          va.suffix_type = "Value"
          va.populate(child)
          self.values.append(va)
      self.populate(child,spaces+"----")
      pass

  #toString function
  def __str__(self):
    toReturn = self.prefix_type+self.suffix_type+":"
    for i in self.attributeNames:
      toReturn += "\n    "+i+": "+self.attributes[i]
    if self.argumentProperty != "":
      toReturn += "\n    Argument:\n        Property: "+self.argumentProperty+"\n        IsRequired: "+self.argumentIsRequired
    for i in self.values:
        toReturn+="\n    "+str(i).replace("\n","\n    ")
    return toReturn
###########################################################################################

###########################################################################################
#Class that populates itself from an MSBuild file and outputs it in CMake
#format

class MSBuildToCMake:
  #document = the entire MSBuild xml file
  def __init__(self,document=None):
    self.enumProperties = []
    self.stringProperties = []
    self.stringListProperties = []
    self.boolProperties = []
    self.intProperties = []
    if document!=None :
      self.populate(document)
    pass

  #document = the entire MSBuild xml file
  #spaces = don't use
  #To add a new property (if they exist) copy and paste this code and fill in appropriate places
  #
  #if child.nodeName == "<Name>Property":
  #        self.<Name>Properties.append(Property("<Name>",[<List of attributes>],child))
  #
  #Replace <Name> with the name of the new property (ex. if property is StringProperty replace <Name> with String)
  #Replace <List of attributes> with a list of attributes in your property's root node
  #in the __init__ function add the line self.<Name>Properties = []
  #
  #That is all that is required to add new properties
  #
  def populate(self,document, spaces=""):
    for i in range(len(document.childNodes)):
      child = document.childNodes[i]
      if child.nodeType == child.ELEMENT_NODE:
        if child.nodeName == "EnumProperty":
          self.enumProperties.append(Property("Enum",["Name","Category"],child))
        if child.nodeName == "StringProperty":
          self.stringProperties.append(Property("String",["Name","Subtype","Separator","Category","Visible","IncludeInCommandLine","Switch","DisplayName","ReadOnly"],child))
        if child.nodeName == "StringListProperty":
           self.stringListProperties.append(Property("StringList",["Name","Category","Switch","DisplayName","Subtype"],child))
        if child.nodeName == "BoolProperty":
           self.boolProperties.append(Property("Bool",["ReverseSwitch","Name","Category","Switch","DisplayName","SwitchPrefix","IncludeInCommandLine"],child))
        if child.nodeName == "IntProperty":
           self.intProperties.append(Property("Int",["Name","Category","Visible"],child))
      self.populate(child,spaces+"----")
    pass

  #outputs information that CMake needs to know about MSBuild xml files
  def toCMake(self):
    toReturn = "static cmVS7FlagTable cmVS10CxxTable[] =\n{\n"
    toReturn += "\n  //Enum Properties\n"
    lastProp = {}
    for i in self.enumProperties:
      if i.attributes["Name"] == "CompileAsManaged":
        #write these out after the rest of the enumProperties
        lastProp = i
        continue
      for j in i.values:
        #hardcore Brad King's manual fixes for cmVS10CLFlagTable.h
        if i.attributes["Name"] == "PrecompiledHeader" and j.attributes["Switch"] != "":
          toReturn+="  {\""+i.attributes["Name"]+"\", \""+j.attributes["Switch"]+"\",\n   \""+j.attributes["DisplayName"]+"\", \""+j.attributes["Name"]+"\",\n   cmVS7FlagTable::UserValueIgnored | cmVS7FlagTable::Continue},\n"
        else:
          #default (normal, non-hardcoded) case
          toReturn+="  {\""+i.attributes["Name"]+"\", \""+j.attributes["Switch"]+"\",\n   \""+j.attributes["DisplayName"]+"\", \""+j.attributes["Name"]+"\", 0},\n"
      toReturn += "\n"

    if lastProp != {}:
      for j in lastProp.values:
          toReturn+="  {\""+lastProp.attributes["Name"]+"\", \""+j.attributes["Switch"]+"\",\n   \""+j.attributes["DisplayName"]+"\", \""+j.attributes["Name"]+"\", 0},\n"
      toReturn += "\n"

    toReturn += "\n  //Bool Properties\n"
    for i in self.boolProperties:
      if i.argumentProperty == "":
        if i.attributes["ReverseSwitch"] != "":
          toReturn += "  {\""+i.attributes["Name"]+"\", \""+i.attributes["ReverseSwitch"]+"\", \"\", \"false\", 0},\n"
        if i.attributes["Switch"] != "":
          toReturn += "  {\""+i.attributes["Name"]+"\", \""+i.attributes["Switch"]+"\", \"\", \"true\", 0},\n"

    toReturn += "\n  //Bool Properties With Argument\n"
    for i in self.boolProperties:
      if i.argumentProperty != "":
        if i.attributes["ReverseSwitch"] != "":
          toReturn += "  {\""+i.attributes["Name"]+"\", \""+i.attributes["ReverseSwitch"]+"\", \"\", \"false\",\n   cmVS7FlagTable::UserValueIgnored | cmVS7FlagTable::Continue},\n"
          toReturn += "  {\""+i.attributes["Name"]+"\", \""+i.attributes["ReverseSwitch"]+"\", \""+i.attributes["DisplayName"]+"\", \"\",\n   cmVS7FlagTable::UserValueRequired},\n"
        if i.attributes["Switch"] != "":
          toReturn += "  {\""+i.attributes["Name"]+"\", \""+i.attributes["Switch"]+"\", \"\", \"true\",\n   cmVS7FlagTable::UserValueIgnored | cmVS7FlagTable::Continue},\n"
          toReturn += "  {\""+i.argumentProperty+"\", \""+i.attributes["Switch"]+"\", \""+i.attributes["DisplayName"]+"\", \"\",\n   cmVS7FlagTable::UserValueRequired},\n"

    toReturn += "\n  //String List Properties\n"
    for i in self.stringListProperties:
      if i.attributes["Switch"] == "":
        toReturn += "  // Skip [" + i.attributes["Name"] + "] - no command line Switch.\n";
      else:
        toReturn +="  {\""+i.attributes["Name"]+"\", \""+i.attributes["Switch"]+"\",\n   \""+i.attributes["DisplayName"]+"\",\n   \"\", cmVS7FlagTable::UserValue | cmVS7FlagTable::SemicolonAppendable},\n"

    toReturn += "\n  //String Properties\n"
    for i in self.stringProperties:
      if i.attributes["Switch"] == "":
        if i.attributes["Name"] == "PrecompiledHeaderFile":
          #more hardcoding
          toReturn += "  {\"PrecompiledHeaderFile\", \"Yc\",\n"
          toReturn += "   \"Precompiled Header Name\",\n"
          toReturn += "   \"\", cmVS7FlagTable::UserValueRequired},\n"
          toReturn += "  {\"PrecompiledHeaderFile\", \"Yu\",\n"
          toReturn += "   \"Precompiled Header Name\",\n"
          toReturn += "   \"\", cmVS7FlagTable::UserValueRequired},\n"
        else:
          toReturn += "  // Skip [" + i.attributes["Name"] + "] - no command line Switch.\n";
      else:
        toReturn +="  {\""+i.attributes["Name"]+"\", \""+i.attributes["Switch"]+i.attributes["Separator"]+"\",\n   \""+i.attributes["DisplayName"]+"\",\n   \"\", cmVS7FlagTable::UserValue},\n"

    toReturn += "  {0,0,0,0,0}\n};"
    return toReturn
    pass

  #toString function
  def __str__(self):
    toReturn = ""
    allList = [self.enumProperties,self.stringProperties,self.stringListProperties,self.boolProperties,self.intProperties]
    for p in allList:
      for i in p:
        toReturn += "==================================================\n"+str(i).replace("\n","\n    ")+"\n==================================================\n"

    return toReturn
###########################################################################################

###########################################################################################
# main function
def main(argv):
  xml_file = None
  help = """
  Please specify an input xml file with -x

  Exiting...
  Have a nice day :)"""
  for i in range(0,len(argv)):
    if argv[i] == "-x":
      xml_file = argv[i+1]
    if argv[i] == "-h":
      print help
      sys.exit(0)
    pass
  if xml_file == None:
    print help
    sys.exit(1)

  f = open(xml_file,"r")
  xml_str = f.read()
  xml_dom = parseString(xml_str)

  convertor = MSBuildToCMake(xml_dom)
  print convertor.toCMake()

  xml_dom.unlink()
###########################################################################################
# main entry point
if __name__ == "__main__":
  main(sys.argv)

sys.exit(0)
