XMLNode.cpp revision f8aea99385df8373b6edd6d5dd1d15b7b36b525b
168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)//
268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)// Copyright 2006 The Android Open Source Project
368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)//
468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)// Build resource files from raw assets.
568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)//
668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)#include "XMLNode.h"
868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)#include "ResourceTable.h"
968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
1068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)#include <host/pseudolocalize.h>
1168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)#include <utils/ByteOrder.h>
1268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)#include <errno.h>
1368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)#include <string.h>
1468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
1568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)#ifndef HAVE_MS_C_RUNTIME
1668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)#define O_BINARY 0
1768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)#endif
1868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
1968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)#define NOISY(x) //x
2068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)#define NOISY_PARSE(x) //x
2168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
2268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)const char* const RESOURCES_ROOT_NAMESPACE = "http://schemas.android.com/apk/res/";
2368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)const char* const RESOURCES_ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android";
2468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)const char* const RESOURCES_ROOT_PRV_NAMESPACE = "http://schemas.android.com/apk/prv/res/";
2568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
2668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)const char* const XLIFF_XMLNS = "urn:oasis:names:tc:xliff:document:1.2";
2768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)const char* const ALLOWED_XLIFF_ELEMENTS[] = {
2868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        "bpt",
2968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        "ept",
3068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        "it",
3168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        "ph",
3268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        "g",
3368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        "bx",
3468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        "ex",
3568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        "x"
3668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    };
3768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
3868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)bool isWhitespace(const char16_t* str)
3968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles){
4068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    while (*str != 0 && *str < 128 && isspace(*str)) {
4168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        str++;
4268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    }
4368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    return *str == 0;
4468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)}
4568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
4668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)static const String16 RESOURCES_PREFIX(RESOURCES_ROOT_NAMESPACE);
4768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)static const String16 RESOURCES_PRV_PREFIX(RESOURCES_ROOT_PRV_NAMESPACE);
4868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)static const String16 RESOURCES_TOOLS_NAMESPACE("http://schemas.android.com/tools");
4968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
5068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)String16 getNamespaceResourcePackage(String16 namespaceUri, bool* outIsPublic)
5168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles){
5268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    //printf("%s starts with %s?\n", String8(namespaceUri).string(),
5368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    //       String8(RESOURCES_PREFIX).string());
5468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    size_t prefixSize;
5568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    bool isPublic = true;
5668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    if (namespaceUri.startsWith(RESOURCES_PREFIX)) {
5768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        prefixSize = RESOURCES_PREFIX.size();
5868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    } else if (namespaceUri.startsWith(RESOURCES_PRV_PREFIX)) {
5968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        isPublic = false;
6068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        prefixSize = RESOURCES_PRV_PREFIX.size();
6168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    } else {
6268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        if (outIsPublic) *outIsPublic = isPublic; // = true
6368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        return String16();
6468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    }
6568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
6668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    //printf("YES!\n");
6768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    //printf("namespace: %s\n", String8(String16(namespaceUri, namespaceUri.size()-prefixSize, prefixSize)).string());
6868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    if (outIsPublic) *outIsPublic = isPublic;
6968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    return String16(namespaceUri, namespaceUri.size()-prefixSize, prefixSize);
7068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)}
7168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
7268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)status_t hasSubstitutionErrors(const char* fileName,
7368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                               ResXMLTree* inXml,
7468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                               String16 str16)
7568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles){
7668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    const char16_t* str = str16.string();
7768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    const char16_t* p = str;
7868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    const char16_t* end = str + str16.size();
7968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
8068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    bool nonpositional = false;
8168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    int argCount = 0;
8268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
8368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    while (p < end) {
8468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        /*
8568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)         * Look for the start of a Java-style substitution sequence.
8668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)         */
8768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        if (*p == '%' && p + 1 < end) {
8868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)            p++;
8968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
9068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)            // A literal percent sign represented by %%
9168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)            if (*p == '%') {
9268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                p++;
9368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                continue;
9468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)            }
9568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
9668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)            argCount++;
9768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
9868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)            if (*p >= '0' && *p <= '9') {
9968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                do {
10068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                    p++;
10168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                } while (*p >= '0' && *p <= '9');
10268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                if (*p != '$') {
10368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                    // This must be a size specification instead of position.
10468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                    nonpositional = true;
10568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                }
10668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)            } else if (*p == '<') {
10768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                // Reusing last argument; bad idea since it can be re-arranged.
10868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                nonpositional = true;
10968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                p++;
11068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
11168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                // Optionally '$' can be specified at the end.
11268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                if (p < end && *p == '$') {
11368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                    p++;
11468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                }
11568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)            } else {
11668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                nonpositional = true;
11768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)            }
11868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
11968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)            // Ignore flags and widths
12068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)            while (p < end && (*p == '-' ||
12168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                    *p == '#' ||
12268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                    *p == '+' ||
12368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                    *p == ' ' ||
12468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                    *p == ',' ||
12568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                    *p == '(' ||
12668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                    (*p >= '0' && *p <= '9'))) {
12768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                p++;
12868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)            }
12968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
13068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)            /*
13168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)             * This is a shortcut to detect strings that are going to Time.format()
13268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)             * instead of String.format()
13368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)             *
13468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)             * Comparison of String.format() and Time.format() args:
13568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)             *
13668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)             * String: ABC E GH  ST X abcdefgh  nost x
13768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)             *   Time:    DEFGHKMS W Za  d   hkm  s w yz
13868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)             *
13968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)             * Therefore we know it's definitely Time if we have:
14068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)             *     DFKMWZkmwyz
14168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)             */
14268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)            if (p < end) {
14368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                switch (*p) {
14468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                case 'D':
14568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                case 'F':
14668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                case 'K':
14768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                case 'M':
14868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                case 'W':
14968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                case 'Z':
15068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                case 'k':
15168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                case 'm':
15268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                case 'w':
15368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                case 'y':
15468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                case 'z':
15568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                    return NO_ERROR;
15668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                }
15768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)            }
15868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        }
15968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
16068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        p++;
16168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    }
16268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
16368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    /*
16468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)     * If we have more than one substitution in this string and any of them
16568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)     * are not in positional form, give the user an error.
16668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)     */
16768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    if (argCount > 1 && nonpositional) {
16868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        SourcePos(String8(fileName), inXml->getLineNumber()).error(
16968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                "Multiple substitutions specified in non-positional format; "
17068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                "did you mean to add the formatted=\"false\" attribute?\n");
17168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        return NOT_ENOUGH_DATA;
17268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    }
17368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
17468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    return NO_ERROR;
17568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)}
17668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
17768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)status_t parseStyledString(Bundle* bundle,
17868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                           const char* fileName,
17968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                           ResXMLTree* inXml,
18068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                           const String16& endTag,
18168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                           String16* outString,
18268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                           Vector<StringPool::entry_style_span>* outSpans,
18368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                           bool isFormatted,
18468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)                           bool pseudolocalize)
18568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles){
18668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    Vector<StringPool::entry_style_span> spanStack;
18768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    String16 curString;
18868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    String16 rawString;
18968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    const char* errorMsg;
1904e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    int xliffDepth = 0;
1915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    bool firstTime = true;
1924e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1934e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    size_t len;
1944e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    ResXMLTree::event_code_t code;
1954e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    while ((code=inXml->next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
1964e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1974e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        if (code == ResXMLTree::TEXT) {
1984e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)            String16 text(inXml->getText(&len));
1994e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)            if (firstTime && text.size() > 0) {
2004e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                firstTime = false;
2014e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                if (text.string()[0] == '@') {
2025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    // If this is a resource reference, don't do the pseudoloc.
2035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    pseudolocalize = false;
2045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                }
2055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            }
2065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            if (xliffDepth == 0 && pseudolocalize) {
2075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                std::string orig(String8(text).string());
2085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                std::string pseudo = pseudolocalize_string(orig);
2095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                curString.append(String16(String8(pseudo.c_str())));
2105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            } else {
2115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                if (isFormatted && hasSubstitutionErrors(fileName, inXml, text) != NO_ERROR) {
2125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    return UNKNOWN_ERROR;
2135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                } else {
2145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    curString.append(text);
2155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                }
2165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            }
2175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        } else if (code == ResXMLTree::START_TAG) {
218            const String16 element16(inXml->getElementName(&len));
219            const String8 element8(element16);
220
221            size_t nslen;
222            const uint16_t* ns = inXml->getElementNamespace(&nslen);
223            if (ns == NULL) {
224                ns = (const uint16_t*)"\0\0";
225                nslen = 0;
226            }
227            const String8 nspace(String16(ns, nslen));
228            if (nspace == XLIFF_XMLNS) {
229                const int N = sizeof(ALLOWED_XLIFF_ELEMENTS)/sizeof(ALLOWED_XLIFF_ELEMENTS[0]);
230                for (int i=0; i<N; i++) {
231                    if (element8 == ALLOWED_XLIFF_ELEMENTS[i]) {
232                        xliffDepth++;
233                        // in this case, treat it like it was just text, in other words, do nothing
234                        // here and silently drop this element
235                        goto moveon;
236                    }
237                }
238                {
239                    SourcePos(String8(fileName), inXml->getLineNumber()).error(
240                            "Found unsupported XLIFF tag <%s>\n",
241                            element8.string());
242                    return UNKNOWN_ERROR;
243                }
244moveon:
245                continue;
246            }
247
248            if (outSpans == NULL) {
249                SourcePos(String8(fileName), inXml->getLineNumber()).error(
250                        "Found style tag <%s> where styles are not allowed\n", element8.string());
251                return UNKNOWN_ERROR;
252            }
253
254            if (!ResTable::collectString(outString, curString.string(),
255                                         curString.size(), false, &errorMsg, true)) {
256                SourcePos(String8(fileName), inXml->getLineNumber()).error("%s (in %s)\n",
257                        errorMsg, String8(curString).string());
258                return UNKNOWN_ERROR;
259            }
260            rawString.append(curString);
261            curString = String16();
262
263            StringPool::entry_style_span span;
264            span.name = element16;
265            for (size_t ai=0; ai<inXml->getAttributeCount(); ai++) {
266                span.name.append(String16(";"));
267                const char16_t* str = inXml->getAttributeName(ai, &len);
268                span.name.append(str, len);
269                span.name.append(String16("="));
270                str = inXml->getAttributeStringValue(ai, &len);
271                span.name.append(str, len);
272            }
273            //printf("Span: %s\n", String8(span.name).string());
274            span.span.firstChar = span.span.lastChar = outString->size();
275            spanStack.push(span);
276
277        } else if (code == ResXMLTree::END_TAG) {
278            size_t nslen;
279            const uint16_t* ns = inXml->getElementNamespace(&nslen);
280            if (ns == NULL) {
281                ns = (const uint16_t*)"\0\0";
282                nslen = 0;
283            }
284            const String8 nspace(String16(ns, nslen));
285            if (nspace == XLIFF_XMLNS) {
286                xliffDepth--;
287                continue;
288            }
289            if (!ResTable::collectString(outString, curString.string(),
290                                         curString.size(), false, &errorMsg, true)) {
291                SourcePos(String8(fileName), inXml->getLineNumber()).error("%s (in %s)\n",
292                        errorMsg, String8(curString).string());
293                return UNKNOWN_ERROR;
294            }
295            rawString.append(curString);
296            curString = String16();
297
298            if (spanStack.size() == 0) {
299                if (strcmp16(inXml->getElementName(&len), endTag.string()) != 0) {
300                    SourcePos(String8(fileName), inXml->getLineNumber()).error(
301                            "Found tag %s where <%s> close is expected\n",
302                            String8(inXml->getElementName(&len)).string(),
303                            String8(endTag).string());
304                    return UNKNOWN_ERROR;
305                }
306                break;
307            }
308            StringPool::entry_style_span span = spanStack.top();
309            String16 spanTag;
310            ssize_t semi = span.name.findFirst(';');
311            if (semi >= 0) {
312                spanTag.setTo(span.name.string(), semi);
313            } else {
314                spanTag.setTo(span.name);
315            }
316            if (strcmp16(inXml->getElementName(&len), spanTag.string()) != 0) {
317                SourcePos(String8(fileName), inXml->getLineNumber()).error(
318                        "Found close tag %s where close tag %s is expected\n",
319                        String8(inXml->getElementName(&len)).string(),
320                        String8(spanTag).string());
321                return UNKNOWN_ERROR;
322            }
323            bool empty = true;
324            if (outString->size() > 0) {
325                span.span.lastChar = outString->size()-1;
326                if (span.span.lastChar >= span.span.firstChar) {
327                    empty = false;
328                    outSpans->add(span);
329                }
330            }
331            spanStack.pop();
332
333            /*
334             * This warning seems to be just an irritation to most people,
335             * since it is typically introduced by translators who then never
336             * see the warning.
337             */
338            if (0 && empty) {
339                fprintf(stderr, "%s:%d: warning: empty '%s' span found in text '%s'\n",
340                        fileName, inXml->getLineNumber(),
341                        String8(spanTag).string(), String8(*outString).string());
342
343            }
344        } else if (code == ResXMLTree::START_NAMESPACE) {
345            // nothing
346        }
347    }
348
349    if (code == ResXMLTree::BAD_DOCUMENT) {
350            SourcePos(String8(fileName), inXml->getLineNumber()).error(
351                    "Error parsing XML\n");
352    }
353
354    if (outSpans != NULL && outSpans->size() > 0) {
355        if (curString.size() > 0) {
356            if (!ResTable::collectString(outString, curString.string(),
357                                         curString.size(), false, &errorMsg, true)) {
358                SourcePos(String8(fileName), inXml->getLineNumber()).error(
359                        "%s (in %s)\n",
360                        errorMsg, String8(curString).string());
361                return UNKNOWN_ERROR;
362            }
363        }
364    } else {
365        // There is no style information, so string processing will happen
366        // later as part of the overall type conversion.  Return to the
367        // client the raw unprocessed text.
368        rawString.append(curString);
369        outString->setTo(rawString);
370    }
371
372    return NO_ERROR;
373}
374
375struct namespace_entry {
376    String8 prefix;
377    String8 uri;
378};
379
380static String8 make_prefix(int depth)
381{
382    String8 prefix;
383    int i;
384    for (i=0; i<depth; i++) {
385        prefix.append("  ");
386    }
387    return prefix;
388}
389
390static String8 build_namespace(const Vector<namespace_entry>& namespaces,
391        const uint16_t* ns)
392{
393    String8 str;
394    if (ns != NULL) {
395        str = String8(ns);
396        const size_t N = namespaces.size();
397        for (size_t i=0; i<N; i++) {
398            const namespace_entry& ne = namespaces.itemAt(i);
399            if (ne.uri == str) {
400                str = ne.prefix;
401                break;
402            }
403        }
404        str.append(":");
405    }
406    return str;
407}
408
409void printXMLBlock(ResXMLTree* block)
410{
411    block->restart();
412
413    Vector<namespace_entry> namespaces;
414
415    ResXMLTree::event_code_t code;
416    int depth = 0;
417    while ((code=block->next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
418        String8 prefix = make_prefix(depth);
419        int i;
420        if (code == ResXMLTree::START_TAG) {
421            size_t len;
422            const uint16_t* ns16 = block->getElementNamespace(&len);
423            String8 elemNs = build_namespace(namespaces, ns16);
424            const uint16_t* com16 = block->getComment(&len);
425            if (com16) {
426                printf("%s <!-- %s -->\n", prefix.string(), String8(com16).string());
427            }
428            printf("%sE: %s%s (line=%d)\n", prefix.string(), elemNs.string(),
429                   String8(block->getElementName(&len)).string(),
430                   block->getLineNumber());
431            int N = block->getAttributeCount();
432            depth++;
433            prefix = make_prefix(depth);
434            for (i=0; i<N; i++) {
435                uint32_t res = block->getAttributeNameResID(i);
436                ns16 = block->getAttributeNamespace(i, &len);
437                String8 ns = build_namespace(namespaces, ns16);
438                String8 name(block->getAttributeName(i, &len));
439                printf("%sA: ", prefix.string());
440                if (res) {
441                    printf("%s%s(0x%08x)", ns.string(), name.string(), res);
442                } else {
443                    printf("%s%s", ns.string(), name.string());
444                }
445                Res_value value;
446                block->getAttributeValue(i, &value);
447                if (value.dataType == Res_value::TYPE_NULL) {
448                    printf("=(null)");
449                } else if (value.dataType == Res_value::TYPE_REFERENCE) {
450                    printf("=@0x%x", (int)value.data);
451                } else if (value.dataType == Res_value::TYPE_ATTRIBUTE) {
452                    printf("=?0x%x", (int)value.data);
453                } else if (value.dataType == Res_value::TYPE_STRING) {
454                    printf("=\"%s\"",
455                            ResTable::normalizeForOutput(String8(block->getAttributeStringValue(i,
456                                        &len)).string()).string());
457                } else {
458                    printf("=(type 0x%x)0x%x", (int)value.dataType, (int)value.data);
459                }
460                const char16_t* val = block->getAttributeStringValue(i, &len);
461                if (val != NULL) {
462                    printf(" (Raw: \"%s\")", ResTable::normalizeForOutput(String8(val).string()).
463                            string());
464                }
465                printf("\n");
466            }
467        } else if (code == ResXMLTree::END_TAG) {
468            depth--;
469        } else if (code == ResXMLTree::START_NAMESPACE) {
470            namespace_entry ns;
471            size_t len;
472            const uint16_t* prefix16 = block->getNamespacePrefix(&len);
473            if (prefix16) {
474                ns.prefix = String8(prefix16);
475            } else {
476                ns.prefix = "<DEF>";
477            }
478            ns.uri = String8(block->getNamespaceUri(&len));
479            namespaces.push(ns);
480            printf("%sN: %s=%s\n", prefix.string(), ns.prefix.string(),
481                    ns.uri.string());
482            depth++;
483        } else if (code == ResXMLTree::END_NAMESPACE) {
484            depth--;
485            const namespace_entry& ns = namespaces.top();
486            size_t len;
487            const uint16_t* prefix16 = block->getNamespacePrefix(&len);
488            String8 pr;
489            if (prefix16) {
490                pr = String8(prefix16);
491            } else {
492                pr = "<DEF>";
493            }
494            if (ns.prefix != pr) {
495                prefix = make_prefix(depth);
496                printf("%s*** BAD END NS PREFIX: found=%s, expected=%s\n",
497                        prefix.string(), pr.string(), ns.prefix.string());
498            }
499            String8 uri = String8(block->getNamespaceUri(&len));
500            if (ns.uri != uri) {
501                prefix = make_prefix(depth);
502                printf("%s *** BAD END NS URI: found=%s, expected=%s\n",
503                        prefix.string(), uri.string(), ns.uri.string());
504            }
505            namespaces.pop();
506        } else if (code == ResXMLTree::TEXT) {
507            size_t len;
508            printf("%sC: \"%s\"\n", prefix.string(), String8(block->getText(&len)).string());
509        }
510    }
511
512    block->restart();
513}
514
515status_t parseXMLResource(const sp<AaptFile>& file, ResXMLTree* outTree,
516                          bool stripAll, bool keepComments,
517                          const char** cDataTags)
518{
519    sp<XMLNode> root = XMLNode::parse(file);
520    if (root == NULL) {
521        return UNKNOWN_ERROR;
522    }
523    root->removeWhitespace(stripAll, cDataTags);
524
525    NOISY(printf("Input XML from %s:\n", (const char*)file->getPrintableSource()));
526    NOISY(root->print());
527    sp<AaptFile> rsc = new AaptFile(String8(), AaptGroupEntry(), String8());
528    status_t err = root->flatten(rsc, !keepComments, false);
529    if (err != NO_ERROR) {
530        return err;
531    }
532    err = outTree->setTo(rsc->getData(), rsc->getSize(), true);
533    if (err != NO_ERROR) {
534        return err;
535    }
536
537    NOISY(printf("Output XML:\n"));
538    NOISY(printXMLBlock(outTree));
539
540    return NO_ERROR;
541}
542
543sp<XMLNode> XMLNode::parse(const sp<AaptFile>& file)
544{
545    char buf[16384];
546    int fd = open(file->getSourceFile().string(), O_RDONLY | O_BINARY);
547    if (fd < 0) {
548        SourcePos(file->getSourceFile(), -1).error("Unable to open file for read: %s",
549                strerror(errno));
550        return NULL;
551    }
552
553    XML_Parser parser = XML_ParserCreateNS(NULL, 1);
554    ParseState state;
555    state.filename = file->getPrintableSource();
556    state.parser = parser;
557    XML_SetUserData(parser, &state);
558    XML_SetElementHandler(parser, startElement, endElement);
559    XML_SetNamespaceDeclHandler(parser, startNamespace, endNamespace);
560    XML_SetCharacterDataHandler(parser, characterData);
561    XML_SetCommentHandler(parser, commentData);
562
563    ssize_t len;
564    bool done;
565    do {
566        len = read(fd, buf, sizeof(buf));
567        done = len < (ssize_t)sizeof(buf);
568        if (len < 0) {
569            SourcePos(file->getSourceFile(), -1).error("Error reading file: %s\n", strerror(errno));
570            close(fd);
571            return NULL;
572        }
573        if (XML_Parse(parser, buf, len, done) == XML_STATUS_ERROR) {
574            SourcePos(file->getSourceFile(), (int)XML_GetCurrentLineNumber(parser)).error(
575                    "Error parsing XML: %s\n", XML_ErrorString(XML_GetErrorCode(parser)));
576            close(fd);
577            return NULL;
578        }
579    } while (!done);
580
581    XML_ParserFree(parser);
582    if (state.root == NULL) {
583        SourcePos(file->getSourceFile(), -1).error("No XML data generated when parsing");
584    }
585    close(fd);
586    return state.root;
587}
588
589XMLNode::XMLNode(const String8& filename, const String16& s1, const String16& s2, bool isNamespace)
590    : mNextAttributeIndex(0x80000000)
591    , mFilename(filename)
592    , mStartLineNumber(0)
593    , mEndLineNumber(0)
594    , mUTF8(false)
595{
596    if (isNamespace) {
597        mNamespacePrefix = s1;
598        mNamespaceUri = s2;
599    } else {
600        mNamespaceUri = s1;
601        mElementName = s2;
602    }
603}
604
605XMLNode::XMLNode(const String8& filename)
606    : mFilename(filename)
607{
608    memset(&mCharsValue, 0, sizeof(mCharsValue));
609}
610
611XMLNode::type XMLNode::getType() const
612{
613    if (mElementName.size() != 0) {
614        return TYPE_ELEMENT;
615    }
616    if (mNamespaceUri.size() != 0) {
617        return TYPE_NAMESPACE;
618    }
619    return TYPE_CDATA;
620}
621
622const String16& XMLNode::getNamespacePrefix() const
623{
624    return mNamespacePrefix;
625}
626
627const String16& XMLNode::getNamespaceUri() const
628{
629    return mNamespaceUri;
630}
631
632const String16& XMLNode::getElementNamespace() const
633{
634    return mNamespaceUri;
635}
636
637const String16& XMLNode::getElementName() const
638{
639    return mElementName;
640}
641
642const Vector<sp<XMLNode> >& XMLNode::getChildren() const
643{
644    return mChildren;
645}
646
647const String8& XMLNode::getFilename() const
648{
649    return mFilename;
650}
651
652const Vector<XMLNode::attribute_entry>&
653    XMLNode::getAttributes() const
654{
655    return mAttributes;
656}
657
658const XMLNode::attribute_entry* XMLNode::getAttribute(const String16& ns,
659        const String16& name) const
660{
661    for (size_t i=0; i<mAttributes.size(); i++) {
662        const attribute_entry& ae(mAttributes.itemAt(i));
663        if (ae.ns == ns && ae.name == name) {
664            return &ae;
665        }
666    }
667
668    return NULL;
669}
670
671XMLNode::attribute_entry* XMLNode::editAttribute(const String16& ns,
672        const String16& name)
673{
674    for (size_t i=0; i<mAttributes.size(); i++) {
675        attribute_entry * ae = &mAttributes.editItemAt(i);
676        if (ae->ns == ns && ae->name == name) {
677            return ae;
678        }
679    }
680
681    return NULL;
682}
683
684const String16& XMLNode::getCData() const
685{
686    return mChars;
687}
688
689const String16& XMLNode::getComment() const
690{
691    return mComment;
692}
693
694int32_t XMLNode::getStartLineNumber() const
695{
696    return mStartLineNumber;
697}
698
699int32_t XMLNode::getEndLineNumber() const
700{
701    return mEndLineNumber;
702}
703
704sp<XMLNode> XMLNode::searchElement(const String16& tagNamespace, const String16& tagName)
705{
706    if (getType() == XMLNode::TYPE_ELEMENT
707            && mNamespaceUri == tagNamespace
708            && mElementName == tagName) {
709        return this;
710    }
711
712    for (size_t i=0; i<mChildren.size(); i++) {
713        sp<XMLNode> found = mChildren.itemAt(i)->searchElement(tagNamespace, tagName);
714        if (found != NULL) {
715            return found;
716        }
717    }
718
719    return NULL;
720}
721
722sp<XMLNode> XMLNode::getChildElement(const String16& tagNamespace, const String16& tagName)
723{
724    for (size_t i=0; i<mChildren.size(); i++) {
725        sp<XMLNode> child = mChildren.itemAt(i);
726        if (child->getType() == XMLNode::TYPE_ELEMENT
727                && child->mNamespaceUri == tagNamespace
728                && child->mElementName == tagName) {
729            return child;
730        }
731    }
732
733    return NULL;
734}
735
736status_t XMLNode::addChild(const sp<XMLNode>& child)
737{
738    if (getType() == TYPE_CDATA) {
739        SourcePos(mFilename, child->getStartLineNumber()).error("Child to CDATA node.");
740        return UNKNOWN_ERROR;
741    }
742    //printf("Adding child %p to parent %p\n", child.get(), this);
743    mChildren.add(child);
744    return NO_ERROR;
745}
746
747status_t XMLNode::insertChildAt(const sp<XMLNode>& child, size_t index)
748{
749    if (getType() == TYPE_CDATA) {
750        SourcePos(mFilename, child->getStartLineNumber()).error("Child to CDATA node.");
751        return UNKNOWN_ERROR;
752    }
753    //printf("Adding child %p to parent %p\n", child.get(), this);
754    mChildren.insertAt(child, index);
755    return NO_ERROR;
756}
757
758status_t XMLNode::addAttribute(const String16& ns, const String16& name,
759                               const String16& value)
760{
761    if (getType() == TYPE_CDATA) {
762        SourcePos(mFilename, getStartLineNumber()).error("Child to CDATA node.");
763        return UNKNOWN_ERROR;
764    }
765
766    if (ns != RESOURCES_TOOLS_NAMESPACE) {
767        attribute_entry e;
768        e.index = mNextAttributeIndex++;
769        e.ns = ns;
770        e.name = name;
771        e.string = value;
772        mAttributes.add(e);
773        mAttributeOrder.add(e.index, mAttributes.size()-1);
774    }
775    return NO_ERROR;
776}
777
778void XMLNode::setAttributeResID(size_t attrIdx, uint32_t resId)
779{
780    attribute_entry& e = mAttributes.editItemAt(attrIdx);
781    if (e.nameResId) {
782        mAttributeOrder.removeItem(e.nameResId);
783    } else {
784        mAttributeOrder.removeItem(e.index);
785    }
786    NOISY(printf("Elem %s %s=\"%s\": set res id = 0x%08x\n",
787            String8(getElementName()).string(),
788            String8(mAttributes.itemAt(attrIdx).name).string(),
789            String8(mAttributes.itemAt(attrIdx).string).string(),
790            resId));
791    mAttributes.editItemAt(attrIdx).nameResId = resId;
792    mAttributeOrder.add(resId, attrIdx);
793}
794
795status_t XMLNode::appendChars(const String16& chars)
796{
797    if (getType() != TYPE_CDATA) {
798        SourcePos(mFilename, getStartLineNumber()).error("Adding characters to element node.");
799        return UNKNOWN_ERROR;
800    }
801    mChars.append(chars);
802    return NO_ERROR;
803}
804
805status_t XMLNode::appendComment(const String16& comment)
806{
807    if (mComment.size() > 0) {
808        mComment.append(String16("\n"));
809    }
810    mComment.append(comment);
811    return NO_ERROR;
812}
813
814void XMLNode::setStartLineNumber(int32_t line)
815{
816    mStartLineNumber = line;
817}
818
819void XMLNode::setEndLineNumber(int32_t line)
820{
821    mEndLineNumber = line;
822}
823
824void XMLNode::removeWhitespace(bool stripAll, const char** cDataTags)
825{
826    //printf("Removing whitespace in %s\n", String8(mElementName).string());
827    size_t N = mChildren.size();
828    if (cDataTags) {
829        String8 tag(mElementName);
830        const char** p = cDataTags;
831        while (*p) {
832            if (tag == *p) {
833                stripAll = false;
834                break;
835            }
836        }
837    }
838    for (size_t i=0; i<N; i++) {
839        sp<XMLNode> node = mChildren.itemAt(i);
840        if (node->getType() == TYPE_CDATA) {
841            // This is a CDATA node...
842            const char16_t* p = node->mChars.string();
843            while (*p != 0 && *p < 128 && isspace(*p)) {
844                p++;
845            }
846            //printf("Space ends at %d in \"%s\"\n",
847            //       (int)(p-node->mChars.string()),
848            //       String8(node->mChars).string());
849            if (*p == 0) {
850                if (stripAll) {
851                    // Remove this node!
852                    mChildren.removeAt(i);
853                    N--;
854                    i--;
855                } else {
856                    node->mChars = String16(" ");
857                }
858            } else {
859                // Compact leading/trailing whitespace.
860                const char16_t* e = node->mChars.string()+node->mChars.size()-1;
861                while (e > p && *e < 128 && isspace(*e)) {
862                    e--;
863                }
864                if (p > node->mChars.string()) {
865                    p--;
866                }
867                if (e < (node->mChars.string()+node->mChars.size()-1)) {
868                    e++;
869                }
870                if (p > node->mChars.string() ||
871                    e < (node->mChars.string()+node->mChars.size()-1)) {
872                    String16 tmp(p, e-p+1);
873                    node->mChars = tmp;
874                }
875            }
876        } else {
877            node->removeWhitespace(stripAll, cDataTags);
878        }
879    }
880}
881
882status_t XMLNode::parseValues(const sp<AaptAssets>& assets,
883                              ResourceTable* table)
884{
885    bool hasErrors = false;
886
887    if (getType() == TYPE_ELEMENT) {
888        const size_t N = mAttributes.size();
889        String16 defPackage(assets->getPackage());
890        for (size_t i=0; i<N; i++) {
891            attribute_entry& e = mAttributes.editItemAt(i);
892            AccessorCookie ac(SourcePos(mFilename, getStartLineNumber()), String8(e.name),
893                    String8(e.string));
894            table->setCurrentXmlPos(SourcePos(mFilename, getStartLineNumber()));
895            if (!assets->getIncludedResources()
896                    .stringToValue(&e.value, &e.string,
897                                  e.string.string(), e.string.size(), true, true,
898                                  e.nameResId, NULL, &defPackage, table, &ac)) {
899                hasErrors = true;
900            }
901            NOISY(printf("Attr %s: type=0x%x, str=%s\n",
902                   String8(e.name).string(), e.value.dataType,
903                   String8(e.string).string()));
904        }
905    }
906    const size_t N = mChildren.size();
907    for (size_t i=0; i<N; i++) {
908        status_t err = mChildren.itemAt(i)->parseValues(assets, table);
909        if (err != NO_ERROR) {
910            hasErrors = true;
911        }
912    }
913    return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
914}
915
916status_t XMLNode::assignResourceIds(const sp<AaptAssets>& assets,
917                                    const ResourceTable* table)
918{
919    bool hasErrors = false;
920
921    if (getType() == TYPE_ELEMENT) {
922        String16 attr("attr");
923        const char* errorMsg;
924        const size_t N = mAttributes.size();
925        for (size_t i=0; i<N; i++) {
926            const attribute_entry& e = mAttributes.itemAt(i);
927            if (e.ns.size() <= 0) continue;
928            bool nsIsPublic;
929            String16 pkg(getNamespaceResourcePackage(e.ns, &nsIsPublic));
930            NOISY(printf("Elem %s %s=\"%s\": namespace(%s) %s ===> %s\n",
931                    String8(getElementName()).string(),
932                    String8(e.name).string(),
933                    String8(e.string).string(),
934                    String8(e.ns).string(),
935                    (nsIsPublic) ? "public" : "private",
936                    String8(pkg).string()));
937            if (pkg.size() <= 0) continue;
938            uint32_t res = table != NULL
939                ? table->getResId(e.name, &attr, &pkg, &errorMsg, nsIsPublic)
940                : assets->getIncludedResources().
941                    identifierForName(e.name.string(), e.name.size(),
942                                      attr.string(), attr.size(),
943                                      pkg.string(), pkg.size());
944            if (res != 0) {
945                NOISY(printf("XML attribute name %s: resid=0x%08x\n",
946                             String8(e.name).string(), res));
947                setAttributeResID(i, res);
948            } else {
949                SourcePos(mFilename, getStartLineNumber()).error(
950                        "No resource identifier found for attribute '%s' in package '%s'\n",
951                        String8(e.name).string(), String8(pkg).string());
952                hasErrors = true;
953            }
954        }
955    }
956    const size_t N = mChildren.size();
957    for (size_t i=0; i<N; i++) {
958        status_t err = mChildren.itemAt(i)->assignResourceIds(assets, table);
959        if (err < NO_ERROR) {
960            hasErrors = true;
961        }
962    }
963
964    return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
965}
966
967status_t XMLNode::flatten(const sp<AaptFile>& dest,
968        bool stripComments, bool stripRawValues) const
969{
970    StringPool strings = StringPool(false, mUTF8);
971    Vector<uint32_t> resids;
972
973    // First collect just the strings for attribute names that have a
974    // resource ID assigned to them.  This ensures that the resource ID
975    // array is compact, and makes it easier to deal with attribute names
976    // in different namespaces (and thus with different resource IDs).
977    collect_resid_strings(&strings, &resids);
978
979    // Next collect all remainibng strings.
980    collect_strings(&strings, &resids, stripComments, stripRawValues);
981
982#if 0  // No longer compiles
983    NOISY(printf("Found strings:\n");
984        const size_t N = strings.size();
985        for (size_t i=0; i<N; i++) {
986            printf("%s\n", String8(strings.entryAt(i).string).string());
987        }
988    );
989#endif
990
991    sp<AaptFile> stringPool = strings.createStringBlock();
992    NOISY(aout << "String pool:"
993          << HexDump(stringPool->getData(), stringPool->getSize()) << endl);
994
995    ResXMLTree_header header;
996    memset(&header, 0, sizeof(header));
997    header.header.type = htods(RES_XML_TYPE);
998    header.header.headerSize = htods(sizeof(header));
999
1000    const size_t basePos = dest->getSize();
1001    dest->writeData(&header, sizeof(header));
1002    dest->writeData(stringPool->getData(), stringPool->getSize());
1003
1004    // If we have resource IDs, write them.
1005    if (resids.size() > 0) {
1006        const size_t resIdsPos = dest->getSize();
1007        const size_t resIdsSize =
1008            sizeof(ResChunk_header)+(sizeof(uint32_t)*resids.size());
1009        ResChunk_header* idsHeader = (ResChunk_header*)
1010            (((const uint8_t*)dest->editData(resIdsPos+resIdsSize))+resIdsPos);
1011        idsHeader->type = htods(RES_XML_RESOURCE_MAP_TYPE);
1012        idsHeader->headerSize = htods(sizeof(*idsHeader));
1013        idsHeader->size = htodl(resIdsSize);
1014        uint32_t* ids = (uint32_t*)(idsHeader+1);
1015        for (size_t i=0; i<resids.size(); i++) {
1016            *ids++ = htodl(resids[i]);
1017        }
1018    }
1019
1020    flatten_node(strings, dest, stripComments, stripRawValues);
1021
1022    void* data = dest->editData();
1023    ResXMLTree_header* hd = (ResXMLTree_header*)(((uint8_t*)data)+basePos);
1024    size_t size = dest->getSize()-basePos;
1025    hd->header.size = htodl(dest->getSize()-basePos);
1026
1027    NOISY(aout << "XML resource:"
1028          << HexDump(dest->getData(), dest->getSize()) << endl);
1029
1030    #if PRINT_STRING_METRICS
1031    fprintf(stderr, "**** total xml size: %d / %d%% strings (in %s)\n",
1032        dest->getSize(), (stringPool->getSize()*100)/dest->getSize(),
1033        dest->getPath().string());
1034    #endif
1035
1036    return NO_ERROR;
1037}
1038
1039void XMLNode::print(int indent)
1040{
1041    String8 prefix;
1042    int i;
1043    for (i=0; i<indent; i++) {
1044        prefix.append("  ");
1045    }
1046    if (getType() == TYPE_ELEMENT) {
1047        String8 elemNs(getNamespaceUri());
1048        if (elemNs.size() > 0) {
1049            elemNs.append(":");
1050        }
1051        printf("%s E: %s%s", prefix.string(),
1052               elemNs.string(), String8(getElementName()).string());
1053        int N = mAttributes.size();
1054        for (i=0; i<N; i++) {
1055            ssize_t idx = mAttributeOrder.valueAt(i);
1056            if (i == 0) {
1057                printf(" / ");
1058            } else {
1059                printf(", ");
1060            }
1061            const attribute_entry& attr = mAttributes.itemAt(idx);
1062            String8 attrNs(attr.ns);
1063            if (attrNs.size() > 0) {
1064                attrNs.append(":");
1065            }
1066            if (attr.nameResId) {
1067                printf("%s%s(0x%08x)", attrNs.string(),
1068                       String8(attr.name).string(), attr.nameResId);
1069            } else {
1070                printf("%s%s", attrNs.string(), String8(attr.name).string());
1071            }
1072            printf("=%s", String8(attr.string).string());
1073        }
1074        printf("\n");
1075    } else if (getType() == TYPE_NAMESPACE) {
1076        printf("%s N: %s=%s\n", prefix.string(),
1077               getNamespacePrefix().size() > 0
1078                    ? String8(getNamespacePrefix()).string() : "<DEF>",
1079               String8(getNamespaceUri()).string());
1080    } else {
1081        printf("%s C: \"%s\"\n", prefix.string(), String8(getCData()).string());
1082    }
1083    int N = mChildren.size();
1084    for (i=0; i<N; i++) {
1085        mChildren.itemAt(i)->print(indent+1);
1086    }
1087}
1088
1089static void splitName(const char* name, String16* outNs, String16* outName)
1090{
1091    const char* p = name;
1092    while (*p != 0 && *p != 1) {
1093        p++;
1094    }
1095    if (*p == 0) {
1096        *outNs = String16();
1097        *outName = String16(name);
1098    } else {
1099        *outNs = String16(name, (p-name));
1100        *outName = String16(p+1);
1101    }
1102}
1103
1104void XMLCALL
1105XMLNode::startNamespace(void *userData, const char *prefix, const char *uri)
1106{
1107    NOISY_PARSE(printf("Start Namespace: %s %s\n", prefix, uri));
1108    ParseState* st = (ParseState*)userData;
1109    sp<XMLNode> node = XMLNode::newNamespace(st->filename,
1110            String16(prefix != NULL ? prefix : ""), String16(uri));
1111    node->setStartLineNumber(XML_GetCurrentLineNumber(st->parser));
1112    if (st->stack.size() > 0) {
1113        st->stack.itemAt(st->stack.size()-1)->addChild(node);
1114    } else {
1115        st->root = node;
1116    }
1117    st->stack.push(node);
1118}
1119
1120void XMLCALL
1121XMLNode::startElement(void *userData, const char *name, const char **atts)
1122{
1123    NOISY_PARSE(printf("Start Element: %s\n", name));
1124    ParseState* st = (ParseState*)userData;
1125    String16 ns16, name16;
1126    splitName(name, &ns16, &name16);
1127    sp<XMLNode> node = XMLNode::newElement(st->filename, ns16, name16);
1128    node->setStartLineNumber(XML_GetCurrentLineNumber(st->parser));
1129    if (st->pendingComment.size() > 0) {
1130        node->appendComment(st->pendingComment);
1131        st->pendingComment = String16();
1132    }
1133    if (st->stack.size() > 0) {
1134        st->stack.itemAt(st->stack.size()-1)->addChild(node);
1135    } else {
1136        st->root = node;
1137    }
1138    st->stack.push(node);
1139
1140    for (int i = 0; atts[i]; i += 2) {
1141        splitName(atts[i], &ns16, &name16);
1142        node->addAttribute(ns16, name16, String16(atts[i+1]));
1143    }
1144}
1145
1146void XMLCALL
1147XMLNode::characterData(void *userData, const XML_Char *s, int len)
1148{
1149    NOISY_PARSE(printf("CDATA: \"%s\"\n", String8(s, len).string()));
1150    ParseState* st = (ParseState*)userData;
1151    sp<XMLNode> node = NULL;
1152    if (st->stack.size() == 0) {
1153        return;
1154    }
1155    sp<XMLNode> parent = st->stack.itemAt(st->stack.size()-1);
1156    if (parent != NULL && parent->getChildren().size() > 0) {
1157        node = parent->getChildren()[parent->getChildren().size()-1];
1158        if (node->getType() != TYPE_CDATA) {
1159            // Last node is not CDATA, need to make a new node.
1160            node = NULL;
1161        }
1162    }
1163
1164    if (node == NULL) {
1165        node = XMLNode::newCData(st->filename);
1166        node->setStartLineNumber(XML_GetCurrentLineNumber(st->parser));
1167        parent->addChild(node);
1168    }
1169
1170    node->appendChars(String16(s, len));
1171}
1172
1173void XMLCALL
1174XMLNode::endElement(void *userData, const char *name)
1175{
1176    NOISY_PARSE(printf("End Element: %s\n", name));
1177    ParseState* st = (ParseState*)userData;
1178    sp<XMLNode> node = st->stack.itemAt(st->stack.size()-1);
1179    node->setEndLineNumber(XML_GetCurrentLineNumber(st->parser));
1180    if (st->pendingComment.size() > 0) {
1181        node->appendComment(st->pendingComment);
1182        st->pendingComment = String16();
1183    }
1184    String16 ns16, name16;
1185    splitName(name, &ns16, &name16);
1186    LOG_ALWAYS_FATAL_IF(node->getElementNamespace() != ns16
1187                        || node->getElementName() != name16,
1188                        "Bad end element %s", name);
1189    st->stack.pop();
1190}
1191
1192void XMLCALL
1193XMLNode::endNamespace(void *userData, const char *prefix)
1194{
1195    const char* nonNullPrefix = prefix != NULL ? prefix : "";
1196    NOISY_PARSE(printf("End Namespace: %s\n", prefix));
1197    ParseState* st = (ParseState*)userData;
1198    sp<XMLNode> node = st->stack.itemAt(st->stack.size()-1);
1199    node->setEndLineNumber(XML_GetCurrentLineNumber(st->parser));
1200    LOG_ALWAYS_FATAL_IF(node->getNamespacePrefix() != String16(nonNullPrefix),
1201                        "Bad end namespace %s", prefix);
1202    st->stack.pop();
1203}
1204
1205void XMLCALL
1206XMLNode::commentData(void *userData, const char *comment)
1207{
1208    NOISY_PARSE(printf("Comment: %s\n", comment));
1209    ParseState* st = (ParseState*)userData;
1210    if (st->pendingComment.size() > 0) {
1211        st->pendingComment.append(String16("\n"));
1212    }
1213    st->pendingComment.append(String16(comment));
1214}
1215
1216status_t XMLNode::collect_strings(StringPool* dest, Vector<uint32_t>* outResIds,
1217        bool stripComments, bool stripRawValues) const
1218{
1219    collect_attr_strings(dest, outResIds, true);
1220
1221    int i;
1222    if (RESOURCES_TOOLS_NAMESPACE != mNamespaceUri) {
1223        if (mNamespacePrefix.size() > 0) {
1224            dest->add(mNamespacePrefix, true);
1225        }
1226        if (mNamespaceUri.size() > 0) {
1227            dest->add(mNamespaceUri, true);
1228        }
1229    }
1230    if (mElementName.size() > 0) {
1231        dest->add(mElementName, true);
1232    }
1233
1234    if (!stripComments && mComment.size() > 0) {
1235        dest->add(mComment, true);
1236    }
1237
1238    const int NA = mAttributes.size();
1239
1240    for (i=0; i<NA; i++) {
1241        const attribute_entry& ae = mAttributes.itemAt(i);
1242        if (ae.ns.size() > 0) {
1243            dest->add(ae.ns, true);
1244        }
1245        if (!stripRawValues || ae.needStringValue()) {
1246            dest->add(ae.string, true);
1247        }
1248        /*
1249        if (ae.value.dataType == Res_value::TYPE_NULL
1250                || ae.value.dataType == Res_value::TYPE_STRING) {
1251            dest->add(ae.string, true);
1252        }
1253        */
1254    }
1255
1256    if (mElementName.size() == 0) {
1257        // If not an element, include the CDATA, even if it is empty.
1258        dest->add(mChars, true);
1259    }
1260
1261    const int NC = mChildren.size();
1262
1263    for (i=0; i<NC; i++) {
1264        mChildren.itemAt(i)->collect_strings(dest, outResIds,
1265                stripComments, stripRawValues);
1266    }
1267
1268    return NO_ERROR;
1269}
1270
1271status_t XMLNode::collect_attr_strings(StringPool* outPool,
1272        Vector<uint32_t>* outResIds, bool allAttrs) const {
1273    const int NA = mAttributes.size();
1274
1275    for (int i=0; i<NA; i++) {
1276        const attribute_entry& attr = mAttributes.itemAt(i);
1277        uint32_t id = attr.nameResId;
1278        if (id || allAttrs) {
1279            // See if we have already assigned this resource ID to a pooled
1280            // string...
1281            const Vector<size_t>* indices = outPool->offsetsForString(attr.name);
1282            ssize_t idx = -1;
1283            if (indices != NULL) {
1284                const int NJ = indices->size();
1285                const size_t NR = outResIds->size();
1286                for (int j=0; j<NJ; j++) {
1287                    size_t strIdx = indices->itemAt(j);
1288                    if (strIdx >= NR) {
1289                        if (id == 0) {
1290                            // We don't need to assign a resource ID for this one.
1291                            idx = strIdx;
1292                            break;
1293                        }
1294                        // Just ignore strings that are out of range of
1295                        // the currently assigned resource IDs...  we add
1296                        // strings as we assign the first ID.
1297                    } else if (outResIds->itemAt(strIdx) == id) {
1298                        idx = strIdx;
1299                        break;
1300                    }
1301                }
1302            }
1303            if (idx < 0) {
1304                idx = outPool->add(attr.name);
1305                NOISY(printf("Adding attr %s (resid 0x%08x) to pool: idx=%d\n",
1306                        String8(attr.name).string(), id, idx));
1307                if (id != 0) {
1308                    while ((ssize_t)outResIds->size() <= idx) {
1309                        outResIds->add(0);
1310                    }
1311                    outResIds->replaceAt(id, idx);
1312                }
1313            }
1314            attr.namePoolIdx = idx;
1315            NOISY(printf("String %s offset=0x%08x\n",
1316                         String8(attr.name).string(), idx));
1317        }
1318    }
1319
1320    return NO_ERROR;
1321}
1322
1323status_t XMLNode::collect_resid_strings(StringPool* outPool,
1324        Vector<uint32_t>* outResIds) const
1325{
1326    collect_attr_strings(outPool, outResIds, false);
1327
1328    const int NC = mChildren.size();
1329
1330    for (int i=0; i<NC; i++) {
1331        mChildren.itemAt(i)->collect_resid_strings(outPool, outResIds);
1332    }
1333
1334    return NO_ERROR;
1335}
1336
1337status_t XMLNode::flatten_node(const StringPool& strings, const sp<AaptFile>& dest,
1338        bool stripComments, bool stripRawValues) const
1339{
1340    ResXMLTree_node node;
1341    ResXMLTree_cdataExt cdataExt;
1342    ResXMLTree_namespaceExt namespaceExt;
1343    ResXMLTree_attrExt attrExt;
1344    const void* extData = NULL;
1345    size_t extSize = 0;
1346    ResXMLTree_attribute attr;
1347    bool writeCurrentNode = true;
1348
1349    const size_t NA = mAttributes.size();
1350    const size_t NC = mChildren.size();
1351    size_t i;
1352
1353    LOG_ALWAYS_FATAL_IF(NA != mAttributeOrder.size(), "Attributes messed up!");
1354
1355    const String16 id16("id");
1356    const String16 class16("class");
1357    const String16 style16("style");
1358
1359    const type type = getType();
1360
1361    memset(&node, 0, sizeof(node));
1362    memset(&attr, 0, sizeof(attr));
1363    node.header.headerSize = htods(sizeof(node));
1364    node.lineNumber = htodl(getStartLineNumber());
1365    if (!stripComments) {
1366        node.comment.index = htodl(
1367            mComment.size() > 0 ? strings.offsetForString(mComment) : -1);
1368        //if (mComment.size() > 0) {
1369        //  printf("Flattening comment: %s\n", String8(mComment).string());
1370        //}
1371    } else {
1372        node.comment.index = htodl((uint32_t)-1);
1373    }
1374    if (type == TYPE_ELEMENT) {
1375        node.header.type = htods(RES_XML_START_ELEMENT_TYPE);
1376        extData = &attrExt;
1377        extSize = sizeof(attrExt);
1378        memset(&attrExt, 0, sizeof(attrExt));
1379        if (mNamespaceUri.size() > 0) {
1380            attrExt.ns.index = htodl(strings.offsetForString(mNamespaceUri));
1381        } else {
1382            attrExt.ns.index = htodl((uint32_t)-1);
1383        }
1384        attrExt.name.index = htodl(strings.offsetForString(mElementName));
1385        attrExt.attributeStart = htods(sizeof(attrExt));
1386        attrExt.attributeSize = htods(sizeof(attr));
1387        attrExt.attributeCount = htods(NA);
1388        attrExt.idIndex = htods(0);
1389        attrExt.classIndex = htods(0);
1390        attrExt.styleIndex = htods(0);
1391        for (i=0; i<NA; i++) {
1392            ssize_t idx = mAttributeOrder.valueAt(i);
1393            const attribute_entry& ae = mAttributes.itemAt(idx);
1394            if (ae.ns.size() == 0) {
1395                if (ae.name == id16) {
1396                    attrExt.idIndex = htods(i+1);
1397                } else if (ae.name == class16) {
1398                    attrExt.classIndex = htods(i+1);
1399                } else if (ae.name == style16) {
1400                    attrExt.styleIndex = htods(i+1);
1401                }
1402            }
1403        }
1404    } else if (type == TYPE_NAMESPACE) {
1405        if (mNamespaceUri == RESOURCES_TOOLS_NAMESPACE) {
1406            writeCurrentNode = false;
1407        } else {
1408            node.header.type = htods(RES_XML_START_NAMESPACE_TYPE);
1409            extData = &namespaceExt;
1410            extSize = sizeof(namespaceExt);
1411            memset(&namespaceExt, 0, sizeof(namespaceExt));
1412            if (mNamespacePrefix.size() > 0) {
1413                namespaceExt.prefix.index = htodl(strings.offsetForString(mNamespacePrefix));
1414            } else {
1415                namespaceExt.prefix.index = htodl((uint32_t)-1);
1416            }
1417            namespaceExt.prefix.index = htodl(strings.offsetForString(mNamespacePrefix));
1418            namespaceExt.uri.index = htodl(strings.offsetForString(mNamespaceUri));
1419        }
1420        LOG_ALWAYS_FATAL_IF(NA != 0, "Namespace nodes can't have attributes!");
1421    } else if (type == TYPE_CDATA) {
1422        node.header.type = htods(RES_XML_CDATA_TYPE);
1423        extData = &cdataExt;
1424        extSize = sizeof(cdataExt);
1425        memset(&cdataExt, 0, sizeof(cdataExt));
1426        cdataExt.data.index = htodl(strings.offsetForString(mChars));
1427        cdataExt.typedData.size = htods(sizeof(cdataExt.typedData));
1428        cdataExt.typedData.res0 = 0;
1429        cdataExt.typedData.dataType = mCharsValue.dataType;
1430        cdataExt.typedData.data = htodl(mCharsValue.data);
1431        LOG_ALWAYS_FATAL_IF(NA != 0, "CDATA nodes can't have attributes!");
1432    }
1433
1434    node.header.size = htodl(sizeof(node) + extSize + (sizeof(attr)*NA));
1435
1436    if (writeCurrentNode) {
1437        dest->writeData(&node, sizeof(node));
1438        if (extSize > 0) {
1439            dest->writeData(extData, extSize);
1440        }
1441    }
1442
1443    for (i=0; i<NA; i++) {
1444        ssize_t idx = mAttributeOrder.valueAt(i);
1445        const attribute_entry& ae = mAttributes.itemAt(idx);
1446        if (ae.ns.size() > 0) {
1447            attr.ns.index = htodl(strings.offsetForString(ae.ns));
1448        } else {
1449            attr.ns.index = htodl((uint32_t)-1);
1450        }
1451        attr.name.index = htodl(ae.namePoolIdx);
1452
1453        if (!stripRawValues || ae.needStringValue()) {
1454            attr.rawValue.index = htodl(strings.offsetForString(ae.string));
1455        } else {
1456            attr.rawValue.index = htodl((uint32_t)-1);
1457        }
1458        attr.typedValue.size = htods(sizeof(attr.typedValue));
1459        if (ae.value.dataType == Res_value::TYPE_NULL
1460                || ae.value.dataType == Res_value::TYPE_STRING) {
1461            attr.typedValue.res0 = 0;
1462            attr.typedValue.dataType = Res_value::TYPE_STRING;
1463            attr.typedValue.data = htodl(strings.offsetForString(ae.string));
1464        } else {
1465            attr.typedValue.res0 = 0;
1466            attr.typedValue.dataType = ae.value.dataType;
1467            attr.typedValue.data = htodl(ae.value.data);
1468        }
1469        dest->writeData(&attr, sizeof(attr));
1470    }
1471
1472    for (i=0; i<NC; i++) {
1473        status_t err = mChildren.itemAt(i)->flatten_node(strings, dest,
1474                stripComments, stripRawValues);
1475        if (err != NO_ERROR) {
1476            return err;
1477        }
1478    }
1479
1480    if (type == TYPE_ELEMENT) {
1481        ResXMLTree_endElementExt endElementExt;
1482        memset(&endElementExt, 0, sizeof(endElementExt));
1483        node.header.type = htods(RES_XML_END_ELEMENT_TYPE);
1484        node.header.size = htodl(sizeof(node)+sizeof(endElementExt));
1485        node.lineNumber = htodl(getEndLineNumber());
1486        node.comment.index = htodl((uint32_t)-1);
1487        endElementExt.ns.index = attrExt.ns.index;
1488        endElementExt.name.index = attrExt.name.index;
1489        dest->writeData(&node, sizeof(node));
1490        dest->writeData(&endElementExt, sizeof(endElementExt));
1491    } else if (type == TYPE_NAMESPACE) {
1492        if (writeCurrentNode) {
1493            node.header.type = htods(RES_XML_END_NAMESPACE_TYPE);
1494            node.lineNumber = htodl(getEndLineNumber());
1495            node.comment.index = htodl((uint32_t)-1);
1496            node.header.size = htodl(sizeof(node)+extSize);
1497            dest->writeData(&node, sizeof(node));
1498            dest->writeData(extData, extSize);
1499        }
1500    }
1501
1502    return NO_ERROR;
1503}
1504