/* * libSpiff - XSPF playlist handling library * * Copyright (C) 2007, Sebastian Pipping / Xiph.Org Foundation * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the following * disclaimer. * * * 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. * * * Neither the name of the Xiph.Org Foundation nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS 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. * * Sebastian Pipping, sping@xiph.org */ /** * @file SpiffXmlFormatter.cpp * Implementation of SpiffXmlFormatter. */ #include #include #include using namespace std; using namespace Spiff::Toolbox; namespace Spiff { /*static*/ const XML_Char * const SpiffXmlFormatter::namespaceKey = SPIFF_NS_HOME; /// @cond DOXYGEN_NON_API /** * D object for SpiffXmlFormatter. */ class SpiffXmlFormatterPrivate { friend class SpiffXmlFormatter; int level; ///< Nesting level, 0 before root element has been written, 1 after that std::map namespaceToPrefix; ///< Namespace prefix map std::list undo; ///< Ordered registration undo list std::set prefixPool; ///< Set of registered prefixes /** * Creates a new D object. */ SpiffXmlFormatterPrivate() : level(0), namespaceToPrefix(), undo(), prefixPool() { } /** * Copy constructor. * * @param source Source to copy from */ SpiffXmlFormatterPrivate(const SpiffXmlFormatterPrivate & source) : level(source.level), namespaceToPrefix(), undo(), prefixPool() { assign(source); } /** * Assignment operator. * * @param source Source to copy from */ SpiffXmlFormatterPrivate & operator=(const SpiffXmlFormatterPrivate & source) { if (this != &source) { this->level = source.level; freeMap(this->namespaceToPrefix); freeList(this->undo); this->prefixPool.clear(); // Same strings again, don'tdelet twice assign(source); } return *this; } /** * Destroys this D object. */ ~SpiffXmlFormatterPrivate() { freeMap(this->namespaceToPrefix); freeList(this->undo); this->prefixPool.clear(); // Same strings again, don'tdelet twice } void assign(const SpiffXmlFormatterPrivate & source) { map ::const_iterator iter = source.namespaceToPrefix.begin(); while (iter != source.namespaceToPrefix.end()) { const XML_Char * const uri = iter->first; XML_Char * const prefix = iter->second; registerNamespace(uri, prefix); iter++; } } static void freeMap(std::map & container) { map::iterator iter = container.begin(); while (iter != container.end()) { delete [] iter->second; iter++; } container.clear(); } static void freeList(std::list< SpiffNamespaceRegistrationUndo *> & container) { list::iterator iter = container.begin(); while (iter != container.end()) { SpiffNamespaceRegistrationUndo * const entry = *iter; delete entry; iter++; } container.clear(); } bool registerNamespace(const XML_Char * uri, const XML_Char * prefixSuggestion) { // Uri registered already? map ::iterator found = this->namespaceToPrefix.find(uri); if (found != this->namespaceToPrefix.end()) { return false; // == Existing } // Find unbound prefix (appending "x" if occupied) XML_Char * testPrefix = newAndCopy(prefixSuggestion); while (this->prefixPool.find(testPrefix) != this->prefixPool.end()) { const int testPrefixLen = static_cast(::PORT_STRLEN(testPrefix)); const int charCount = testPrefixLen + 1 + 1; XML_Char * nextPrefix = new XML_Char[charCount]; ::PORT_SNPRINTF(nextPrefix, charCount, _PT("%sx"), testPrefix); delete [] testPrefix; testPrefix = nextPrefix; } // Add prefix to map and pool this->namespaceToPrefix.insert(pair(uri, testPrefix)); this->prefixPool.insert(testPrefix); // Create undo entry SpiffNamespaceRegistrationUndo * undo = new SpiffNamespaceRegistrationUndo(this->level, uri); this->undo.push_front(undo); return true; // == Added } }; /// @endcond SpiffXmlFormatter::SpiffXmlFormatter() : d(new SpiffXmlFormatterPrivate()), introDone(false), output(NULL) { } SpiffXmlFormatter::SpiffXmlFormatter(const SpiffXmlFormatter & source) : d(new SpiffXmlFormatterPrivate(*(source.d))), introDone(source.introDone), output(source.output) { } SpiffXmlFormatter & SpiffXmlFormatter::operator=(const SpiffXmlFormatter & source) { if (this != &source) { *(this->d) = *(source.d); this->introDone = source.introDone; this->output = source.output; } return *this; } SpiffXmlFormatter::~SpiffXmlFormatter() { delete this->d; } const XML_Char * SpiffXmlFormatter::getPrefix(const XML_Char * nsUri) const { map ::const_iterator found = this->d->namespaceToPrefix.find(nsUri); if (found != this->d->namespaceToPrefix.end()) { return found->second; } else { return NULL; } } XML_Char * SpiffXmlFormatter::makeFullName(const XML_Char * nsUri, const XML_Char * localName) const { const XML_Char * const prefix = getPrefix(nsUri); if (prefix != NULL) { const int prefixLen = static_cast(::PORT_STRLEN(prefix)); const int localNameLen = static_cast(::PORT_STRLEN(localName)); XML_Char * fullName = NULL; if (prefixLen == 0) { // Default namespace fullName = new XML_Char[localNameLen + 1]; ::PORT_STRCPY(fullName, localName); } else { // Namespace with prefix fullName = new XML_Char[prefixLen + 1 + localNameLen + 1]; ::PORT_STRCPY(fullName, prefix); ::PORT_STRCPY(fullName + prefixLen, _PT(":")); ::PORT_STRCPY(fullName + prefixLen + 1, localName); } return fullName; } else { // TODO What exactly do we do with unregistered // namespace URIs? Register a new prefix and use it? return newAndCopy(localName); } } bool SpiffXmlFormatter::registerNamespace(const XML_Char * uri, const XML_Char * prefixSuggestion) { return this->d->registerNamespace(uri, prefixSuggestion); } void SpiffXmlFormatter::cleanupNamespaceRegs() { list::iterator iter = this->d->undo.begin(); while (iter != this->d->undo.end()) { SpiffNamespaceRegistrationUndo * const entry = *iter; if (entry->level >= this->d->level) { map ::iterator foundUri = this->d->namespaceToPrefix.find(entry->uri); if (foundUri != this->d->namespaceToPrefix.end()) { XML_Char * & prefix = foundUri->second; // Remove prefix set::iterator foundPrefix = this->d->prefixPool.find(prefix); if (foundPrefix != this->d->prefixPool.end()) { this->d->prefixPool.erase(foundPrefix); } delete [] prefix; // Remove URI this->d->namespaceToPrefix.erase(foundUri); } this->d->undo.erase(iter); delete entry; } else { break; } iter = this->d->undo.begin(); } } void SpiffXmlFormatter::setOutput(std::basic_ostringstream & output) { this->output = &output; } void SpiffXmlFormatter::writeStart(const XML_Char * ns, const XML_Char * localName, const XML_Char ** atts, const XML_Char ** nsRegs) { if (nsRegs != NULL) { list > attribs; // Process namespace registrations const XML_Char ** nsRegsWalk = nsRegs; while (nsRegsWalk[0] != NULL) { const XML_Char * & uri = nsRegsWalk[0]; const XML_Char * & prefix = nsRegsWalk[1]; // New namespace? if (registerNamespace(uri, prefix)) { const XML_Char * const finalPrefix = getPrefix(uri); XML_Char * finalKey = NULL; if (::PORT_STRLEN(finalPrefix) == 0) { // Default namespace finalKey = new XML_Char[5 + 1]; ::PORT_STRCPY(finalKey, _PT("xmlns")); } else { // Namespace with prefix const int finalPrefixLen = static_cast(::PORT_STRLEN(finalPrefix)); finalKey = new XML_Char[5 + 1 + finalPrefixLen + 1]; ::PORT_STRCPY(finalKey, _PT("xmlns:")); ::PORT_STRCPY(finalKey + 6, finalPrefix); } attribs.push_back(pair(finalKey, uri)); } nsRegsWalk += 2; } // Append normal attributes const XML_Char ** attsWalk = atts; while (attsWalk[0] != NULL) { // TODO Copying the first is a lazy hack. Improve. attribs.push_back(pair( newAndCopy(attsWalk[0]), attsWalk[1])); attsWalk += 2; } // Convert const int attribCount = static_cast(attribs.size()); const XML_Char ** finalAtts = new const XML_Char *[2 * attribCount + 1]; list >::iterator iter = attribs.begin(); const XML_Char ** finalAttsWalk = finalAtts; while (iter != attribs.end()) { finalAttsWalk[0] = (*iter).first; finalAttsWalk[1] = (*iter).second; finalAttsWalk += 2; iter++; } finalAttsWalk[0] = NULL; // Write tag const XML_Char * fullName = makeFullName(ns, localName); writeStart(fullName, finalAtts); // Full cleanup delete [] fullName; finalAttsWalk = finalAtts; while (finalAttsWalk[0] != NULL) { delete [] finalAttsWalk[0]; finalAttsWalk += 2; } delete [] finalAtts; } else { // No registrations const XML_Char * fullName = makeFullName(ns, localName); writeStart(fullName, atts); delete [] fullName; } this->d->level++; } void SpiffXmlFormatter::writeEnd(const XML_Char * ns, const XML_Char * localName) { const XML_Char * fullName = makeFullName(ns, localName); writeEnd(fullName); delete [] fullName; cleanupNamespaceRegs(); this->d->level--; } void SpiffXmlFormatter::writeHomeStart(const XML_Char * localName, const XML_Char ** atts, const XML_Char ** nsRegs) { writeStart(SpiffXmlFormatter::namespaceKey, localName, atts, nsRegs); } void SpiffXmlFormatter::writeHomeEnd(const XML_Char * localName) { writeEnd(SpiffXmlFormatter::namespaceKey, localName); } void SpiffXmlFormatter::writeCharacterData(const XML_Char * data) { // Extensible Markup Language (XML) 1.0 (Fourth Edition) // 2.4 Character Data and Markup // http://www.w3.org/TR/REC-xml/#syntax const XML_Char * start = data; const XML_Char * end = data; for (;;) { switch (*end) { case _PT('\0'): this->output->write(start, static_cast(end - start)); return; case _PT('<'): this->output->write(start, static_cast(end - start)); *this->output << _PT("<"); end++; start = end; break; case _PT('&'): this->output->write(start, static_cast(end - start)); *this->output << _PT("&"); end++; start = end; break; case _PT('\''): this->output->write(start, static_cast(end - start)); *this->output << _PT("'"); end++; start = end; break; case _PT('"'): this->output->write(start, static_cast(end - start)); *this->output << _PT("""); end++; start = end; break; case _PT(']'): if ((*(end + 1) == _PT(']')) && (*(end + 2) == _PT('>'))) { this->output->write(start, static_cast(end - start)); *this->output << _PT("]]>"); end += 3; start = end; } else { end++; } break; default: end++; break; } } } }