Vidalia  0.2.21
wixtool.cpp
Go to the documentation of this file.
1 /*
2 ** $Id$
3 **
4 ** Copyright (C) 2009 The Tor Project, Inc.
5 ** See LICENSE file for terms; may be used according
6 ** Vidalia or Tor license constraints. (dual license)
7 */
8 
9 #include <QFile>
10 #include <QDomDocument>
11 #include <QTextStream>
12 #include <QTextCodec>
13 #include <QStringList>
14 #include <stdlib.h>
15 
16 #define WIX_ATTR_ID "Id"
17 #define WIX_ATTR_DIRACTION "uninstall"
18 #define WIX_ATTR_REGACTION "createAndRemoveOnUninstall"
19 #define WIX_ATTR_VALUE "Value"
20 #define WIX_ATTR_KEY "KeyPath"
21 #define WIX_ATTR_GUID "Guid"
22 #define WIX_ATTR_NAME "Name"
23 #define WIX_ATTR_REG_TYPE "Type"
24 #define WIX_ATTR_REG_NAME "Name"
25 #define WIX_ATTR_REG_ROOT "Root"
26 #define WIX_ATTR_REG_KEYPATH "Key"
27 #define WIX_ATTR_REG_ACTION "Action"
28 #define WIX_REG_KEY_TYPE "integer"
29 #define WIX_TAG_FILE "File"
30 #define WIX_TAG_DIR "Directory"
31 #define WIX_TAG_FEATURE "Feature"
32 #define WIX_TAG_COMPONENT "Component"
33 #define WIX_TAG_COMPONENT_REF "ComponentRef"
34 #define WIX_TAG_CREATEDIR "CreateFolder"
35 #define WIX_TAG_REMOVEDIR "RemoveFolder"
36 #define WIX_TAG_REGKEY "RegistryKey"
37 #define WIX_TAG_REGVAL "RegistryValue"
38 
39 typedef void (*TraverseCallback)(void *cbdata, QDomElement e);
40 
41 /* Splice command takes an element or sub tree from one
42  * document and inserts it into another. This is useful for
43  * expanding placeholder elements with their desired content
44  * for example.
45  * If an element name is not unique the conventional WiX Id
46  * attribute can be used to identify the specific element.
47  */
48 typedef struct s_SpliceData {
49  QString dtag;
50  QString did;
51  QDomElement splice;
52 } SpliceData;
53 
54 /* Replace operates on tags by name or Id like Splice but
55  * only makes modifications to individual elements. Replace
56  * can also remove elements. (replace with null)
57  */
58 typedef struct s_ReplaceData {
59  QString dtag;
60  QString did;
61  QString dprop;
62  QString newtag;
63  QString newprop;
64  QString newpropval;
65 } ReplaceData;
66 
67 /* Add operates on tags by name or Id as usual.
68  */
69 typedef struct s_AddData {
70  QString dtag;
71  QString did;
72  QString newtag;
73  QString newprop;
74  QString newpropval;
75 } AddData;
76 
77 /* In order to support local per user installation some basic
78  * constrains must apply to every component included in a
79  * package. This includes using a key path for each component
80  * via registry keys and placing all application data under the
81  * local user profile folder.
82  * This utility will navigate the components and convert any
83  * keys to registry key paths and create folders in the deployment
84  * hierarchy as required.
85  */
86 typedef struct s_UserLocalData {
87  QString keypath;
88  QString featureid;
89  QStringList newcomps;
91 
92 
93 /* Note that we must walk the tree ourselves as locate by ID
94  * nor suitable select by classification is available in the
95  * Qt API.
96  */
97 bool
98 do_walkdoc(QDomNode n,
100  void * cbdata,
101  QString *errorMessage)
102 {
103  QTextStream error(stderr);
104  if ( !n.isNull() ) {
105  if ( n.isElement() ) {
106  QDomElement e = n.toElement();
107  (*cb)(cbdata, e);
108  }
109  if ( n.hasChildNodes() ) {
110  QDomNodeList subnodes = n.childNodes();
111  int i = 0;
112  while (i < subnodes.count()) {
113  do_walkdoc(subnodes.item(i++), cb, cbdata, errorMessage);
114  }
115  }
116  }
117  return true;
118 }
119 
120 bool
121 walkdoc(QDomDocument *doc,
122  TraverseCallback cb,
123  void * cbdata,
124  QString *errorMessage)
125 {
126  QTextStream error(stderr);
127  QDomNode n = doc->documentElement();
128  do_walkdoc(n, cb, cbdata, errorMessage);
129  return true;
130 }
131 
132 void
133 splicefunc(void *cbdata,
134  QDomElement e)
135 {
136  SpliceData *d = reinterpret_cast<SpliceData *>(cbdata);
137  QString eid = e.attribute(WIX_ATTR_ID);
138 
139  if (e.tagName().compare(d->dtag) == 0) {
140  /* if a specific Id is set, verify it too. */
141  if (d->did.isEmpty() ||
142  (eid.size() && !eid.compare(d->did)) ) {
143 
144  /* expected behavior is to graft children of the splice under target.
145  * if we're only given a single element graft it instead.
146  */
147  if (d->splice.hasChildNodes()) {
148  QDomNodeList subnodes = d->splice.childNodes();
149  int i = 0;
150  while (i < subnodes.count()) {
151  e.appendChild(e.ownerDocument().importNode(subnodes.item(i++), true));
152  }
153  }
154  else {
155  e.appendChild(e.ownerDocument().importNode(d->splice, true));
156  }
157  }
158  }
159 }
160 
161 /** Make modifications to requested documents.
162  * returns false on error and <b>errorMessage</b> will be set.
163  */
164 bool
165 docsplice(QDomDocument *doc,
166  QString arguments,
167  QString *errorMessage)
168 {
169  Q_ASSERT(doc);
170  Q_ASSERT(errorMessage);
171  SpliceData cbdata;
172 
173  QStringList spliceinfo = arguments.split("=");
174  if (spliceinfo.count() != 2) {
175  *errorMessage = "Invalid argument for splice command: " + arguments;
176  return false;
177  }
178  if (spliceinfo[0].contains(':')) {
179  /* Id syntax */
180  QStringList destinfo = spliceinfo[0].split(":");
181  cbdata.dtag = destinfo[0];
182  cbdata.did = destinfo[1];
183  }
184  else {
185  cbdata.dtag = spliceinfo[0];
186  }
187 
188  QStringList srcinfo = spliceinfo[1].split(":");
189  if (srcinfo.count() < 2) {
190  *errorMessage = "Invalid source argument for splice command: " + arguments;
191  return false;
192  }
193  QFile spliceFile(srcinfo[0]);
194  if (!spliceFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
195  *errorMessage = QString("Unable to open '%1' for reading: %2\n")
196  .arg(srcinfo[0]).arg(spliceFile.errorString());
197  return false;
198  }
199  QTextStream sfiletxt(&spliceFile);
200  QDomDocument sdoc;
201  QString parseError;
202  int badline, badcol;
203  if (!sdoc.setContent (sfiletxt.readAll(), false, &parseError, &badline, &badcol)) {
204  *errorMessage = QString("Error parsing splice document '%1' at line %2 and column %3: %4")
205  .arg(srcinfo[0]).arg(badline).arg(badcol).arg(parseError);
206  return false;
207  }
208 
209  QDomNodeList elist = sdoc.elementsByTagName(srcinfo[1]);
210  if (elist.count() == 0) {
211  *errorMessage = QString("Unable to locate splice element '%1' in document.\n").arg(srcinfo[1]);
212  return false;
213  }
214  if (srcinfo.count() == 3) {
215  /* Id syntax for source elem */
216  for (int i=0; i < elist.count(); i++) {
217  QString eid = elist.item(i).toElement().attribute(WIX_ATTR_ID);
218  if (eid.size() && !eid.compare(srcinfo[2])) {
219  cbdata.splice = elist.item(i).toElement();
220  }
221  }
222  }
223  else {
224  /* without an Id the tag name should be unique. */
225  cbdata.splice = elist.item(0).toElement();
226  }
227  return walkdoc(doc, &splicefunc, &cbdata, errorMessage);
228 }
229 
230 void
231 replacefunc(void *cbdata,
232  QDomElement e)
233 {
234  ReplaceData *d = reinterpret_cast<ReplaceData *>(cbdata);
235  QString eid = e.attribute(WIX_ATTR_ID);
236 
237  if (e.tagName().compare(d->dtag) == 0) {
238  /* if a specific Id is set, verify it too. */
239  if (d->did.isEmpty() ||
240  (eid.size() && !eid.compare(d->did)) ) {
241 
242  /* no destination means remove node from tree */
243  if (d->newtag.isNull()) {
244  QDomNode parent = e.parentNode();
245  parent.removeChild(e);
246  }
247  else {
248  if (d->newtag.compare(e.tagName())) {
249  e.setTagName (d->newtag);
250  }
251  if (d->newprop.isNull()) {
252  /* clear all attributes (except Id if present) */
253  QDomNamedNodeMap attrs = e.attributes();
254  for (int i = 0; i < attrs.count(); i++) {
255  if (attrs.item(i).nodeName().compare(WIX_ATTR_ID)) {
256  e.removeAttribute(attrs.item(i).nodeName());
257  }
258  }
259  }
260  else {
261  /* only modify / clear a specific property */
262  QDomNode prop = e.attributeNode(d->newprop);
263  if (!prop.isNull()) {
264  e.setAttribute(d->newprop, d->newpropval);
265  }
266  }
267  }
268  }
269  }
270 }
271 
272 /** Make modifications to requested documents.
273  * returns false on error and <b>errorMessage</b> will be set.
274  */
275 bool
276 docreplace(QDomDocument *doc,
277  QString arguments,
278  QString *errorMessage)
279 {
280  Q_ASSERT(doc);
281  Q_ASSERT(errorMessage);
282  ReplaceData cbdata;
283 
284  QStringList replaceinfo = arguments.split("=");
285  if (replaceinfo.count() < 1) {
286  *errorMessage = "Invalid argument for replace command: " + arguments;
287  return false;
288  }
289  if (replaceinfo[0].contains(':')) {
290  /* Id syntax */
291  QStringList destinfo = replaceinfo[0].split(":");
292  cbdata.dtag = destinfo[0];
293  cbdata.did = destinfo[1];
294  if (destinfo.count() >= 3) {
295  cbdata.dprop = destinfo[2];
296  }
297  }
298  else {
299  cbdata.dtag = replaceinfo[0];
300  }
301  if (replaceinfo.count() > 1) {
302  QStringList srcinfo = replaceinfo[1].split(":");
303  if (srcinfo.count() < 1) {
304  *errorMessage = "Invalid target argument for replace command: " + arguments;
305  return false;
306  }
307  if (srcinfo.count() >= 1) {
308  if (srcinfo[0].length()) cbdata.newtag = srcinfo[0];
309  }
310  if (srcinfo.count() >= 2) {
311  if (srcinfo[1].length()) cbdata.newprop = srcinfo[1];
312  }
313  if (srcinfo.count() >= 3) {
314  if (srcinfo[2].length()) cbdata.newpropval = srcinfo[2];
315  }
316  }
317  return walkdoc(doc, &replacefunc, &cbdata, errorMessage);
318 }
319 
320 void
321 addfunc(void *cbdata,
322  QDomElement e)
323 {
324  AddData *d = reinterpret_cast<AddData *>(cbdata);
325  QString eid = e.attribute(WIX_ATTR_ID);
326 
327  if (e.tagName().compare(d->dtag) == 0) {
328  /* if a specific Id is set, verify it too. */
329  if (d->did.isEmpty() ||
330  (eid.size() && !eid.compare(d->did)) ) {
331  if (d->newtag.compare(d->dtag)) {
332  QDomElement ne = e.ownerDocument().createElement(d->newtag);
333  if (!d->newprop.isNull()) {
334  ne.setAttribute(d->newprop, d->newpropval);
335  }
336  e.appendChild(ne);
337  }
338  else {
339  e.setAttribute(d->newprop, d->newpropval);
340  }
341  }
342  }
343 }
344 
345 /** Make modifications to requested documents.
346  * returns false on error and <b>errorMessage</b> will be set.
347  */
348 bool
349 docadd(QDomDocument *doc,
350  QString arguments,
351  QString *errorMessage)
352 {
353  Q_ASSERT(doc);
354  Q_ASSERT(errorMessage);
355  AddData cbdata;
356 
357  QStringList addinfo = arguments.split("=");
358  if (addinfo.count() < 1) {
359  *errorMessage = "Invalid argument for add command: " + arguments;
360  return false;
361  }
362  if (addinfo[0].contains(':')) {
363  /* Id syntax */
364  QStringList destinfo = addinfo[0].split(":");
365  cbdata.dtag = destinfo[0];
366  cbdata.did = destinfo[1];
367  }
368  else {
369  cbdata.dtag = addinfo[0];
370  }
371  if (addinfo.count() > 1) {
372  QStringList srcinfo = addinfo[1].split(":");
373  if (srcinfo.count() < 1) {
374  *errorMessage = "Invalid target argument for add command: " + arguments;
375  return false;
376  }
377  if (srcinfo.count() >= 1) {
378  if (srcinfo[0].length()) cbdata.newtag = srcinfo[0];
379  }
380  if (srcinfo.count() >= 2) {
381  if (srcinfo[1].length()) cbdata.newprop = srcinfo[1];
382  }
383  if (srcinfo.count() >= 3) {
384  if (srcinfo[2].length()) cbdata.newpropval = srcinfo[2];
385  }
386  }
387  return walkdoc(doc, &addfunc, &cbdata, errorMessage);
388 }
389 
390 void
392  QString dirName,
393  QString keyPath)
394 {
395  QDomElement nrk = e.ownerDocument().createElement(WIX_TAG_REGKEY);
396  QDomElement nrv = e.ownerDocument().createElement(WIX_TAG_REGVAL);
397  nrk.setAttribute(WIX_ATTR_REG_ROOT, "HKCU");
398  nrk.setAttribute(WIX_ATTR_REG_ACTION, "createAndRemoveOnUninstall");
399  nrk.setAttribute(WIX_ATTR_REG_KEYPATH, keyPath);
400  nrv.setAttribute(WIX_ATTR_REG_TYPE, WIX_REG_KEY_TYPE);
401  nrv.setAttribute(WIX_ATTR_REG_NAME, dirName);
402  nrv.setAttribute(WIX_ATTR_VALUE, "1");
403  nrv.setAttribute(WIX_ATTR_KEY, "yes");
404  nrk.appendChild(nrv);
405  e.appendChild(nrk);
406 }
407 
408 void
410  QString dirName)
411 {
412  QDomElement nce;
413  /* An empty dir might produce a createdir, so only add if not present. */
414  if (e.elementsByTagName(WIX_TAG_CREATEDIR).count() == 0) {
415  nce = e.ownerDocument().createElement(WIX_TAG_CREATEDIR);
416  e.appendChild(nce);
417  }
418  nce = e.ownerDocument().createElement(WIX_TAG_REMOVEDIR);
419  nce.setAttribute("On", WIX_ATTR_DIRACTION);
420  nce.setAttribute(WIX_ATTR_ID, QString("Remove").append(dirName));
421  e.appendChild(nce);
422 }
423 
424 void
425 userlocalfunc(void *cbdata,
426  QDomElement e)
427 {
428  UserLocalData *ulinfo = reinterpret_cast<UserLocalData *>(cbdata);
429  QString eid = e.attribute(WIX_ATTR_ID);
430 
431  if (e.tagName().compare(WIX_TAG_FILE) == 0) {
432  e.removeAttribute(WIX_ATTR_KEY);
433  }
434  else if (e.tagName().compare(WIX_TAG_COMPONENT) == 0) {
435  /* If the WiX tools get confused we need to remove KeyPath attrs
436  * on any component elements after creation or merging.
437  * Empty directories with a CreateFolder and nothing else will do this.
438  */
439  e.removeAttribute(WIX_ATTR_KEY);
440  }
441  else if (e.tagName().compare(WIX_TAG_FEATURE) == 0) {
442  /* be sure to remove any default feature names; changed added above. */
443  QDomNodeList cnl = e.elementsByTagName(WIX_TAG_COMPONENT_REF);
444  for (int i = 0; i < cnl.count(); i++) {
445  QDomElement cre = cnl.item(i).toElement();
446  if (cre.attribute(WIX_ATTR_ID).compare(WIX_TAG_COMPONENT) == 0) {
447  e.removeChild(cre);
448  }
449  }
450  if (ulinfo->featureid.compare(e.attribute(WIX_ATTR_ID)) == 0) {
451  /* this is the target feature element for the new components, if any. */
452  QDomElement ne;
453  for (int i = 0; i < ulinfo->newcomps.count(); i++) {
454  QString currid = ulinfo->newcomps[i];
455  ne = e.ownerDocument().createElement(WIX_TAG_COMPONENT_REF);
456  ne.setAttribute(WIX_ATTR_ID, currid);
457  e.appendChild(ne);
458  }
459  }
460  }
461  else if (e.tagName().compare(WIX_TAG_DIR) == 0) {
462  QString dirName = e.attribute(WIX_ATTR_NAME);
463  QString dirId = e.attribute(WIX_ATTR_ID);
464  /* find all child components for this dir and see if it contains:
465  * create/remove folder elements, a registry element
466  */
467  if ( e.hasChildNodes() ) {
468  QDomElement fc;
469  bool hasComponent = false;
470  bool hasRegKey;
471  QDomNodeList subnodes = e.childNodes();
472  for (int i = 0; i < subnodes.count(); i++) {
473  hasRegKey = false;
474  if (subnodes.item(i).isElement()) {
475  QDomElement ce = subnodes.item(i).toElement();
476  if (ce.tagName().compare(WIX_TAG_COMPONENT) == 0) {
477  if (!hasComponent) {
478  hasComponent = true;
479  fc = ce;
480  if (ce.attribute(WIX_ATTR_ID).compare(WIX_TAG_COMPONENT) == 0) {
481  /* Fix default named components before adding registry elements. */
482  ce.setAttribute(WIX_ATTR_ID, QString("DCOMP").append(dirName));
483  ulinfo->newcomps.append(ce.attribute(WIX_ATTR_ID));
484  }
485  if (ce.elementsByTagName(WIX_TAG_REMOVEDIR).count() == 0) {
486  createDirMgmtComponent(ce, ce.attribute(WIX_ATTR_ID));
487  }
488  }
489  QDomNodeList compnodes = ce.childNodes();
490  for (int j = 0; j < compnodes.count(); j++) {
491  if (compnodes.item(j).isElement()) {
492  QDomElement compe = compnodes.item(j).toElement();
493  if (compe.tagName().compare(WIX_TAG_REGKEY) == 0) {
494  hasRegKey = true;
495  }
496  }
497  }
498  if (!hasRegKey) {
499  createRegLocalComponent(ce, QString("RK").append(ce.attribute(WIX_ATTR_ID)), ulinfo->keypath);
500  }
501  }
502  }
503  }
504  if (!hasComponent) {
505  /* Certain system directories must be ignored; we don't manage them. */
506  if (dirId.compare("LocalAppDataFolder") &&
507  dirId.compare("AppDataFolder") &&
508  dirId.compare("CommonAppDataFolder") &&
509  dirId.compare("CommonFilesFolder") &&
510  dirId.compare("DesktopFolder") &&
511  dirId.compare("PersonalFolder") &&
512  dirId.compare("ProgramFilesFolder") &&
513  dirId.compare("ProgramMenuFolder") &&
514  dirId.compare("StartMenuFolder") &&
515  dirId.compare("StartupFolder") &&
516  dirId.compare("SystemFolder") &&
517  dirId.compare("TempFolder") &&
518  dirId.compare("WindowsFolder") ) {
519  /* if there is no component under this dir parent then we
520  * must create a component for the sole purpose of dir
521  * creation with the requisite registry key path.
522  */
523  QDomElement ne = e.ownerDocument().createElement(WIX_TAG_COMPONENT);
524  QString compId = QString("ULDirComp_").append(dirName);
525  ne.setAttribute(WIX_ATTR_GUID, "*");
526  ne.setAttribute(WIX_ATTR_ID, compId);
527  e.appendChild(ne);
528  createDirMgmtComponent(ne, dirName);
529  createRegLocalComponent(ne, QString("DRK").append(dirName), ulinfo->keypath);
530  ulinfo->newcomps.append(compId);
531  }
532  }
533  }
534  }
535 }
536 
537 /** Make modifications to requested documents.
538  * returns false on error and <b>errorMessage</b> will be set.
539  */
540 bool
541 docuserlocal(QDomDocument *doc,
542  QString argument,
543  QString *errorMessage)
544 {
545  Q_ASSERT(doc);
546  Q_ASSERT(errorMessage);
547  UserLocalData cbdata;
548 
549  QStringList ulinfo = argument.split(":");
550  if (ulinfo.count() < 2) {
551  *errorMessage = "Invalid argument for userlocal command: " + argument;
552  return false;
553  }
554  cbdata.keypath = ulinfo[0];
555  cbdata.featureid = ulinfo[1];
556  return walkdoc(doc, &userlocalfunc, &cbdata, errorMessage);
557 }
558 
559 /** Display application usage and exit. */
560 void
562 {
563  QTextStream error(stderr);
564  error << "usage: wixtool <command> [-q] -i <infile> -o <outfile> <Arg0> [... <ArgN>]" << endl;
565  error << " command one of: " << endl;
566  error << " splice Splice children from one document into another." << endl;
567  error << " replace Replace elements or attributes in a document." << endl;
568  error << " add Add elements or attributes into a document." << endl;
569  error << " userlocal Convert File elements into per-user local elements." << endl;
570  error << " -i <infile> Input or template file" << endl;
571  error << " -o <outfile> Output file" << endl;
572  error << endl;
573  error << " splice args: desttagname[:Id]=file:basetag[:Id]" << endl;
574  error << " Splice children of basetag in file under desttagname" << endl;
575  error << endl;
576  error << " replace args: tagname[:Id]:property=newtagname[:Id]:property:value" << endl;
577  error << " If newtagname is empty the element is deleted" << endl;
578  error << " If newproperty is empty the property is deleted" << endl;
579  error << endl;
580  error << " add args: desttagname[:Id]=newtagname[:Id]:property:value" << endl;
581  error << " Add properties or child elements to target" << endl;
582  error << " If newtagname is empty only properties added to dest" << endl;
583  error << endl;
584  error << " userlocal arg: <registry key path>:<dest feature id>" << endl;
585  error << " Convert KeyPath File elements into the per user local idiom" << endl;
586  error << " with corresponding Create/RemoveDir and RegistryKey elements." << endl;
587  error << endl;
588  error << " NOTE: text content within an element is not accessible." << endl;
589  error << " Use the Value= attribute syntax if necessary." << endl;
590  error << " The optional :Id syntax restricts matching to elements with" << endl;
591  error << " the Id attribute set to the value indicated." << endl;
592  error.flush();
593  exit(1);
594 }
595 
596 int
597 main(int argc, char *argv[])
598 {
599  QTextStream error(stderr);
600  QString command, errorMessage;
601  char *infile = 0, *outfile = 0;
602  QTextCodec *codec = QTextCodec::codecForName("utf-8");
603  bool quiet = false;
604  QStringList commandargs;
605 
606  /* Check for the correct number of input parameters. */
607  if (argc < 6)
609 
610  /* Verify command is supported. */
611  command = argv[1];
612  if ( command.compare("splice", Qt::CaseInsensitive) &&
613  command.compare("replace", Qt::CaseInsensitive) &&
614  command.compare("add", Qt::CaseInsensitive) &&
615  command.compare("userlocal", Qt::CaseInsensitive) ) {
617  }
618 
619  /* Gather remaining arguments. */
620  for (int i = 2; i < argc; i++) {
621  QString arg(argv[i]);
622  if (!arg.compare("-q", Qt::CaseInsensitive))
623  quiet = true;
624  else if (!arg.compare("-i", Qt::CaseInsensitive) && ++i < argc)
625  infile = argv[i];
626  else if (!arg.compare("-o", Qt::CaseInsensitive) && ++i < argc)
627  outfile = argv[i];
628  else if (infile && outfile) {
629  commandargs.append(arg);
630  }
631  }
632  if ( !infile || !outfile || !commandargs.count() ) {
634  }
635 
636  /* Open the source document for reading. */
637  QFile srcFile(infile);
638  QTextStream sfiletxt(&srcFile);
639  sfiletxt.setCodec(codec);
640  if (!srcFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
641  error << QString("Unable to open '%1' for reading: %2\n").arg(infile)
642  .arg(srcFile.errorString());
643  return 2;
644  }
645 
646  /* Make sure the outfile does not exist before we use it. */
647  if (QFile::exists(outfile)) {
648  if (!QFile::remove(outfile)) {
649  error << QString("Unable to truncate outfile '%1'\n").arg(outfile);
650  return 2;
651  }
652  }
653 
654  QDomDocument doc;
655  QString parseError;
656  int badline, badcol;
657  if (!doc.setContent (sfiletxt.readAll(), false, &parseError, &badline, &badcol)) {
658  error << QString("Error parsing source document '%1' at line %2 and column %3: %4")
659  .arg(infile).arg(badline).arg(badcol).arg(parseError);
660  return 3;
661  }
662 
663  if (!command.compare("userlocal", Qt::CaseInsensitive)) {
664  if (!docuserlocal(&doc, commandargs[0], &errorMessage)) {
665  error << QString("Unable to convert document components to user local: %1\n")
666  .arg(errorMessage);
667  return 4;
668  }
669  }
670  else {
671  for (int i = 0; i < commandargs.count(); i++) {
672  if (!command.compare("splice", Qt::CaseInsensitive)) {
673  if (!docsplice(&doc, commandargs[i], &errorMessage)) {
674  error << QString("Unable to process splice command '%1': %2\n")
675  .arg(commandargs[i]).arg(errorMessage);
676  return 4;
677  }
678  }
679  else if (!command.compare("replace", Qt::CaseInsensitive)) {
680  if (!docreplace(&doc, commandargs[i], &errorMessage)) {
681  error << QString("Unable to process replace command '%1': %2\n")
682  .arg(commandargs[i]).arg(errorMessage);
683  return 4;
684  }
685  }
686  else if (!command.compare("add", Qt::CaseInsensitive)) {
687  if (!docadd(&doc, commandargs[i], &errorMessage)) {
688  error << QString("Unable to process add command '%1': %2\n")
689  .arg(commandargs[i]).arg(errorMessage);
690  return 4;
691  }
692  }
693  }
694  }
695 
696  /* Open the output file for writing. */
697  QFile docFile(outfile);
698  if (!docFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
699  error << QString("Unable to open '%1' for writing: %2\n").arg(outfile)
700  .arg(docFile.errorString());
701  return 5;
702  }
703 
704  /* Write the .wxl output. */
705  QTextStream out(&docFile);
706  out << doc.toString(4);
707 
708  return 0;
709 }
710