/*
* 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 "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)) != NULL)
if (this->addAttribute(name, value))
return false;
if ((node = dom.getFirstChild(node)) != NULL)
do {
if (!this->parse(dom, node))
return false;
} while ((node = dom.getNextSibling(node)) != NULL);
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(NULL)
{
}
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 != NULL; 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 != NULL; 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 NULL;
}
/////////////////////////////////////////////////////////////////////////////////////
const SkDOM::Attr* SkDOM::getFirstAttr(const Node* node) const
{
return node->fAttrCount ? node->attrs() : NULL;
}
const SkDOM::Attr* SkDOM::getNextAttr(const Node* node, const Attr* attr) const
{
SkASSERT(node);
if (attr == NULL)
return NULL;
return (attr - node->attrs() + 1) < node->fAttrCount ? attr + 1 : NULL;
}
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 = NULL;
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 {
bool fNeedToFlush;
public:
SkDOMParser(SkChunkAlloc* chunk) : SkXMLParser(&fParserError), fAlloc(chunk)
{
fRoot = NULL;
fLevel = 0;
fNeedToFlush = true;
}
SkDOM::Node* getRoot() const { return fRoot; }
SkXMLParserError fParserError;
protected:
void flushAttributes()
{
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 = NULL;
node->fAttrCount = SkToU16(attrCount);
node->fType = SkDOM::kElement_Type;
if (fRoot == NULL)
{
node->fNextSibling = NULL;
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;
memcpy(node->attrs(), fAttrs.begin(), attrCount * sizeof(SkDOM::Attr));
fAttrs.reset();
}
virtual bool onStartElement(const char elem[])
{
if (fLevel > 0 && fNeedToFlush)
this->flushAttributes();
fNeedToFlush = true;
fElemName = dupstr(fAlloc, elem);
++fLevel;
return false;
}
virtual bool onAddAttribute(const char name[], const char value[])
{
SkDOM::Attr* attr = fAttrs.append();
attr->fName = dupstr(fAlloc, name);
attr->fValue = dupstr(fAlloc, value);
return false;
}
virtual bool onEndElement(const char elem[])
{
--fLevel;
if (fNeedToFlush)
this->flushAttributes();
fNeedToFlush = false;
SkDOM::Node* parent;
fParentStack.pop(&parent);
SkDOM::Node* child = parent->fFirstChild;
SkDOM::Node* prev = NULL;
while (child)
{
SkDOM::Node* next = child->fNextSibling;
child->fNextSibling = prev;
prev = child;
child = next;
}
parent->fFirstChild = prev;
return false;
}
private:
SkTDArray<SkDOM::Node*> fParentStack;
SkChunkAlloc* fAlloc;
SkDOM::Node* fRoot;
// state needed for flushAttributes()
SkTDArray<SkDOM::Attr> fAttrs;
char* fElemName;
int fLevel;
};
const SkDOM::Node* SkDOM::build(const char doc[], size_t len)
{
fAlloc.reset();
SkDOMParser parser(&fAlloc);
if (!parser.parse(doc, len))
{
SkDEBUGCODE(SkDebugf("xml parse error, line %d\n", parser.fParserError.getLineNumber());)
fRoot = NULL;
fAlloc.reset();
return NULL;
}
fRoot = parser.getRoot();
return fRoot;
}
///////////////////////////////////////////////////////////////////////////
static void walk_dom(const SkDOM& dom, const SkDOM::Node* node, SkXMLParser* parser)
{
const char* elem = dom.getName(node);
parser->startElement(elem);
SkDOM::AttrIter iter(dom, node);
const char* name;
const char* value;
while ((name = iter.next(&value)) != NULL)
parser->addAttribute(name, value);
node = dom.getFirstChild(node, NULL);
while (node)
{
walk_dom(dom, node, parser);
node = dom.getNextSibling(node, NULL);
}
parser->endElement(elem);
}
const SkDOM::Node* SkDOM::copy(const SkDOM& dom, const SkDOM::Node* node)
{
fAlloc.reset();
SkDOMParser parser(&fAlloc);
walk_dom(dom, node, &parser);
fRoot = parser.getRoot();
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
static void tab(int level)
{
while (--level >= 0)
SkDebugf("\t");
}
void SkDOM::dump(const Node* node, int level) const
{
if (node == NULL)
node = this->getRootNode();
if (node)
{
tab(level);
SkDebugf("<%s", this->getName(node));
const Attr* attr = node->attrs();
const Attr* stop = attr + node->fAttrCount;
for (; attr < stop; attr++)
SkDebugf(" %s=\"%s\"", attr->fName, attr->fValue);
const Node* child = this->getFirstChild(node);
if (child)
{
SkDebugf(">\n");
while (child)
{
this->dump(child, level+1);
child = this->getNextSibling(child);
}
tab(level);
SkDebugf("</%s>\n", node->fName);
}
else
SkDebugf("/>\n");
}
}
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() == NULL);
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 == NULL);
SkASSERT(dom.getFirstChild(root, "elem1"));
SkASSERT(!dom.getFirstChild(root, "subelem1"));
dom.dump();
#endif
}
#endif