352 lines
9.2 KiB
C++
352 lines
9.2 KiB
C++
/**
|
|
* PANDA 3D SOFTWARE
|
|
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
|
*
|
|
* All use of this software is subject to the terms of the revised BSD
|
|
* license. You should have received a copy of this license along
|
|
* with this source code in a file named "LICENSE."
|
|
*
|
|
* @file binaryXml.cxx
|
|
* @author drose
|
|
* @date 2009-07-13
|
|
*/
|
|
|
|
#include "binaryXml.h"
|
|
#include "p3d_lock.h"
|
|
#include <sstream>
|
|
|
|
using std::istream;
|
|
using std::ostream;
|
|
using std::ostringstream;
|
|
using std::string;
|
|
|
|
static const bool debug_xml_output = false;
|
|
|
|
static LOCK xml_lock;
|
|
static bool xml_lock_initialized = false;
|
|
|
|
#define DO_BINARY_XML 1
|
|
|
|
enum NodeType {
|
|
NT_unknown,
|
|
NT_document,
|
|
NT_element,
|
|
NT_text,
|
|
};
|
|
|
|
// This typedef defines a 32-bit unsigned integer. It's used for passing
|
|
// values through the binary XML stream.
|
|
typedef unsigned int xml_uint32;
|
|
|
|
// These are both prime numbers, though I don't know if that really matters.
|
|
// Mainly, they're big random numbers.
|
|
static const xml_uint32 length_nonce1 = 812311453;
|
|
static const xml_uint32 length_nonce2 = 612811373;
|
|
|
|
/**
|
|
* Should be called before spawning any threads to ensure the lock is
|
|
* initialized.
|
|
*/
|
|
void
|
|
init_xml() {
|
|
if (!xml_lock_initialized) {
|
|
INIT_LOCK(xml_lock);
|
|
xml_lock_initialized = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recursively writes a node and all of its children to the given stream.
|
|
*/
|
|
static void
|
|
write_xml_node(ostream &out, TiXmlNode *xnode) {
|
|
const string &value = xnode->ValueStr();
|
|
xml_uint32 value_length = value.length();
|
|
xml_uint32 value_proof = (value_length + length_nonce1) * length_nonce2;
|
|
|
|
// We write out not only value_length, but the same value again hashed by
|
|
// length_nonce1 and 2 (and truncated back to xml_uint32), just to prove to
|
|
// the reader that we're still on the same page. We do this only on the top
|
|
// node; we don't bother for the nested nodes.
|
|
out.write((char *)&value_length, sizeof(value_length));
|
|
out.write((char *)&value_proof, sizeof(value_proof));
|
|
out.write(value.data(), value_length);
|
|
|
|
// Now write out the node type.
|
|
NodeType type = NT_element;
|
|
if (xnode->ToDocument() != nullptr) {
|
|
type = NT_document;
|
|
} else if (xnode->ToElement() != nullptr) {
|
|
type = NT_element;
|
|
} else if (xnode->ToText() != nullptr) {
|
|
type = NT_text;
|
|
} else {
|
|
type = NT_unknown;
|
|
}
|
|
|
|
out.put((char)type);
|
|
// We don't bother to write any further data for the unknown types.
|
|
if (type == NT_unknown) {
|
|
return;
|
|
}
|
|
|
|
if (type == NT_element) {
|
|
// Write the element attributes.
|
|
TiXmlElement *xelement = xnode->ToElement();
|
|
assert(xelement != nullptr);
|
|
const TiXmlAttribute *xattrib = xelement->FirstAttribute();
|
|
|
|
while (xattrib != nullptr) {
|
|
// We have an attribute.
|
|
out.put((char)true);
|
|
|
|
string name = xattrib->Name();
|
|
xml_uint32 name_length = name.length();
|
|
out.write((char *)&name_length, sizeof(name_length));
|
|
out.write(name.data(), name_length);
|
|
|
|
const string &value = xattrib->ValueStr();
|
|
xml_uint32 value_length = value.length();
|
|
out.write((char *)&value_length, sizeof(value_length));
|
|
out.write(value.data(), value_length);
|
|
|
|
xattrib = xattrib->Next();
|
|
}
|
|
|
|
// The end of the attributes list.
|
|
out.put((char)false);
|
|
}
|
|
|
|
// Now write all of the children.
|
|
TiXmlNode *xchild = xnode->FirstChild();
|
|
while (xchild != nullptr) {
|
|
// We have a child.
|
|
out.put((char)true);
|
|
write_xml_node(out, xchild);
|
|
xchild = xchild->NextSibling();
|
|
}
|
|
|
|
// The end of the children list.
|
|
out.put((char)false);
|
|
}
|
|
|
|
/**
|
|
* Recursively reads a node and all of its children to the given stream.
|
|
* Returns the newly-allocated node. The caller is responsible for eventually
|
|
* deleting the return value. Returns NULL on error.
|
|
*/
|
|
static TiXmlNode *
|
|
read_xml_node(istream &in, char *&buffer, xml_uint32 &buffer_length,
|
|
ostream &logfile) {
|
|
xml_uint32 value_length;
|
|
in.read((char *)&value_length, sizeof(value_length));
|
|
if (in.gcount() != sizeof(value_length)) {
|
|
return nullptr;
|
|
}
|
|
xml_uint32 value_proof_expect = (value_length + length_nonce1) * length_nonce2;
|
|
xml_uint32 value_proof;
|
|
in.read((char *)&value_proof, sizeof(value_proof));
|
|
if (in.gcount() != sizeof(value_proof)) {
|
|
return nullptr;
|
|
}
|
|
if (value_proof != value_proof_expect) {
|
|
// Hey, we ran into garbage: the proof value didn't match our expected
|
|
// proof value.
|
|
logfile << "Garbage on XML stream!\n";
|
|
|
|
// Print out the garbage; maybe it will help the developer figure out
|
|
// where it came from.
|
|
logfile << "Begin garbage:\n";
|
|
ostringstream strm;
|
|
strm.write((char *)&value_length, sizeof(value_length));
|
|
strm.write((char *)&value_proof, sizeof(value_proof));
|
|
logfile << strm.str();
|
|
for (size_t i = 0; i < 100; ++i) {
|
|
int ch = in.get();
|
|
if (ch != EOF) {
|
|
logfile.put(ch);
|
|
}
|
|
}
|
|
logfile << "\n";
|
|
logfile << "End garbage.\n";
|
|
return nullptr;
|
|
}
|
|
|
|
if (value_length > buffer_length) {
|
|
delete[] buffer;
|
|
buffer_length = value_length;
|
|
buffer = new char[buffer_length];
|
|
}
|
|
|
|
in.read(buffer, value_length);
|
|
string value(buffer, value_length);
|
|
|
|
// Read the node type.
|
|
NodeType type = (NodeType)in.get();
|
|
if (type == NT_unknown) {
|
|
return nullptr;
|
|
}
|
|
|
|
TiXmlNode *xnode = nullptr;
|
|
if (type == NT_element) {
|
|
xnode = new TiXmlElement(value);
|
|
} else if (type == NT_document) {
|
|
xnode = new TiXmlDocument;
|
|
} else if (type == NT_text) {
|
|
xnode = new TiXmlText(value);
|
|
} else {
|
|
assert(false);
|
|
}
|
|
|
|
if (type == NT_element) {
|
|
// Read the element attributes.
|
|
TiXmlElement *xelement = xnode->ToElement();
|
|
assert(xelement != nullptr);
|
|
bool got_attrib = (bool)(in.get() != 0);
|
|
|
|
while (got_attrib && in && !in.eof()) {
|
|
// We have an attribute.
|
|
xml_uint32 name_length;
|
|
in.read((char *)&name_length, sizeof(name_length));
|
|
if (in.gcount() != sizeof(name_length)) {
|
|
delete xnode;
|
|
return nullptr;
|
|
}
|
|
|
|
if (name_length > buffer_length) {
|
|
delete[] buffer;
|
|
buffer_length = name_length;
|
|
buffer = new char[buffer_length];
|
|
}
|
|
|
|
in.read(buffer, name_length);
|
|
string name(buffer, name_length);
|
|
|
|
xml_uint32 value_length;
|
|
in.read((char *)&value_length, sizeof(value_length));
|
|
if (in.gcount() != sizeof(value_length)) {
|
|
delete xnode;
|
|
return nullptr;
|
|
}
|
|
|
|
if (value_length > buffer_length) {
|
|
delete[] buffer;
|
|
buffer_length = value_length;
|
|
buffer = new char[buffer_length];
|
|
}
|
|
|
|
in.read(buffer, value_length);
|
|
string value(buffer, value_length);
|
|
|
|
xelement->SetAttribute(name, value);
|
|
|
|
got_attrib = (bool)(in.get() != 0);
|
|
}
|
|
}
|
|
|
|
// Now read all of the children.
|
|
bool got_child = (bool)(in.get() != 0);
|
|
|
|
while (got_child && in && !in.eof()) {
|
|
// We have a child.
|
|
TiXmlNode *xchild = read_xml_node(in, buffer, buffer_length, logfile);
|
|
if (xchild != nullptr) {
|
|
xnode->LinkEndChild(xchild);
|
|
}
|
|
|
|
got_child = (bool)(in.get() != 0);
|
|
}
|
|
|
|
return xnode;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Writes the indicated TinyXml document to the given stream.
|
|
*/
|
|
void
|
|
write_xml(ostream &out, TiXmlDocument *doc, ostream &logfile) {
|
|
assert(xml_lock_initialized);
|
|
ACQUIRE_LOCK(xml_lock);
|
|
|
|
#ifdef DO_BINARY_XML
|
|
// Binary write.
|
|
write_xml_node(out, doc);
|
|
|
|
#else
|
|
// Formatted ASCII write.
|
|
|
|
// We need a declaration to write it safely.
|
|
TiXmlDeclaration decl("1.0", "utf-8", "");
|
|
doc->InsertBeforeChild(doc->FirstChild(), decl);
|
|
|
|
out << *doc;
|
|
#endif
|
|
|
|
out << flush;
|
|
|
|
if (debug_xml_output) {
|
|
// Write via ostringstream, so it all goes in one operation, to help out
|
|
// the interleaving from multiple threads.
|
|
ostringstream logout;
|
|
logout << "sent: " << *doc << "\n";
|
|
logfile << logout.str() << flush;
|
|
}
|
|
|
|
RELEASE_LOCK(xml_lock);
|
|
}
|
|
|
|
/**
|
|
* Reads a TinyXml document from the given stream, and returns it. If the
|
|
* document is not yet available, blocks until it is, or until there is an
|
|
* error condition on the input.
|
|
*
|
|
* The return value is NULL if there is an error, or the newly-allocated
|
|
* document if it is successfully read. If not NULL, the document has been
|
|
* allocated with new, and should be eventually freed by the caller with
|
|
* delete.
|
|
*/
|
|
TiXmlDocument *
|
|
read_xml(istream &in, ostream &logfile) {
|
|
// We don't acquire xml_lock while reading. We can't, because our XML
|
|
// readers are all designed to block until data is available, and they can't
|
|
// block while holding the lock.
|
|
|
|
// Fortunately, there should be only one reader at a time, so a lock isn't
|
|
// really needed here.
|
|
|
|
#if DO_BINARY_XML
|
|
// binary read.
|
|
xml_uint32 buffer_length = 128;
|
|
char *buffer = new char[buffer_length];
|
|
TiXmlNode *xnode = read_xml_node(in, buffer, buffer_length, logfile);
|
|
delete[] buffer;
|
|
if (xnode == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
TiXmlDocument *doc = xnode->ToDocument();
|
|
assert(doc != nullptr);
|
|
|
|
#else
|
|
// standard ASCII read.
|
|
TiXmlDocument *doc = new TiXmlDocument;
|
|
in >> *doc;
|
|
if (in.fail() || in.eof()) {
|
|
delete doc;
|
|
return nullptr;
|
|
}
|
|
#endif
|
|
|
|
if (debug_xml_output) {
|
|
// Write via ostringstream, so it all goes in one operation, to help out
|
|
// the interleaving from multiple threads.
|
|
ostringstream logout;
|
|
logout << "received: " << *doc << "\n";
|
|
logfile << logout.str() << flush;
|
|
}
|
|
|
|
return doc;
|
|
}
|