yate/engine/XML.cpp

3962 lines
107 KiB
C++

/**
* XML.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2014 Null Team
*
* This software is distributed under multiple licenses;
* see the COPYING file in the main directory for licensing
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#include <yatexml.h>
#include <string.h>
using namespace TelEngine;
const String XmlElement::s_ns = "xmlns";
const String XmlElement::s_nsPrefix = "xmlns:";
static const String s_type("type");
static const String s_name("name");
// Return a replacement char for the given string
static inline char replace(const char* str, const XmlEscape* esc)
{
if (!str)
return 0;
if (esc) {
for (; esc->value; esc++)
if (!::strcmp(str,esc->value))
return esc->replace;
}
return 0;
}
// Return a replacement string for the given char
static inline const char* replace(char replace, const XmlEscape* esc)
{
if (esc) {
for (; esc->value; esc++)
if (replace == esc->replace)
return esc->value;
}
return 0;
}
// XmlEscape a string or replace it if found in a list of restrictions
static inline void addAuth(String& buf, const String& comp, const String& value,
bool esc, const String* auth)
{
if (auth) {
for (; !auth->null(); auth++)
if (*auth == comp) {
buf << "***";
return;
}
}
if (esc)
XmlSaxParser::escape(buf,value);
else
buf << value;
}
/*
* XmlSaxParser
*/
const TokenDict XmlSaxParser::s_errorString[] = {
{"No error", NoError},
{"Error", Unknown},
{"Not well formed", NotWellFormed},
{"I/O error", IOError},
{"Error parsing Element", ElementParse},
{"Failed to read Element name", ReadElementName},
{"Bad element name", InvalidElementName},
{"Error reading Attributes", ReadingAttributes},
{"Error reading end tag", ReadingEndTag},
{"Error parsing Comment", CommentParse},
{"Error parsing Declaration", DeclarationParse},
{"Error parsing Definition", DefinitionParse},
{"Error parsing CDATA", CDataParse},
{"Incomplete", Incomplete},
{"Invalid encoding", InvalidEncoding},
{"Unsupported encoding", UnsupportedEncoding},
{"Unsupported version", UnsupportedVersion},
{0,0}
};
const XmlEscape XmlSaxParser::s_escape[] = {
{"&lt;", '<'},
{"&gt;", '>'},
{"&amp;", '&'},
{"&quot;", '\"'},
{"&apos;", '\''},
{0,0}
};
XmlSaxParser::XmlSaxParser(const char* name)
: m_offset(0), m_row(1), m_column(1), m_error(NoError),
m_parsed(""), m_unparsed(None)
{
debugName(name);
}
XmlSaxParser::~XmlSaxParser()
{
}
// Parse a given string
bool XmlSaxParser::parse(const char* text)
{
if (TelEngine::null(text))
return m_error == NoError;
#ifdef XDEBUG
String tmp;
m_parsed.dump(tmp," ");
if (tmp)
tmp = " parsed=" + tmp;
XDebug(this,DebugAll,"XmlSaxParser::parse(%s) unparsed=%u%s buf=%s [%p]",
text,unparsed(),tmp.safe(),m_buf.safe(),this);
#endif
char car;
setError(NoError);
String auxData;
m_buf << text;
if (m_buf.lenUtf8() == -1) {
//FIXME this should not be here in case we have a different encoding
DDebug(this,DebugNote,"Request to parse invalid utf-8 data [%p]",this);
return setError(Incomplete);
}
if (unparsed()) {
if (unparsed() != Text) {
if (!auxParse())
return false;
}
else
auxData = m_parsed;
resetParsed();
setUnparsed(None);
}
unsigned int len = 0;
while (m_buf.at(len) && !error()) {
car = m_buf.at(len);
if (car != '<' ) { // We have a new child check what it is
if (car == '>' || !checkDataChar(car)) {
Debug(this,DebugNote,"XML text contains unescaped '%c' character [%p]",
car,this);
return setError(Unknown);
}
len++; // Append xml Text
continue;
}
if (len > 0) {
auxData << m_buf.substr(0,len);
}
if (auxData.c_str()) { // We have an end of tag or another child is riseing
if (!processText(auxData))
return false;
m_buf = m_buf.substr(len);
len = 0;
auxData = "";
}
char auxCar = m_buf.at(1);
if (!auxCar)
return setError(Incomplete);
if (auxCar == '?') {
m_buf = m_buf.substr(2);
if (!parseInstruction())
return false;
continue;
}
if (auxCar == '!') {
m_buf = m_buf.substr(2);
if (!parseSpecial())
return false;
continue;
}
if (auxCar == '/') {
m_buf = m_buf.substr(2);
if (!parseEndTag())
return false;
continue;
}
// If we are here mens that we have a element
// process an xml element
m_buf = m_buf.substr(1);
if (!parseElement())
return false;
}
// Incomplete text
if ((unparsed() == None || unparsed() == Text) && (auxData || m_buf)) {
if (!auxData)
m_parsed.assign(m_buf);
else {
auxData << m_buf;
m_parsed.assign(auxData);
}
m_buf = "";
setUnparsed(Text);
return setError(Incomplete);
}
if (error()) {
DDebug(this,DebugNote,"Got error while parsing %s [%p]",getError(),this);
return false;
}
m_buf = "";
resetParsed();
setUnparsed(None);
return true;
}
// Process incomplete text
bool XmlSaxParser::completeText()
{
if (!completed() || unparsed() != Text || error() != Incomplete)
return error() == NoError;
String tmp = m_parsed;
return processText(tmp);
}
// Parse an unfinished xml object
bool XmlSaxParser::auxParse()
{
switch (unparsed()) {
case Element:
return parseElement();
case CData:
return parseCData();
case Comment:
return parseComment();
case Declaration:
return parseDeclaration();
case Instruction:
return parseInstruction();
case EndTag:
return parseEndTag();
case Special:
return parseSpecial();
default:
return false;
}
}
// Set the error code and destroys a child if error code is not NoError
bool XmlSaxParser::setError(Error error, XmlChild* child)
{
m_error = error;
if (child && error)
TelEngine::destruct(child);
return m_error == XmlSaxParser::NoError;
}
// Parse an endtag form the main buffer
bool XmlSaxParser::parseEndTag()
{
bool aux = false;
String* name = extractName(aux);
// We don't check aux flag because we don't look for attributes here
if (!name) {
if (error() && error() == Incomplete)
setUnparsed(EndTag);
return false;
}
if (!aux || m_buf.at(0) == '/') { // The end tag has attributes or contains / char at the end of name
setError(ReadingEndTag);
Debug(this,DebugNote,"Got bad end tag </%s/> [%p]",name->c_str(),this);
setUnparsed(EndTag);
m_buf = *name + m_buf;
return false;
}
resetError();
endElement(*name);
if (error()) {
setUnparsed(EndTag);
m_buf = *name + ">";
TelEngine::destruct(name);
return false;
}
m_buf = m_buf.substr(1);
TelEngine::destruct(name);
return true;
}
// Parse an instruction form the main buffer
bool XmlSaxParser::parseInstruction()
{
XDebug(this,DebugAll,"XmlSaxParser::parseInstruction() buf len=%u [%p]",m_buf.length(),this);
setUnparsed(Instruction);
if (!m_buf.c_str())
return setError(Incomplete);
// extract the name
String name;
char c;
int len = 0;
if (!m_parsed) {
bool nameComplete = false;
bool endDecl = false;
while (0 != (c = m_buf.at(len))) {
nameComplete = blank(c);
if (!nameComplete) {
// Check for instruction end: '?>'
if (c == '?') {
char next = m_buf.at(len + 1);
if (!next)
return setError(Incomplete);
if (next == '>') {
nameComplete = endDecl = true;
break;
}
}
if (checkNameCharacter(c)) {
len++;
continue;
}
Debug(this,DebugNote,"Instruction name contains bad character '%c' [%p]",c,this);
return setError(InvalidElementName);
}
// Blank found
if (len)
break;
Debug(this,DebugNote,"Instruction with empty name [%p]",this);
return setError(InvalidElementName);
}
if (!len) {
if (!endDecl)
return setError(Incomplete);
// Remove instruction end from buffer
m_buf = m_buf.substr(2);
Debug(this,DebugNote,"Instruction with empty name [%p]",this);
return setError(InvalidElementName);
}
if (!nameComplete)
return setError(Incomplete);
name = m_buf.substr(0,len);
m_buf = m_buf.substr(!endDecl ? len : len + 2);
if (name == YSTRING("xml")) {
if (!endDecl)
return parseDeclaration();
resetParsed();
resetError();
setUnparsed(None);
gotDeclaration(NamedList::empty());
return error() == NoError;
}
// Instruction name can't be xml case insensitive
if (name.length() == 3 && name.startsWith("xml",false,true)) {
Debug(this,DebugNote,"Instruction name '%s' reserved [%p]",name.c_str(),this);
return setError(InvalidElementName);
}
}
else {
name = m_parsed;
resetParsed();
}
// Retrieve instruction content
skipBlanks();
len = 0;
while (0 != (c = m_buf.at(len))) {
if (c != '?') {
if (c == 0x0c) {
setError(Unknown);
Debug(this,DebugNote,"Xml instruction with unaccepted character '%c' [%p]",
c,this);
return false;
}
len++;
continue;
}
char ch = m_buf.at(len + 1);
if (!ch)
break;
if (ch == '>') { // end of instruction
NamedString inst(name,m_buf.substr(0,len));
// Parsed instruction: remove instruction end from buffer and reset parsed
m_buf = m_buf.substr(len + 2);
resetParsed();
resetError();
setUnparsed(None);
gotProcessing(inst);
return error() == NoError;
}
len ++;
}
// If we are here mens that text has reach his bounds is an error or we need to receive more data
m_parsed.assign(name);
return setError(Incomplete);
}
// Parse a declaration form the main buffer
bool XmlSaxParser::parseDeclaration()
{
XDebug(this,DebugAll,"XmlSaxParser::parseDeclaration() buf len=%u [%p]",m_buf.length(),this);
setUnparsed(Declaration);
if (!m_buf.c_str())
return setError(Incomplete);
NamedList dc("xml");
if (m_parsed.count()) {
dc.copyParams(m_parsed);
resetParsed();
}
char c;
skipBlanks();
int len = 0;
while (m_buf.at(len)) {
c = m_buf.at(len);
if (c != '?') {
skipBlanks();
NamedString* s = getAttribute();
if (!s) {
if (error() == Incomplete)
m_parsed = dc;
return false;
}
len = 0;
if (dc.getParam(s->name())) {
Debug(this,DebugNote,"Duplicate attribute '%s' in declaration [%p]",
s->name().c_str(),this);
TelEngine::destruct(s);
return setError(DeclarationParse);
}
dc.addParam(s);
char ch = m_buf.at(len);
if (ch && !blank(ch) && ch != '?') {
Debug(this,DebugNote,"No blanks between attributes in declaration [%p]",this);
return setError(DeclarationParse);
}
skipBlanks();
continue;
}
if (!m_buf.at(++len))
break;
char ch = m_buf.at(len);
if (ch == '>') { // end of declaration
// Parsed declaration: remove declaration end from buffer and reset parsed
resetError();
resetParsed();
setUnparsed(None);
m_buf = m_buf.substr(len + 1);
gotDeclaration(dc);
return error() == NoError;
}
Debug(this,DebugNote,"Invalid declaration ending char '%c' [%p]",ch,this);
return setError(DeclarationParse);
}
m_parsed.copyParams(dc);
setError(Incomplete);
return false;
}
// Parse a CData section form the main buffer
bool XmlSaxParser::parseCData()
{
if (!m_buf.c_str()) {
setUnparsed(CData);
setError(Incomplete);
return false;
}
String cdata = "";
if (m_parsed.c_str()) {
cdata = m_parsed;
resetParsed();
}
char c;
int len = 0;
while (m_buf.at(len)) {
c = m_buf.at(len);
if (c != ']') {
len ++;
continue;
}
if (m_buf.substr(++len,2) == "]>") { // End of CData section
cdata += m_buf.substr(0,len - 1);
resetError();
gotCdata(cdata);
resetParsed();
if (error())
return false;
m_buf = m_buf.substr(len + 2);
return true;
}
}
cdata += m_buf;
m_buf = "";
setUnparsed(CData);
int length = cdata.length();
m_buf << cdata.substr(length - 2);
if (length > 1)
m_parsed.assign(cdata.substr(0,length - 2));
setError(Incomplete);
return false;
}
// Helper method to classify the Xml objects starting with "<!" sequence
bool XmlSaxParser::parseSpecial()
{
if (m_buf.length() < 2) {
setUnparsed(Special);
return setError(Incomplete);
}
if (m_buf.startsWith("--")) {
m_buf = m_buf.substr(2);
if (!parseComment())
return false;
return true;
}
if (m_buf.length() < 7) {
setUnparsed(Special);
return setError(Incomplete);
}
if (m_buf.startsWith("[CDATA[")) {
m_buf = m_buf.substr(7);
if (!parseCData())
return false;
return true;
}
if (m_buf.startsWith("DOCTYPE")) {
m_buf = m_buf.substr(7);
if (!parseDoctype())
return false;
return true;
}
Debug(this,DebugNote,"Can't parse unknown special starting with '%s' [%p]",
m_buf.c_str(),this);
setError(Unknown);
return false;
}
// Extract from the given buffer an comment and check if is valid
bool XmlSaxParser::parseComment()
{
String comment;
if (m_parsed.c_str()) {
comment = m_parsed;
resetParsed();
}
char c;
int len = 0;
while (m_buf.at(len)) {
c = m_buf.at(len);
if (c != '-') {
if (c == 0x0c) {
Debug(this,DebugNote,"Xml comment with unaccepted character '%c' [%p]",c,this);
return setError(NotWellFormed);
}
len++;
continue;
}
if (m_buf.at(len + 1) == '-' && m_buf.at(len + 2) == '>') { // End of comment
comment << m_buf.substr(0,len);
m_buf = m_buf.substr(len + 3);
#ifdef DEBUG
if (comment.at(0) == '-' || comment.at(comment.length() - 1) == '-')
DDebug(this,DebugInfo,"Comment starts or ends with '-' character [%p]",this);
if (comment.find("--") >= 0)
DDebug(this,DebugInfo,"Comment contains '--' char sequence [%p]",this);
#endif
gotComment(comment);
resetParsed();
// The comment can apear anywhere sow SaxParser never
// sets an error when receive a comment
return true;
}
len++;
}
// If we are here we haven't detect the end of comment
comment << m_buf;
int length = comment.length();
// Keep the last 2 charaters in buffer because if the input buffer ends
// between "--" and ">"
m_buf = comment.substr(length - 2);
setUnparsed(Comment);
if (length > 1)
m_parsed.assign(comment.substr(0,length - 2));
return setError(Incomplete);
}
// Parse an element form the main buffer
bool XmlSaxParser::parseElement()
{
XDebug(this,DebugAll,"XmlSaxParser::parseElement() buf len=%u [%p]",m_buf.length(),this);
if (!m_buf.c_str()) {
setUnparsed(Element);
return setError(Incomplete);
}
bool empty = false;
if (!m_parsed.c_str()) {
String* name = extractName(empty);
if (!name) {
if (error() == Incomplete)
setUnparsed(Element);
return false;
}
#ifdef XML_STRICT
// http://www.w3.org/TR/REC-xml/
// Names starting with 'xml' (case insensitive) are reserved
if (name->startsWith("xml",false,true)) {
Debug(this,DebugNote,"Element tag starts with 'xml' [%p]",this);
TelEngine::destruct(name);
return setError(ReadElementName);
}
#endif
m_parsed.assign(*name);
TelEngine::destruct(name);
}
if (empty) { // empty flag means that the element does not have attributes
// check if the element is empty
bool aux = m_buf.at(0) == '/';
if (!processElement(m_parsed,aux))
return false;
if (aux)
m_buf = m_buf.substr(2); // go back where we were
else
m_buf = m_buf.substr(1); // go back where we were
return true;
}
char c;
skipBlanks();
int len = 0;
while (m_buf.at(len)) {
c = m_buf.at(len);
if (c == '/' || c == '>') { // end of element declaration
if (c == '>') {
if (!processElement(m_parsed,false))
return false;
m_buf = m_buf.substr(1);
return true;
}
if (!m_buf.at(++len))
break;
char ch = m_buf.at(len);
if (ch != '>') {
Debug(this,DebugNote,"Element attribute name contains '/' character [%p]",this);
return setError(ReadingAttributes);
}
if (!processElement(m_parsed,true))
return false;
m_buf = m_buf.substr(len + 1);
return true;
}
NamedString* ns = getAttribute();
if (!ns) { // Attribute is invalid
if (error() == Incomplete)
break;
return false;
}
if (m_parsed.getParam(ns->name())) {
Debug(this,DebugNote,"Duplicate attribute '%s' [%p]",ns->name().c_str(),this);
TelEngine::destruct(ns);
return setError(NotWellFormed);
}
XDebug(this,DebugAll,"Parser adding attribute %s='%s' to '%s' [%p]",
ns->name().c_str(),ns->c_str(),m_parsed.c_str(),this);
m_parsed.setParam(ns);
char ch = m_buf.at(len);
if (ch && !blank(ch) && (ch != '/' && ch != '>')) {
Debug(this,DebugNote,"Element without blanks between attributes [%p]",this);
return setError(NotWellFormed);
}
skipBlanks();
}
setUnparsed(Element);
return setError(Incomplete);
}
// Parse a doctype form the main buffer
bool XmlSaxParser::parseDoctype()
{
if (!m_buf.c_str()) {
setUnparsed(Doctype);
setError(Incomplete);
return false;
}
unsigned int len = 0;
skipBlanks();
while (m_buf.at(len) && !blank(m_buf.at(len)))
len++;
// Use a while() to break to the end
while (m_buf.at(len)) {
while (m_buf.at(len) && blank(m_buf.at(len)))
len++;
if (len >= m_buf.length())
break;
if (m_buf[len++] == '[') {
while (len < m_buf.length()) {
if (m_buf[len] != ']') {
len ++;
continue;
}
if (m_buf.at(++len) != '>')
continue;
gotDoctype(m_buf.substr(0,len));
resetParsed();
m_buf = m_buf.substr(len + 1);
return true;
}
break;
}
while (len < m_buf.length()) {
if (m_buf[len] != '>') {
len++;
continue;
}
gotDoctype(m_buf.substr(0,len));
resetParsed();
m_buf = m_buf.substr(len + 1);
return true;
}
break;
}
setUnparsed(Doctype);
return setError(Incomplete);
}
// Extract the name of tag
String* XmlSaxParser::extractName(bool& empty)
{
skipBlanks();
unsigned int len = 0;
bool ok = false;
empty = false;
while (len < m_buf.length()) {
char c = m_buf[len];
if (blank(c)) {
if (checkFirstNameCharacter(m_buf[0])) {
ok = true;
break;
}
Debug(this,DebugNote,"Element tag starting with invalid char %c [%p]",
m_buf[0],this);
setError(ReadElementName);
return 0;
}
if (c == '/' || c == '>') { // end of element declaration
if (c == '>') {
if (checkFirstNameCharacter(m_buf[0])) {
empty = true;
ok = true;
break;
}
Debug(this,DebugNote,"Element tag starting with invalid char %c [%p]",
m_buf[0],this);
setError(ReadElementName);
return 0;
}
char ch = m_buf.at(len + 1);
if (!ch)
break;
if (ch != '>') {
Debug(this,DebugNote,"Element tag contains '/' character [%p]",this);
setError(ReadElementName);
return 0;
}
if (checkFirstNameCharacter(m_buf[0])) {
empty = true;
ok = true;
break;
}
Debug(this,DebugNote,"Element tag starting with invalid char %c [%p]",
m_buf[0],this);
setError(ReadElementName);
return 0;
}
if (checkNameCharacter(c))
len++;
else {
Debug(this,DebugNote,"Element tag contains invalid char %c [%p]",c,this);
setError(ReadElementName);
return 0;
}
}
if (ok) {
String* name = new String(m_buf.substr(0,len));
m_buf = m_buf.substr(len);
if (!empty) {
skipBlanks();
empty = (m_buf && m_buf[0] == '>') ||
(m_buf.length() > 1 && m_buf[0] == '/' && m_buf[1] == '>');
}
return name;
}
setError(Incomplete);
return 0;
}
// Extract an attribute
NamedString* XmlSaxParser::getAttribute()
{
String name = "";
skipBlanks();
char c,sep = 0;
unsigned int len = 0;
while (len < m_buf.length()) { // Circle until we find attribute value startup character (["]|['])
c = m_buf[len];
if (blank(c) || c == '=') {
if (!name.c_str())
name = m_buf.substr(0,len);
len++;
continue;
}
if (!name.c_str()) {
if (!checkNameCharacter(c)) {
Debug(this,DebugNote,"Attribute name contains %c character [%p]",c,this);
setError(ReadingAttributes);
return 0;
}
len++;
continue;
}
if (c != '\'' && c != '\"') {
Debug(this,DebugNote,"Unenclosed attribute value [%p]",this);
setError(ReadingAttributes);
return 0;
}
sep = c;
break;
}
if (!sep) {
setError(Incomplete);
return 0;
}
if (!checkFirstNameCharacter(name[0])) {
Debug(this,DebugNote,"Attribute name starting with bad character %c [%p]",
name.at(0),this);
setError(ReadingAttributes);
return 0;
}
int pos = ++len;
while (len < m_buf.length()) {
c = m_buf[len];
if (c != sep && !badCharacter(c)) {
len ++;
continue;
}
if (badCharacter(c)) {
Debug(this,DebugNote,"Attribute value with unescaped character '%c' [%p]",
c,this);
setError(ReadingAttributes);
return 0;
}
NamedString* ns = new NamedString(name,m_buf.substr(pos,len - pos));
m_buf = m_buf.substr(len + 1);
// End of attribute value
unEscape(*ns);
if (error()) {
TelEngine::destruct(ns);
return 0;
}
return ns;
}
setError(Incomplete);
return 0;
}
// Reset this parser
void XmlSaxParser::reset()
{
m_offset = 0;
m_row = 1;
m_column = 1;
m_error = NoError;
m_buf.clear();
resetParsed();
m_unparsed = None;
}
// Check if the given character is in the range allowed for an xml char
bool XmlSaxParser::checkDataChar(unsigned char c)
{
return c == 0x9 || c == 0xA || c == 0xD || (c >= 0x20);
}
// Remove blank characters from the beginning of the buffer
void XmlSaxParser::skipBlanks()
{
unsigned int len = 0;
while (len < m_buf.length() && blank(m_buf[len]))
len++;
if (len != 0)
m_buf = m_buf.substr(len);
}
// Obtain a char from an ascii decimal char declaration
inline unsigned char getDec(String& dec)
{
if (dec.length() > 6) {
DDebug(DebugNote,"Decimal number '%s' too long",dec.c_str());
return 0;
}
int num = dec.substr(2,dec.length() - 3).toInteger(-1);
if (num > 0 && num < 256)
return num;
DDebug(DebugNote,"Invalid decimal number '%s'",dec.c_str());
return 0;
}
// Unescape the given text
void XmlSaxParser::unEscape(String& text)
{
String error;
if (unEscape(text,&error))
return;
Debug(this,DebugNote,"Unescape. %s [%p]",error.c_str(),this);
setError(NotWellFormed);
}
// Check if a given string is a valid xml tag name
bool XmlSaxParser::validTag(const String& buf)
{
if (!(buf && checkFirstNameCharacter(buf[0])))
return false;
for (unsigned int i = 1; i < buf.length(); i++)
if (!checkNameCharacter(buf[i]))
return false;
return true;
}
// XmlEscape the given text
String& XmlSaxParser::escape(String& buf, const String& text)
{
const char* str = text.c_str();
if (!str)
return buf;
const char* accum = str;
unsigned int aLen = 0;
while (*str) {
const char* rep = replace(*str++,XmlSaxParser::s_escape);
if (!rep) {
aLen++;
continue;
}
if (aLen)
buf.append(accum,aLen);
accum = str;
aLen = 0;
buf += rep;
}
if (aLen)
return buf.append(accum,aLen);
return buf;
}
// Unescape the given text
bool XmlSaxParser::unEscape(String& text, const char* str, unsigned int n, String* error,
bool inText, bool* escFound)
{
if (escFound)
*escFound = false;
if (!(str && n))
return true;
inText = inText && (str != text.c_str());
String tmp;
String& buf = inText ? text : tmp;
String aux = "&";
unsigned int len = 0;
int found = -1;
for (unsigned int i = 0; i < n; ++i) {
if (str[len] == '&' && found < 0) {
found = len++;
continue;
}
if (found < 0) {
len++;
continue;
}
if (str[len] == '&') {
if (error)
*error = "Duplicate '&' in expression";
return false;
}
if (str[len] != ';')
len++;
else { // We have a candidate for escaping
len += 1; // Append ';' character
String aux(str + found,len - found);
char re = 0;
if (aux.startsWith("&#")) {
if (aux.at(2) == 'x') {
if (aux.length() > 4 && aux.length() <= 12) {
int esc = aux.substr(3,aux.length() - 4).toInteger(-1,16);
if (esc != -1) {
UChar uc(esc);
buf.append(str,found) << uc.c_str();
str += len;
len = 0;
found = -1;
continue;
}
}
} else
re = getDec(aux);
}
if (re == '&') {
if (str[len] == '#') {
aux.assign(str + len,4);
if (aux == "#60;") {
re = '<';
len += 4;
}
if (aux == "#38;") {
re = '&';
len += 4;
}
}
}
else if (!re)
re = replace(aux,s_escape);
if (!re) {
if (error)
error->printf("No replacement found for '%s'",(String(str + found,len - found)).c_str());
return false;
}
if (escFound)
*escFound = true;
// We have an valid escape character
buf.append(str,found) << re;
str += len;
len = 0;
found = -1;
}
}
if (found >= 0) {
if (error)
*error = "Unexpected end of expression";
return false;
}
if (inText) {
if (len)
buf.append(str,len);
}
else if (len) {
if (str != text.c_str()) {
buf.append(str,len);
text = buf;
}
}
else
text = buf;
return true;
}
// Calls gotElement(). Reset parsed if ok
bool XmlSaxParser::processElement(NamedList& list, bool empty)
{
gotElement(list,empty);
if (error() == XmlSaxParser::NoError) {
resetParsed();
return true;
}
return false;
}
// Calls gotText() and reset parsed on success
bool XmlSaxParser::processText(String& text)
{
resetError();
unEscape(text);
if (!error())
gotText(text);
else
setUnparsed(Text);
if (!error()) {
resetParsed();
setUnparsed(None);
}
return error() == NoError;
}
/*
* XmlDomPareser
*/
XmlDomParser::XmlDomParser(const char* name, bool fragment)
: XmlSaxParser(name),
m_current(0), m_data(0), m_ownData(true)
{
if (fragment)
m_data = new XmlFragment();
else
m_data = new XmlDocument();
}
XmlDomParser::XmlDomParser(XmlParent* fragment, bool takeOwnership)
: m_current(0), m_data(0), m_ownData(takeOwnership)
{
m_data = fragment;
}
XmlDomParser::~XmlDomParser()
{
if (m_ownData) {
reset();
if (m_data)
delete m_data;
}
}
// Create a new xml comment and append it in the xml three
void XmlDomParser::gotComment(const String& text)
{
XmlComment* com = new XmlComment(text);
if (m_current)
setError(m_current->addChild(com),com);
else
setError(m_data->addChild(com),com);
}
// Append a new xml doctype to main xml parent
void XmlDomParser::gotDoctype(const String& doc)
{
m_data->addChild(new XmlDoctype(doc));
}
// TODO implement it see what to do
void XmlDomParser::gotProcessing(const NamedString& instr)
{
DDebug(this,DebugStub,"gotProcessing(%s=%s) not implemented [%p]",
instr.name().c_str(),instr.safe(),this);
}
// Create a new xml declaration, verifies the version and encoding
// and append it in the main xml parent
void XmlDomParser::gotDeclaration(const NamedList& decl)
{
if (m_current) {
setError(DeclarationParse);
Debug(this,DebugNote,"Received declaration inside element bounds [%p]",this);
return;
}
Error err = NoError;
while (true) {
String* version = decl.getParam("version");
if (version) {
int ver = version->substr(0,version->find('.')).toInteger();
if (ver != 1) {
err = UnsupportedVersion;
break;
}
}
String* enc = decl.getParam("encoding");
if (enc && !(*enc &= "utf-8")) {
err = UnsupportedEncoding;
break;
}
break;
}
if (err == NoError) {
XmlDeclaration* dec = new XmlDeclaration(decl);
setError(m_data->addChild(dec),dec);
}
else {
setError(err);
Debug(this,DebugNote,
"Received unacceptable declaration version='%s' encoding='%s' error '%s' [%p]",
decl.getValue("version"),decl.getValue("encoding"),getError(),this);
}
}
// Create a new xml text and append it in the xml tree
void XmlDomParser::gotText(const String& text)
{
XmlText* tet = new XmlText(text);
if (m_current)
m_current->addChild(tet);
else
setError(m_data->addChild(tet),tet);
}
// Create a new xml Cdata and append it in the xml tree
void XmlDomParser::gotCdata(const String& data)
{
XmlCData* cdata = new XmlCData(data);
if (!m_current) {
if (m_data->document()) {
Debug(this,DebugNote,"Document got CDATA outside element [%p]",this);
setError(NotWellFormed);
TelEngine::destruct(cdata);
return;
}
setError(m_data->addChild(cdata),cdata);
return;
}
setError(m_current->addChild(cdata),cdata);
}
// Create a new xml element and append it in the xml tree
void XmlDomParser::gotElement(const NamedList& elem, bool empty)
{
XmlElement* element = 0;
if (!m_current) {
// If we don't have curent element menns that the main fragment
// should hold it
element = new XmlElement(elem,empty);
setError(m_data->addChild(element),element);
if (!empty && error() == XmlSaxParser::NoError)
m_current = element;
}
else {
if (empty) {
element = new XmlElement(elem,empty);
setError(m_current->addChild(element),element);
}
else {
element = new XmlElement(elem,empty,m_current);
setError(m_current->addChild(element),element);
if (error() == XmlSaxParser::NoError)
m_current = element;
}
}
}
// Verify if is the closeing tag for the current element
// Complete th current element and make current the current parent
void XmlDomParser::endElement(const String& name)
{
if (!m_current) {
setError(ReadingEndTag);
Debug(this,DebugNote,"Unexpected element end tag %s [%p]",name.c_str(),this);
return;
}
if (m_current->getName() != name) {
setError(ReadingEndTag);
Debug(this,DebugNote,
"Received end element for %s, but the expected one is for %s [%p]",
name.c_str(),m_current->getName().c_str(),this);
return;
}
m_current->setCompleted();
XDebug(this,DebugInfo,"End element for %s [%p]",m_current->getName().c_str(),this);
m_current = static_cast<XmlElement*>(m_current->getParent());
}
// Reset this parser
void XmlDomParser::reset()
{
m_data->reset();
m_current = 0;
XmlSaxParser::reset();
}
/*
* XmlDeclaration
*/
// Create a new XmlDeclaration from version and encoding
XmlDeclaration::XmlDeclaration(const char* version, const char* enc)
: m_declaration("")
{
XDebug(DebugAll,"XmlDeclaration::XmlDeclaration(%s,%s) [%p]",version,enc,this);
if (!TelEngine::null(version))
m_declaration.addParam("version",version);
if (!TelEngine::null(enc))
m_declaration.addParam("encoding",enc);
}
// Constructor
XmlDeclaration::XmlDeclaration(const NamedList& decl)
: m_declaration(decl)
{
XDebug(DebugAll,"XmlDeclaration::XmlDeclaration(%s) [%p]",m_declaration.c_str(),this);
}
// Copy Constructor
XmlDeclaration::XmlDeclaration(const XmlDeclaration& decl)
: m_declaration(decl.getDec())
{
}
// Destructor
XmlDeclaration::~XmlDeclaration()
{
XDebug(DebugAll,"XmlDeclaration::~XmlDeclaration() ( %s| %p )",
m_declaration.c_str(),this);
}
// Create a String from this Xml Declaration
void XmlDeclaration::toString(String& dump, bool esc) const
{
dump << "<?" << "xml";
int n = m_declaration.count();
for (int i = 0;i < n;i ++) {
NamedString* ns = m_declaration.getParam(i);
if (!ns)
continue;
dump += " ";
dump += ns->name();
dump << "=\"";
if (esc)
XmlSaxParser::escape(dump,*ns);
else
dump += *ns;
dump << "\"";
}
dump << "?>";
}
/*
* XmlFragment
*/
// Constructor
XmlFragment::XmlFragment()
: m_list()
{
XDebug(DebugAll,"XmlFragment::XmlFragment() ( %p )",this);
}
// Copy Constructor
XmlFragment::XmlFragment(const XmlFragment& orig)
{
copy(orig);
}
// Destructor
XmlFragment::~XmlFragment()
{
m_list.clear();
XDebug(DebugAll,"XmlFragment::~XmlFragment() ( %p )",this);
}
// Reset. Clear children list
void XmlFragment::reset()
{
m_list.clear();
}
// Append a new child
XmlSaxParser::Error XmlFragment::addChild(XmlChild* child)
{
if (child)
m_list.append(child);
return XmlSaxParser::NoError;
}
// Remove the first XmlElement from list and returns it if completed
XmlElement* XmlFragment::popElement()
{
for (ObjList* o = m_list.skipNull(); o; o = o->skipNext()) {
XmlChild* c = static_cast<XmlChild*>(o->get());
XmlElement* x = c->xmlElement();
if (x) {
if (x->completed()) {
o->remove(false);
return x;
}
return 0;
}
}
return 0;
}
// Remove a child
XmlChild* XmlFragment::removeChild(XmlChild* child, bool delObj)
{
XmlChild* ch = static_cast<XmlChild*>(m_list.remove(child,delObj));
if (ch && ch->xmlElement())
ch->xmlElement()->setParent(0);
return ch;
}
// Copy other fragment into this one
void XmlFragment::copy(const XmlFragment& other, XmlParent* parent)
{
for (ObjList* o = other.getChildren().skipNull(); o; o = o->skipNext()) {
XmlChild* ch = static_cast<XmlChild*>(o->get());
if (ch->xmlElement())
ch = new XmlElement(*(ch->xmlElement()));
else if (ch->xmlCData())
ch = new XmlCData(*(ch->xmlCData()));
else if (ch->xmlText())
ch = new XmlText(*(ch->xmlText()));
else if (ch->xmlComment())
ch = new XmlComment(*(ch->xmlComment()));
else if (ch->xmlDeclaration())
ch = new XmlDeclaration(*(ch->xmlDeclaration()));
else if (ch->xmlDoctype())
ch = new XmlDoctype(*(ch->xmlDoctype()));
else
continue;
ch->setParent(parent);
addChild(ch);
}
}
// Create a String from this XmlFragment
void XmlFragment::toString(String& dump, bool escape, const String& indent,
const String& origIndent, bool completeOnly, const String* auth,
const XmlElement* parent) const
{
ObjList* ob = m_list.skipNull();
if (!ob)
return;
ObjList buffers;
for (;ob;ob = ob->skipNext()) {
String* s = new String;
XmlChild* obj = static_cast<XmlChild*>(ob->get());
if (obj->xmlElement())
obj->xmlElement()->toString(*s,escape,indent,origIndent,completeOnly,auth);
else if (obj->xmlText())
obj->xmlText()->toString(*s,escape,indent,auth,parent);
else if (obj->xmlCData())
obj->xmlCData()->toString(*s,indent);
else if (obj->xmlComment())
obj->xmlComment()->toString(*s,indent);
else if (obj->xmlDeclaration())
obj->xmlDeclaration()->toString(*s,escape);
else if (obj->xmlDoctype())
obj->xmlDoctype()->toString(*s,origIndent);
else
Debug(DebugStub,"XmlFragment::toString() unhandled element type!");
if (!TelEngine::null(s))
buffers.append(s);
else
TelEngine::destruct(s);
}
dump.append(buffers);
}
XmlElement* XmlFragment::getElement(ObjList*& lst, const String* name, const String* ns,
bool noPrefix)
{
for (; lst; lst = lst->skipNext()) {
XmlElement* x = (static_cast<XmlChild*>(lst->get()))->xmlElement();
if (!(x && x->completed()))
continue;
if (name || ns) {
if (!ns) {
// Compare tag
if (noPrefix) {
if (*name != x->unprefixedTag())
continue;
}
else if (*name != x->toString())
continue;
}
else if (name) {
// Compare tag and namespace
const String* t = 0;
const String* n = 0;
if (!(x->getTag(t,n) && *t == *name && n && *n == *ns))
continue;
}
else {
// Compare namespace
const String* n = x->xmlns();
if (!n || *n != *ns)
continue;
}
}
lst = lst->skipNext();
return x;
}
return 0;
}
// Replaces all ${paramname} in fragment's children with the corresponding parameters
void XmlFragment::replaceParams(const NamedList& params)
{
for (ObjList* o = m_list.skipNull(); o; o = o->skipNext())
static_cast<XmlChild*>(o->get())->replaceParams(params);
}
/*
* XmlDocument
*/
// Constructor
XmlDocument::XmlDocument()
: m_root(0)
{
}
// Destructor
XmlDocument::~XmlDocument()
{
reset();
}
// Append a new child to this document
// Set the root to an XML element if not already set. If we already have a completed root
// the element will be added to the root, otherwise an error will be returned.
// If we don't have a root non xml elements (other then text) will be added the list
// of elements before root
XmlSaxParser::Error XmlDocument::addChild(XmlChild* child)
{
if (!child)
return XmlSaxParser::NoError;
XmlElement* element = child->xmlElement();
if (!m_root) {
if (element) {
m_root = element;
return XmlSaxParser::NoError;
}
XmlDeclaration* decl = child->xmlDeclaration();
if (decl && declaration()) {
DDebug(DebugNote,"XmlDocument. Request to add duplicate declaration [%p]",this);
return XmlSaxParser::NotWellFormed;
}
// Text outside root: ignore empty, raise error otherwise
XmlText* text = child->xmlText();
if (text) {
if (text->onlySpaces()) {
m_beforeRoot.addChild(text);
return XmlSaxParser::NoError;
}
Debug(DebugNote,"XmlDocument. Got text outside element [%p]",this);
return XmlSaxParser::NotWellFormed;
}
return m_beforeRoot.addChild(child);
}
// We have a root
if (element) {
if (m_root->completed())
return m_root->addChild(child);
DDebug(DebugStub,"XmlDocument. Request to add xml element child to incomplete root [%p]",this);
return XmlSaxParser::NotWellFormed;
}
if ((child->xmlText() && child->xmlText()->onlySpaces()) || child->xmlComment())
return m_afterRoot.addChild(child);
// TODO: check what xml we can add after the root or if we can add
// anything after an incomplete root
Debug(DebugStub,"XmlDocument. Request to add non element while having a root [%p]",this);
return XmlSaxParser::NotWellFormed;
}
// Retrieve the document declaration
XmlDeclaration* XmlDocument::declaration() const
{
for (ObjList* o = m_beforeRoot.getChildren().skipNull(); o; o = o->skipNext()) {
XmlDeclaration* d = (static_cast<XmlChild*>(o->get()))->xmlDeclaration();
if (d)
return d;
}
return 0;
}
// Obtain root element completed ot not
XmlElement* XmlDocument::root(bool completed) const
{
return (m_root && (m_root->completed() || !completed)) ? m_root : 0;
}
void XmlDocument::toString(String& dump, bool escape, const String& indent, const String& origIndent) const
{
m_beforeRoot.toString(dump,escape,indent,origIndent);
if (m_root) {
dump << origIndent;
m_root->toString(dump,escape,indent,origIndent);
}
m_afterRoot.toString(dump,escape,indent,origIndent);
}
// Reset this XmlDocument. Destroys root and clear the others xml objects
void XmlDocument::reset()
{
TelEngine::destruct(m_root);
m_beforeRoot.clearChildren();
m_afterRoot.clearChildren();
m_file.clear();
}
// Load this document from data stream and parse it
XmlSaxParser::Error XmlDocument::read(Stream& in, int* error)
{
XmlDomParser parser(static_cast<XmlParent*>(this),false);
char buf[8096];
bool start = true;
while (true) {
int rd = in.readData(buf,sizeof(buf) - 1);
if (rd > 0) {
buf[rd] = 0;
const char* text = buf;
if (start) {
String::stripBOM(text);
start = false;
}
if (parser.parse(text) || parser.error() == XmlSaxParser::Incomplete)
continue;
break;
}
break;
}
parser.completeText();
if (parser.error() != XmlSaxParser::NoError) {
DDebug(DebugNote,"XmlDocument error loading stream. Parser error %d '%s' [%p]",
parser.error(),parser.getError(),this);
return parser.error();
}
if (in.error()) {
if (error)
*error = in.error();
#ifdef DEBUG
String tmp;
Thread::errorString(tmp,in.error());
Debug(DebugNote,"XmlDocument error loading stream. I/O error %d '%s' [%p]",
in.error(),tmp.c_str(),this);
#endif
return XmlSaxParser::IOError;
}
return XmlSaxParser::NoError;
}
// Write this document to a data stream
int XmlDocument::write(Stream& out, bool escape, const String& indent,
const String& origIndent, bool completeOnly) const
{
String dump;
m_beforeRoot.toString(dump,escape,indent,origIndent);
if (m_root)
m_root->toString(dump,escape,indent,origIndent,completeOnly);
m_afterRoot.toString(dump,escape,indent,origIndent);
return out.writeData(dump);
}
// Load a file and parse it
XmlSaxParser::Error XmlDocument::loadFile(const char* file, int* error)
{
reset();
if (TelEngine::null(file))
return XmlSaxParser::NoError;
m_file = file;
File f;
if (f.openPath(file))
return read(f,error);
if (error)
*error = f.error();
#ifdef DEBUG
String tmp;
Thread::errorString(tmp,f.error());
Debug(DebugNote,"XmlDocument error opening file '%s': %d '%s' [%p]",
file,f.error(),tmp.c_str(),this);
#endif
return XmlSaxParser::IOError;
}
// Save this xml document in a file
int XmlDocument::saveFile(const char* file, bool esc, const String& indent,
bool completeOnly, const char* eoln) const
{
if (!file)
file = m_file;
if (!file)
return 0;
File f;
int err = 0;
if (f.openPath(file,true,false,true,false)) {
String eol(eoln);
if (eoln && !eol)
eol = "\r\n";
write(f,esc,eol,indent,completeOnly);
err = f.error();
// Add an empty line
if (err >= 0 && eol)
f.writeData((void*)eol.c_str(),eol.length());
}
else
err = f.error();
if (!err) {
XDebug(DebugAll,"XmlDocument saved file '%s' [%p]",file,this);
return 0;
}
#ifdef DEBUG
String error;
Thread::errorString(error,err);
Debug(DebugNote,"Error saving XmlDocument to file '%s'. %d '%s' [%p]",
file,err,error.c_str(),this);
#endif
return f.error();
}
// Replaces all ${paramname} in document's components with the corresponding parameters
void XmlDocument::replaceParams(const NamedList& params)
{
if (m_root)
m_root->replaceParams(params);
m_beforeRoot.replaceParams(params);
m_afterRoot.replaceParams(params);
}
/*
* XmlChild
*/
XmlChild::XmlChild()
{
}
/*
* XmlElement
*/
XmlElement::XmlElement(const NamedList& element, bool empty, XmlParent* parent)
: m_element(element), m_prefixed(0),
m_parent(0), m_inheritedNs(0),
m_empty(empty), m_complete(empty)
{
XDebug(DebugAll,"XmlElement::XmlElement(%s,%u,%p) [%p]",
element.c_str(),empty,parent,this);
setPrefixed();
setParent(parent);
}
// Copy constructor
XmlElement::XmlElement(const XmlElement& el)
: m_element(el.getElement()), m_prefixed(0),
m_parent(0), m_inheritedNs(0),
m_empty(el.empty()), m_complete(el.completed())
{
setPrefixed();
setInheritedNs(&el,true);
m_children.copy(el.m_children,this);
}
// Create an empty xml element
XmlElement::XmlElement(const char* name, bool complete)
: m_element(name), m_prefixed(0),
m_parent(0), m_inheritedNs(0),
m_empty(true), m_complete(complete)
{
setPrefixed();
XDebug(DebugAll,"XmlElement::XmlElement(%s) [%p]",
m_element.c_str(),this);
}
// Create a new element with a text child
XmlElement::XmlElement(const char* name, const char* value, bool complete)
: m_element(name), m_prefixed(0),
m_parent(0), m_inheritedNs(0),
m_empty(true), m_complete(complete)
{
setPrefixed();
addText(value);
XDebug(DebugAll,"XmlElement::XmlElement(%s) [%p]",
m_element.c_str(),this);
}
// Destructor
XmlElement::~XmlElement()
{
setInheritedNs();
TelEngine::destruct(m_prefixed);
XDebug(DebugAll,"XmlElement::~XmlElement() ( %s| %p )",
m_element.c_str(),this);
}
// Set element's unprefixed tag, don't change namespace prefix
void XmlElement::setUnprefixedTag(const String& s)
{
if (!s || s == unprefixedTag())
return;
if (TelEngine::null(m_prefixed))
m_element.assign(s);
else
m_element.assign(*m_prefixed + ":" + s);
setPrefixed();
}
// Set inherited namespaces from a given element. Reset them anyway
void XmlElement::setInheritedNs(const XmlElement* xml, bool inherit)
{
XDebug(DebugAll,"XmlElement(%s) setInheritedNs(%p,%s) [%p]",
tag(),xml,String::boolText(inherit),this);
TelEngine::destruct(m_inheritedNs);
if (!xml)
return;
addInheritedNs(xml->attributes());
if (!inherit)
return;
XmlElement* p = xml->parent();
bool xmlAdd = (p == 0);
while (p) {
addInheritedNs(p->attributes());
const NamedList* i = p->inheritedNs();
p = p->parent();
if (!p && i)
addInheritedNs(*i);
}
if (xmlAdd && xml->inheritedNs())
addInheritedNs(*xml->inheritedNs());
}
// Add inherited namespaces from a list
void XmlElement::addInheritedNs(const NamedList& list)
{
XDebug(DebugAll,"XmlElement(%s) addInheritedNs(%s) [%p]",tag(),list.c_str(),this);
unsigned int n = list.count();
for (unsigned int i = 0; i < n; i++) {
NamedString* ns = list.getParam(i);
if (!(ns && isXmlns(ns->name())))
continue;
// Avoid adding already overridden namespaces
if (m_element.getParam(ns->name()))
continue;
if (m_inheritedNs && m_inheritedNs->getParam(ns->name()))
continue;
// TODO: Check if attribute names are unique after adding the namespace
// See http://www.w3.org/TR/xml-names/ Section 6.3
if (!m_inheritedNs)
m_inheritedNs = new NamedList("");
XDebug(DebugAll,"XmlElement(%s) adding inherited %s=%s [%p]",
tag(),ns->name().c_str(),ns->c_str(),this);
m_inheritedNs->addParam(ns->name(),*ns);
}
}
// Obtain the first text of this xml element
const String& XmlElement::getText() const
{
const XmlText* txt = 0;
for (ObjList* ob = getChildren().skipNull(); ob && !txt; ob = ob->skipNext())
txt = (static_cast<XmlChild*>(ob->get()))->xmlText();
return txt ? txt->getText() : String::empty();
}
XmlChild* XmlElement::getFirstChild()
{
if (!m_children.getChildren().skipNull())
return 0;
return static_cast<XmlChild*>(m_children.getChildren().skipNull()->get());
}
XmlText* XmlElement::setText(const char* text)
{
XmlText* txt = 0;
for (ObjList* o = getChildren().skipNull(); o; o = o->skipNext()) {
txt = (static_cast<XmlChild*>(o->get()))->xmlText();
if (txt)
break;
}
if (txt) {
if (!text)
return static_cast<XmlText*>(removeChild(txt));
txt->setText(text);
}
else if (text) {
txt = new XmlText(text);
addChild(txt);
}
return txt;
}
// Add a text child
void XmlElement::addText(const char* text)
{
if (!TelEngine::null(text))
addChild(new XmlText(text));
}
// Retrieve the element's tag (without prefix) and namespace
bool XmlElement::getTag(const String*& tag, const String*& ns) const
{
if (!m_prefixed) {
tag = &m_element;
ns = xmlns();
return true;
}
// Prefixed element
tag = &m_prefixed->name();
ns = xmlns();
return ns != 0;
}
// Append a new child
XmlSaxParser::Error XmlElement::addChild(XmlChild* child)
{
if (!child)
return XmlSaxParser::NoError;
// TODO: Check if a child element's attribute names are unique in the new context
// See http://www.w3.org/TR/xml-names/ Section 6.3
XmlSaxParser::Error err = m_children.addChild(child);
if (err == XmlSaxParser::NoError)
child->setParent(this);
return err;
}
// Remove a child
XmlChild* XmlElement::removeChild(XmlChild* child, bool delObj)
{
return m_children.removeChild(child,delObj);
}
// Set this element's parent. Update inherited namespaces
void XmlElement::setParent(XmlParent* parent)
{
XDebug(DebugAll,"XmlElement(%s) setParent(%p) element=%s [%p]",
tag(),parent,String::boolText(parent != 0),this);
if (m_parent && m_parent->element()) {
// Reset inherited namespaces if the new parent is an element
// Otherwise set them from the old parent
if (parent && parent->element())
setInheritedNs(0);
else
setInheritedNs(m_parent->element());
}
m_parent = parent;
}
// Obtain a string from this xml element
void XmlElement::toString(String& dump, bool esc, const String& indent,
const String& origIndent, bool completeOnly, const String* auth) const
{
XDebug(DebugAll,"XmlElement(%s) toString(%u,%s,%s,%u,%p) complete=%u [%p]",
tag(),esc,indent.c_str(),origIndent.c_str(),completeOnly,auth,m_complete,this);
if (!m_complete && completeOnly)
return;
String auxDump;
auxDump << indent << "<" << m_element;
int n = m_element.count();
for (int i = 0; i < n; i++) {
NamedString* ns = m_element.getParam(i);
if (!ns)
continue;
auxDump << " " << ns->name() << "=\"";
addAuth(auxDump,ns->name(),*ns,esc,auth);
auxDump << "\"";
}
int m = getChildren().count();
if (m_complete && !m)
auxDump << "/";
auxDump << ">";
if (m) {
// Avoid adding text on new line when text is the only child
XmlText* text = 0;
if (m == 1)
text = static_cast<XmlChild*>(getChildren().skipNull()->get())->xmlText();
if (!text)
m_children.toString(auxDump,esc,indent + origIndent,origIndent,completeOnly,auth,this);
else
text->toString(auxDump,esc,String::empty(),auth,this);
if (m_complete)
auxDump << (!text ? indent : String::empty()) << "</" << getName() << ">";
}
dump << auxDump;
}
// Copy element attributes to a list of parameters
unsigned int XmlElement::copyAttributes(NamedList& list, const String& prefix) const
{
unsigned int copy = 0;
unsigned int n = m_element.length();
for (unsigned int i = 0; i < n; i++) {
NamedString* ns = m_element.getParam(i);
if (!(ns && ns->name()))
continue;
list.addParam(prefix + ns->name(),*ns);
copy++;
}
return copy;
}
void XmlElement::setAttributes(NamedList& list, const String& prefix, bool skipPrefix)
{
if (prefix)
m_element.copySubParams(list,prefix,skipPrefix);
else
m_element.copyParams(list);
}
// Retrieve a namespace attribute. Search in parent or inherited for it
String* XmlElement::xmlnsAttribute(const String& name) const
{
String* tmp = getAttribute(name);
if (tmp)
return tmp;
XmlElement* p = parent();
if (p)
return p->xmlnsAttribute(name);
return m_inheritedNs ? m_inheritedNs->getParam(name) : 0;
}
// Set the element's namespace
bool XmlElement::setXmlns(const String& name, bool addAttr, const String& value)
{
const String* cmp = name ? &name : &s_ns;
XDebug(DebugAll,"XmlElement(%s)::setXmlns(%s,%u,%s) [%p]",
tag(),cmp->c_str(),addAttr,value.c_str(),this);
if (*cmp == s_ns) {
if (m_prefixed) {
m_element.assign(m_prefixed->name());
setPrefixed();
// TODO: remove children and attributes prefixes
}
}
else if (!m_prefixed || *m_prefixed != cmp) {
if (!m_prefixed)
m_element.assign(*cmp + ":" + tag());
else
m_element.assign(*cmp + ":" + m_prefixed->name());
setPrefixed();
// TODO: change children and attributes prefixes
}
if (!(addAttr && value))
return true;
String attr;
if (*cmp == s_ns)
attr = s_ns;
else
attr << s_nsPrefix << *cmp;
NamedString* ns = m_element.getParam(attr);
if (!ns && m_inheritedNs && m_inheritedNs->getParam(attr))
m_inheritedNs->clearParam(attr);
// TODO: Check if attribute names are unique after adding the namespace
// See http://www.w3.org/TR/xml-names/ Section 6.3
if (!ns)
m_element.addParam(attr,value);
else
*ns = value;
return true;
}
// Replaces all ${paramname} in element's attributes and children with the
// corresponding parameters
void XmlElement::replaceParams(const NamedList& params)
{
m_children.replaceParams(params);
for (ObjList* o = m_element.paramList()->skipNull(); o; o = o->skipNext())
params.replaceParams(*static_cast<String*>(o->get()));
}
// Build an XML element from a list parameter
XmlElement* XmlElement::param2xml(NamedString* param, const String& tag, bool copyXml)
{
if (!(param && param->name() && tag))
return 0;
XmlElement* xml = new XmlElement(tag);
xml->setAttribute(s_name,param->name());
xml->setAttributeValid(YSTRING("value"),*param);
NamedPointer* np = YOBJECT(NamedPointer,param);
if (!(np && np->userData()))
return xml;
DataBlock* db = YOBJECT(DataBlock,np->userData());
if (db) {
xml->setAttribute(s_type,"DataBlock");
Base64 b(db->data(),db->length(),false);
String tmp;
b.encode(tmp);
b.clear(false);
xml->addText(tmp);
return xml;
}
XmlElement* element = YOBJECT(XmlElement,np->userData());
if (element) {
xml->setAttribute(s_type,"XmlElement");
if (!copyXml) {
np->takeData();
xml->addChild(element);
}
else
xml->addChild(new XmlElement(*element));
return xml;
}
NamedList* list = YOBJECT(NamedList,np->userData());
if (list) {
xml->setAttribute(s_type,"NamedList");
xml->addText(list->c_str());
unsigned int n = list->length();
for (unsigned int i = 0; i < n; i++)
xml->addChild(param2xml(list->getParam(i),tag,copyXml));
return xml;
}
return xml;
}
// Build a list parameter from xml element
NamedString* XmlElement::xml2param(XmlElement* xml, const String* tag, bool copyXml)
{
const char* name = xml ? xml->attribute(s_name) : 0;
if (TelEngine::null(name))
return 0;
GenObject* gen = 0;
String* type = xml->getAttribute(s_type);
if (type) {
if (*type == YSTRING("DataBlock")) {
gen = new DataBlock;
const String& text = xml->getText();
Base64 b((void*)text.c_str(),text.length(),false);
b.decode(*(static_cast<DataBlock*>(gen)));
b.clear(false);
}
else if (*type == YSTRING("XmlElement")) {
if (!copyXml)
gen = xml->pop();
else {
XmlElement* tmp = xml->findFirstChild();
if (tmp)
gen = new XmlElement(*tmp);
}
}
else if (*type == YSTRING("NamedList")) {
gen = new NamedList(xml->getText());
xml2param(*(static_cast<NamedList*>(gen)),xml,tag,copyXml);
}
else
Debug(DebugStub,"XmlElement::xml2param: unhandled type=%s",type->c_str());
}
if (!gen)
return new NamedString(name,xml->attribute(YSTRING("value")));
return new NamedPointer(name,gen,xml->attribute(YSTRING("value")));
}
// Build and add list parameters from XML element children
void XmlElement::xml2param(NamedList& list, XmlElement* parent, const String* tag,
bool copyXml)
{
if (!parent)
return;
XmlElement* ch = 0;
while (0 != (ch = parent->findNextChild(ch,tag))) {
NamedString* ns = xml2param(ch,tag,copyXml);
if (ns)
list.addParam(ns);
}
}
/*
* XmlComment
*/
// Constructor
XmlComment::XmlComment(const String& comm)
: m_comment(comm)
{
XDebug(DebugAll,"XmlComment::XmlComment(const String& comm) ( %s| %p )",
m_comment.c_str(),this);
}
// Copy Constructor
XmlComment::XmlComment(const XmlComment& comm)
: m_comment(comm.getComment())
{
}
// Destructor
XmlComment::~XmlComment()
{
XDebug(DebugAll,"XmlComment::~XmlComment() ( %s| %p )",
m_comment.c_str(),this);
}
// Obtain string representation of this xml comment
void XmlComment::toString(String& dump, const String& indent) const
{
dump << indent << "<!--" << getComment() << "-->";
}
/*
* XmlCData
*/
// Constructor
XmlCData::XmlCData(const String& data)
: m_data(data)
{
XDebug(DebugAll,"XmlCData::XmlCData(const String& data) ( %s| %p )",
m_data.c_str(),this);
}
// Copy Constructor
XmlCData::XmlCData(const XmlCData& data)
: m_data(data.getCData())
{
}
// Destructor
XmlCData::~XmlCData()
{
XDebug(DebugAll,"XmlCData::~XmlCData() ( %s| %p )",
m_data.c_str(),this);
}
// Obtain string representation of this xml Cdata
void XmlCData::toString(String& dump, const String& indent) const
{
dump << indent << "<![CDATA[" << getCData() << "]]>";
}
/*
* XmlText
*/
// Constructor
XmlText::XmlText(const String& text)
: m_text(text)
{
XDebug(DebugAll,"XmlText::XmlText(%s) [%p]",m_text.c_str(),this);
}
// Copy Constructor
XmlText::XmlText(const XmlText& text)
: m_text(text.getText())
{
XDebug(DebugAll,"XmlText::XmlText(%p,%s) [%p]",
&text,TelEngine::c_safe(text.getText()),this);
}
// Destructor
XmlText::~XmlText()
{
XDebug(DebugAll,"XmlText::~XmlText [%p]",this);
}
// Obtain string representation of this xml text
void XmlText::toString(String& dump, bool esc, const String& indent,
const String* auth, const XmlElement* parent) const
{
dump << indent;
if (auth)
addAuth(dump,parent ? parent->toString() : String::empty(),m_text,esc,auth);
else if (esc)
XmlSaxParser::escape(dump,m_text);
else
dump << m_text;
}
bool XmlText::onlySpaces()
{
if (!m_text)
return true;
const char *s = m_text;
unsigned int i = 0;
for (;i < m_text.length();i++) {
if (s[i] == ' ' || s[i] == '\t' || s[i] == '\v' || s[i] == '\f' || s[i] == '\r' || s[i] == '\n')
continue;
return false;
}
return true;
}
// Replaces all ${paramname} in text with the corresponding parameters
void XmlText::replaceParams(const NamedList& params)
{
params.replaceParams(m_text);
}
/*
* XmlDoctype
*/
// Constructor
XmlDoctype::XmlDoctype(const String& doctype)
: m_doctype(doctype)
{
XDebug(DebugAll,"XmlDoctype::XmlDoctype(const String& doctype) ( %s| %p )",
m_doctype.c_str(),this);
}
// Copy Constructor
XmlDoctype::XmlDoctype(const XmlDoctype& doctype)
: m_doctype(doctype.getDoctype())
{
}
// Destructor
XmlDoctype::~XmlDoctype()
{
XDebug(DebugAll,"XmlDoctype::~XmlDoctype() ( %s| %p )",
m_doctype.c_str(),this);
}
// Obtain string representation of this xml doctype
void XmlDoctype::toString(String& dump, const String& indent) const
{
dump << indent << "<!DOCTYPE " << m_doctype << ">";
}
/*
* XPath
*/
// Maximul number of predicates in step
#ifndef XPATH_MAX_PREDICATES
#define XPATH_MAX_PREDICATES 5
#endif
#ifdef XDEBUG
#define XPATH_DEBUG_PARSE
#define XPATH_XDEBUG_PARSE
#define XPATH_DEBUG_FIND
#define XPATH_XDEBUG_FIND
#else
//#define XPATH_DEBUG_PARSE
//#define XPATH_XDEBUG_PARSE
//#define XPATH_DEBUG_FIND
//#define XPATH_XDEBUG_FIND
#endif
#ifdef XPATH_DEBUG_PARSE
#define XPathDebugParse Debug
#ifdef XPATH_XDEBUG_PARSE
#define XPathXDebugParse XPathDebugParse
#endif
#else
#define XPathDebugParse XDebug
#define XPathXDebugParse XDebug
#endif
#ifdef XPATH_DEBUG_FIND
#define XPathDebugFind Debug
#else
#define XPathDebugFind XDebug
#endif
static const TokenDict s_xpathErrors[] = {
{"Empty item", XPath::EEmptyItem},
{"Syntax error", XPath::ESyntax},
{"Semantic error", XPath::ESemantic},
{"Value out of range", XPath::ERange},
{"Always empty result", XPath::EEmptyResult},
{0,0}
};
/**
* Internal processing action
*/
enum XPathProcessAction {
// Not matched
XPathProcStop = -1, // Don't handle current element. Stop further handling
XPathProcCont = 0, // Don't handle current element. Continue handling
// Matched
XPathProcHandleCont = 1, // Handle curent element. Continue handling other elements in the same list
XPathProcHandleStop = 2, // Handle curent element. Stop handling other elements
};
static const TokenDict s_xpathProcAct[] = {
{"Stop", XPathProcStop},
{"Continue", XPathProcCont},
{"HandleContinue", XPathProcHandleCont},
{"HandleStop", XPathProcHandleStop},
{0,0}
};
namespace TelEngine {
class XPathParseData
{
public:
enum Opc {
OpcNone = 0,
OpcEq,
OpcNotEq,
};
inline XPathParseData(const char* b, unsigned int l, unsigned int flags)
: strictParse(0 != (flags & XPath::StrictParse)),
checkEmptyRes(0 == (flags & XPath::IgnoreEmptyResult)),
checkXmlName(0 == (flags & XPath::NoXmlNameCheck)),
m_step(0), m_buf(b), m_idx(0), m_length(b ? l : 0)
{}
inline unsigned int step() const
{ return m_step; }
// Retrieve original buffer length
inline unsigned int origLength() const
{ return m_length; }
// Return index in original buffer
inline unsigned int index() const
{ return m_idx; }
// Retrieve current buffer
inline const char* c_str() const
{ return m_buf; }
// Retrieve current buffer length
inline unsigned int length() const
{ return (m_length > m_idx) ? m_length - m_idx : 0; }
// Retrieve char in current buffer
inline char crt() const
{ return *m_buf; }
// Retrieve char in current buffer at index (not original !)
inline char at(unsigned int i) const
{ return m_buf[i]; }
// Advance 1 char in buffer
inline void advance() { m_buf++; m_idx++; }
// Advance 1 char in buffer. Return char before advance
inline char getCrtAdvance() { char c = *m_buf++; m_idx++; return c; }
// Advance n char(s) in buffer
inline void skip(unsigned int n) { m_buf += n; m_idx += n; }
// Advance step and buffer
inline void advanceStep() { m_step++; if (haveData()) advance(); }
inline bool haveData()
{ return index() < origLength(); }
inline bool ended()
{ return index() >= origLength(); }
inline bool isChar(char c)
{ return c == crt(); }
inline bool isSep()
{ return isSep(crt()); }
inline bool isBlank()
{ return XmlSaxParser::blank(crt()); }
inline bool isDigit()
{ return isDigit(crt()); }
inline bool isStepEnd()
{ return ended() || isSep(); }
inline bool isPredicatedEnd()
{ return isStepEnd() || isChar(']'); }
// Skip blanks in buffer. Return false if buffer ended
inline bool skipBlanks() {
while (haveData() && isBlank())
advance();
return haveData();
}
// Parse an operator. Return it and skip data if found
inline int parseOperator() {
if (ended())
return 0;
if (isChar('=')) {
advance();
return OpcEq;
}
if (isChar('!')) {
if (length() < 2 || at(1) != '=')
return 0;
skip(2);
return OpcNotEq;
}
return 0;
}
// Parse a string literal
inline const char* parseStringLiteral(const char*& start, unsigned int& n,
char& delimiter, bool& esc, bool req = true) {
delimiter = isStringLiteralDelimiter(crt()) ? getCrtAdvance() : 0;
if (!delimiter)
return req ? "Expecting string literal" : 0;
start = c_str();
n = 0;
for (; haveData(); ++n, advance()) {
if (!isChar(delimiter))
continue;
advance();
// Check for escaped delimiter (char repeated)
// See https://www.w3.org/TR/xpath-30/#prod-xpath30-EscapeQuot
if (!isChar(delimiter))
return 0;
n++;
esc = true;
}
start = 0;
n = 0;
return "Unexpected end of data while parsing string literal";
}
// Parse an XML string (possible escapes!)
inline const char* parseStringXml(const char*& start, unsigned int& n,
char& delimiter, bool& esc, bool req = true) {
delimiter = isStringLiteralDelimiter(crt()) ? getCrtAdvance() : 0;
if (!delimiter)
return req ? "Expecting string" : 0;
esc = true;
start = c_str();
n = 0;
for (; haveData(); ++n, advance()) {
if (!isChar(delimiter))
continue;
advance();
return 0;
}
start = 0;
n = 0;
return "Unexpected end of data while parsing string";
}
// Check a buffer for a valid XML name if check is enabled
// Return 0 on success, offending char otherwise
inline char validXmlName(const char* b, unsigned int n) {
if (!(checkXmlName && b && n))
return 0;
if (!XmlSaxParser::checkFirstNameCharacter(*b))
return *b;
for (unsigned int i = 1; i < n; ++b, ++i)
if (!XmlSaxParser::checkNameCharacter(*b))
return *b;
return 0;
}
// Check if current buffer char equals a given char
inline bool operator==(char c) const
{ return crt() == c; }
// Check if current buffer char id different than a given char
inline bool operator!=(char c) const
{ return crt() != c; }
static inline bool isSep(char c)
{ return '/' == c; }
static inline bool isDigit(char c)
{ return c >= '0' && c <= '9'; }
static inline bool isStringLiteralDelimiter(char c)
{ return c == '\'' || c == '"'; }
// Unescape an XPath string literal
static inline bool unEscapeLiteral(String& buf,
const char* b, unsigned int n, char esc, String* error) {
if (!(esc && b && n)) {
buf.append(b,n);
return true;
}
const char* accum = b;
unsigned int aLen = 0;
for (unsigned int i = 0; i < n; ++i) {
aLen++;
if (*b++ != esc)
continue;
if (*b != esc) {
if (error)
error->printf("Invalid char '%c' following escape",*b);;
return false;
}
// Append current buffer. Update accumulator. Skip second escape
buf.append(accum,aLen);
accum = ++b;
aLen = 0;
++i;
}
if (aLen)
buf.append(accum,aLen);
return true;
}
// Escape an XPath string literal
static inline String& escapeStringLiteral(String& buf, const String& str, char esc)
{ return escapeStringLiteral(buf,str.c_str(),str.length(),esc); }
// Escape an XPath string literal
static inline String& escapeStringLiteral(String& buf,
const char* b, unsigned int n, char esc) {
if (!(esc && b && n))
return buf.append(b,n);
const char* accum = b;
unsigned int aLen = 0;
for (unsigned int i = 0; i < n; ++i) {
aLen++;
if (*b++ != esc)
continue;
buf.append(accum,aLen);
buf << esc;
accum = b;
aLen = 0;
}
if (aLen)
return buf.append(accum,aLen);
return buf;
}
bool strictParse; // String buffer parsing
bool checkEmptyRes; // Check if a path will always produce empty result
bool checkXmlName; // Validate XML names
protected:
unsigned int m_step; // Current path step index
const char* m_buf; // Current buffer pointer
unsigned int m_idx; // Current index in original buffer
unsigned int m_length; // Original buffer length
};
}; // namespace TelEngine
// Utility. Use when parsing to accumulate and store parsed item info
class XPathParseItem
{
public:
inline XPathParseItem(const char* b = 0, unsigned int n = 0)
: buf(b), len(n), delimiter(0), esc(false)
{}
inline const char* c_str() const
{ return buf; }
inline unsigned int length() const
{ return len; }
inline void advance()
{ len++; }
inline void set(const char* b = 0, unsigned int n = 0)
{ buf = b; len = n; }
inline String& appendTo(String& s) const
{ return length() ? s.append((const char*)c_str(),length()) : s; }
inline String& assignTo(String& s) const {
if (length())
s.assign((const char*)c_str(),length());
else
s.clear();
return s;
}
inline char operator[](unsigned int idx) const
{ return c_str()[idx]; }
inline const String& value() const
{ return assignTo(m_value); }
const char* buf;
unsigned int len;
char delimiter;
bool esc;
protected:
mutable String m_value; // Temporary string
};
class XPathEscapedString
{
public:
inline XPathEscapedString(String* str, bool literal = false)
: m_delimiter(0), m_esc(false), m_literal(literal), m_str(str)
{}
inline void setLiteral(bool on)
{ m_literal = on; }
inline char delimiter() const
{ return m_delimiter; }
inline bool setString(const XPathParseItem& b, String* error)
{ return setString(b.c_str(),b.length(),b.delimiter,b.esc,error); }
inline bool setString(const char* b, unsigned int n, char delim, bool esc, String* error) {
if (m_str)
return setString(*m_str,b,n,delim,esc,error);
if (error)
*error = "Internal. No destination string";
return false;
}
inline bool setString(String& s, const char* b, unsigned int n, char delim, bool esc,
String* error) {
m_delimiter = delim;
if (!m_delimiter)
return true;
m_esc = esc;
if (!(esc && b && n))
s.assign(b,n);
else if (!(m_literal ? XPathParseData::unEscapeLiteral(s,b,n,m_delimiter,error) :
XmlSaxParser::unEscape(s,b,n,error,true,&m_esc))) {
s.clear();
return false;
}
return true;
}
inline String& dumpString(String& buf, bool escape = false) const
{ return m_str ? dumpString(buf,*m_str,escape) : buf; }
inline String& dumpString(String& buf, const String& str, bool escape = false) const {
if (!m_delimiter)
return buf;
buf << m_delimiter;
if (!(escape && m_esc && str.length()))
return buf << str << m_delimiter;
if (!m_literal)
return XmlSaxParser::escape(buf,str) << m_delimiter;
return XPathParseData::escapeStringLiteral(buf,str,m_delimiter) << m_delimiter;
}
protected:
char m_delimiter; // Delimiter. Non 0 indicates data is used
bool m_esc; // String contains escaped chars
bool m_literal; // String is an XPATH literal
String* m_str; // String to handle
};
// XPath string literal
class XPathString : public String, public XPathEscapedString
{
public:
inline XPathString(bool literal = false)
: XPathEscapedString(this,literal)
{}
inline String& dump(String& buf, bool escape = false) const
{ return dumpString(buf,escape); }
};
// XPath regexp literal
class XPathRegexp : public Regexp, public XPathEscapedString
{
public:
inline XPathRegexp()
: XPathEscapedString(this),
m_match(true)
{}
inline bool matches(const char* value) const
{ return m_match == Regexp::matches(value); }
inline const XPathString& flags() const
{ return m_flags; }
inline bool set(bool match, const XPathParseItem& rex, XPathParseItem& flags, String* error) {
if (!setString(rex,error))
return false;
if (!m_flags.setString(flags,error))
return false;
m_match = match;
bool insensitive = false;
bool extended = true;
for (unsigned int i = 0; i < m_flags.length(); i++) {
switch (m_flags[i]) {
case 'i': insensitive = true; continue;
case 'b': extended = false; continue;
}
}
setFlags(extended,insensitive);
if (compile())
return true;
if (error)
*error = Regexp::length() ? "Invalid regexp" : "Empty regexp";
return false;
}
inline String& dump(String& buf, bool escape) const {
char sep = ',';
buf << sep;
dumpString(buf,escape);
if (m_flags) {
buf << sep;
m_flags.dumpString(buf,escape);
}
return buf;
}
protected:
bool m_match; // (Reverse) match
XPathString m_flags; // Regexp flags
};
namespace TelEngine {
class XPathPredicate
{
public:
// Predicate type
// Used to select input for operator
enum Type {
// Values
None = 0,
Index, // Node index in list
Text, // Xml element text
XmlName = 0x10,
Attribute, // XML element attribute match
Child, // XML child match (presence or text),
};
// Operators
enum Opc {
OpcEq = XPathParseData::OpcEq, // Equality operator
OpcNotEq = XPathParseData::OpcNotEq, // Inequality operator
OpcFunc = 0x10,
OpcMatch, // Regexp match operator
OpcMatchNot, // Regexp match NOT operator
};
inline XPathPredicate()
: m_type(0), m_opc(0)
{}
inline int type() const
{ return m_type; }
inline unsigned int opc() const
{ return m_opc; }
inline const char* opcName() const
{ return opcName(opc()); }
inline const char* typeName() const
{ return lookup(m_type,s_typeName); }
inline bool valid() const
{ return 0 != type(); }
inline bool isPosition() const
{ return Index == type(); }
inline int check(unsigned int index, const XmlElement* xml = 0, const NamedString* attr = 0) const {
if (Index == m_type) {
if (index == m_opc)
return XPathProcHandleStop;
return index < m_opc ? XPathProcCont : XPathProcStop;
}
if (Text == m_type || Child == m_type) {
const String* txt = xml ?
((Child == m_type) ? xml->childText(m_name) : &(xml->getText())) : 0;
return ((!m_opc && txt) || (txt && runOpc(*txt))) ?
XPathProcHandleCont : XPathProcCont;
}
if (Attribute == m_type) {
const ObjList* o = xml ? xml->attributes().paramList()->skipNull() : 0;
for (; o; o = o->skipNext()) {
NamedString* ns = static_cast<NamedString*>(o->get());
if (m_name && m_name != ns->name())
continue;
if (!m_opc || runOpc(*ns))
return XPathProcHandleCont;
if (m_name)
break;
}
return XPathProcCont;
}
if (m_type)
Debug("XPath",DebugStub,"Predicate type %u '%s' not handled in check",m_type,typeName());
return XPathProcHandleCont;
}
inline bool runOpc(const String& value) const {
switch (m_opc) {
case OpcEq: return m_value == value;
case OpcNotEq: return m_value != value;
case OpcMatch: return m_regexp.matches(value);
case OpcMatchNot: return m_regexp.matches(value);
}
Debug("XPath",DebugStub,"Operator %u not handled in operator check",m_opc);
return false;
}
inline String& dump(String& buf, bool escape = false) const {
if (!valid())
return buf;
buf << "[";
if (Index == m_type)
buf << m_opc;
else {
bool func = 0 != (m_opc & OpcFunc);
dumpType(buf,func);
dumpOpc(buf,escape,func);
}
return buf << "]";
}
inline void dumpType(String& buf, bool opcFunc) const {
if (opcFunc)
buf << opcName() << '(';
if (Attribute == m_type)
buf << '@' << m_name.safe("*");
else if (Child == m_type)
buf << m_name.safe("*");
else
buf << typeName() << "()";
}
inline void dumpOpc(String& buf, bool escape, bool func, bool fin = true) const {
if (func) {
m_regexp.dump(buf,escape);
if (fin)
buf << ')';
}
else if (m_opc) {
buf << opcName();
m_value.dump(buf,escape);
}
}
static inline const char* opcName(int opc)
{ return lookup(opc,s_opcAll); }
static const TokenDict s_opcAll[];
static const TokenDict s_opcFunc[];
static const TokenDict s_opcBin[];
static const TokenDict s_typeName[];
unsigned int m_type; // Predicate type
unsigned int m_opc; // Node index value to check or operator
String m_name; // Name of the item to check
XPathString m_value; // Value to check
XPathRegexp m_regexp; // Regexp value to use in functions
};
}; // namespace TelEngine
#define XPATH_PREDICATE_DECLARE_OPC_FUNC \
{"matches", OpcMatch}, \
{"notMatches", OpcMatchNot},
#define XPATH_PREDICATE_DECLARE_OPC_BIN \
{"=", OpcEq}, \
{"!=", OpcNotEq},
const TokenDict XPathPredicate::s_opcAll[] = {
XPATH_PREDICATE_DECLARE_OPC_BIN
XPATH_PREDICATE_DECLARE_OPC_FUNC
{0,0},
};
const TokenDict XPathPredicate::s_opcFunc[] = {
XPATH_PREDICATE_DECLARE_OPC_FUNC
{0,0},
};
const TokenDict XPathPredicate::s_opcBin[] = {
XPATH_PREDICATE_DECLARE_OPC_BIN
{0,0},
};
#undef XPATH_PREDICATE_DECLARE_OPC_FUNC
#undef XPATH_PREDICATE_DECLARE_OPC_BIN
const TokenDict XPathPredicate::s_typeName[] = {
{"index", Index},
{"attribute", Attribute},
{"child", Child},
{"text", Text},
{0,0},
};
class XPathPredicateList
{
public:
inline XPathPredicateList()
: m_indexPredicate(0), m_stopProc(false)
{}
inline bool valid() const
{ return m_predicates[0].valid(); }
inline XPathPredicate* first()
{ return m_predicates; }
inline const XPathPredicate* first() const
{ return m_predicates; }
inline int check(unsigned int& index, const XmlElement* xml = 0,
const NamedString* attr = 0) const {
if (!valid())
return XPathProcHandleCont;
index++;
int rProc = XPathProcHandleCont;
if (!m_stopProc) {
// Always evalute predicates with position first. May lead to fast return
if (m_indexPredicate)
check(rProc,true,*m_indexPredicate,index,xml,attr);
const XPathPredicate* f = first();
for (unsigned int i = 0; rProc > 0 && i < XPATH_MAX_PREDICATES && f->valid(); ++i, ++f) {
if (!f->isPosition())
check(rProc,!(m_indexPredicate || i),*f,index,xml,attr);
}
}
else
rProc = XPathProcStop;
#ifdef XPATH_DEBUG_FIND
if (rProc <= 0)
Debug("XPath",DebugAll,
"Checked %s '%s' idx=%u. Predicate(s) not matched proc=%s",
(xml ? "xml" : (attr ? "attribute" : "???")),
(xml ? xml->tag() : ((attr ? attr->name().c_str() : ""))),
index,lookup(rProc,s_xpathProcAct));
#endif
return rProc;
}
inline bool check(int& rProc, bool first, const XPathPredicate& pred,
unsigned int index, const XmlElement* xml, const NamedString* attr) const {
#ifdef XPATH_XDEBUG_FIND
String tmp;
pred.dump(tmp) << " index=" << index;
#endif
if (first) {
rProc = pred.check(index,xml,attr);
#ifdef XPATH_XDEBUG_FIND
Debug("XPath",DebugAll,"Predicate %s check returned %s",
tmp.safe(),lookup(rProc,s_xpathProcAct));
#endif
}
else {
int proc = pred.check(index,xml,attr);
rProc = filterProc(rProc,proc);
#ifdef XPATH_XDEBUG_FIND
Debug("XPath",DebugAll,"Predicate %s check returned %s. Filtered: %s",
tmp.safe(),lookup(proc,s_xpathProcAct),lookup(rProc,s_xpathProcAct));
#endif
}
return rProc > 0;
}
inline String& dump(String& buf, bool escape = false) const {
const XPathPredicate* f = first();
for (unsigned int i = 0; i < XPATH_MAX_PREDICATES && f->valid(); ++i, ++f)
f->dump(buf,escape);
return buf;
}
// Filter curent predicate evaluation result agains accumulated result
// NOTE: Assume previous predicate(s) check is handle (stop or continue)
static inline int filterProc(int prev, int crt) {
// Predicate matched. Handle current item. Do not handle susequent items
if (XPathProcHandleStop == crt)
return crt;
// Predicate matched. Handle current and subsequent item(s)
// Previous check indicated handle stop/continue: honor it
if (XPathProcHandleCont == crt)
return prev;
// Predicate not matched indicating Stop: honor it
if (XPathProcStop == crt)
return crt;
// Predicate not matched indicating continue processing next items
// Check previous
if (XPathProcHandleStop == prev)
return XPathProcStop;
return XPathProcCont;
}
XPathPredicate m_predicates[XPATH_MAX_PREDICATES]; // Predicate expression(s)
XPathPredicate* m_indexPredicate;
bool m_stopProc;
};
namespace TelEngine {
class YATE_API XPathStep : public String
{
YCLASS(XPathStep,String)
public:
/**
* Path item type
*/
enum Type {
// Mask(s)
ElementNode = 0x1000, // Step is an XML element nod
XmlName = 0x2000, // Step name is subject to XML name validity check
// Values
Unknown = 0,
Xml = ElementNode | XmlName | 1, // XML Element
Attribute = XmlName | 2, // Node attribute(s)
Text = 2, // XML Text
ChildText = 3, // XML Element child text
};
inline XPathStep(int nodeType, const char* value = 0)
: String(value),
m_nodeType(nodeType)
{}
inline XPathStep(const XPathStep& other)
: String(other.c_str()),
m_nodeType(other.m_nodeType)
{}
inline int nodeType() const
{ return m_nodeType; }
inline const char* nodeTypeName() const
{ return lookup(nodeType(),s_xpathNodeType,"Unknown"); }
inline int isElementNode() const
{ return isElementNode(nodeType()); }
inline bool valueMatchAny() const
{ return 0 == String::length(); }
inline const String* valueMatch() const
{ return valueMatchAny() ? 0 : (const String*)this; }
inline const XPathPredicateList* predicates() const
{ return m_predicates.valid() ? &m_predicates : 0; }
inline String& dump(String& buf, bool escape = false) {
switch (m_nodeType) {
case Xml:
buf << String::safe("*");
break;
case Attribute:
buf << "@" << String::safe("*");
break;
default:
const char* f = lookup(m_nodeType,s_xpathNodeSelFunction);
if (f)
buf << f << "()";
else
buf << "unk_function(" << m_nodeType << ")";
}
return m_predicates.dump(buf,escape);
}
// Check if a given item should be added to result set
inline int checkHandle(const XPath* path, unsigned int& resultIdx,
const XmlElement* xml = 0, const NamedString* attr = 0,
const String& name = String::empty(), const String* nameCheck = 0) const {
if (nameCheck && name != *nameCheck) {
XPathDebugFind("XPath",DebugAll,"Checked %s '%s': not matched [%p]",
(xml ? "xml" : "attribute"),name.c_str(),path);
return XPathProcCont;
}
return m_predicates.check(resultIdx,xml,attr);
}
static inline bool matchAny(const char* buf, unsigned int len)
{ return 1 == len && '*' == *buf; }
static inline bool isElementNode(int type)
{ return 0 != (ElementNode & type); }
// Utility used when processing path items in find
// Filter action
static inline int filterProc(int upperProc, int proc) {
if (upperProc < 0 || XPathProcHandleStop == upperProc)
return upperProc;
return proc;
}
static const TokenDict s_xpathNodeType[];
static const TokenDict s_xpathNodeSelFunction[];
int m_nodeType; // Node selector type
XPathPredicateList m_predicates; // Step predicates
};
}; // namespace TelEngine
#define XPATH_DECLARE_NODE_SEL_FUNC \
{"text", Text}, \
{"child::text", ChildText}, \
const TokenDict XPathStep::s_xpathNodeType[] = {
{"element" , Xml},
{"attribute", Attribute},
XPATH_DECLARE_NODE_SEL_FUNC
{0,0}
};
const TokenDict XPathStep::s_xpathNodeSelFunction[] = {
XPATH_DECLARE_NODE_SEL_FUNC
{0,0}
};
#undef XPATH_DECLARE_NODE_SEL_FUNC
XPath::XPath(const char* value, unsigned int flags)
: String(value),
m_flags(flags & ~FInternal),
m_status(NotParsed),
m_errorItem(0)
{
XDebug(DebugAll,"XPath(%s,0x%x) [%p]",c_str(),m_flags,this);
if (0 == (m_flags & LateParse))
changed();
}
XPath::XPath(const XPath& other)
: String(other.c_str()),
m_flags(other.m_flags),
m_status(other.m_status),
m_errorItem(other.m_errorItem),
m_error(other.m_error)
{
XDebug(DebugAll,"XPath(%s,0x%x) [%p]",c_str(),m_flags,this);
ObjList* itAppend = &m_items;
for (ObjList* o = other.m_items.skipNull(); o; o = o->skipNext())
itAppend->append(new XPathStep(*static_cast<XPathStep*>(o->get())));
}
XPath::~XPath()
{
reset();
}
static inline bool addFindResult(const GenObject* gen, ObjList*& list)
{
if (!list)
return false;
list = list->append(gen);
list->setDelete(false);
return true;
}
static inline bool setFindResult(const XmlElement* xml, ObjList*& list,
const XmlElement** xmlReq, const GenObject** anyReq)
{
if (xmlReq) {
if (!*xmlReq)
*xmlReq = xml;
}
else if (anyReq) {
if (!*anyReq)
*anyReq = xml;
}
return addFindResult(xml,list);
}
static inline bool setFindResult(const String* str, ObjList*& list,
const String** textReq, const GenObject** anyReq)
{
if (textReq) {
if (!*textReq)
*textReq = str;
}
else if (anyReq) {
if (!*anyReq)
*anyReq = str;
}
return addFindResult(str,list);
}
static inline bool xpathAddResult(const XPath* ptr, const GenObject* item,
const GenObject*& res, ObjList*& list)
{
#ifdef XPATH_DEBUG_FIND
String tmp;
XmlElement* xml = YOBJECT(XmlElement,item);
NamedString* attr = YOBJECT(NamedString,item);
String* text = YOBJECT(String,item);
if (xml)
tmp.printf("XML (%p) '%s'",xml,xml->tag());
else if (attr)
tmp.printf("ATTR (%p) '%s'='%s'",attr,attr->name().c_str(),attr->safe());
else if (text)
tmp.printf("TEXT %s(%p) '%s'",(text == &String::empty() ? "EMPTY " : ""),text,text->safe());
else
tmp.printf("??? (%p) '%s'",item,item->toString().safe());
Debug("XPath",(xml || attr || text) ? DebugAll : DebugFail,"FIND adding result %s [%p]",tmp.c_str(),ptr);
#endif
if (!res)
res = item;
if (!list)
return false;
list = list->append(item);
list->setDelete(false);
return true;
}
int XPath::find(unsigned int& total, const XmlElement& src, const GenObject*& res, ObjList* list,
unsigned int what, ObjList* crtItem, unsigned int step, bool absolute) const
{
#ifdef XPATH_DEBUG_FIND
String stepInfo, tmp;
if (!step) {
dump(tmp,false," ");
stepInfo << "items=" << m_items.count() << " ";
}
String req;
if (what != FindAny) {
if (what & FindXml)
req.append("XML","_");
if (what & FindText)
req.append("TEXT","_");
if (what & FindAttr)
req.append("ATTR","_");
}
Debugger debug(step ? DebugInfo : DebugCall,"XPath FIND"," %sstep=%u req=%s%s [%p]%s",
stepInfo.safe(),step,req.safe("ANY"),(list ? "_LIST" : ""),
this,tmp.safe());
#endif
if (!crtItem) {
crtItem = m_items.skipNull();
if (!crtItem) {
XPathDebugFind("XPath",DebugInfo,"FIND step=%u res_count=0 returning proc %s [%p]",
step,lookup(XPathProcStop,s_xpathProcAct),this);
return XPathProcStop;
}
}
XPathStep& it = *static_cast<XPathStep*>(crtItem->get());
ObjList* nextItem = crtItem->skipNext();
ObjList* lstAppend = list;
unsigned int n = 0;
bool stop = false;
unsigned int resultIdx = 0;
while (true) {
if (it.isElementNode()) {
ObjList* o = 0;
XmlElement* x = 0;
if (absolute)
x = (XmlElement*)&src;
else {
o = src.getChildren().skipNull();
x = XmlFragment::getElement(o);
}
bool xmlReq = 0 != (what & FindXml);
// Last item but no XML/TEXT requested ?
if (!nextItem && !xmlReq && 0 == (what & FindText)) {
stop = true;
break;
}
const String* tag = it.valueMatch();
for ( ; x; x = XmlFragment::getElement(o)) {
int proc = it.checkHandle(this,resultIdx,x,0,x->getTag(),tag);
if (proc > 0) {
if (nextItem)
proc = XPathStep::filterProc(proc,find(n,*x,res,list,what,nextItem,step + 1));
else if (xmlReq) {
n++;
if (!xpathAddResult(this,x,res,lstAppend))
proc = XPathProcStop;
}
else
// Last item pointing to an XML but XML was not requested
proc = XPathStep::filterProc(proc,getText(n,*x,0,resultIdx,res,list));
}
if (proc < 0 || XPathProcHandleStop == proc)
break;
}
break;
}
if (XPathStep::Text == it.m_nodeType || XPathStep::Text == it.m_nodeType) {
// No need to check anything if the requested result set contains other data
// type (non XML Text) or we have a next item
// If next item is present there is nothing there to match (XmlText has no attributes, children ...)
// NOTE: This won't be true if we are going to implement other node type selector
// that may be possible (e.g. parent)
if (nextItem || 0 == (FindText & what)) {
stop = true;
break;
}
if (XPathStep::Text == it.m_nodeType)
getText(n,src,&it,resultIdx,res,list);
else {
ObjList* o = src.getChildren().skipNull();
for (XmlElement* x = XmlFragment::getElement(o); x; x = XmlFragment::getElement(o)) {
int proc = getText(n,*x,&it,resultIdx,res,list);
if (proc < 0 || XPathProcHandleStop == proc)
break;
}
}
break;
}
if (XPathStep::Attribute == it.m_nodeType) {
// If next item is present there is nothing there to match
// (an attribute has no relatinship with other data)
if (nextItem || 0 == (FindAttr & what)) {
stop = true;
break;
}
const String* name = it.valueMatch();
for (const ObjList* o = src.attributes().paramList()->skipNull(); o; o = o->skipNext()) {
NamedString* ns = static_cast<NamedString*>(o->get());
int proc = it.checkHandle(this,resultIdx,0,ns,ns->name(),name);
if (proc > 0) {
n++;
if (!xpathAddResult(this,ns,res,lstAppend))
proc = XPathProcStop;
}
if (proc < 0 || XPathProcHandleStop == proc)
break;
}
break;
}
Debug("XPath",DebugStub,"Node type selector %d '%s' not handled [%p]",
it.m_nodeType,it.nodeTypeName(),this);
stop = true;
break;
}
total += n;
int rProc = XPathProcCont;
if (stop || (n && !list))
rProc = XPathProcStop;
XPathDebugFind("XPath",DebugInfo,"FIND step=%u res_count=%u returning proc %s [%p]",
step,n,lookup(rProc,s_xpathProcAct),this);
return rProc;
}
int XPath::getText(unsigned int& total, const XmlElement& src, const XPathStep* step,
unsigned int& resultIdx, const GenObject*& res, ObjList* list) const
{
XPathDebugFind("XPath",DebugAll,"Get text xml '%s' multi=%s step=(%p) [%p]",
src.getTag().c_str(),String::boolText(list),step,this);
unsigned int n = 0;
int proc = XPathProcHandleCont;
ObjList* o = src.getChildren().skipNull();
for (XmlText* t = XmlFragment::getText(o); t; t = XmlFragment::getText(o)) {
if (step)
proc = step->checkHandle(this,resultIdx);
if (proc > 0) {
n++;
if (!xpathAddResult(this,&t->getText(),res,list))
proc = XPathProcStop;
}
if (proc < 0 || XPathProcHandleStop == proc)
break;
}
total += n;
XPathDebugFind("XPath",DebugAll,"Get text found=%u returning proc %s [%p]",
n,lookup(proc,s_xpathProcAct),this);
return proc;
}
void XPath::changed()
{
parsePath();
}
#define XPATH_SET_STATUS_BREAK(code,str) { setStatus(code,data.step(),str); break; }
#define XPATH_SET_STATUS_RET(code,str) { return setStatus(code,data.step(),str); }
#define XPATH_SET_SYNTAX_BREAK(str) XPATH_SET_STATUS_BREAK(ESyntax,str)
#define XPATH_SET_SYNTAX_RET(str) XPATH_SET_STATUS_RET(ESyntax,str)
#define XPATH_PARSE_EOB(str) ((String("Unexpected end of buffer ") + str).c_str())
#define XPATH_CHECK_END_BREAK(str) if (data.ended()) XPATH_SET_SYNTAX_BREAK(XPATH_PARSE_EOB(str));
#define XPATH_CHECK_END_RET(str) if (data.ended()) XPATH_SET_SYNTAX_RET(XPATH_PARSE_EOB(str));
#define XPATH_PARSE_SKIP_BLANKS_BREAK(str) if (!data.skipBlanks()) XPATH_SET_SYNTAX_BREAK(XPATH_PARSE_EOB(str));
#define XPATH_PARSE_SKIP_BLANKS_RET(str) if (!data.skipBlanks()) XPATH_SET_SYNTAX_RET(XPATH_PARSE_EOB(str));
#define XPATH_PARSE_STRICT_BLANK_BREAK(str) { \
XPATH_CHECK_END_BREAK(str); \
if (data.isBlank()) { \
if (data.strictParse) \
{ XPATH_SET_SYNTAX_BREAK((String("Unexpected space ") + str).c_str()); } \
else \
XPATH_PARSE_SKIP_BLANKS_BREAK(str); \
} \
}
#define XPATH_PARSE_STRICT_BLANK_RET(str) { \
XPATH_CHECK_END_RET(str); \
if (data.isBlank()) { \
if (data.strictParse) \
{ XPATH_SET_SYNTAX_RET((String("Unexpected space ") + str).c_str()); } \
else \
XPATH_PARSE_SKIP_BLANKS_RET(str); \
} \
}
#define XPATH_PARSE_STRICT_BLANK_PREDICATE XPATH_PARSE_STRICT_BLANK_RET("while parsing predicate")
void XPath::parsePath()
{
reset();
m_flags = m_flags & ~(unsigned int)FAbsolute;
m_status = 0;
XPathParseData data(c_str(),String::length(),m_flags);
String tmp;
#ifdef XPATH_DEBUG_PARSE
Debugger dbg(DebugCall,"XPath PARSE"," flags=0x%x len=%u '%s' [%p]",
m_flags,data.origLength(),data.c_str(),this);
#endif
XPathStep* step = 0;
XPathStep* prevStep = 0;
XPathParseItem stepStart;
while (true) {
// Path step start
XPathDebugParse("XPath",DebugAll,"Parsing step %u idx=%u '%s' [%p]",
data.step(),data.index(),data.c_str(),this);
stepStart.set(data.c_str(),data.index());
if (data.haveData() && data.isBlank()) {
if (data.strictParse)
XPATH_SET_SYNTAX_BREAK("Unexpected space at step start");
if (!data.skipBlanks())
XPATH_SET_STATUS_BREAK(EEmptyItem,"");
}
if (data.isStepEnd()) {
if (data.step() || data.ended())
XPATH_SET_STATUS_BREAK(EEmptyItem,"");
XPathDebugParse("XPath",DebugAll,"Processed empty step %u idx=%u '%s' [%p]",
data.step(),data.index(),data.c_str(),this);
// Empty first step: set absolute path flag
m_flags |= FAbsolute;
data.advance();
continue;
}
#ifdef XPATH_DEBUG_PARSE
Debugger dbgStep(DebugCall,"XPath PARSE STEP"," %u idx=%u '%s' [%p]",
data.step(),data.index(),data.c_str(),this);
#endif
// Retrieve step expression
// https://www.w3.org/TR/xpath-30/#doc-xpath30-StepExpr
const char* name = data.c_str();
unsigned int n = data.index();
while (data.haveData()
&& !(data.isSep() || (data == '(') || (data == '[') || data.isBlank()))
data.advance();
n = data.index() - n;
if (!n)
XPATH_SET_SYNTAX_BREAK("Empty step expression");
if (data.haveData())
XPATH_PARSE_STRICT_BLANK_BREAK("while parsing step expression");
if (data.isStepEnd() || data == '[') {
// Handle element tag or attribute
if ('@' == *name) {
// Attribute(s) ...
if (n < 2)
XPATH_SET_SYNTAX_BREAK("Empty attribute match in step");
step = new XPathStep(XPathStep::Attribute);
name++;
n--;
}
else
step = new XPathStep(XPathStep::Xml);
if (!XPathStep::matchAny(name,n)) {
char c = data.validXmlName(name,n);
if (c)
XPATH_SET_SYNTAX_BREAK(tmp.printf("Invalid char '%c' in %s name",c,step->nodeTypeName()));
step->assign(name,n);
}
}
else if (data == '(') {
// Node selector. Function call: selector()
String fn(name,n);
int type = lookup(fn,XPathStep::s_xpathNodeSelFunction);
if (!type)
XPATH_SET_SYNTAX_BREAK(tmp.printf("Unknown node selector '%s'",fn.c_str()));
data.advance();
XPATH_PARSE_STRICT_BLANK_BREAK("while parsing node selector");
// Node selector can't have parameters. Expect end of function call
if (data != ')')
XPATH_SET_SYNTAX_BREAK("Non empty node selector");
data.advance();
if (!data.strictParse)
data.skipBlanks();
step = new XPathStep(type);
}
if (data.checkEmptyRes) {
// Previous selector is not XML
// We are only handling XML element or text nodes
// If previous is a text node there is nothing else following it
// Searching something using this path will alsways produce an empty result
if (prevStep && !prevStep->isElementNode())
XPATH_SET_STATUS_BREAK(EEmptyResult,"Path step after a final selector step");
prevStep = step;
}
XPathPredicate* p = step->m_predicates.first();
for (unsigned int i = 0; ; ++p, ++i) {
if (!data.strictParse)
data.skipBlanks();
if (data.isStepEnd())
break;
if (data != '[') {
if (i)
XPATH_SET_SYNTAX_BREAK("Unexpected char after step predicate");
XPATH_SET_SYNTAX_BREAK("Unexpected char after step selector");
}
if (i == XPATH_MAX_PREDICATES)
XPATH_SET_STATUS_BREAK(ERange,"Too many predicates");
if (!parseStepPredicate(data,p))
break;
if (!checkStepPredicate(data,step,p))
break;
}
if (m_status)
break;
#ifdef XPATH_DEBUG_PARSE
String tmpStep;
XPathDebugParse("XPath",DebugInfo,"Parsed step %u type=%d (%s) '%s' value='%s' [%p]",
data.step(),step->nodeType(),step->nodeTypeName(),step->dump(tmpStep).c_str(),
step->c_str(),this);
#endif
m_items.append(step);
step = 0;
if (data.ended())
break;
data.advanceStep();
}
TelEngine::destruct(step);
#ifdef XPATH_DEBUG_PARSE
tmp = "";
String tmp2;
if (m_status)
Debug("XPath",DebugNote,"Parse failed step=%u offset=%u '%s': %s [%p]",
data.step(),data.index() - stepStart.length(),stepStart.value().c_str(),
describeError(tmp).c_str(),this);
else {
Debug("XPath",DebugCall,"Parsed%s [%p]\r\n-----\r\n%s\r\n%s\r\n-----",
(absolute() ? " absolute" : ""),this,
dump(tmp2,true,"/",absolute()).c_str(),dump(tmp).safe());
}
#endif
}
// Parse predicate (filter expression) [...]
bool XPath::parseStepPredicate(XPathParseData& data, XPathPredicate* pred)
{
#ifdef XPATH_DEBUG_PARSE
Debugger dbg(DebugAll,"XPath PARSE predicate"," len=%u '%s' [%p]",
data.length(),data.c_str(),this);
#endif
// Skip over starting [ and spaces
data.advance();
// Allow spaces in predicate start
XPATH_PARSE_STRICT_BLANK_PREDICATE;
if (data.isPredicatedEnd()) {
if (data.isStepEnd())
XPATH_SET_SYNTAX_RET("Expectind predicate contents");
XPATH_SET_SYNTAX_RET("Empty predicate");
}
String tmp;
XPathParseItem selector(data.c_str());
// Nothing can start with decimal digits except for Index
if (data.isDigit()) {
for (; data.haveData() && data.isDigit(); data.advance())
selector.advance();
XPATH_PARSE_STRICT_BLANK_PREDICATE;
if (data.isStepEnd())
XPATH_SET_SYNTAX_RET("Unexpected end of step while parsing predicate");
if (data != ']')
XPATH_SET_SYNTAX_RET(tmp.printf("Unexpected char '%c' while parsing index predicate",data.crt()));
data.advance();
uint64_t val = (selector.length() && '0' != selector[0]) ?
selector.value().toUInt64() : 0;
if (!val || val > 0xffffffff)
XPATH_SET_SYNTAX_RET("Predicate index value invalid or out of range");
pred->m_type = XPathPredicate::Index;
pred->m_opc = (unsigned int)val;
XPathDebugParse("XPath",DebugInfo,"Parsed predicate %u '%s' value=%u [%p]",
pred->type(),pred->typeName(),pred->opc(),this);
return true;
}
pred->m_type = XPathPredicate::None;
unsigned int selMin = 1;
if (data == '@') {
data.advance();
if (data.isPredicatedEnd())
XPATH_SET_SYNTAX_RET("Unexpected end of predicate attribute selector");
pred->m_type = XPathPredicate::Attribute;
selector.advance();
selMin = 2;
}
unsigned int opc = 0;
String fn;
int funcParam = 0;
int reqParams = 0;
int maxParams = -1;
XPathParseItem op1; // Second predicate operand or second function parameter
XPathParseItem op2; // Third function parameter
// Parse input from XML
// Predicate may be an XML selector: attribute, child, text() or function call
// Non function may be followed by a binary operator
// Function first parameter MUST be an XML selector
while (!data.isPredicatedEnd()) {
if (fn) {
// We are in function call
if (funcParam) {
XPATH_PARSE_STRICT_BLANK_PREDICATE;
if (funcParam <= maxParams) {
XPathParseItem& op = (funcParam == 1) ? op1 : op2;
const char* e = data.parseStringXml(op.buf,op.len,op.delimiter,op.esc);
if (e)
XPATH_SET_SYNTAX_RET(String(e) + " in predicate function parameter");
XPathXDebugParse("XPath",DebugAll,"Parsed function param %u '%s' [%p]",
funcParam,op.value().safe(),this);
XPATH_PARSE_STRICT_BLANK_PREDICATE;
}
if (data == ')') {
if (funcParam < reqParams)
XPATH_SET_SYNTAX_RET("Missing function parameter");
data.advance();
break;
}
if (data != ',')
XPATH_SET_SYNTAX_RET("Expecting function parameters separator");
funcParam++;
if (funcParam > maxParams)
XPATH_SET_SYNTAX_RET("Too many predicate function parameters");
data.advance();
continue;
}
if (data == ',' || data == '(' || data == ')') {
// Enf of selector
if (!selector.length()) {
if (data == '(')
XPATH_SET_SYNTAX_RET("Unexpected '(' in function parameter");
XPATH_SET_SYNTAX_RET("Missing function parameter");
}
XPathXDebugParse("XPath",DebugAll,"Parsed function selector '%s' [%p]",
selector.value().c_str(),this);
// Check name
if (data == '(') {
const String& f = selector.value();
unsigned int func = lookup(f,XPathPredicate::s_typeName);
switch (func) {
case XPathPredicate::Text:
// Empty parameter list allowed
if (!data.ended())
data.advance();
if (data.ended() || data != ')')
XPATH_SET_SYNTAX_RET("Expecting ')' after predicate input selector");
pred->m_type = XPathPredicate::Text;
break;
default:
if (func)
tmp.printf("Predicate function '%s' not implemented",f.c_str());
else
tmp.printf("Unknown function '%s' in predicate",f.c_str());
XPATH_SET_SYNTAX_RET(tmp);
}
data.advance();
}
else if ('@' == selector[0]) {
if (selector.length() < 2)
XPATH_SET_SYNTAX_RET("Empty attribute name in function parameter");
pred->m_type = XPathPredicate::Attribute;
}
if (data != ')')
data.advance();
XPATH_PARSE_STRICT_BLANK_PREDICATE;
funcParam = 1;
}
else {
if (!selector.length()) {
XPATH_PARSE_STRICT_BLANK_PREDICATE;
selector.set(data.c_str());
}
selector.advance();
data.advance();
}
continue;
}
if (data.isBlank()) {
if (selector.length() < selMin)
XPATH_SET_SYNTAX_RET("Unexpected space in predicate operand");
data.advance();
// Next blanks will be skipped if allowed
XPATH_PARSE_SKIP_BLANKS_RET("while parsing predicate");
if (!op1.c_str())
XPathXDebugParse("XPath",DebugAll,"Parsed selector '%s' [%p]",
selector.value().c_str(),this);
// Prepare second operand
op1.set(data.c_str());
continue;
}
if (!opc) {
opc = data.parseOperator();
if (opc) {
if (selector.length() < selMin)
XPATH_SET_SYNTAX_RET("Unexpected operator while parsing predicate");
if (!data.ended() && data.isBlank()) {
data.advance();
// Next blanks will be skipped if allowed
XPATH_PARSE_SKIP_BLANKS_RET("while parsing predicate");
}
if (!op1.c_str())
XPathXDebugParse("XPath",DebugAll,"Parsed selector '%s' [%p]",
selector.value().c_str(),this);
// Prepare second operand
XPathXDebugParse("XPath",DebugAll,"Parsed operator %u '%s' [%p]",
opc,XPathPredicate::opcName(opc),this);
op1.set(data.c_str());
continue;
}
if (data == '(') {
if (selector.length() < selMin)
XPATH_SET_SYNTAX_RET("Unexpected operator while parsing predicate");
// Function call
if (pred->type()) {
tmp.printf("Unexpected '(' after %s operand",pred->typeName());
XPATH_SET_SYNTAX_RET(tmp);
}
selector.assignTo(fn);
data.advance();
selector.advance();
unsigned int func = lookup(fn,XPathPredicate::s_opcFunc);
switch (func) {
case XPathPredicate::OpcMatch:
case XPathPredicate::OpcMatchNot:
maxParams = 2;
reqParams = 1;
break;
case 0:
// Function not found. Check for selector type function
func = lookup(fn,XPathPredicate::s_typeName);
switch (func) {
case XPathPredicate::Text:
if (data.ended() || data != ')')
XPATH_SET_SYNTAX_RET("Expecting ')' after predicate input selector");
pred->m_type = XPathPredicate::Text;
func = 0;
selector.advance();
data.advance();
break;
default:
if (func)
tmp.printf("Predicate function '%s' not implemented",fn.c_str());
else
tmp.printf("Unknown function '%s' in predicate",fn.c_str());
XPATH_SET_SYNTAX_RET(tmp);
}
break;
default:
tmp.printf("Predicate function '%s' not implemented",fn.c_str());
XPATH_SET_SYNTAX_RET(tmp);
}
if (func) {
XPathXDebugParse("XPath",DebugAll,"Parsed function %u '%s' [%p]",
func,XPathPredicate::opcName(func),this);
opc = func;
// Reset selector: parse input from XML
selector.set();
}
else {
// Prepare second operand
XPathXDebugParse("XPath",DebugAll,"Parsed selector '%s' [%p]",
selector.value().c_str(),this);
op1.set(data.c_str());
fn.clear();
}
continue;
}
// Operator not matched after first operand ?
if (op1.c_str())
XPATH_SET_SYNTAX_RET("Expecting operator");
}
if (!op1.c_str()) {
selector.advance();
data.advance();
continue;
}
const char* e = data.parseStringLiteral(op1.buf,op1.len,op1.delimiter,op1.esc);
if (e)
XPATH_SET_SYNTAX_RET(String(e) + " in predicate operand");
XPathXDebugParse("XPath",DebugAll,"Parsed operand '%s' [%p]",op1.value().safe(),this);
XPATH_PARSE_STRICT_BLANK_PREDICATE;
break;
}
if (!data.isPredicatedEnd())
XPATH_PARSE_STRICT_BLANK_PREDICATE;
if (data.isStepEnd())
XPATH_SET_SYNTAX_RET("Unexpected end of step while parsing predicate");
if (data != ']')
XPATH_SET_SYNTAX_RET(tmp.printf("Unexpected char '%c' while parsing predicate",data.crt()));
data.advance();
if (!pred->type())
pred->m_type = XPathPredicate::Child;
pred->m_opc = opc;
#if 0
#ifdef XPATH_XDEBUG_PARSE
String xd;
xd << tmp.printf("\r\nselector: %u '%s'",selector.length(),selector.value().c_str());
xd << tmp.printf("\r\nop1: %u '%s'",op1.length(),op1.value().c_str());
xd << tmp.printf("\r\nop2: %u '%s'",op2.length(),op2.value().c_str());
Debug("XPath",DebugAll,"Processing predicate %u opc=%u [%p]\r\n-----%s\r\n-----",
pred->type(),pred->opc(),this,xd.c_str());
#endif
#endif
if (XPathPredicate::Attribute == pred->type()) {
if (selector.length() < 2)
XPATH_SET_SYNTAX_RET("Empty attribute name in predicate operand");
if (XPathStep::matchAny(selector.c_str() + 1,selector.length() - 1))
selector.set();
else
selector.set(selector.c_str() + 1,selector.length() - 1);
}
if (selector.length()) {
if (0 != (pred->type() & XPathPredicate::XmlName)) {
char c = data.validXmlName(selector.c_str(),selector.length());
if (c)
XPATH_SET_SYNTAX_RET(
tmp.printf("Invalid char '%c' in %s name predicate",c,pred->typeName()));
}
selector.assignTo(pred->m_name);
}
if (op1.c_str()) {
bool ok = true;
switch (pred->opc()) {
case XPathPredicate::OpcMatch: ok = pred->m_regexp.set(true,op1,op2,&tmp); break;
case XPathPredicate::OpcMatchNot: ok = pred->m_regexp.set(false,op1,op2,&tmp); break;
default:
pred->m_value.setLiteral(true);
ok = pred->m_value.setString(op1,&tmp);
}
if (!ok)
XPATH_SET_SYNTAX_RET(tmp + " in predicate function parameter");
}
#ifdef XPATH_DEBUG_PARSE
String pInfo;
if (XPathPredicate::Index == pred->type())
pInfo << "value " << pred->opc();
else {
pInfo << "name='" << pred->m_name << "'";
if (pred->opc()) {
pInfo << " opc=" << pred->opc() << " (" << pred->opcName() << ")";
if (pred->m_value.delimiter()) {
pInfo << " value=";
pred->m_value.dump(pInfo);
}
if (pred->m_regexp.delimiter()) {
pInfo << " regexp=";
pred->m_regexp.dumpString(pInfo);
if (pred->m_regexp.flags()) {
pInfo << " flags=";
pred->m_regexp.flags().dumpString(pInfo);
}
}
}
}
Debug("XPath",DebugInfo,"Parsed predicate %u '%s' %s [%p]",
pred->type(),pred->typeName(),pInfo.c_str(),this);
#endif
return true;
}
bool XPath::checkStepPredicate(XPathParseData& data, XPathStep* step, XPathPredicate* pred)
{
if (pred->type() == XPathPredicate::Index) {
XPathPredicateList& lst = step->m_predicates;
if (!lst.m_indexPredicate)
lst.m_indexPredicate = pred;
else {
if (data.strictParse)
XPATH_SET_STATUS_RET(ESemantic,"Repeated index predicate in step");
if (pred->opc() != lst.m_indexPredicate->opc())
if (data.checkEmptyRes)
XPATH_SET_STATUS_RET(EEmptyResult,"Path step with different index value in predicate");
lst.m_stopProc = true;
}
}
else {
if (data.checkEmptyRes) {
switch (pred->type()) {
case XPathPredicate::Attribute:
case XPathPredicate::Child:
case XPathPredicate::Text:
// Only XML Element can have children or attributes
// For all other types there will be no match
if (!step->isElementNode()) {
String tmp;
tmp.printf("Found %s predicate for '%s' selector step",
pred->typeName(),step->nodeTypeName());
XPATH_SET_STATUS_RET(EEmptyResult,tmp);
}
break;
case XPathPredicate::Index:
break;
default:
Debug("XPath",DebugStub,
"Predicate type %d (%s) not handled in step empty result check [%p]",
pred->type(),pred->typeName(),this);
}
}
}
return true;
}
String& XPath::dump(String& buf, bool escape, const char* itemSep, bool sepFirst) const
{
for (ObjList* o = m_items.skipNull(); o; o = o->skipNext()) {
String tmp;
(static_cast<XPathStep*>(o->get()))->dump(tmp,escape);
if (sepFirst)
buf << itemSep << tmp;
else {
buf << tmp;
sepFirst = true;
}
}
return buf;
}
void XPath::dump(ObjList& lst, bool escape) const
{
ObjList* a = &lst;
for (ObjList* o = m_items.skipNull(); o; o = o->skipNext()) {
String* tmp = new String;
(static_cast<XPathStep*>(o->get()))->dump(*tmp,escape);
a = a->append(tmp);
}
}
String& XPath::escape(String& buf, const String& str, char quot, bool literal)
{
if (quot != '"' && quot != '\'')
quot = '"';
if (!str)
return buf << quot << quot;
buf << quot;
if (literal)
return XPathParseData::escapeStringLiteral(buf,str,quot) << quot;
return XmlSaxParser::escape(buf,str) << quot;
}
unsigned int XPath::maxStepPredicates()
{
return XPATH_MAX_PREDICATES;
}
void XPath::reset()
{
setStatus(NotParsed);
m_items.clear();
}
const TokenDict* XPath::dictErrors()
{
return s_xpathErrors;
}
bool XPath::setStatus(unsigned int code, unsigned int itemIdx, const char* error,
XPathParseData* data)
{
m_status = code;
m_errorItem = itemIdx;
m_error = error;
#ifdef XPATH_DEBUG_PARSE
if (data && m_status) {
String s;
Debug("XPath",DebugNote,"Status %s data: index=%u '%s' [%p]",
describeError(s).c_str(),data->index(),data->c_str(),this);
}
#endif
return false;
}
/* vi: set ts=8 sw=4 sts=4 noet: */