[5916] | 1 | #!/usr/bin/env python |
---|
[3620] | 2 | ### =========================================================================== |
---|
| 3 | ### |
---|
| 4 | ### Modifies or add an element in an XML file |
---|
| 5 | ### |
---|
| 6 | ### =========================================================================== |
---|
| 7 | ## |
---|
[6190] | 8 | ## MOSAIX is under CeCILL_V2 licence. See "Licence_CeCILL_V2-en.txt" |
---|
| 9 | ## file for an english version of the licence and |
---|
| 10 | ## "Licence_CeCILL_V2-fr.txt" for a french version. |
---|
[3620] | 11 | ## |
---|
[6190] | 12 | ## Permission is hereby granted, free of charge, to any person or |
---|
| 13 | ## organization obtaining a copy of the software and accompanying |
---|
| 14 | ## documentation covered by this license (the "Software") to use, |
---|
| 15 | ## reproduce, display, distribute, execute, and transmit the |
---|
| 16 | ## Software, and to prepare derivative works of the Software, and to |
---|
| 17 | ## permit third-parties to whom the Software is furnished to do so, |
---|
| 18 | ## all subject to the following: |
---|
| 19 | ## |
---|
| 20 | ## Warning, to install, configure, run, use any of MOSAIX software or |
---|
| 21 | ## to read the associated documentation you'll need at least one (1) |
---|
| 22 | ## brain in a reasonably working order. Lack of this implement will |
---|
| 23 | ## void any warranties (either express or implied). Authors assumes |
---|
| 24 | ## no responsability for errors, omissions, data loss, or any other |
---|
| 25 | ## consequences caused directly or indirectly by the usage of his |
---|
| 26 | ## software by incorrectly or partially configured |
---|
| 27 | ## |
---|
[6666] | 28 | ''' |
---|
| 29 | Python script used to perform on the fly |
---|
| 30 | editing of xml files. Used by `CreateWeights.bash`. More |
---|
| 31 | information with `python update_xml.py -h`. |
---|
| 32 | |
---|
[3623] | 33 | ## SVN information |
---|
[6666] | 34 | Author = "$Author$" |
---|
| 35 | Date = "$Date$" |
---|
| 36 | Revision = "$Revision$" |
---|
| 37 | Id = "$Id$" |
---|
| 38 | HeadURL = "$HeadURL$" |
---|
| 39 | ''' |
---|
[3623] | 40 | |
---|
[6666] | 41 | ## SVN information |
---|
| 42 | __SVN__ = ({ |
---|
| 43 | 'Author' : "$Author$", |
---|
| 44 | 'Date' : "$Date$", |
---|
| 45 | 'Revision' : "$Revision$", |
---|
| 46 | 'Id' : "$Id$", |
---|
| 47 | 'HeadURL' : "$HeadURL$", |
---|
| 48 | }) |
---|
| 49 | |
---|
[6093] | 50 | # python update_xml.py -i ~/Unix/TOOLS/MOSAIX/iodef_atm_to_oce.xml -o essai.xml -n 'context[@id="interpol_read"]/file_definition/file[@id="file_src"]/field[@id="mask_src"]' -k name=Bidon |
---|
[6091] | 51 | # python update_xml.py -i ~/Unix/TOOLS/MOSAIX/iodef_atm_to_oce.xml -d -o essai.xml -n 'context[@id="interpol_run"]/file_definition/file[@id="dia"]/variable[@name="title"]' -t "SRC mask interpolated to DST" |
---|
| 52 | # python update_xml.py -i ~/Unix/TOOLS/MOSAIX/iodef_atm_to_oce.xml -o essai.xml -c InFile.txt |
---|
| 53 | |
---|
[3620] | 54 | # Tested with python/2.7.12 and python/3.6.4 |
---|
| 55 | # |
---|
| 56 | import xml.etree.ElementTree |
---|
[6666] | 57 | import argparse, sys, shlex |
---|
| 58 | |
---|
[3671] | 59 | # Check version of Python |
---|
| 60 | Version = sys.version_info |
---|
[6091] | 61 | |
---|
| 62 | ## ============================================================================ |
---|
| 63 | ## Needed functions |
---|
| 64 | |
---|
| 65 | def simplify_string_list (list_str) : |
---|
| 66 | '''Concatenate some elements of the list of strings when needed''' |
---|
| 67 | zlist = list_str.copy () ; list_new = [] |
---|
[6666] | 68 | while len (zlist) > 0 : |
---|
| 69 | arg = zlist.pop (0) |
---|
[6091] | 70 | if arg[0] == '"' : |
---|
| 71 | for arg2 in zlist.copy () : |
---|
| 72 | arg = arg + " " + arg2 |
---|
| 73 | zlist.pop (0) |
---|
| 74 | if arg2[-1] == '"' : break |
---|
| 75 | arg = arg.strip('"').strip("'") |
---|
| 76 | list_new.append (arg) |
---|
| 77 | return list_new |
---|
[6666] | 78 | |
---|
| 79 | def UpdateNode (piodef, pNode, pText=None, pKey=None) : |
---|
[6091] | 80 | '''Update an xml node''' |
---|
| 81 | # Remove whitespaces at both ends |
---|
[6666] | 82 | zNode = pNode.rstrip().lstrip() |
---|
[6091] | 83 | |
---|
| 84 | ## Find node |
---|
[6666] | 85 | nodeList = piodef.findall (zNode) |
---|
[6091] | 86 | |
---|
| 87 | ## Check that one and only one node is found |
---|
| 88 | if len (nodeList) == 0 : |
---|
| 89 | print ( "Error : node not found" ) |
---|
[6666] | 90 | print ( "Node : ", zNode ) |
---|
[6091] | 91 | sys.exit (1) |
---|
[6666] | 92 | |
---|
[6091] | 93 | if len (nodeList) > 1 : |
---|
[6093] | 94 | print ( "Error : " + len (nodeList)+" occurences of node found in file" ) |
---|
[6666] | 95 | print ( "Node : ", zNode ) |
---|
[6091] | 96 | sys.exit (2) |
---|
| 97 | |
---|
| 98 | ## Update element |
---|
| 99 | elem = nodeList[0] |
---|
| 100 | |
---|
[6666] | 101 | if pText is not None : |
---|
| 102 | if Verbose : print ( 'Node:', zNode, ' -- Text:', pText ) |
---|
[6091] | 103 | if Debug : |
---|
| 104 | print ( 'Attributes of node: ' + str (elem.attrib) ) |
---|
| 105 | print ( 'Text : ' + str (elem.text) ) |
---|
[6666] | 106 | elem.text = pText |
---|
[6091] | 107 | |
---|
[6666] | 108 | if pKey is not None : |
---|
| 109 | |
---|
[6093] | 110 | # Check the syntax |
---|
[6666] | 111 | if not '=' in pKey : |
---|
[6093] | 112 | print ( 'Key syntax error. Correct syntax is -k Key=Value' ) |
---|
| 113 | sys.exit (-1) |
---|
| 114 | else : |
---|
[6666] | 115 | KeyName, Value = pKey.split ('=') |
---|
| 116 | if Verbose : print ( 'Node:', zNode, ' -- Key:', pKey ) |
---|
[6093] | 117 | # To do : check that KeyName exist (it is added if not : do we want that ?) |
---|
| 118 | if Debug : |
---|
| 119 | print ( 'Attributes of node: ' + str (elem.attrib) ) |
---|
| 120 | elem.attrib.update ( { KeyName:Value } ) |
---|
[6091] | 121 | |
---|
[6666] | 122 | return piodef |
---|
[6091] | 123 | |
---|
| 124 | ## ============================================================================ |
---|
| 125 | ## Main code |
---|
| 126 | |
---|
[5916] | 127 | # Creating a parser to read the command line arguments |
---|
| 128 | # The first step in using the argparse is creating an ArgumentParser object: |
---|
| 129 | parser = argparse.ArgumentParser (description = """ |
---|
[6666] | 130 | Examples with the modification on the command line : |
---|
[5916] | 131 | python %(prog)s -i iodef.xml -n 'context[@id="interpol_run"]/file_definition/file[@id="file_src"]/field[@id="mask_source"]' -k name -v maskutil_T |
---|
| 132 | python %(prog)s -i iodef.xml -n 'context[@id="interpol_run"]/file_definition/file[@id="dia"]/variable[@name="dest_grid"]' -t dstDomainType |
---|
[3620] | 133 | |
---|
[6666] | 134 | Usage with a command file : |
---|
[6091] | 135 | python %(prog)s -i iodef.xml -c commands.txt |
---|
[6666] | 136 | |
---|
| 137 | Syntax in the command file : removes the quote around the node description : |
---|
[6091] | 138 | -n context[@id="interpol_run"]/file_definition/file[@id="dia"]/variable[@name="dest_grid"] -t dstDomainType |
---|
| 139 | |
---|
[6666] | 140 | """ + "SVN : " + __SVN__['Revision'], formatter_class=argparse.RawDescriptionHelpFormatter, epilog='-------- This is the end of the help message --------') |
---|
[6091] | 141 | |
---|
[5916] | 142 | # Adding arguments |
---|
[6091] | 143 | group1 = parser.add_mutually_exclusive_group (required=False) |
---|
[3620] | 144 | |
---|
[6093] | 145 | parser.add_argument ( '-i', '--input' , help="XML input file" , default='iodef.xml', type=str, metavar='<input_file>' ) |
---|
| 146 | parser.add_argument ( '-o', '--output' , help="XML output file" , default=None , type=str, metavar='<output_file>' ) |
---|
[6091] | 147 | parser.add_argument ( '-n', '--node' , help="XML node in Xpath syntax", default=None, type=str, metavar='<xml_node>') |
---|
[6093] | 148 | group1.add_argument ( '-k', '--key' , help="XML key to update and new value (-k <name>=<value>)", default=None, type=str, metavar='<xml_key>' ) |
---|
[6091] | 149 | group1.add_argument ( '-t', '--text' , help="Will replace the 'text' part of the Xpath by <text>", default=None, type=str, metavar='<text>' ) |
---|
[6093] | 150 | parser.add_argument ( '-d', '--debug' , help="Extensive debug prints", action="store_true", default=False ) |
---|
| 151 | parser.add_argument ( '-v', '--verbose', help="Some verbosity" , action="store_true", default=False ) |
---|
[6091] | 152 | parser.add_argument ( '-c', '--commandfile', help="file with list of command", default=None, type=str ) |
---|
[3620] | 153 | |
---|
[5916] | 154 | # Parse command line |
---|
[6091] | 155 | myargs = parser.parse_args () |
---|
| 156 | Verbose = myargs.verbose |
---|
| 157 | Debug = myargs.debug |
---|
[3620] | 158 | |
---|
[6091] | 159 | if Debug : print ( "Command line arguments : ", myargs ) |
---|
[3671] | 160 | |
---|
[6091] | 161 | FileCommand = myargs.commandfile |
---|
| 162 | FileIn = myargs.input |
---|
| 163 | FileOut = myargs.output |
---|
| 164 | Node = myargs.node |
---|
| 165 | Key = myargs.key |
---|
| 166 | Text = myargs.text |
---|
[6666] | 167 | |
---|
| 168 | if FileCommand is not None : |
---|
| 169 | if ( Node is not None or Key is not None or Text is not None ) : |
---|
[6093] | 170 | print ('Error : when a command file is specified, options -k|--key, -n|--node are unused' ) |
---|
[6091] | 171 | exit (-2) |
---|
[6666] | 172 | |
---|
| 173 | if FileOut is None : FileOut = FileIn |
---|
| 174 | |
---|
[3620] | 175 | ## Get XML tree from input file |
---|
| 176 | iodef = xml.etree.ElementTree.parse ( FileIn ) |
---|
| 177 | |
---|
[6666] | 178 | if FileCommand is None : |
---|
[6091] | 179 | ## Only one node to modify |
---|
[6093] | 180 | iodef = UpdateNode (iodef, Node, Key, Text) |
---|
[3620] | 181 | |
---|
[6091] | 182 | else : |
---|
| 183 | ## Read a list of modification commands in a command file |
---|
[6666] | 184 | fic = open (FileCommand, 'r', encoding='utf-8') |
---|
| 185 | lignes = fic.readlines () |
---|
[3620] | 186 | |
---|
[6091] | 187 | for nn, ligne in enumerate (lignes) : |
---|
[6093] | 188 | ligne = ligne.strip ().split ('#')[0] # Remove leading and trailing blanks, and trailing comments |
---|
| 189 | if Debug : print (nn+1, ':', type (ligne) , ':', len (ligne) , ':', ligne, ':') |
---|
[6666] | 190 | if ligne == '' or ligne is None or len(ligne) == 0 or ligne[0] == '#' : |
---|
[6093] | 191 | if Debug : print ('Skips blank or comment line') |
---|
[6666] | 192 | else : |
---|
[6093] | 193 | list_args = shlex.split (ligne) |
---|
[6666] | 194 | |
---|
[6093] | 195 | if Debug : |
---|
| 196 | print ( '{:3d} : '.format(nn+1), end='') |
---|
| 197 | print ( list_args ) |
---|
| 198 | # Parse args line |
---|
| 199 | myargs_ligne = parser.parse_args (list_args) |
---|
[3620] | 200 | |
---|
[6093] | 201 | Node = myargs_ligne.node |
---|
| 202 | Key = myargs_ligne.key |
---|
| 203 | Text = myargs_ligne.text |
---|
[6091] | 204 | |
---|
[6666] | 205 | iodef = UpdateNode (iodef, Node, Text, Key) |
---|
| 206 | |
---|
[3620] | 207 | ## Writes XML tree to file |
---|
| 208 | iodef.write ( FileOut ) |
---|
| 209 | |
---|
| 210 | ## This is the end |
---|
[6093] | 211 | if Debug : print ('This is the end') |
---|
| 212 | #sys.exit (0) |
---|
[6666] | 213 | |
---|
[3620] | 214 | ### =========================================================================== |
---|
| 215 | ### |
---|
| 216 | ### That's all folk's !!! |
---|
| 217 | ### |
---|
| 218 | ### =========================================================================== |
---|