| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #ifndef AAPT_XML_PULL_PARSER_H |
| #define AAPT_XML_PULL_PARSER_H |
| |
| #include "Resource.h" |
| #include "process/IResourceTableConsumer.h" |
| #include "util/Maybe.h" |
| #include "util/StringPiece.h" |
| #include "xml/XmlUtil.h" |
| |
| #include <algorithm> |
| #include <expat.h> |
| #include <istream> |
| #include <ostream> |
| #include <queue> |
| #include <stack> |
| #include <string> |
| #include <vector> |
| |
| namespace aapt { |
| namespace xml { |
| |
| class XmlPullParser : public IPackageDeclStack { |
| public: |
| enum class Event { |
| kBadDocument, |
| kStartDocument, |
| kEndDocument, |
| |
| kStartNamespace, |
| kEndNamespace, |
| kStartElement, |
| kEndElement, |
| kText, |
| kComment, |
| }; |
| |
| /** |
| * Skips to the next direct descendant node of the given startDepth, |
| * skipping namespace nodes. |
| * |
| * When nextChildNode returns true, you can expect Comments, Text, and StartElement events. |
| */ |
| static bool nextChildNode(XmlPullParser* parser, size_t startDepth); |
| static bool skipCurrentElement(XmlPullParser* parser); |
| static bool isGoodEvent(Event event); |
| |
| XmlPullParser(std::istream& in); |
| ~XmlPullParser(); |
| |
| /** |
| * Returns the current event that is being processed. |
| */ |
| Event getEvent() const; |
| |
| const std::string& getLastError() const; |
| |
| /** |
| * Note, unlike XmlPullParser, the first call to next() will return |
| * StartElement of the first element. |
| */ |
| Event next(); |
| |
| // |
| // These are available for all nodes. |
| // |
| |
| const std::string& getComment() const; |
| size_t getLineNumber() const; |
| size_t getDepth() const; |
| |
| /** |
| * Returns the character data for a Text event. |
| */ |
| const std::string& getText() const; |
| |
| // |
| // Namespace prefix and URI are available for StartNamespace and EndNamespace. |
| // |
| |
| const std::string& getNamespacePrefix() const; |
| const std::string& getNamespaceUri() const; |
| |
| // |
| // These are available for StartElement and EndElement. |
| // |
| |
| const std::string& getElementNamespace() const; |
| const std::string& getElementName() const; |
| |
| /* |
| * Uses the current stack of namespaces to resolve the package. Eg: |
| * xmlns:app = "http://schemas.android.com/apk/res/com.android.app" |
| * ... |
| * android:text="@app:string/message" |
| * |
| * In this case, 'app' will be converted to 'com.android.app'. |
| * |
| * If xmlns:app="http://schemas.android.com/apk/res-auto", then |
| * 'package' will be set to 'defaultPackage'. |
| */ |
| Maybe<ExtractedPackage> transformPackageAlias( |
| const StringPiece& alias, const StringPiece& localPackage) const override; |
| |
| // |
| // Remaining methods are for retrieving information about attributes |
| // associated with a StartElement. |
| // |
| // Attributes must be in sorted order (according to the less than operator |
| // of struct Attribute). |
| // |
| |
| struct Attribute { |
| std::string namespaceUri; |
| std::string name; |
| std::string value; |
| |
| int compare(const Attribute& rhs) const; |
| bool operator<(const Attribute& rhs) const; |
| bool operator==(const Attribute& rhs) const; |
| bool operator!=(const Attribute& rhs) const; |
| }; |
| |
| using const_iterator = std::vector<Attribute>::const_iterator; |
| |
| const_iterator beginAttributes() const; |
| const_iterator endAttributes() const; |
| size_t getAttributeCount() const; |
| const_iterator findAttribute(StringPiece namespaceUri, StringPiece name) const; |
| |
| private: |
| static void XMLCALL startNamespaceHandler(void* userData, const char* prefix, const char* uri); |
| static void XMLCALL startElementHandler(void* userData, const char* name, const char** attrs); |
| static void XMLCALL characterDataHandler(void* userData, const char* s, int len); |
| static void XMLCALL endElementHandler(void* userData, const char* name); |
| static void XMLCALL endNamespaceHandler(void* userData, const char* prefix); |
| static void XMLCALL commentDataHandler(void* userData, const char* comment); |
| |
| struct EventData { |
| Event event; |
| size_t lineNumber; |
| size_t depth; |
| std::string data1; |
| std::string data2; |
| std::vector<Attribute> attributes; |
| }; |
| |
| std::istream& mIn; |
| XML_Parser mParser; |
| char mBuffer[16384]; |
| std::queue<EventData> mEventQueue; |
| std::string mLastError; |
| const std::string mEmpty; |
| size_t mDepth; |
| std::stack<std::string> mNamespaceUris; |
| |
| struct PackageDecl { |
| std::string prefix; |
| ExtractedPackage package; |
| }; |
| std::vector<PackageDecl> mPackageAliases; |
| }; |
| |
| /** |
| * Finds the attribute in the current element within the global namespace. |
| */ |
| Maybe<StringPiece> findAttribute(const XmlPullParser* parser, const StringPiece& name); |
| |
| /** |
| * Finds the attribute in the current element within the global namespace. The attribute's value |
| * must not be the empty string. |
| */ |
| Maybe<StringPiece> findNonEmptyAttribute(const XmlPullParser* parser, const StringPiece& name); |
| |
| // |
| // Implementation |
| // |
| |
| inline ::std::ostream& operator<<(::std::ostream& out, XmlPullParser::Event event) { |
| switch (event) { |
| case XmlPullParser::Event::kBadDocument: return out << "BadDocument"; |
| case XmlPullParser::Event::kStartDocument: return out << "StartDocument"; |
| case XmlPullParser::Event::kEndDocument: return out << "EndDocument"; |
| case XmlPullParser::Event::kStartNamespace: return out << "StartNamespace"; |
| case XmlPullParser::Event::kEndNamespace: return out << "EndNamespace"; |
| case XmlPullParser::Event::kStartElement: return out << "StartElement"; |
| case XmlPullParser::Event::kEndElement: return out << "EndElement"; |
| case XmlPullParser::Event::kText: return out << "Text"; |
| case XmlPullParser::Event::kComment: return out << "Comment"; |
| } |
| return out; |
| } |
| |
| inline bool XmlPullParser::nextChildNode(XmlPullParser* parser, size_t startDepth) { |
| Event event; |
| |
| // First get back to the start depth. |
| while (isGoodEvent(event = parser->next()) && parser->getDepth() > startDepth + 1) {} |
| |
| // Now look for the first good node. |
| while ((event != Event::kEndElement || parser->getDepth() > startDepth) && isGoodEvent(event)) { |
| switch (event) { |
| case Event::kText: |
| case Event::kComment: |
| case Event::kStartElement: |
| return true; |
| default: |
| break; |
| } |
| event = parser->next(); |
| } |
| return false; |
| } |
| |
| inline bool XmlPullParser::skipCurrentElement(XmlPullParser* parser) { |
| int depth = 1; |
| while (depth > 0) { |
| switch (parser->next()) { |
| case Event::kEndDocument: |
| return true; |
| case Event::kBadDocument: |
| return false; |
| case Event::kStartElement: |
| depth++; |
| break; |
| case Event::kEndElement: |
| depth--; |
| break; |
| default: |
| break; |
| } |
| } |
| return true; |
| } |
| |
| inline bool XmlPullParser::isGoodEvent(XmlPullParser::Event event) { |
| return event != Event::kBadDocument && event != Event::kEndDocument; |
| } |
| |
| inline int XmlPullParser::Attribute::compare(const Attribute& rhs) const { |
| int cmp = namespaceUri.compare(rhs.namespaceUri); |
| if (cmp != 0) return cmp; |
| return name.compare(rhs.name); |
| } |
| |
| inline bool XmlPullParser::Attribute::operator<(const Attribute& rhs) const { |
| return compare(rhs) < 0; |
| } |
| |
| inline bool XmlPullParser::Attribute::operator==(const Attribute& rhs) const { |
| return compare(rhs) == 0; |
| } |
| |
| inline bool XmlPullParser::Attribute::operator!=(const Attribute& rhs) const { |
| return compare(rhs) != 0; |
| } |
| |
| inline XmlPullParser::const_iterator XmlPullParser::findAttribute(StringPiece namespaceUri, |
| StringPiece name) const { |
| const auto endIter = endAttributes(); |
| const auto iter = std::lower_bound(beginAttributes(), endIter, |
| std::pair<StringPiece, StringPiece>(namespaceUri, name), |
| [](const Attribute& attr, const std::pair<StringPiece, StringPiece>& rhs) -> bool { |
| int cmp = attr.namespaceUri.compare(0, attr.namespaceUri.size(), |
| rhs.first.data(), rhs.first.size()); |
| if (cmp < 0) return true; |
| if (cmp > 0) return false; |
| cmp = attr.name.compare(0, attr.name.size(), rhs.second.data(), rhs.second.size()); |
| if (cmp < 0) return true; |
| return false; |
| } |
| ); |
| |
| if (iter != endIter && namespaceUri == iter->namespaceUri && name == iter->name) { |
| return iter; |
| } |
| return endIter; |
| } |
| |
| } // namespace xml |
| } // namespace aapt |
| |
| #endif // AAPT_XML_PULL_PARSER_H |