static int
Parse_test()
{
    XLIFFFile* xf = XLIFFFile::Parse("testdata/xliff1.xliff");
    if (xf == NULL) {
        return 1;
    }

    set<StringResource> const& strings = xf->GetStringResources();

    if (false) {
        for (set<StringResource>::iterator it=strings.begin(); it!=strings.end(); it++) {
            const StringResource& str = *it;
            printf("STRING!!! id=%s index=%d value='%s' pos=%s file=%s version=%d(%s)\n",
                    str.id.c_str(), str.index,
                    str.value->ContentsToString(ANDROID_NAMESPACES).c_str(),
                    str.pos.ToString().c_str(), str.file.c_str(), str.version,
                    str.versionString.c_str());
        }
        printf("XML:[[%s]]\n", xf->ToString().c_str());
    }

    delete xf;
    return 0;
}
static int
do_pseudo(const string& infile, const string& outfile, bool expand)
{
    int err;

    XLIFFFile* xliff = XLIFFFile::Parse(infile);
    if (xliff == NULL) {
        return 1;
    }

    pseudolocalize_xliff(xliff, expand);

    err = write_to_file(outfile, xliff->ToString());

    delete xliff;

    return err;
}
XLIFFFile*
XLIFFFile::Parse(const string& filename)
{
    XLIFFFile* result = new XLIFFFile();

    XMLNode* root = NodeHandler::ParseFile(filename, XMLNode::PRETTY);
    if (root == NULL) {
        return NULL;
    }

    // <file>
    vector<XMLNode*> files = root->GetElementsByName(XLIFF_XMLNS, "file");
    for (size_t i=0; i<files.size(); i++) {
        XMLNode* file = files[i];

        string datatype = file->GetAttribute("", "datatype", "");
        string originalFile = file->GetAttribute("", "original", "");

        Configuration sourceConfig;
        sourceConfig.locale = file->GetAttribute("", "source-language", "");
        result->m_sourceConfig = sourceConfig;

        Configuration targetConfig;
        targetConfig.locale = file->GetAttribute("", "target-language", "");
        result->m_targetConfig = targetConfig;

        result->m_currentVersion = file->GetAttribute("", "build-num", "");
        result->m_oldVersion = "old";

        // <body>
        XMLNode* body = get_unique_node(file, XLIFF_XMLNS, "body", true);
        if (body == NULL) continue;

        // <trans-unit>
        vector<XMLNode*> transUnits = body->GetElementsByName(XLIFF_XMLNS, "trans-unit");
        for (size_t j=0; j<transUnits.size(); j++) {
            XMLNode* transUnit = transUnits[j];

            string rawID = transUnit->GetAttribute("", "id", "");
            if (rawID == "") {
                transUnit->Position().Error("<trans-unit> tag requires an id");
                continue;
            }
            string id;
            int index;

            if (!StringResource::ParseTypedID(rawID, &id, &index)) {
                transUnit->Position().Error("<trans-unit> has invalid id '%s'\n", rawID.c_str());
                continue;
            }

            // <source>
            XMLNode* source = get_unique_node(transUnit, XLIFF_XMLNS, "source", false);
            if (source != NULL) {
                XMLNode* node = source->Clone();
                node->SetPrettyRecursive(XMLNode::EXACT);
                result->AddStringResource(StringResource(source->Position(), originalFile,
                            sourceConfig, id, index, node, CURRENT_VERSION,
                            result->m_currentVersion));
            }

            // <target>
            XMLNode* target = get_unique_node(transUnit, XLIFF_XMLNS, "target", false);
            if (target != NULL) {
                XMLNode* node = target->Clone();
                node->SetPrettyRecursive(XMLNode::EXACT);
                result->AddStringResource(StringResource(target->Position(), originalFile,
                            targetConfig, id, index, node, CURRENT_VERSION,
                            result->m_currentVersion));
            }

            // <alt-trans>
            XMLNode* altTrans = get_unique_node(transUnit, XLIFF_XMLNS, "alt-trans", false);
            if (altTrans != NULL) {
                // <source>
                XMLNode* altSource = get_unique_node(altTrans, XLIFF_XMLNS, "source", false);
                if (altSource != NULL) {
                    XMLNode* node = altSource->Clone();
                    node->SetPrettyRecursive(XMLNode::EXACT);
                    result->AddStringResource(StringResource(altSource->Position(),
                                originalFile, sourceConfig, id, index, node, OLD_VERSION,
                                result->m_oldVersion));
                }

                // <target>
                XMLNode* altTarget = get_unique_node(altTrans, XLIFF_XMLNS, "target", false);
                if (altTarget != NULL) {
                    XMLNode* node = altTarget->Clone();
                    node->SetPrettyRecursive(XMLNode::EXACT);
                    result->AddStringResource(StringResource(altTarget->Position(),
                                originalFile, targetConfig, id, index, node, OLD_VERSION,
                                result->m_oldVersion));
                }
            }
        }
    }
    delete root;
    return result;
}
static int
do_export(const string& settingsFile, const string& rootDir, const string& outDir,
          const string& targetLocale, const vector<string>& configs)
{
    bool success = true;
    int err;

    if (false) {
        printf("settingsFile=%s\n", settingsFile.c_str());
        printf("rootDir=%s\n", rootDir.c_str());
        printf("outDir=%s\n", outDir.c_str());
        for (size_t i=0; i<configs.size(); i++) {
            printf("config[%zd]=%s\n", i, configs[i].c_str());
        }
    }

    map<string,Settings> settings;
    err = read_settings(settingsFile, &settings, rootDir);
    if (err != 0) {
        return err;
    }

    err = validate_configs(settingsFile, settings, configs);
    if (err != 0) {
        return err;
    }

    vector<vector<string> > allResFiles;
    err = select_files(&allResFiles, configs, settings, rootDir);
    if (err != 0) {
        return err;
    }

    size_t totalFileCount = 0;
    for (size_t i=0; i<allResFiles.size(); i++) {
        totalFileCount += allResFiles[i].size();
    }
    totalFileCount *= 3; // we try all 3 versions of the file

    size_t fileProgress = 0;
    vector<Stats> stats;
    vector<pair<string,XLIFFFile*> > xliffs;

    for (size_t i=0; i<configs.size(); i++) {
        const string& config = configs[i];
        const Settings& setting = settings[config];

        if (false) {
            fprintf(stderr, "Configuration: %s (%zd of %zd)\n", config.c_str(), i+1,
                    configs.size());
            fprintf(stderr, "  Old CL:     %s\n", setting.oldVersion.c_str());
            fprintf(stderr, "  Current CL: %s\n", setting.currentVersion.c_str());
        }

        Configuration english;
        english.locale = "en_US";
        Configuration translated;
        translated.locale = targetLocale;
        XLIFFFile* xliff = XLIFFFile::Create(english, translated, setting.currentVersion);

        const vector<string>& resFiles = allResFiles[i];
        const size_t J = resFiles.size();
        for (size_t j=0; j<J; j++) {
            string resFile = resFiles[j];

            // parse the files into a ValuesFile
            // pull out the strings and add them to the XLIFFFile

            // current file
            print_file_status(++fileProgress, totalFileCount);
            ValuesFile* currentFile = get_values_file(resFile, english, CURRENT_VERSION,
                                      setting.currentVersion, true);
            if (currentFile != NULL) {
                ValuesFile_to_XLIFFFile(currentFile, xliff, resFile);
                //printf("currentFile=[%s]\n", currentFile->ToString().c_str());
            } else {
                fprintf(stderr, "error reading file %s@%s\n", resFile.c_str(),
                        setting.currentVersion.c_str());
                success = false;
            }

            // old file
            print_file_status(++fileProgress, totalFileCount);
            ValuesFile* oldFile = get_values_file(resFile, english, OLD_VERSION,
                                                  setting.oldVersion, false);
            if (oldFile != NULL) {
                ValuesFile_to_XLIFFFile(oldFile, xliff, resFile);
                //printf("oldFile=[%s]\n", oldFile->ToString().c_str());
            }

            // translated version
            // (get the head of the tree for the most recent translation, but it's considered
            // the old one because the "current" one hasn't been made yet, and this goes into
            // the <alt-trans> tag if necessary
            print_file_status(++fileProgress, totalFileCount);
            string transFilename = translated_file_name(resFile, targetLocale);
            ValuesFile* transFile = get_values_file(transFilename, translated, OLD_VERSION,
                                                    setting.currentVersion, false);
            if (transFile != NULL) {
                ValuesFile_to_XLIFFFile(transFile, xliff, resFile);
            }

            delete currentFile;
            delete oldFile;
            delete transFile;
        }

        Stats beforeFilterStats = xliff->GetStats(config);

        // run through the XLIFFFile and strip out TransUnits that have identical
        // old and current source values and are not in the reject list, or just
        // old values and no source values
        xliff->Filter(keep_this_trans_unit, (void*)&setting);

        Stats afterFilterStats = xliff->GetStats(config);
        afterFilterStats.totalStrings = beforeFilterStats.totalStrings;

        // add the reject comments
        for (vector<Reject>::const_iterator reject = setting.reject.begin();
                reject != setting.reject.end(); reject++) {
            TransUnit* tu = xliff->EditTransUnit(reject->file, reject->name);
            tu->rejectComment = reject->comment;
        }

        // config-locale-current_cl.xliff
        stringstream filename;
        if (outDir != "") {
            filename << outDir << '/';
        }
        filename << config << '-' << targetLocale << '-' << setting.currentVersion << ".xliff";
        xliffs.push_back(pair<string,XLIFFFile*>(filename.str(), xliff));

        stats.push_back(afterFilterStats);
    }

    // today is a good day to die
    if (!success || SourcePos::HasErrors()) {
        return 1;
    }

    // write the XLIFF files
    printf("\nWriting %zd file%s...\n", xliffs.size(), xliffs.size() == 1 ? "" : "s");
    for (vector<pair<string,XLIFFFile*> >::iterator it = xliffs.begin(); it != xliffs.end(); it++) {
        const string& filename = it->first;
        XLIFFFile* xliff = it->second;
        string text = xliff->ToString();
        write_to_file(filename, text);
    }

    // the stats
    printf("\n"
           "                                  to          without     total\n"
           " config               files       translate   comments    strings\n"
           "-----------------------------------------------------------------------\n");
    Stats totals;
    totals.config = "total";
    totals.files = 0;
    totals.toBeTranslated = 0;
    totals.noComments = 0;
    totals.totalStrings = 0;
    for (vector<Stats>::iterator it=stats.begin(); it!=stats.end(); it++) {
        string cfg = it->config;
        if (cfg.length() > 20) {
            cfg.resize(20);
        }
        printf(" %-20s  %-9zd   %-9zd   %-9zd   %-19zd\n", cfg.c_str(), it->files,
               it->toBeTranslated, it->noComments, it->totalStrings);
        totals.files += it->files;
        totals.toBeTranslated += it->toBeTranslated;
        totals.noComments += it->noComments;
        totals.totalStrings += it->totalStrings;
    }
    if (stats.size() > 1) {
        printf("-----------------------------------------------------------------------\n"
               " %-20s  %-9zd   %-9zd   %-9zd   %-19zd\n", totals.config.c_str(), totals.files,
               totals.toBeTranslated, totals.noComments, totals.totalStrings);
    }
    printf("\n");
    return 0;
}