/* * 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 SpiffReader.cpp * Implementation of SpiffReader. */ #include #include #include #include #include #include #include #include #include #include #include #include #include // atoi #if (URI_VER_MINOR < 3) # uriparser 0.3.0 or later is required #endif namespace Spiff { /// @cond DOXYGEN_NON_API /** * D object for SpiffReader. */ class SpiffReaderPrivate { friend class SpiffReader; friend class SpiffExtensionReader; private: SpiffStack * stack; ///< Element stack SpiffProps * props; ///< Playlist properties slot SpiffTrack * track; ///< Track slot int version; ///< XSPF version XML_Parser parser; ///< Expat parser handle SpiffReaderCallback * callback; ///< Reader callback std::basic_string accum; ///< Element content accumulator std::basic_string lastRelValue; ///< Value of the previous "rel" attribute SpiffExtensionReader * extensionReader; ///< Current extension reader SpiffExtensionReaderFactory * extensionReaderFactory; ///< Extension reader factory int errorCode; ///< Error code int errorLine; ///< Number of the line that caused an error std::basic_string errorText; ///< Detailed error info bool insideExtension; ///< Flag wether the parser is inside an extension element bool firstPlaylistAnnotation; ///< First annotation in playlist flag bool firstPlaylistAttribution; ///< First attributation in playlist flag bool firstPlaylistCreator; ///< First creator in playlist flag bool firstPlaylistDate; ///< First date in playlist flag bool firstPlaylistIdentifier; ///< First identifier in playlist flag bool firstPlaylistImage; ///< First image in playlist flag bool firstPlaylistInfo; ///< First info in playlist flag bool firstPlaylistLicense; ///< First license in playlist flag bool firstPlaylistLocation; ///< First location in playlist flag bool firstPlaylistTitle; ///< First title in playlist flag bool firstPlaylistTrackList; ///< First trackList in playlist flag bool firstTrackTitle; ///< First title in track flag bool firstTrackCreator; ///< First creator in track flag bool firstTrackAnnotation; ///< First annotation in track flag bool firstTrackInfo; ///< First info in track flag bool firstTrackImage; ///< First image in track flag bool firstTrackAlbum; ///< First album in track flag bool firstTrackTrackNum; ///< First trackNum in track flag bool firstTrackDuration; ///< First duration in track flag bool firstTrack; ///< First track flag /** * Creates a new D object. * * @param handlerFactory Factory used to create handlers */ SpiffReaderPrivate(SpiffExtensionReaderFactory * handlerFactory) : stack(new SpiffStack()), props(NULL), track(NULL), version(-1), callback(callback), extensionReader(NULL), extensionReaderFactory(handlerFactory), errorCode(SPIFF_READER_SUCCESS), errorLine(-1), insideExtension(false), firstPlaylistAnnotation(true), firstPlaylistAttribution(true), firstPlaylistCreator(true), firstPlaylistDate(true), firstPlaylistIdentifier(true), firstPlaylistImage(true), firstPlaylistInfo(true), firstPlaylistLicense(true), firstPlaylistLocation(true), firstPlaylistTitle(true), firstPlaylistTrackList(true), firstTrackTitle(true), firstTrackCreator(true), firstTrackAnnotation(true), firstTrackInfo(true), firstTrackImage(true), firstTrackAlbum(true), firstTrackTrackNum(true), firstTrackDuration(true), firstTrack(true) { } /** * Copy constructor. * * @param source Source to copy from */ SpiffReaderPrivate(const SpiffReaderPrivate & source) : stack(new SpiffStack(*(source.stack))), props((source.props != NULL) ? new SpiffProps(*(source.props)) : NULL), track((source.track != NULL) ? new SpiffTrack(*(source.track)) : NULL), version(source.version), callback(source.callback), extensionReader((source.extensionReader != NULL) ? source.extensionReader->createBrother() : NULL), extensionReaderFactory(source.extensionReaderFactory), errorCode(source.errorCode), errorLine(source.errorLine), insideExtension(source.insideExtension), firstPlaylistAnnotation(source.firstPlaylistAnnotation), firstPlaylistAttribution(source.firstPlaylistAttribution), firstPlaylistCreator(source.firstPlaylistCreator), firstPlaylistDate(source.firstPlaylistDate), firstPlaylistIdentifier(source.firstPlaylistIdentifier), firstPlaylistImage(source.firstPlaylistImage), firstPlaylistInfo(source.firstPlaylistInfo), firstPlaylistLicense(source.firstPlaylistLicense), firstPlaylistLocation(source.firstPlaylistLocation), firstPlaylistTitle(source.firstPlaylistTitle), firstPlaylistTrackList(source.firstPlaylistTrackList), firstTrackTitle(source.firstTrackTitle), firstTrackCreator(source.firstTrackCreator), firstTrackAnnotation(source.firstTrackAnnotation), firstTrackInfo(source.firstTrackInfo), firstTrackImage(source.firstTrackImage), firstTrackAlbum(source.firstTrackAlbum), firstTrackTrackNum(source.firstTrackTrackNum), firstTrackDuration(source.firstTrackDuration), firstTrack(source.firstTrack) { } /** * Assignment operator. * * @param source Source to copy from */ SpiffReaderPrivate & operator=(const SpiffReaderPrivate & source) { if (this != &source) { // stack delete this->stack; this->stack = new SpiffStack(*(source.stack)); // props if (this->props != NULL) { delete this->props; } this->props = (source.props != NULL) ? new SpiffProps(*(source.props)) : NULL; // props if (this->track != NULL) { delete this->track; } this->track = (source.track != NULL) ? new SpiffTrack(*(source.track)) : NULL; this->version = source.version; this->callback = source.callback; // extension reader if (this->extensionReader != NULL) { delete this->track; } this->extensionReader = (source.extensionReader != NULL) ? source.extensionReader->createBrother() : NULL; this->extensionReaderFactory = source.extensionReaderFactory; this->errorCode = source.errorCode; this->errorLine = source.errorLine; this->insideExtension = source.insideExtension; this->firstPlaylistAnnotation = source.firstPlaylistAnnotation; this->firstPlaylistAttribution = source.firstPlaylistAttribution; this->firstPlaylistCreator = source.firstPlaylistCreator; this->firstPlaylistDate = source.firstPlaylistDate; this->firstPlaylistIdentifier = source.firstPlaylistIdentifier; this->firstPlaylistImage = source.firstPlaylistImage; this->firstPlaylistInfo = source.firstPlaylistInfo; this->firstPlaylistLicense = source.firstPlaylistLicense; this->firstPlaylistLocation = source.firstPlaylistLocation; this->firstPlaylistTitle = source.firstPlaylistTitle; this->firstPlaylistTrackList = source.firstPlaylistTrackList; this->firstTrackTitle = source.firstTrackTitle; this->firstTrackCreator = source.firstTrackCreator; this->firstTrackAnnotation = source.firstTrackAnnotation; this->firstTrackInfo = source.firstTrackInfo; this->firstTrackImage = source.firstTrackImage; this->firstTrackAlbum = source.firstTrackAlbum; this->firstTrackTrackNum = source.firstTrackTrackNum; this->firstTrackDuration = source.firstTrackDuration; this->firstTrack = source.firstTrack; } return *this; } /** * Destroys this D object. */ ~SpiffReaderPrivate() { if (this->stack != NULL) { delete this->stack; } if (this->props != NULL) { delete this->props; } if (this->track != NULL) { delete this->track; } if (this->extensionReader != NULL) { delete this->extensionReader; } } }; /// @endcond SpiffReader::SpiffReader(SpiffExtensionReaderFactory * handlerFactory) : d(new SpiffReaderPrivate(handlerFactory)) { } void SpiffReader::makeReusable() { // Reset everything but the error state this->d->stack->clear(); if (this->d->props != NULL) { delete this->d->props; this->d->props = NULL; } if (this->d->track != NULL) { delete this->d->track; this->d->track = NULL; } this->d->callback = NULL; this->d->accum.clear(); this->d->lastRelValue.clear(); this->d->firstPlaylistAnnotation = true; this->d->firstPlaylistAttribution = true; this->d->firstPlaylistCreator = true; this->d->firstPlaylistDate = true; this->d->firstPlaylistIdentifier = true; this->d->firstPlaylistImage = true; this->d->firstPlaylistInfo = true; this->d->firstPlaylistLicense = true; this->d->firstPlaylistLocation = true; this->d->firstPlaylistTitle = true; this->d->firstPlaylistTrackList = true; this->d->firstTrackTitle = true; this->d->firstTrackCreator = true; this->d->firstTrackAnnotation = true; this->d->firstTrackInfo = true; this->d->firstTrackImage = true; this->d->firstTrackAlbum = true; this->d->firstTrackTrackNum = true; this->d->firstTrackDuration = true; this->d->firstTrack = true; this->d->insideExtension = false; this->d->version = -1; if (this->d->extensionReader != NULL) { delete this->d->extensionReader; this->d->extensionReader = NULL; } } SpiffReader::SpiffReader(const SpiffReader & source) : d(new SpiffReaderPrivate(*(source.d))) { } SpiffReader & SpiffReader::operator=(const SpiffReader & source) { if (this != &source) { *(this->d) = *(source.d); } return *this; } SpiffReader::~SpiffReader() { delete this->d; } void SpiffReader::onBeforeParse(SpiffReaderCallback * callback) { // Set callback, NULL is no problem this->d->callback = callback; clearError(); // Create parser this->d->parser = XML_ParserCreateNS(NULL, SPIFF_NS_SEP_CHAR); // Put class pointer into user data XML_SetUserData(this->d->parser, this); // Register handlers XML_SetElementHandler(this->d->parser, masterStart, masterEnd); XML_SetCharacterDataHandler(this->d->parser, masterCharacters); } void SpiffReader::onAfterParse() { XML_ParserFree(this->d->parser); makeReusable(); } int SpiffReader::parse(const XML_Char * filename, SpiffReaderCallback * callback) { // Backward compatibility return parseFile(filename, callback); } void SpiffReader::setExpatError() { const XML_Error expatCode = XML_GetErrorCode(this->d->parser); setError(SPIFF_READER_ERROR_EXPAT + static_cast(expatCode), SPIFF_READER_TEXT_ONE_EXPAT_ERROR, XML_ErrorString(expatCode)); } int SpiffReader::parseFile(const XML_Char * filename, SpiffReaderCallback * callback) { // Check filename if (filename == NULL) { setError(SPIFF_READER_ERROR_NO_INPUT, SPIFF_READER_TEXT_ZERO_FILENAME_NULL); return this->d->errorCode; } // Init onBeforeParse(callback); // Open file FILE * file = ::PORT_FOPEN(filename, _PT("r")); if (file == NULL) { setError(SPIFF_READER_ERROR_NO_INPUT, SPIFF_READER_TEXT_ONE_FILE_READING_ERROR, filename); return this->d->errorCode; } // Get filesize ::fseek(file, 0, SEEK_END); const long filesize = ::ftell(file); ::fseek(file, 0, SEEK_SET); // Read and parse file void * buffer; if (filesize > SPIFF_MAX_BLOCK_SIZE) { // In several blocks long sizeLeft = filesize; while (sizeLeft > 0) { const long blockSize = std::min(sizeLeft, SPIFF_MAX_BLOCK_SIZE); buffer = XML_GetBuffer(this->d->parser, blockSize); ::fread(buffer, 1, blockSize, file); sizeLeft -= blockSize; if (XML_ParseBuffer(this->d->parser, blockSize, sizeLeft == 0) == XML_STATUS_ERROR) { if (this->d->errorCode == SPIFF_READER_SUCCESS) { setExpatError(); } break; } } ::fclose(file); } else { // One single go buffer = XML_GetBuffer(this->d->parser, filesize); ::fread(buffer, 1, filesize, file); ::fclose(file); if (XML_ParseBuffer(this->d->parser, filesize, 1) == XML_STATUS_ERROR) { if (this->d->errorCode == SPIFF_READER_SUCCESS) { setExpatError(); } } } // Cleanup onAfterParse(); return this->d->errorCode; } int SpiffReader::parseMemory(const char * memory, int numBytes, SpiffReaderCallback * callback) { // Init onBeforeParse(callback); // Parse if (XML_Parse(this->d->parser, memory, numBytes, 1) == XML_STATUS_ERROR) { if (this->d->errorCode == SPIFF_READER_SUCCESS) { setExpatError(); } } // Cleanup onAfterParse(); return this->d->errorCode; } int SpiffReader::parseChunks(SpiffChunkCallback * inputCallback, SpiffReaderCallback * dataCallback) { // inputCallback // Init onBeforeParse(dataCallback); // Parse chunks in a loop for (;;) { // Ask callback for buffer size const int bufferByteSize = inputCallback->getMinimumBufferByteSize(); void * buffer = NULL; // init not needed // Create and fill buffer int bytesToParse = 0; if (bufferByteSize > 0) { buffer = XML_GetBuffer(this->d->parser, bufferByteSize); bytesToParse = inputCallback->fillBuffer(buffer); } // Parse chunk if (XML_ParseBuffer(this->d->parser, bytesToParse, bytesToParse == 0) == XML_STATUS_ERROR) { // Error if (this->d->errorCode == SPIFF_READER_SUCCESS) { setExpatError(); } break; } else { // Fine, continue? if (bytesToParse == 0) { break; } } } inputCallback->notifyStop(); // Cleanup onAfterParse(); return this->d->errorCode; } bool SpiffReader::handleStartOne(const XML_Char * fullName, const XML_Char ** atts) { // Check full root name if (::PORT_STRNCMP(fullName, SPIFF_NS_HOME, SPIFF_NS_HOME_LEN) || ::PORT_STRCMP(fullName + SPIFF_NS_HOME_LEN + 1, _PT("playlist"))) { setError(SPIFF_READER_ERROR_ELEMENT_FORBIDDEN, SPIFF_READER_TEXT_ONE_WRONG_ROOT_NAME, fullName); return false; } if (!handlePlaylistAttribs(atts)) { return false; } this->d->stack->push(TAG_PLAYLIST); this->d->props = new SpiffProps(); this->d->props->setVersion(this->d->version); return true; } bool SpiffReader::handleStartTwo(const XML_Char * fullName, const XML_Char ** atts) { // Check and skip namespace if (::PORT_STRNCMP(fullName, SPIFF_NS_HOME, SPIFF_NS_HOME_LEN)) { setError(SPIFF_READER_ERROR_ELEMENT_FORBIDDEN, SPIFF_READER_TEXT_ONE_ELEMENT_FORBIDDEN, fullName); return false; } const XML_Char * localName = fullName + SPIFF_NS_HOME_LEN + 1; switch (localName[0]) { case _PT('a'): switch (localName[1]) { case _PT('n'): if (::PORT_STRCMP(localName + 2, _PT("notation"))) { break; } if (!this->d->firstPlaylistAnnotation) { setError(SPIFF_READER_ERROR_ELEMENT_TOOMANY, SPIFF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(SPIFF_NS_HOME, _PT("annotation"))); return false; } else if (!handleNoAttribs(atts)) { return false; } else { this->d->firstPlaylistAnnotation = false; this->d->stack->push(TAG_PLAYLIST_ANNOTATION); return true; } break; case _PT('t'): if (::PORT_STRCMP(localName + 2, _PT("tribution"))) { break; } if (!this->d->firstPlaylistAttribution) { setError(SPIFF_READER_ERROR_ELEMENT_TOOMANY, SPIFF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(SPIFF_NS_HOME, _PT("attribution"))); return false; } else if (!handleNoAttribs(atts)) { return false; } else { this->d->firstPlaylistAttribution = false; this->d->stack->push(TAG_PLAYLIST_ATTRIBUTION); return true; } break; } break; case _PT('c'): if (::PORT_STRCMP(localName + 1, _PT("reator"))) { break; } if (!this->d->firstPlaylistCreator) { setError(SPIFF_READER_ERROR_ELEMENT_TOOMANY, SPIFF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(SPIFF_NS_HOME, _PT("creator"))); return false; } else if (!handleNoAttribs(atts)) { return false; } else { this->d->firstPlaylistCreator = false; this->d->stack->push(TAG_PLAYLIST_CREATOR); return true; } break; case _PT('d'): if (::PORT_STRCMP(localName + 1, _PT("ate"))) { break; } if (!this->d->firstPlaylistDate) { setError(SPIFF_READER_ERROR_ELEMENT_TOOMANY, SPIFF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(SPIFF_NS_HOME, _PT("date"))); return false; } else if (!handleNoAttribs(atts)) { return false; } else { this->d->firstPlaylistDate = false; this->d->stack->push(TAG_PLAYLIST_DATE); return true; } break; case _PT('e'): if (::PORT_STRCMP(localName + 1, _PT("xtension"))) { break; } // Tag only allowed in v1 if (this->d->version == 0) { setError(SPIFF_READER_ERROR_ELEMENT_FORBIDDEN, SPIFF_READER_TEXT_ONE_ELEMENT_FORBIDDEN_VERSION_ZERO, fullName); return false; } const XML_Char * applicationUri; if (handleExtensionAttribs(atts, &applicationUri)) { this->d->insideExtension = true; // Create suitable handler if (this->d->extensionReaderFactory != NULL) { this->d->extensionReader = this->d->extensionReaderFactory ->newPlaylistExtensionReader(applicationUri, this); } if (this->d->extensionReader == NULL) { this->d->extensionReader = new SpiffSkipExtensionReader(this); } return this->d->extensionReader->handleExtensionStart(fullName, atts); } else { return false; } break; case _PT('i'): switch (localName[1]) { case _PT('d'): if (::PORT_STRCMP(localName + 2, _PT("entifier"))) { break; } if (!this->d->firstPlaylistIdentifier) { setError(SPIFF_READER_ERROR_ELEMENT_TOOMANY, SPIFF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(SPIFF_NS_HOME, _PT("identifier"))); return false; } else if (!handleNoAttribs(atts)) { return false; } else { this->d->firstPlaylistIdentifier = false; this->d->stack->push(TAG_PLAYLIST_IDENTIFIER); return true; } break; case _PT('m'): if (::PORT_STRCMP(localName + 2, _PT("age"))) { break; } if (!this->d->firstPlaylistImage) { setError(SPIFF_READER_ERROR_ELEMENT_TOOMANY, SPIFF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(SPIFF_NS_HOME, _PT("image"))); return false; } else if (!handleNoAttribs(atts)) { return false; } else { this->d->firstPlaylistImage = false; this->d->stack->push(TAG_PLAYLIST_IMAGE); return true; } break; case _PT('n'): if (::PORT_STRCMP(localName + 2, _PT("fo"))) { break; } if (!this->d->firstPlaylistInfo) { setError(SPIFF_READER_ERROR_ELEMENT_TOOMANY, SPIFF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(SPIFF_NS_HOME, _PT("info"))); return false; } else if (!handleNoAttribs(atts)) { return false; } else { this->d->firstPlaylistInfo = false; this->d->stack->push(TAG_PLAYLIST_INFO); return true; } break; } break; case _PT('l'): switch (localName[1]) { case _PT('i'): switch (localName[2]) { case _PT('c'): if (::PORT_STRCMP(localName + 3, _PT("ense"))) { break; } if (!this->d->firstPlaylistLicense) { setError(SPIFF_READER_ERROR_ELEMENT_TOOMANY, SPIFF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(SPIFF_NS_HOME, _PT("license"))); return false; } else if (!handleNoAttribs(atts)) { return false; } else { this->d->firstPlaylistLicense = false; this->d->stack->push(TAG_PLAYLIST_LICENSE); return true; } break; case _PT('n'): if (::PORT_STRCMP(localName + 3, _PT("k"))) { break; } if (handleMetaLinkAttribs(atts)) { this->d->stack->push(TAG_PLAYLIST_LINK); this->d->lastRelValue.assign(atts[1]); return true; } else { return false; } break; } break; case _PT('o'): if (::PORT_STRCMP(localName + 2, _PT("cation"))) { break; } if (!this->d->firstPlaylistLocation) { setError(SPIFF_READER_ERROR_ELEMENT_TOOMANY, SPIFF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(SPIFF_NS_HOME, _PT("location"))); return false; } else if (!handleNoAttribs(atts)) { return false; } else { this->d->firstPlaylistLocation = false; this->d->stack->push(TAG_PLAYLIST_LOCATION); return true; } break; } break; case _PT('m'): if (::PORT_STRCMP(localName + 1, _PT("eta"))) { break; } if (handleMetaLinkAttribs(atts)) { this->d->stack->push(TAG_PLAYLIST_META); this->d->lastRelValue.assign(atts[1]); return true; } else { return false; } break; case _PT('t'): switch (localName[1]) { case _PT('i'): if (::PORT_STRCMP(localName + 2, _PT("tle"))) { break; } if (!this->d->firstPlaylistTitle) { setError(SPIFF_READER_ERROR_ELEMENT_TOOMANY, SPIFF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(SPIFF_NS_HOME, _PT("title"))); return false; } else if (!handleNoAttribs(atts)) { return false; } else { this->d->firstPlaylistTitle = false; this->d->stack->push(TAG_PLAYLIST_TITLE); return true; } break; case _PT('r'): if (::PORT_STRCMP(localName + 2, _PT("ackList"))) { break; } if (!this->d->firstPlaylistTrackList) { setError(SPIFF_READER_ERROR_ELEMENT_TOOMANY, SPIFF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(SPIFF_NS_HOME, _PT("trackList"))); return false; } else if (!handleNoAttribs(atts)) { return false; } else { this->d->firstPlaylistTrackList = false; this->d->stack->push(TAG_PLAYLIST_TRACKLIST); return true; } break; } break; } setError(SPIFF_READER_ERROR_ELEMENT_FORBIDDEN, SPIFF_READER_TEXT_ONE_ELEMENT_FORBIDDEN, fullName); return false; } bool SpiffReader::handleStartThree(const XML_Char * fullName, const XML_Char ** atts) { // Check and skip namespace if (::PORT_STRNCMP(fullName, SPIFF_NS_HOME, SPIFF_NS_HOME_LEN)) { setError(SPIFF_READER_ERROR_ELEMENT_FORBIDDEN, SPIFF_READER_TEXT_ONE_ELEMENT_FORBIDDEN, fullName); return false; } const XML_Char * localName = fullName + SPIFF_NS_HOME_LEN + 1; switch (this->d->stack->top()) { case TAG_PLAYLIST_ATTRIBUTION: switch (localName[0]) { case _PT('i'): if (::PORT_STRCMP(localName + 1, _PT("dentifier"))) { break; } if (!handleNoAttribs(atts)) { return false; } this->d->stack->push(TAG_PLAYLIST_ATTRIBUTION_IDENTIFIER); return true; case _PT('l'): if (::PORT_STRCMP(localName + 1, _PT("ocation"))) { break; } if (!handleNoAttribs(atts)) { return false; } this->d->stack->push(TAG_PLAYLIST_ATTRIBUTION_LOCATION); return true; } break; case TAG_PLAYLIST_TRACKLIST: if (::PORT_STRCMP(localName, _PT("track"))) { break; } if (!handleNoAttribs(atts)) { return false; } this->d->firstTrack = false; this->d->stack->push(TAG_PLAYLIST_TRACKLIST_TRACK); this->d->track = new SpiffTrack(); return true; } setError(SPIFF_READER_ERROR_ELEMENT_FORBIDDEN, SPIFF_READER_TEXT_ONE_ELEMENT_FORBIDDEN, fullName); return false; } bool SpiffReader::handleStartFour(const XML_Char * fullName, const XML_Char ** atts) { if (this->d->stack->top() != TAG_PLAYLIST_TRACKLIST_TRACK) { return false; } // Check and skip namespace if (::PORT_STRNCMP(fullName, SPIFF_NS_HOME, SPIFF_NS_HOME_LEN)) { setError(SPIFF_READER_ERROR_ELEMENT_FORBIDDEN, SPIFF_READER_TEXT_ONE_ELEMENT_FORBIDDEN, fullName); return false; } const XML_Char * localName = fullName + SPIFF_NS_HOME_LEN + 1; switch (localName[0]) { case _PT('a'): switch (localName[1]) { case _PT('l'): if (::PORT_STRCMP(localName + 2, _PT("bum"))) { break; } if (!this->d->firstTrackAlbum) { setError(SPIFF_READER_ERROR_ELEMENT_TOOMANY, SPIFF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(SPIFF_NS_HOME, _PT("album"))); return false; } else if (!handleNoAttribs(atts)) { return false; } else { this->d->firstTrackAlbum = false; this->d->stack->push(TAG_PLAYLIST_TRACKLIST_TRACK_ALBUM); return true; } break; case _PT('n'): if (::PORT_STRCMP(localName + 2, _PT("notation"))) { break; } if (!this->d->firstTrackAnnotation) { setError(SPIFF_READER_ERROR_ELEMENT_TOOMANY, SPIFF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(SPIFF_NS_HOME, _PT("annotation"))); return false; } else if (!handleNoAttribs(atts)) { return false; } else { this->d->firstTrackAnnotation = false; this->d->stack->push(TAG_PLAYLIST_TRACKLIST_TRACK_ANNOTATION); return true; } break; } break; case _PT('c'): if (::PORT_STRCMP(localName + 1, _PT("reator"))) { break; } if (!this->d->firstTrackCreator) { setError(SPIFF_READER_ERROR_ELEMENT_TOOMANY, SPIFF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(SPIFF_NS_HOME, _PT("creator"))); return false; } else if (!handleNoAttribs(atts)) { return false; } else { this->d->firstTrackCreator = false; this->d->stack->push(TAG_PLAYLIST_TRACKLIST_TRACK_CREATOR); return true; } break; case _PT('d'): if (::PORT_STRCMP(localName + 1, _PT("uration"))) { break; } if (!this->d->firstTrackDuration) { setError(SPIFF_READER_ERROR_ELEMENT_TOOMANY, SPIFF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(SPIFF_NS_HOME, _PT("duration"))); return false; } else if (!handleNoAttribs(atts)) { return false; } else { this->d->firstTrackDuration = false; this->d->stack->push(TAG_PLAYLIST_TRACKLIST_TRACK_DURATION); return true; } break; case _PT('e'): if (::PORT_STRCMP(localName + 1, _PT("xtension"))) { break; } // Tag only allowed in v1 if (this->d->version == 0) { setError(SPIFF_READER_ERROR_ELEMENT_FORBIDDEN, SPIFF_READER_TEXT_ONE_ELEMENT_FORBIDDEN_VERSION_ZERO, fullName); return false; } const XML_Char * applicationUri; if (handleExtensionAttribs(atts, &applicationUri)) { this->d->insideExtension = true; // Create suitable handler if (this->d->extensionReaderFactory != NULL) { this->d->extensionReader = this->d->extensionReaderFactory ->newTrackExtensionReader(applicationUri, this); } if (this->d->extensionReader == NULL) { this->d->extensionReader = new SpiffSkipExtensionReader(this); } return this->d->extensionReader->handleExtensionStart(fullName, atts); } else { return false; } break; case _PT('i'): switch (localName[1]) { case _PT('d'): if (::PORT_STRCMP(localName + 2, _PT("entifier"))) { break; } if (!handleNoAttribs(atts)) { return false; } this->d->stack->push(TAG_PLAYLIST_TRACKLIST_TRACK_IDENTIFIER); return true; case _PT('m'): if (::PORT_STRCMP(localName + 2, _PT("age"))) { break; } if (!this->d->firstTrackImage) { setError(SPIFF_READER_ERROR_ELEMENT_TOOMANY, SPIFF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(SPIFF_NS_HOME, _PT("image"))); return false; } else if (!handleNoAttribs(atts)) { return false; } else { this->d->firstTrackImage = false; this->d->stack->push(TAG_PLAYLIST_TRACKLIST_TRACK_IMAGE); return true; } break; case _PT('n'): if (::PORT_STRCMP(localName + 2, _PT("fo"))) { break; } if (!this->d->firstTrackInfo) { setError(SPIFF_READER_ERROR_ELEMENT_TOOMANY, SPIFF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(SPIFF_NS_HOME, _PT("info"))); return false; } else if (!handleNoAttribs(atts)) { return false; } else { this->d->firstTrackInfo = false; this->d->stack->push(TAG_PLAYLIST_TRACKLIST_TRACK_INFO); return true; } break; } break; case _PT('l'): switch (localName[1]) { case _PT('i'): if (::PORT_STRCMP(localName + 2, _PT("nk"))) { break; } if (handleMetaLinkAttribs(atts)) { this->d->stack->push(TAG_PLAYLIST_TRACKLIST_TRACK_LINK); this->d->lastRelValue.assign(atts[1]); return true; } else { return false; } break; case _PT('o'): if (::PORT_STRCMP(localName + 2, _PT("cation"))) { break; } if (!handleNoAttribs(atts)) { return false; } this->d->stack->push(TAG_PLAYLIST_TRACKLIST_TRACK_LOCATION); return true; } break; case _PT('m'): if (::PORT_STRCMP(localName + 1, _PT("eta"))) { break; } if (handleMetaLinkAttribs(atts)) { this->d->stack->push(TAG_PLAYLIST_TRACKLIST_TRACK_META); this->d->lastRelValue.assign(atts[1]); return true; } else { return false; } break; case _PT('t'): switch (localName[1]) { case _PT('i'): if (::PORT_STRCMP(localName + 2, _PT("tle"))) { break; } if (!this->d->firstTrackTitle) { setError(SPIFF_READER_ERROR_ELEMENT_TOOMANY, SPIFF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(SPIFF_NS_HOME, _PT("title"))); return false; } else if (!handleNoAttribs(atts)) { return false; } else { this->d->firstTrackTitle = false; this->d->stack->push(TAG_PLAYLIST_TRACKLIST_TRACK_TITLE); return true; } break; case _PT('r'): if (::PORT_STRCMP(localName + 2, _PT("ackNum"))) { break; } if (!this->d->firstTrackTrackNum) { setError(SPIFF_READER_ERROR_ELEMENT_TOOMANY, SPIFF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(SPIFF_NS_HOME, _PT("trackNum"))); return false; } else if (!handleNoAttribs(atts)) { return false; } else { this->d->firstTrackTrackNum = false; this->d->stack->push(TAG_PLAYLIST_TRACKLIST_TRACK_TRACKNUM); return true; } break; } break; } setError(SPIFF_READER_ERROR_ELEMENT_FORBIDDEN, SPIFF_READER_TEXT_ONE_ELEMENT_FORBIDDEN, fullName); return false; } void SpiffReader::handleStart(const XML_Char * fullName, const XML_Char ** atts) { bool res = false; // Init not needed if (this->d->insideExtension) { res = this->d->extensionReader->handleExtensionStart(fullName, atts); } else { switch (this->d->stack->getSize() + 1) { case 1: res = handleStartOne(fullName, atts); break; case 2: res = handleStartTwo(fullName, atts); break; case 3: res = handleStartThree(fullName, atts); break; case 4: res = handleStartFour(fullName, atts); break; case 5: setError(SPIFF_READER_ERROR_ELEMENT_FORBIDDEN, SPIFF_READER_TEXT_ONE_ELEMENT_FORBIDDEN, fullName); res = false; break; } } if (!res) { stop(); } } bool SpiffReader::handleEndOne(const XML_Char * /*fullName*/) { if (this->d->firstPlaylistTrackList) { setError(SPIFF_READER_ERROR_ELEMENT_MISSING, SPIFF_READER_TEXT_ZERO_ELEMENT_MISSING(SPIFF_NS_HOME, _PT("trackList"))); return false; } // Call property callback if (this->d->callback != NULL) { // Note: setProps() deletes the props for us this->d->callback->setProps(this->d->props); } else { delete this->d->props; } this->d->props = NULL; return true; } bool SpiffReader::handleEndTwo(const XML_Char * /*fullName*/) { const XML_Char * const finalAccum = this->d->accum.c_str(); switch (this->d->stack->top()) { case TAG_PLAYLIST_ANNOTATION: this->d->props->giveAnnotation(finalAccum, true); break; /* case TAG_PLAYLIST_ATTRIBUTION: break; */ case TAG_PLAYLIST_CREATOR: this->d->props->giveCreator(finalAccum, true); break; case TAG_PLAYLIST_DATE: { SpiffDateTime * const dateTime = new SpiffDateTime; if (!SpiffReader::extractDateTime(finalAccum, dateTime)) { setError(SPIFF_READER_ERROR_CONTENT_INVALID, SPIFF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(SPIFF_NS_HOME, _PT("date"), _PT("dateTime"))); delete dateTime; return false; } else { this->d->props->giveDate(dateTime, false); } } break; /* case TAG_PLAYLIST_EXTENSION: break; */ case TAG_PLAYLIST_IDENTIFIER: if (!isURI(finalAccum)) { setError(SPIFF_READER_ERROR_CONTENT_INVALID, SPIFF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(SPIFF_NS_HOME, _PT("identifier"), _PT("URI"))); return false; } else { this->d->props->giveIdentifier(finalAccum, true); } break; case TAG_PLAYLIST_IMAGE: if (!isURI(finalAccum)) { setError(SPIFF_READER_ERROR_CONTENT_INVALID, SPIFF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(SPIFF_NS_HOME, _PT("image"), _PT("URI"))); return false; } else { this->d->props->giveImage(finalAccum, true); } break; case TAG_PLAYLIST_INFO: if (!isURI(finalAccum)) { setError(SPIFF_READER_ERROR_CONTENT_INVALID, SPIFF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(SPIFF_NS_HOME, _PT("info"), _PT("URI"))); return false; } else { this->d->props->giveInfo(finalAccum, true); } break; case TAG_PLAYLIST_LICENSE: if (!isURI(finalAccum)) { setError(SPIFF_READER_ERROR_CONTENT_INVALID, SPIFF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(SPIFF_NS_HOME, _PT("license"), _PT("URI"))); return false; } else { this->d->props->giveLicense(finalAccum, true); } break; case TAG_PLAYLIST_LINK: if (!isURI(finalAccum)) { setError(SPIFF_READER_ERROR_CONTENT_INVALID, SPIFF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(SPIFF_NS_HOME, _PT("link"), _PT("URI"))); return false; } else { this->d->props->giveAppendLink(this->d->lastRelValue.c_str(), true, finalAccum, true); } break; case TAG_PLAYLIST_LOCATION: if (!isURI(finalAccum)) { setError(SPIFF_READER_ERROR_CONTENT_INVALID, SPIFF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(SPIFF_NS_HOME, _PT("location"), _PT("URI"))); return false; } else { this->d->props->giveLocation(finalAccum, true); } break; case TAG_PLAYLIST_META: this->d->props->giveAppendMeta(this->d->lastRelValue.c_str(), true, finalAccum, true); break; case TAG_PLAYLIST_TITLE: this->d->props->giveTitle(finalAccum, true); break; case TAG_PLAYLIST_TRACKLIST: // Check if empty for v0 if ((this->d->version == 0) && (this->d->firstTrack)) { setError(SPIFF_READER_ERROR_ELEMENT_MISSING, SPIFF_READER_TEXT_ZERO_ELEMENT_MISSING_VERSION_ZERO(SPIFF_NS_HOME, _PT("track"))); return false; } break; } this->d->accum.clear(); return true; } bool SpiffReader::handleEndThree(const XML_Char * /*fullName*/) { const XML_Char * const finalAccum = this->d->accum.c_str(); switch (this->d->stack->top()) { case TAG_PLAYLIST_ATTRIBUTION_IDENTIFIER: if (!isURI(finalAccum)) { setError(SPIFF_READER_ERROR_CONTENT_INVALID, SPIFF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(SPIFF_NS_HOME, _PT("identifier"), _PT("URI"))); return false; } else { this->d->props->giveAppendAttributionIdentifier(finalAccum, true); } break; case TAG_PLAYLIST_ATTRIBUTION_LOCATION: if (!isURI(finalAccum)) { setError(SPIFF_READER_ERROR_CONTENT_INVALID, SPIFF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(SPIFF_NS_HOME, _PT("location"), _PT("URI"))); return false; } else { this->d->props->giveAppendAttributionLocation(finalAccum, true); } break; case TAG_PLAYLIST_TRACKLIST_TRACK: // Call track callback if (this->d->callback != NULL) { // Note: addTrack() deletes the track for us this->d->callback->addTrack(this->d->track); } else { delete this->d->track; } this->d->track = NULL; this->d->firstTrackTitle = true; this->d->firstTrackCreator = true; this->d->firstTrackAnnotation = true; this->d->firstTrackInfo = true; this->d->firstTrackImage = true; this->d->firstTrackAlbum = true; this->d->firstTrackTrackNum = true; this->d->firstTrackDuration = true; } this->d->accum.clear(); return true; } bool SpiffReader::handleEndFour(const XML_Char * /*fullName*/) { const XML_Char * const finalAccum = this->d->accum.c_str(); switch (this->d->stack->top()) { case TAG_PLAYLIST_TRACKLIST_TRACK_ALBUM: this->d->track->giveAlbum(finalAccum, true); break; case TAG_PLAYLIST_TRACKLIST_TRACK_ANNOTATION: this->d->track->giveAnnotation(finalAccum, true); break; case TAG_PLAYLIST_TRACKLIST_TRACK_CREATOR: this->d->track->giveCreator(finalAccum, true); break; case TAG_PLAYLIST_TRACKLIST_TRACK_DURATION: int duration; if (!SpiffReader::extractInteger(finalAccum, 0, &duration)) { setError(SPIFF_READER_ERROR_CONTENT_INVALID, SPIFF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(SPIFF_NS_HOME, _PT("duration"), _PT("unsigned integer"))); return false; } else { this->d->track->setDuration(duration); } break; /* case TAG_PLAYLIST_TRACKLIST_TRACK_EXTENSION: break; */ case TAG_PLAYLIST_TRACKLIST_TRACK_IDENTIFIER: if (!isURI(finalAccum)) { setError(SPIFF_READER_ERROR_CONTENT_INVALID, SPIFF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(SPIFF_NS_HOME, _PT("identifier"), _PT("URI"))); return false; } else { this->d->track->giveAppendIdentifier(finalAccum, true); } break; case TAG_PLAYLIST_TRACKLIST_TRACK_IMAGE: if (!isURI(finalAccum)) { setError(SPIFF_READER_ERROR_CONTENT_INVALID, SPIFF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(SPIFF_NS_HOME, _PT("image"), _PT("URI"))); return false; } else { this->d->track->giveImage(finalAccum, true); } break; case TAG_PLAYLIST_TRACKLIST_TRACK_INFO: if (!isURI(finalAccum)) { setError(SPIFF_READER_ERROR_CONTENT_INVALID, SPIFF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(SPIFF_NS_HOME, _PT("info"), _PT("URI"))); return false; } else { this->d->track->giveInfo(finalAccum, true); } break; case TAG_PLAYLIST_TRACKLIST_TRACK_LINK: if (!isURI(finalAccum)) { setError(SPIFF_READER_ERROR_CONTENT_INVALID, SPIFF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(SPIFF_NS_HOME, _PT("link"), _PT("URI"))); return false; } else { this->d->track->giveAppendLink(this->d->lastRelValue.c_str(), true, finalAccum, true); } break; case TAG_PLAYLIST_TRACKLIST_TRACK_LOCATION: if (!isURI(finalAccum)) { setError(SPIFF_READER_ERROR_CONTENT_INVALID, SPIFF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(SPIFF_NS_HOME, _PT("location"), _PT("URI"))); return false; } else { this->d->track->giveAppendLocation(finalAccum, true); } break; case TAG_PLAYLIST_TRACKLIST_TRACK_META: this->d->track->giveAppendMeta(this->d->lastRelValue.c_str(), true, finalAccum, true); break; case TAG_PLAYLIST_TRACKLIST_TRACK_TITLE: this->d->track->giveTitle(finalAccum, true); break; case TAG_PLAYLIST_TRACKLIST_TRACK_TRACKNUM: int trackNum; if (!SpiffReader::extractInteger(finalAccum, 1, &trackNum)) { setError(SPIFF_READER_ERROR_CONTENT_INVALID, SPIFF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(SPIFF_NS_HOME, _PT("trackNum"), _PT("unsigned integer greater zero"))); return false; } else { this->d->track->setTrackNum(trackNum); } break; } this->d->accum.clear(); return true; } void SpiffReader::handleEnd(const XML_Char * fullName) { bool extensionLeft = false; int pushBackTag = TAG_UNKNOWN; // Init not needed but kills the warning... if (this->d->insideExtension) { switch (this->d->stack->getSize()) { case 2: if (this->d->stack->top() == TAG_PLAYLIST_EXTENSION) { pushBackTag = TAG_PLAYLIST_EXTENSION; extensionLeft = true; } break; case 4: if (this->d->stack->top() == TAG_PLAYLIST_TRACKLIST_TRACK_EXTENSION) { pushBackTag = TAG_PLAYLIST_TRACKLIST_TRACK_EXTENSION; extensionLeft = true; } break; } if (!this->d->extensionReader->handleExtensionEnd(fullName)) { stop(); return; } if (extensionLeft) { this->d->insideExtension = false; // Add extension SpiffExtension * const extension = this->d->extensionReader->wrap(); if (extension != NULL) { if (pushBackTag == TAG_PLAYLIST_EXTENSION) { this->d->props->giveAppendExtension(extension, false); } else { this->d->track->giveAppendExtension(extension, false); } } // Destroy extension reader delete this->d->extensionReader; this->d->extensionReader = NULL; this->d->stack->push(pushBackTag); } else { return; } } bool res = false; switch (this->d->stack->getSize()) { case 1: res = handleEndOne(fullName); break; case 2: res = handleEndTwo(fullName); break; case 3: res = handleEndThree(fullName); break; case 4: res = handleEndFour(fullName); break; } if (!res) { stop(); return; } // Prevent popping twice // if (!extensionLeft) { this->d->stack->pop(); // } } void SpiffReader::handleCharacters(const XML_Char * s, int len) { if (this->d->insideExtension) { if (!this->d->extensionReader->handleExtensionCharacters(s, len)) { stop(); } return; } switch (this->d->stack->getSize()) { case 1: // Must be all whitespace if (!isWhiteSpace(s, len)) { setError(SPIFF_READER_ERROR_CONTENT_INVALID, SPIFF_READER_TEXT_ZERO_TEXT_FORBIDDEN(SPIFF_NS_HOME, _PT("playlist"))); stop(); return; } break; case 2: switch (this->d->stack->top()) { case TAG_PLAYLIST_TRACKLIST: // Must be all whitespace if (!isWhiteSpace(s, len)) { setError(SPIFF_READER_ERROR_CONTENT_INVALID, SPIFF_READER_TEXT_ZERO_TEXT_FORBIDDEN(SPIFF_NS_HOME, _PT("trackList"))); stop(); return; } break; case TAG_PLAYLIST_ATTRIBUTION: // Must be all whitespace if (!isWhiteSpace(s, len)) { setError(SPIFF_READER_ERROR_CONTENT_INVALID, SPIFF_READER_TEXT_ZERO_TEXT_FORBIDDEN(SPIFF_NS_HOME, _PT("attribution"))); stop(); return; } break; // Collapse elements // NOTE: whitespace in the middle of , // , and is illegal anyway // which is why we we only cut head and tail here case TAG_PLAYLIST_INFO: case TAG_PLAYLIST_LOCATION: case TAG_PLAYLIST_IDENTIFIER: case TAG_PLAYLIST_IMAGE: case TAG_PLAYLIST_DATE: case TAG_PLAYLIST_LICENSE: case TAG_PLAYLIST_LINK: case TAG_PLAYLIST_META: { // Append without white head and tail const XML_Char * blackSpaceStart = NULL; int blackSpaceNumChars = 0; cutOffWhiteSpace(s, len, blackSpaceStart, blackSpaceNumChars); if (blackSpaceNumChars > 0) { this->d->accum.append(blackSpaceStart, blackSpaceNumChars); } } break; default: // Append unmodified this->d->accum.append(s, len); } break; case 3: switch (this->d->stack->top()) { case TAG_PLAYLIST_ATTRIBUTION_IDENTIFIER: case TAG_PLAYLIST_ATTRIBUTION_LOCATION: { // Append without white head and tail const XML_Char * blackSpaceStart = NULL; int blackSpaceNumChars = 0; cutOffWhiteSpace(s, len, blackSpaceStart, blackSpaceNumChars); if (blackSpaceNumChars > 0) { this->d->accum.append(blackSpaceStart, blackSpaceNumChars); } } break; default: // == TAG_PLAYLIST_TRACKLIST_TRACK // Must be all whitespace if (!isWhiteSpace(s, len)) { setError(SPIFF_READER_ERROR_CONTENT_INVALID, SPIFF_READER_TEXT_ZERO_TEXT_FORBIDDEN(SPIFF_NS_HOME, _PT("track"))); stop(); return; } break; } break; case 4: switch (this->d->stack->top()) { case TAG_PLAYLIST_TRACKLIST_TRACK_LOCATION: case TAG_PLAYLIST_TRACKLIST_TRACK_IDENTIFIER: case TAG_PLAYLIST_TRACKLIST_TRACK_INFO: case TAG_PLAYLIST_TRACKLIST_TRACK_IMAGE: case TAG_PLAYLIST_TRACKLIST_TRACK_TRACKNUM: case TAG_PLAYLIST_TRACKLIST_TRACK_DURATION: case TAG_PLAYLIST_TRACKLIST_TRACK_LINK: case TAG_PLAYLIST_TRACKLIST_TRACK_META: { // Append without white head and tail const XML_Char * blackSpaceStart = NULL; int blackSpaceNumChars = 0; cutOffWhiteSpace(s, len, blackSpaceStart, blackSpaceNumChars); if (blackSpaceNumChars > 0) { this->d->accum.append(blackSpaceStart, blackSpaceNumChars); } } break; default: // Append unmodified this->d->accum.append(s, len); break; } } } void SpiffReader::stop() { // Remove handlers XML_SetElementHandler(this->d->parser, NULL, NULL); XML_SetCharacterDataHandler(this->d->parser, NULL); // Full stop XML_StopParser(this->d->parser, XML_FALSE); } bool SpiffReader::handlePlaylistAttribs(const XML_Char ** atts) { // No attributes? if (atts[0] == NULL) { setError(SPIFF_READER_ERROR_ATTRIBUTE_MISSING, SPIFF_READER_TEXT_ZERO_ATTRIBUTE_MISSING(_PT("version"))); return false; } // Is key "version"? if (::PORT_STRCMP(atts[0], _PT("version"))) { setError(SPIFF_READER_ERROR_ATTRIBUTE_FORBIDDEN, SPIFF_READER_TEXT_ONE_ATTRIBUTE_FORBIDDEN, atts[0]); return false; } // Check and set version int dummyVersion; if (!SpiffReader::extractInteger(atts[1], 0, &dummyVersion) || (dummyVersion > 1)) { setError(SPIFF_READER_ERROR_ATTRIBUTE_INVALID, SPIFF_READER_TEXT_ONE_WRONG_VERSION, atts[1]); return false; } this->d->version = dummyVersion; // More than this one attribute? if (atts[2] != NULL) { setError(SPIFF_READER_ERROR_ATTRIBUTE_FORBIDDEN, SPIFF_READER_TEXT_ONE_ATTRIBUTE_FORBIDDEN, atts[2]); return false; } return true; } bool SpiffReader::handleMetaLinkAttribs(const XML_Char ** atts) { // No attributes? if (atts[0] == NULL) { setError(SPIFF_READER_ERROR_ATTRIBUTE_MISSING, SPIFF_READER_TEXT_ZERO_ATTRIBUTE_MISSING(_PT("rel"))); return false; } // Is key "rel"? if (::PORT_STRCMP(atts[0], _PT("rel"))) { setError(SPIFF_READER_ERROR_ATTRIBUTE_FORBIDDEN, SPIFF_READER_TEXT_ONE_ATTRIBUTE_FORBIDDEN, atts[0]); return false; } // Check URI if (!isURI(atts[1])) { setError(SPIFF_READER_ERROR_ATTRIBUTE_INVALID, SPIFF_READER_TEXT_ZERO_WRONG_ATTRIBUTE_TYPE(_PT("rel"), _PT("URI"))); return false; } // More than this one attribute? if (atts[2] != NULL) { setError(SPIFF_READER_ERROR_ATTRIBUTE_FORBIDDEN, SPIFF_READER_TEXT_ONE_ATTRIBUTE_FORBIDDEN, atts[2]); return false; } return true; } bool SpiffReader::handleExtensionAttribs(const XML_Char ** atts, const XML_Char ** application) { // No attributes? if (atts[0] == NULL) { setError(SPIFF_READER_ERROR_ATTRIBUTE_MISSING, SPIFF_READER_TEXT_ZERO_ATTRIBUTE_MISSING(_PT("application"))); return false; } // Is key "application"? if (::PORT_STRCMP(atts[0], _PT("application"))) { setError(SPIFF_READER_ERROR_ATTRIBUTE_FORBIDDEN, SPIFF_READER_TEXT_ONE_ATTRIBUTE_FORBIDDEN, atts[0]); return false; } // Check URI if (!isURI(atts[1])) { setError(SPIFF_READER_ERROR_ATTRIBUTE_INVALID, SPIFF_READER_TEXT_ZERO_WRONG_ATTRIBUTE_TYPE(_PT("application"), _PT("URI"))); return false; } // More than this one attribute? if (atts[2] != NULL) { setError(SPIFF_READER_ERROR_ATTRIBUTE_FORBIDDEN, SPIFF_READER_TEXT_ONE_ATTRIBUTE_FORBIDDEN, atts[2]); return false; } // Extract URI if (application != NULL) { *application = atts[1]; } return true; } bool SpiffReader::handleNoAttribs(const XML_Char ** atts) { // No attributes? if (atts[0] != NULL) { setError(SPIFF_READER_ERROR_ATTRIBUTE_FORBIDDEN, SPIFF_READER_TEXT_ONE_ATTRIBUTE_FORBIDDEN, atts[0]); return false; } return true; } void SpiffReader::setError(int code, const XML_Char * text) { this->d->errorCode = code; this->d->errorText.assign(text); this->d->errorLine = XML_GetCurrentLineNumber(this->d->parser); /* TODO this->d->errorColumn = XML_GetCurrentColumnNumber(this->d->parser); */ } void SpiffReader::setError(int code, const XML_Char * format, const XML_Char * param) { this->d->errorCode = code; if (param == NULL) { this->d->errorText.assign(format); } else { const size_t charCount = ::PORT_STRLEN(format) + ::PORT_STRLEN(param) + 1; XML_Char * const final = new XML_Char[charCount]; ::PORT_SNPRINTF(final, charCount, format, param); this->d->errorText.assign(final); delete [] final; } this->d->errorLine = XML_GetCurrentLineNumber(this->d->parser); } void SpiffReader::clearError() { this->d->errorCode = SPIFF_READER_SUCCESS; this->d->errorLine = -1; this->d->errorText.clear(); } const XML_Char * SpiffReader::getErrorText() { return this->d->errorText.c_str(); } int SpiffReader::getErrorLine() { return this->d->errorLine; } /*static*/ inline void SpiffReader::masterStart(void * userData, const XML_Char * fullName, const XML_Char ** atts) { SpiffReader * const parser = reinterpret_cast(userData); parser->handleStart(fullName, atts); } /*static*/ inline void SpiffReader::masterEnd(void * userData, const XML_Char * fullName) { SpiffReader * const parser = reinterpret_cast(userData); parser->handleEnd(fullName); } /*static*/ inline void SpiffReader::masterCharacters(void * userData, const XML_Char * s, int len) { SpiffReader * const parser = reinterpret_cast(userData); parser->handleCharacters(s, len); } /*static*/ bool SpiffReader::isURI(const XML_Char * text) { #ifdef UNICODE UriParserStateW stateW; UriUriW uriW; stateW.uri = &uriW; const int res = uriParseUriW(&stateW, text); uriFreeUriMembersW(&uriW); #else UriParserStateA stateA; UriUriA uriA; stateA.uri = &uriA; const int res = uriParseUriA(&stateA, text); uriFreeUriMembersA(&uriA); #endif return (res == 0); } /*static*/ bool SpiffReader::extractInteger(const XML_Char * text, int inclusiveMinimum, int * output) { int & number = *output; number = ::PORT_ATOI(text); if ((number < inclusiveMinimum) || ((number == 0) && ::PORT_STRCMP(text, _PT("0")))) { return false; } else { return true; } } /*static*/ bool SpiffReader::extractDateTime(const XML_Char * text, SpiffDateTime * output) { // http://www.w3.org/TR/xmlschema-2/#dateTime-lexical-representation // '-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)? // '-'? const bool leadingMinus = (*text == _PT('-')); if (leadingMinus) { text++; } // yyyy if ((::PORT_STRNCMP(text, _PT("0001"), 4) < 0) || (::PORT_STRNCMP(_PT("9999"), text, 4) < 0)) { return false; } const int year = Toolbox::PORT_ANTOI(text, 4); output->setYear(year); text += 4; // '-' mm if ((::PORT_STRNCMP(text, _PT("-01"), 3) < 0) || (::PORT_STRNCMP(_PT("-12"), text, 3) < 0)) { return false; } const int month = Toolbox::PORT_ANTOI(text + 1, 2); output->setMonth(month); text += 3; // '-' dd if ((::PORT_STRNCMP(text, _PT("-01"), 3) < 0) || (::PORT_STRNCMP(_PT("-31"), text, 3) < 0)) { return false; } const int day = Toolbox::PORT_ANTOI(text + 1, 2); output->setDay(day); text += 3; // Month specific day check switch (month) { case 2: switch (day) { case 31: case 30: return false; case 29: if (((year % 400) != 0) && (((year % 4) != 0) || ((year % 100) == 0))) { // Not a leap year return false; } break; case 28: break; } break; case 4: case 6: case 9: case 11: if (day > 30) { return false; } break; } // 'T' hh if ((::PORT_STRNCMP(text, _PT("T00"), 3) < 0) || (::PORT_STRNCMP(_PT("T23"), text, 3) < 0)) { return false; } output->setHour(Toolbox::PORT_ANTOI(text + 1, 2)); text += 3; // ':' mm if ((::PORT_STRNCMP(text, _PT(":00"), 3) < 0) || (::PORT_STRNCMP(_PT(":59"), text, 3) < 0)) { return false; } output->setMinutes(Toolbox::PORT_ANTOI(text + 1, 2)); text += 3; // ':' ss if ((::PORT_STRNCMP(text, _PT(":00"), 2) < 0) || (::PORT_STRNCMP(_PT(":59"), text, 2) < 0)) { return false; } output->setSeconds(Toolbox::PORT_ANTOI(text + 1, 2)); text += 3; // ('.' s+)? if (*text == _PT('.')) { text++; int counter = 0; while ((*text >= _PT('0')) && (*text <= _PT('9'))) { text++; counter++; } if (counter == 0) { return false; } else { // The fractional second string, if present, must not end in '0' if (*(text - 1) == _PT('0')) { return false; } } } // (zzzzzz)? := (('+' | '-') hh ':' mm) | 'Z' const XML_Char * const timeZoneStart = text; switch (*text) { case _PT('+'): case _PT('-'): { text++; if ((::PORT_STRNCMP(text, _PT("00"), 2) < 0) || (::PORT_STRNCMP(_PT("14"), text, 2) < 0)) { return false; } const int distHours = Toolbox::PORT_ANTOI(text, 2); output->setDistHours(distHours); text += 2; if ((::PORT_STRNCMP(text, _PT(":00"), 3) < 0) || (::PORT_STRNCMP(_PT(":59"), text, 3) < 0)) { return false; } const int distMinutes = Toolbox::PORT_ANTOI(text + 1, 2); output->setDistMinutes(distMinutes); if ((distHours == 14) && (distMinutes != 0)) { return false; } text += 3; if (*text != _PT('\0')) { return false; } if (*timeZoneStart == _PT('-')) { output->setDistHours(-distHours); output->setDistMinutes(-distMinutes); } } break; case _PT('Z'): text++; if (*text != _PT('\0')) { return false; } // NO BREAK case _PT('\0'): output->setDistHours(0); output->setDistMinutes(0); break; default: return false; } return true; } /*static*/ bool SpiffReader::isWhiteSpace(const XML_Char * text, int numChars) { if ((text == NULL) || (numChars < 1)) { return true; } const XML_Char * walk = text; do { switch (*walk) { case _PT('\0'): return true; case _PT(' '): case _PT('\t'): case _PT('\x0a'): case _PT('\x0d'): walk++; break; default: return false; } } while (walk - text < numChars); return true; } /*static*/ void SpiffReader::cutOffWhiteSpace(const XML_Char * input, int inputNumChars, const XML_Char * & blackSpaceStart, int & blackSpaceNumChars) { if ((input == NULL) || (inputNumChars < 1)) { blackSpaceStart = NULL; blackSpaceNumChars = 0; return; } const XML_Char * walk = input; const XML_Char * firstBlackChar = NULL; const XML_Char * lastBlackChar = NULL; do { switch (*walk) { case _PT(' '): case _PT('\t'): case _PT('\x0a'): case _PT('\x0d'): break; default: if (firstBlackChar == NULL) { firstBlackChar = walk; } lastBlackChar = walk; break; } walk++; } while (walk - input < inputNumChars); if (firstBlackChar == NULL) { // No black space blackSpaceStart = walk; // Right after the string blackSpaceNumChars = 0; } else { // Back space found blackSpaceStart = firstBlackChar; blackSpaceNumChars = static_cast(lastBlackChar - firstBlackChar) + 1; } } SpiffStack * SpiffReader::getStack() { return this->d->stack; } }