/* * Copyright (C) 2013 Open Education Foundation * * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour * l'Education Numérique en Afrique (GIP ENA) * * This file is part of OpenBoard. * * OpenBoard is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License, * with a specific linking exception for the OpenSSL project's * "OpenSSL" library (or with modified versions of it that use the * same license as the "OpenSSL" library). * * OpenBoard is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenBoard. If not, see . */ #include #include #include #include #include #include #include #include "Parser.h" #include "Object.h" #include "Exception.h" #include "Utils.h" #include "core/memcheck.h" using namespace merge_lib; using namespace std; const std::string Parser::WHITESPACES(" \t\f\v\n\r"); const std::string Parser::DELIMETERS("()<>{}/%]["); const std::string Parser::NUMBERS("0123456789"); const std::string Parser::WHITESPACES_AND_DELIMETERS = Parser::WHITESPACES + Parser::DELIMETERS; Document * Parser::parseDocument(const char * fileName) { _document = new Document(fileName); try { _createObjectTree(fileName); _createDocument(fileName); } catch( std::exception &) { _clearParser(); delete _document; _document = NULL; throw; } return _document; } void Parser::_retrieveAllPages(Object * objectWithKids) { std::string & objectContent = objectWithKids->getObjectContent(); unsigned int startOfKids = objectContent.find("/Kids"); unsigned int endOfKids = objectContent.find("]", startOfKids); if( ((int)startOfKids == -1) && ((int)objectContent.find("/Page") != -1) ) { unsigned int numberOfPages = _document->_pages.size() + 1; Page * newPage = new Page(numberOfPages); newPage->_root = objectWithKids; _document->_pages.insert(std::pair(numberOfPages, newPage)); return; } const std::vector & kids = objectWithKids->getSortedByPositionChildren(startOfKids, endOfKids); for(size_t i(0); i < kids.size(); ++i) { _retrieveAllPages(kids[i]); } } void Parser::_createDocument(const char * docName) { Q_UNUSED(docName); _document->_root = _root; std::string & rootContent = _root->getObjectContent(); unsigned int startOfPages = rootContent.find("/Pages"); if((int)startOfPages == -1) throw Exception("Some document is wrong"); unsigned int endOfPages = rootContent.find("R", startOfPages); std::vector objectWithKids = _root->getChildrenByBounds(startOfPages, endOfPages); if(objectWithKids.size() != 1) throw Exception("Some document is wrong"); _retrieveAllPages(objectWithKids[0]); _root->retrieveMaxObjectNumber(_document->_maxObjectNumber); _clearParser(); } void Parser::_clearParser() { _root = 0; _fileContent.clear(); _fileContent.reserve(); _objects.clear(); } void Parser::_getFileContent(const char * fileName) { ifstream pdfFile; pdfFile.open (fileName, ios::binary ); if (pdfFile.fail()) { stringstream errorMessage("File "); errorMessage << fileName << " is absent" << "\0"; throw Exception(errorMessage); } // get length of file: pdfFile.seekg (0, ios::end); int length = pdfFile.tellg(); pdfFile.seekg (0, ios::beg); _fileContent.resize(length); pdfFile.read(&_fileContent[0], length); // check version const char *header = "%PDF-1."; size_t verPos = _fileContent.find(header); if( verPos == 0 ) { verPos += strlen(header); char ver = _fileContent[verPos]; if( ver < '0' || ver > '4' ) { stringstream errorMsg; errorMsg<<" File with verion 1."<::const_iterator it(_objects.begin()); for(;it != _objects.end();it++) { delete (*it).second; } _objects.clear(); throw; } std::map::iterator objectsIterator; for ( objectsIterator = _objects.begin() ; objectsIterator != _objects.end(); objectsIterator++ ) { Object * currentObject = (*objectsIterator).second; _document->_allObjects.push_back(currentObject); //key - object number : value - positions in object content of this reference const std::map & refs = _getReferences(currentObject->getObjectContent()); std::map::const_iterator refsIterator = refs.begin(); for(; refsIterator != refs.end(); ++refsIterator) { if(_objects.count((*refsIterator).first)) currentObject->addChild(_objects[(*refsIterator).first], (*refsIterator).second); } } _root = _objects[rootObjectNumber]; } const std::map & Parser::_getReferences(const std::string & objectContent) { unsigned int currentPosition(0), startOfNextSearch(0); static std::map > searchResult; searchResult.clear(); unsigned int streamStart = objectContent.find("stream"); if((int)streamStart == -1) streamStart = objectContent.size(); while(startOfNextSearch < streamStart) { //try to find reference. reference example is 15 0 R startOfNextSearch = objectContent.find(" R", startOfNextSearch); currentPosition = startOfNextSearch; if((int)currentPosition != -1) { //check that next character of " R" is WHITESPACE. if(((int)WHITESPACES.find(objectContent[currentPosition + 2]) == -1) && ((int)DELIMETERS.find(objectContent[currentPosition + 2]) == -1) ) { //this is not reference. this is something looks like "0 0 0 RG" ++startOfNextSearch; continue; } //get previos symbol and check that it is a number unsigned int numberSearchCounter = _skipNumber(objectContent, --currentPosition); //previos symbol is not a number if(numberSearchCounter == currentPosition) { ++startOfNextSearch; continue; } else { currentPosition = numberSearchCounter; } bool isFound(false); //previos symbols should be whitespaces while((objectContent[currentPosition] == ' ') && --currentPosition) { isFound = true; } //previos symbol is not a whitespace if(!isFound) { ++startOfNextSearch; continue; } //check that this and may be previos symbols are a numbers numberSearchCounter = _skipNumber(objectContent, currentPosition); if(numberSearchCounter == currentPosition) { ++startOfNextSearch; continue; } unsigned int objectNumber = Utils::stringToInt(objectContent.substr(numberSearchCounter + 1, currentPosition - numberSearchCounter)); searchResult[objectNumber].push_back(numberSearchCounter + 1); ++startOfNextSearch; } else break; } return searchResult; } unsigned int Parser::_skipNumber(const std::string & str, unsigned int currentPosition) { unsigned int numberSearchCounter = currentPosition; while(((int)NUMBERS.find(str[numberSearchCounter]) != -1) && --numberSearchCounter) {} return numberSearchCounter; } void Parser::_readXRefAndCreateObjects() { unsigned int currentPostion = _getStartOfXrefWithRoot(); do { const std::string & currentToken = _getNextToken(currentPostion); if(currentToken != "xref") { throw Exception("Wrong xref in some document"); } unsigned int endOfLine = _getEndOfLineFromContent(currentPostion ); if(_countTokens(currentPostion, endOfLine) != 2) { throw Exception("Wrong xref in some document"); } //now we are reading the xref while(1) { Utils::stringToInt(_getNextToken(currentPostion)); unsigned int objectCount = Utils::stringToInt(_getNextToken(currentPostion)); for(unsigned int i(0); i < objectCount; i++) { unsigned long first; if(_countTokens(currentPostion, _getEndOfLineFromContent(currentPostion)) == 3) { first = Utils::stringToInt(_getNextToken(currentPostion)); Utils::stringToInt(_getNextToken(currentPostion)); const string & use = _getNextToken(currentPostion); if(!use.compare("n")) { unsigned int objectNumber; try { std::pair streamBounds; bool hasObjectStream; unsigned int generationNumber; const std::string content = _getObjectContent(first, objectNumber, generationNumber, streamBounds, hasObjectStream); if(!_objects.count(objectNumber)) { Object * newObject = new Object(objectNumber, generationNumber, content, _document->_documentName ,streamBounds, hasObjectStream); _objects[objectNumber] = newObject; } } catch(std::exception &) { } } } else { ; } ++currentPostion; } unsigned int previosPostion = currentPostion; const std::string & isTrailer = _getNextToken(currentPostion); std::string trailer("trailer"); if(isTrailer == trailer) { currentPostion -= trailer.size(); break; } else currentPostion = previosPostion; } } while(_readTrailerAndRterievePrev(currentPostion, currentPostion)); } unsigned int Parser::_getStartOfXrefWithRoot() { unsigned int leftBoundOfStartOfXref = _fileContent.rfind("startxref"); leftBoundOfStartOfXref = _fileContent.find_first_of(NUMBERS, leftBoundOfStartOfXref); unsigned int rightBoundOfStartOfXref = _fileContent.find_first_not_of(NUMBERS, leftBoundOfStartOfXref + 1); std::string startOfXref = _fileContent.substr(leftBoundOfStartOfXref, rightBoundOfStartOfXref - leftBoundOfStartOfXref); int integerStartOfXref = Utils::stringToInt(startOfXref); return integerStartOfXref; } unsigned int Parser::_getEndOfLineFromContent(unsigned int fromPosition) { fromPosition = _skipWhiteSpacesFromContent(fromPosition); unsigned int endOfLine = _fileContent.find_first_of("\n\r", fromPosition); endOfLine = _fileContent.find_last_of("\n\r", endOfLine); return endOfLine; } const std::pair & Parser::_getLineBounds(const std::string & str, unsigned int fromPosition) { static std::pair bounds; bounds.first = str.rfind('\n', fromPosition); if((int)bounds.first == -1) bounds.first = 0; bounds.second = str.find('\n', fromPosition); if((int)bounds.second == -1) bounds.second = str.size(); return bounds; } const std::string & Parser::_getNextToken(unsigned int & fromPosition) { fromPosition = _skipWhiteSpacesFromContent(fromPosition); unsigned int position = _fileContent.find_first_of(WHITESPACES, fromPosition); static std::string token; if(position > fromPosition) { unsigned int tokenSize = position - fromPosition; token.resize(tokenSize); memcpy(&token[0], &_fileContent[fromPosition], tokenSize); fromPosition = position; return token; } else { //TODO throw exception } token = ""; return token; } unsigned int Parser::_countTokens(unsigned int leftBound, unsigned int rightBount) { unsigned int position = _skipWhiteSpacesFromContent(leftBound); unsigned int tokensCount = 0; while (position < rightBount) { position = _fileContent.find_first_of(WHITESPACES, position); if ((int)position != -1) ++tokensCount; //start search from next symbol ++position; } return tokensCount; } unsigned int Parser::_skipWhiteSpaces(const std::string & str, unsigned int fromPosition) { unsigned int position = fromPosition; if((int)WHITESPACES.find(str[0]) != -1) position = str.find_first_not_of(WHITESPACES, position); return position; } unsigned int Parser::_skipWhiteSpacesFromContent(unsigned int fromPosition) { unsigned int position = fromPosition; if((int)WHITESPACES.find(_fileContent[position]) != -1) position = _fileContent.find_first_not_of(WHITESPACES, position);// + 1; return position; } const std::string & Parser::_getObjectContent(unsigned int objectPosition, unsigned int & objectNumber, unsigned int & generationNumber, std::pair & streamBounds, bool & hasObjectStream) { hasObjectStream = false; unsigned int currentPosition = objectPosition; std::string token = _getNextToken(currentPosition); // number of object objectNumber = Utils::stringToInt(token); token = _getNextToken(currentPosition); // generation number - not interesting generationNumber = Utils::stringToInt(token); token = Parser::getNextToken(_fileContent,currentPosition); if( token != "obj" ) { std::stringstream strOut; strOut<<"Wrong object in PDF, in position "<>streamEnd; streamEnd += beginOfStream; unsigned int streamEndBegin = _fileContent.find("endstream",streamEnd); if((int) streamEndBegin != -1 ) { endOfStream = streamEndBegin; } } } streamBounds.second = endOfStream; endOfContent = beginOfStream; hasObjectStream = true; } unsigned int contentSize = endOfContent - currentPosition; objectContent.resize(contentSize); memcpy(&objectContent[0], &_fileContent[currentPosition], contentSize); return objectContent; } unsigned int Parser::_readTrailerAndReturnRoot() { unsigned int startOfTrailer = Parser::findToken(_fileContent,"trailer", _getStartOfXrefWithRoot()); std::string rootStr("/Root"); unsigned int startOfRoot = Parser::findToken(_fileContent,rootStr.data(), startOfTrailer); if((int) startOfRoot == -1) { throw Exception("Cannot find Root object !"); } std::string encryptStr("/Encrypt"); if((int) Parser::findToken(_fileContent,encryptStr,startOfTrailer) != -1 ) { throw Exception("Encrypted PDF is not supported!"); } startOfRoot += rootStr.size()+1; //"/Root + ' ' unsigned int endOfRoot = startOfRoot; while((int)NUMBERS.find(_fileContent[endOfRoot++]) != -1) {} --endOfRoot; return Utils::stringToInt(_fileContent.substr(startOfRoot, endOfRoot - startOfRoot)); } unsigned int Parser::_readTrailerAndRterievePrev(const unsigned int startPositionForSearch, unsigned int & previosXref) { unsigned int startOfTrailer = Parser::findToken(_fileContent,"trailer", startPositionForSearch); if((int) startOfTrailer == -1 ) { throw Exception("Cannot find trailer!"); } unsigned int startOfPrev = _fileContent.find("Prev ", startOfTrailer); unsigned int startxref = _fileContent.find("startxref", startOfTrailer); if((int)startOfPrev == -1 || (startOfPrev > startxref)) return false; //"Prev "s length = 5 else startOfPrev += 5; unsigned int endOfPrev = startOfPrev; while((int)NUMBERS.find(_fileContent[endOfPrev++]) != -1) {} --endOfPrev; previosXref = Utils::stringToInt(_fileContent.substr(startOfPrev, endOfPrev - startOfPrev)); return true; } //Method finds the token from current position from string // It uses PDF whitespaces and delimeters to recognize // Returned string without begin/end spaces std::string Parser::getNextToken(const std::string &str, unsigned int &position) { if( position >= str.size() ) { return ""; } //skip first spaces size_t beg_pos = str.find_first_not_of(Parser::WHITESPACES,position); if ((int) beg_pos == -1 ) { // it is empty string! return ""; } size_t end_pos = str.find_first_of(Parser::WHITESPACES_AND_DELIMETERS,beg_pos); if ((int) end_pos == -1 ) { end_pos = str.size(); } position = end_pos; std::string out = str.substr(beg_pos,end_pos - beg_pos); Parser::trim(out); return out; } /** @brief getNextWord * * method finds and returns next word from the string * For example: " 1 0 R \n" will return "1" , then "0" then "R" */ bool Parser::getNextWord(std::string &out, const std::string &str, size_t &nextPosition, size_t *found) { if( found ) { *found = -1; } //trace("position = %d",position); if( nextPosition >= str.size() ) { return false; } //skip first spaces size_t beg_pos = str.find_first_not_of(Parser::WHITESPACES,nextPosition); if ((int) beg_pos == -1 ) { // it is empty string! return false; } if( found ) { *found = beg_pos; } size_t end_pos = str.find_first_of(Parser::WHITESPACES,beg_pos); if ((int) end_pos == -1 ) { end_pos = str.size(); } nextPosition = end_pos; out = str.substr(beg_pos,end_pos - beg_pos); Parser::trim(out); if( out.empty() ) { return false; } return true; } /** @brief trim * * @todo: document this function */ void Parser::trim(std::string &str) { std::string::size_type pos1 = str.find_first_not_of(WHITESPACES); std::string::size_type pos2 = str.find_last_not_of(WHITESPACES); str = str.substr((int)pos1 == -1 ? 0 : pos1, (int)pos2 == -1 ? str.length() - 1 : pos2 - pos1 + 1); } // Method tries to find the PDF token from the content // The token is "/L 12 0R" or /Length 123 std::string Parser::findTokenStr(const std::string &content, const std::string &pattern, size_t start, size_t &foundStart, size_t &foundEnd) { size_t cur_pos = Parser::findToken(content,pattern,start); if((int) cur_pos == -1 ) { return ""; } foundStart = cur_pos; cur_pos += pattern.size(); // then lets parse the content of remaining part size_t end_pos = content.find_first_of(Parser::DELIMETERS,cur_pos); if((int) end_pos == -1 ) { end_pos = content.size(); } std::string token = content.substr(cur_pos,end_pos-cur_pos); foundEnd = end_pos -1; return token; } // Method tries to find token in the string from specified position, // returns position of first occurent or npos if not found // It properly handles cases when content contains strings which // contains token but not euqal to it // Example: content "/Transparency/ ..." pattern "/Trans // will return npos. size_t Parser::findToken(const std::string &content, const std::string &keyword,size_t start) { size_t cur_pos = start; // lets find pattern first size_t foundStart = -1; size_t savedPos = 0; while( 1 ) { cur_pos = content.find(keyword,cur_pos); if((int) cur_pos == -1 ) { break; } savedPos = cur_pos; cur_pos += keyword.size(); if( cur_pos < content.size() ) { if((int) Parser::WHITESPACES.find(content[cur_pos]) != -1 || (int)Parser::DELIMETERS.find(content[cur_pos]) != -1 ) { foundStart = savedPos; break; } } else { foundStart = savedPos; // end of line is reached break; } } return foundStart; } // Method checks if token at current position can be a Name or it is not name but value // Example // /H /P /P 12 0 R // the tag /P can be a name (and a value also), while 12 cannot // start defines the position of token content bool Parser::tokenIsAName(const std::string &content, size_t start ) { std::string openBraces = "<[({"; bool found = false; while(1) { size_t foundNonWhite = content.find_first_not_of(Parser::WHITESPACES,start); size_t foundDelim = content.find_first_of(Parser::DELIMETERS,start); if( (int)foundNonWhite != -1 && (int)foundDelim != -1 ) { if( (foundNonWhite < foundDelim ) || ( (int)openBraces.find(content[foundDelim]) != -1) ) { if( found ) { return false; } else { return true; } } else { if( found ) { return true; } else { found = true; start = content.find_first_of(Parser::WHITESPACES_AND_DELIMETERS,foundDelim+1); } } } else { return true; } } } // Method tries to find token name in the string from specified position, // For example, the string contains /H /P /P 12 0 R. // If search for /P then it will return position of /P 12 0 R, not value of // /H /P size_t Parser::findTokenName(const std::string &content, const std::string &keyword,size_t start) { size_t cur_pos = start; // lets find pattern first size_t foundStart = -1; size_t savedPos = 0; std::string braces = "<[({"; while( 1 ) { cur_pos = content.find(keyword,cur_pos); if((int) cur_pos == -1 ) { break; } savedPos = cur_pos; cur_pos += keyword.size(); if( cur_pos < content.size() ) { if((int) Parser::WHITESPACES_AND_DELIMETERS.find(content[cur_pos]) != -1 ) { if( tokenIsAName(content,cur_pos ) ) { foundStart = savedPos; break; } } } else { foundStart = savedPos; // end of line is reached break; } } return foundStart; } unsigned int Parser::findEndOfElementContent(const std::string &content,unsigned int startOfPageElement) { unsigned int foundEnd = -1; std::stack delimStack; std::string endDelim = "/]>)}"; unsigned int curPos = startOfPageElement; std::string openDict("<"); std::string openArray("["); std::string delimeter = endDelim; delimStack.push(delimeter); //initial delimeter bool compensation = true; while(1) { unsigned int nonWhiteSpace = content.find_first_not_of(Parser::WHITESPACES,curPos); unsigned int foundDelimeter = content.find_first_of(delimeter,curPos); unsigned int foundOpenBrace = content.find("[",curPos); unsigned int foundOpenDict = content.find("<",curPos); if((int) foundDelimeter == -1 && (int)foundOpenBrace == -1 && (int)foundOpenDict == -1 ) { if( !delimStack.empty() ) { delimStack.pop(); } } else if( (foundDelimeter <= foundOpenBrace && foundDelimeter <= foundOpenDict ) ) { if( !delimStack.empty() ) { delimStack.pop(); } if( nonWhiteSpace == foundDelimeter && delimeter == endDelim ) { curPos = foundDelimeter; if(content[foundDelimeter] == '/' && compensation ) { curPos ++; compensation = false; } } else { compensation = false; if( delimeter == endDelim ) { curPos = foundDelimeter; } else { curPos = foundDelimeter + delimeter.size(); } } } else if( foundOpenBrace <= foundDelimeter && foundOpenBrace <= foundOpenDict ) { compensation = false; delimStack.push("]"); curPos = foundOpenBrace + openArray.size(); } else if( foundOpenDict <= foundDelimeter && foundOpenDict <= foundOpenBrace ) { compensation = false; delimStack.push(">"); curPos = foundOpenDict + openDict.size(); } if( delimStack.empty() ) { foundEnd = content.find_first_of(delimeter,curPos); if((int) foundEnd == -1 ) { foundEnd = curPos; } break; } delimeter = delimStack.top(); } return foundEnd; }