2 // Copyright 2006 The Android Open Source Project
4 // Build resource files from raw assets.
8 #include "ResourceTable.h"
10 #include <host/pseudolocalize.h>
11 #include <utils/ByteOrder.h>
15 #ifndef HAVE_MS_C_RUNTIME
20 #define NOISY_PARSE(x) //x
22 const char* const RESOURCES_ROOT_NAMESPACE
= "http://schemas.android.com/apk/res/";
23 const char* const RESOURCES_ANDROID_NAMESPACE
= "http://schemas.android.com/apk/res/android";
24 const char* const RESOURCES_AUTO_PACKAGE_NAMESPACE
= "http://schemas.android.com/apk/res-auto";
25 const char* const RESOURCES_ROOT_PRV_NAMESPACE
= "http://schemas.android.com/apk/prv/res/";
27 const char* const XLIFF_XMLNS
= "urn:oasis:names:tc:xliff:document:1.2";
28 const char* const ALLOWED_XLIFF_ELEMENTS
[] = {
39 bool isWhitespace(const char16_t* str
)
41 while (*str
!= 0 && *str
< 128 && isspace(*str
)) {
47 static const String16
RESOURCES_PREFIX(RESOURCES_ROOT_NAMESPACE
);
48 static const String16
RESOURCES_PREFIX_AUTO_PACKAGE(RESOURCES_AUTO_PACKAGE_NAMESPACE
);
49 static const String16
RESOURCES_PRV_PREFIX(RESOURCES_ROOT_PRV_NAMESPACE
);
50 static const String16
RESOURCES_TOOLS_NAMESPACE("http://schemas.android.com/tools");
52 String16
getNamespaceResourcePackage(String16 appPackage
, String16 namespaceUri
, bool* outIsPublic
)
54 //printf("%s starts with %s?\n", String8(namespaceUri).string(),
55 // String8(RESOURCES_PREFIX).string());
58 if(namespaceUri
.startsWith(RESOURCES_PREFIX_AUTO_PACKAGE
)) {
59 NOISY(printf("Using default application package: %s -> %s\n", String8(namespaceUri
).string(), String8(appPackage
).string()));
62 } else if (namespaceUri
.startsWith(RESOURCES_PREFIX
)) {
63 prefixSize
= RESOURCES_PREFIX
.size();
64 } else if (namespaceUri
.startsWith(RESOURCES_PRV_PREFIX
)) {
66 prefixSize
= RESOURCES_PRV_PREFIX
.size();
68 if (outIsPublic
) *outIsPublic
= isPublic
; // = true
73 //printf("namespace: %s\n", String8(String16(namespaceUri, namespaceUri.size()-prefixSize, prefixSize)).string());
74 if (outIsPublic
) *outIsPublic
= isPublic
;
75 return String16(namespaceUri
, namespaceUri
.size()-prefixSize
, prefixSize
);
78 status_t
hasSubstitutionErrors(const char* fileName
,
82 const char16_t* str
= str16
.string();
83 const char16_t* p
= str
;
84 const char16_t* end
= str
+ str16
.size();
86 bool nonpositional
= false;
91 * Look for the start of a Java-style substitution sequence.
93 if (*p
== '%' && p
+ 1 < end
) {
96 // A literal percent sign represented by %%
104 if (*p
>= '0' && *p
<= '9') {
107 } while (*p
>= '0' && *p
<= '9');
109 // This must be a size specification instead of position.
110 nonpositional
= true;
112 } else if (*p
== '<') {
113 // Reusing last argument; bad idea since it can be re-arranged.
114 nonpositional
= true;
117 // Optionally '$' can be specified at the end.
118 if (p
< end
&& *p
== '$') {
122 nonpositional
= true;
125 // Ignore flags and widths
126 while (p
< end
&& (*p
== '-' ||
132 (*p
>= '0' && *p
<= '9'))) {
137 * This is a shortcut to detect strings that are going to Time.format()
138 * instead of String.format()
140 * Comparison of String.format() and Time.format() args:
142 * String: ABC E GH ST X abcdefgh nost x
143 * Time: DEFGHKMS W Za d hkm s w yz
145 * Therefore we know it's definitely Time if we have:
170 * If we have more than one substitution in this string and any of them
171 * are not in positional form, give the user an error.
173 if (argCount
> 1 && nonpositional
) {
174 SourcePos(String8(fileName
), inXml
->getLineNumber()).error(
175 "Multiple substitutions specified in non-positional format; "
176 "did you mean to add the formatted=\"false\" attribute?\n");
177 return NOT_ENOUGH_DATA
;
183 status_t
parseStyledString(Bundle
* bundle
,
184 const char* fileName
,
186 const String16
& endTag
,
188 Vector
<StringPool::entry_style_span
>* outSpans
,
192 Vector
<StringPool::entry_style_span
> spanStack
;
195 const char* errorMsg
;
197 bool firstTime
= true;
200 ResXMLTree::event_code_t code
;
201 while ((code
=inXml
->next()) != ResXMLTree::END_DOCUMENT
&& code
!= ResXMLTree::BAD_DOCUMENT
) {
203 if (code
== ResXMLTree::TEXT
) {
204 String16
text(inXml
->getText(&len
));
205 if (firstTime
&& text
.size() > 0) {
207 if (text
.string()[0] == '@') {
208 // If this is a resource reference, don't do the pseudoloc.
209 pseudolocalize
= false;
212 if (xliffDepth
== 0 && pseudolocalize
) {
213 std::string
orig(String8(text
).string());
214 std::string pseudo
= pseudolocalize_string(orig
);
215 curString
.append(String16(String8(pseudo
.c_str())));
217 if (isFormatted
&& hasSubstitutionErrors(fileName
, inXml
, text
) != NO_ERROR
) {
218 return UNKNOWN_ERROR
;
220 curString
.append(text
);
223 } else if (code
== ResXMLTree::START_TAG
) {
224 const String16
element16(inXml
->getElementName(&len
));
225 const String8
element8(element16
);
228 const uint16_t* ns
= inXml
->getElementNamespace(&nslen
);
230 ns
= (const uint16_t*)"\0\0";
233 const String8
nspace(String16(ns
, nslen
));
234 if (nspace
== XLIFF_XMLNS
) {
235 const int N
= sizeof(ALLOWED_XLIFF_ELEMENTS
)/sizeof(ALLOWED_XLIFF_ELEMENTS
[0]);
236 for (int i
=0; i
<N
; i
++) {
237 if (element8
== ALLOWED_XLIFF_ELEMENTS
[i
]) {
239 // in this case, treat it like it was just text, in other words, do nothing
240 // here and silently drop this element
245 SourcePos(String8(fileName
), inXml
->getLineNumber()).error(
246 "Found unsupported XLIFF tag <%s>\n",
248 return UNKNOWN_ERROR
;
254 if (outSpans
== NULL
) {
255 SourcePos(String8(fileName
), inXml
->getLineNumber()).error(
256 "Found style tag <%s> where styles are not allowed\n", element8
.string());
257 return UNKNOWN_ERROR
;
260 if (!ResTable::collectString(outString
, curString
.string(),
261 curString
.size(), false, &errorMsg
, true)) {
262 SourcePos(String8(fileName
), inXml
->getLineNumber()).error("%s (in %s)\n",
263 errorMsg
, String8(curString
).string());
264 return UNKNOWN_ERROR
;
266 rawString
.append(curString
);
267 curString
= String16();
269 StringPool::entry_style_span span
;
270 span
.name
= element16
;
271 for (size_t ai
=0; ai
<inXml
->getAttributeCount(); ai
++) {
272 span
.name
.append(String16(";"));
273 const char16_t* str
= inXml
->getAttributeName(ai
, &len
);
274 span
.name
.append(str
, len
);
275 span
.name
.append(String16("="));
276 str
= inXml
->getAttributeStringValue(ai
, &len
);
277 span
.name
.append(str
, len
);
279 //printf("Span: %s\n", String8(span.name).string());
280 span
.span
.firstChar
= span
.span
.lastChar
= outString
->size();
281 spanStack
.push(span
);
283 } else if (code
== ResXMLTree::END_TAG
) {
285 const uint16_t* ns
= inXml
->getElementNamespace(&nslen
);
287 ns
= (const uint16_t*)"\0\0";
290 const String8
nspace(String16(ns
, nslen
));
291 if (nspace
== XLIFF_XMLNS
) {
295 if (!ResTable::collectString(outString
, curString
.string(),
296 curString
.size(), false, &errorMsg
, true)) {
297 SourcePos(String8(fileName
), inXml
->getLineNumber()).error("%s (in %s)\n",
298 errorMsg
, String8(curString
).string());
299 return UNKNOWN_ERROR
;
301 rawString
.append(curString
);
302 curString
= String16();
304 if (spanStack
.size() == 0) {
305 if (strcmp16(inXml
->getElementName(&len
), endTag
.string()) != 0) {
306 SourcePos(String8(fileName
), inXml
->getLineNumber()).error(
307 "Found tag %s where <%s> close is expected\n",
308 String8(inXml
->getElementName(&len
)).string(),
309 String8(endTag
).string());
310 return UNKNOWN_ERROR
;
314 StringPool::entry_style_span span
= spanStack
.top();
316 ssize_t semi
= span
.name
.findFirst(';');
318 spanTag
.setTo(span
.name
.string(), semi
);
320 spanTag
.setTo(span
.name
);
322 if (strcmp16(inXml
->getElementName(&len
), spanTag
.string()) != 0) {
323 SourcePos(String8(fileName
), inXml
->getLineNumber()).error(
324 "Found close tag %s where close tag %s is expected\n",
325 String8(inXml
->getElementName(&len
)).string(),
326 String8(spanTag
).string());
327 return UNKNOWN_ERROR
;
330 if (outString
->size() > 0) {
331 span
.span
.lastChar
= outString
->size()-1;
332 if (span
.span
.lastChar
>= span
.span
.firstChar
) {
340 * This warning seems to be just an irritation to most people,
341 * since it is typically introduced by translators who then never
345 fprintf(stderr
, "%s:%d: warning: empty '%s' span found in text '%s'\n",
346 fileName
, inXml
->getLineNumber(),
347 String8(spanTag
).string(), String8(*outString
).string());
350 } else if (code
== ResXMLTree::START_NAMESPACE
) {
355 if (code
== ResXMLTree::BAD_DOCUMENT
) {
356 SourcePos(String8(fileName
), inXml
->getLineNumber()).error(
357 "Error parsing XML\n");
360 if (outSpans
!= NULL
&& outSpans
->size() > 0) {
361 if (curString
.size() > 0) {
362 if (!ResTable::collectString(outString
, curString
.string(),
363 curString
.size(), false, &errorMsg
, true)) {
364 SourcePos(String8(fileName
), inXml
->getLineNumber()).error(
366 errorMsg
, String8(curString
).string());
367 return UNKNOWN_ERROR
;
371 // There is no style information, so string processing will happen
372 // later as part of the overall type conversion. Return to the
373 // client the raw unprocessed text.
374 rawString
.append(curString
);
375 outString
->setTo(rawString
);
381 struct namespace_entry
{
386 static String8
make_prefix(int depth
)
390 for (i
=0; i
<depth
; i
++) {
396 static String8
build_namespace(const Vector
<namespace_entry
>& namespaces
,
402 const size_t N
= namespaces
.size();
403 for (size_t i
=0; i
<N
; i
++) {
404 const namespace_entry
& ne
= namespaces
.itemAt(i
);
415 void printXMLBlock(ResXMLTree
* block
)
419 Vector
<namespace_entry
> namespaces
;
421 ResXMLTree::event_code_t code
;
423 while ((code
=block
->next()) != ResXMLTree::END_DOCUMENT
&& code
!= ResXMLTree::BAD_DOCUMENT
) {
424 String8 prefix
= make_prefix(depth
);
426 if (code
== ResXMLTree::START_TAG
) {
428 const uint16_t* ns16
= block
->getElementNamespace(&len
);
429 String8 elemNs
= build_namespace(namespaces
, ns16
);
430 const uint16_t* com16
= block
->getComment(&len
);
432 printf("%s <!-- %s -->\n", prefix
.string(), String8(com16
).string());
434 printf("%sE: %s%s (line=%d)\n", prefix
.string(), elemNs
.string(),
435 String8(block
->getElementName(&len
)).string(),
436 block
->getLineNumber());
437 int N
= block
->getAttributeCount();
439 prefix
= make_prefix(depth
);
440 for (i
=0; i
<N
; i
++) {
441 uint32_t res
= block
->getAttributeNameResID(i
);
442 ns16
= block
->getAttributeNamespace(i
, &len
);
443 String8 ns
= build_namespace(namespaces
, ns16
);
444 String8
name(block
->getAttributeName(i
, &len
));
445 printf("%sA: ", prefix
.string());
447 printf("%s%s(0x%08x)", ns
.string(), name
.string(), res
);
449 printf("%s%s", ns
.string(), name
.string());
452 block
->getAttributeValue(i
, &value
);
453 if (value
.dataType
== Res_value::TYPE_NULL
) {
455 } else if (value
.dataType
== Res_value::TYPE_REFERENCE
) {
456 printf("=@0x%x", (int)value
.data
);
457 } else if (value
.dataType
== Res_value::TYPE_ATTRIBUTE
) {
458 printf("=?0x%x", (int)value
.data
);
459 } else if (value
.dataType
== Res_value::TYPE_STRING
) {
461 ResTable::normalizeForOutput(String8(block
->getAttributeStringValue(i
,
462 &len
)).string()).string());
464 printf("=(type 0x%x)0x%x", (int)value
.dataType
, (int)value
.data
);
466 const char16_t* val
= block
->getAttributeStringValue(i
, &len
);
468 printf(" (Raw: \"%s\")", ResTable::normalizeForOutput(String8(val
).string()).
473 } else if (code
== ResXMLTree::END_TAG
) {
475 } else if (code
== ResXMLTree::START_NAMESPACE
) {
478 const uint16_t* prefix16
= block
->getNamespacePrefix(&len
);
480 ns
.prefix
= String8(prefix16
);
484 ns
.uri
= String8(block
->getNamespaceUri(&len
));
486 printf("%sN: %s=%s\n", prefix
.string(), ns
.prefix
.string(),
489 } else if (code
== ResXMLTree::END_NAMESPACE
) {
491 const namespace_entry
& ns
= namespaces
.top();
493 const uint16_t* prefix16
= block
->getNamespacePrefix(&len
);
496 pr
= String8(prefix16
);
500 if (ns
.prefix
!= pr
) {
501 prefix
= make_prefix(depth
);
502 printf("%s*** BAD END NS PREFIX: found=%s, expected=%s\n",
503 prefix
.string(), pr
.string(), ns
.prefix
.string());
505 String8 uri
= String8(block
->getNamespaceUri(&len
));
507 prefix
= make_prefix(depth
);
508 printf("%s *** BAD END NS URI: found=%s, expected=%s\n",
509 prefix
.string(), uri
.string(), ns
.uri
.string());
512 } else if (code
== ResXMLTree::TEXT
) {
514 printf("%sC: \"%s\"\n", prefix
.string(),
515 ResTable::normalizeForOutput(String8(block
->getText(&len
)).string()).string());
522 status_t
parseXMLResource(const sp
<AaptFile
>& file
, ResXMLTree
* outTree
,
523 bool stripAll
, bool keepComments
,
524 const char** cDataTags
)
526 sp
<XMLNode
> root
= XMLNode::parse(file
);
528 return UNKNOWN_ERROR
;
530 root
->removeWhitespace(stripAll
, cDataTags
);
532 NOISY(printf("Input XML from %s:\n", (const char*)file
->getPrintableSource()));
533 NOISY(root
->print());
534 sp
<AaptFile
> rsc
= new AaptFile(String8(), AaptGroupEntry(), String8());
535 status_t err
= root
->flatten(rsc
, !keepComments
, false);
536 if (err
!= NO_ERROR
) {
539 err
= outTree
->setTo(rsc
->getData(), rsc
->getSize(), true);
540 if (err
!= NO_ERROR
) {
544 NOISY(printf("Output XML:\n"));
545 NOISY(printXMLBlock(outTree
));
550 sp
<XMLNode
> XMLNode::parse(const sp
<AaptFile
>& file
)
553 int fd
= open(file
->getSourceFile().string(), O_RDONLY
| O_BINARY
);
555 SourcePos(file
->getSourceFile(), -1).error("Unable to open file for read: %s",
560 XML_Parser parser
= XML_ParserCreateNS(NULL
, 1);
562 state
.filename
= file
->getPrintableSource();
563 state
.parser
= parser
;
564 XML_SetUserData(parser
, &state
);
565 XML_SetElementHandler(parser
, startElement
, endElement
);
566 XML_SetNamespaceDeclHandler(parser
, startNamespace
, endNamespace
);
567 XML_SetCharacterDataHandler(parser
, characterData
);
568 XML_SetCommentHandler(parser
, commentData
);
573 len
= read(fd
, buf
, sizeof(buf
));
574 done
= len
< (ssize_t
)sizeof(buf
);
576 SourcePos(file
->getSourceFile(), -1).error("Error reading file: %s\n", strerror(errno
));
580 if (XML_Parse(parser
, buf
, len
, done
) == XML_STATUS_ERROR
) {
581 SourcePos(file
->getSourceFile(), (int)XML_GetCurrentLineNumber(parser
)).error(
582 "Error parsing XML: %s\n", XML_ErrorString(XML_GetErrorCode(parser
)));
588 XML_ParserFree(parser
);
589 if (state
.root
== NULL
) {
590 SourcePos(file
->getSourceFile(), -1).error("No XML data generated when parsing");
596 XMLNode::XMLNode(const String8
& filename
, const String16
& s1
, const String16
& s2
, bool isNamespace
)
597 : mNextAttributeIndex(0x80000000)
598 , mFilename(filename
)
599 , mStartLineNumber(0)
604 mNamespacePrefix
= s1
;
612 XMLNode::XMLNode(const String8
& filename
)
613 : mFilename(filename
)
615 memset(&mCharsValue
, 0, sizeof(mCharsValue
));
618 XMLNode::type
XMLNode::getType() const
620 if (mElementName
.size() != 0) {
623 if (mNamespaceUri
.size() != 0) {
624 return TYPE_NAMESPACE
;
629 const String16
& XMLNode::getNamespacePrefix() const
631 return mNamespacePrefix
;
634 const String16
& XMLNode::getNamespaceUri() const
636 return mNamespaceUri
;
639 const String16
& XMLNode::getElementNamespace() const
641 return mNamespaceUri
;
644 const String16
& XMLNode::getElementName() const
649 const Vector
<sp
<XMLNode
> >& XMLNode::getChildren() const
654 const String8
& XMLNode::getFilename() const
659 const Vector
<XMLNode::attribute_entry
>&
660 XMLNode::getAttributes() const
665 const XMLNode::attribute_entry
* XMLNode::getAttribute(const String16
& ns
,
666 const String16
& name
) const
668 for (size_t i
=0; i
<mAttributes
.size(); i
++) {
669 const attribute_entry
& ae(mAttributes
.itemAt(i
));
670 if (ae
.ns
== ns
&& ae
.name
== name
) {
678 XMLNode::attribute_entry
* XMLNode::editAttribute(const String16
& ns
,
679 const String16
& name
)
681 for (size_t i
=0; i
<mAttributes
.size(); i
++) {
682 attribute_entry
* ae
= &mAttributes
.editItemAt(i
);
683 if (ae
->ns
== ns
&& ae
->name
== name
) {
691 const String16
& XMLNode::getCData() const
696 const String16
& XMLNode::getComment() const
701 int32_t XMLNode::getStartLineNumber() const
703 return mStartLineNumber
;
706 int32_t XMLNode::getEndLineNumber() const
708 return mEndLineNumber
;
711 sp
<XMLNode
> XMLNode::searchElement(const String16
& tagNamespace
, const String16
& tagName
)
713 if (getType() == XMLNode::TYPE_ELEMENT
714 && mNamespaceUri
== tagNamespace
715 && mElementName
== tagName
) {
719 for (size_t i
=0; i
<mChildren
.size(); i
++) {
720 sp
<XMLNode
> found
= mChildren
.itemAt(i
)->searchElement(tagNamespace
, tagName
);
729 sp
<XMLNode
> XMLNode::getChildElement(const String16
& tagNamespace
, const String16
& tagName
)
731 for (size_t i
=0; i
<mChildren
.size(); i
++) {
732 sp
<XMLNode
> child
= mChildren
.itemAt(i
);
733 if (child
->getType() == XMLNode::TYPE_ELEMENT
734 && child
->mNamespaceUri
== tagNamespace
735 && child
->mElementName
== tagName
) {
743 status_t
XMLNode::addChild(const sp
<XMLNode
>& child
)
745 if (getType() == TYPE_CDATA
) {
746 SourcePos(mFilename
, child
->getStartLineNumber()).error("Child to CDATA node.");
747 return UNKNOWN_ERROR
;
749 //printf("Adding child %p to parent %p\n", child.get(), this);
750 mChildren
.add(child
);
754 status_t
XMLNode::insertChildAt(const sp
<XMLNode
>& child
, size_t index
)
756 if (getType() == TYPE_CDATA
) {
757 SourcePos(mFilename
, child
->getStartLineNumber()).error("Child to CDATA node.");
758 return UNKNOWN_ERROR
;
760 //printf("Adding child %p to parent %p\n", child.get(), this);
761 mChildren
.insertAt(child
, index
);
765 status_t
XMLNode::addAttribute(const String16
& ns
, const String16
& name
,
766 const String16
& value
)
768 if (getType() == TYPE_CDATA
) {
769 SourcePos(mFilename
, getStartLineNumber()).error("Child to CDATA node.");
770 return UNKNOWN_ERROR
;
773 if (ns
!= RESOURCES_TOOLS_NAMESPACE
) {
775 e
.index
= mNextAttributeIndex
++;
780 mAttributeOrder
.add(e
.index
, mAttributes
.size()-1);
785 void XMLNode::setAttributeResID(size_t attrIdx
, uint32_t resId
)
787 attribute_entry
& e
= mAttributes
.editItemAt(attrIdx
);
789 mAttributeOrder
.removeItem(e
.nameResId
);
791 mAttributeOrder
.removeItem(e
.index
);
793 NOISY(printf("Elem %s %s=\"%s\": set res id = 0x%08x\n",
794 String8(getElementName()).string(),
795 String8(mAttributes
.itemAt(attrIdx
).name
).string(),
796 String8(mAttributes
.itemAt(attrIdx
).string
).string(),
798 mAttributes
.editItemAt(attrIdx
).nameResId
= resId
;
799 mAttributeOrder
.add(resId
, attrIdx
);
802 status_t
XMLNode::appendChars(const String16
& chars
)
804 if (getType() != TYPE_CDATA
) {
805 SourcePos(mFilename
, getStartLineNumber()).error("Adding characters to element node.");
806 return UNKNOWN_ERROR
;
808 mChars
.append(chars
);
812 status_t
XMLNode::appendComment(const String16
& comment
)
814 if (mComment
.size() > 0) {
815 mComment
.append(String16("\n"));
817 mComment
.append(comment
);
821 void XMLNode::setStartLineNumber(int32_t line
)
823 mStartLineNumber
= line
;
826 void XMLNode::setEndLineNumber(int32_t line
)
828 mEndLineNumber
= line
;
831 void XMLNode::removeWhitespace(bool stripAll
, const char** cDataTags
)
833 //printf("Removing whitespace in %s\n", String8(mElementName).string());
834 size_t N
= mChildren
.size();
836 String8
tag(mElementName
);
837 const char** p
= cDataTags
;
845 for (size_t i
=0; i
<N
; i
++) {
846 sp
<XMLNode
> node
= mChildren
.itemAt(i
);
847 if (node
->getType() == TYPE_CDATA
) {
848 // This is a CDATA node...
849 const char16_t* p
= node
->mChars
.string();
850 while (*p
!= 0 && *p
< 128 && isspace(*p
)) {
853 //printf("Space ends at %d in \"%s\"\n",
854 // (int)(p-node->mChars.string()),
855 // String8(node->mChars).string());
859 mChildren
.removeAt(i
);
863 node
->mChars
= String16(" ");
866 // Compact leading/trailing whitespace.
867 const char16_t* e
= node
->mChars
.string()+node
->mChars
.size()-1;
868 while (e
> p
&& *e
< 128 && isspace(*e
)) {
871 if (p
> node
->mChars
.string()) {
874 if (e
< (node
->mChars
.string()+node
->mChars
.size()-1)) {
877 if (p
> node
->mChars
.string() ||
878 e
< (node
->mChars
.string()+node
->mChars
.size()-1)) {
879 String16
tmp(p
, e
-p
+1);
884 node
->removeWhitespace(stripAll
, cDataTags
);
889 status_t
XMLNode::parseValues(const sp
<AaptAssets
>& assets
,
890 ResourceTable
* table
)
892 bool hasErrors
= false;
894 if (getType() == TYPE_ELEMENT
) {
895 const size_t N
= mAttributes
.size();
896 String16
defPackage(assets
->getPackage());
897 for (size_t i
=0; i
<N
; i
++) {
898 attribute_entry
& e
= mAttributes
.editItemAt(i
);
899 AccessorCookie
ac(SourcePos(mFilename
, getStartLineNumber()), String8(e
.name
),
901 table
->setCurrentXmlPos(SourcePos(mFilename
, getStartLineNumber()));
902 if (!assets
->getIncludedResources()
903 .stringToValue(&e
.value
, &e
.string
,
904 e
.string
.string(), e
.string
.size(), true, true,
905 e
.nameResId
, NULL
, &defPackage
, table
, &ac
)) {
908 NOISY(printf("Attr %s: type=0x%x, str=%s\n",
909 String8(e
.name
).string(), e
.value
.dataType
,
910 String8(e
.string
).string()));
913 const size_t N
= mChildren
.size();
914 for (size_t i
=0; i
<N
; i
++) {
915 status_t err
= mChildren
.itemAt(i
)->parseValues(assets
, table
);
916 if (err
!= NO_ERROR
) {
920 return hasErrors
? UNKNOWN_ERROR
: NO_ERROR
;
923 status_t
XMLNode::assignResourceIds(const sp
<AaptAssets
>& assets
,
924 const ResourceTable
* table
)
926 bool hasErrors
= false;
928 if (getType() == TYPE_ELEMENT
) {
929 String16
attr("attr");
930 const char* errorMsg
;
931 const size_t N
= mAttributes
.size();
932 for (size_t i
=0; i
<N
; i
++) {
933 const attribute_entry
& e
= mAttributes
.itemAt(i
);
934 if (e
.ns
.size() <= 0) continue;
936 String16
pkg(getNamespaceResourcePackage(String16(assets
->getPackage()), e
.ns
, &nsIsPublic
));
937 NOISY(printf("Elem %s %s=\"%s\": namespace(%s) %s ===> %s\n",
938 String8(getElementName()).string(),
939 String8(e
.name
).string(),
940 String8(e
.string
).string(),
941 String8(e
.ns
).string(),
942 (nsIsPublic
) ? "public" : "private",
943 String8(pkg
).string()));
944 if (pkg
.size() <= 0) continue;
945 uint32_t res
= table
!= NULL
946 ? table
->getResId(e
.name
, &attr
, &pkg
, &errorMsg
, nsIsPublic
)
947 : assets
->getIncludedResources().
948 identifierForName(e
.name
.string(), e
.name
.size(),
949 attr
.string(), attr
.size(),
950 pkg
.string(), pkg
.size());
952 NOISY(printf("XML attribute name %s: resid=0x%08x\n",
953 String8(e
.name
).string(), res
));
954 setAttributeResID(i
, res
);
956 SourcePos(mFilename
, getStartLineNumber()).error(
957 "No resource identifier found for attribute '%s' in package '%s'\n",
958 String8(e
.name
).string(), String8(pkg
).string());
963 const size_t N
= mChildren
.size();
964 for (size_t i
=0; i
<N
; i
++) {
965 status_t err
= mChildren
.itemAt(i
)->assignResourceIds(assets
, table
);
966 if (err
< NO_ERROR
) {
971 return hasErrors
? UNKNOWN_ERROR
: NO_ERROR
;
974 status_t
XMLNode::flatten(const sp
<AaptFile
>& dest
,
975 bool stripComments
, bool stripRawValues
) const
977 StringPool strings
= StringPool(false, mUTF8
);
978 Vector
<uint32_t> resids
;
980 // First collect just the strings for attribute names that have a
981 // resource ID assigned to them. This ensures that the resource ID
982 // array is compact, and makes it easier to deal with attribute names
983 // in different namespaces (and thus with different resource IDs).
984 collect_resid_strings(&strings
, &resids
);
986 // Next collect all remainibng strings.
987 collect_strings(&strings
, &resids
, stripComments
, stripRawValues
);
989 #if 0 // No longer compiles
990 NOISY(printf("Found strings:\n");
991 const size_t N
= strings
.size();
992 for (size_t i
=0; i
<N
; i
++) {
993 printf("%s\n", String8(strings
.entryAt(i
).string
).string());
998 sp
<AaptFile
> stringPool
= strings
.createStringBlock();
999 NOISY(aout
<< "String pool:"
1000 << HexDump(stringPool
->getData(), stringPool
->getSize()) << endl
);
1002 ResXMLTree_header header
;
1003 memset(&header
, 0, sizeof(header
));
1004 header
.header
.type
= htods(RES_XML_TYPE
);
1005 header
.header
.headerSize
= htods(sizeof(header
));
1007 const size_t basePos
= dest
->getSize();
1008 dest
->writeData(&header
, sizeof(header
));
1009 dest
->writeData(stringPool
->getData(), stringPool
->getSize());
1011 // If we have resource IDs, write them.
1012 if (resids
.size() > 0) {
1013 const size_t resIdsPos
= dest
->getSize();
1014 const size_t resIdsSize
=
1015 sizeof(ResChunk_header
)+(sizeof(uint32_t)*resids
.size());
1016 ResChunk_header
* idsHeader
= (ResChunk_header
*)
1017 (((const uint8_t*)dest
->editData(resIdsPos
+resIdsSize
))+resIdsPos
);
1018 idsHeader
->type
= htods(RES_XML_RESOURCE_MAP_TYPE
);
1019 idsHeader
->headerSize
= htods(sizeof(*idsHeader
));
1020 idsHeader
->size
= htodl(resIdsSize
);
1021 uint32_t* ids
= (uint32_t*)(idsHeader
+1);
1022 for (size_t i
=0; i
<resids
.size(); i
++) {
1023 *ids
++ = htodl(resids
[i
]);
1027 flatten_node(strings
, dest
, stripComments
, stripRawValues
);
1029 void* data
= dest
->editData();
1030 ResXMLTree_header
* hd
= (ResXMLTree_header
*)(((uint8_t*)data
)+basePos
);
1031 size_t size
= dest
->getSize()-basePos
;
1032 hd
->header
.size
= htodl(dest
->getSize()-basePos
);
1034 NOISY(aout
<< "XML resource:"
1035 << HexDump(dest
->getData(), dest
->getSize()) << endl
);
1037 #if PRINT_STRING_METRICS
1038 fprintf(stderr
, "**** total xml size: %d / %d%% strings (in %s)\n",
1039 dest
->getSize(), (stringPool
->getSize()*100)/dest
->getSize(),
1040 dest
->getPath().string());
1046 void XMLNode::print(int indent
)
1050 for (i
=0; i
<indent
; i
++) {
1053 if (getType() == TYPE_ELEMENT
) {
1054 String8
elemNs(getNamespaceUri());
1055 if (elemNs
.size() > 0) {
1058 printf("%s E: %s%s", prefix
.string(),
1059 elemNs
.string(), String8(getElementName()).string());
1060 int N
= mAttributes
.size();
1061 for (i
=0; i
<N
; i
++) {
1062 ssize_t idx
= mAttributeOrder
.valueAt(i
);
1068 const attribute_entry
& attr
= mAttributes
.itemAt(idx
);
1069 String8
attrNs(attr
.ns
);
1070 if (attrNs
.size() > 0) {
1073 if (attr
.nameResId
) {
1074 printf("%s%s(0x%08x)", attrNs
.string(),
1075 String8(attr
.name
).string(), attr
.nameResId
);
1077 printf("%s%s", attrNs
.string(), String8(attr
.name
).string());
1079 printf("=%s", String8(attr
.string
).string());
1082 } else if (getType() == TYPE_NAMESPACE
) {
1083 printf("%s N: %s=%s\n", prefix
.string(),
1084 getNamespacePrefix().size() > 0
1085 ? String8(getNamespacePrefix()).string() : "<DEF>",
1086 String8(getNamespaceUri()).string());
1088 printf("%s C: \"%s\"\n", prefix
.string(), String8(getCData()).string());
1090 int N
= mChildren
.size();
1091 for (i
=0; i
<N
; i
++) {
1092 mChildren
.itemAt(i
)->print(indent
+1);
1096 static void splitName(const char* name
, String16
* outNs
, String16
* outName
)
1098 const char* p
= name
;
1099 while (*p
!= 0 && *p
!= 1) {
1103 *outNs
= String16();
1104 *outName
= String16(name
);
1106 *outNs
= String16(name
, (p
-name
));
1107 *outName
= String16(p
+1);
1112 XMLNode::startNamespace(void *userData
, const char *prefix
, const char *uri
)
1114 NOISY_PARSE(printf("Start Namespace: %s %s\n", prefix
, uri
));
1115 ParseState
* st
= (ParseState
*)userData
;
1116 sp
<XMLNode
> node
= XMLNode::newNamespace(st
->filename
,
1117 String16(prefix
!= NULL
? prefix
: ""), String16(uri
));
1118 node
->setStartLineNumber(XML_GetCurrentLineNumber(st
->parser
));
1119 if (st
->stack
.size() > 0) {
1120 st
->stack
.itemAt(st
->stack
.size()-1)->addChild(node
);
1124 st
->stack
.push(node
);
1128 XMLNode::startElement(void *userData
, const char *name
, const char **atts
)
1130 NOISY_PARSE(printf("Start Element: %s\n", name
));
1131 ParseState
* st
= (ParseState
*)userData
;
1132 String16 ns16
, name16
;
1133 splitName(name
, &ns16
, &name16
);
1134 sp
<XMLNode
> node
= XMLNode::newElement(st
->filename
, ns16
, name16
);
1135 node
->setStartLineNumber(XML_GetCurrentLineNumber(st
->parser
));
1136 if (st
->pendingComment
.size() > 0) {
1137 node
->appendComment(st
->pendingComment
);
1138 st
->pendingComment
= String16();
1140 if (st
->stack
.size() > 0) {
1141 st
->stack
.itemAt(st
->stack
.size()-1)->addChild(node
);
1145 st
->stack
.push(node
);
1147 for (int i
= 0; atts
[i
]; i
+= 2) {
1148 splitName(atts
[i
], &ns16
, &name16
);
1149 node
->addAttribute(ns16
, name16
, String16(atts
[i
+1]));
1154 XMLNode::characterData(void *userData
, const XML_Char
*s
, int len
)
1156 NOISY_PARSE(printf("CDATA: \"%s\"\n", String8(s
, len
).string()));
1157 ParseState
* st
= (ParseState
*)userData
;
1158 sp
<XMLNode
> node
= NULL
;
1159 if (st
->stack
.size() == 0) {
1162 sp
<XMLNode
> parent
= st
->stack
.itemAt(st
->stack
.size()-1);
1163 if (parent
!= NULL
&& parent
->getChildren().size() > 0) {
1164 node
= parent
->getChildren()[parent
->getChildren().size()-1];
1165 if (node
->getType() != TYPE_CDATA
) {
1166 // Last node is not CDATA, need to make a new node.
1172 node
= XMLNode::newCData(st
->filename
);
1173 node
->setStartLineNumber(XML_GetCurrentLineNumber(st
->parser
));
1174 parent
->addChild(node
);
1177 node
->appendChars(String16(s
, len
));
1181 XMLNode::endElement(void *userData
, const char *name
)
1183 NOISY_PARSE(printf("End Element: %s\n", name
));
1184 ParseState
* st
= (ParseState
*)userData
;
1185 sp
<XMLNode
> node
= st
->stack
.itemAt(st
->stack
.size()-1);
1186 node
->setEndLineNumber(XML_GetCurrentLineNumber(st
->parser
));
1187 if (st
->pendingComment
.size() > 0) {
1188 node
->appendComment(st
->pendingComment
);
1189 st
->pendingComment
= String16();
1191 String16 ns16
, name16
;
1192 splitName(name
, &ns16
, &name16
);
1193 LOG_ALWAYS_FATAL_IF(node
->getElementNamespace() != ns16
1194 || node
->getElementName() != name16
,
1195 "Bad end element %s", name
);
1200 XMLNode::endNamespace(void *userData
, const char *prefix
)
1202 const char* nonNullPrefix
= prefix
!= NULL
? prefix
: "";
1203 NOISY_PARSE(printf("End Namespace: %s\n", prefix
));
1204 ParseState
* st
= (ParseState
*)userData
;
1205 sp
<XMLNode
> node
= st
->stack
.itemAt(st
->stack
.size()-1);
1206 node
->setEndLineNumber(XML_GetCurrentLineNumber(st
->parser
));
1207 LOG_ALWAYS_FATAL_IF(node
->getNamespacePrefix() != String16(nonNullPrefix
),
1208 "Bad end namespace %s", prefix
);
1213 XMLNode::commentData(void *userData
, const char *comment
)
1215 NOISY_PARSE(printf("Comment: %s\n", comment
));
1216 ParseState
* st
= (ParseState
*)userData
;
1217 if (st
->pendingComment
.size() > 0) {
1218 st
->pendingComment
.append(String16("\n"));
1220 st
->pendingComment
.append(String16(comment
));
1223 status_t
XMLNode::collect_strings(StringPool
* dest
, Vector
<uint32_t>* outResIds
,
1224 bool stripComments
, bool stripRawValues
) const
1226 collect_attr_strings(dest
, outResIds
, true);
1229 if (RESOURCES_TOOLS_NAMESPACE
!= mNamespaceUri
) {
1230 if (mNamespacePrefix
.size() > 0) {
1231 dest
->add(mNamespacePrefix
, true);
1233 if (mNamespaceUri
.size() > 0) {
1234 dest
->add(mNamespaceUri
, true);
1237 if (mElementName
.size() > 0) {
1238 dest
->add(mElementName
, true);
1241 if (!stripComments
&& mComment
.size() > 0) {
1242 dest
->add(mComment
, true);
1245 const int NA
= mAttributes
.size();
1247 for (i
=0; i
<NA
; i
++) {
1248 const attribute_entry
& ae
= mAttributes
.itemAt(i
);
1249 if (ae
.ns
.size() > 0) {
1250 dest
->add(ae
.ns
, true);
1252 if (!stripRawValues
|| ae
.needStringValue()) {
1253 dest
->add(ae
.string
, true);
1256 if (ae.value.dataType == Res_value::TYPE_NULL
1257 || ae.value.dataType == Res_value::TYPE_STRING) {
1258 dest->add(ae.string, true);
1263 if (mElementName
.size() == 0) {
1264 // If not an element, include the CDATA, even if it is empty.
1265 dest
->add(mChars
, true);
1268 const int NC
= mChildren
.size();
1270 for (i
=0; i
<NC
; i
++) {
1271 mChildren
.itemAt(i
)->collect_strings(dest
, outResIds
,
1272 stripComments
, stripRawValues
);
1278 status_t
XMLNode::collect_attr_strings(StringPool
* outPool
,
1279 Vector
<uint32_t>* outResIds
, bool allAttrs
) const {
1280 const int NA
= mAttributes
.size();
1282 for (int i
=0; i
<NA
; i
++) {
1283 const attribute_entry
& attr
= mAttributes
.itemAt(i
);
1284 uint32_t id
= attr
.nameResId
;
1285 if (id
|| allAttrs
) {
1286 // See if we have already assigned this resource ID to a pooled
1288 const Vector
<size_t>* indices
= outPool
->offsetsForString(attr
.name
);
1290 if (indices
!= NULL
) {
1291 const int NJ
= indices
->size();
1292 const size_t NR
= outResIds
->size();
1293 for (int j
=0; j
<NJ
; j
++) {
1294 size_t strIdx
= indices
->itemAt(j
);
1297 // We don't need to assign a resource ID for this one.
1301 // Just ignore strings that are out of range of
1302 // the currently assigned resource IDs... we add
1303 // strings as we assign the first ID.
1304 } else if (outResIds
->itemAt(strIdx
) == id
) {
1311 idx
= outPool
->add(attr
.name
);
1312 NOISY(printf("Adding attr %s (resid 0x%08x) to pool: idx=%d\n",
1313 String8(attr
.name
).string(), id
, idx
));
1315 while ((ssize_t
)outResIds
->size() <= idx
) {
1318 outResIds
->replaceAt(id
, idx
);
1321 attr
.namePoolIdx
= idx
;
1322 NOISY(printf("String %s offset=0x%08x\n",
1323 String8(attr
.name
).string(), idx
));
1330 status_t
XMLNode::collect_resid_strings(StringPool
* outPool
,
1331 Vector
<uint32_t>* outResIds
) const
1333 collect_attr_strings(outPool
, outResIds
, false);
1335 const int NC
= mChildren
.size();
1337 for (int i
=0; i
<NC
; i
++) {
1338 mChildren
.itemAt(i
)->collect_resid_strings(outPool
, outResIds
);
1344 status_t
XMLNode::flatten_node(const StringPool
& strings
, const sp
<AaptFile
>& dest
,
1345 bool stripComments
, bool stripRawValues
) const
1347 ResXMLTree_node node
;
1348 ResXMLTree_cdataExt cdataExt
;
1349 ResXMLTree_namespaceExt namespaceExt
;
1350 ResXMLTree_attrExt attrExt
;
1351 const void* extData
= NULL
;
1353 ResXMLTree_attribute attr
;
1354 bool writeCurrentNode
= true;
1356 const size_t NA
= mAttributes
.size();
1357 const size_t NC
= mChildren
.size();
1360 LOG_ALWAYS_FATAL_IF(NA
!= mAttributeOrder
.size(), "Attributes messed up!");
1362 const String16
id16("id");
1363 const String16
class16("class");
1364 const String16
style16("style");
1366 const type type
= getType();
1368 memset(&node
, 0, sizeof(node
));
1369 memset(&attr
, 0, sizeof(attr
));
1370 node
.header
.headerSize
= htods(sizeof(node
));
1371 node
.lineNumber
= htodl(getStartLineNumber());
1372 if (!stripComments
) {
1373 node
.comment
.index
= htodl(
1374 mComment
.size() > 0 ? strings
.offsetForString(mComment
) : -1);
1375 //if (mComment.size() > 0) {
1376 // printf("Flattening comment: %s\n", String8(mComment).string());
1379 node
.comment
.index
= htodl((uint32_t)-1);
1381 if (type
== TYPE_ELEMENT
) {
1382 node
.header
.type
= htods(RES_XML_START_ELEMENT_TYPE
);
1384 extSize
= sizeof(attrExt
);
1385 memset(&attrExt
, 0, sizeof(attrExt
));
1386 if (mNamespaceUri
.size() > 0) {
1387 attrExt
.ns
.index
= htodl(strings
.offsetForString(mNamespaceUri
));
1389 attrExt
.ns
.index
= htodl((uint32_t)-1);
1391 attrExt
.name
.index
= htodl(strings
.offsetForString(mElementName
));
1392 attrExt
.attributeStart
= htods(sizeof(attrExt
));
1393 attrExt
.attributeSize
= htods(sizeof(attr
));
1394 attrExt
.attributeCount
= htods(NA
);
1395 attrExt
.idIndex
= htods(0);
1396 attrExt
.classIndex
= htods(0);
1397 attrExt
.styleIndex
= htods(0);
1398 for (i
=0; i
<NA
; i
++) {
1399 ssize_t idx
= mAttributeOrder
.valueAt(i
);
1400 const attribute_entry
& ae
= mAttributes
.itemAt(idx
);
1401 if (ae
.ns
.size() == 0) {
1402 if (ae
.name
== id16
) {
1403 attrExt
.idIndex
= htods(i
+1);
1404 } else if (ae
.name
== class16
) {
1405 attrExt
.classIndex
= htods(i
+1);
1406 } else if (ae
.name
== style16
) {
1407 attrExt
.styleIndex
= htods(i
+1);
1411 } else if (type
== TYPE_NAMESPACE
) {
1412 if (mNamespaceUri
== RESOURCES_TOOLS_NAMESPACE
) {
1413 writeCurrentNode
= false;
1415 node
.header
.type
= htods(RES_XML_START_NAMESPACE_TYPE
);
1416 extData
= &namespaceExt
;
1417 extSize
= sizeof(namespaceExt
);
1418 memset(&namespaceExt
, 0, sizeof(namespaceExt
));
1419 if (mNamespacePrefix
.size() > 0) {
1420 namespaceExt
.prefix
.index
= htodl(strings
.offsetForString(mNamespacePrefix
));
1422 namespaceExt
.prefix
.index
= htodl((uint32_t)-1);
1424 namespaceExt
.prefix
.index
= htodl(strings
.offsetForString(mNamespacePrefix
));
1425 namespaceExt
.uri
.index
= htodl(strings
.offsetForString(mNamespaceUri
));
1427 LOG_ALWAYS_FATAL_IF(NA
!= 0, "Namespace nodes can't have attributes!");
1428 } else if (type
== TYPE_CDATA
) {
1429 node
.header
.type
= htods(RES_XML_CDATA_TYPE
);
1430 extData
= &cdataExt
;
1431 extSize
= sizeof(cdataExt
);
1432 memset(&cdataExt
, 0, sizeof(cdataExt
));
1433 cdataExt
.data
.index
= htodl(strings
.offsetForString(mChars
));
1434 cdataExt
.typedData
.size
= htods(sizeof(cdataExt
.typedData
));
1435 cdataExt
.typedData
.res0
= 0;
1436 cdataExt
.typedData
.dataType
= mCharsValue
.dataType
;
1437 cdataExt
.typedData
.data
= htodl(mCharsValue
.data
);
1438 LOG_ALWAYS_FATAL_IF(NA
!= 0, "CDATA nodes can't have attributes!");
1441 node
.header
.size
= htodl(sizeof(node
) + extSize
+ (sizeof(attr
)*NA
));
1443 if (writeCurrentNode
) {
1444 dest
->writeData(&node
, sizeof(node
));
1446 dest
->writeData(extData
, extSize
);
1450 for (i
=0; i
<NA
; i
++) {
1451 ssize_t idx
= mAttributeOrder
.valueAt(i
);
1452 const attribute_entry
& ae
= mAttributes
.itemAt(idx
);
1453 if (ae
.ns
.size() > 0) {
1454 attr
.ns
.index
= htodl(strings
.offsetForString(ae
.ns
));
1456 attr
.ns
.index
= htodl((uint32_t)-1);
1458 attr
.name
.index
= htodl(ae
.namePoolIdx
);
1460 if (!stripRawValues
|| ae
.needStringValue()) {
1461 attr
.rawValue
.index
= htodl(strings
.offsetForString(ae
.string
));
1463 attr
.rawValue
.index
= htodl((uint32_t)-1);
1465 attr
.typedValue
.size
= htods(sizeof(attr
.typedValue
));
1466 if (ae
.value
.dataType
== Res_value::TYPE_NULL
1467 || ae
.value
.dataType
== Res_value::TYPE_STRING
) {
1468 attr
.typedValue
.res0
= 0;
1469 attr
.typedValue
.dataType
= Res_value::TYPE_STRING
;
1470 attr
.typedValue
.data
= htodl(strings
.offsetForString(ae
.string
));
1472 attr
.typedValue
.res0
= 0;
1473 attr
.typedValue
.dataType
= ae
.value
.dataType
;
1474 attr
.typedValue
.data
= htodl(ae
.value
.data
);
1476 dest
->writeData(&attr
, sizeof(attr
));
1479 for (i
=0; i
<NC
; i
++) {
1480 status_t err
= mChildren
.itemAt(i
)->flatten_node(strings
, dest
,
1481 stripComments
, stripRawValues
);
1482 if (err
!= NO_ERROR
) {
1487 if (type
== TYPE_ELEMENT
) {
1488 ResXMLTree_endElementExt endElementExt
;
1489 memset(&endElementExt
, 0, sizeof(endElementExt
));
1490 node
.header
.type
= htods(RES_XML_END_ELEMENT_TYPE
);
1491 node
.header
.size
= htodl(sizeof(node
)+sizeof(endElementExt
));
1492 node
.lineNumber
= htodl(getEndLineNumber());
1493 node
.comment
.index
= htodl((uint32_t)-1);
1494 endElementExt
.ns
.index
= attrExt
.ns
.index
;
1495 endElementExt
.name
.index
= attrExt
.name
.index
;
1496 dest
->writeData(&node
, sizeof(node
));
1497 dest
->writeData(&endElementExt
, sizeof(endElementExt
));
1498 } else if (type
== TYPE_NAMESPACE
) {
1499 if (writeCurrentNode
) {
1500 node
.header
.type
= htods(RES_XML_END_NAMESPACE_TYPE
);
1501 node
.lineNumber
= htodl(getEndLineNumber());
1502 node
.comment
.index
= htodl((uint32_t)-1);
1503 node
.header
.size
= htodl(sizeof(node
)+extSize
);
1504 dest
->writeData(&node
, sizeof(node
));
1505 dest
->writeData(extData
, extSize
);