/*
CVSNT Generic API
Copyright (C) 2004 Tony Hoyle and March-Hare Software Ltd
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "lib/api_system.h"
#include <stdio.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif
#include <ctype.h>
#include <string.h>
#include <expat.h>
#include <algorithm>
#include <string>
#include "cvs_string.h"
#include "cvs_smartptr.h"
#include "Codepage.h"
#include "ServerIO.h"
#define INC_EXPAT
#include "Xmlapi.h"
static bool operator==(CXmlNode::ChildArray_t::value_type& a, CXmlNode* b)
{
return &(*a)==b;
};
static bool operator<(const CXmlNode::ChildArray_t::value_type& a, const std::pair<CXmlNode::XmlTypeEnum,const char *>& b)
{
if(a->getType()==CXmlNode::XmlTypeAttribute && b.first!=CXmlNode::XmlTypeAttribute)
return true;
if(a->getType()!=CXmlNode::XmlTypeAttribute && b.first==CXmlNode::XmlTypeAttribute)
return false;
return strcmp(a->GetName(),b.second)<0?true:false;
};
CXmlNode::CXmlNode(const CXmlNode& other)
{
name = other.name;
text = other.text;
Parent = other.Parent; //?? Is this ever true?
XmlType = other.XmlType;
StartLineNumber = other.StartLineNumber;
EndLineNumber = other.EndLineNumber;
Children = other.Children;
keyNum = other.keyNum;
sorted = other.sorted;
for(ChildArray_t::iterator i=Children.begin(); i!=Children.end(); ++i)
(*i)->Parent = this;
}
CXmlNode *CXmlNode::_New(XmlTypeEnum Type, const char *name, const char *value)
{
/* Can't add child node to attribute */
if(this->XmlType==XmlTypeAttribute)
return NULL;
Children.push_back(new CXmlNode(m_tree));
sorted=false;
CXmlNode *n = Children[Children.size()-1];
n->XmlType=Type;
n->name=name;
if(value)
n->SetValue(value);
n->Parent=this;
return n;
}
/* very simple xpath-like syntax. Basically all we understand is '/' and '@',
which is enough for interrogating the tree. */
/* Extended by =F and =U for filename and username comparisons */
CXmlNode *CXmlNode::Lookup(const char *path, bool autoCreate /* = false */)
{
ChildArray_t::iterator i,j;
const char *p;
char *q,*r,*s,*tag;
static char tmp[256];
bool isattr;
char qt;
int (*cmp)(const char *,const char *);
for(p=path,qt=0; *p && (qt || *p!='/'); p++)
{
if(qt && (qt=='\\' || *p==qt))
qt=0;
else if(*p=='"' || *p=='\'' || *p=='\\')
qt=*p;
}
if((p-path)<sizeof(tmp))
{
memcpy(tmp,path,p-path);
tmp[p-path]='\0';
}
else
{
/* Overflow case.. normally wouldn't happen */
strncpy(tmp,path,sizeof(tmp)-1);
tmp[sizeof(tmp)-1]='\0';
}
q=strchr(tmp,'[');
s=NULL;
if(q && *(q+1)=='@' && ((r=strchr(q+2,'='))!=NULL) && ((s=strchr(r+1,']'))!=NULL))
{
*q='\0';
q+=2;
*(r++)='\0';
*s='\0';
if(*r=='U' && *(r+1)=='\'')
{
cmp=usercmp;
r++;
}
else if(*r=='F' && *(r+1)=='\'')
{
cmp=fncmp;
r++;
}
else
cmp=strcmp;
if(*r=='\'' && *(s-1)=='\'')
{
*(r++)='\0';
*(--s)='\0';
}
}
tag=tmp; isattr=false;
if(!*p && tag[0]=='@') { tag++; isattr=true; }
SortMe();
i=std::lower_bound(Children.begin(),Children.end(),std::pair<CXmlNode::XmlTypeEnum,const char *>(isattr?XmlTypeAttribute:XmlTypeNode,tag));
if(i!=Children.end() && !strcmp(tag,(*i)->name.c_str()))
{
do
{
if(!isattr && q && s)
{
(*i)->SortMe();
j=std::lower_bound((*i)->Children.begin(),(*i)->Children.end(),std::pair<CXmlNode::XmlTypeEnum,const char *>(XmlTypeAttribute,q));
if(j==(*i)->Children.end() || strcmp(q,(*j)->name.c_str()) )
continue;
if(cmp((*j)->GetValue(),r))
continue;
}
if(*p)
{
CXmlNode *node = (*i)->Lookup(p+1,autoCreate);
if(node)
return node;
}
else
{
if((isattr && (*i)->XmlType!=XmlTypeAttribute) ||
(!isattr && (*i)->XmlType!=XmlTypeNode))
break;
return *i;
}
} while(++i!=Children.end() && (*i)->name==tmp);
}
if(autoCreate)
{
CXmlNode *node = NewNode(tmp,NULL);
if(q && r)
node->NewAttribute(q,r);
if(*p)
return node->Lookup(p+1,autoCreate);
else
return node;
}
return NULL;
}
void CXmlTree::startElement(void *userData, const char *name, const char **atts)
{
CXmlTree *tree=(CXmlTree*)userData;
CXmlNode *node,*attnode;
int line;
CXmlNode *parent = tree->m_lastNode;
if(tree->m_ignore_level)
{
tree->m_ignore_level++;
parent->text.append("<");
parent->text.append(name);
parent->text.append(">");
return;
}
line = XML_GetCurrentLineNumber(tree->m_parser);
if(parent)
node = parent->NewNode(name,NULL);
else
node = new CXmlNode(tree,CXmlNode::XmlTypeNode,name,NULL);
node->StartLineNumber = line;
while(*atts)
{
void *att = NULL;
size_t attlen;
if(tree->m_cp.ConvertEncoding(atts[1],strlen(atts[1])+1,att,attlen))
{
attnode = node->NewAttribute(atts[0],(const char *)att);
free(att);
}
else
attnode = node->NewAttribute(atts[0],atts[1]);
attnode->StartLineNumber = attnode->EndLineNumber = line;
atts+=2;
}
tree->m_lastNode = node;
/* If the tag is 'ignored' we treat everything within it as literal */
if(std::find(tree->m_ignore_tag.begin(),tree->m_ignore_tag.end(),name)!=tree->m_ignore_tag.end())
tree->m_ignore_level++;
}
void CXmlTree::endElement(void *userData, const char *name)
{
CXmlTree *tree=(CXmlTree*)userData;
int line;
size_t n;
CXmlNode *parent = tree->m_lastNode;
if(tree->m_ignore_level && --tree->m_ignore_level)
{
parent->text.append("</");
parent->text.append(name);
parent->text.append(">");
return;
}
line = XML_GetCurrentLineNumber(tree->m_parser);
parent->EndLineNumber = line;
for(n=0; n<parent->text.length(); n++)
{
if(!isspace(parent->text[n]))
break;
}
if(n==parent->text.length())
parent->text=""; // GCC 2.95 fix - broken STL doesn't support clear
parent->SortMe();
if(parent->Parent) /* Theoretically this should be set unless we're at the root node (end of parsing) */
tree->m_lastNode = parent->Parent;
}
void CXmlTree::charData(void *userData, const char * s, int len)
{
CXmlTree *tree=(CXmlTree*)userData;
void *str = NULL;
size_t strLen;
CXmlNode *node = tree->m_lastNode;
if(tree->m_cp.ConvertEncoding(s,len,str,strLen))
{
node->AppendValue((const char*)str,strLen);
free(str);
}
else
node->AppendValue((const char *)s,len);
}
int CXmlTree::getEncoding(void *userData, const XML_Char *name, XML_Encoding *info)
{
return 0;
}
CXmlNode *CXmlTree::ReadXmlFile(FILE * file)
{
std::vector<cvs::string> ignore_tag;
return ReadXmlFile(file,ignore_tag);
}
CXmlNode *CXmlTree::ParseXmlFromMemory(const char *data)
{
std::vector<cvs::string> ignore_tag;
return ParseXmlFromMemory(data,ignore_tag);
}
CXmlNode *CXmlTree::ReadXmlFile(FILE * file, std::vector<cvs::string>& ignore_tag)
{
XML_Parser parser;
char buf[BUFSIZ];
int done;
const char *enc;
m_ignore_tag = ignore_tag;
m_ignore_level=0;
fgets(buf,sizeof(buf),file);
if(strstr(buf,"encoding=\"UTF-8\""))
enc="UTF-8";
else
enc="ISO-8859-1";
fseek(file,0,0);
m_lastNode = NULL;
parser = XML_ParserCreate(enc);
m_cp.BeginEncoding(CCodepage::Utf8Encoding,CCodepage::NullEncoding);
m_cp.SetBytestream();
m_parser = parser;
XML_SetUserData(parser, this);
XML_SetElementHandler(parser, startElement, endElement);
XML_SetCharacterDataHandler(parser, charData);
XML_SetUnknownEncodingHandler(parser, getEncoding, NULL);
done = 0;
do {
size_t len = fread(buf, 1, sizeof(buf), file);
done = len < sizeof(buf);
if (!XML_Parse(parser, buf, (int)len, done))
{
CServerIo::error("Error in xml_read: %s at line %d\n",
XML_ErrorString(XML_GetErrorCode(parser)),
XML_GetCurrentLineNumber(parser));
delete m_lastNode;
m_cp.EndEncoding();
return NULL;
}
} while (!done);
XML_ParserFree(parser);
m_cp.EndEncoding();
return m_lastNode;
}
CXmlNode *CXmlTree::ParseXmlFromMemory(const char *data,std::vector<cvs::string>& ignore_tag)
{
XML_Parser parser;
int done;
const char *enc;
m_ignore_tag = ignore_tag;
m_ignore_level=0;
if(strstr(data,"encoding=\"UTF-8\""))
enc="UTF-8";
else
enc="ISO-8859-1";
m_lastNode = NULL;
parser = XML_ParserCreate(enc);
m_cp.BeginEncoding(CCodepage::Utf8Encoding,CCodepage::NullEncoding);
m_cp.SetBytestream();
m_parser = parser;
XML_SetUserData(parser, this);
XML_SetElementHandler(parser, startElement, endElement);
XML_SetCharacterDataHandler(parser, charData);
XML_SetUnknownEncodingHandler(parser, getEncoding, NULL);
done = 0;
if (!XML_Parse(parser, data, (int)strlen(data), 1))
{
CServerIo::error("Error in xml_read: %s at line %d\n",
XML_ErrorString(XML_GetErrorCode(parser)),
XML_GetCurrentLineNumber(parser));
delete m_lastNode;
m_cp.EndEncoding();
return NULL;
}
XML_ParserFree(parser);
m_cp.EndEncoding();
return m_lastNode;
}
bool CXmlNode::WriteXmlFile(FILE *file) const
{
m_tree->m_cp.BeginEncoding(CCodepage::NullEncoding,CCodepage::Utf8Encoding);
m_tree->m_cp.SetBytestream();
fprintf(file,"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
return WriteXmlNode(file,0);
}
bool CXmlNode::WriteXmlToString(cvs::string& string) const
{
m_tree->m_cp.BeginEncoding(CCodepage::NullEncoding,CCodepage::Utf8Encoding);
m_tree->m_cp.SetBytestream();
string.reserve(1024);
cvs::sprintf(string,64,"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
return WriteXmlNodeToString(string,0);
}
bool CXmlNode::WriteXmlNode(FILE *file, int indent) const
{
ChildArray_t::const_iterator i;
int count;
for(int n=0; n<indent; n++)
fprintf(file," ");
fprintf(file,"<%s",name.c_str());
for(i=Children.begin(); i!=Children.end(); ++i)
{
if((*i)->name.empty())
continue;
if((*i)->XmlType==XmlTypeAttribute)
{
if((*i)->text.empty())
fprintf(file," %s",(*i)->name.c_str());
else
{
void *val = NULL;
size_t vallen;
std::string strval;
std::string::size_type off;
if(m_tree->m_cp.ConvertEncoding((*i)->text.c_str(),(*i)->text.length()+1,val,vallen))
strval = (const char *)val;
else
strval = (*i)->text;
/* We do &, because expat barfs otherwise, and ", because it's the end of the string */
off=(std::string::size_type)-1;
while((off=strval.find('&',off+1))!=std::string::npos)
strval.replace(off,1,"&");
off=(std::string::size_type)-1;
while((off=strval.find('"',off+1))!=std::string::npos)
strval.replace(off,1,""");
fprintf(file," %s=\"%s\"",(*i)->name.c_str(),strval.c_str());
free(val);
}
}
}
count=0;
for(i=Children.begin(); i!=Children.end(); ++i)
{
if((*i)->XmlType==XmlTypeNode)
count++;
}
if(!count && text.empty())
{
fprintf(file," />\n");
}
else
{
if(count)
{
fprintf(file,">\n");
for(i=Children.begin(); i!=Children.end(); ++i)
{
if((*i)->XmlType==XmlTypeNode)
(*i)->WriteXmlNode(file,indent+1);
}
for(int n=0; n<indent; n++)
fprintf(file," ");
fprintf(file,"</%s>\n",name.c_str());
}
else
{
void *val = NULL;
size_t vallen;
std::string strval;
std::string::size_type off;
if(m_tree->m_cp.ConvertEncoding(text.c_str(),text.length()+1,val,vallen))
strval = (const char *)val;
else
strval = text;
/* We do &, because expat barfs otherwise, and <, because it's the start
of the next token */
off=(std::string::size_type)-1;
while((off=strval.find('&',off+1))!=std::string::npos)
strval.replace(off,1,"&");
off=(std::string::size_type)-1;
while((off=strval.find('<',off+1))!=std::string::npos)
strval.replace(off,1,"<");
fprintf(file,">%s</%s>\n",strval.c_str(),name.c_str());
free(val);
}
}
return true;
}
bool CXmlNode::WriteXmlNodeToString(cvs::string& string, int indent) const
{
ChildArray_t::const_iterator i;
int count;
for(int n=0; n<indent; n++)
string+=" ";
string+='<';
string+=name.c_str();
for(i=Children.begin(); i!=Children.end(); ++i)
{
if((*i)->name.empty())
continue;
if((*i)->XmlType==XmlTypeAttribute)
{
if((*i)->text.empty())
{
string+=' ';
string+=(*i)->name.c_str();
}
else
{
void *val = NULL;
size_t vallen;
std::string strval;
std::string::size_type off;
if(m_tree->m_cp.ConvertEncoding((*i)->text.c_str(),(*i)->text.length()+1,val,vallen))
strval = (const char *)val;
else
strval = (*i)->text;
/* We do &, because expat barfs otherwise, and ", because it's the end of the string */
off=(std::string::size_type)-1;
while((off=strval.find('&',off+1))!=std::string::npos)
strval.replace(off,1,"&");
off=(std::string::size_type)-1;
while((off=strval.find('"',off+1))!=std::string::npos)
strval.replace(off,1,""");
string+=' ';
string+=(*i)->name.c_str();
string+="=\"";
string+=strval.c_str();
string+="\"";
free(val);
}
}
}
count=0;
for(i=Children.begin(); i!=Children.end(); ++i)
{
if((*i)->XmlType==XmlTypeNode)
count++;
}
if(!count && text.empty())
{
string+=" />\n";
}
else
{
if(count)
{
string+=">\n";
for(i=Children.begin(); i!=Children.end(); ++i)
{
if((*i)->XmlType==XmlTypeNode)
(*i)->WriteXmlNodeToString(string,indent+1);
}
for(int n=0; n<indent; n++)
string+=" ";
string+="</";
string+=name.c_str();
string+=">\n";
}
else
{
void *val = NULL;
size_t vallen;
std::string strval;
std::string::size_type off;
if(m_tree->m_cp.ConvertEncoding(text.c_str(),text.length()+1,val,vallen))
strval = (const char *)val;
else
strval = text;
/* We do &, because expat barfs otherwise, and <, because it's the start
of the next token */
off=(std::string::size_type)-1;
while((off=strval.find('&',off+1))!=std::string::npos)
strval.replace(off,1,"&");
off=(std::string::size_type)-1;
while((off=strval.find('<',off+1))!=std::string::npos)
strval.replace(off,1,"<");
string+='>';
string+=strval.c_str();
string+="</";
string+=name.c_str();
string+=">\n";
free(val);
}
}
return true;
}
CXmlNode *CXmlNode::Next()
{
ChildArray_t::iterator i;
if(!Parent)
return NULL;
i = Parent->FindIterator(this);
if(i==Parent->Children.end())
return NULL;
++i;
if(i==Parent->Children.end())
return NULL;
if((*i)->Parent!=Parent)
return NULL;
return *i;
}
CXmlNode *CXmlNode::Previous()
{
ChildArray_t::iterator i;
if(!Parent)
return NULL;
i = Parent->FindIterator(this);
if(i==Parent->Children.end())
return NULL;
if(i==Parent->Children.begin())
return NULL;
if((*i)->Parent!=Parent)
return NULL;
--i;
return *i;
}
bool CXmlNode::Delete(CXmlNode *child)
{
ChildArray_t::iterator i;
i = FindIterator(child);
if(i!=Children.end())
Children.erase(i);
return true;
}
bool CXmlNode::BatchDelete()
{
name=""; /* Having no name marks the tag for deletion */
return true;
}
/* For some reason this doesn't work in the class (seems to be VC bug). Make
global static instead */
static bool operator==(std::pair<const std::string,CXmlNode>& a,CXmlNode *b)
{
return &a.second == b;
}
CXmlNode::ChildArray_t::iterator CXmlNode::FindIterator(CXmlNode *node)
{
return std::find(Children.begin(),Children.end(),node);
}
bool CXmlNode::Prune()
{
ChildArray_t::iterator i;
size_t count = 0;
for(i=Children.begin(); i!=Children.end(); )
{
if((*i)->name.empty())
Children.erase(i);
else
{
if((*i)->XmlType==XmlTypeNode)
count++;
++i;
}
}
if(!Parent)
return true; /* We never prune the root node */
CXmlNode *node = Parent;
if(!count)
node->Delete(this);
return node->Prune();
}
CXmlNode *CXmlNode::Copy()
{
CXmlNode *newNode = new CXmlNode(*this);
return newNode;
}
bool CXmlNode::Paste(CXmlNode *from)
{
text = from->text;
std::copy(from->Children.begin(),from->Children.end(),std::inserter(Children,Children.end()));
for(ChildArray_t::iterator i=Children.begin(); i!=Children.end(); ++i)
(*i)->Parent = this;
return true;
}
int CXmlNode::cmp(CXmlNode* b)
{
/* Attributes always stack first.. looks nicer in the resultant file */
if(XmlType==XmlTypeAttribute && b->XmlType==XmlTypeNode)
return -1;
if(XmlType==XmlTypeNode && b->XmlType==XmlTypeAttribute)
return 1;
int diff = strcmp(name.c_str(),b->name.c_str());
if(!diff)
diff = strcmp(text.c_str(),b->text.c_str());
return diff;
}
bool CXmlNode::sortPred(ChildArray_t::value_type a, ChildArray_t::value_type b)
{
int diff = a->cmp(b);
if(!diff)
{
ChildArray_t::const_iterator i = a->begin();
ChildArray_t::const_iterator j = b->begin();
while(!diff && i!=a->end() && j!=b->end())
{
diff = (*i)->cmp(*j);
i++;
j++;
}
if(!diff)
diff = (int)(a->size() - b->size());
}
return diff<0;
}
bool CXmlNode::SortMe()
{
if(sorted)
return true;
for(ChildArray_t::iterator i = Children.begin(); i!=Children.end(); i++)
(*i)->SortMe();
std::sort(Children.begin(),Children.end(),sortPred);
sorted = true;
return true;
}
CXmlTree::CXmlTree()
{
}
CXmlTree::~CXmlTree()
{
}
syntax highlighted by Code2HTML, v. 0.9.1