1
0
forked from 0ad/0ad
0ad/source/ps/XML/Xeromyces.cpp

504 lines
14 KiB
C++
Raw Normal View History

2004-07-08 17:22:09 +02:00
#include "precompiled.h"
#include <vector>
#include <set>
#include <map>
#include <stack>
#include <algorithm>
2004-07-08 17:22:09 +02:00
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "Xeromyces.h"
2004-07-08 17:22:09 +02:00
#define LOG_CATEGORY "xml"
2004-07-08 17:22:09 +02:00
#include "XML.h"
int CXeromyces::XercesLoaded = 0; // for once-only initialisation
// Convenient storage for the internal tree
typedef struct {
std::string name;
utf16string value;
2004-07-08 17:22:09 +02:00
} XMLAttribute;
typedef struct XMLElement {
std::string name;
int linenum;
utf16string text;
2004-07-08 17:22:09 +02:00
std::vector<XMLElement*> childs;
std::vector<XMLAttribute*> attrs;
} XMLElement;
class XeroHandler : public DefaultHandler
{
public:
XeroHandler() : m_locator(NULL), Root(NULL) {}
~XeroHandler()
{
if (Root)
DeallocateElement(Root);
}
2004-07-08 17:22:09 +02:00
// SAX2 event handlers:
virtual void startDocument();
virtual void endDocument();
virtual void startElement(const XMLCh* const uri, const XMLCh* const localname, const XMLCh* const qname, const Attributes& attrs);
virtual void endElement(const XMLCh* const uri, const XMLCh* const localname, const XMLCh* const qname);
virtual void characters(const XMLCh* const chars, const unsigned int length);
const Locator* m_locator;
virtual void setDocumentLocator(const Locator* const locator)
{
m_locator = locator;
}
2004-07-08 17:22:09 +02:00
// Non-SAX2 stuff, used for storing the
// parsed data and constructing the XMB:
void CreateXMB();
WriteBuffer writeBuffer;
2004-07-08 17:22:09 +02:00
private:
std::set<std::string> ElementNames;
std::set<std::string> AttributeNames;
2004-07-08 17:22:09 +02:00
XMLElement* Root;
XMLElement* CurrentElement;
std::stack<XMLElement*> ElementStack;
std::map<std::string, int> ElementID;
std::map<std::string, int> AttributeID;
2004-07-08 17:22:09 +02:00
void OutputElement(XMLElement* el);
// Recursively frees memory
void DeallocateElement(XMLElement* el);
2004-07-08 17:22:09 +02:00
};
CXeromyces::CXeromyces()
{
}
CXeromyces::~CXeromyces()
{
2004-07-08 17:22:09 +02:00
}
void CXeromyces::Terminate()
{
if (XercesLoaded)
{
XMLPlatformUtils::Terminate();
XercesLoaded = 0;
}
}
// Find out write location of the XMB file corresponding to xmlFilename
void CXeromyces::GetXMBPath(const PIVFS& vfs, const VfsPath& xmlFilename, const VfsPath& xmbFilename, VfsPath& xmbActualPath)
{
// rationale:
// - it is necessary to write out XMB files into a subdirectory
// corresponding to the mod from which the XML file is taken.
// this avoids confusion when multiple mods are active -
// their XMB files' VFS filename would otherwise be indistinguishable.
// - we group files in the cache/ mount point first by mod, and only
// then XMB. this is so that all output files for a given mod can
// easily be deleted. the operation of deleting all old/unused
// XMB files requires a program anyway (to find out which are no
// longer needed), so it's not a problem that XMB files reside in
// a subdirectory (which would make manually deleting all harder).
// get real path of XML file (e.g. mods/official/entities/...)
Path P_XMBRealPath;
vfs->GetRealPath(xmlFilename, P_XMBRealPath);
// extract mod name from that
char modName[PATH_MAX];
// .. NOTE: can't use %s, of course (keeps going beyond '/')
int matches = sscanf(P_XMBRealPath.string().c_str(), "mods/%[^/]", modName);
debug_assert(matches == 1);
// build full name: cache, then mod name, XMB subdir, original XMB path
xmbActualPath = VfsPath("cache/mods") / modName / "xmb" / xmbFilename;
}
PSRETURN CXeromyces::Load(const VfsPath& filename)
2004-07-08 17:22:09 +02:00
{
// Make sure the .xml actually exists
if (! FileExists(filename))
{
LOG(CLogger::Error, LOG_CATEGORY, "CXeromyces: Failed to find XML file %s", filename.string().c_str());
return PSRETURN_Xeromyces_XMLOpenFailed;
}
2004-07-08 17:22:09 +02:00
// Get some data about the .xml file
FileInfo fileInfo;
if (g_VFS->GetFileInfo(filename, &fileInfo) < 0)
2004-07-08 17:22:09 +02:00
{
LOG(CLogger::Error, LOG_CATEGORY, "CXeromyces: Failed to stat XML file %s", filename.string().c_str());
return PSRETURN_Xeromyces_XMLOpenFailed;
2004-07-08 17:22:09 +02:00
}
/*
XMBs are stored with a unique name, where the name is generated from
characteristics of the XML file. If a file already exists with the
generated name, it is assumed that that file is a valid conversion of
the XML, and so it's loaded. Otherwise, the XMB is created with that
filename.
This means it's never necessary to overwrite existing XMB files; since
the XMBs are often in archives, it's not easy to rewrite those files,
and it's not possible to switch to using a loose file because the VFS
has already decided that file is inside an archive. So each XMB is given
a unique name, and old ones are somehow purged.
*/
2004-07-08 17:22:09 +02:00
// Generate the filename for the xmb:
// <xml filename>_<mtime><size><format version>.xmb
// with mtime/size as 8-digit hex, where mtime's lowest bit is
// zeroed because zip files only have 2 second resolution.
const int suffixLength = 22;
char suffix[suffixLength+1];
int ret = sprintf(suffix, "_%08x%08xB.xmb", (int)(fileInfo.MTime() & ~1), (int)fileInfo.Size());
debug_assert(ret == suffixLength);
VfsPath xmbFilename = change_extension(filename, suffix);
2004-07-08 17:22:09 +02:00
VfsPath xmbPath;
GetXMBPath(g_VFS, filename, xmbFilename, xmbPath);
// If the file exists, use it
if (FileExists(xmbPath))
{
if (ReadXMBFile(xmbPath))
return PSRETURN_OK;
// (no longer return PSRETURN_Xeromyces_XMLOpenFailed here because
// failure legitimately happens due to partially-written XMB files.)
}
2004-07-08 17:22:09 +02:00
// XMB isn't up to date with the XML, so rebuild it:
// Load Xerces if necessary
if (! XercesLoaded)
{
XMLPlatformUtils::Initialize();
XercesLoaded = 1;
}
// Open the .xml file
CVFSInputSource source;
if (source.OpenFile(filename) < 0)
{
LOG(CLogger::Error, LOG_CATEGORY, "CXeromyces: Failed to open XML file %s", filename.string().c_str());
return PSRETURN_Xeromyces_XMLOpenFailed;
}
2004-07-08 17:22:09 +02:00
// Set up the Xerces parser
2004-07-08 17:22:09 +02:00
SAX2XMLReader* Parser = XMLReaderFactory::createXMLReader();
// Enable validation
Parser->setFeature(XMLUni::fgSAX2CoreValidation, true);
Parser->setFeature(XMLUni::fgXercesDynamic, true);
2004-07-08 17:22:09 +02:00
XeroHandler handler;
Parser->setContentHandler(&handler);
CXercesErrorHandler errorHandler;
Parser->setErrorHandler(&errorHandler);
CVFSEntityResolver entityResolver(filename.string().c_str());
2004-07-08 17:22:09 +02:00
Parser->setEntityResolver(&entityResolver);
// Build a tree inside handler
Parser->parse(source);
// (It's horribly inefficient doing SAX2->tree then tree->XMB,
// but the XML->XMB conversion should be done very rarely
// anyway. If it's ever needed, the XMB writing can be done
// directly from inside the SAX2 event handlers, although that's
// a little more complex)
delete Parser;
if (errorHandler.GetSawErrors())
{
LOG(CLogger::Error, LOG_CATEGORY, "CXeromyces: Errors in XML file '%s'", filename.string().c_str());
return PSRETURN_Xeromyces_XMLParseError;
// The internal tree of the XeroHandler will be cleaned up automatically
}
// Convert the data structures into the XMB format
handler.CreateXMB();
2004-07-08 17:22:09 +02:00
// Save the file to disk, so it can be loaded quickly next time
WriteBuffer& writeBuffer = handler.writeBuffer;
g_VFS->CreateFile(xmbPath, writeBuffer.Data(), writeBuffer.Size());
2004-07-08 17:22:09 +02:00
XMBBuffer = writeBuffer.Data(); // add a reference
2004-07-08 17:22:09 +02:00
// Set up the XMBFile
const bool ok = Initialise((const char*)XMBBuffer.get());
debug_assert(ok);
return PSRETURN_OK;
2004-07-08 17:22:09 +02:00
}
bool CXeromyces::ReadXMBFile(const VfsPath& filename)
2004-07-08 17:22:09 +02:00
{
size_t size;
if(g_VFS->LoadFile(filename, XMBBuffer, size) < 0)
2004-07-08 17:22:09 +02:00
return false;
debug_assert(size >= 42); // else: invalid XMB file size. (42 bytes is the smallest possible XMB. (Well, maybe not quite, but it's a nice number.))
2004-07-08 17:22:09 +02:00
// Set up the XMBFile
if(!Initialise((const char*)XMBBuffer.get()))
return false;
2004-07-08 17:22:09 +02:00
return true;
}
void XeroHandler::startDocument()
{
Root = new XMLElement;
ElementStack.push(Root);
}
void XeroHandler::endDocument()
{
}
# Housekeeping and pathfinder enhancements / optimization when dealing with ranged actions. - Modified Xeromyces to no longer automatically convert element and attribute names to lowercase, so that we can have camelCase names. We should now be able to convert all the multi-word entity properties, like pass_through_allies, to camelCase, like passThroughAllies, which is more consistent with the rest of our JavaScript naming conventions. To support the existing code that assumes lowercase element names, I made the getElementID and getAttributeID methods (used in the EL and AT macros) ignore case, and I changed any code that directly accessed element names to use the right case. CEntityTemplate now converts Names_LikeThis to names_likeThis (changing each separate "word" in the name to camelCase). Changed the version letter in XMB filenames from A to B to support this without requiring people to delete old XMBs. - Enhanced the pathfinder's handling of contact paths, resulting in a very large speedup for actions like attacking, construction, etc. The problem was that the pathfinder used to not count a given state as the goal unless it was exactly coincident with the target location. This is fine when you order a unit to go exactly to a certain spot, but if you're ordering a unit to build, gather or attack something, then the target tile is impassable (because your target is there) and therefore the pathfinder never declares a state final. As a result, the pathfinder tries hundreds of extra tiles in case there is a long path that gets to the goal, and after failing to find any path that reaches the goal, it gives you one to the closest point it got to. To fix it, I made the pathfinder take into account a radius around the goal in which it's OK to be, which depends on the size of the target unit and the range of your action. This was SVN commit r4186.
2006-08-01 05:41:21 +02:00
/*
// Silently clobbers non-ASCII characters
std::string lowercase_ascii(const XMLCh *a)
{
std::string b;
size_t len=XMLString::stringLen(a);
b.resize(len);
for (size_t i = 0; i < len; ++i)
b[i] = (char)towlower(a[i]);
return b;
}
# Housekeeping and pathfinder enhancements / optimization when dealing with ranged actions. - Modified Xeromyces to no longer automatically convert element and attribute names to lowercase, so that we can have camelCase names. We should now be able to convert all the multi-word entity properties, like pass_through_allies, to camelCase, like passThroughAllies, which is more consistent with the rest of our JavaScript naming conventions. To support the existing code that assumes lowercase element names, I made the getElementID and getAttributeID methods (used in the EL and AT macros) ignore case, and I changed any code that directly accessed element names to use the right case. CEntityTemplate now converts Names_LikeThis to names_likeThis (changing each separate "word" in the name to camelCase). Changed the version letter in XMB filenames from A to B to support this without requiring people to delete old XMBs. - Enhanced the pathfinder's handling of contact paths, resulting in a very large speedup for actions like attacking, construction, etc. The problem was that the pathfinder used to not count a given state as the goal unless it was exactly coincident with the target location. This is fine when you order a unit to go exactly to a certain spot, but if you're ordering a unit to build, gather or attack something, then the target tile is impassable (because your target is there) and therefore the pathfinder never declares a state final. As a result, the pathfinder tries hundreds of extra tiles in case there is a long path that gets to the goal, and after failing to find any path that reaches the goal, it gives you one to the closest point it got to. To fix it, I made the pathfinder take into account a radius around the goal in which it's OK to be, which depends on the size of the target unit and the range of your action. This was SVN commit r4186.
2006-08-01 05:41:21 +02:00
*/
/**
* Return an ASCII version of the given 16-bit string, ignoring
* any non-ASCII characters.
*
* @param const XMLCh * a Input string.
* @return std::string 8-bit ASCII version of <code>a</code>.
**/
std::string toAscii( const XMLCh* a )
{
std::string b;
size_t len=XMLString::stringLen(a);
# Housekeeping and pathfinder enhancements / optimization when dealing with ranged actions. - Modified Xeromyces to no longer automatically convert element and attribute names to lowercase, so that we can have camelCase names. We should now be able to convert all the multi-word entity properties, like pass_through_allies, to camelCase, like passThroughAllies, which is more consistent with the rest of our JavaScript naming conventions. To support the existing code that assumes lowercase element names, I made the getElementID and getAttributeID methods (used in the EL and AT macros) ignore case, and I changed any code that directly accessed element names to use the right case. CEntityTemplate now converts Names_LikeThis to names_likeThis (changing each separate "word" in the name to camelCase). Changed the version letter in XMB filenames from A to B to support this without requiring people to delete old XMBs. - Enhanced the pathfinder's handling of contact paths, resulting in a very large speedup for actions like attacking, construction, etc. The problem was that the pathfinder used to not count a given state as the goal unless it was exactly coincident with the target location. This is fine when you order a unit to go exactly to a certain spot, but if you're ordering a unit to build, gather or attack something, then the target tile is impassable (because your target is there) and therefore the pathfinder never declares a state final. As a result, the pathfinder tries hundreds of extra tiles in case there is a long path that gets to the goal, and after failing to find any path that reaches the goal, it gives you one to the closest point it got to. To fix it, I made the pathfinder take into account a radius around the goal in which it's OK to be, which depends on the size of the target unit and the range of your action. This was SVN commit r4186.
2006-08-01 05:41:21 +02:00
b.reserve(len);
for (size_t i = 0; i < len; ++i)
# Housekeeping and pathfinder enhancements / optimization when dealing with ranged actions. - Modified Xeromyces to no longer automatically convert element and attribute names to lowercase, so that we can have camelCase names. We should now be able to convert all the multi-word entity properties, like pass_through_allies, to camelCase, like passThroughAllies, which is more consistent with the rest of our JavaScript naming conventions. To support the existing code that assumes lowercase element names, I made the getElementID and getAttributeID methods (used in the EL and AT macros) ignore case, and I changed any code that directly accessed element names to use the right case. CEntityTemplate now converts Names_LikeThis to names_likeThis (changing each separate "word" in the name to camelCase). Changed the version letter in XMB filenames from A to B to support this without requiring people to delete old XMBs. - Enhanced the pathfinder's handling of contact paths, resulting in a very large speedup for actions like attacking, construction, etc. The problem was that the pathfinder used to not count a given state as the goal unless it was exactly coincident with the target location. This is fine when you order a unit to go exactly to a certain spot, but if you're ordering a unit to build, gather or attack something, then the target tile is impassable (because your target is there) and therefore the pathfinder never declares a state final. As a result, the pathfinder tries hundreds of extra tiles in case there is a long path that gets to the goal, and after failing to find any path that reaches the goal, it gives you one to the closest point it got to. To fix it, I made the pathfinder take into account a radius around the goal in which it's OK to be, which depends on the size of the target unit and the range of your action. This was SVN commit r4186.
2006-08-01 05:41:21 +02:00
{
if(a[i] < 0x80)
# Housekeeping and pathfinder enhancements / optimization when dealing with ranged actions. - Modified Xeromyces to no longer automatically convert element and attribute names to lowercase, so that we can have camelCase names. We should now be able to convert all the multi-word entity properties, like pass_through_allies, to camelCase, like passThroughAllies, which is more consistent with the rest of our JavaScript naming conventions. To support the existing code that assumes lowercase element names, I made the getElementID and getAttributeID methods (used in the EL and AT macros) ignore case, and I changed any code that directly accessed element names to use the right case. CEntityTemplate now converts Names_LikeThis to names_likeThis (changing each separate "word" in the name to camelCase). Changed the version letter in XMB filenames from A to B to support this without requiring people to delete old XMBs. - Enhanced the pathfinder's handling of contact paths, resulting in a very large speedup for actions like attacking, construction, etc. The problem was that the pathfinder used to not count a given state as the goal unless it was exactly coincident with the target location. This is fine when you order a unit to go exactly to a certain spot, but if you're ordering a unit to build, gather or attack something, then the target tile is impassable (because your target is there) and therefore the pathfinder never declares a state final. As a result, the pathfinder tries hundreds of extra tiles in case there is a long path that gets to the goal, and after failing to find any path that reaches the goal, it gives you one to the closest point it got to. To fix it, I made the pathfinder take into account a radius around the goal in which it's OK to be, which depends on the size of the target unit and the range of your action. This was SVN commit r4186.
2006-08-01 05:41:21 +02:00
b += (char) a[i];
}
return b;
}
2004-07-08 17:22:09 +02:00
void XeroHandler::startElement(const XMLCh* const UNUSED(uri), const XMLCh* const localname, const XMLCh* const UNUSED(qname), const Attributes& attrs)
2004-07-08 17:22:09 +02:00
{
# Housekeeping and pathfinder enhancements / optimization when dealing with ranged actions. - Modified Xeromyces to no longer automatically convert element and attribute names to lowercase, so that we can have camelCase names. We should now be able to convert all the multi-word entity properties, like pass_through_allies, to camelCase, like passThroughAllies, which is more consistent with the rest of our JavaScript naming conventions. To support the existing code that assumes lowercase element names, I made the getElementID and getAttributeID methods (used in the EL and AT macros) ignore case, and I changed any code that directly accessed element names to use the right case. CEntityTemplate now converts Names_LikeThis to names_likeThis (changing each separate "word" in the name to camelCase). Changed the version letter in XMB filenames from A to B to support this without requiring people to delete old XMBs. - Enhanced the pathfinder's handling of contact paths, resulting in a very large speedup for actions like attacking, construction, etc. The problem was that the pathfinder used to not count a given state as the goal unless it was exactly coincident with the target location. This is fine when you order a unit to go exactly to a certain spot, but if you're ordering a unit to build, gather or attack something, then the target tile is impassable (because your target is there) and therefore the pathfinder never declares a state final. As a result, the pathfinder tries hundreds of extra tiles in case there is a long path that gets to the goal, and after failing to find any path that reaches the goal, it gives you one to the closest point it got to. To fix it, I made the pathfinder take into account a radius around the goal in which it's OK to be, which depends on the size of the target unit and the range of your action. This was SVN commit r4186.
2006-08-01 05:41:21 +02:00
std::string elementName = toAscii(localname);
2004-07-08 17:22:09 +02:00
ElementNames.insert(elementName);
// Create a new element
XMLElement* e = new XMLElement;
e->name = elementName;
e->linenum = m_locator->getLineNumber();
2004-07-08 17:22:09 +02:00
// Store all the attributes in the new element
for (unsigned int i = 0; i < attrs.getLength(); ++i)
{
# Housekeeping and pathfinder enhancements / optimization when dealing with ranged actions. - Modified Xeromyces to no longer automatically convert element and attribute names to lowercase, so that we can have camelCase names. We should now be able to convert all the multi-word entity properties, like pass_through_allies, to camelCase, like passThroughAllies, which is more consistent with the rest of our JavaScript naming conventions. To support the existing code that assumes lowercase element names, I made the getElementID and getAttributeID methods (used in the EL and AT macros) ignore case, and I changed any code that directly accessed element names to use the right case. CEntityTemplate now converts Names_LikeThis to names_likeThis (changing each separate "word" in the name to camelCase). Changed the version letter in XMB filenames from A to B to support this without requiring people to delete old XMBs. - Enhanced the pathfinder's handling of contact paths, resulting in a very large speedup for actions like attacking, construction, etc. The problem was that the pathfinder used to not count a given state as the goal unless it was exactly coincident with the target location. This is fine when you order a unit to go exactly to a certain spot, but if you're ordering a unit to build, gather or attack something, then the target tile is impassable (because your target is there) and therefore the pathfinder never declares a state final. As a result, the pathfinder tries hundreds of extra tiles in case there is a long path that gets to the goal, and after failing to find any path that reaches the goal, it gives you one to the closest point it got to. To fix it, I made the pathfinder take into account a radius around the goal in which it's OK to be, which depends on the size of the target unit and the range of your action. This was SVN commit r4186.
2006-08-01 05:41:21 +02:00
std::string attrName = toAscii(attrs.getLocalName(i));
2004-07-08 17:22:09 +02:00
AttributeNames.insert(attrName);
XMLAttribute* a = new XMLAttribute;
a->name = attrName;
const XMLCh *tmp = attrs.getValue(i);
a->value = utf16string(tmp, tmp+XMLString::stringLen(tmp));
2004-07-08 17:22:09 +02:00
e->attrs.push_back(a);
}
// Add the element to its parent
ElementStack.top()->childs.push_back(e);
// Set as parent of following elements
ElementStack.push(e);
}
void XeroHandler::endElement(const XMLCh* const UNUSED(uri), const XMLCh* const UNUSED(localname), const XMLCh* const UNUSED(qname))
2004-07-08 17:22:09 +02:00
{
ElementStack.pop();
}
void XeroHandler::characters(const XMLCh* const chars, const unsigned int UNUSED(length))
2004-07-08 17:22:09 +02:00
{
ElementStack.top()->text += utf16string(chars, chars+XMLString::stringLen(chars));
2004-07-08 17:22:09 +02:00
}
void XeroHandler::CreateXMB()
2004-07-08 17:22:09 +02:00
{
// Header
writeBuffer.Append(UnfinishedHeaderMagicStr, 4);
2004-07-08 17:22:09 +02:00
std::set<std::string>::iterator it;
2004-07-08 17:22:09 +02:00
int i;
// Element names
i = 0;
int ElementCount = (int)ElementNames.size();
writeBuffer.Append(&ElementCount, 4);
2004-07-08 17:22:09 +02:00
for (it = ElementNames.begin(); it != ElementNames.end(); ++it)
{
int TextLen = (int)it->length()+1;
writeBuffer.Append(&TextLen, 4);
writeBuffer.Append((void*)it->c_str(), TextLen);
2004-07-08 17:22:09 +02:00
ElementID[*it] = i++;
}
// Attribute names
i = 0;
int AttributeCount = (int)AttributeNames.size();
writeBuffer.Append(&AttributeCount, 4);
2004-07-08 17:22:09 +02:00
for (it = AttributeNames.begin(); it != AttributeNames.end(); ++it)
{
int TextLen = (int)it->length()+1;
writeBuffer.Append(&TextLen, 4);
writeBuffer.Append((void*)it->c_str(), TextLen);
2004-07-08 17:22:09 +02:00
AttributeID[*it] = i++;
}
// All the XML contents must be surrounded by a single element
debug_assert(Root->childs.size() == 1);
2004-07-08 17:22:09 +02:00
OutputElement(Root->childs[0]);
2004-07-08 17:22:09 +02:00
delete Root;
Root = NULL;
// file is now valid, so insert correct magic string
writeBuffer.Overwrite(HeaderMagicStr, 4, 0);
2004-07-08 17:22:09 +02:00
}
// Writes a whole element (recursively if it has children) into the buffer,
// and also frees all the memory that has been allocated for that element.
void XeroHandler::OutputElement(XMLElement* el)
{
// Filled in later with the length of the element
int Pos_Length = (int)writeBuffer.Size();
writeBuffer.Append("????", 4);
2004-07-08 17:22:09 +02:00
int NameID = ElementID[el->name];
writeBuffer.Append(&NameID, 4);
2004-07-08 17:22:09 +02:00
int AttrCount = (int)el->attrs.size();
writeBuffer.Append(&AttrCount, 4);
2004-07-08 17:22:09 +02:00
int ChildCount = (int)el->childs.size();
writeBuffer.Append(&ChildCount, 4);
2004-07-08 17:22:09 +02:00
// Filled in later with the offset to the list of child elements
int Pos_ChildrenOffset = (int)writeBuffer.Size();
writeBuffer.Append("????", 4);
2004-07-08 17:22:09 +02:00
// Trim excess whitespace in the entity's text, while counting
// the number of newlines trimmed (so that JS error reporting
// can give the correct line number)
std::string whitespaceA = " \t\r\n";
utf16string whitespace (whitespaceA.begin(), whitespaceA.end());
// Find the start of the non-whitespace section
2004-07-08 17:22:09 +02:00
size_t first = el->text.find_first_not_of(whitespace);
if (first == el->text.npos)
// Entirely whitespace - easy to handle
el->text = utf16string();
2004-07-08 17:22:09 +02:00
else
{
// Count the number of \n being cut off,
// and add them to the line number
utf16string trimmed (el->text.begin(), el->text.begin()+first);
el->linenum += (int)std::count(trimmed.begin(), trimmed.end(), (utf16_t)'\n');
// Find the end of the non-whitespace section,
// and trim off everything else
2004-07-08 17:22:09 +02:00
size_t last = el->text.find_last_not_of(whitespace);
el->text = el->text.substr(first, 1+last-first);
}
2004-07-08 17:22:09 +02:00
// Output text, prefixed by length in bytes
if (el->text.length() == 0)
{
// No text; don't write much
writeBuffer.Append("\0\0\0\0", 4);
}
else
{
// Write length and line number and null-terminated text
int NodeLen = 4 + 2*((int)el->text.length()+1);
writeBuffer.Append(&NodeLen, 4);
writeBuffer.Append(&el->linenum, 4);
writeBuffer.Append((void*)el->text.c_str(), NodeLen-4);
}
// Output attributes
2004-07-08 17:22:09 +02:00
int i;
for (i = 0; i < AttrCount; ++i)
2004-07-08 17:22:09 +02:00
{
int AttrName = AttributeID[el->attrs[i]->name];
writeBuffer.Append(&AttrName, 4);
2004-07-08 17:22:09 +02:00
int AttrLen = 2*((int)el->attrs[i]->value.length()+1);
writeBuffer.Append(&AttrLen, 4);
writeBuffer.Append((void*)el->attrs[i]->value.c_str(), AttrLen);
2004-07-08 17:22:09 +02:00
// Free each attribute as soon as it's been dealt with
delete el->attrs[i];
}
// Go back and fill in the child-element offset
int ChildrenOffset = (int)writeBuffer.Size() - (Pos_ChildrenOffset+4);
writeBuffer.Overwrite(&ChildrenOffset, 4, Pos_ChildrenOffset);
2004-07-08 17:22:09 +02:00
// Output all child nodes
for (i = 0; i < ChildCount; ++i)
2004-07-08 17:22:09 +02:00
OutputElement(el->childs[i]);
// Go back and fill in the length
int Length = (int)writeBuffer.Size() - Pos_Length;
writeBuffer.Overwrite(&Length, 4, Pos_Length);
2004-07-08 17:22:09 +02:00
// Tidy up the parser's mess
delete el;
}
void XeroHandler::DeallocateElement(XMLElement* el)
{
size_t i;
for (i = 0; i < el->attrs.size(); ++i)
delete el->attrs[i];
for (i = 0; i < el->childs.size(); ++i)
DeallocateElement(el->childs[i]);
delete el;
}