XMLNode.cpp revision e348909c1966c3d192841cc131a32be6ed90da18
1//
2// Copyright 2006 The Android Open Source Project
3//
4// Build resource files from raw assets.
5//
6
7#include "XMLNode.h"
8#include "ResourceTable.h"
9
10#include <host/pseudolocalize.h>
11#include <utils/ByteOrder.h>
12#include <errno.h>
13#include <string.h>
14
15#ifndef HAVE_MS_C_RUNTIME
16#define O_BINARY 0
17#endif
18
19#define NOISY(x) //x
20#define NOISY_PARSE(x) //x
21
22const char* const RESOURCES_ROOT_NAMESPACE = "http://schemas.android.com/apk/res/";
23const char* const RESOURCES_ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android";
24const char* const RESOURCES_AUTO_PACKAGE_NAMESPACE = "http://schemas.android.com/apk/res/auto";
25const char* const RESOURCES_ROOT_PRV_NAMESPACE = "http://schemas.android.com/apk/prv/res/";
26
27const char* const XLIFF_XMLNS = "urn:oasis:names:tc:xliff:document:1.2";
28const char* const ALLOWED_XLIFF_ELEMENTS[] = {
29        "bpt",
30        "ept",
31        "it",
32        "ph",
33        "g",
34        "bx",
35        "ex",
36        "x"
37    };
38
39bool isWhitespace(const char16_t* str)
40{
41    while (*str != 0 && *str < 128 && isspace(*str)) {
42        str++;
43    }
44    return *str == 0;
45}
46
47static const String16 RESOURCES_PREFIX(RESOURCES_ROOT_NAMESPACE);
48static const String16 RESOURCES_PREFIX_AUTO_PACKAGE(RESOURCES_AUTO_PACKAGE_NAMESPACE);
49static const String16 RESOURCES_PRV_PREFIX(RESOURCES_ROOT_PRV_NAMESPACE);
50
51String16 getNamespaceResourcePackage(String16 appPackage, String16 namespaceUri, bool* outIsPublic)
52{
53    //printf("%s starts with %s?\n", String8(namespaceUri).string(),
54    //       String8(RESOURCES_PREFIX).string());
55    size_t prefixSize;
56    bool isPublic = true;
57    if(namespaceUri.startsWith(RESOURCES_PREFIX_AUTO_PACKAGE)) {
58        NOISY(printf("Using default application package: %s -> %s\n", String8(namespaceUri).string(), String8(appPackage).string()));
59        isPublic = true;
60        return appPackage;
61    } else if (namespaceUri.startsWith(RESOURCES_PREFIX)) {
62        prefixSize = RESOURCES_PREFIX.size();
63    } else if (namespaceUri.startsWith(RESOURCES_PRV_PREFIX)) {
64        isPublic = false;
65        prefixSize = RESOURCES_PRV_PREFIX.size();
66    } else {
67        if (outIsPublic) *outIsPublic = isPublic; // = true
68        return String16();
69    }
70
71    //printf("YES!\n");
72    //printf("namespace: %s\n", String8(String16(namespaceUri, namespaceUri.size()-prefixSize, prefixSize)).string());
73    if (outIsPublic) *outIsPublic = isPublic;
74    return String16(namespaceUri, namespaceUri.size()-prefixSize, prefixSize);
75}
76
77status_t hasSubstitutionErrors(const char* fileName,
78                               ResXMLTree* inXml,
79                               String16 str16)
80{
81    const char16_t* str = str16.string();
82    const char16_t* p = str;
83    const char16_t* end = str + str16.size();
84
85    bool nonpositional = false;
86    int argCount = 0;
87
88    while (p < end) {
89        /*
90         * Look for the start of a Java-style substitution sequence.
91         */
92        if (*p == '%' && p + 1 < end) {
93            p++;
94
95            // A literal percent sign represented by %%
96            if (*p == '%') {
97                p++;
98                continue;
99            }
100
101            argCount++;
102
103            if (*p >= '0' && *p <= '9') {
104                do {
105                    p++;
106                } while (*p >= '0' && *p <= '9');
107                if (*p != '$') {
108                    // This must be a size specification instead of position.
109                    nonpositional = true;
110                }
111            } else if (*p == '<') {
112                // Reusing last argument; bad idea since it can be re-arranged.
113                nonpositional = true;
114                p++;
115
116                // Optionally '$' can be specified at the end.
117                if (p < end && *p == '$') {
118                    p++;
119                }
120            } else {
121                nonpositional = true;
122            }
123
124            // Ignore flags and widths
125            while (p < end && (*p == '-' ||
126                    *p == '#' ||
127                    *p == '+' ||
128                    *p == ' ' ||
129                    *p == ',' ||
130                    *p == '(' ||
131                    (*p >= '0' && *p <= '9'))) {
132                p++;
133            }
134
135            /*
136             * This is a shortcut to detect strings that are going to Time.format()
137             * instead of String.format()
138             *
139             * Comparison of String.format() and Time.format() args:
140             *
141             * String: ABC E GH  ST X abcdefgh  nost x
142             *   Time:    DEFGHKMS W Za  d   hkm  s w yz
143             *
144             * Therefore we know it's definitely Time if we have:
145             *     DFKMWZkmwyz
146             */
147            if (p < end) {
148                switch (*p) {
149                case 'D':
150                case 'F':
151                case 'K':
152                case 'M':
153                case 'W':
154                case 'Z':
155                case 'k':
156                case 'm':
157                case 'w':
158                case 'y':
159                case 'z':
160                    return NO_ERROR;
161                }
162            }
163        }
164
165        p++;
166    }
167
168    /*
169     * If we have more than one substitution in this string and any of them
170     * are not in positional form, give the user an error.
171     */
172    if (argCount > 1 && nonpositional) {
173        SourcePos(String8(fileName), inXml->getLineNumber()).error(
174                "Multiple substitutions specified in non-positional format; "
175                "did you mean to add the formatted=\"false\" attribute?\n");
176        return NOT_ENOUGH_DATA;
177    }
178
179    return NO_ERROR;
180}
181
182status_t parseStyledString(Bundle* bundle,
183                           const char* fileName,
184                           ResXMLTree* inXml,
185                           const String16& endTag,
186                           String16* outString,
187                           Vector<StringPool::entry_style_span>* outSpans,
188                           bool isFormatted,
189                           bool pseudolocalize)
190{
191    Vector<StringPool::entry_style_span> spanStack;
192    String16 curString;
193    String16 rawString;
194    const char* errorMsg;
195    int xliffDepth = 0;
196    bool firstTime = true;
197
198    size_t len;
199    ResXMLTree::event_code_t code;
200    while ((code=inXml->next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
201
202        if (code == ResXMLTree::TEXT) {
203            String16 text(inXml->getText(&len));
204            if (firstTime && text.size() > 0) {
205                firstTime = false;
206                if (text.string()[0] == '@') {
207                    // If this is a resource reference, don't do the pseudoloc.
208                    pseudolocalize = false;
209                }
210            }
211            if (xliffDepth == 0 && pseudolocalize) {
212                std::string orig(String8(text).string());
213                std::string pseudo = pseudolocalize_string(orig);
214                curString.append(String16(String8(pseudo.c_str())));
215            } else {
216                if (isFormatted && hasSubstitutionErrors(fileName, inXml, text) != NO_ERROR) {
217                    return UNKNOWN_ERROR;
218                } else {
219                    curString.append(text);
220                }
221            }
222        } else if (code == ResXMLTree::START_TAG) {
223            const String16 element16(inXml->getElementName(&len));
224            const String8 element8(element16);
225
226            size_t nslen;
227            const uint16_t* ns = inXml->getElementNamespace(&nslen);
228            if (ns == NULL) {
229                ns = (const uint16_t*)"\0\0";
230                nslen = 0;
231            }
232            const String8 nspace(String16(ns, nslen));
233            if (nspace == XLIFF_XMLNS) {
234                const int N = sizeof(ALLOWED_XLIFF_ELEMENTS)/sizeof(ALLOWED_XLIFF_ELEMENTS[0]);
235                for (int i=0; i<N; i++) {
236                    if (element8 == ALLOWED_XLIFF_ELEMENTS[i]) {
237                        xliffDepth++;
238                        // in this case, treat it like it was just text, in other words, do nothing
239                        // here and silently drop this element
240                        goto moveon;
241                    }
242                }
243                {
244                    SourcePos(String8(fileName), inXml->getLineNumber()).error(
245                            "Found unsupported XLIFF tag <%s>\n",
246                            element8.string());
247                    return UNKNOWN_ERROR;
248                }
249moveon:
250                continue;
251            }
252
253            if (outSpans == NULL) {
254                SourcePos(String8(fileName), inXml->getLineNumber()).error(
255                        "Found style tag <%s> where styles are not allowed\n", element8.string());
256                return UNKNOWN_ERROR;
257            }
258
259            if (!ResTable::collectString(outString, curString.string(),
260                                         curString.size(), false, &errorMsg, true)) {
261                SourcePos(String8(fileName), inXml->getLineNumber()).error("%s (in %s)\n",
262                        errorMsg, String8(curString).string());
263                return UNKNOWN_ERROR;
264            }
265            rawString.append(curString);
266            curString = String16();
267
268            StringPool::entry_style_span span;
269            span.name = element16;
270            for (size_t ai=0; ai<inXml->getAttributeCount(); ai++) {
271                span.name.append(String16(";"));
272                const char16_t* str = inXml->getAttributeName(ai, &len);
273                span.name.append(str, len);
274                span.name.append(String16("="));
275                str = inXml->getAttributeStringValue(ai, &len);
276                span.name.append(str, len);
277            }
278            //printf("Span: %s\n", String8(span.name).string());
279            span.span.firstChar = span.span.lastChar = outString->size();
280            spanStack.push(span);
281
282        } else if (code == ResXMLTree::END_TAG) {
283            size_t nslen;
284            const uint16_t* ns = inXml->getElementNamespace(&nslen);
285            if (ns == NULL) {
286                ns = (const uint16_t*)"\0\0";
287                nslen = 0;
288            }
289            const String8 nspace(String16(ns, nslen));
290            if (nspace == XLIFF_XMLNS) {
291                xliffDepth--;
292                continue;
293            }
294            if (!ResTable::collectString(outString, curString.string(),
295                                         curString.size(), false, &errorMsg, true)) {
296                SourcePos(String8(fileName), inXml->getLineNumber()).error("%s (in %s)\n",
297                        errorMsg, String8(curString).string());
298                return UNKNOWN_ERROR;
299            }
300            rawString.append(curString);
301            curString = String16();
302
303            if (spanStack.size() == 0) {
304                if (strcmp16(inXml->getElementName(&len), endTag.string()) != 0) {
305                    SourcePos(String8(fileName), inXml->getLineNumber()).error(
306                            "Found tag %s where <%s> close is expected\n",
307                            String8(inXml->getElementName(&len)).string(),
308                            String8(endTag).string());
309                    return UNKNOWN_ERROR;
310                }
311                break;
312            }
313            StringPool::entry_style_span span = spanStack.top();
314            String16 spanTag;
315            ssize_t semi = span.name.findFirst(';');
316            if (semi >= 0) {
317                spanTag.setTo(span.name.string(), semi);
318            } else {
319                spanTag.setTo(span.name);
320            }
321            if (strcmp16(inXml->getElementName(&len), spanTag.string()) != 0) {
322                SourcePos(String8(fileName), inXml->getLineNumber()).error(
323                        "Found close tag %s where close tag %s is expected\n",
324                        String8(inXml->getElementName(&len)).string(),
325                        String8(spanTag).string());
326                return UNKNOWN_ERROR;
327            }
328            bool empty = true;
329            if (outString->size() > 0) {
330                span.span.lastChar = outString->size()-1;
331                if (span.span.lastChar >= span.span.firstChar) {
332                    empty = false;
333                    outSpans->add(span);
334                }
335            }
336            spanStack.pop();
337
338            /*
339             * This warning seems to be just an irritation to most people,
340             * since it is typically introduced by translators who then never
341             * see the warning.
342             */
343            if (0 && empty) {
344                fprintf(stderr, "%s:%d: warning: empty '%s' span found in text '%s'\n",
345                        fileName, inXml->getLineNumber(),
346                        String8(spanTag).string(), String8(*outString).string());
347
348            }
349        } else if (code == ResXMLTree::START_NAMESPACE) {
350            // nothing
351        }
352    }
353
354    if (code == ResXMLTree::BAD_DOCUMENT) {
355            SourcePos(String8(fileName), inXml->getLineNumber()).error(
356                    "Error parsing XML\n");
357    }
358
359    if (outSpans != NULL && outSpans->size() > 0) {
360        if (curString.size() > 0) {
361            if (!ResTable::collectString(outString, curString.string(),
362                                         curString.size(), false, &errorMsg, true)) {
363                SourcePos(String8(fileName), inXml->getLineNumber()).error(
364                        "%s (in %s)\n",
365                        errorMsg, String8(curString).string());
366                return UNKNOWN_ERROR;
367            }
368        }
369    } else {
370        // There is no style information, so string processing will happen
371        // later as part of the overall type conversion.  Return to the
372        // client the raw unprocessed text.
373        rawString.append(curString);
374        outString->setTo(rawString);
375    }
376
377    return NO_ERROR;
378}
379
380struct namespace_entry {
381    String8 prefix;
382    String8 uri;
383};
384
385static String8 make_prefix(int depth)
386{
387    String8 prefix;
388    int i;
389    for (i=0; i<depth; i++) {
390        prefix.append("  ");
391    }
392    return prefix;
393}
394
395static String8 build_namespace(const Vector<namespace_entry>& namespaces,
396        const uint16_t* ns)
397{
398    String8 str;
399    if (ns != NULL) {
400        str = String8(ns);
401        const size_t N = namespaces.size();
402        for (size_t i=0; i<N; i++) {
403            const namespace_entry& ne = namespaces.itemAt(i);
404            if (ne.uri == str) {
405                str = ne.prefix;
406                break;
407            }
408        }
409        str.append(":");
410    }
411    return str;
412}
413
414void printXMLBlock(ResXMLTree* block)
415{
416    block->restart();
417
418    Vector<namespace_entry> namespaces;
419
420    ResXMLTree::event_code_t code;
421    int depth = 0;
422    while ((code=block->next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
423        String8 prefix = make_prefix(depth);
424        int i;
425        if (code == ResXMLTree::START_TAG) {
426            size_t len;
427            const uint16_t* ns16 = block->getElementNamespace(&len);
428            String8 elemNs = build_namespace(namespaces, ns16);
429            const uint16_t* com16 = block->getComment(&len);
430            if (com16) {
431                printf("%s <!-- %s -->\n", prefix.string(), String8(com16).string());
432            }
433            printf("%sE: %s%s (line=%d)\n", prefix.string(), elemNs.string(),
434                   String8(block->getElementName(&len)).string(),
435                   block->getLineNumber());
436            int N = block->getAttributeCount();
437            depth++;
438            prefix = make_prefix(depth);
439            for (i=0; i<N; i++) {
440                uint32_t res = block->getAttributeNameResID(i);
441                ns16 = block->getAttributeNamespace(i, &len);
442                String8 ns = build_namespace(namespaces, ns16);
443                String8 name(block->getAttributeName(i, &len));
444                printf("%sA: ", prefix.string());
445                if (res) {
446                    printf("%s%s(0x%08x)", ns.string(), name.string(), res);
447                } else {
448                    printf("%s%s", ns.string(), name.string());
449                }
450                Res_value value;
451                block->getAttributeValue(i, &value);
452                if (value.dataType == Res_value::TYPE_NULL) {
453                    printf("=(null)");
454                } else if (value.dataType == Res_value::TYPE_REFERENCE) {
455                    printf("=@0x%x", (int)value.data);
456                } else if (value.dataType == Res_value::TYPE_ATTRIBUTE) {
457                    printf("=?0x%x", (int)value.data);
458                } else if (value.dataType == Res_value::TYPE_STRING) {
459                    printf("=\"%s\"",
460                            ResTable::normalizeForOutput(String8(block->getAttributeStringValue(i,
461                                        &len)).string()).string());
462                } else {
463                    printf("=(type 0x%x)0x%x", (int)value.dataType, (int)value.data);
464                }
465                const char16_t* val = block->getAttributeStringValue(i, &len);
466                if (val != NULL) {
467                    printf(" (Raw: \"%s\")", ResTable::normalizeForOutput(String8(val).string()).
468                            string());
469                }
470                printf("\n");
471            }
472        } else if (code == ResXMLTree::END_TAG) {
473            depth--;
474        } else if (code == ResXMLTree::START_NAMESPACE) {
475            namespace_entry ns;
476            size_t len;
477            const uint16_t* prefix16 = block->getNamespacePrefix(&len);
478            if (prefix16) {
479                ns.prefix = String8(prefix16);
480            } else {
481                ns.prefix = "<DEF>";
482            }
483            ns.uri = String8(block->getNamespaceUri(&len));
484            namespaces.push(ns);
485            printf("%sN: %s=%s\n", prefix.string(), ns.prefix.string(),
486                    ns.uri.string());
487            depth++;
488        } else if (code == ResXMLTree::END_NAMESPACE) {
489            depth--;
490            const namespace_entry& ns = namespaces.top();
491            size_t len;
492            const uint16_t* prefix16 = block->getNamespacePrefix(&len);
493            String8 pr;
494            if (prefix16) {
495                pr = String8(prefix16);
496            } else {
497                pr = "<DEF>";
498            }
499            if (ns.prefix != pr) {
500                prefix = make_prefix(depth);
501                printf("%s*** BAD END NS PREFIX: found=%s, expected=%s\n",
502                        prefix.string(), pr.string(), ns.prefix.string());
503            }
504            String8 uri = String8(block->getNamespaceUri(&len));
505            if (ns.uri != uri) {
506                prefix = make_prefix(depth);
507                printf("%s *** BAD END NS URI: found=%s, expected=%s\n",
508                        prefix.string(), uri.string(), ns.uri.string());
509            }
510            namespaces.pop();
511        } else if (code == ResXMLTree::TEXT) {
512            size_t len;
513            printf("%sC: \"%s\"\n", prefix.string(), ResTable::normalizeForOutput(
514                        String8(block->getText(&len)).string()).string());
515        }
516    }
517
518    block->restart();
519}
520
521status_t parseXMLResource(const sp<AaptFile>& file, ResXMLTree* outTree,
522                          bool stripAll, bool keepComments,
523                          const char** cDataTags)
524{
525    sp<XMLNode> root = XMLNode::parse(file);
526    if (root == NULL) {
527        return UNKNOWN_ERROR;
528    }
529    root->removeWhitespace(stripAll, cDataTags);
530
531    NOISY(printf("Input XML from %s:\n", (const char*)file->getPrintableSource()));
532    NOISY(root->print());
533    sp<AaptFile> rsc = new AaptFile(String8(), AaptGroupEntry(), String8());
534    status_t err = root->flatten(rsc, !keepComments, false);
535    if (err != NO_ERROR) {
536        return err;
537    }
538    err = outTree->setTo(rsc->getData(), rsc->getSize(), true);
539    if (err != NO_ERROR) {
540        return err;
541    }
542
543    NOISY(printf("Output XML:\n"));
544    NOISY(printXMLBlock(outTree));
545
546    return NO_ERROR;
547}
548
549sp<XMLNode> XMLNode::parse(const sp<AaptFile>& file)
550{
551    char buf[16384];
552    int fd = open(file->getSourceFile().string(), O_RDONLY | O_BINARY);
553    if (fd < 0) {
554        SourcePos(file->getSourceFile(), -1).error("Unable to open file for read: %s",
555                strerror(errno));
556        return NULL;
557    }
558
559    XML_Parser parser = XML_ParserCreateNS(NULL, 1);
560    ParseState state;
561    state.filename = file->getPrintableSource();
562    state.parser = parser;
563    XML_SetUserData(parser, &state);
564    XML_SetElementHandler(parser, startElement, endElement);
565    XML_SetNamespaceDeclHandler(parser, startNamespace, endNamespace);
566    XML_SetCharacterDataHandler(parser, characterData);
567    XML_SetCommentHandler(parser, commentData);
568
569    ssize_t len;
570    bool done;
571    do {
572        len = read(fd, buf, sizeof(buf));
573        done = len < (ssize_t)sizeof(buf);
574        if (len < 0) {
575            SourcePos(file->getSourceFile(), -1).error("Error reading file: %s\n", strerror(errno));
576            close(fd);
577            return NULL;
578        }
579        if (XML_Parse(parser, buf, len, done) == XML_STATUS_ERROR) {
580            SourcePos(file->getSourceFile(), (int)XML_GetCurrentLineNumber(parser)).error(
581                    "Error parsing XML: %s\n", XML_ErrorString(XML_GetErrorCode(parser)));
582            close(fd);
583            return NULL;
584        }
585    } while (!done);
586
587    XML_ParserFree(parser);
588    if (state.root == NULL) {
589        SourcePos(file->getSourceFile(), -1).error("No XML data generated when parsing");
590    }
591    close(fd);
592    return state.root;
593}
594
595XMLNode::XMLNode(const String8& filename, const String16& s1, const String16& s2, bool isNamespace)
596    : mNextAttributeIndex(0x80000000)
597    , mFilename(filename)
598    , mStartLineNumber(0)
599    , mEndLineNumber(0)
600    , mUTF8(false)
601{
602    if (isNamespace) {
603        mNamespacePrefix = s1;
604        mNamespaceUri = s2;
605    } else {
606        mNamespaceUri = s1;
607        mElementName = s2;
608    }
609}
610
611XMLNode::XMLNode(const String8& filename)
612    : mFilename(filename)
613{
614    memset(&mCharsValue, 0, sizeof(mCharsValue));
615}
616
617XMLNode::type XMLNode::getType() const
618{
619    if (mElementName.size() != 0) {
620        return TYPE_ELEMENT;
621    }
622    if (mNamespaceUri.size() != 0) {
623        return TYPE_NAMESPACE;
624    }
625    return TYPE_CDATA;
626}
627
628const String16& XMLNode::getNamespacePrefix() const
629{
630    return mNamespacePrefix;
631}
632
633const String16& XMLNode::getNamespaceUri() const
634{
635    return mNamespaceUri;
636}
637
638const String16& XMLNode::getElementNamespace() const
639{
640    return mNamespaceUri;
641}
642
643const String16& XMLNode::getElementName() const
644{
645    return mElementName;
646}
647
648const Vector<sp<XMLNode> >& XMLNode::getChildren() const
649{
650    return mChildren;
651}
652
653const String8& XMLNode::getFilename() const
654{
655    return mFilename;
656}
657
658const Vector<XMLNode::attribute_entry>&
659    XMLNode::getAttributes() const
660{
661    return mAttributes;
662}
663
664const XMLNode::attribute_entry* XMLNode::getAttribute(const String16& ns,
665        const String16& name) const
666{
667    for (size_t i=0; i<mAttributes.size(); i++) {
668        const attribute_entry& ae(mAttributes.itemAt(i));
669        if (ae.ns == ns && ae.name == name) {
670            return &ae;
671        }
672    }
673
674    return NULL;
675}
676
677XMLNode::attribute_entry* XMLNode::editAttribute(const String16& ns,
678        const String16& name)
679{
680    for (size_t i=0; i<mAttributes.size(); i++) {
681        attribute_entry * ae = &mAttributes.editItemAt(i);
682        if (ae->ns == ns && ae->name == name) {
683            return ae;
684        }
685    }
686
687    return NULL;
688}
689
690const String16& XMLNode::getCData() const
691{
692    return mChars;
693}
694
695const String16& XMLNode::getComment() const
696{
697    return mComment;
698}
699
700int32_t XMLNode::getStartLineNumber() const
701{
702    return mStartLineNumber;
703}
704
705int32_t XMLNode::getEndLineNumber() const
706{
707    return mEndLineNumber;
708}
709
710sp<XMLNode> XMLNode::searchElement(const String16& tagNamespace, const String16& tagName)
711{
712    if (getType() == XMLNode::TYPE_ELEMENT
713            && mNamespaceUri == tagNamespace
714            && mElementName == tagName) {
715        return this;
716    }
717
718    for (size_t i=0; i<mChildren.size(); i++) {
719        sp<XMLNode> found = mChildren.itemAt(i)->searchElement(tagNamespace, tagName);
720        if (found != NULL) {
721            return found;
722        }
723    }
724
725    return NULL;
726}
727
728sp<XMLNode> XMLNode::getChildElement(const String16& tagNamespace, const String16& tagName)
729{
730    for (size_t i=0; i<mChildren.size(); i++) {
731        sp<XMLNode> child = mChildren.itemAt(i);
732        if (child->getType() == XMLNode::TYPE_ELEMENT
733                && child->mNamespaceUri == tagNamespace
734                && child->mElementName == tagName) {
735            return child;
736        }
737    }
738
739    return NULL;
740}
741
742status_t XMLNode::addChild(const sp<XMLNode>& child)
743{
744    if (getType() == TYPE_CDATA) {
745        SourcePos(mFilename, child->getStartLineNumber()).error("Child to CDATA node.");
746        return UNKNOWN_ERROR;
747    }
748    //printf("Adding child %p to parent %p\n", child.get(), this);
749    mChildren.add(child);
750    return NO_ERROR;
751}
752
753status_t XMLNode::insertChildAt(const sp<XMLNode>& child, size_t index)
754{
755    if (getType() == TYPE_CDATA) {
756        SourcePos(mFilename, child->getStartLineNumber()).error("Child to CDATA node.");
757        return UNKNOWN_ERROR;
758    }
759    //printf("Adding child %p to parent %p\n", child.get(), this);
760    mChildren.insertAt(child, index);
761    return NO_ERROR;
762}
763
764status_t XMLNode::addAttribute(const String16& ns, const String16& name,
765                               const String16& value)
766{
767    if (getType() == TYPE_CDATA) {
768        SourcePos(mFilename, getStartLineNumber()).error("Child to CDATA node.");
769        return UNKNOWN_ERROR;
770    }
771    attribute_entry e;
772    e.index = mNextAttributeIndex++;
773    e.ns = ns;
774    e.name = name;
775    e.string = value;
776    mAttributes.add(e);
777    mAttributeOrder.add(e.index, mAttributes.size()-1);
778    return NO_ERROR;
779}
780
781void XMLNode::setAttributeResID(size_t attrIdx, uint32_t resId)
782{
783    attribute_entry& e = mAttributes.editItemAt(attrIdx);
784    if (e.nameResId) {
785        mAttributeOrder.removeItem(e.nameResId);
786    } else {
787        mAttributeOrder.removeItem(e.index);
788    }
789    NOISY(printf("Elem %s %s=\"%s\": set res id = 0x%08x\n",
790            String8(getElementName()).string(),
791            String8(mAttributes.itemAt(attrIdx).name).string(),
792            String8(mAttributes.itemAt(attrIdx).string).string(),
793            resId));
794    mAttributes.editItemAt(attrIdx).nameResId = resId;
795    mAttributeOrder.add(resId, attrIdx);
796}
797
798status_t XMLNode::appendChars(const String16& chars)
799{
800    if (getType() != TYPE_CDATA) {
801        SourcePos(mFilename, getStartLineNumber()).error("Adding characters to element node.");
802        return UNKNOWN_ERROR;
803    }
804    mChars.append(chars);
805    return NO_ERROR;
806}
807
808status_t XMLNode::appendComment(const String16& comment)
809{
810    if (mComment.size() > 0) {
811        mComment.append(String16("\n"));
812    }
813    mComment.append(comment);
814    return NO_ERROR;
815}
816
817void XMLNode::setStartLineNumber(int32_t line)
818{
819    mStartLineNumber = line;
820}
821
822void XMLNode::setEndLineNumber(int32_t line)
823{
824    mEndLineNumber = line;
825}
826
827void XMLNode::removeWhitespace(bool stripAll, const char** cDataTags)
828{
829    //printf("Removing whitespace in %s\n", String8(mElementName).string());
830    size_t N = mChildren.size();
831    if (cDataTags) {
832        String8 tag(mElementName);
833        const char** p = cDataTags;
834        while (*p) {
835            if (tag == *p) {
836                stripAll = false;
837                break;
838            }
839        }
840    }
841    for (size_t i=0; i<N; i++) {
842        sp<XMLNode> node = mChildren.itemAt(i);
843        if (node->getType() == TYPE_CDATA) {
844            // This is a CDATA node...
845            const char16_t* p = node->mChars.string();
846            while (*p != 0 && *p < 128 && isspace(*p)) {
847                p++;
848            }
849            //printf("Space ends at %d in \"%s\"\n",
850            //       (int)(p-node->mChars.string()),
851            //       String8(node->mChars).string());
852            if (*p == 0) {
853                if (stripAll) {
854                    // Remove this node!
855                    mChildren.removeAt(i);
856                    N--;
857                    i--;
858                } else {
859                    node->mChars = String16(" ");
860                }
861            } else {
862                // Compact leading/trailing whitespace.
863                const char16_t* e = node->mChars.string()+node->mChars.size()-1;
864                while (e > p && *e < 128 && isspace(*e)) {
865                    e--;
866                }
867                if (p > node->mChars.string()) {
868                    p--;
869                }
870                if (e < (node->mChars.string()+node->mChars.size()-1)) {
871                    e++;
872                }
873                if (p > node->mChars.string() ||
874                    e < (node->mChars.string()+node->mChars.size()-1)) {
875                    String16 tmp(p, e-p+1);
876                    node->mChars = tmp;
877                }
878            }
879        } else {
880            node->removeWhitespace(stripAll, cDataTags);
881        }
882    }
883}
884
885status_t XMLNode::parseValues(const sp<AaptAssets>& assets,
886                              ResourceTable* table)
887{
888    bool hasErrors = false;
889
890    if (getType() == TYPE_ELEMENT) {
891        const size_t N = mAttributes.size();
892        String16 defPackage(assets->getPackage());
893        for (size_t i=0; i<N; i++) {
894            attribute_entry& e = mAttributes.editItemAt(i);
895            AccessorCookie ac(SourcePos(mFilename, getStartLineNumber()), String8(e.name),
896                    String8(e.string));
897            table->setCurrentXmlPos(SourcePos(mFilename, getStartLineNumber()));
898            if (!assets->getIncludedResources()
899                    .stringToValue(&e.value, &e.string,
900                                  e.string.string(), e.string.size(), true, true,
901                                  e.nameResId, NULL, &defPackage, table, &ac)) {
902                hasErrors = true;
903            }
904            NOISY(printf("Attr %s: type=0x%x, str=%s\n",
905                   String8(e.name).string(), e.value.dataType,
906                   String8(e.string).string()));
907        }
908    }
909    const size_t N = mChildren.size();
910    for (size_t i=0; i<N; i++) {
911        status_t err = mChildren.itemAt(i)->parseValues(assets, table);
912        if (err != NO_ERROR) {
913            hasErrors = true;
914        }
915    }
916    return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
917}
918
919status_t XMLNode::assignResourceIds(const sp<AaptAssets>& assets,
920                                    const ResourceTable* table)
921{
922    bool hasErrors = false;
923
924    if (getType() == TYPE_ELEMENT) {
925        String16 attr("attr");
926        const char* errorMsg;
927        const size_t N = mAttributes.size();
928        for (size_t i=0; i<N; i++) {
929            const attribute_entry& e = mAttributes.itemAt(i);
930            if (e.ns.size() <= 0) continue;
931            bool nsIsPublic;
932            String16 pkg(getNamespaceResourcePackage(String16(assets->getPackage()), e.ns, &nsIsPublic));
933            NOISY(printf("Elem %s %s=\"%s\": namespace(%s) %s ===> %s\n",
934                    String8(getElementName()).string(),
935                    String8(e.name).string(),
936                    String8(e.string).string(),
937                    String8(e.ns).string(),
938                    (nsIsPublic) ? "public" : "private",
939                    String8(pkg).string()));
940            if (pkg.size() <= 0) continue;
941            uint32_t res = table != NULL
942                ? table->getResId(e.name, &attr, &pkg, &errorMsg, nsIsPublic)
943                : assets->getIncludedResources().
944                    identifierForName(e.name.string(), e.name.size(),
945                                      attr.string(), attr.size(),
946                                      pkg.string(), pkg.size());
947            if (res != 0) {
948                NOISY(printf("XML attribute name %s: resid=0x%08x\n",
949                             String8(e.name).string(), res));
950                setAttributeResID(i, res);
951            } else {
952                SourcePos(mFilename, getStartLineNumber()).error(
953                        "No resource identifier found for attribute '%s' in package '%s'\n",
954                        String8(e.name).string(), String8(pkg).string());
955                hasErrors = true;
956            }
957        }
958    }
959    const size_t N = mChildren.size();
960    for (size_t i=0; i<N; i++) {
961        status_t err = mChildren.itemAt(i)->assignResourceIds(assets, table);
962        if (err < NO_ERROR) {
963            hasErrors = true;
964        }
965    }
966
967    return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
968}
969
970status_t XMLNode::flatten(const sp<AaptFile>& dest,
971        bool stripComments, bool stripRawValues) const
972{
973    StringPool strings = StringPool(false, mUTF8);
974    Vector<uint32_t> resids;
975
976    // First collect just the strings for attribute names that have a
977    // resource ID assigned to them.  This ensures that the resource ID
978    // array is compact, and makes it easier to deal with attribute names
979    // in different namespaces (and thus with different resource IDs).
980    collect_resid_strings(&strings, &resids);
981
982    // Next collect all remainibng strings.
983    collect_strings(&strings, &resids, stripComments, stripRawValues);
984
985#if 0  // No longer compiles
986    NOISY(printf("Found strings:\n");
987        const size_t N = strings.size();
988        for (size_t i=0; i<N; i++) {
989            printf("%s\n", String8(strings.entryAt(i).string).string());
990        }
991    );
992#endif
993
994    sp<AaptFile> stringPool = strings.createStringBlock();
995    NOISY(aout << "String pool:"
996          << HexDump(stringPool->getData(), stringPool->getSize()) << endl);
997
998    ResXMLTree_header header;
999    memset(&header, 0, sizeof(header));
1000    header.header.type = htods(RES_XML_TYPE);
1001    header.header.headerSize = htods(sizeof(header));
1002
1003    const size_t basePos = dest->getSize();
1004    dest->writeData(&header, sizeof(header));
1005    dest->writeData(stringPool->getData(), stringPool->getSize());
1006
1007    // If we have resource IDs, write them.
1008    if (resids.size() > 0) {
1009        const size_t resIdsPos = dest->getSize();
1010        const size_t resIdsSize =
1011            sizeof(ResChunk_header)+(sizeof(uint32_t)*resids.size());
1012        ResChunk_header* idsHeader = (ResChunk_header*)
1013            (((const uint8_t*)dest->editData(resIdsPos+resIdsSize))+resIdsPos);
1014        idsHeader->type = htods(RES_XML_RESOURCE_MAP_TYPE);
1015        idsHeader->headerSize = htods(sizeof(*idsHeader));
1016        idsHeader->size = htodl(resIdsSize);
1017        uint32_t* ids = (uint32_t*)(idsHeader+1);
1018        for (size_t i=0; i<resids.size(); i++) {
1019            *ids++ = htodl(resids[i]);
1020        }
1021    }
1022
1023    flatten_node(strings, dest, stripComments, stripRawValues);
1024
1025    void* data = dest->editData();
1026    ResXMLTree_header* hd = (ResXMLTree_header*)(((uint8_t*)data)+basePos);
1027    size_t size = dest->getSize()-basePos;
1028    hd->header.size = htodl(dest->getSize()-basePos);
1029
1030    NOISY(aout << "XML resource:"
1031          << HexDump(dest->getData(), dest->getSize()) << endl);
1032
1033    #if PRINT_STRING_METRICS
1034    fprintf(stderr, "**** total xml size: %d / %d%% strings (in %s)\n",
1035        dest->getSize(), (stringPool->getSize()*100)/dest->getSize(),
1036        dest->getPath().string());
1037    #endif
1038
1039    return NO_ERROR;
1040}
1041
1042void XMLNode::print(int indent)
1043{
1044    String8 prefix;
1045    int i;
1046    for (i=0; i<indent; i++) {
1047        prefix.append("  ");
1048    }
1049    if (getType() == TYPE_ELEMENT) {
1050        String8 elemNs(getNamespaceUri());
1051        if (elemNs.size() > 0) {
1052            elemNs.append(":");
1053        }
1054        printf("%s E: %s%s", prefix.string(),
1055               elemNs.string(), String8(getElementName()).string());
1056        int N = mAttributes.size();
1057        for (i=0; i<N; i++) {
1058            ssize_t idx = mAttributeOrder.valueAt(i);
1059            if (i == 0) {
1060                printf(" / ");
1061            } else {
1062                printf(", ");
1063            }
1064            const attribute_entry& attr = mAttributes.itemAt(idx);
1065            String8 attrNs(attr.ns);
1066            if (attrNs.size() > 0) {
1067                attrNs.append(":");
1068            }
1069            if (attr.nameResId) {
1070                printf("%s%s(0x%08x)", attrNs.string(),
1071                       String8(attr.name).string(), attr.nameResId);
1072            } else {
1073                printf("%s%s", attrNs.string(), String8(attr.name).string());
1074            }
1075            printf("=%s", String8(attr.string).string());
1076        }
1077        printf("\n");
1078    } else if (getType() == TYPE_NAMESPACE) {
1079        printf("%s N: %s=%s\n", prefix.string(),
1080               getNamespacePrefix().size() > 0
1081                    ? String8(getNamespacePrefix()).string() : "<DEF>",
1082               String8(getNamespaceUri()).string());
1083    } else {
1084        printf("%s C: \"%s\"\n", prefix.string(), String8(getCData()).string());
1085    }
1086    int N = mChildren.size();
1087    for (i=0; i<N; i++) {
1088        mChildren.itemAt(i)->print(indent+1);
1089    }
1090}
1091
1092static void splitName(const char* name, String16* outNs, String16* outName)
1093{
1094    const char* p = name;
1095    while (*p != 0 && *p != 1) {
1096        p++;
1097    }
1098    if (*p == 0) {
1099        *outNs = String16();
1100        *outName = String16(name);
1101    } else {
1102        *outNs = String16(name, (p-name));
1103        *outName = String16(p+1);
1104    }
1105}
1106
1107void XMLCALL
1108XMLNode::startNamespace(void *userData, const char *prefix, const char *uri)
1109{
1110    NOISY_PARSE(printf("Start Namespace: %s %s\n", prefix, uri));
1111    ParseState* st = (ParseState*)userData;
1112    sp<XMLNode> node = XMLNode::newNamespace(st->filename,
1113            String16(prefix != NULL ? prefix : ""), String16(uri));
1114    node->setStartLineNumber(XML_GetCurrentLineNumber(st->parser));
1115    if (st->stack.size() > 0) {
1116        st->stack.itemAt(st->stack.size()-1)->addChild(node);
1117    } else {
1118        st->root = node;
1119    }
1120    st->stack.push(node);
1121}
1122
1123void XMLCALL
1124XMLNode::startElement(void *userData, const char *name, const char **atts)
1125{
1126    NOISY_PARSE(printf("Start Element: %s\n", name));
1127    ParseState* st = (ParseState*)userData;
1128    String16 ns16, name16;
1129    splitName(name, &ns16, &name16);
1130    sp<XMLNode> node = XMLNode::newElement(st->filename, ns16, name16);
1131    node->setStartLineNumber(XML_GetCurrentLineNumber(st->parser));
1132    if (st->pendingComment.size() > 0) {
1133        node->appendComment(st->pendingComment);
1134        st->pendingComment = String16();
1135    }
1136    if (st->stack.size() > 0) {
1137        st->stack.itemAt(st->stack.size()-1)->addChild(node);
1138    } else {
1139        st->root = node;
1140    }
1141    st->stack.push(node);
1142
1143    for (int i = 0; atts[i]; i += 2) {
1144        splitName(atts[i], &ns16, &name16);
1145        node->addAttribute(ns16, name16, String16(atts[i+1]));
1146    }
1147}
1148
1149void XMLCALL
1150XMLNode::characterData(void *userData, const XML_Char *s, int len)
1151{
1152    NOISY_PARSE(printf("CDATA: \"%s\"\n", String8(s, len).string()));
1153    ParseState* st = (ParseState*)userData;
1154    sp<XMLNode> node = NULL;
1155    if (st->stack.size() == 0) {
1156        return;
1157    }
1158    sp<XMLNode> parent = st->stack.itemAt(st->stack.size()-1);
1159    if (parent != NULL && parent->getChildren().size() > 0) {
1160        node = parent->getChildren()[parent->getChildren().size()-1];
1161        if (node->getType() != TYPE_CDATA) {
1162            // Last node is not CDATA, need to make a new node.
1163            node = NULL;
1164        }
1165    }
1166
1167    if (node == NULL) {
1168        node = XMLNode::newCData(st->filename);
1169        node->setStartLineNumber(XML_GetCurrentLineNumber(st->parser));
1170        parent->addChild(node);
1171    }
1172
1173    node->appendChars(String16(s, len));
1174}
1175
1176void XMLCALL
1177XMLNode::endElement(void *userData, const char *name)
1178{
1179    NOISY_PARSE(printf("End Element: %s\n", name));
1180    ParseState* st = (ParseState*)userData;
1181    sp<XMLNode> node = st->stack.itemAt(st->stack.size()-1);
1182    node->setEndLineNumber(XML_GetCurrentLineNumber(st->parser));
1183    if (st->pendingComment.size() > 0) {
1184        node->appendComment(st->pendingComment);
1185        st->pendingComment = String16();
1186    }
1187    String16 ns16, name16;
1188    splitName(name, &ns16, &name16);
1189    LOG_ALWAYS_FATAL_IF(node->getElementNamespace() != ns16
1190                        || node->getElementName() != name16,
1191                        "Bad end element %s", name);
1192    st->stack.pop();
1193}
1194
1195void XMLCALL
1196XMLNode::endNamespace(void *userData, const char *prefix)
1197{
1198    const char* nonNullPrefix = prefix != NULL ? prefix : "";
1199    NOISY_PARSE(printf("End Namespace: %s\n", prefix));
1200    ParseState* st = (ParseState*)userData;
1201    sp<XMLNode> node = st->stack.itemAt(st->stack.size()-1);
1202    node->setEndLineNumber(XML_GetCurrentLineNumber(st->parser));
1203    LOG_ALWAYS_FATAL_IF(node->getNamespacePrefix() != String16(nonNullPrefix),
1204                        "Bad end namespace %s", prefix);
1205    st->stack.pop();
1206}
1207
1208void XMLCALL
1209XMLNode::commentData(void *userData, const char *comment)
1210{
1211    NOISY_PARSE(printf("Comment: %s\n", comment));
1212    ParseState* st = (ParseState*)userData;
1213    if (st->pendingComment.size() > 0) {
1214        st->pendingComment.append(String16("\n"));
1215    }
1216    st->pendingComment.append(String16(comment));
1217}
1218
1219status_t XMLNode::collect_strings(StringPool* dest, Vector<uint32_t>* outResIds,
1220        bool stripComments, bool stripRawValues) const
1221{
1222    collect_attr_strings(dest, outResIds, true);
1223
1224    int i;
1225    if (mNamespacePrefix.size() > 0) {
1226        dest->add(mNamespacePrefix, true);
1227    }
1228    if (mNamespaceUri.size() > 0) {
1229        dest->add(mNamespaceUri, true);
1230    }
1231    if (mElementName.size() > 0) {
1232        dest->add(mElementName, true);
1233    }
1234
1235    if (!stripComments && mComment.size() > 0) {
1236        dest->add(mComment, true);
1237    }
1238
1239    const int NA = mAttributes.size();
1240
1241    for (i=0; i<NA; i++) {
1242        const attribute_entry& ae = mAttributes.itemAt(i);
1243        if (ae.ns.size() > 0) {
1244            dest->add(ae.ns, true);
1245        }
1246        if (!stripRawValues || ae.needStringValue()) {
1247            dest->add(ae.string, true);
1248        }
1249        /*
1250        if (ae.value.dataType == Res_value::TYPE_NULL
1251                || ae.value.dataType == Res_value::TYPE_STRING) {
1252            dest->add(ae.string, true);
1253        }
1254        */
1255    }
1256
1257    if (mElementName.size() == 0) {
1258        // If not an element, include the CDATA, even if it is empty.
1259        dest->add(mChars, true);
1260    }
1261
1262    const int NC = mChildren.size();
1263
1264    for (i=0; i<NC; i++) {
1265        mChildren.itemAt(i)->collect_strings(dest, outResIds,
1266                stripComments, stripRawValues);
1267    }
1268
1269    return NO_ERROR;
1270}
1271
1272status_t XMLNode::collect_attr_strings(StringPool* outPool,
1273        Vector<uint32_t>* outResIds, bool allAttrs) const {
1274    const int NA = mAttributes.size();
1275
1276    for (int i=0; i<NA; i++) {
1277        const attribute_entry& attr = mAttributes.itemAt(i);
1278        uint32_t id = attr.nameResId;
1279        if (id || allAttrs) {
1280            // See if we have already assigned this resource ID to a pooled
1281            // string...
1282            const Vector<size_t>* indices = outPool->offsetsForString(attr.name);
1283            ssize_t idx = -1;
1284            if (indices != NULL) {
1285                const int NJ = indices->size();
1286                const size_t NR = outResIds->size();
1287                for (int j=0; j<NJ; j++) {
1288                    size_t strIdx = indices->itemAt(j);
1289                    if (strIdx >= NR) {
1290                        if (id == 0) {
1291                            // We don't need to assign a resource ID for this one.
1292                            idx = strIdx;
1293                            break;
1294                        }
1295                        // Just ignore strings that are out of range of
1296                        // the currently assigned resource IDs...  we add
1297                        // strings as we assign the first ID.
1298                    } else if (outResIds->itemAt(strIdx) == id) {
1299                        idx = strIdx;
1300                        break;
1301                    }
1302                }
1303            }
1304            if (idx < 0) {
1305                idx = outPool->add(attr.name);
1306                NOISY(printf("Adding attr %s (resid 0x%08x) to pool: idx=%d\n",
1307                        String8(attr.name).string(), id, idx));
1308                if (id != 0) {
1309                    while ((ssize_t)outResIds->size() <= idx) {
1310                        outResIds->add(0);
1311                    }
1312                    outResIds->replaceAt(id, idx);
1313                }
1314            }
1315            attr.namePoolIdx = idx;
1316            NOISY(printf("String %s offset=0x%08x\n",
1317                         String8(attr.name).string(), idx));
1318        }
1319    }
1320
1321    return NO_ERROR;
1322}
1323
1324status_t XMLNode::collect_resid_strings(StringPool* outPool,
1325        Vector<uint32_t>* outResIds) const
1326{
1327    collect_attr_strings(outPool, outResIds, false);
1328
1329    const int NC = mChildren.size();
1330
1331    for (int i=0; i<NC; i++) {
1332        mChildren.itemAt(i)->collect_resid_strings(outPool, outResIds);
1333    }
1334
1335    return NO_ERROR;
1336}
1337
1338status_t XMLNode::flatten_node(const StringPool& strings, const sp<AaptFile>& dest,
1339        bool stripComments, bool stripRawValues) const
1340{
1341    ResXMLTree_node node;
1342    ResXMLTree_cdataExt cdataExt;
1343    ResXMLTree_namespaceExt namespaceExt;
1344    ResXMLTree_attrExt attrExt;
1345    const void* extData = NULL;
1346    size_t extSize = 0;
1347    ResXMLTree_attribute attr;
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        node.header.type = htods(RES_XML_START_NAMESPACE_TYPE);
1406        extData = &namespaceExt;
1407        extSize = sizeof(namespaceExt);
1408        memset(&namespaceExt, 0, sizeof(namespaceExt));
1409        if (mNamespacePrefix.size() > 0) {
1410            namespaceExt.prefix.index = htodl(strings.offsetForString(mNamespacePrefix));
1411        } else {
1412            namespaceExt.prefix.index = htodl((uint32_t)-1);
1413        }
1414        namespaceExt.prefix.index = htodl(strings.offsetForString(mNamespacePrefix));
1415        namespaceExt.uri.index = htodl(strings.offsetForString(mNamespaceUri));
1416        LOG_ALWAYS_FATAL_IF(NA != 0, "Namespace nodes can't have attributes!");
1417    } else if (type == TYPE_CDATA) {
1418        node.header.type = htods(RES_XML_CDATA_TYPE);
1419        extData = &cdataExt;
1420        extSize = sizeof(cdataExt);
1421        memset(&cdataExt, 0, sizeof(cdataExt));
1422        cdataExt.data.index = htodl(strings.offsetForString(mChars));
1423        cdataExt.typedData.size = htods(sizeof(cdataExt.typedData));
1424        cdataExt.typedData.res0 = 0;
1425        cdataExt.typedData.dataType = mCharsValue.dataType;
1426        cdataExt.typedData.data = htodl(mCharsValue.data);
1427        LOG_ALWAYS_FATAL_IF(NA != 0, "CDATA nodes can't have attributes!");
1428    }
1429
1430    node.header.size = htodl(sizeof(node) + extSize + (sizeof(attr)*NA));
1431
1432    dest->writeData(&node, sizeof(node));
1433    if (extSize > 0) {
1434        dest->writeData(extData, extSize);
1435    }
1436
1437    for (i=0; i<NA; i++) {
1438        ssize_t idx = mAttributeOrder.valueAt(i);
1439        const attribute_entry& ae = mAttributes.itemAt(idx);
1440        if (ae.ns.size() > 0) {
1441            attr.ns.index = htodl(strings.offsetForString(ae.ns));
1442        } else {
1443            attr.ns.index = htodl((uint32_t)-1);
1444        }
1445        attr.name.index = htodl(ae.namePoolIdx);
1446
1447        if (!stripRawValues || ae.needStringValue()) {
1448            attr.rawValue.index = htodl(strings.offsetForString(ae.string));
1449        } else {
1450            attr.rawValue.index = htodl((uint32_t)-1);
1451        }
1452        attr.typedValue.size = htods(sizeof(attr.typedValue));
1453        if (ae.value.dataType == Res_value::TYPE_NULL
1454                || ae.value.dataType == Res_value::TYPE_STRING) {
1455            attr.typedValue.res0 = 0;
1456            attr.typedValue.dataType = Res_value::TYPE_STRING;
1457            attr.typedValue.data = htodl(strings.offsetForString(ae.string));
1458        } else {
1459            attr.typedValue.res0 = 0;
1460            attr.typedValue.dataType = ae.value.dataType;
1461            attr.typedValue.data = htodl(ae.value.data);
1462        }
1463        dest->writeData(&attr, sizeof(attr));
1464    }
1465
1466    for (i=0; i<NC; i++) {
1467        status_t err = mChildren.itemAt(i)->flatten_node(strings, dest,
1468                stripComments, stripRawValues);
1469        if (err != NO_ERROR) {
1470            return err;
1471        }
1472    }
1473
1474    if (type == TYPE_ELEMENT) {
1475        ResXMLTree_endElementExt endElementExt;
1476        memset(&endElementExt, 0, sizeof(endElementExt));
1477        node.header.type = htods(RES_XML_END_ELEMENT_TYPE);
1478        node.header.size = htodl(sizeof(node)+sizeof(endElementExt));
1479        node.lineNumber = htodl(getEndLineNumber());
1480        node.comment.index = htodl((uint32_t)-1);
1481        endElementExt.ns.index = attrExt.ns.index;
1482        endElementExt.name.index = attrExt.name.index;
1483        dest->writeData(&node, sizeof(node));
1484        dest->writeData(&endElementExt, sizeof(endElementExt));
1485    } else if (type == TYPE_NAMESPACE) {
1486        node.header.type = htods(RES_XML_END_NAMESPACE_TYPE);
1487        node.lineNumber = htodl(getEndLineNumber());
1488        node.comment.index = htodl((uint32_t)-1);
1489        node.header.size = htodl(sizeof(node)+extSize);
1490        dest->writeData(&node, sizeof(node));
1491        dest->writeData(extData, extSize);
1492    }
1493
1494    return NO_ERROR;
1495}
1496