blob: 3fab211eaf8949e07d69bea636886ab9ea01b898 [file] [log] [blame]
#include "XMLHandler.h"
#include <algorithm>
#include <expat.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#define NS_SEPARATOR 1
#define MORE_INDENT " "
static string
xml_text_escape(const string& s)
{
string result;
const size_t N = s.length();
for (size_t i=0; i<N; i++) {
char c = s[i];
switch (c) {
case '<':
result += "&lt;";
break;
case '>':
result += "&gt;";
break;
case '&':
result += "&amp;";
break;
default:
result += c;
break;
}
}
return result;
}
static string
xml_attr_escape(const string& s)
{
string result;
const size_t N = s.length();
for (size_t i=0; i<N; i++) {
char c = s[i];
switch (c) {
case '\"':
result += "&quot;";
break;
default:
result += c;
break;
}
}
return result;
}
XMLNamespaceMap::XMLNamespaceMap()
{
}
XMLNamespaceMap::XMLNamespaceMap(char const*const* nspaces)
{
while (*nspaces) {
m_map[nspaces[1]] = nspaces[0];
nspaces += 2;
}
}
string
XMLNamespaceMap::Get(const string& ns) const
{
if (ns == "xml") {
return ns;
}
map<string,string>::const_iterator it = m_map.find(ns);
if (it == m_map.end()) {
return "";
} else {
return it->second;
}
}
string
XMLNamespaceMap::GetPrefix(const string& ns) const
{
if (ns == "") {
return "";
}
map<string,string>::const_iterator it = m_map.find(ns);
if (it != m_map.end()) {
if (it->second == "") {
return "";
} else {
return it->second + ":";
}
} else {
return ":"; // invalid
}
}
void
XMLNamespaceMap::AddToAttributes(vector<XMLAttribute>* attrs) const
{
map<string,string>::const_iterator it;
for (it=m_map.begin(); it!=m_map.end(); it++) {
if (it->second == "xml") {
continue;
}
XMLAttribute attr;
if (it->second == "") {
attr.name = "xmlns";
} else {
attr.name = "xmlns:";
attr.name += it->second;
}
attr.value = it->first;
attrs->push_back(attr);
}
}
XMLAttribute::XMLAttribute()
{
}
XMLAttribute::XMLAttribute(const XMLAttribute& that)
:ns(that.ns),
name(that.name),
value(that.value)
{
}
XMLAttribute::XMLAttribute(string n, string na, string v)
:ns(n),
name(na),
value(v)
{
}
XMLAttribute::~XMLAttribute()
{
}
int
XMLAttribute::Compare(const XMLAttribute& that) const
{
if (ns != that.ns) {
return ns < that.ns ? -1 : 1;
}
if (name != that.name) {
return name < that.name ? -1 : 1;
}
return 0;
}
string
XMLAttribute::Find(const vector<XMLAttribute>& list, const string& ns, const string& name,
const string& def)
{
const size_t N = list.size();
for (size_t i=0; i<N; i++) {
const XMLAttribute& attr = list[i];
if (attr.ns == ns && attr.name == name) {
return attr.value;
}
}
return def;
}
struct xml_handler_data {
vector<XMLHandler*> stack;
XML_Parser parser;
vector<vector<XMLAttribute>*> attributes;
string filename;
};
XMLNode::XMLNode()
{
}
XMLNode::~XMLNode()
{
// for_each(m_children.begin(), m_children.end(), delete_object<XMLNode>);
}
XMLNode*
XMLNode::Clone() const
{
switch (m_type) {
case ELEMENT: {
XMLNode* e = XMLNode::NewElement(m_pos, m_ns, m_name, m_attrs, m_pretty);
const size_t N = m_children.size();
for (size_t i=0; i<N; i++) {
e->m_children.push_back(m_children[i]->Clone());
}
return e;
}
case TEXT: {
return XMLNode::NewText(m_pos, m_text, m_pretty);
}
default:
return NULL;
}
}
XMLNode*
XMLNode::NewElement(const SourcePos& pos, const string& ns, const string& name,
const vector<XMLAttribute>& attrs, int pretty)
{
XMLNode* node = new XMLNode();
node->m_type = ELEMENT;
node->m_pretty = pretty;
node->m_pos = pos;
node->m_ns = ns;
node->m_name = name;
node->m_attrs = attrs;
return node;
}
XMLNode*
XMLNode::NewText(const SourcePos& pos, const string& text, int pretty)
{
XMLNode* node = new XMLNode();
node->m_type = TEXT;
node->m_pretty = pretty;
node->m_pos = pos;
node->m_text = text;
return node;
}
void
XMLNode::SetPrettyRecursive(int value)
{
m_pretty = value;
const size_t N = m_children.size();
for (size_t i=0; i<N; i++) {
m_children[i]->SetPrettyRecursive(value);
}
}
string
XMLNode::ContentsToString(const XMLNamespaceMap& nspaces) const
{
return contents_to_string(nspaces, "");
}
string
XMLNode::ToString(const XMLNamespaceMap& nspaces) const
{
return to_string(nspaces, "");
}
string
XMLNode::OpenTagToString(const XMLNamespaceMap& nspaces, int pretty) const
{
return open_tag_to_string(nspaces, "", pretty);
}
string
XMLNode::contents_to_string(const XMLNamespaceMap& nspaces, const string& indent) const
{
string result;
const size_t N = m_children.size();
for (size_t i=0; i<N; i++) {
const XMLNode* child = m_children[i];
switch (child->Type()) {
case ELEMENT:
if (m_pretty == PRETTY) {
result += '\n';
result += indent;
}
case TEXT:
result += child->to_string(nspaces, indent);
break;
}
}
return result;
}
string
trim_string(const string& str)
{
const char* p = str.c_str();
while (*p && isspace(*p)) {
p++;
}
const char* q = str.c_str() + str.length() - 1;
while (q > p && isspace(*q)) {
q--;
}
q++;
return string(p, q-p);
}
string
XMLNode::open_tag_to_string(const XMLNamespaceMap& nspaces, const string& indent, int pretty) const
{
if (m_type != ELEMENT) {
return "";
}
string result = "<";
result += nspaces.GetPrefix(m_ns);
result += m_name;
vector<XMLAttribute> attrs = m_attrs;
sort(attrs.begin(), attrs.end());
const size_t N = attrs.size();
for (size_t i=0; i<N; i++) {
const XMLAttribute& attr = attrs[i];
if (i == 0 || m_pretty == EXACT || pretty == EXACT) {
result += ' ';
}
else {
result += "\n";
result += indent;
result += MORE_INDENT;
result += MORE_INDENT;
}
result += nspaces.GetPrefix(attr.ns);
result += attr.name;
result += "=\"";
result += xml_attr_escape(attr.value);
result += '\"';
}
if (m_children.size() > 0) {
result += '>';
} else {
result += " />";
}
return result;
}
string
XMLNode::to_string(const XMLNamespaceMap& nspaces, const string& indent) const
{
switch (m_type)
{
case TEXT: {
if (m_pretty == EXACT) {
return xml_text_escape(m_text);
} else {
return xml_text_escape(trim_string(m_text));
}
}
case ELEMENT: {
string result = open_tag_to_string(nspaces, indent, PRETTY);
if (m_children.size() > 0) {
result += contents_to_string(nspaces, indent + MORE_INDENT);
if (m_pretty == PRETTY && m_children.size() > 0) {
result += '\n';
result += indent;
}
result += "</";
result += nspaces.GetPrefix(m_ns);
result += m_name;
result += '>';
}
return result;
}
default:
return "";
}
}
string
XMLNode::CollapseTextContents() const
{
if (m_type == TEXT) {
return m_text;
}
else if (m_type == ELEMENT) {
string result;
const size_t N=m_children.size();
for (size_t i=0; i<N; i++) {
result += m_children[i]->CollapseTextContents();
}
return result;
}
else {
return "";
}
}
vector<XMLNode*>
XMLNode::GetElementsByName(const string& ns, const string& name) const
{
vector<XMLNode*> result;
const size_t N=m_children.size();
for (size_t i=0; i<N; i++) {
XMLNode* child = m_children[i];
if (child->m_type == ELEMENT && child->m_ns == ns && child->m_name == name) {
result.push_back(child);
}
}
return result;
}
XMLNode*
XMLNode::GetElementByNameAt(const string& ns, const string& name, size_t index) const
{
vector<XMLNode*> result;
const size_t N=m_children.size();
for (size_t i=0; i<N; i++) {
XMLNode* child = m_children[i];
if (child->m_type == ELEMENT && child->m_ns == ns && child->m_name == name) {
if (index == 0) {
return child;
} else {
index--;
}
}
}
return NULL;
}
size_t
XMLNode::CountElementsByName(const string& ns, const string& name) const
{
size_t result = 0;
const size_t N=m_children.size();
for (size_t i=0; i<N; i++) {
XMLNode* child = m_children[i];
if (child->m_type == ELEMENT && child->m_ns == ns && child->m_name == name) {
result++;
}
}
return result;
}
string
XMLNode::GetAttribute(const string& ns, const string& name, const string& def) const
{
return XMLAttribute::Find(m_attrs, ns, name, def);
}
static void
parse_namespace(const char* data, string* ns, string* name)
{
const char* p = strchr(data, NS_SEPARATOR);
if (p != NULL) {
ns->assign(data, p-data);
name->assign(p+1);
} else {
ns->assign("");
name->assign(data);
}
}
static void
convert_attrs(const char** in, vector<XMLAttribute>* out)
{
while (*in) {
XMLAttribute attr;
parse_namespace(in[0], &attr.ns, &attr.name);
attr.value = in[1];
out->push_back(attr);
in += 2;
}
}
static bool
list_contains(const vector<XMLHandler*>& stack, XMLHandler* handler)
{
const size_t N = stack.size();
for (size_t i=0; i<N; i++) {
if (stack[i] == handler) {
return true;
}
}
return false;
}
static void XMLCALL
start_element_handler(void *userData, const char *name, const char **attrs)
{
xml_handler_data* data = (xml_handler_data*)userData;
XMLHandler* handler = data->stack[data->stack.size()-1];
SourcePos pos(data->filename, (int)XML_GetCurrentLineNumber(data->parser));
string nsString;
string nameString;
XMLHandler* next = handler;
vector<XMLAttribute> attributes;
parse_namespace(name, &nsString, &nameString);
convert_attrs(attrs, &attributes);
handler->OnStartElement(pos, nsString, nameString, attributes, &next);
if (next == NULL) {
next = handler;
}
if (next != handler) {
next->elementPos = pos;
next->elementNamespace = nsString;
next->elementName = nameString;
next->elementAttributes = attributes;
}
data->stack.push_back(next);
}
static void XMLCALL
end_element_handler(void *userData, const char *name)
{
xml_handler_data* data = (xml_handler_data*)userData;
XMLHandler* handler = data->stack[data->stack.size()-1];
data->stack.pop_back();
SourcePos pos(data->filename, (int)XML_GetCurrentLineNumber(data->parser));
if (!list_contains(data->stack, handler)) {
handler->OnDone(pos);
if (data->stack.size() > 1) {
// not top one
delete handler;
}
}
handler = data->stack[data->stack.size()-1];
string nsString;
string nameString;
parse_namespace(name, &nsString, &nameString);
handler->OnEndElement(pos, nsString, nameString);
}
static void XMLCALL
text_handler(void *userData, const XML_Char *s, int len)
{
xml_handler_data* data = (xml_handler_data*)userData;
XMLHandler* handler = data->stack[data->stack.size()-1];
SourcePos pos(data->filename, (int)XML_GetCurrentLineNumber(data->parser));
handler->OnText(pos, string(s, len));
}
static void XMLCALL
comment_handler(void *userData, const char *comment)
{
xml_handler_data* data = (xml_handler_data*)userData;
XMLHandler* handler = data->stack[data->stack.size()-1];
SourcePos pos(data->filename, (int)XML_GetCurrentLineNumber(data->parser));
handler->OnComment(pos, string(comment));
}
bool
XMLHandler::ParseFile(const string& filename, XMLHandler* handler)
{
char buf[16384];
int fd = open(filename.c_str(), O_RDONLY);
if (fd < 0) {
SourcePos(filename, -1).Error("Unable to open file for read: %s", strerror(errno));
return false;
}
XML_Parser parser = XML_ParserCreateNS(NULL, NS_SEPARATOR);
xml_handler_data state;
state.stack.push_back(handler);
state.parser = parser;
state.filename = filename;
XML_SetUserData(parser, &state);
XML_SetElementHandler(parser, start_element_handler, end_element_handler);
XML_SetCharacterDataHandler(parser, text_handler);
XML_SetCommentHandler(parser, comment_handler);
ssize_t len;
bool done;
do {
len = read(fd, buf, sizeof(buf));
done = len < (ssize_t)sizeof(buf);
if (len < 0) {
SourcePos(filename, -1).Error("Error reading file: %s\n", strerror(errno));
close(fd);
return false;
}
if (XML_Parse(parser, buf, len, done) == XML_STATUS_ERROR) {
SourcePos(filename, (int)XML_GetCurrentLineNumber(parser)).Error(
"Error parsing XML: %s\n", XML_ErrorString(XML_GetErrorCode(parser)));
close(fd);
return false;
}
} while (!done);
XML_ParserFree(parser);
close(fd);
return true;
}
bool
XMLHandler::ParseString(const string& filename, const string& text, XMLHandler* handler)
{
XML_Parser parser = XML_ParserCreateNS(NULL, NS_SEPARATOR);
xml_handler_data state;
state.stack.push_back(handler);
state.parser = parser;
state.filename = filename;
XML_SetUserData(parser, &state);
XML_SetElementHandler(parser, start_element_handler, end_element_handler);
XML_SetCharacterDataHandler(parser, text_handler);
XML_SetCommentHandler(parser, comment_handler);
if (XML_Parse(parser, text.c_str(), text.size(), true) == XML_STATUS_ERROR) {
SourcePos(filename, (int)XML_GetCurrentLineNumber(parser)).Error(
"Error parsing XML: %s\n", XML_ErrorString(XML_GetErrorCode(parser)));
return false;
}
XML_ParserFree(parser);
return true;
}
XMLHandler::XMLHandler()
{
}
XMLHandler::~XMLHandler()
{
}
int
XMLHandler::OnStartElement(const SourcePos& pos, const string& ns, const string& name,
const vector<XMLAttribute>& attrs, XMLHandler** next)
{
return 0;
}
int
XMLHandler::OnEndElement(const SourcePos& pos, const string& ns, const string& name)
{
return 0;
}
int
XMLHandler::OnText(const SourcePos& pos, const string& text)
{
return 0;
}
int
XMLHandler::OnComment(const SourcePos& pos, const string& text)
{
return 0;
}
int
XMLHandler::OnDone(const SourcePos& pos)
{
return 0;
}
TopElementHandler::TopElementHandler(const string& ns, const string& name, XMLHandler* next)
:m_ns(ns),
m_name(name),
m_next(next)
{
}
int
TopElementHandler::OnStartElement(const SourcePos& pos, const string& ns, const string& name,
const vector<XMLAttribute>& attrs, XMLHandler** next)
{
*next = m_next;
return 0;
}
int
TopElementHandler::OnEndElement(const SourcePos& pos, const string& ns, const string& name)
{
return 0;
}
int
TopElementHandler::OnText(const SourcePos& pos, const string& text)
{
return 0;
}
int
TopElementHandler::OnDone(const SourcePos& pos)
{
return 0;
}
NodeHandler::NodeHandler(XMLNode* root, int pretty)
:m_root(root),
m_pretty(pretty)
{
if (root != NULL) {
m_nodes.push_back(root);
}
}
NodeHandler::~NodeHandler()
{
}
int
NodeHandler::OnStartElement(const SourcePos& pos, const string& ns, const string& name,
const vector<XMLAttribute>& attrs, XMLHandler** next)
{
int pretty;
if (XMLAttribute::Find(attrs, XMLNS_XMLNS, "space", "") == "preserve") {
pretty = XMLNode::EXACT;
} else {
if (m_root == NULL) {
pretty = m_pretty;
} else {
pretty = m_nodes[m_nodes.size()-1]->Pretty();
}
}
XMLNode* n = XMLNode::NewElement(pos, ns, name, attrs, pretty);
if (m_root == NULL) {
m_root = n;
} else {
m_nodes[m_nodes.size()-1]->EditChildren().push_back(n);
}
m_nodes.push_back(n);
return 0;
}
int
NodeHandler::OnEndElement(const SourcePos& pos, const string& ns, const string& name)
{
m_nodes.pop_back();
return 0;
}
int
NodeHandler::OnText(const SourcePos& pos, const string& text)
{
if (m_root == NULL) {
return 1;
}
XMLNode* n = XMLNode::NewText(pos, text, m_nodes[m_nodes.size()-1]->Pretty());
m_nodes[m_nodes.size()-1]->EditChildren().push_back(n);
return 0;
}
int
NodeHandler::OnComment(const SourcePos& pos, const string& text)
{
return 0;
}
int
NodeHandler::OnDone(const SourcePos& pos)
{
return 0;
}
XMLNode*
NodeHandler::ParseFile(const string& filename, int pretty)
{
NodeHandler handler(NULL, pretty);
if (!XMLHandler::ParseFile(filename, &handler)) {
fprintf(stderr, "error parsing file: %s\n", filename.c_str());
return NULL;
}
return handler.Root();
}
XMLNode*
NodeHandler::ParseString(const string& filename, const string& text, int pretty)
{
NodeHandler handler(NULL, pretty);
if (!XMLHandler::ParseString(filename, text, &handler)) {
fprintf(stderr, "error parsing file: %s\n", filename.c_str());
return NULL;
}
return handler.Root();
}