using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text; using System.Xml; using IronJS; namespace xmlTools { /// /// This classes parses a .patch xml tools file and applies its content to the files which it specifies /// class XmlPatch { String fileName; String forceFiles = ""; bool globalNoBackups = false; IronJS.Hosting.CSharp.Context jsEngine = null; // initialize only when necessary public XmlPatch(String file, bool noBackups) { fileName = file; globalNoBackups = noBackups; } public XmlPatch(String file, String forceInFiles, bool noBackups) { fileName = file; forceFiles = forceInFiles; //We support apply the operation in diverse forced files (NameOfFile parameter will be ignored) globalNoBackups = noBackups; } /// /// Applies the patch file. Returns true if successful otherwise returns false. /// /// public bool startPatch() { string line; // Read the file and display it line by line. System.IO.StreamReader file = new System.IO.StreamReader(fileName); while ((line = file.ReadLine()) != null) //read while we don't reach the end of the file { if (line.StartsWith("@ADDTO ")) { string operation = line; string xmlToInject = ""; file.ReadLine(); //ignore start header while ((line = file.ReadLine()) != "") { xmlToInject += line + System.Environment.NewLine; //get all the xml that will be injected } if (!addOperation(operation, xmlToInject)) { Program.printAppError(Program.appErrors.PATCH_ADDTO_PROCESS_ERROR, "Error while performing adding operation in patch file. Aborting..."); return false; } } else if (line.StartsWith("@REMOVE ")) { if (!removeOperation(line)) { Program.printAppError(Program.appErrors.PATCH_REMOVE_PROCESS_ERROR, "Error while performing remove operation in patch file. Aborting..."); return false; } } else if (line.StartsWith("@COMMAND ")) { if (!executeCommand(line)) { Program.printAppError(Program.appErrors.PATCH_COMMAND_PROCESS_ERROR, "Error while performing command operation in patch file. Aborting..."); return false; } } else if (line.StartsWith("@CUSTOMCODE ")) { string operation = line; string jsCode = ""; file.ReadLine(); //ignore start header while ((line = file.ReadLine()) != "") { jsCode += line + System.Environment.NewLine; //get all the xml that will be injected } if (!executeCode(operation, jsCode)) { Program.printAppError(Program.appErrors.PATCH_CODE_PROCESS_ERROR, "Error while performing code operation in patch file. Aborting..."); return false; } } } file.Close(); return true; } /// /// Inserts xml in a desired Element. Returns true or false if it succeeds /// /// /// /// private bool addOperation(string operation, string xmlToInject) { //@ADDTO File "example.xml" ParentElement "Animation" Element "Lookup" string FileParam = "", ParentElementParam = "", ElementParam = ""; //---------------------------------------------------Parse Operation command (start) try { if (String.IsNullOrEmpty(forceFiles)) { FileParam = getPatchParameter(operation, "File"); } else { FileParam = forceFiles; } ParentElementParam = getPatchParameter(operation, "ParentElement"); //Get the ParentElement ElementParam = getPatchParameter(operation, "Element"); //Get the Element } catch (Exception e) { Program.printAppError(Program.appErrors.PATCH_ADDTO_ERROR_PARSING_XML, "Error parsing addOperation in Patch file.\n" + e.ToString()); return false; } if (String.IsNullOrEmpty(ElementParam)) { return false; } //---------------------------------------------------Parse Operation command (end) List filesToProcess = new List(); if (String.IsNullOrEmpty(FileParam)) { filesToProcess = Util.getAllXmlFiles(); //no file specified, use all xml files found in same folder } else if (Util.containsWildcard(FileParam)) { filesToProcess = Util.getXmlFilesWildcard(FileParam); } else { filesToProcess.Add(FileParam); } //---------------------------------------------------XML Injection (start) foreach (String currFile in filesToProcess) { if (!this.globalNoBackups && !Util.ContainsIgnoreCase(operation, "NoBackups")) // only skip backup if specified via global parameter or in patch file { Util.backupFile(currFile); } XmlDocument xdoc = new XmlDocument(); xdoc.Load(currFile); List myElements = new List(); Util.getAllSpecificElements(xdoc.DocumentElement, ref myElements, ElementParam, ParentElementParam); //Returns all after "Oni" element if (myElements.Count == 0) { Program.printAppError(Program.appErrors.PATCH_ELEMENT_NOT_FOUND, "Error in addOperation in Patch file: the element specified doesn't exist."); return false; } try { XmlNode newXml = xdoc.ImportNode(Util.stringToXmlNode(xmlToInject), true); //necessary to import node or ArgumentException will be thrown when appending myElements[myElements.Count - 1].AppendChild(newXml); // Append the code after last element xdoc.Save(currFile); } catch (XmlException e) { Program.printAppError(Program.appErrors.PATCH_ADDTO_ERROR_PARSING_XML, "Error parsing xml to addOperation in Patch file.\n" + e.ToString()); return false; } } //---------------------------------------------------XML Injection (end) return true; } /// /// Removes a xml element, right now it removes the first element it finds with matchs "Element" and "ParentElement" parameters /// /// /// true or false depending if succeed or not private bool removeOperation(string operation) { //@REMOVE File "example.xml" ParentElement "Particles" Element "Particle" string FileParam = "", ParentElementParam = "", ElementParam = ""; //---------------------------------------------------Parse Operation command (start) try { if (String.IsNullOrEmpty(forceFiles)) { FileParam = getPatchParameter(operation, "File"); } else { FileParam = forceFiles; } ParentElementParam = getPatchParameter(operation, "ParentElement"); //Get the ParentElement ElementParam = getPatchParameter(operation, "Element"); //Get the Element } catch (Exception e) { Program.printAppError(Program.appErrors.PATCH_REMOVE_PROCESS_ERROR, "Error parsing removeOperation in Patch file.\n" + e.ToString()); return false; } if (String.IsNullOrEmpty(ElementParam)) { return false; } //---------------------------------------------------Parse Operation command (end) List filesToProcess = new List(); if (String.IsNullOrEmpty(FileParam)) { filesToProcess = Util.getAllXmlFiles(); //no file specified, use all xml files found in same folder } else if (Util.containsWildcard(FileParam)) { filesToProcess = Util.getXmlFilesWildcard(FileParam); } else { filesToProcess.Add(FileParam); } //---------------------------------------------------XML Remove (start) foreach (String currFile in filesToProcess) { if (!this.globalNoBackups && !Util.ContainsIgnoreCase(operation, "NoBackups")) // only skip backup if specified via global parameter or in patch file { Util.backupFile(currFile); } XmlDocument xdoc = new XmlDocument(); xdoc.Load(currFile); List myElements = new List(); Util.getAllSpecificElements(xdoc.DocumentElement, ref myElements, ElementParam, ParentElementParam); //Returns all after "Oni" element if (myElements.Count == 0) { Program.printAppError(Program.appErrors.PATCH_ELEMENT_NOT_FOUND, "Error in removeOperation in Patch file: the element specified doesn't exist."); return false; } myElements[0].ParentNode.RemoveChild(myElements[0]); // Removes the first occurrence which matches the "Element" and "ParentElement" given xdoc.Save(currFile); } //---------------------------------------------------XML Remove (end) return true; } /// /// Executes a command for xmlTools /// /// /// true or false depending if succeed or not private bool executeCommand(string command) { //---------------------------------------------------Parse Operation command (start) command = command.Replace("@COMMAND ", ""); //get only the command to process if (String.IsNullOrEmpty(command.Trim())) { Program.printAppError(Program.appErrors.PATCH_COMMAND_NOT_FOUND, "Error parsing commandOperation in Patch file: Command is empty."); return false; } try { if (!String.IsNullOrEmpty(forceFiles)) { string paramType = ""; // Filename already exists? if (Util.ContainsIgnoreCase(command, "filename:")) { paramType = "filename:"; } else if (Util.ContainsIgnoreCase(command, "filename=")) { paramType = "filename="; } // Add the filename if it doesn't exists else { command = command.Insert(command.Length, " -filename:" + this.forceFiles); } if (!String.IsNullOrEmpty(paramType)) { int startIdx = command.IndexOf(paramType) + paramType.Length; int endIdx = command.IndexOf(" ", startIdx); // it may end with space if (endIdx == -1) { endIdx = command.IndexOf("\n", startIdx); // or with endline if (endIdx == -1) { // Filename parameters is the last one in the file (file ends with this parameter) endIdx = command.Length - 1; } } string currFilename = command.Substring(startIdx, endIdx - startIdx); command = command.Replace(currFilename, this.forceFiles); } } if (this.globalNoBackups && !Util.ContainsIgnoreCase(command, "nobackups")) // add noBackup flag if provided as global parameter { command = command.Insert(command.Length, " -nobackups"); } Program.Main(Util.stringToArgsArray(command)); // use the current process is more efficient than start a new one } catch (Exception e) { Program.printAppError(Program.appErrors.PATCH_COMMAND_PROCESS_ERROR, "Error processing command in Patch file.\n" + e.ToString()); return false; } return true; } /// /// Executes custom Javascript code over the xml file specified. Uses .NET JINT library. /// /// /// private bool executeCode(string operation, string jsCode) { string FileParam = ""; //---------------------------------------------------Parse Operation command (start) try { if (String.IsNullOrEmpty(forceFiles)) { FileParam = getPatchParameter(operation, "File"); } else { FileParam = forceFiles; } } catch (Exception e) { Program.printAppError(Program.appErrors.PATCH_CODE_PROCESS_ERROR, "Error parsing codeOperation in Patch file.\n" + e.ToString()); return false; } //---------------------------------------------------Parse Operation command (end) List filesToProcess = new List(); if (String.IsNullOrEmpty(FileParam)) { filesToProcess = Util.getAllXmlFiles(); //no file specified, use all xml files found in same folder } else if (Util.containsWildcard(FileParam)) { filesToProcess = Util.getXmlFilesWildcard(FileParam); } else { filesToProcess.Add(FileParam); } //---------------------------------------------------JS Code Proccess (start) foreach (String currXMLFile in filesToProcess) { if (!this.globalNoBackups && !Util.ContainsIgnoreCase(operation, "NoBackups")) // only skip backup if specified via global parameter or in patch file { Util.backupFile(currXMLFile); } string xmlFileContent = File.ReadAllText(currXMLFile); // Initialize Jint Engine if (jsEngine == null) { jsEngine = new IronJS.Hosting.CSharp.Context(); // Load XML libraries jsEngine.Execute(xmlTools.Properties.Resources.tinyxmlsax); jsEngine.Execute(xmlTools.Properties.Resources.tinyxmlw3cdom); } // Construct code to execute StringBuilder sourceCode = new StringBuilder(); // give user the xml we needs to edit... 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) // append the user js code... sourceCode.Append(jsCode).AppendLine(); // return to .NET the new xml data sourceCode.Append("$xmlData;"); try { xmlFileContent = jsEngine.Execute(sourceCode.ToString()).ToString(); } catch (Exception e) { Program.printAppError(Program.appErrors.PATCH_CODE_PROCESS_ERROR, "Error parsing code in customCodeOperation in Patch file.\n" + e.ToString()); return false; } // Let's see if the returned result is valid xml... try { XmlDocument xmlDoc = new XmlDocument(); xmlDoc.LoadXml(xmlFileContent); xmlDoc.Save(currXMLFile); //saving the new xml with this method will auto ident it. } catch (Exception e) { Program.printAppError(Program.appErrors.PATCH_CODE_PARSE_XML_OUTPUT_ERROR, "Error parsing result xml to customCodeOperation in Patch file.\n" + e.ToString()); return false; } } //---------------------------------------------------JS Code Proccess (end) return true; } private string getPatchParameter(string line, string parameterName) { string result = ""; int startIdx = 0, endIdx = 0; string temp = parameterName + " \""; startIdx = line.IndexOf(temp); if (startIdx != -1) //we have Parameter specified { startIdx += temp.Length; endIdx = line.IndexOf("\"", startIdx); result = line.Substring(startIdx, endIdx - startIdx); //Get the parameter value } return result; } } }