Vidalia 0.2.15
wixtool.cpp
Go to the documentation of this file.
00001 /*
00002 **  $Id$
00003 **
00004 **  Copyright (C) 2009  The Tor Project, Inc.
00005 **  See LICENSE file for terms; may be used according
00006 **  Vidalia or Tor license constraints. (dual license)
00007 */
00008 
00009 #include <QFile>
00010 #include <QDomDocument>
00011 #include <QTextStream>
00012 #include <QTextCodec>
00013 #include <QStringList>
00014 #include <stdlib.h>
00015 
00016 #define WIX_ATTR_ID  "Id"
00017 #define WIX_ATTR_DIRACTION "uninstall"
00018 #define WIX_ATTR_REGACTION "createAndRemoveOnUninstall"
00019 #define WIX_ATTR_VALUE "Value"
00020 #define WIX_ATTR_KEY "KeyPath"
00021 #define WIX_ATTR_GUID "Guid"
00022 #define WIX_ATTR_NAME "Name"
00023 #define WIX_ATTR_REG_TYPE "Type"
00024 #define WIX_ATTR_REG_NAME "Name"
00025 #define WIX_ATTR_REG_ROOT "Root"
00026 #define WIX_ATTR_REG_KEYPATH  "Key"
00027 #define WIX_ATTR_REG_ACTION  "Action"
00028 #define WIX_REG_KEY_TYPE "integer"
00029 #define WIX_TAG_FILE "File"
00030 #define WIX_TAG_DIR "Directory"
00031 #define WIX_TAG_FEATURE "Feature"
00032 #define WIX_TAG_COMPONENT "Component"
00033 #define WIX_TAG_COMPONENT_REF "ComponentRef"
00034 #define WIX_TAG_CREATEDIR "CreateFolder"
00035 #define WIX_TAG_REMOVEDIR "RemoveFolder"
00036 #define WIX_TAG_REGKEY "RegistryKey"
00037 #define WIX_TAG_REGVAL "RegistryValue"
00038 
00039 typedef void (*TraverseCallback)(void *cbdata, QDomElement e);
00040 
00041 /* Splice command takes an element or sub tree from one
00042  * document and inserts it into another. This is useful for
00043  * expanding placeholder elements with their desired content
00044  * for example.
00045  * If an element name is not unique the conventional WiX Id
00046  * attribute can be used to identify the specific element.
00047  */
00048 typedef struct s_SpliceData {
00049   QString      dtag;
00050   QString      did;
00051   QDomElement  splice;
00052 } SpliceData;
00053 
00054 /* Replace operates on tags by name or Id like Splice but
00055  * only makes modifications to individual elements. Replace
00056  * can also remove elements. (replace with null)
00057  */
00058 typedef struct s_ReplaceData {
00059   QString  dtag;
00060   QString  did;
00061   QString  dprop;
00062   QString  newtag;
00063   QString  newprop;
00064   QString  newpropval;
00065 } ReplaceData;
00066 
00067 /* Add operates on tags by name or Id as usual.
00068  */
00069 typedef struct s_AddData {
00070   QString  dtag;
00071   QString  did;
00072   QString  newtag;
00073   QString  newprop;
00074   QString  newpropval;
00075 } AddData;
00076 
00077 /* In order to support local per user installation some basic
00078  * constrains must apply to every component included in a
00079  * package. This includes using a key path for each component
00080  * via registry keys and placing all application data under the
00081  * local user profile folder.
00082  * This utility will navigate the components and convert any
00083  * keys to registry key paths and create folders in the deployment
00084  * hierarchy as required.
00085  */
00086 typedef struct s_UserLocalData {
00087   QString      keypath;
00088   QString      featureid;
00089   QStringList  newcomps;
00090 } UserLocalData;
00091 
00092 
00093 /* Note that we must walk the tree ourselves as locate by ID
00094  * nor suitable select by classification is available in the
00095  * Qt API.
00096  */
00097 bool
00098 do_walkdoc(QDomNode  n,
00099   TraverseCallback  cb,
00100   void *            cbdata,
00101   QString *errorMessage)
00102 {
00103   QTextStream error(stderr);
00104   if ( !n.isNull() ) {
00105     if ( n.isElement() ) {
00106       QDomElement e = n.toElement();
00107       (*cb)(cbdata, e);
00108     }
00109     if ( n.hasChildNodes() ) {
00110       QDomNodeList subnodes = n.childNodes();
00111       int i = 0;
00112       while (i < subnodes.count()) {
00113         do_walkdoc(subnodes.item(i++), cb, cbdata, errorMessage);
00114       }
00115     }
00116   }
00117   return true;
00118 }
00119 
00120 bool
00121 walkdoc(QDomDocument *doc,
00122   TraverseCallback  cb,
00123   void *            cbdata,
00124   QString *errorMessage)
00125 {
00126   QTextStream error(stderr);
00127   QDomNode n = doc->documentElement();
00128   do_walkdoc(n, cb, cbdata, errorMessage);
00129   return true;
00130 }
00131 
00132 void 
00133 splicefunc(void *cbdata,
00134  QDomElement e)
00135 {
00136   SpliceData *d = reinterpret_cast<SpliceData *>(cbdata);
00137   QString eid = e.attribute(WIX_ATTR_ID);
00138 
00139   if (e.tagName().compare(d->dtag) == 0) {
00140     /* if a specific Id is set, verify it too. */
00141     if (d->did.isEmpty() ||
00142         (eid.size() && !eid.compare(d->did)) ) {
00143 
00144       /* expected behavior is to graft children of the splice under target.
00145        * if we're only given a single element graft it instead.
00146        */
00147       if (d->splice.hasChildNodes()) {
00148         QDomNodeList subnodes = d->splice.childNodes();
00149         int i = 0;
00150         while (i < subnodes.count()) {
00151           e.appendChild(e.ownerDocument().importNode(subnodes.item(i++), true));
00152         }
00153       }
00154       else {
00155         e.appendChild(e.ownerDocument().importNode(d->splice, true));
00156       }
00157     }
00158   }
00159 }
00160 
00161 /** Make modifications to requested documents.
00162  * returns false on error and <b>errorMessage</b> will be set.
00163  */
00164 bool
00165 docsplice(QDomDocument *doc,
00166   QString arguments,
00167   QString *errorMessage)
00168 {
00169   Q_ASSERT(doc);
00170   Q_ASSERT(errorMessage);
00171   SpliceData  cbdata;
00172 
00173   QStringList spliceinfo = arguments.split("=");
00174   if (spliceinfo.count() != 2) {
00175     *errorMessage = "Invalid argument for splice command: " + arguments;
00176     return false;
00177   }
00178   if (spliceinfo[0].contains(':')) {
00179     /* Id syntax */
00180     QStringList destinfo = spliceinfo[0].split(":");
00181     cbdata.dtag = destinfo[0];
00182     cbdata.did  = destinfo[1];
00183   }
00184   else {
00185     cbdata.dtag = spliceinfo[0];
00186   }
00187 
00188   QStringList  srcinfo = spliceinfo[1].split(":");
00189   if (srcinfo.count() < 2) {
00190     *errorMessage = "Invalid source argument for splice command: " + arguments;
00191     return false;
00192   }
00193   QFile spliceFile(srcinfo[0]);
00194   if (!spliceFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
00195     *errorMessage = QString("Unable to open '%1' for reading: %2\n")
00196                      .arg(srcinfo[0]).arg(spliceFile.errorString());
00197     return false;
00198   }
00199   QTextStream sfiletxt(&spliceFile);
00200   QDomDocument sdoc;
00201   QString parseError;
00202   int  badline, badcol;
00203   if (!sdoc.setContent (sfiletxt.readAll(), false, &parseError, &badline, &badcol)) {
00204     *errorMessage = QString("Error parsing splice document '%1' at line %2 and column %3: %4")
00205                     .arg(srcinfo[0]).arg(badline).arg(badcol).arg(parseError);
00206     return false;
00207   }
00208 
00209   QDomNodeList elist = sdoc.elementsByTagName(srcinfo[1]);
00210   if (elist.count() == 0) {
00211     *errorMessage = QString("Unable to locate splice element '%1' in document.\n").arg(srcinfo[1]);
00212     return false;
00213   }
00214   if (srcinfo.count() == 3) {
00215     /* Id syntax for source elem */
00216     for (int i=0; i < elist.count(); i++) {
00217       QString eid = elist.item(i).toElement().attribute(WIX_ATTR_ID);
00218       if (eid.size() && !eid.compare(srcinfo[2])) {
00219         cbdata.splice = elist.item(i).toElement();
00220       }
00221     }
00222   }
00223   else {
00224     /* without an Id the tag name should be unique. */
00225     cbdata.splice = elist.item(0).toElement();
00226   }
00227   return walkdoc(doc, &splicefunc, &cbdata, errorMessage);
00228 }
00229 
00230 void 
00231 replacefunc(void *cbdata,
00232  QDomElement e)
00233 {
00234   ReplaceData *d = reinterpret_cast<ReplaceData *>(cbdata);
00235   QString eid = e.attribute(WIX_ATTR_ID);
00236 
00237   if (e.tagName().compare(d->dtag) == 0) {
00238     /* if a specific Id is set, verify it too. */
00239     if (d->did.isEmpty() ||
00240         (eid.size() && !eid.compare(d->did)) ) {
00241 
00242       /* no destination means remove node from tree */
00243       if (d->newtag.isNull()) {
00244         QDomNode parent = e.parentNode();
00245         parent.removeChild(e);
00246       }
00247       else {
00248         if (d->newtag.compare(e.tagName())) {
00249           e.setTagName (d->newtag);
00250         }
00251         if (d->newprop.isNull()) {
00252           /* clear all attributes (except Id if present) */
00253           QDomNamedNodeMap attrs = e.attributes();
00254           for (int i = 0; i < attrs.count(); i++) {
00255             if (attrs.item(i).nodeName().compare(WIX_ATTR_ID)) {
00256               e.removeAttribute(attrs.item(i).nodeName());
00257             }
00258           }
00259         }
00260         else {
00261           /* only modify / clear a specific property */
00262           QDomNode prop = e.attributeNode(d->newprop);
00263           if (!prop.isNull()) {
00264             e.setAttribute(d->newprop, d->newpropval);
00265           }
00266         }
00267       }
00268     }
00269   }
00270 }
00271 
00272 /** Make modifications to requested documents.
00273  * returns false on error and <b>errorMessage</b> will be set.
00274  */
00275 bool
00276 docreplace(QDomDocument *doc,
00277   QString arguments,
00278   QString *errorMessage)
00279 {
00280   Q_ASSERT(doc);
00281   Q_ASSERT(errorMessage);
00282   ReplaceData  cbdata;
00283 
00284   QStringList replaceinfo = arguments.split("=");
00285   if (replaceinfo.count() < 1) {
00286     *errorMessage = "Invalid argument for replace command: " + arguments;
00287     return false;
00288   }
00289   if (replaceinfo[0].contains(':')) {
00290     /* Id syntax */
00291     QStringList destinfo = replaceinfo[0].split(":");
00292     cbdata.dtag = destinfo[0];
00293     cbdata.did  = destinfo[1];
00294     if (destinfo.count() >= 3) {
00295       cbdata.dprop = destinfo[2];
00296     }
00297   }
00298   else {
00299     cbdata.dtag = replaceinfo[0];
00300   }
00301   if (replaceinfo.count() > 1) {
00302     QStringList  srcinfo = replaceinfo[1].split(":");
00303     if (srcinfo.count() < 1) {
00304       *errorMessage = "Invalid target argument for replace command: " + arguments;
00305       return false;
00306     }
00307     if (srcinfo.count() >= 1) {
00308       if (srcinfo[0].length()) cbdata.newtag = srcinfo[0];
00309     }
00310     if (srcinfo.count() >= 2) {
00311       if (srcinfo[1].length()) cbdata.newprop = srcinfo[1];
00312     }
00313     if (srcinfo.count() >= 3) {
00314       if (srcinfo[2].length()) cbdata.newpropval = srcinfo[2];
00315     }
00316   }
00317   return walkdoc(doc, &replacefunc, &cbdata, errorMessage);
00318 }
00319 
00320 void 
00321 addfunc(void *cbdata,
00322  QDomElement e)
00323 {
00324   AddData *d = reinterpret_cast<AddData *>(cbdata);
00325   QString eid = e.attribute(WIX_ATTR_ID);
00326 
00327   if (e.tagName().compare(d->dtag) == 0) {
00328     /* if a specific Id is set, verify it too. */
00329     if (d->did.isEmpty() ||
00330         (eid.size() && !eid.compare(d->did)) ) {
00331       if (d->newtag.compare(d->dtag)) {
00332         QDomElement ne = e.ownerDocument().createElement(d->newtag);
00333         if (!d->newprop.isNull()) {
00334           ne.setAttribute(d->newprop, d->newpropval);
00335         }
00336         e.appendChild(ne);
00337       }
00338       else {
00339         e.setAttribute(d->newprop, d->newpropval);
00340       }
00341     }
00342   }
00343 }
00344 
00345 /** Make modifications to requested documents.
00346  * returns false on error and <b>errorMessage</b> will be set.
00347  */
00348 bool
00349 docadd(QDomDocument *doc,
00350   QString arguments,
00351   QString *errorMessage)
00352 {
00353   Q_ASSERT(doc);
00354   Q_ASSERT(errorMessage);
00355   AddData  cbdata;
00356 
00357   QStringList addinfo = arguments.split("=");
00358   if (addinfo.count() < 1) {
00359     *errorMessage = "Invalid argument for add command: " + arguments;
00360     return false;
00361   }
00362   if (addinfo[0].contains(':')) {
00363     /* Id syntax */
00364     QStringList destinfo = addinfo[0].split(":");
00365     cbdata.dtag = destinfo[0];
00366     cbdata.did  = destinfo[1];
00367   }
00368   else {
00369     cbdata.dtag = addinfo[0];
00370   }
00371   if (addinfo.count() > 1) {
00372     QStringList  srcinfo = addinfo[1].split(":");
00373     if (srcinfo.count() < 1) {
00374       *errorMessage = "Invalid target argument for add command: " + arguments;
00375       return false;
00376     }
00377     if (srcinfo.count() >= 1) {
00378       if (srcinfo[0].length()) cbdata.newtag = srcinfo[0];
00379     }
00380     if (srcinfo.count() >= 2) {
00381       if (srcinfo[1].length()) cbdata.newprop = srcinfo[1];
00382     }
00383     if (srcinfo.count() >= 3) {
00384       if (srcinfo[2].length()) cbdata.newpropval = srcinfo[2];
00385     }
00386   }
00387   return walkdoc(doc, &addfunc, &cbdata, errorMessage);
00388 }
00389 
00390 void
00391 createRegLocalComponent(QDomElement e,
00392  QString dirName,
00393  QString keyPath)
00394 { 
00395   QDomElement nrk = e.ownerDocument().createElement(WIX_TAG_REGKEY);
00396   QDomElement nrv = e.ownerDocument().createElement(WIX_TAG_REGVAL);
00397   nrk.setAttribute(WIX_ATTR_REG_ROOT, "HKCU");
00398   nrk.setAttribute(WIX_ATTR_REG_ACTION, "createAndRemoveOnUninstall");
00399   nrk.setAttribute(WIX_ATTR_REG_KEYPATH, keyPath);
00400   nrv.setAttribute(WIX_ATTR_REG_TYPE, WIX_REG_KEY_TYPE);
00401   nrv.setAttribute(WIX_ATTR_REG_NAME, dirName);
00402   nrv.setAttribute(WIX_ATTR_VALUE, "1");
00403   nrv.setAttribute(WIX_ATTR_KEY, "yes");
00404   nrk.appendChild(nrv);
00405   e.appendChild(nrk);
00406 }
00407 
00408 void
00409 createDirMgmtComponent(QDomElement e,
00410  QString dirName)
00411 {
00412   QDomElement nce;
00413   /* An empty dir might produce a createdir, so only add if not present. */
00414   if (e.elementsByTagName(WIX_TAG_CREATEDIR).count() == 0) {
00415     nce = e.ownerDocument().createElement(WIX_TAG_CREATEDIR);
00416     e.appendChild(nce);
00417   }
00418   nce = e.ownerDocument().createElement(WIX_TAG_REMOVEDIR);
00419   nce.setAttribute("On", WIX_ATTR_DIRACTION);
00420   nce.setAttribute(WIX_ATTR_ID, QString("Remove").append(dirName));
00421   e.appendChild(nce);
00422 }
00423 
00424 void
00425 userlocalfunc(void *cbdata,
00426  QDomElement e)
00427 {
00428   UserLocalData *ulinfo = reinterpret_cast<UserLocalData *>(cbdata);
00429   QString eid = e.attribute(WIX_ATTR_ID);
00430 
00431   if (e.tagName().compare(WIX_TAG_FILE) == 0) {
00432     e.removeAttribute(WIX_ATTR_KEY);
00433   }
00434   else if (e.tagName().compare(WIX_TAG_COMPONENT) == 0) {
00435     /* If the WiX tools get confused we need to remove KeyPath attrs
00436      * on any component elements after creation or merging.
00437      * Empty directories with a CreateFolder and nothing else will do this.
00438      */
00439     e.removeAttribute(WIX_ATTR_KEY);
00440   }
00441   else if (e.tagName().compare(WIX_TAG_FEATURE) == 0) {
00442     /* be sure to remove any default feature names; changed added above. */
00443     QDomNodeList cnl = e.elementsByTagName(WIX_TAG_COMPONENT_REF);
00444     for (int i = 0; i < cnl.count(); i++) {
00445       QDomElement cre = cnl.item(i).toElement();
00446       if (cre.attribute(WIX_ATTR_ID).compare(WIX_TAG_COMPONENT) == 0) {
00447         e.removeChild(cre);
00448       }
00449     }
00450     if (ulinfo->featureid.compare(e.attribute(WIX_ATTR_ID)) == 0) {
00451       /* this is the target feature element for the new components, if any. */
00452       QDomElement ne;
00453       for (int i = 0; i < ulinfo->newcomps.count(); i++) {
00454         QString currid = ulinfo->newcomps[i];
00455         ne = e.ownerDocument().createElement(WIX_TAG_COMPONENT_REF);
00456         ne.setAttribute(WIX_ATTR_ID, currid);
00457         e.appendChild(ne);
00458       }
00459     }
00460   }
00461   else if (e.tagName().compare(WIX_TAG_DIR) == 0) {
00462     QString dirName = e.attribute(WIX_ATTR_NAME);
00463     QString dirId = e.attribute(WIX_ATTR_ID);
00464     /* find all child components for this dir and see if it contains:
00465      * create/remove folder elements, a registry element
00466      */
00467     if ( e.hasChildNodes() ) {
00468       QDomElement fc;
00469       bool  hasComponent = false;
00470       bool  hasRegKey;
00471       QDomNodeList subnodes = e.childNodes();
00472       for (int i = 0; i < subnodes.count(); i++) {
00473         hasRegKey = false;
00474         if (subnodes.item(i).isElement()) {
00475           QDomElement ce = subnodes.item(i).toElement();
00476           if (ce.tagName().compare(WIX_TAG_COMPONENT) == 0) {
00477             if (!hasComponent) {
00478               hasComponent = true;
00479               fc = ce;
00480               if (ce.attribute(WIX_ATTR_ID).compare(WIX_TAG_COMPONENT) == 0) {
00481                 /* Fix default named components before adding registry elements. */
00482                 ce.setAttribute(WIX_ATTR_ID, QString("DCOMP").append(dirName));
00483                 ulinfo->newcomps.append(ce.attribute(WIX_ATTR_ID));
00484               }
00485               if (ce.elementsByTagName(WIX_TAG_REMOVEDIR).count() == 0) {
00486                 createDirMgmtComponent(ce, ce.attribute(WIX_ATTR_ID));
00487               }
00488             }
00489             QDomNodeList compnodes = ce.childNodes();
00490             for (int j = 0; j < compnodes.count(); j++) {
00491               if (compnodes.item(j).isElement()) {
00492                 QDomElement compe = compnodes.item(j).toElement();
00493                 if (compe.tagName().compare(WIX_TAG_REGKEY) == 0) {
00494                   hasRegKey = true;
00495                 }
00496               }
00497             }
00498             if (!hasRegKey) {
00499               createRegLocalComponent(ce, QString("RK").append(ce.attribute(WIX_ATTR_ID)), ulinfo->keypath);
00500             }
00501           }
00502         }
00503       }
00504       if (!hasComponent) {
00505         /* Certain system directories must be ignored; we don't manage them. */
00506         if (dirId.compare("LocalAppDataFolder") &&
00507             dirId.compare("AppDataFolder") &&
00508             dirId.compare("CommonAppDataFolder") &&
00509             dirId.compare("CommonFilesFolder") &&
00510             dirId.compare("DesktopFolder") &&
00511             dirId.compare("PersonalFolder") &&
00512             dirId.compare("ProgramFilesFolder") &&
00513             dirId.compare("ProgramMenuFolder") &&
00514             dirId.compare("StartMenuFolder") &&
00515             dirId.compare("StartupFolder") &&
00516             dirId.compare("SystemFolder") &&
00517             dirId.compare("TempFolder") &&
00518             dirId.compare("WindowsFolder") ) {
00519           /* if there is no component under this dir parent then we
00520            * must create a component for the sole purpose of dir
00521            * creation with the requisite registry key path.
00522            */
00523           QDomElement ne = e.ownerDocument().createElement(WIX_TAG_COMPONENT);
00524           QString compId = QString("ULDirComp_").append(dirName);
00525           ne.setAttribute(WIX_ATTR_GUID, "*");
00526           ne.setAttribute(WIX_ATTR_ID, compId);
00527           e.appendChild(ne);
00528           createDirMgmtComponent(ne, dirName);
00529           createRegLocalComponent(ne, QString("DRK").append(dirName), ulinfo->keypath);
00530           ulinfo->newcomps.append(compId);
00531         }
00532       }
00533     }
00534   }
00535 }
00536 
00537 /** Make modifications to requested documents.
00538  * returns false on error and <b>errorMessage</b> will be set.
00539  */
00540 bool
00541 docuserlocal(QDomDocument *doc,
00542   QString argument,
00543   QString *errorMessage)
00544 {
00545   Q_ASSERT(doc);
00546   Q_ASSERT(errorMessage);
00547   UserLocalData  cbdata;
00548 
00549   QStringList ulinfo = argument.split(":");
00550   if (ulinfo.count() < 2) {
00551     *errorMessage = "Invalid argument for userlocal command: " + argument;
00552     return false;
00553   }
00554   cbdata.keypath = ulinfo[0];
00555   cbdata.featureid = ulinfo[1];
00556   return walkdoc(doc, &userlocalfunc, &cbdata, errorMessage);
00557 }
00558 
00559 /** Display application usage and exit. */
00560 void
00561 print_usage_and_exit()
00562 {
00563   QTextStream error(stderr);
00564   error << "usage: wixtool <command> [-q] -i <infile> -o <outfile> <Arg0> [... <ArgN>]" << endl;
00565   error << "  command one of: " << endl;
00566   error << "    splice        Splice children from one document into another." << endl;
00567   error << "    replace       Replace elements or attributes in a document." << endl;
00568   error << "    add           Add elements or attributes into a document." << endl;
00569   error << "    userlocal     Convert File elements into per-user local elements." << endl;
00570   error << "  -i <infile>     Input or template file" << endl;
00571   error << "  -o <outfile>    Output file" << endl;
00572   error << endl;
00573   error << "  splice args:    desttagname[:Id]=file:basetag[:Id]" << endl;
00574   error << "    Splice children of basetag in file under desttagname" << endl;
00575   error << endl;
00576   error << "  replace args:   tagname[:Id]:property=newtagname[:Id]:property:value" << endl;
00577   error << "    If newtagname is empty the element is deleted" << endl;
00578   error << "    If newproperty is empty the property is deleted" << endl;
00579   error << endl;
00580   error << "  add args:       desttagname[:Id]=newtagname[:Id]:property:value" << endl;
00581   error << "    Add properties or child elements to target" << endl;
00582   error << "    If newtagname is empty only properties added to dest" << endl;
00583   error << endl;
00584   error << "  userlocal arg:  <registry key path>:<dest feature id>" << endl;
00585   error << "    Convert KeyPath File elements into the per user local idiom" << endl;
00586   error << "    with corresponding Create/RemoveDir and RegistryKey elements." << endl;
00587   error << endl;
00588   error << " NOTE: text content within an element is not accessible." << endl;
00589   error << "       Use the Value= attribute syntax if necessary." << endl;
00590   error << "       The optional :Id syntax restricts matching to elements with" << endl;
00591   error << "       the Id attribute set to the value indicated." << endl;
00592   error.flush();
00593   exit(1);
00594 }
00595 
00596 int
00597 main(int argc, char *argv[])
00598 {
00599   QTextStream error(stderr);
00600   QString command, errorMessage;
00601   char *infile = 0, *outfile = 0;
00602   QTextCodec *codec = QTextCodec::codecForName("utf-8");
00603   bool quiet = false;
00604   QStringList commandargs;
00605 
00606   /* Check for the correct number of input parameters. */
00607   if (argc < 6)
00608     print_usage_and_exit();
00609 
00610   /* Verify command is supported. */
00611   command = argv[1];
00612   if ( command.compare("splice", Qt::CaseInsensitive) &&
00613        command.compare("replace", Qt::CaseInsensitive) &&
00614        command.compare("add", Qt::CaseInsensitive) &&
00615        command.compare("userlocal", Qt::CaseInsensitive) ) {
00616     print_usage_and_exit();
00617   }
00618 
00619   /* Gather remaining arguments. */
00620   for (int i = 2; i < argc; i++) {
00621     QString arg(argv[i]);
00622     if (!arg.compare("-q", Qt::CaseInsensitive))
00623       quiet = true;
00624     else if (!arg.compare("-i", Qt::CaseInsensitive) && ++i < argc)
00625       infile = argv[i];
00626     else if (!arg.compare("-o", Qt::CaseInsensitive) && ++i < argc)
00627       outfile = argv[i];
00628     else if (infile && outfile) {
00629       commandargs.append(arg);
00630     }
00631   }
00632   if ( !infile || !outfile || !commandargs.count() ) {
00633     print_usage_and_exit();
00634   }
00635 
00636   /* Open the source document for reading. */
00637   QFile srcFile(infile);
00638   QTextStream sfiletxt(&srcFile);
00639   sfiletxt.setCodec(codec);
00640   if (!srcFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
00641     error << QString("Unable to open '%1' for reading: %2\n").arg(infile)
00642                                                 .arg(srcFile.errorString());
00643     return 2;
00644   }
00645 
00646   /* Make sure the outfile does not exist before we use it. */
00647   if (QFile::exists(outfile)) {
00648     if (!QFile::remove(outfile)) {
00649       error << QString("Unable to truncate outfile '%1'\n").arg(outfile);
00650       return 2;
00651     }
00652   }
00653 
00654   QDomDocument doc;
00655   QString parseError;
00656   int  badline, badcol;
00657   if (!doc.setContent (sfiletxt.readAll(), false, &parseError, &badline, &badcol)) {
00658     error << QString("Error parsing source document '%1' at line %2 and column %3: %4")
00659                     .arg(infile).arg(badline).arg(badcol).arg(parseError);
00660     return 3;
00661   }
00662 
00663   if (!command.compare("userlocal", Qt::CaseInsensitive)) {
00664     if (!docuserlocal(&doc, commandargs[0], &errorMessage)) {
00665       error << QString("Unable to convert document components to user local: %1\n")
00666                           .arg(errorMessage);
00667       return 4;
00668     }
00669   }
00670   else {
00671     for (int i = 0; i < commandargs.count(); i++) {
00672       if (!command.compare("splice", Qt::CaseInsensitive)) {
00673         if (!docsplice(&doc, commandargs[i], &errorMessage)) {
00674           error << QString("Unable to process splice command '%1': %2\n")
00675                           .arg(commandargs[i]).arg(errorMessage);
00676           return 4;
00677         }
00678       }
00679       else if (!command.compare("replace", Qt::CaseInsensitive)) {
00680         if (!docreplace(&doc, commandargs[i], &errorMessage)) {
00681           error << QString("Unable to process replace command '%1': %2\n")
00682                           .arg(commandargs[i]).arg(errorMessage);
00683           return 4;
00684         }
00685       }
00686       else if (!command.compare("add", Qt::CaseInsensitive)) {
00687         if (!docadd(&doc, commandargs[i], &errorMessage)) { 
00688           error << QString("Unable to process add command '%1': %2\n")
00689                           .arg(commandargs[i]).arg(errorMessage);
00690           return 4;
00691         }
00692       }
00693     }
00694   }
00695 
00696   /* Open the output file for writing. */
00697   QFile docFile(outfile);
00698   if (!docFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
00699     error << QString("Unable to open '%1' for writing: %2\n").arg(outfile)
00700                                                 .arg(docFile.errorString());
00701     return 5;
00702   }
00703 
00704   /* Write the .wxl output. */
00705   QTextStream out(&docFile);
00706   out << doc.toString(4);
00707 
00708   return 0;
00709 }
00710