source: XmlTools2/trunk/xmlpatch.cpp@ 953

Last change on this file since 953 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
Line 
1#include "xmlpatch.h"
2
3XmlPatch::XmlPatch(QString patchFilesWildcard, QString forceTargetFilesWildcard, bool noBackups, bool noVerbose)
4{
5 this->patchFilesToProcess=UtilXmlTools::getAllPatchFilesByWildcard(patchFilesWildcard);
6 this->forceTargetFilesWildcard=forceTargetFilesWildcard;
7 this->backupsEnabled=!noBackups;
8 this->verboseEnabled=!noVerbose;
9
10 if(forceTargetFilesWildcard!=""){
11 std::cout << "User forced patch in the target file(s): " << forceTargetFilesWildcard.toUtf8().constData() << std::endl;
12 }
13
14 if(this->patchFilesToProcess.isEmpty()){
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
47void XmlPatch::insertNodesOperation(const QString &xmlString, XmlFilter &filters, const QString &xPathExpression, const QString &filesWildcard){
48
49 QStringList filesToProcess;
50 QList<pugi::xml_node> nodesToInsertion;
51 pugi::xml_document newNode;
52 pugi::xml_parse_result result;
53
54 filesToProcess=UtilXmlTools::getAllXmlFilesByWildcard(filesWildcard);
55
56 if(filesToProcess.isEmpty()){
57 UtilXmlTools::displayErrorMessage("@ADD_INSIDE_NODES","No XML files were found for the wildcard: "+filesWildcard);
58 }
59
60 result=newNode.load(xmlString.toUtf8().constData()); // load xml to insert
61
62 if(result.status!=pugi::status_ok){
63 UtilXmlTools::displayErrorMessage("@ADD_INSIDE_NODES", "The xml node to insert is invalid.\n" + Util::toQString(result.description()));
64 }
65
66 // Process all XmlFiles
67 for(int i=0; i<filesToProcess.size(); i++){
68
69 UtilXmlTools::loadXmlFile(filesToProcess[i],this->document,this->rootNode,this->backupsEnabled,this->verboseEnabled,"@ADD_INSIDE_NODES");
70
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 }
78
79 if(nodesToInsertion[0].type()==pugi::node_null){
80
81 QString errMessage;
82
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 }
91 }
92 else{
93 errMessage = "It wasn't found any node with a XPathExpression: '" + xPathExpression + "'";
94 }
95
96 UtilXmlTools::displayErrorMessage("@ADD_INSIDE_NODES",errMessage);
97 }
98
99 for(int j=0; j<nodesToInsertion.size(); j++){
100 for (pugi::xml_node currNodeToInsert = newNode.first_child(); currNodeToInsert; currNodeToInsert = currNodeToInsert.next_sibling())
101 {
102 nodesToInsertion[j].append_copy(currNodeToInsert); // append the new node
103 }
104
105 }
106
107 UtilXmlTools::saveXmlFile(filesToProcess[i],this->document, "@ADD_INSIDE_NODES");
108
109 nodesToInsertion.clear();
110 }
111
112 UtilXmlTools::displaySuccessMessage(filesToProcess.size(),"@ADD_INSIDE_NODES");
113}
114
115void XmlPatch::removeNodesOperation(XmlFilter &filters, const QString &xPathExpression, const QString &filesWildcard){
116
117 QStringList filesToProcess;
118
119 QList<pugi::xml_node> nodesToDeletion;
120
121 filesToProcess=UtilXmlTools::getAllXmlFilesByWildcard(filesWildcard);
122
123 if(filesToProcess.isEmpty()){
124 UtilXmlTools::displayErrorMessage("@REMOVE_NODES","No XML files were found for the wildcard: "+filesWildcard);
125 }
126
127 // Process all XmlFiles
128 for(int i=0; i<filesToProcess.size(); i++){
129
130 UtilXmlTools::loadXmlFile(filesToProcess[i],this->document,this->rootNode,this->backupsEnabled,this->verboseEnabled,"@REMOVE_NODES");
131
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 }
139
140 if(nodesToDeletion[0].type()==pugi::node_null){
141
142 QString errMessage;
143
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 }
152 }
153 else{
154 errMessage = "It wasn't found any node with a XPathExpression: '" + xPathExpression + "'";
155 }
156
157 UtilXmlTools::displayErrorMessage("@REMOVE_NODES",errMessage);
158 }
159
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
163
164 QString errMessage;
165 if(xPathExpression.isEmpty()){
166 errMessage = "Couldn't remove the node with Element '" + filters.getElementName() + "'";
167
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 }
175
176 UtilXmlTools::displayErrorMessage("@REMOVE_NODES",errMessage);
177 }
178 }
179
180 UtilXmlTools::saveXmlFile(filesToProcess[i],this->document, "@REMOVE_NODES");
181
182 nodesToDeletion.clear();
183 }
184
185 UtilXmlTools::displaySuccessMessage(filesToProcess.size(), "@REMOVE_NODES");
186}
187
188void XmlPatch::executeCommandOperation(const QString &commandString){
189
190 // Avoid infinite fork loops
191 if(commandString.contains("-p ") || commandString.contains("--patch-files ")){
192 UtilXmlTools::displayErrorMessage("@COMMAND","Use of --patch-files option is not allowed inside a patch file");
193 }
194
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");
198 }
199
200 std::cout << "@COMMAND patch operation output:\n"
201 << "########################################################################"
202 << std::endl;
203
204 OptionsParser myParser(Util::QStringToArgsArray(commandString));
205 myParser.parse();
206
207 std::cout
208 << "########################################################################"
209 << std::endl;
210
211 UtilXmlTools::displaySuccessMessage(1,"@COMMAND");
212}
213
214QScriptValue echo(QScriptContext *context, QScriptEngine *engine)
215{
216 std::cout << context->argument(0).toString().toUtf8().constData() << std::endl;
217
218 return "";
219}
220
221void XmlPatch::executeCustomCommandOperation(const QString &jsString, const QString &filesWildcard){
222
223 QString rexmlString, jsxmlString;
224 QStringList filesToProcess=UtilXmlTools::getAllXmlFilesByWildcard(filesWildcard);
225
226 if(filesToProcess.isEmpty()){
227 UtilXmlTools::displayErrorMessage("@CUSTOM_CODE","No XML files were found for the wildcard: "+filesWildcard);
228 }
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
240#pragma omp parallel for
241 for(int i=0; i<filesToProcess.size(); i++){
242
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
260 if(this->backupsEnabled){
261 UtilXmlTools::backupFile(filesToProcess[i], this->verboseEnabled);
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
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
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
342 QString line, filesWildcard;
343 QString xmlToInsert, command, jsCode;
344 QString xPathExpression;
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 }
354 else if(line.startsWith("@ADD_INSIDE_NODES")){
355 xPathExpression=getPatchParameterValue(line,"XPathExpression");
356 filters.setElementName(getPatchParameterValue(line,"ElementName"));
357 filters.setParentElementName(getPatchParameterValue(line,"ParentElementName"));
358 filters.setAttributeName(getPatchParameterValue(line,"AttributeName"));
359 filters.setAttributeValue(getPatchParameterValue(line,"AttributeValue"));
360
361 if(this->forceTargetFilesWildcard!=""){
362 filesWildcard=this->forceTargetFilesWildcard;
363 }
364 else{
365 filesWildcard=getPatchParameterValue(line,"Files");
366 }
367
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 }
375 if(filters.getAttributeName()!="" && filters.getAttributeValue()==""){
376 UtilXmlTools::displayErrorMessage("@ADD_INSIDE_NODES","AttributeValue option is required if using AttributeName option.");
377 }
378
379 if(filters.getAttributeValue()!="" && filters.getAttributeName()==""){
380 UtilXmlTools::displayErrorMessage("@ADD_INSIDE_NODES","AttributeName option is required if using AttributeValue option.");
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
391 insertNodesOperation(xmlToInsert,filters,xPathExpression,filesWildcard);
392
393 xmlToInsert.clear();
394 filters.clear();
395 xPathExpression.clear();
396 filesWildcard.clear();
397 }
398 else if(line.startsWith("@REMOVE_NODES")){
399
400 xPathExpression=getPatchParameterValue(line,"XPathExpression");
401 filters.setElementName(getPatchParameterValue(line,"ElementName"));
402 filters.setParentElementName(getPatchParameterValue(line,"ParentElementName"));
403 filters.setAttributeName(getPatchParameterValue(line,"AttributeName"));
404 filters.setAttributeValue(getPatchParameterValue(line,"AttributeValue"));
405
406 if(this->forceTargetFilesWildcard!=""){
407 filesWildcard=this->forceTargetFilesWildcard;
408 }
409 else{
410 filesWildcard=getPatchParameterValue(line,"Files");
411 }
412
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
421 if(filters.getAttributeName()!="" && filters.getAttributeValue()==""){
422 UtilXmlTools::displayErrorMessage("@REMOVE_NODES","AttributeValue option is required if using AttributeName option.");
423 }
424
425 if(filters.getAttributeValue()!="" && filters.getAttributeName()==""){
426 UtilXmlTools::displayErrorMessage("@REMOVE_NODES","AttributeName option is required if using AttributeValue option.");
427 }
428
429 removeNodesOperation(filters,xPathExpression,filesWildcard);
430
431 filters.clear();
432 xPathExpression.clear();
433 filesWildcard.clear();
434 }
435 else if(line.startsWith("@COMMAND")){
436
437 command=GlobalVars::AppExecutable;
438
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
455 command.replace("'","\""); //replace apostrophe by quotes, to avoid problems
456
457 executeCommandOperation(command);
458
459 command.clear();
460 filesWildcard.clear();
461 }
462 else if(line.startsWith("@CUSTOM_CODE")){
463
464 if(this->forceTargetFilesWildcard!=""){
465 filesWildcard=this->forceTargetFilesWildcard;
466 }
467 else{
468 filesWildcard=getPatchParameterValue(line,"Files");
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);
481
482 jsCode.clear();
483 filesWildcard.clear();
484 }
485 }
486}
487
488QString XmlPatch::getPatchParameterValue(const QString& line, QString parameter){
489
490 int startValueIdx, endValueIdx;
491
492 parameter=" "+parameter+" "; // Parameters include a space before and after it's value.
493
494 if(!line.contains(parameter)){
495 if(parameter==" ParentElementName " || parameter==" AttributeName " || parameter==" AttributeValue "
496 || parameter==" ElementName " || parameter==" XPathExpression "){
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.