source: XmlTools2/trunk/xmlpatch.cpp @ 958

Last change on this file since 958 was 958, checked in by s10k, 5 years ago

XmlTools
More updates.

File size: 20.6 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 = "No node was found with an 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 = "No node was found with an 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 = "No node was found with an 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 = "No node was found with an 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        QScriptEngine engine;
246        QScriptValue engineResult; // variable to check for js_errors
247        double elapsed_secs; // elapsed seconds that a user script took
248        clock_t begin; // seconds that a script started
249
250        // Add echo function so user can debug the code
251        QScriptValue echoFunction = engine.newFunction(echo);
252        engine.globalObject().setProperty("echo", echoFunction);
253
254        engine.evaluate(rexmlString); // load js libraries
255        engine.evaluate(jsxmlString);
256
257        if(this->backupsEnabled){
258            UtilXmlTools::backupFile(filesToProcess[i], this->verboseEnabled);
259        }
260
261        QFile currXmlFile(filesToProcess[i]);
262
263        if(!currXmlFile.open(QFile::ReadOnly | QFile::Text)){
264            UtilXmlTools::displayErrorMessage("@CUSTOM_CODE","Error loading '" + filesToProcess[i] + "' file for read operation.");
265        }
266
267        currXmlFileString=QTextStream(&currXmlFile).readAll();
268
269        currXmlFile.close(); // close reading
270
271        engine.globalObject().setProperty("$xmlData",currXmlFileString);
272
273        if(this->verboseEnabled){
274            begin = clock();
275        }
276
277        // main needs to be called so the user code is evaluated
278        // alternatively you can do: myFunc=engine.evaluate('(function main(){})'); myFunc.call();
279        // Note the () around the function
280        engineResult=engine.evaluate("main(); function main() {"+jsString+"}"); // main funtion allows to use return to exit prematurely from user code
281
282        if(this->verboseEnabled){
283            elapsed_secs = double(clock() - begin) / CLOCKS_PER_SEC;
284
285            // Warn the user if the script took much time
286            if(elapsed_secs>SLOW_SCRIPT_TIME){
287                std::cout << "Warning: Slow javascript code detected.\n" <<
288                             "Warning: Script execution seconds: " << elapsed_secs
289                          << std::endl;
290            }
291        }
292
293        if (engine.hasUncaughtException()) {
294            displayJsException(engine,engineResult);
295        }
296
297        if(!currXmlFile.open(QFile::WriteOnly | QFile::Text | QIODevice::Truncate)){
298            UtilXmlTools::displayErrorMessage("@CUSTOM_CODE","Error loading '" + filesToProcess[i] + "' file for @CUSTOM_CODE write operation.");
299        }
300
301        engineResult=engine.globalObject().property("$xmlData");
302
303        if (engine.hasUncaughtException()) {
304            displayJsException(engine,engineResult);
305        }
306
307        QTextStream(&currXmlFile) << engineResult.toString(); // retreive the modified xml by javascript and save it to the file
308    }
309
310    UtilXmlTools::displaySuccessMessage(filesToProcess.size(), "@CUSTOM_CODE");
311}
312
313void XmlPatch::checkPatchVersion(const QString &file, QTextStream &fileStream){
314
315    QString line, patchVersion="";
316
317    // First get the patch version and check it validity
318    while ( !fileStream.atEnd() ){
319        line = fileStream.readLine();
320
321        if(line.startsWith('#')){ // Ignore comments
322            continue;
323        }
324        else if(line.startsWith("@XML_TOOLS")){
325
326            patchVersion=getPatchParameterValue(line,"Version");
327
328            if(!patchVersion.startsWith("2.0")){
329                QString errMessage;
330
331                errMessage = "The current patch version is incompatible with this XmlTools version:\n";
332                errMessage += "Patch file name: '" + file + "'\n";
333                errMessage += "XmlTools version:  " + GlobalVars::AppVersion + "\n" + "CurrPatch version: " + patchVersion + "";
334                UtilXmlTools::displayErrorMessage("@XML_TOOLS",errMessage);
335            }
336            break; // We have got what we wanted
337        }
338    }
339
340    if(patchVersion==""){
341        UtilXmlTools::displayErrorMessage("@XML_TOOLS","Patch version not found.");
342    }
343
344}
345
346void XmlPatch::checkAndProcessValidCommands(QTextStream &fileStream){
347
348    QString line, filesWildcard;
349    QString xmlToInsert, command, jsCode;
350    QString xPathExpression;
351    XmlFilter filters;
352
353    // Process the rest of the commands in patch file
354    while ( !fileStream.atEnd() ){
355        line = fileStream.readLine();
356
357        if(line.startsWith('#')){ // Ignore comments
358            continue;
359        }
360        else if(line.startsWith("@ADD_INSIDE_NODES")){
361            xPathExpression=getPatchParameterValue(line,"XPathExpression");
362            filters.setElementName(getPatchParameterValue(line,"ElementName"));
363            filters.setParentElementName(getPatchParameterValue(line,"ParentElementName"));
364            filters.setAttributeName(getPatchParameterValue(line,"AttributeName"));
365            filters.setAttributeValue(getPatchParameterValue(line,"AttributeValue"));
366
367            if(this->forceTargetFilesWildcard!=""){
368                filesWildcard=this->forceTargetFilesWildcard;
369            }
370            else{
371                filesWildcard=getPatchParameterValue(line,"Files");
372            }
373
374            // Check options
375            if(xPathExpression.isEmpty() && filters.getElementName().isEmpty()){
376                UtilXmlTools::displayErrorMessage("@ADD_INSIDE_NODES","ElementName option or XPathExpression option is required.");
377            }
378            else if(!xPathExpression.isEmpty() && !filters.getElementName().isEmpty()){
379                UtilXmlTools::displayErrorMessage("@ADD_INSIDE_NODES","ElementName option and XPathExpression option cannot be used simultaneously.");
380            }
381            if(filters.getAttributeName()!="" && filters.getAttributeValue()==""){
382                UtilXmlTools::displayErrorMessage("@ADD_INSIDE_NODES","AttributeValue option is required if using AttributeName option.");
383            }
384
385            if(filters.getAttributeValue()!="" && filters.getAttributeName()==""){
386                UtilXmlTools::displayErrorMessage("@ADD_INSIDE_NODES","AttributeName option is required if using AttributeValue option.");
387            }
388
389            while ( !fileStream.atEnd() && !line.startsWith("</xml>")){
390                line = fileStream.readLine();
391
392                if(!line.startsWith("<xml>") && !line.startsWith("</xml>")){
393                    xmlToInsert += line + "\n";
394                }
395            }
396
397            insertNodesOperation(xmlToInsert,filters,xPathExpression,filesWildcard);
398
399            xmlToInsert.clear();
400            filters.clear();
401            xPathExpression.clear();
402            filesWildcard.clear();
403        }
404        else if(line.startsWith("@REMOVE_NODES")){
405
406            xPathExpression=getPatchParameterValue(line,"XPathExpression");
407            filters.setElementName(getPatchParameterValue(line,"ElementName"));
408            filters.setParentElementName(getPatchParameterValue(line,"ParentElementName"));
409            filters.setAttributeName(getPatchParameterValue(line,"AttributeName"));
410            filters.setAttributeValue(getPatchParameterValue(line,"AttributeValue"));
411
412            if(this->forceTargetFilesWildcard!=""){
413                filesWildcard=this->forceTargetFilesWildcard;
414            }
415            else{
416                filesWildcard=getPatchParameterValue(line,"Files");
417            }
418
419            // Check options
420            if(xPathExpression.isEmpty() && filters.getElementName().isEmpty()){
421                UtilXmlTools::displayErrorMessage("@REMOVE_NODES","ElementName option or XPathExpression option is required.");
422            }
423            else if(!xPathExpression.isEmpty() && !filters.getElementName().isEmpty()){
424                UtilXmlTools::displayErrorMessage("@REMOVE_NODES","ElementName option and XPathExpression option cannot be used simultaneously.");
425            }
426
427            if(filters.getAttributeName()!="" && filters.getAttributeValue()==""){
428                UtilXmlTools::displayErrorMessage("@REMOVE_NODES","AttributeValue option is required if using AttributeName option.");
429            }
430
431            if(filters.getAttributeValue()!="" && filters.getAttributeName()==""){
432                UtilXmlTools::displayErrorMessage("@REMOVE_NODES","AttributeName option is required if using AttributeValue option.");
433            }
434
435            removeNodesOperation(filters,xPathExpression,filesWildcard);
436
437            filters.clear();
438            xPathExpression.clear();
439            filesWildcard.clear();
440        }
441        else if(line.startsWith("@COMMAND")){
442
443            command=GlobalVars::AppExecutable;
444
445            // Append files if forced to
446            if(!this->forceTargetFilesWildcard.isEmpty()){
447                command+=" --files '"+this->forceTargetFilesWildcard+"' ";
448            }
449
450            command+=" "+getPatchParameterValue(line,"Options");
451
452            // Add --no-backups and --no-verbose if patch was called with that arguments
453            if(!this->backupsEnabled){
454                command.append(" --no-backups ");
455            }
456
457            if(!this->verboseEnabled){
458                command.append(" --no-verbose ");
459            }
460
461            command.replace("'","\""); //replace apostrophe by quotes, to avoid problems
462
463            executeCommandOperation(command);
464
465            command.clear();
466            filesWildcard.clear();
467        }
468        else if(line.startsWith("@CUSTOM_CODE")){
469
470            if(this->forceTargetFilesWildcard!=""){
471                filesWildcard=this->forceTargetFilesWildcard;
472            }
473            else{
474                filesWildcard=getPatchParameterValue(line,"Files");
475            }
476
477            while ( !fileStream.atEnd() && !line.startsWith("</code>")){
478
479                line = fileStream.readLine();
480
481                if(!line.startsWith("<code>") && !line.startsWith("</code>")){
482                    jsCode += line + "\n";
483                }
484            }
485
486            executeCustomCommandOperation(jsCode,filesWildcard);
487
488            jsCode.clear();
489            filesWildcard.clear();
490        }
491    }
492}
493
494QString XmlPatch::getPatchParameterValue(const QString& line, QString parameter){
495
496    int startValueIdx, endValueIdx;
497
498    parameter=" "+parameter+" "; // Parameters include a space before and after it's value.
499
500    if(!line.contains(parameter)){
501        if(parameter==" ParentElementName " || parameter==" AttributeName " || parameter==" AttributeValue "
502                || parameter==" ElementName " || parameter==" XPathExpression "){
503            return ""; // not mandatory
504        }
505        parameter.remove(" "); // just remove the space added so it doesn't look weird when outputted to the user
506
507        UtilXmlTools::displayErrorMessage("Read patch file parameter","Couldn't retrieve '" + parameter + "' parameter.");
508    }
509
510    startValueIdx=line.indexOf(parameter); // get the position where parameter is defined
511    endValueIdx=line.indexOf("\"",startValueIdx)+1; // get the position where parameter value begins (+1 to ignore mandotory quote)
512
513    startValueIdx=endValueIdx;
514    endValueIdx=line.indexOf("\"",startValueIdx); // get the last mandatory quote of the value
515
516    return line.mid(startValueIdx,endValueIdx-startValueIdx); // return the value of the parameter (is in the middle of the mandatory quotes)
517}
518
519void XmlPatch::displayJsException(QScriptEngine &engine, QScriptValue &engineResult){
520    if (engine.hasUncaughtException()) {
521        UtilXmlTools::displayErrorMessage("@CUSTOM_CODE","Uncaught js exception (user code) at line " +QString::number(engine.uncaughtExceptionLineNumber()) + ":\n" + engineResult.toString());
522    }
523}
Note: See TracBrowser for help on using the repository browser.