| |
| /* |
| * Copyright 2006 The Android Open Source Project |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| |
| #include "SkDOM.h" |
| #include "SkStream.h" |
| #include "SkXMLWriter.h" |
| |
| ///////////////////////////////////////////////////////////////////////// |
| |
| #include "SkXMLParser.h" |
| bool SkXMLParser::parse(const SkDOM& dom, const SkDOMNode* node) |
| { |
| const char* elemName = dom.getName(node); |
| |
| if (this->startElement(elemName)) |
| return false; |
| |
| SkDOM::AttrIter iter(dom, node); |
| const char* name, *value; |
| |
| while ((name = iter.next(&value)) != nullptr) |
| if (this->addAttribute(name, value)) |
| return false; |
| |
| if ((node = dom.getFirstChild(node)) != nullptr) |
| do { |
| if (!this->parse(dom, node)) |
| return false; |
| } while ((node = dom.getNextSibling(node)) != nullptr); |
| |
| return !this->endElement(elemName); |
| } |
| |
| ///////////////////////////////////////////////////////////////////////// |
| |
| struct SkDOMAttr { |
| const char* fName; |
| const char* fValue; |
| }; |
| |
| struct SkDOMNode { |
| const char* fName; |
| SkDOMNode* fFirstChild; |
| SkDOMNode* fNextSibling; |
| uint16_t fAttrCount; |
| uint8_t fType; |
| uint8_t fPad; |
| |
| const SkDOMAttr* attrs() const |
| { |
| return (const SkDOMAttr*)(this + 1); |
| } |
| SkDOMAttr* attrs() |
| { |
| return (SkDOMAttr*)(this + 1); |
| } |
| }; |
| |
| ///////////////////////////////////////////////////////////////////////// |
| |
| #define kMinChunkSize 512 |
| |
| SkDOM::SkDOM() : fAlloc(kMinChunkSize), fRoot(nullptr) |
| { |
| } |
| |
| SkDOM::~SkDOM() |
| { |
| } |
| |
| const SkDOM::Node* SkDOM::getRootNode() const |
| { |
| return fRoot; |
| } |
| |
| const SkDOM::Node* SkDOM::getFirstChild(const Node* node, const char name[]) const |
| { |
| SkASSERT(node); |
| const Node* child = node->fFirstChild; |
| |
| if (name) |
| { |
| for (; child != nullptr; child = child->fNextSibling) |
| if (!strcmp(name, child->fName)) |
| break; |
| } |
| return child; |
| } |
| |
| const SkDOM::Node* SkDOM::getNextSibling(const Node* node, const char name[]) const |
| { |
| SkASSERT(node); |
| const Node* sibling = node->fNextSibling; |
| if (name) |
| { |
| for (; sibling != nullptr; sibling = sibling->fNextSibling) |
| if (!strcmp(name, sibling->fName)) |
| break; |
| } |
| return sibling; |
| } |
| |
| SkDOM::Type SkDOM::getType(const Node* node) const |
| { |
| SkASSERT(node); |
| return (Type)node->fType; |
| } |
| |
| const char* SkDOM::getName(const Node* node) const |
| { |
| SkASSERT(node); |
| return node->fName; |
| } |
| |
| const char* SkDOM::findAttr(const Node* node, const char name[]) const |
| { |
| SkASSERT(node); |
| const Attr* attr = node->attrs(); |
| const Attr* stop = attr + node->fAttrCount; |
| |
| while (attr < stop) |
| { |
| if (!strcmp(attr->fName, name)) |
| return attr->fValue; |
| attr += 1; |
| } |
| return nullptr; |
| } |
| |
| ///////////////////////////////////////////////////////////////////////////////////// |
| |
| const SkDOM::Attr* SkDOM::getFirstAttr(const Node* node) const |
| { |
| return node->fAttrCount ? node->attrs() : nullptr; |
| } |
| |
| const SkDOM::Attr* SkDOM::getNextAttr(const Node* node, const Attr* attr) const |
| { |
| SkASSERT(node); |
| if (attr == nullptr) |
| return nullptr; |
| return (attr - node->attrs() + 1) < node->fAttrCount ? attr + 1 : nullptr; |
| } |
| |
| const char* SkDOM::getAttrName(const Node* node, const Attr* attr) const |
| { |
| SkASSERT(node); |
| SkASSERT(attr); |
| return attr->fName; |
| } |
| |
| const char* SkDOM::getAttrValue(const Node* node, const Attr* attr) const |
| { |
| SkASSERT(node); |
| SkASSERT(attr); |
| return attr->fValue; |
| } |
| |
| ///////////////////////////////////////////////////////////////////////////////////// |
| |
| SkDOM::AttrIter::AttrIter(const SkDOM&, const SkDOM::Node* node) |
| { |
| SkASSERT(node); |
| fAttr = node->attrs(); |
| fStop = fAttr + node->fAttrCount; |
| } |
| |
| const char* SkDOM::AttrIter::next(const char** value) |
| { |
| const char* name = nullptr; |
| |
| if (fAttr < fStop) |
| { |
| name = fAttr->fName; |
| if (value) |
| *value = fAttr->fValue; |
| fAttr += 1; |
| } |
| return name; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| #include "SkXMLParser.h" |
| #include "SkTDArray.h" |
| |
| static char* dupstr(SkChunkAlloc* chunk, const char src[]) |
| { |
| SkASSERT(chunk && src); |
| size_t len = strlen(src); |
| char* dst = (char*)chunk->alloc(len + 1, SkChunkAlloc::kThrow_AllocFailType); |
| memcpy(dst, src, len + 1); |
| return dst; |
| } |
| |
| class SkDOMParser : public SkXMLParser { |
| public: |
| SkDOMParser(SkChunkAlloc* chunk) : SkXMLParser(&fParserError), fAlloc(chunk) |
| { |
| fAlloc->reset(); |
| fRoot = nullptr; |
| fLevel = 0; |
| fNeedToFlush = true; |
| } |
| SkDOM::Node* getRoot() const { return fRoot; } |
| SkXMLParserError fParserError; |
| |
| protected: |
| void flushAttributes() |
| { |
| SkASSERT(fLevel > 0); |
| |
| int attrCount = fAttrs.count(); |
| |
| SkDOM::Node* node = (SkDOM::Node*)fAlloc->alloc(sizeof(SkDOM::Node) + attrCount * sizeof(SkDOM::Attr), |
| SkChunkAlloc::kThrow_AllocFailType); |
| |
| node->fName = fElemName; |
| node->fFirstChild = nullptr; |
| node->fAttrCount = SkToU16(attrCount); |
| node->fType = fElemType; |
| |
| if (fRoot == nullptr) |
| { |
| node->fNextSibling = nullptr; |
| fRoot = node; |
| } |
| else // this adds siblings in reverse order. gets corrected in onEndElement() |
| { |
| SkDOM::Node* parent = fParentStack.top(); |
| SkASSERT(fRoot && parent); |
| node->fNextSibling = parent->fFirstChild; |
| parent->fFirstChild = node; |
| } |
| *fParentStack.push() = node; |
| |
| sk_careful_memcpy(node->attrs(), fAttrs.begin(), attrCount * sizeof(SkDOM::Attr)); |
| fAttrs.reset(); |
| |
| } |
| |
| bool onStartElement(const char elem[]) override { |
| this->startCommon(elem, SkDOM::kElement_Type); |
| return false; |
| } |
| |
| bool onAddAttribute(const char name[], const char value[]) override { |
| SkDOM::Attr* attr = fAttrs.append(); |
| attr->fName = dupstr(fAlloc, name); |
| attr->fValue = dupstr(fAlloc, value); |
| return false; |
| } |
| |
| bool onEndElement(const char elem[]) override { |
| --fLevel; |
| if (fNeedToFlush) |
| this->flushAttributes(); |
| fNeedToFlush = false; |
| |
| SkDOM::Node* parent; |
| |
| fParentStack.pop(&parent); |
| |
| SkDOM::Node* child = parent->fFirstChild; |
| SkDOM::Node* prev = nullptr; |
| while (child) |
| { |
| SkDOM::Node* next = child->fNextSibling; |
| child->fNextSibling = prev; |
| prev = child; |
| child = next; |
| } |
| parent->fFirstChild = prev; |
| return false; |
| } |
| |
| bool onText(const char text[], int len) override { |
| SkString str(text, len); |
| this->startCommon(str.c_str(), SkDOM::kText_Type); |
| this->SkDOMParser::onEndElement(str.c_str()); |
| |
| return false; |
| } |
| |
| private: |
| void startCommon(const char elem[], SkDOM::Type type) { |
| if (fLevel > 0 && fNeedToFlush) |
| this->flushAttributes(); |
| |
| fNeedToFlush = true; |
| fElemName = dupstr(fAlloc, elem); |
| fElemType = type; |
| ++fLevel; |
| } |
| |
| SkTDArray<SkDOM::Node*> fParentStack; |
| SkChunkAlloc* fAlloc; |
| SkDOM::Node* fRoot; |
| bool fNeedToFlush; |
| |
| // state needed for flushAttributes() |
| SkTDArray<SkDOM::Attr> fAttrs; |
| char* fElemName; |
| SkDOM::Type fElemType; |
| int fLevel; |
| }; |
| |
| const SkDOM::Node* SkDOM::build(const char doc[], size_t len) |
| { |
| SkDOMParser parser(&fAlloc); |
| if (!parser.parse(doc, len)) |
| { |
| SkDEBUGCODE(SkDebugf("xml parse error, line %d\n", parser.fParserError.getLineNumber());) |
| fRoot = nullptr; |
| fAlloc.reset(); |
| return nullptr; |
| } |
| fRoot = parser.getRoot(); |
| return fRoot; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////// |
| |
| static void walk_dom(const SkDOM& dom, const SkDOM::Node* node, SkXMLParser* parser) |
| { |
| const char* elem = dom.getName(node); |
| if (dom.getType(node) == SkDOM::kText_Type) { |
| SkASSERT(dom.countChildren(node) == 0); |
| parser->text(elem, SkToInt(strlen(elem))); |
| return; |
| } |
| |
| parser->startElement(elem); |
| |
| SkDOM::AttrIter iter(dom, node); |
| const char* name; |
| const char* value; |
| while ((name = iter.next(&value)) != nullptr) |
| parser->addAttribute(name, value); |
| |
| node = dom.getFirstChild(node, nullptr); |
| while (node) |
| { |
| walk_dom(dom, node, parser); |
| node = dom.getNextSibling(node, nullptr); |
| } |
| |
| parser->endElement(elem); |
| } |
| |
| const SkDOM::Node* SkDOM::copy(const SkDOM& dom, const SkDOM::Node* node) |
| { |
| SkDOMParser parser(&fAlloc); |
| |
| walk_dom(dom, node, &parser); |
| |
| fRoot = parser.getRoot(); |
| return fRoot; |
| } |
| |
| SkXMLParser* SkDOM::beginParsing() { |
| SkASSERT(!fParser); |
| fParser.reset(new SkDOMParser(&fAlloc)); |
| |
| return fParser.get(); |
| } |
| |
| const SkDOM::Node* SkDOM::finishParsing() { |
| SkASSERT(fParser); |
| fRoot = fParser->getRoot(); |
| fParser.free(); |
| |
| return fRoot; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////// |
| |
| int SkDOM::countChildren(const Node* node, const char elem[]) const |
| { |
| int count = 0; |
| |
| node = this->getFirstChild(node, elem); |
| while (node) |
| { |
| count += 1; |
| node = this->getNextSibling(node, elem); |
| } |
| return count; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////// |
| |
| #include "SkParse.h" |
| |
| bool SkDOM::findS32(const Node* node, const char name[], int32_t* value) const |
| { |
| const char* vstr = this->findAttr(node, name); |
| return vstr && SkParse::FindS32(vstr, value); |
| } |
| |
| bool SkDOM::findScalars(const Node* node, const char name[], SkScalar value[], int count) const |
| { |
| const char* vstr = this->findAttr(node, name); |
| return vstr && SkParse::FindScalars(vstr, value, count); |
| } |
| |
| bool SkDOM::findHex(const Node* node, const char name[], uint32_t* value) const |
| { |
| const char* vstr = this->findAttr(node, name); |
| return vstr && SkParse::FindHex(vstr, value); |
| } |
| |
| bool SkDOM::findBool(const Node* node, const char name[], bool* value) const |
| { |
| const char* vstr = this->findAttr(node, name); |
| return vstr && SkParse::FindBool(vstr, value); |
| } |
| |
| int SkDOM::findList(const Node* node, const char name[], const char list[]) const |
| { |
| const char* vstr = this->findAttr(node, name); |
| return vstr ? SkParse::FindList(vstr, list) : -1; |
| } |
| |
| bool SkDOM::hasAttr(const Node* node, const char name[], const char value[]) const |
| { |
| const char* vstr = this->findAttr(node, name); |
| return vstr && !strcmp(vstr, value); |
| } |
| |
| bool SkDOM::hasS32(const Node* node, const char name[], int32_t target) const |
| { |
| const char* vstr = this->findAttr(node, name); |
| int32_t value; |
| return vstr && SkParse::FindS32(vstr, &value) && value == target; |
| } |
| |
| bool SkDOM::hasScalar(const Node* node, const char name[], SkScalar target) const |
| { |
| const char* vstr = this->findAttr(node, name); |
| SkScalar value; |
| return vstr && SkParse::FindScalar(vstr, &value) && value == target; |
| } |
| |
| bool SkDOM::hasHex(const Node* node, const char name[], uint32_t target) const |
| { |
| const char* vstr = this->findAttr(node, name); |
| uint32_t value; |
| return vstr && SkParse::FindHex(vstr, &value) && value == target; |
| } |
| |
| bool SkDOM::hasBool(const Node* node, const char name[], bool target) const |
| { |
| const char* vstr = this->findAttr(node, name); |
| bool value; |
| return vstr && SkParse::FindBool(vstr, &value) && value == target; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////// |
| |
| #ifdef SK_DEBUG |
| |
| void SkDOM::dump(const Node* node, int level) const |
| { |
| if (node == nullptr) |
| node = this->getRootNode(); |
| |
| SkDebugWStream debugStream; |
| SkXMLStreamWriter xmlWriter(&debugStream); |
| xmlWriter.writeDOM(*this, node, false); |
| } |
| |
| void SkDOM::UnitTest() |
| { |
| #ifdef SK_SUPPORT_UNITTEST |
| static const char gDoc[] = |
| "<root a='1' b='2'>" |
| "<elem1 c='3' />" |
| "<elem2 d='4' />" |
| "<elem3 e='5'>" |
| "<subelem1/>" |
| "<subelem2 f='6' g='7'/>" |
| "</elem3>" |
| "<elem4 h='8'/>" |
| "</root>" |
| ; |
| |
| SkDOM dom; |
| |
| SkASSERT(dom.getRootNode() == nullptr); |
| |
| const Node* root = dom.build(gDoc, sizeof(gDoc) - 1); |
| SkASSERT(root && dom.getRootNode() == root); |
| |
| const char* v = dom.findAttr(root, "a"); |
| SkASSERT(v && !strcmp(v, "1")); |
| v = dom.findAttr(root, "b"); |
| SkASSERT(v && !strcmp(v, "2")); |
| v = dom.findAttr(root, "c"); |
| SkASSERT(v == nullptr); |
| |
| SkASSERT(dom.getFirstChild(root, "elem1")); |
| SkASSERT(!dom.getFirstChild(root, "subelem1")); |
| |
| dom.dump(); |
| #endif |
| } |
| |
| #endif |