source: XmlTools2/trunk/xmlpatch.cpp@ 954

Last change on this file since 954 was 953, checked in by s10k, 11 years ago

XmlTools
What if we increase the performance of javascript by 2.5 times or more? (trying multithreading)

File size: 20.4 KB
RevLine 
[906]1#include "xmlpatch.h"
2
[923]3XmlPatch::XmlPatch(QString patchFilesWildcard, QString forceTargetFilesWildcard, bool noBackups, bool noVerbose)
[906]4{
5 this->patchFilesToProcess=UtilXmlTools::getAllPatchFilesByWildcard(patchFilesWildcard);
6 this->forceTargetFilesWildcard=forceTargetFilesWildcard;
7 this->backupsEnabled=!noBackups;
[923]8 this->verboseEnabled=!noVerbose;
[906]9
10 if(forceTargetFilesWildcard!=""){
[935]11 std::cout << "User forced patch in the target file(s): " << forceTargetFilesWildcard.toUtf8().constData() << std::endl;
[906]12 }
13
[910]14 if(this->patchFilesToProcess.isEmpty()){
[906]15 UtilXmlTools::displayErrorMessage("Loading patch files","No .patch or .oni-patch files were found for the wildcard: "+patchFilesWildcard);
16 }
17
18}
19
20void XmlPatch::readAndProcessPatchFile(){
21
22 // Process all PatchFiles
23 for(int i=0; i<this->patchFilesToProcess.size(); i++){
24
25 QFile inputFile(this->patchFilesToProcess[i]);
26
27 if (inputFile.open(QIODevice::ReadOnly))
28 {
29
30 QTextStream fileStream(&inputFile);
31
32 checkPatchVersion(this->patchFilesToProcess[i], fileStream);
33 checkAndProcessValidCommands(fileStream);
34
35 inputFile.close();
36 }
37 else{
38 UtilXmlTools::displayErrorMessage("Read patch file", "Error opening patch file: '" + this->patchFilesToProcess[i] + "'.\n" + inputFile.errorString());
39 }
40
41 }
42
43 UtilXmlTools::displaySuccessMessage(this->patchFilesToProcess.size(),"Patch File(s)");
44
45}
46
[920]47void XmlPatch::insertNodesOperation(const QString &xmlString, XmlFilter &filters, const QString &xPathExpression, const QString &filesWildcard){
[906]48
49 QStringList filesToProcess;
[920]50 QList<pugi::xml_node> nodesToInsertion;
[906]51 pugi::xml_document newNode;
52 pugi::xml_parse_result result;
53
[910]54 filesToProcess=UtilXmlTools::getAllXmlFilesByWildcard(filesWildcard);
55
56 if(filesToProcess.isEmpty()){
[920]57 UtilXmlTools::displayErrorMessage("@ADD_INSIDE_NODES","No XML files were found for the wildcard: "+filesWildcard);
[910]58 }
59
[935]60 result=newNode.load(xmlString.toUtf8().constData()); // load xml to insert
[906]61
62 if(result.status!=pugi::status_ok){
[920]63 UtilXmlTools::displayErrorMessage("@ADD_INSIDE_NODES", "The xml node to insert is invalid.\n" + Util::toQString(result.description()));
[906]64 }
65
66 // Process all XmlFiles
67 for(int i=0; i<filesToProcess.size(); i++){
68
[923]69 UtilXmlTools::loadXmlFile(filesToProcess[i],this->document,this->rootNode,this->backupsEnabled,this->verboseEnabled,"@ADD_INSIDE_NODES");
[906]70
[920]71 // Check how the element will be fetched via element name or xpath expression
72 if(xPathExpression.isEmpty()){
73 UtilXmlTools::getAllNamedElements(this->rootNode,nodesToInsertion,filters);
74 }
75 else{
76 UtilXmlTools::getAllXpathElements(xPathExpression,this->document,nodesToInsertion);
77 }
[906]78
[920]79 if(nodesToInsertion[0].type()==pugi::node_null){
[906]80
81 QString errMessage;
82
[920]83 if(xPathExpression.isEmpty()){
84 errMessage = "It wasn't found any node with a ElementName: '" + filters.getElementName() + "'";
85 if(filters.getParentElementName()!=""){
86 errMessage += " and a ParentElementName: '" + filters.getParentElementName() + "'";
87 }
88 if(filters.getAttributeName()!=""){
89 errMessage += " and an AttributeName: '" + filters.getAttributeName() + "' and an AttributeValue: '" + filters.getAttributeValue() + "'";
90 }
[906]91 }
[920]92 else{
93 errMessage = "It wasn't found any node with a XPathExpression: '" + xPathExpression + "'";
[906]94 }
95
[920]96 UtilXmlTools::displayErrorMessage("@ADD_INSIDE_NODES",errMessage);
[906]97 }
98
[920]99 for(int j=0; j<nodesToInsertion.size(); j++){
[923]100 for (pugi::xml_node currNodeToInsert = newNode.first_child(); currNodeToInsert; currNodeToInsert = currNodeToInsert.next_sibling())
101 {
[926]102 nodesToInsertion[j].append_copy(currNodeToInsert); // append the new node
[923]103 }
104
[920]105 }
[906]106
[923]107 UtilXmlTools::saveXmlFile(filesToProcess[i],this->document, "@ADD_INSIDE_NODES");
[906]108
[923]109 nodesToInsertion.clear();
[906]110 }
111
[920]112 UtilXmlTools::displaySuccessMessage(filesToProcess.size(),"@ADD_INSIDE_NODES");
[906]113}
114
[920]115void XmlPatch::removeNodesOperation(XmlFilter &filters, const QString &xPathExpression, const QString &filesWildcard){
[906]116
117 QStringList filesToProcess;
118
[920]119 QList<pugi::xml_node> nodesToDeletion;
[910]120
[906]121 filesToProcess=UtilXmlTools::getAllXmlFilesByWildcard(filesWildcard);
122
[910]123 if(filesToProcess.isEmpty()){
[920]124 UtilXmlTools::displayErrorMessage("@REMOVE_NODES","No XML files were found for the wildcard: "+filesWildcard);
[910]125 }
[906]126
127 // Process all XmlFiles
128 for(int i=0; i<filesToProcess.size(); i++){
129
[923]130 UtilXmlTools::loadXmlFile(filesToProcess[i],this->document,this->rootNode,this->backupsEnabled,this->verboseEnabled,"@REMOVE_NODES");
[906]131
[920]132 // Check how the element will be fetched via element name or xpath expression
133 if(xPathExpression.isEmpty()){
134 UtilXmlTools::getAllNamedElements(this->rootNode,nodesToDeletion,filters);
135 }
136 else{
137 UtilXmlTools::getAllXpathElements(xPathExpression,this->document,nodesToDeletion);
138 }
[906]139
[920]140 if(nodesToDeletion[0].type()==pugi::node_null){
141
[906]142 QString errMessage;
143
[920]144 if(xPathExpression.isEmpty()){
145 errMessage = "It wasn't found any node with a ElementName: '" + filters.getElementName() + "'";
146 if(filters.getParentElementName()!=""){
147 errMessage += " and a ParentElementName: '" + filters.getParentElementName() + "'";
148 }
149 if(filters.getAttributeName()!=""){
150 errMessage += " and an AttributeName: '" + filters.getAttributeName() + "' and an AttributeValue: '" + filters.getAttributeValue() + "'";
151 }
[906]152 }
[920]153 else{
154 errMessage = "It wasn't found any node with a XPathExpression: '" + xPathExpression + "'";
[906]155 }
156
[920]157 UtilXmlTools::displayErrorMessage("@REMOVE_NODES",errMessage);
[906]158 }
159
[920]160 // Delete all the specified nodes
161 for(int j=0; j<nodesToDeletion.size(); j++){
162 if(!nodesToDeletion[j].parent().remove_child(nodesToDeletion[j])){ // remove the node
[906]163
[920]164 QString errMessage;
165 if(xPathExpression.isEmpty()){
166 errMessage = "Couldn't remove the node with Element '" + filters.getElementName() + "'";
[906]167
[920]168 if(filters.getParentElementName()!=""){
169 errMessage += " and a ParentElement: '" + filters.getParentElementName() + "'";
170 }
171 }
172 else{
173 errMessage = "Couldn't remove the node with the XPathExpression '" + xPathExpression + "'";
174 }
[906]175
[920]176 UtilXmlTools::displayErrorMessage("@REMOVE_NODES",errMessage);
[906]177 }
178 }
179
[920]180 UtilXmlTools::saveXmlFile(filesToProcess[i],this->document, "@REMOVE_NODES");
[923]181
182 nodesToDeletion.clear();
[906]183 }
184
[920]185 UtilXmlTools::displaySuccessMessage(filesToProcess.size(), "@REMOVE_NODES");
[906]186}
187
188void XmlPatch::executeCommandOperation(const QString &commandString){
189
190 // Avoid infinite fork loops
[926]191 if(commandString.contains("-p ") || commandString.contains("--patch-files ")){
[906]192 UtilXmlTools::displayErrorMessage("@COMMAND","Use of --patch-files option is not allowed inside a patch file");
193 }
194
[926]195 // Reserved to AEI
196 if(commandString.contains("--aei-patch-files-list ")){
197 UtilXmlTools::displayErrorMessage("@COMMAND","Use of --aei-patch-files-list option is not allowed inside a patch file");
[906]198 }
199
[920]200 std::cout << "@COMMAND patch operation output:\n"
[953]201 << "########################################################################"
202 << std::endl;
[906]203
[926]204 OptionsParser myParser(Util::QStringToArgsArray(commandString));
205 myParser.parse();
206
207 std::cout
[953]208 << "########################################################################"
209 << std::endl;
[926]210
[906]211 UtilXmlTools::displaySuccessMessage(1,"@COMMAND");
212}
213
[947]214QScriptValue echo(QScriptContext *context, QScriptEngine *engine)
215{
216 std::cout << context->argument(0).toString().toUtf8().constData() << std::endl;
217
218 return "";
219}
220
[906]221void XmlPatch::executeCustomCommandOperation(const QString &jsString, const QString &filesWildcard){
222
[953]223 QString rexmlString, jsxmlString;
224 QStringList filesToProcess=UtilXmlTools::getAllXmlFilesByWildcard(filesWildcard);
[906]225
[953]226 if(filesToProcess.isEmpty()){
227 UtilXmlTools::displayErrorMessage("@CUSTOM_CODE","No XML files were found for the wildcard: "+filesWildcard);
228 }
[906]229
230 QFile rexmlfile(":/resources/libs/rexml.js");
231 QFile jsxmlfile(":/resources/libs/jsxml.js");
232
233 rexmlfile.open(QFile::ReadOnly | QFile::Text);
234 jsxmlfile.open(QFile::ReadOnly | QFile::Text);
235
236 rexmlString=QTextStream(&rexmlfile).readAll();
237 jsxmlString=QTextStream(&jsxmlfile).readAll();
238
239 // Process all XmlFiles
[953]240#pragma omp parallel for
[906]241 for(int i=0; i<filesToProcess.size(); i++){
242
[953]243 QString currXmlFileString;
244
245#ifdef _USE_OLD_JS_ENGINE
246 QScriptEngine engine;
247 QScriptValue engineResult; // variable to check for js_errors
248#else
249 QJSEngine engine;
250 QJSValue engineResult; // variable to check for js_errors
251#endif
252
253 // Add echo function so user can debug the code
254 QScriptValue echoFunction = engine.newFunction(echo);
255 engine.globalObject().setProperty("echo", echoFunction);
256
257 engine.evaluate(rexmlString); // load js libraries
258 engine.evaluate(jsxmlString);
259
[906]260 if(this->backupsEnabled){
[923]261 UtilXmlTools::backupFile(filesToProcess[i], this->verboseEnabled);
[906]262 }
263
264 QFile currXmlFile(filesToProcess[i]);
265
266 if(!currXmlFile.open(QFile::ReadOnly | QFile::Text)){
267 UtilXmlTools::displayErrorMessage("@CUSTOM_CODE","Error loading '" + filesToProcess[i] + "' file for read operation.");
268 }
269
270 currXmlFileString=QTextStream(&currXmlFile).readAll();
271
272 currXmlFile.close(); // close reading
273
274 engine.globalObject().setProperty("$xmlData",currXmlFileString);
275
[947]276 // main needs to be called so the user code is evaluated
277 engineResult=engine.evaluate("main(); function main() {"+jsString+"}"); // main funtion allows to use return to exit prematurely from user code
[906]278
279#ifdef _USE_OLD_JS_ENGINE
280 if (engine.hasUncaughtException()) {
281 displayJsException(engine,engineResult);
282 }
283#else
284 checkForJsException(engineResult);
285#endif
286
287 if(!currXmlFile.open(QFile::WriteOnly | QFile::Text | QIODevice::Truncate)){
288 UtilXmlTools::displayErrorMessage("@CUSTOM_CODE","Error loading '" + filesToProcess[i] + "' file for @CUSTOM_CODE write operation.");
289 }
290
291 engineResult=engine.globalObject().property("$xmlData");
292
293#ifdef _USE_OLD_JS_ENGINE
294 if (engine.hasUncaughtException()) {
295 displayJsException(engine,engineResult);
296 }
297#else
298 checkForJsException(engineResult);
299#endif
300
301 QTextStream(&currXmlFile) << engineResult.toString(); // retreive the modified xml by javascript and save it to the file
302 }
303
304 UtilXmlTools::displaySuccessMessage(filesToProcess.size(), "@CUSTOM_CODE");
305}
306
307void XmlPatch::checkPatchVersion(const QString &file, QTextStream &fileStream){
308
309 QString line, patchVersion="";
310
311 // First get the patch version and check it validity
312 while ( !fileStream.atEnd() ){
313 line = fileStream.readLine();
314
315 if(line.startsWith('#')){ // Ignore comments
316 continue;
317 }
318 else if(line.startsWith("@XML_TOOLS")){
319
320 patchVersion=getPatchParameterValue(line,"Version");
321
322 if(!patchVersion.startsWith("2.0")){
323 QString errMessage;
324
325 errMessage = "The current patch version is incompatible with this XmlTools version:\n";
326 errMessage += "Patch file name: '" + file + "'\n";
327 errMessage += "XmlTools version: " + GlobalVars::AppVersion + "\n" + "CurrPatch version: " + patchVersion + "";
328 UtilXmlTools::displayErrorMessage("@XML_TOOLS",errMessage);
329 }
330 break; // We have got what we wanted
331 }
332 }
333
334 if(patchVersion==""){
335 UtilXmlTools::displayErrorMessage("@XML_TOOLS","Patch version not found.");
336 }
337
338}
339
340void XmlPatch::checkAndProcessValidCommands(QTextStream &fileStream){
341
[910]342 QString line, filesWildcard;
[906]343 QString xmlToInsert, command, jsCode;
[920]344 QString xPathExpression;
[906]345 XmlFilter filters;
346
347 // Process the rest of the commands in patch file
348 while ( !fileStream.atEnd() ){
349 line = fileStream.readLine();
350
351 if(line.startsWith('#')){ // Ignore comments
352 continue;
353 }
[920]354 else if(line.startsWith("@ADD_INSIDE_NODES")){
355 xPathExpression=getPatchParameterValue(line,"XPathExpression");
[906]356 filters.setElementName(getPatchParameterValue(line,"ElementName"));
357 filters.setParentElementName(getPatchParameterValue(line,"ParentElementName"));
358 filters.setAttributeName(getPatchParameterValue(line,"AttributeName"));
359 filters.setAttributeValue(getPatchParameterValue(line,"AttributeValue"));
[910]360
361 if(this->forceTargetFilesWildcard!=""){
362 filesWildcard=this->forceTargetFilesWildcard;
[906]363 }
364 else{
[910]365 filesWildcard=getPatchParameterValue(line,"Files");
[906]366 }
367
[920]368 // Check options
369 if(xPathExpression.isEmpty() && filters.getElementName().isEmpty()){
370 UtilXmlTools::displayErrorMessage("@ADD_INSIDE_NODES","ElementName option or XPathExpression option is required.");
371 }
372 else if(!xPathExpression.isEmpty() && !filters.getElementName().isEmpty()){
373 UtilXmlTools::displayErrorMessage("@ADD_INSIDE_NODES","ElementName option and XPathExpression options cannot be used simultaneously.");
374 }
[906]375 if(filters.getAttributeName()!="" && filters.getAttributeValue()==""){
[920]376 UtilXmlTools::displayErrorMessage("@ADD_INSIDE_NODES","AttributeValue option is required if using AttributeName option.");
[906]377 }
378
379 if(filters.getAttributeValue()!="" && filters.getAttributeName()==""){
[920]380 UtilXmlTools::displayErrorMessage("@ADD_INSIDE_NODES","AttributeName option is required if using AttributeValue option.");
[906]381 }
382
383 while ( !fileStream.atEnd() && !line.startsWith("</xml>")){
384 line = fileStream.readLine();
385
386 if(!line.startsWith("<xml>") && !line.startsWith("</xml>")){
387 xmlToInsert += line + "\n";
388 }
389 }
390
[920]391 insertNodesOperation(xmlToInsert,filters,xPathExpression,filesWildcard);
[906]392
[920]393 xmlToInsert.clear();
394 filters.clear();
395 xPathExpression.clear();
396 filesWildcard.clear();
[906]397 }
[920]398 else if(line.startsWith("@REMOVE_NODES")){
[906]399
[920]400 xPathExpression=getPatchParameterValue(line,"XPathExpression");
[906]401 filters.setElementName(getPatchParameterValue(line,"ElementName"));
402 filters.setParentElementName(getPatchParameterValue(line,"ParentElementName"));
403 filters.setAttributeName(getPatchParameterValue(line,"AttributeName"));
404 filters.setAttributeValue(getPatchParameterValue(line,"AttributeValue"));
[910]405
406 if(this->forceTargetFilesWildcard!=""){
407 filesWildcard=this->forceTargetFilesWildcard;
[906]408 }
409 else{
[910]410 filesWildcard=getPatchParameterValue(line,"Files");
[906]411 }
412
[920]413 // Check options
414 if(xPathExpression.isEmpty() && filters.getElementName().isEmpty()){
415 UtilXmlTools::displayErrorMessage("@REMOVE_NODES","ElementName option or XPathExpression option is required.");
416 }
417 else if(!xPathExpression.isEmpty() && !filters.getElementName().isEmpty()){
418 UtilXmlTools::displayErrorMessage("@REMOVE_NODES","ElementName option and XPathExpression options cannot be used simultaneously.");
419 }
420
[906]421 if(filters.getAttributeName()!="" && filters.getAttributeValue()==""){
[920]422 UtilXmlTools::displayErrorMessage("@REMOVE_NODES","AttributeValue option is required if using AttributeName option.");
[906]423 }
424
425 if(filters.getAttributeValue()!="" && filters.getAttributeName()==""){
[920]426 UtilXmlTools::displayErrorMessage("@REMOVE_NODES","AttributeName option is required if using AttributeValue option.");
[906]427 }
428
[920]429 removeNodesOperation(filters,xPathExpression,filesWildcard);
[906]430
[920]431 filters.clear();
432 xPathExpression.clear();
433 filesWildcard.clear();
[906]434 }
435 else if(line.startsWith("@COMMAND")){
436
[926]437 command=GlobalVars::AppExecutable;
[906]438
[926]439 // Append files if forced to
440 if(!this->forceTargetFilesWildcard.isEmpty()){
441 command+=" --files '"+this->forceTargetFilesWildcard+"' ";
442 }
443
444 command+=" "+getPatchParameterValue(line,"Options");
445
446 // Add --no-backups and --no-verbose if patch was called with that arguments
447 if(!this->backupsEnabled){
448 command.append(" --no-backups");
449 }
450
451 if(!this->verboseEnabled){
452 command.append(" --no-verbose");
453 }
454
[920]455 command.replace("'","\""); //replace apostrophe by quotes, to avoid problems
456
[906]457 executeCommandOperation(command);
[920]458
459 command.clear();
[926]460 filesWildcard.clear();
[906]461 }
462 else if(line.startsWith("@CUSTOM_CODE")){
463
[910]464 if(this->forceTargetFilesWildcard!=""){
465 filesWildcard=this->forceTargetFilesWildcard;
[906]466 }
467 else{
[910]468 filesWildcard=getPatchParameterValue(line,"Files");
[906]469 }
470
471 while ( !fileStream.atEnd() && !line.startsWith("</code>")){
472
473 line = fileStream.readLine();
474
475 if(!line.startsWith("<code>") && !line.startsWith("</code>")){
476 jsCode += line + "\n";
477 }
478 }
479
480 executeCustomCommandOperation(jsCode,filesWildcard);
[920]481
482 jsCode.clear();
483 filesWildcard.clear();
[906]484 }
485 }
486}
487
488QString XmlPatch::getPatchParameterValue(const QString& line, QString parameter){
489
490 int startValueIdx, endValueIdx;
491
[920]492 parameter=" "+parameter+" "; // Parameters include a space before and after it's value.
[906]493
494 if(!line.contains(parameter)){
[920]495 if(parameter==" ParentElementName " || parameter==" AttributeName " || parameter==" AttributeValue "
496 || parameter==" ElementName " || parameter==" XPathExpression "){
[906]497 return ""; // not mandatory
498 }
499 parameter.remove(" "); // just remove the space added so it doesn't look weird when outputted to the user
500
501 UtilXmlTools::displayErrorMessage("Read patch file parameter","Couldn't retreive '" + parameter + "' parameter.");
502 }
503
504 startValueIdx=line.indexOf(parameter); // get the position where parameter is defined
505 endValueIdx=line.indexOf("\"",startValueIdx)+1; // get the position where parameter value begins (+1 to ignore mandotory quote)
506
507 startValueIdx=endValueIdx;
508 endValueIdx=line.indexOf("\"",startValueIdx); // get the last mandatory quote of the value
509
510 return line.mid(startValueIdx,endValueIdx-startValueIdx); // return the value of the parameter (is in the middle of the mandatory quotes)
511}
512
513#ifdef _USE_OLD_JS_ENGINE
514void XmlPatch::displayJsException(QScriptEngine &engine, QScriptValue &engineResult){
515 if (engine.hasUncaughtException()) {
516 UtilXmlTools::displayErrorMessage("@CUSTOM_CODE","Uncaught js exception (user code) at line " +QString::number(engine.uncaughtExceptionLineNumber()) + ":\n" + engineResult.toString());
517 }
518}
519#else
520void XmlPatch::checkForJsException(QJSValue &engineResult){
521 if (engineResult.isError()) {
522 UtilXmlTools::displayErrorMessage("@CUSTOM_CODE","Uncaught js exception (user code):\n" + engineResult.toString());
523 }
524}
525#endif
Note: See TracBrowser for help on using the repository browser.