1 | #!/usr/bin/env python |
---|
2 | ### =========================================================================== |
---|
3 | ### |
---|
4 | ### Modifies or add an element in an XML file |
---|
5 | ### |
---|
6 | ### =========================================================================== |
---|
7 | ## |
---|
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. |
---|
11 | ## |
---|
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 | ## |
---|
28 | ## |
---|
29 | ## SVN information |
---|
30 | __Author__ = "$Author$" |
---|
31 | __Date__ = "$Date$" |
---|
32 | __Revision__ = "$Revision$" |
---|
33 | __Id__ = "$Id$" |
---|
34 | __HeadURL = "$HeadURL$" |
---|
35 | |
---|
36 | # 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 |
---|
37 | # 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" |
---|
38 | # python update_xml.py -i ~/Unix/TOOLS/MOSAIX/iodef_atm_to_oce.xml -o essai.xml -c InFile.txt |
---|
39 | |
---|
40 | # Tested with python/2.7.12 and python/3.6.4 |
---|
41 | # |
---|
42 | import xml.etree.ElementTree |
---|
43 | import argparse, sys, textwrap, shlex |
---|
44 | |
---|
45 | # Check version of Python |
---|
46 | Version = sys.version_info |
---|
47 | if Version < (2,7,0) : |
---|
48 | sys.stderr.write ( "You need python 2.7 or later to run this script\n" ) |
---|
49 | sys.stderr.write ( "Present version is: " + str(Version[0]) + "." + str(Version[1]) + "." + str(Version[2]) + "\n" ) |
---|
50 | sys.exit (1) |
---|
51 | |
---|
52 | ## ============================================================================ |
---|
53 | ## Needed functions |
---|
54 | |
---|
55 | def simplify_string_list (list_str) : |
---|
56 | '''Concatenate some elements of the list of strings when needed''' |
---|
57 | zlist = list_str.copy () ; list_new = [] |
---|
58 | while ( len (zlist) > 0 ) : |
---|
59 | arg = zlist.pop (0) |
---|
60 | if arg[0] == '"' : |
---|
61 | for arg2 in zlist.copy () : |
---|
62 | arg = arg + " " + arg2 |
---|
63 | zlist.pop (0) |
---|
64 | if arg2[-1] == '"' : break |
---|
65 | arg = arg.strip('"').strip("'") |
---|
66 | list_new.append (arg) |
---|
67 | return list_new |
---|
68 | |
---|
69 | def UpdateNode (iodef, Node, Text=None, Key=None) : |
---|
70 | '''Update an xml node''' |
---|
71 | # Remove whitespaces at both ends |
---|
72 | Node = Node.rstrip().lstrip() |
---|
73 | |
---|
74 | ## Find node |
---|
75 | nodeList = iodef.findall (Node) |
---|
76 | |
---|
77 | ## Check that one and only one node is found |
---|
78 | if len (nodeList) == 0 : |
---|
79 | print ( "Error : node not found" ) |
---|
80 | print ( "Node : ", Node ) |
---|
81 | sys.exit (1) |
---|
82 | |
---|
83 | if len (nodeList) > 1 : |
---|
84 | print ( "Error : " + len (nodeList)+" occurences of node found in file" ) |
---|
85 | print ( "Node : ", Node ) |
---|
86 | sys.exit (2) |
---|
87 | |
---|
88 | ## Update element |
---|
89 | elem = nodeList[0] |
---|
90 | |
---|
91 | if Text != None : |
---|
92 | if Verbose : print ( 'Node:', Node, ' -- Text:', Text ) |
---|
93 | if Debug : |
---|
94 | print ( 'Attributes of node: ' + str (elem.attrib) ) |
---|
95 | print ( 'Text : ' + str (elem.text) ) |
---|
96 | elem.text = Text |
---|
97 | |
---|
98 | if Key != None : |
---|
99 | |
---|
100 | # Check the syntax |
---|
101 | if not '=' in Key : |
---|
102 | print ( 'Key syntax error. Correct syntax is -k Key=Value' ) |
---|
103 | sys.exit (-1) |
---|
104 | else : |
---|
105 | KeyName, Value = Key.split ('=') |
---|
106 | if Verbose : print ( 'Node:', Node, ' -- Key:', Key ) |
---|
107 | # To do : check that KeyName exist (it is added if not : do we want that ?) |
---|
108 | if Debug : |
---|
109 | print ( 'Attributes of node: ' + str (elem.attrib) ) |
---|
110 | elem.attrib.update ( { KeyName:Value } ) |
---|
111 | |
---|
112 | return iodef |
---|
113 | |
---|
114 | ## ============================================================================ |
---|
115 | ## Main code |
---|
116 | |
---|
117 | # Creating a parser to read the command line arguments |
---|
118 | # The first step in using the argparse is creating an ArgumentParser object: |
---|
119 | parser = argparse.ArgumentParser (description = """ |
---|
120 | Examples with the modification on the command line : |
---|
121 | 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 |
---|
122 | python %(prog)s -i iodef.xml -n 'context[@id="interpol_run"]/file_definition/file[@id="dia"]/variable[@name="dest_grid"]' -t dstDomainType |
---|
123 | |
---|
124 | Usage with a command file : |
---|
125 | python %(prog)s -i iodef.xml -c commands.txt |
---|
126 | |
---|
127 | Syntax in the command file : removes the quote around the node description : |
---|
128 | -n context[@id="interpol_run"]/file_definition/file[@id="dia"]/variable[@name="dest_grid"] -t dstDomainType |
---|
129 | |
---|
130 | """ + "SVN : " + __Revision__, formatter_class=argparse.RawDescriptionHelpFormatter, epilog='-------- This is the end of the help message --------') |
---|
131 | |
---|
132 | # Adding arguments |
---|
133 | group1 = parser.add_mutually_exclusive_group (required=False) |
---|
134 | |
---|
135 | parser.add_argument ( '-i', '--input' , help="XML input file" , default='iodef.xml', type=str, metavar='<input_file>' ) |
---|
136 | parser.add_argument ( '-o', '--output' , help="XML output file" , default=None , type=str, metavar='<output_file>' ) |
---|
137 | parser.add_argument ( '-n', '--node' , help="XML node in Xpath syntax", default=None, type=str, metavar='<xml_node>') |
---|
138 | group1.add_argument ( '-k', '--key' , help="XML key to update and new value (-k <name>=<value>)", default=None, type=str, metavar='<xml_key>' ) |
---|
139 | group1.add_argument ( '-t', '--text' , help="Will replace the 'text' part of the Xpath by <text>", default=None, type=str, metavar='<text>' ) |
---|
140 | parser.add_argument ( '-d', '--debug' , help="Extensive debug prints", action="store_true", default=False ) |
---|
141 | parser.add_argument ( '-v', '--verbose', help="Some verbosity" , action="store_true", default=False ) |
---|
142 | parser.add_argument ( '-c', '--commandfile', help="file with list of command", default=None, type=str ) |
---|
143 | |
---|
144 | # Parse command line |
---|
145 | myargs = parser.parse_args () |
---|
146 | Verbose = myargs.verbose |
---|
147 | Debug = myargs.debug |
---|
148 | |
---|
149 | if Debug : print ( "Command line arguments : ", myargs ) |
---|
150 | |
---|
151 | FileCommand = myargs.commandfile |
---|
152 | FileIn = myargs.input |
---|
153 | FileOut = myargs.output |
---|
154 | Node = myargs.node |
---|
155 | Key = myargs.key |
---|
156 | Text = myargs.text |
---|
157 | |
---|
158 | if FileCommand != None : |
---|
159 | if ( Node != None or Key != None or Text != None ) : |
---|
160 | print ('Error : when a command file is specified, options -k|--key, -n|--node are unused' ) |
---|
161 | exit (-2) |
---|
162 | |
---|
163 | if FileOut == None : FileOut = FileIn |
---|
164 | |
---|
165 | ## Get XML tree from input file |
---|
166 | iodef = xml.etree.ElementTree.parse ( FileIn ) |
---|
167 | |
---|
168 | if FileCommand == None : |
---|
169 | ## Only one node to modify |
---|
170 | iodef = UpdateNode (iodef, Node, Key, Text) |
---|
171 | |
---|
172 | else : |
---|
173 | ## Read a list of modification commands in a command file |
---|
174 | fic = open (FileCommand, 'r') |
---|
175 | lignes = fic.readlines() |
---|
176 | |
---|
177 | for nn, ligne in enumerate (lignes) : |
---|
178 | ligne = ligne.strip ().split ('#')[0] # Remove leading and trailing blanks, and trailing comments |
---|
179 | if Debug : print (nn+1, ':', type (ligne) , ':', len (ligne) , ':', ligne, ':') |
---|
180 | if ligne == '' or ligne == None or len(ligne) == 0 or ligne[0] == '#' : |
---|
181 | if Debug : print ('Skips blank or comment line') |
---|
182 | else : |
---|
183 | list_args = shlex.split (ligne) |
---|
184 | |
---|
185 | if Debug : |
---|
186 | print ( '{:3d} : '.format(nn+1), end='') |
---|
187 | print ( list_args ) |
---|
188 | # Parse args line |
---|
189 | myargs_ligne = parser.parse_args (list_args) |
---|
190 | |
---|
191 | Node = myargs_ligne.node |
---|
192 | Key = myargs_ligne.key |
---|
193 | Text = myargs_ligne.text |
---|
194 | |
---|
195 | UpdateNode (iodef, Node, Text, Key) |
---|
196 | |
---|
197 | ## Writes XML tree to file |
---|
198 | iodef.write ( FileOut ) |
---|
199 | |
---|
200 | ## This is the end |
---|
201 | if Debug : print ('This is the end') |
---|
202 | #sys.exit (0) |
---|
203 | |
---|
204 | ### =========================================================================== |
---|
205 | ### |
---|
206 | ### That's all folk's !!! |
---|
207 | ### |
---|
208 | ### =========================================================================== |
---|
209 | |
---|