source: xmlTools/trunk/posUpdate/XmlPatch.cs@ 1080

Last change on this file since 1080 was 874, checked in by s10k, 11 years ago

XmlTools: v0.9

File size: 19.0 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.Diagnostics;
4using System.IO;
5using System.Text;
6using System.Xml;
7using IronJS;
8
9namespace xmlTools
10{
11 /// <summary>
12 /// This classes parses a .patch xml tools file and applies its content to the files which it specifies
13 /// </summary>
14 class XmlPatch
15 {
16 String fileName;
17 String forceFiles = "";
18 bool globalNoBackups = false;
19 IronJS.Hosting.CSharp.Context jsEngine = null; // initialize only when necessary
20
21 public XmlPatch(String file, bool noBackups)
22 {
23 fileName = file;
24 globalNoBackups = noBackups;
25 }
26
27 public XmlPatch(String file, String forceInFiles, bool noBackups)
28 {
29 fileName = file;
30 forceFiles = forceInFiles; //We support apply the operation in diverse forced files (NameOfFile parameter will be ignored)
31 globalNoBackups = noBackups;
32 }
33
34 /// <summary>
35 /// Applies the patch file. Returns true if successful otherwise returns false.
36 /// </summary>
37 /// <returns></returns>
38 public bool startPatch()
39 {
40 string line;
41
42 // Read the file and display it line by line.
43 System.IO.StreamReader file = new System.IO.StreamReader(fileName);
44
45 while ((line = file.ReadLine()) != null) //read while we don't reach the end of the file
46 {
47 if (line.StartsWith("@ADDTO "))
48 {
49 string operation = line;
50 string xmlToInject = "";
51
52 file.ReadLine(); //ignore <xml> start header
53 while ((line = file.ReadLine()) != "</xml>")
54 {
55 xmlToInject += line + System.Environment.NewLine; //get all the xml that will be injected
56 }
57 if (!addOperation(operation, xmlToInject))
58 {
59 Program.printAppError(Program.appErrors.PATCH_ADDTO_PROCESS_ERROR, "Error while performing adding operation in patch file. Aborting...");
60 return false;
61 }
62 }
63 else if (line.StartsWith("@REMOVE "))
64 {
65 if (!removeOperation(line))
66 {
67 Program.printAppError(Program.appErrors.PATCH_REMOVE_PROCESS_ERROR, "Error while performing remove operation in patch file. Aborting...");
68 return false;
69 }
70 }
71 else if (line.StartsWith("@COMMAND "))
72 {
73 if (!executeCommand(line))
74 {
75 Program.printAppError(Program.appErrors.PATCH_COMMAND_PROCESS_ERROR, "Error while performing command operation in patch file. Aborting...");
76 return false;
77 }
78 }
79 else if (line.StartsWith("@CUSTOMCODE "))
80 {
81 string operation = line;
82 string jsCode = "";
83
84 file.ReadLine(); //ignore <xml> start header
85 while ((line = file.ReadLine()) != "</code>")
86 {
87 jsCode += line + System.Environment.NewLine; //get all the xml that will be injected
88 }
89 if (!executeCode(operation, jsCode))
90 {
91 Program.printAppError(Program.appErrors.PATCH_CODE_PROCESS_ERROR, "Error while performing code operation in patch file. Aborting...");
92 return false;
93 }
94 }
95 }
96
97 file.Close();
98
99 return true;
100 }
101
102 /// <summary>
103 /// Inserts xml in a desired Element. Returns true or false if it succeeds
104 /// </summary>
105 /// <param name="operation"></param>
106 /// <param name="xmlToInject"></param>
107 /// <returns></returns>
108 private bool addOperation(string operation, string xmlToInject)
109 {
110 //@ADDTO File "example.xml" ParentElement "Animation" Element "Lookup"
111
112 string FileParam = "", ParentElementParam = "", ElementParam = "";
113
114 //---------------------------------------------------Parse Operation command (start)
115 try
116 {
117 if (String.IsNullOrEmpty(forceFiles))
118 {
119 FileParam = getPatchParameter(operation, "File");
120 }
121 else
122 {
123 FileParam = forceFiles;
124 }
125
126 ParentElementParam = getPatchParameter(operation, "ParentElement"); //Get the ParentElement
127
128 ElementParam = getPatchParameter(operation, "Element"); //Get the Element
129 }
130 catch (Exception e)
131 {
132 Program.printAppError(Program.appErrors.PATCH_ADDTO_ERROR_PARSING_XML, "Error parsing addOperation in Patch file.\n" + e.ToString());
133 return false;
134 }
135
136 if (String.IsNullOrEmpty(ElementParam))
137 {
138 return false;
139 }
140
141 //---------------------------------------------------Parse Operation command (end)
142 List<String> filesToProcess = new List<String>();
143 if (String.IsNullOrEmpty(FileParam))
144 {
145 filesToProcess = Util.getAllXmlFiles(); //no file specified, use all xml files found in same folder
146 }
147 else if (Util.containsWildcard(FileParam))
148 {
149 filesToProcess = Util.getXmlFilesWildcard(FileParam);
150 }
151 else
152 {
153 filesToProcess.Add(FileParam);
154 }
155
156 //---------------------------------------------------XML Injection (start)
157 foreach (String currFile in filesToProcess)
158 {
159 if (!this.globalNoBackups && !Util.ContainsIgnoreCase(operation, "NoBackups")) // only skip backup if specified via global parameter or in patch file
160 {
161 Util.backupFile(currFile);
162 }
163
164 XmlDocument xdoc = new XmlDocument();
165 xdoc.Load(currFile);
166
167 List<XmlNode> myElements = new List<XmlNode>();
168 Util.getAllSpecificElements(xdoc.DocumentElement, ref myElements, ElementParam, ParentElementParam); //Returns all after "Oni" element
169
170 if (myElements.Count == 0)
171 {
172 Program.printAppError(Program.appErrors.PATCH_ELEMENT_NOT_FOUND, "Error in addOperation in Patch file: the element specified doesn't exist.");
173 return false;
174 }
175
176 try
177 {
178 XmlNode newXml = xdoc.ImportNode(Util.stringToXmlNode(xmlToInject), true); //necessary to import node or ArgumentException will be thrown when appending
179
180 myElements[myElements.Count - 1].AppendChild(newXml); // Append the code after last element
181
182 xdoc.Save(currFile);
183 }
184 catch (XmlException e)
185 {
186 Program.printAppError(Program.appErrors.PATCH_ADDTO_ERROR_PARSING_XML, "Error parsing xml to addOperation in Patch file.\n" + e.ToString());
187 return false;
188 }
189 }
190 //---------------------------------------------------XML Injection (end)
191
192 return true;
193 }
194
195 /// <summary>
196 /// Removes a xml element, right now it removes the first element it finds with matchs "Element" and "ParentElement" parameters
197 /// </summary>
198 /// <param name="operation"></param>
199 /// <returns>true or false depending if succeed or not</returns>
200 private bool removeOperation(string operation)
201 {
202 //@REMOVE File "example.xml" ParentElement "Particles" Element "Particle"
203
204 string FileParam = "", ParentElementParam = "", ElementParam = "";
205
206 //---------------------------------------------------Parse Operation command (start)
207 try
208 {
209 if (String.IsNullOrEmpty(forceFiles))
210 {
211 FileParam = getPatchParameter(operation, "File");
212 }
213 else
214 {
215 FileParam = forceFiles;
216 }
217
218 ParentElementParam = getPatchParameter(operation, "ParentElement"); //Get the ParentElement
219
220 ElementParam = getPatchParameter(operation, "Element"); //Get the Element
221 }
222 catch (Exception e)
223 {
224 Program.printAppError(Program.appErrors.PATCH_REMOVE_PROCESS_ERROR, "Error parsing removeOperation in Patch file.\n" + e.ToString());
225 return false;
226 }
227
228 if (String.IsNullOrEmpty(ElementParam))
229 {
230 return false;
231 }
232
233 //---------------------------------------------------Parse Operation command (end)
234
235 List<String> filesToProcess = new List<String>();
236 if (String.IsNullOrEmpty(FileParam))
237 {
238 filesToProcess = Util.getAllXmlFiles(); //no file specified, use all xml files found in same folder
239 }
240 else if (Util.containsWildcard(FileParam))
241 {
242 filesToProcess = Util.getXmlFilesWildcard(FileParam);
243 }
244 else
245 {
246 filesToProcess.Add(FileParam);
247 }
248
249 //---------------------------------------------------XML Remove (start)
250
251 foreach (String currFile in filesToProcess)
252 {
253
254 if (!this.globalNoBackups && !Util.ContainsIgnoreCase(operation, "NoBackups")) // only skip backup if specified via global parameter or in patch file
255 {
256 Util.backupFile(currFile);
257 }
258
259 XmlDocument xdoc = new XmlDocument();
260 xdoc.Load(currFile);
261
262 List<XmlNode> myElements = new List<XmlNode>();
263 Util.getAllSpecificElements(xdoc.DocumentElement, ref myElements, ElementParam, ParentElementParam); //Returns all after "Oni" element
264
265 if (myElements.Count == 0)
266 {
267 Program.printAppError(Program.appErrors.PATCH_ELEMENT_NOT_FOUND, "Error in removeOperation in Patch file: the element specified doesn't exist.");
268 return false;
269 }
270
271 myElements[0].ParentNode.RemoveChild(myElements[0]); // Removes the first occurrence which matches the "Element" and "ParentElement" given
272
273 xdoc.Save(currFile);
274
275 }
276 //---------------------------------------------------XML Remove (end)
277
278
279 return true;
280 }
281
282 /// <summary>
283 /// Executes a command for xmlTools
284 /// </summary>
285 /// <param name="command"></param>
286 /// <returns>true or false depending if succeed or not</returns>
287 private bool executeCommand(string command)
288 {
289 //---------------------------------------------------Parse Operation command (start)
290
291 command = command.Replace("@COMMAND ", ""); //get only the command to process
292
293 if (String.IsNullOrEmpty(command.Trim()))
294 {
295 Program.printAppError(Program.appErrors.PATCH_COMMAND_NOT_FOUND, "Error parsing commandOperation in Patch file: Command is empty.");
296 return false;
297 }
298
299 try
300 {
301 if (!String.IsNullOrEmpty(forceFiles))
302 {
303 string paramType = "";
304
305 // Filename already exists?
306 if (Util.ContainsIgnoreCase(command, "filename:"))
307 {
308 paramType = "filename:";
309 }
310 else if (Util.ContainsIgnoreCase(command, "filename="))
311 {
312 paramType = "filename=";
313 }
314 // Add the filename if it doesn't exists
315 else
316 {
317 command = command.Insert(command.Length, " -filename:" + this.forceFiles);
318 }
319
320 if (!String.IsNullOrEmpty(paramType))
321 {
322 int startIdx = command.IndexOf(paramType) + paramType.Length;
323 int endIdx = command.IndexOf(" ", startIdx); // it may end with space
324 if (endIdx == -1)
325 {
326 endIdx = command.IndexOf("\n", startIdx); // or with endline
327 if (endIdx == -1)
328 { // Filename parameters is the last one in the file (file ends with this parameter)
329 endIdx = command.Length - 1;
330 }
331 }
332 string currFilename = command.Substring(startIdx, endIdx - startIdx);
333 command = command.Replace(currFilename, this.forceFiles);
334 }
335
336 }
337
338 if (this.globalNoBackups && !Util.ContainsIgnoreCase(command, "nobackups")) // add noBackup flag if provided as global parameter
339 {
340 command = command.Insert(command.Length, " -nobackups");
341 }
342
343 Program.Main(Util.stringToArgsArray(command)); // use the current process is more efficient than start a new one
344 }
345 catch (Exception e)
346 {
347 Program.printAppError(Program.appErrors.PATCH_COMMAND_PROCESS_ERROR, "Error processing command in Patch file.\n" + e.ToString());
348 return false;
349 }
350
351 return true;
352 }
353
354 /// <summary>
355 /// Executes custom Javascript code over the xml file specified. Uses .NET JINT library.
356 /// </summary>
357 /// <param name="command"></param>
358 /// <returns></returns>
359 private bool executeCode(string operation, string jsCode)
360 {
361 string FileParam = "";
362
363 //---------------------------------------------------Parse Operation command (start)
364 try
365 {
366 if (String.IsNullOrEmpty(forceFiles))
367 {
368 FileParam = getPatchParameter(operation, "File");
369 }
370 else
371 {
372 FileParam = forceFiles;
373 }
374
375 }
376 catch (Exception e)
377 {
378 Program.printAppError(Program.appErrors.PATCH_CODE_PROCESS_ERROR, "Error parsing codeOperation in Patch file.\n" + e.ToString());
379 return false;
380 }
381
382 //---------------------------------------------------Parse Operation command (end)
383 List<String> filesToProcess = new List<String>();
384 if (String.IsNullOrEmpty(FileParam))
385 {
386 filesToProcess = Util.getAllXmlFiles(); //no file specified, use all xml files found in same folder
387 }
388 else if (Util.containsWildcard(FileParam))
389 {
390 filesToProcess = Util.getXmlFilesWildcard(FileParam);
391 }
392 else
393 {
394 filesToProcess.Add(FileParam);
395 }
396
397 //---------------------------------------------------JS Code Proccess (start)
398 foreach (String currXMLFile in filesToProcess)
399 {
400 if (!this.globalNoBackups && !Util.ContainsIgnoreCase(operation, "NoBackups")) // only skip backup if specified via global parameter or in patch file
401 {
402 Util.backupFile(currXMLFile);
403 }
404
405 string xmlFileContent = File.ReadAllText(currXMLFile);
406
407 // Initialize Jint Engine
408 if (jsEngine == null)
409 {
410 jsEngine = new IronJS.Hosting.CSharp.Context();
411
412 // Load XML libraries
413 jsEngine.Execute(xmlTools.Properties.Resources.tinyxmlsax);
414 jsEngine.Execute(xmlTools.Properties.Resources.tinyxmlw3cdom);
415 }
416
417 // Construct code to execute
418 StringBuilder sourceCode = new StringBuilder();
419
420 // give user the xml we needs to edit...
421 sourceCode.Append("var $xmlData='").Append(xmlFileContent.Replace(System.Environment.NewLine, " \\" + System.Environment.NewLine)).Append("';").AppendLine(); // replace is for multine string in javascript (http://stackoverflow.com/questions/805107/creating-multiline-strings-in-javascript)
422 // append the user js code...
423 sourceCode.Append(jsCode).AppendLine();
424 // return to .NET the new xml data
425 sourceCode.Append("$xmlData;");
426
427 try
428 {
429 xmlFileContent = jsEngine.Execute(sourceCode.ToString()).ToString();
430 }
431 catch (Exception e)
432 {
433 Program.printAppError(Program.appErrors.PATCH_CODE_PROCESS_ERROR, "Error parsing code in customCodeOperation in Patch file.\n" + e.ToString());
434 return false;
435 }
436
437 // Let's see if the returned result is valid xml...
438 try
439 {
440 XmlDocument xmlDoc = new XmlDocument();
441
442 xmlDoc.LoadXml(xmlFileContent);
443
444 xmlDoc.Save(currXMLFile); //saving the new xml with this method will auto ident it.
445
446 }
447 catch (Exception e)
448 {
449 Program.printAppError(Program.appErrors.PATCH_CODE_PARSE_XML_OUTPUT_ERROR, "Error parsing result xml to customCodeOperation in Patch file.\n" + e.ToString());
450 return false;
451 }
452 }
453 //---------------------------------------------------JS Code Proccess (end)
454
455 return true;
456 }
457
458 private string getPatchParameter(string line, string parameterName)
459 {
460 string result = "";
461 int startIdx = 0, endIdx = 0;
462
463 string temp = parameterName + " \"";
464
465 startIdx = line.IndexOf(temp);
466 if (startIdx != -1) //we have Parameter specified
467 {
468 startIdx += temp.Length;
469 endIdx = line.IndexOf("\"", startIdx);
470 result = line.Substring(startIdx, endIdx - startIdx); //Get the parameter value
471 }
472
473 return result;
474 }
475 }
476}
Note: See TracBrowser for help on using the repository browser.