⛏️ index : haiku.git

/*
 * Copyright (c) 1999-2000, Eric Moon.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions, and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


// Importer.cpp
// e.moon 28jun99

#include "Importer.h"
#include <stdexcept>

#include <Autolock.h>
#include <Debug.h>

using namespace std;

__USE_CORTEX_NAMESPACE

// -------------------------------------------------------- //
// expat hooks
// -------------------------------------------------------- //

void _oc_handle_start(
	void* pUser,
	const XML_Char* pName,
	const XML_Char** ppAtts) {
	((Importer*)pUser)->xmlElementStart(pName, ppAtts);	
}

void _oc_handle_end(
	void* pUser,
	const XML_Char* pName) {
	((Importer*)pUser)->xmlElementEnd(pName);
}

void _oc_handle_pi(
	void* pUser,
	const XML_Char* pTarget,
	const XML_Char* pData) {
	((Importer*)pUser)->xmlProcessingInstruction(pTarget, pData);
}

void _oc_handle_char(
	void* pUser,
	const XML_Char* pData,
	int length) {
	((Importer*)pUser)->xmlCharacterData(pData, length);
}

void _oc_handle_default(
	void* pUser,
	const XML_Char* pData,
	int length) {
	((Importer*)pUser)->xmlDefaultData(pData, length);
}

// -------------------------------------------------------- //
// ctor/dtor
// -------------------------------------------------------- //

Importer::~Importer() {
	// clean up
	freeParser();

	delete m_context;
}


Importer::Importer(
	list<BString>&							errors) :

	m_parser(0),
	m_docType(0),
	m_identify(false),
	m_context(new ImportContext(errors)),
	m_rootObject(0) {
	
	initParser();
}

Importer::Importer(
	ImportContext*							context) :

	m_parser(0),
	m_docType(0),
	m_identify(false),
	m_context(context),
	m_rootObject(0) {
	
	ASSERT(m_context);
	
	initParser();
}

Importer::Importer(
	list<BString>&							errors,
	IPersistent*								rootObject,
	XML::DocumentType*					docType) :

	m_parser(0),
	m_docType(docType),
	m_identify(false),
	m_context(new ImportContext(errors)),
	m_rootObject(rootObject) {

	ASSERT(rootObject);
	ASSERT(docType);

	initParser();
}

Importer::Importer(
	ImportContext*							context,
	IPersistent*								rootObject,
	XML::DocumentType*					docType) :

	m_parser(0),
	m_docType(docType),
	m_identify(false),
	m_context(context),
	m_rootObject(rootObject) {

	ASSERT(m_context);
	ASSERT(rootObject);
	ASSERT(docType);

	initParser();
}
		
// -------------------------------------------------------- //
// accessors
// -------------------------------------------------------- //

// the import context
const ImportContext& Importer::context() const {
	return *m_context;
}

// matched (or provided) document type
XML::DocumentType* Importer::docType() const {
	return m_docType;
}

// completed object (available if
// context().state() == ImportContext::COMPLETE, or
// if a root object was provided to the ctor)
IPersistent* Importer::target() const {
	return m_rootObject;
}
		
// -------------------------------------------------------- //
// operations
// -------------------------------------------------------- //

// put the importer into 'identify mode'
// (disengaged once the first element is encountered)
void Importer::setIdentifyMode() {
	reset();
	m_docType = 0;
	m_identify = true;
}

void Importer::reset() {
	// doesn't forget document type from identify cycle!
		
	m_identify = false;
	m_context->reset();
	m_rootObject = 0;
}

// handle a buffer; return false if an error occurs
bool Importer::parseBuffer(
	const char* pBuffer,
	uint32 length,
	bool last) {

	ASSERT(m_parser);

	int err = XML_Parse(m_parser, pBuffer, length, last);
		
	if(!err) {
		BString str = "Parse Error: ";
		str << XML_ErrorString(XML_GetErrorCode(m_parser));
		m_context->reportError(str.String());
		return false;

	} else
		return true;
}
	
// -------------------------------------------------------- //
// internal operations
// -------------------------------------------------------- //

// create & initialize parser
void Importer::initParser() {
	ASSERT(!m_parser);
	m_parser = XML_ParserCreate(0);
	m_context->m_pParser = m_parser;
	
	XML_SetElementHandler(
		m_parser,
		&_oc_handle_start,
		&_oc_handle_end);
	
	XML_SetProcessingInstructionHandler(
		m_parser,
		&_oc_handle_pi);

	XML_SetCharacterDataHandler(
		m_parser,
		&_oc_handle_char);
		
	XML_SetDefaultHandlerExpand(
		m_parser,
		&_oc_handle_default);
		
	XML_SetUserData(
		m_parser,
		(void*)this);	
}
	
// clean up the parser
void Importer::freeParser() {
	ASSERT(m_parser);
	XML_ParserFree(m_parser);
	m_parser = 0;
}

// -------------------------------------------------------- //
// XML parser event hooks
// -------------------------------------------------------- //

void Importer::xmlElementStart(
	const char*			pName,
	const char**		ppAttributes) {

	if(m_context->m_state != ImportContext::PARSING)
		return;

	IPersistent* target = 0;

	if(!m_context->m_elementStack.size()) {
		// this is the first element; identify or verify document type
		
		if(m_rootObject) {
			// test against expected document type
			ASSERT(m_docType);
			if(m_docType->rootElement != pName) {
				BString err("Unexpected document element (should be <");
				err << m_docType->rootElement << "/>";
				m_context->reportError(err.String());
				return;
			}
			
			// target the provided root object
			target = m_rootObject;
		}
		else {
			// look up doc type
			BAutolock _l(XML::s_docTypeLock);
			XML::doc_type_map::iterator it = XML::s_docTypeMap.find(
				BString(pName));
		
			if(it != XML::s_docTypeMap.end())
				m_docType = (*it).second;
			else {
				// whoops, don't know how to handle this element:
				BString err("No document type registered for element '");
				err << pName << "'.";
				m_context->reportError(err.String());
				return;
			}
				
			if(m_identify) {
				// end of identify cycle
				m_context->m_state = ImportContext::COMPLETE;
				return;
			}
		}
	}
	// at this point, there'd better be a valid document type
	ASSERT(m_docType);	
	
	// push element name onto the stack
	m_context->m_elementStack.push_back(pName);

	// try to create an object for this element if necessary
	if(!target)
		target = m_docType->objectFor(pName);	
	
	if(target) {
		// call 'begin import' hook
		m_context->m_objectStack.push_back(
			make_pair(m_context->element(), target));
		target->xmlImportBegin(*m_context);
		
		// error? bail
		if(m_context->state() != ImportContext::PARSING)
			return;
	
		// walk attributes
		while(*ppAttributes) {
			target->xmlImportAttribute(	
				ppAttributes[0],
				ppAttributes[1],
				*m_context);

			// error? bail
			if(m_context->state() != ImportContext::PARSING)
				return;
			
			ppAttributes += 2;
		}
	} else {
		// no object directly maps to this element; hand to
		// the current focus object

		ASSERT(m_context->m_objectStack.size());
		IPersistent* curObject = m_context->m_objectStack.back().second;
		ASSERT(curObject);
		
		curObject->xmlImportChildBegin(
			pName,
			*m_context);

		// error? bail
		if(m_context->state() != ImportContext::PARSING)
			return;

		// walk attributes
		while(*ppAttributes) {
			curObject->xmlImportChildAttribute(	
				ppAttributes[0],
				ppAttributes[1],
				*m_context);

			// error? bail
			if(m_context->state() != ImportContext::PARSING)
				return;
			
			ppAttributes += 2;
		}
	}
}
		
void Importer::xmlElementEnd(
	const char*			pName) {

	if(m_context->m_state != ImportContext::PARSING)
		return;
	ASSERT(m_docType);

//	PRINT(("Importer::xmlElementEnd(): %s\n", pName));

	// compare name to element on top of stack
	if(!m_context->m_elementStack.size() ||
		m_context->m_elementStack.back() != pName) {
		m_context->reportError("Mismatched end tag.");
		return;
	}

	// see if it matches the topmost object
	IPersistent* pObject = 0;
	if(!m_context->m_objectStack.size()) {
		m_context->reportError("No object being constructed.");
		return;
	}
	if(m_context->m_objectStack.back().first == m_context->element()) {
		// matched; pop it
		pObject = m_context->m_objectStack.back().second;
		m_context->m_objectStack.pop_back();
	}

	if(pObject) {
		// notify object that import is complete
		pObject->xmlImportComplete(
			*m_context);
		
		// error? bail
		if(m_context->state() != ImportContext::PARSING)
			return;

		if(m_context->m_objectStack.size()) {
			// hand the newly-constructed child to its parent
			m_context->m_objectStack.back().second->xmlImportChild(
				pObject,
				*m_context);
		} else {
			// done
			ASSERT(m_context->m_elementStack.size() == 1);
			m_context->m_state = ImportContext::COMPLETE;
			if(m_rootObject) {
				ASSERT(m_rootObject == pObject);
			} else
				m_rootObject = pObject;
		}
	}
	else {
		// notify current topmost object
		ASSERT(m_context->m_objectStack.size());
		IPersistent* curObject = m_context->m_objectStack.back().second;
		ASSERT(curObject);
		
		curObject->xmlImportChildComplete(
			pName,
			*m_context);
	}
	
	// remove entry from element stack
	m_context->m_elementStack.pop_back();
	ASSERT(m_context->m_objectStack.size() <= m_context->m_elementStack.size());
}
	
void Importer::xmlProcessingInstruction(
	const char*			pTarget,
	const char*			pData) {

	if(m_context->m_state != ImportContext::PARSING)
		return;
//	PRINT(("Importer::xmlProcessingInstruction(): %s, %s\n",
//		pTarget, pData));
	
}

// not 0-terminated
void Importer::xmlCharacterData(
	const char*			pData,
	int32					length) {

	if(m_context->m_state != ImportContext::PARSING)
		return;

	// see if the current element matches the topmost object
	IPersistent* pObject = 0;
	if(!m_context->m_objectStack.size()) {
		m_context->reportError("No object being constructed.");
		return;
	}

	pObject = m_context->m_objectStack.back().second;
	if(m_context->m_objectStack.back().first == m_context->element()) {
		
		pObject->xmlImportContent(
			pData,
			length,
			*m_context);
	}
	else {
		pObject->xmlImportChildContent(
			pData,
			length,
			*m_context);
	}
}
		
// not 0-terminated
void Importer::xmlDefaultData(
	const char*			pData,
	int32					length) {
	
	if(m_context->m_state != ImportContext::PARSING)
		return;
//	PRINT(("Importer::xmlDefaultData()\n"));
}
		
// END -- Importer.cpp --