/* * MIT License * * Copyright(c) 2018 Jimmie Bergmann * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files(the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions : * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */ #include "Yaml.hpp" #include #include #include #include #include #include #include // Implementation access definitions. #define NODE_IMP static_cast(m_pImp) #define NODE_IMP_EXT(node) static_cast(node.m_pImp) #define TYPE_IMP static_cast(m_pImp)->m_pImp #define IT_IMP static_cast(m_pImp) namespace Yaml { class ReaderLine; // Exception message definitions. static const std::string g_ErrorInvalidCharacter = "Invalid character found."; static const std::string g_ErrorKeyMissing = "Missing key."; static const std::string g_ErrorKeyIncorrect = "Incorrect key."; static const std::string g_ErrorValueIncorrect = "Incorrect value."; static const std::string g_ErrorTabInOffset = "Tab found in offset."; static const std::string g_ErrorBlockSequenceNotAllowed = "Sequence entries are not allowed in this context."; static const std::string g_ErrorUnexpectedDocumentEnd = "Unexpected document end."; static const std::string g_ErrorDiffEntryNotAllowed = "Different entry is not allowed in this context."; static const std::string g_ErrorIncorrectOffset = "Incorrect offset."; static const std::string g_ErrorSequenceError = "Error in sequence node."; static const std::string g_ErrorCannotOpenFile = "Cannot open file."; static const std::string g_ErrorIndentation = "Space indentation is less than 2."; static const std::string g_ErrorInvalidBlockScalar = "Invalid block scalar."; static const std::string g_ErrorInvalidQuote = "Invalid quote."; static const std::string g_EmptyString = ""; static Yaml::Node g_NoneNode; // Global function definitions. Implemented at end of this source file. static std::string ExceptionMessage(const std::string & message, ReaderLine & line); static std::string ExceptionMessage(const std::string & message, ReaderLine & line, const size_t errorPos); static std::string ExceptionMessage(const std::string & message, const size_t errorLine, const size_t errorPos); static std::string ExceptionMessage(const std::string & message, const size_t errorLine, const std::string & data); static bool FindQuote(const std::string & input, size_t & start, size_t & end, size_t searchPos = 0); static size_t FindNotCited(const std::string & input, char token, size_t & preQuoteCount); static size_t FindNotCited(const std::string & input, char token); static bool ValidateQuote(const std::string & input); static void CopyNode(const Node & from, Node & to); static bool ShouldBeCited(const std::string & key); static void AddEscapeTokens(std::string & input, const std::string & tokens); static void RemoveAllEscapeTokens(std::string & input); // Exception implementations Exception::Exception(const std::string & message, const eType type) : std::runtime_error(message), m_Type(type) { } Exception::eType Exception::Type() const { return m_Type; } const char * Exception::Message() const { return what(); } InternalException::InternalException(const std::string & message) : Exception(message, InternalError) { } ParsingException::ParsingException(const std::string & message) : Exception(message, ParsingError) { } OperationException::OperationException(const std::string & message) : Exception(message, OperationError) { } class TypeImp { public: virtual ~TypeImp() { } virtual const std::string & GetData() const = 0; virtual bool SetData(const std::string & data) = 0; virtual size_t GetSize() const = 0; virtual Node * GetNode(const size_t index) = 0; virtual Node * GetNode(const std::string & key) = 0; virtual Node * Insert(const size_t index) = 0; virtual Node * PushFront() = 0; virtual Node * PushBack() = 0; virtual void Erase(const size_t index) = 0; virtual void Erase(const std::string & key) = 0; }; class SequenceImp : public TypeImp { public: ~SequenceImp() { for(auto it = m_Sequence.begin(); it != m_Sequence.end(); it++) { delete it->second; } } virtual const std::string & GetData() const { return g_EmptyString; } virtual bool SetData(const std::string & data) { return false; } virtual size_t GetSize() const { return m_Sequence.size(); } virtual Node * GetNode(const size_t index) { auto it = m_Sequence.find(index); if(it != m_Sequence.end()) { return it->second; } return nullptr; } virtual Node * GetNode(const std::string & key) { return nullptr; } virtual Node * Insert(const size_t index) { if(m_Sequence.size() == 0) { Node * pNode = new Node; m_Sequence.insert({0, pNode}); return pNode; } if(index >= m_Sequence.size()) { auto it = m_Sequence.end(); --it; Node * pNode = new Node; m_Sequence.insert({it->first, pNode}); return pNode; } auto it = m_Sequence.cbegin(); while(it != m_Sequence.cend()) { m_Sequence[it->first+1] = it->second; if(it->first == index) { break; } } Node * pNode = new Node; m_Sequence.insert({index, pNode}); return pNode; } virtual Node * PushFront() { for(auto it = m_Sequence.cbegin(); it != m_Sequence.cend(); it++) { m_Sequence[it->first+1] = it->second; } Node * pNode = new Node; m_Sequence.insert({0, pNode}); return pNode; } virtual Node * PushBack() { size_t index = 0; if(m_Sequence.size()) { auto it = m_Sequence.end(); --it; index = it->first + 1; } Node * pNode = new Node; m_Sequence.insert({index, pNode}); return pNode; } virtual void Erase(const size_t index) { auto it = m_Sequence.find(index); if(it == m_Sequence.end()) { return; } delete it->second; m_Sequence.erase(index); } virtual void Erase(const std::string & key) { } std::map m_Sequence; }; class MapImp : public TypeImp { public: ~MapImp() { for(auto it = m_Map.begin(); it != m_Map.end(); it++) { delete it->second; } } virtual const std::string & GetData() const { return g_EmptyString; } virtual bool SetData(const std::string & data) { return false; } virtual size_t GetSize() const { return m_Map.size(); } virtual Node * GetNode(const size_t index) { return nullptr; } virtual Node * GetNode(const std::string & key) { auto it = m_Map.find(key); if(it == m_Map.end()) { Node * pNode = new Node; m_Map.insert({key, pNode}); return pNode; } return it->second; } virtual Node * Insert(const size_t index) { return nullptr; } virtual Node * PushFront() { return nullptr; } virtual Node * PushBack() { return nullptr; } virtual void Erase(const size_t index) { } virtual void Erase(const std::string & key) { auto it = m_Map.find(key); if(it == m_Map.end()) { return; } delete it->second; m_Map.erase(key); } std::map m_Map; }; class ScalarImp : public TypeImp { public: ~ScalarImp() { } virtual const std::string & GetData() const { return m_Value; } virtual bool SetData(const std::string & data) { m_Value = data; return true; } virtual size_t GetSize() const { return 0; } virtual Node * GetNode(const size_t index) { return nullptr; } virtual Node * GetNode(const std::string & key) { return nullptr; } virtual Node * Insert(const size_t index) { return nullptr; } virtual Node * PushFront() { return nullptr; } virtual Node * PushBack() { return nullptr; } virtual void Erase(const size_t index) { } virtual void Erase(const std::string & key) { } std::string m_Value; }; // Node implementations. class NodeImp { public: NodeImp() : m_Type(Node::None), m_pImp(nullptr) { } ~NodeImp() { Clear(); } void Clear() { if(m_pImp != nullptr) { delete m_pImp; m_pImp = nullptr; } m_Type = Node::None; } void InitSequence() { if(m_Type != Node::SequenceType || m_pImp == nullptr) { if(m_pImp) { delete m_pImp; } m_pImp = new SequenceImp; m_Type = Node::SequenceType; } } void InitMap() { if(m_Type != Node::MapType || m_pImp == nullptr) { if(m_pImp) { delete m_pImp; } m_pImp = new MapImp; m_Type = Node::MapType; } } void InitScalar() { if(m_Type != Node::ScalarType || m_pImp == nullptr) { if(m_pImp) { delete m_pImp; } m_pImp = new ScalarImp; m_Type = Node::ScalarType; } } Node::eType m_Type; ///< Type of node. TypeImp * m_pImp; ///< Imp of type. }; // Iterator implementation class class IteratorImp { public: virtual ~IteratorImp() { } virtual Node::eType GetType() const = 0; virtual void InitBegin(SequenceImp * pSequenceImp) = 0; virtual void InitEnd(SequenceImp * pSequenceImp) = 0; virtual void InitBegin(MapImp * pMapImp) = 0; virtual void InitEnd(MapImp * pMapImp) = 0; }; class SequenceIteratorImp : public IteratorImp { public: virtual Node::eType GetType() const { return Node::SequenceType; } virtual void InitBegin(SequenceImp * pSequenceImp) { m_Iterator = pSequenceImp->m_Sequence.begin(); } virtual void InitEnd(SequenceImp * pSequenceImp) { m_Iterator = pSequenceImp->m_Sequence.end(); } virtual void InitBegin(MapImp * pMapImp) { } virtual void InitEnd(MapImp * pMapImp) { } void Copy(const SequenceIteratorImp & it) { m_Iterator = it.m_Iterator; } std::map::iterator m_Iterator; }; class MapIteratorImp : public IteratorImp { public: virtual Node::eType GetType() const { return Node::MapType; } virtual void InitBegin(SequenceImp * pSequenceImp) { } virtual void InitEnd(SequenceImp * pSequenceImp) { } virtual void InitBegin(MapImp * pMapImp) { m_Iterator = pMapImp->m_Map.begin(); } virtual void InitEnd(MapImp * pMapImp) { m_Iterator = pMapImp->m_Map.end(); } void Copy(const MapIteratorImp & it) { m_Iterator = it.m_Iterator; } std::map::iterator m_Iterator; }; class SequenceConstIteratorImp : public IteratorImp { public: virtual Node::eType GetType() const { return Node::SequenceType; } virtual void InitBegin(SequenceImp * pSequenceImp) { m_Iterator = pSequenceImp->m_Sequence.begin(); } virtual void InitEnd(SequenceImp * pSequenceImp) { m_Iterator = pSequenceImp->m_Sequence.end(); } virtual void InitBegin(MapImp * pMapImp) { } virtual void InitEnd(MapImp * pMapImp) { } void Copy(const SequenceConstIteratorImp & it) { m_Iterator = it.m_Iterator; } std::map::const_iterator m_Iterator; }; class MapConstIteratorImp : public IteratorImp { public: virtual Node::eType GetType() const { return Node::MapType; } virtual void InitBegin(SequenceImp * pSequenceImp) { } virtual void InitEnd(SequenceImp * pSequenceImp) { } virtual void InitBegin(MapImp * pMapImp) { m_Iterator = pMapImp->m_Map.begin(); } virtual void InitEnd(MapImp * pMapImp) { m_Iterator = pMapImp->m_Map.end(); } void Copy(const MapConstIteratorImp & it) { m_Iterator = it.m_Iterator; } std::map::const_iterator m_Iterator; }; // Iterator class Iterator::Iterator() : m_Type(None), m_pImp(nullptr) { } Iterator::~Iterator() { if(m_pImp) { switch(m_Type) { case SequenceType: delete static_cast(m_pImp); break; case MapType: delete static_cast(m_pImp); break; default: break; } } } Iterator::Iterator(const Iterator & it) : m_Type(None), m_pImp(nullptr) { *this = it; } Iterator & Iterator::operator = (const Iterator & it) { if(m_pImp) { switch(m_Type) { case SequenceType: delete static_cast(m_pImp); break; case MapType: delete static_cast(m_pImp); break; default: break; } m_pImp = nullptr; m_Type = None; } IteratorImp * pNewImp = nullptr; switch(it.m_Type) { case SequenceType: m_Type = SequenceType; pNewImp = new SequenceIteratorImp; static_cast(pNewImp)->m_Iterator = static_cast(it.m_pImp)->m_Iterator; break; case MapType: m_Type = MapType; pNewImp = new MapIteratorImp; static_cast(pNewImp)->m_Iterator = static_cast(it.m_pImp)->m_Iterator; break; default: break; } m_pImp = pNewImp; return *this; } std::pair Iterator::operator *() { switch(m_Type) { case SequenceType: return { g_EmptyString, *(static_cast(m_pImp)->m_Iterator->second)}; break; case MapType: return {static_cast(m_pImp)->m_Iterator->first, *(static_cast(m_pImp)->m_Iterator->second)}; break; default: break; } g_NoneNode.Clear(); return { g_EmptyString, g_NoneNode}; } Iterator & Iterator::operator ++ (int dummy) { switch(m_Type) { case SequenceType: static_cast(m_pImp)->m_Iterator++; break; case MapType: static_cast(m_pImp)->m_Iterator++; break; default: break; } return *this; } Iterator & Iterator::operator -- (int dummy) { switch(m_Type) { case SequenceType: static_cast(m_pImp)->m_Iterator--; break; case MapType: static_cast(m_pImp)->m_Iterator--; break; default: break; } return *this; } bool Iterator::operator == (const Iterator & it) { if(m_Type != it.m_Type) { return false; } switch(m_Type) { case SequenceType: return static_cast(m_pImp)->m_Iterator == static_cast(it.m_pImp)->m_Iterator; break; case MapType: return static_cast(m_pImp)->m_Iterator == static_cast(it.m_pImp)->m_Iterator; break; default: break; } return false; } bool Iterator::operator != (const Iterator & it) { return !(*this == it); } // Const Iterator class ConstIterator::ConstIterator() : m_Type(None), m_pImp(nullptr) { } ConstIterator::~ConstIterator() { if(m_pImp) { switch(m_Type) { case SequenceType: delete static_cast(m_pImp); break; case MapType: delete static_cast(m_pImp); break; default: break; } } } ConstIterator::ConstIterator(const ConstIterator & it) : m_Type(None), m_pImp(nullptr) { *this = it; } ConstIterator & ConstIterator::operator = (const ConstIterator & it) { if(m_pImp) { switch(m_Type) { case SequenceType: delete static_cast(m_pImp); break; case MapType: delete static_cast(m_pImp); break; default: break; } m_pImp = nullptr; m_Type = None; } IteratorImp * pNewImp = nullptr; switch(it.m_Type) { case SequenceType: m_Type = SequenceType; pNewImp = new SequenceConstIteratorImp; static_cast(pNewImp)->m_Iterator = static_cast(it.m_pImp)->m_Iterator; break; case MapType: m_Type = MapType; pNewImp = new MapConstIteratorImp; static_cast(pNewImp)->m_Iterator = static_cast(it.m_pImp)->m_Iterator; break; default: break; } m_pImp = pNewImp; return *this; } std::pair ConstIterator::operator *() { switch(m_Type) { case SequenceType: return { g_EmptyString, *(static_cast(m_pImp)->m_Iterator->second)}; break; case MapType: return {static_cast(m_pImp)->m_Iterator->first, *(static_cast(m_pImp)->m_Iterator->second)}; break; default: break; } g_NoneNode.Clear(); return { g_EmptyString, g_NoneNode}; } ConstIterator & ConstIterator::operator ++ (int dummy) { switch(m_Type) { case SequenceType: static_cast(m_pImp)->m_Iterator++; break; case MapType: static_cast(m_pImp)->m_Iterator++; break; default: break; } return *this; } ConstIterator & ConstIterator::operator -- (int dummy) { switch(m_Type) { case SequenceType: static_cast(m_pImp)->m_Iterator--; break; case MapType: static_cast(m_pImp)->m_Iterator--; break; default: break; } return *this; } bool ConstIterator::operator == (const ConstIterator & it) { if(m_Type != it.m_Type) { return false; } switch(m_Type) { case SequenceType: return static_cast(m_pImp)->m_Iterator == static_cast(it.m_pImp)->m_Iterator; break; case MapType: return static_cast(m_pImp)->m_Iterator == static_cast(it.m_pImp)->m_Iterator; break; default: break; } return false; } bool ConstIterator::operator != (const ConstIterator & it) { return !(*this == it); } // Node class Node::Node() : m_pImp(new NodeImp) { } Node::Node(const Node & node) : Node() { *this = node; } Node::Node(const std::string & value) : Node() { *this = value; } Node::Node(const char * value) : Node() { *this = value; } Node::~Node() { delete static_cast(m_pImp); } Node::eType Node::Type() const { return NODE_IMP->m_Type; } bool Node::IsNone() const { return NODE_IMP->m_Type == Node::None; } bool Node::IsSequence() const { return NODE_IMP->m_Type == Node::SequenceType; } bool Node::IsMap() const { return NODE_IMP->m_Type == Node::MapType; } bool Node::IsScalar() const { return NODE_IMP->m_Type == Node::ScalarType; } void Node::Clear() { NODE_IMP->Clear(); } size_t Node::Size() const { if(TYPE_IMP == nullptr) { return 0; } return TYPE_IMP->GetSize(); } Node & Node::Insert(const size_t index) { NODE_IMP->InitSequence(); return *TYPE_IMP->Insert(index); } Node & Node::PushFront() { NODE_IMP->InitSequence(); return *TYPE_IMP->PushFront(); } Node & Node::PushBack() { NODE_IMP->InitSequence(); return *TYPE_IMP->PushBack(); } Node & Node::operator[](const size_t index) { NODE_IMP->InitSequence(); Node * pNode = TYPE_IMP->GetNode(index); if(pNode == nullptr) { g_NoneNode.Clear(); return g_NoneNode; } return *pNode; } Node & Node::operator[](const std::string & key) { NODE_IMP->InitMap(); return *TYPE_IMP->GetNode(key); } void Node::Erase(const size_t index) { if(TYPE_IMP == nullptr || NODE_IMP->m_Type != Node::SequenceType) { return; } return TYPE_IMP->Erase(index); } void Node::Erase(const std::string & key) { if(TYPE_IMP == nullptr || NODE_IMP->m_Type != Node::MapType) { return; } return TYPE_IMP->Erase(key); } Node & Node::operator = (const Node & node) { NODE_IMP->Clear(); CopyNode(node, *this); return *this; } Node & Node::operator = (const std::string & value) { NODE_IMP->InitScalar(); TYPE_IMP->SetData(value); return *this; } Node & Node::operator = (const char * value) { NODE_IMP->InitScalar(); TYPE_IMP->SetData(value ? std::string(value) : ""); return *this; } Iterator Node::Begin() { Iterator it; if(TYPE_IMP != nullptr) { IteratorImp * pItImp = nullptr; switch(NODE_IMP->m_Type) { case Node::SequenceType: it.m_Type = Iterator::SequenceType; pItImp = new SequenceIteratorImp; pItImp->InitBegin(static_cast(TYPE_IMP)); break; case Node::MapType: it.m_Type = Iterator::MapType; pItImp = new MapIteratorImp; pItImp->InitBegin(static_cast(TYPE_IMP)); break; default: break; } it.m_pImp = pItImp; } return it; } ConstIterator Node::Begin() const { ConstIterator it; if(TYPE_IMP != nullptr) { IteratorImp * pItImp = nullptr; switch(NODE_IMP->m_Type) { case Node::SequenceType: it.m_Type = ConstIterator::SequenceType; pItImp = new SequenceConstIteratorImp; pItImp->InitBegin(static_cast(TYPE_IMP)); break; case Node::MapType: it.m_Type = ConstIterator::MapType; pItImp = new MapConstIteratorImp; pItImp->InitBegin(static_cast(TYPE_IMP)); break; default: break; } it.m_pImp = pItImp; } return it; } Iterator Node::End() { Iterator it; if(TYPE_IMP != nullptr) { IteratorImp * pItImp = nullptr; switch(NODE_IMP->m_Type) { case Node::SequenceType: it.m_Type = Iterator::SequenceType; pItImp = new SequenceIteratorImp; pItImp->InitEnd(static_cast(TYPE_IMP)); break; case Node::MapType: it.m_Type = Iterator::MapType; pItImp = new MapIteratorImp; pItImp->InitEnd(static_cast(TYPE_IMP)); break; default: break; } it.m_pImp = pItImp; } return it; } ConstIterator Node::End() const { ConstIterator it; if(TYPE_IMP != nullptr) { IteratorImp * pItImp = nullptr; switch(NODE_IMP->m_Type) { case Node::SequenceType: it.m_Type = ConstIterator::SequenceType; pItImp = new SequenceConstIteratorImp; pItImp->InitEnd(static_cast(TYPE_IMP)); break; case Node::MapType: it.m_Type = ConstIterator::MapType; pItImp = new MapConstIteratorImp; pItImp->InitEnd(static_cast(TYPE_IMP)); break; default: break; } it.m_pImp = pItImp; } return it; } const std::string & Node::AsString() const { if(TYPE_IMP == nullptr) { return g_EmptyString; } return TYPE_IMP->GetData(); } // Reader implementations /** * @breif Line information structure. * */ class ReaderLine { public: /** * @breif Constructor. * */ ReaderLine(const std::string & data = "", const size_t no = 0, const size_t offset = 0, const Node::eType type = Node::None, const unsigned char flags = 0) : Data(data), No(no), Offset(offset), Type(type), Flags(flags), NextLine(nullptr) { } enum eFlag { LiteralScalarFlag, ///< Literal scalar type, defined as "|". FoldedScalarFlag, ///< Folded scalar type, defined as "<". ScalarNewlineFlag ///< Scalar ends with a newline. }; /** * @breif Set flag. * */ void SetFlag(const eFlag flag) { Flags |= FlagMask[static_cast(flag)]; } /** * @breif Set flags by mask value. * */ void SetFlags(const unsigned char flags) { Flags |= flags; } /** * @breif Unset flag. * */ void UnsetFlag(const eFlag flag) { Flags &= ~FlagMask[static_cast(flag)]; } /** * @breif Unset flags by mask value. * */ void UnsetFlags(const unsigned char flags) { Flags &= ~flags; } /** * @breif Get flag value. * */ bool GetFlag(const eFlag flag) const { return Flags & FlagMask[static_cast(flag)]; } /** * @breif Copy and replace scalar flags from another ReaderLine. * */ void CopyScalarFlags(ReaderLine * from) { if (from == nullptr) { return; } unsigned char newFlags = from->Flags & (FlagMask[0] | FlagMask[1] | FlagMask[2]); Flags |= newFlags; } static const unsigned char FlagMask[3]; std::string Data; ///< Data of line. size_t No; ///< Line number. size_t Offset; ///< Offset to first character in data. Node::eType Type; ///< Type of line. unsigned char Flags; ///< Flags of line. ReaderLine * NextLine; ///< Pointer to next line. }; const unsigned char ReaderLine::FlagMask[3] = { 0x01, 0x02, 0x04 }; /** * @breif Implementation class of Yaml parsing. * Parsing incoming stream and outputs a root node. * */ class ParseImp { public: /** * @breif Default constructor. * */ ParseImp() { } /** * @breif Destructor. * */ ~ParseImp() { ClearLines(); } /** * @breif Run full parsing procedure. * */ void Parse(Node & root, std::iostream & stream) { try { root.Clear(); ReadLines(stream); PostProcessLines(); //Print(); ParseRoot(root); } catch(Exception e) { root.Clear(); throw; } } private: /** * @breif Copy constructor. * */ ParseImp(const ParseImp & copy) { } /** * @breif Read all lines. * Ignoring: * - Empty lines. * - Comments. * - Document start/end. * */ void ReadLines(std::iostream & stream) { std::string line = ""; size_t lineNo = 0; bool documentStartFound = false; bool foundFirstNotEmpty = false; std::streampos streamPos = 0; // Read all lines, as long as the stream is ok. while (!stream.eof() && !stream.fail()) { // Read line streamPos = stream.tellg(); std::getline(stream, line); lineNo++; // Remove comment const size_t commentPos = FindNotCited(line, '#'); if(commentPos != std::string::npos) { line.resize(commentPos); } // Start of document. if (documentStartFound == false && line == "---") { // Erase all lines before this line. ClearLines(); documentStartFound = true; continue; } // End of document. if (line == "...") { break; } else if(line == "---") { stream.seekg(streamPos); break; } // Remove trailing return. if (line.size()) { if (line[line.size() - 1] == '\r') { line.resize(line.size() - 1); } } // Validate characters. for (size_t i = 0; i < line.size(); i++) { if (line[i] != '\t' && (line[i] < 32 || line[i] > 125)) { throw ParsingException(ExceptionMessage(g_ErrorInvalidCharacter, lineNo, i + 1)); } } // Validate tabs const size_t firstTabPos = line.find_first_of('\t'); size_t startOffset = line.find_first_not_of(" \t"); // Make sure no tabs are in the very front. if (startOffset != std::string::npos) { if(firstTabPos < startOffset) { throw ParsingException(ExceptionMessage(g_ErrorTabInOffset, lineNo, firstTabPos)); } // Remove front spaces. line = line.substr(startOffset); } else { startOffset = 0; line = ""; } // Add line. if(foundFirstNotEmpty == false) { if(line.size()) { foundFirstNotEmpty = true; } else { continue; } } ReaderLine * pLine = new ReaderLine(line, lineNo, startOffset); m_Lines.push_back(pLine); } } /** * @breif Run post-processing on all lines. * Basically split lines into multiple lines if needed, to follow the parsing algorithm. * */ void PostProcessLines() { for (auto it = m_Lines.begin(); it != m_Lines.end();) { // Sequence. if (PostProcessSequenceLine(it) == true) { continue; } // Mapping. if (PostProcessMappingLine(it) == true) { continue; } // Scalar. PostProcessScalarLine(it); } // Set next line of all lines. if (m_Lines.size()) { if (m_Lines.back()->Type != Node::ScalarType) { throw ParsingException(ExceptionMessage(g_ErrorUnexpectedDocumentEnd, *m_Lines.back())); } if (m_Lines.size() > 1) { auto prevEnd = m_Lines.end(); --prevEnd; for (auto it = m_Lines.begin(); it != prevEnd; it++) { auto nextIt = it; ++nextIt; (*it)->NextLine = *nextIt; } } } } /** * @breif Run post-processing and check for sequence. * Split line into two lines if sequence token is not on it's own line. * * @return true if line is sequence, else false. * */ bool PostProcessSequenceLine(std::list::iterator & it) { ReaderLine * pLine = *it; // Sequence split if (IsSequenceStart(pLine->Data) == false) { return false; } pLine->Type = Node::SequenceType; ClearTrailingEmptyLines(++it); const size_t valueStart = pLine->Data.find_first_not_of(" \t", 1); if (valueStart == std::string::npos) { return true; } // Create new line and insert std::string newLine = pLine->Data.substr(valueStart); it = m_Lines.insert(it, new ReaderLine(newLine, pLine->No, pLine->Offset + valueStart)); pLine->Data = ""; return false; } /** * @breif Run post-processing and check for mapping. * Split line into two lines if mapping value is not on it's own line. * * @return true if line is mapping, else move on to scalar parsing. * */ bool PostProcessMappingLine(std::list::iterator & it) { ReaderLine * pLine = *it; // Find map key. size_t preKeyQuotes = 0; size_t tokenPos = FindNotCited(pLine->Data, ':', preKeyQuotes); if (tokenPos == std::string::npos) { return false; } if(preKeyQuotes > 1) { throw ParsingException(ExceptionMessage(g_ErrorKeyIncorrect, *pLine)); } pLine->Type = Node::MapType; // Get key std::string key = pLine->Data.substr(0, tokenPos); const size_t keyEnd = key.find_last_not_of(" \t"); if (keyEnd == std::string::npos) { throw ParsingException(ExceptionMessage(g_ErrorKeyMissing, *pLine)); } key.resize(keyEnd + 1); // Handle cited key. if(preKeyQuotes == 1) { if(key.front() != '"' || key.back() != '"') { throw ParsingException(ExceptionMessage(g_ErrorKeyIncorrect, *pLine)); } key = key.substr(1, key.size() - 2); } RemoveAllEscapeTokens(key); // Get value std::string value = ""; size_t valueStart = std::string::npos; if (tokenPos + 1 != pLine->Data.size()) { valueStart = pLine->Data.find_first_not_of(" \t", tokenPos + 1); if (valueStart != std::string::npos) { value = pLine->Data.substr(valueStart); } } // Make sure the value is not a sequence start. if (IsSequenceStart(value) == true) { throw ParsingException(ExceptionMessage(g_ErrorBlockSequenceNotAllowed, *pLine, valueStart)); } pLine->Data = key; // Remove all empty lines after map key. ClearTrailingEmptyLines(++it); // Add new empty line? size_t newLineOffset = valueStart; if(newLineOffset == std::string::npos) { if(it != m_Lines.end() && (*it)->Offset > pLine->Offset) { return true; } newLineOffset = tokenPos + 2; } else { newLineOffset += pLine->Offset; } // Add new line with value. unsigned char dummyBlockFlags = 0; if(IsBlockScalar(value, pLine->No, dummyBlockFlags) == true) { newLineOffset = pLine->Offset; } ReaderLine * pNewLine = new ReaderLine(value, pLine->No, newLineOffset, Node::ScalarType); it = m_Lines.insert(it, pNewLine); // Return false in order to handle next line(scalar value). return false; } /** * @breif Run post-processing and check for scalar. * Checking for multi-line scalars. * * @return true if scalar search should continue, else false. * */ void PostProcessScalarLine(std::list::iterator & it) { ReaderLine * pLine = *it; pLine->Type = Node::ScalarType; size_t parentOffset = pLine->Offset; if(pLine != m_Lines.front()) { std::list::iterator lastIt = it; --lastIt; parentOffset = (*lastIt)->Offset; } std::list::iterator lastNotEmpty = it++; // Find last empty lines while(it != m_Lines.end()) { pLine = *it; pLine->Type = Node::ScalarType; if(pLine->Data.size()) { if(pLine->Offset <= parentOffset) { break; } else { lastNotEmpty = it; } } ++it; } ClearTrailingEmptyLines(++lastNotEmpty); } /** * @breif Process root node and start of document. * */ void ParseRoot(Node & root) { // Get first line and start type. auto it = m_Lines.begin(); if(it == m_Lines.end()) { return; } Node::eType type = (*it)->Type; ReaderLine * pLine = *it; // Handle next line. switch(type) { case Node::SequenceType: ParseSequence(root, it); break; case Node::MapType: ParseMap(root, it); break; case Node::ScalarType: ParseScalar(root, it); break; default: break; } if(it != m_Lines.end()) { throw InternalException(ExceptionMessage(g_ErrorUnexpectedDocumentEnd, *pLine)); } } /** * @breif Process sequence node. * */ void ParseSequence(Node & node, std::list::iterator & it) { ReaderLine * pNextLine = nullptr; while(it != m_Lines.end()) { ReaderLine * pLine = *it; Node & childNode = node.PushBack(); // Move to next line, error check. ++it; if(it == m_Lines.end()) { throw InternalException(ExceptionMessage(g_ErrorUnexpectedDocumentEnd, *pLine)); } // Handle value of map Node::eType valueType = (*it)->Type; switch(valueType) { case Node::SequenceType: ParseSequence(childNode, it); break; case Node::MapType: ParseMap(childNode, it); break; case Node::ScalarType: ParseScalar(childNode, it); break; default: break; } // Check next line. if sequence and correct level, go on, else exit. // If same level but but of type map = error. if(it == m_Lines.end() || ((pNextLine = *it)->Offset < pLine->Offset)) { break; } if(pNextLine->Offset > pLine->Offset) { throw ParsingException(ExceptionMessage(g_ErrorIncorrectOffset, *pNextLine)); } if(pNextLine->Type != Node::SequenceType) { throw InternalException(ExceptionMessage(g_ErrorDiffEntryNotAllowed, *pNextLine)); } } } /** * @breif Process map node. * */ void ParseMap(Node & node, std::list::iterator & it) { ReaderLine * pNextLine = nullptr; while(it != m_Lines.end()) { ReaderLine * pLine = *it; Node & childNode = node[pLine->Data]; // Move to next line, error check. ++it; if(it == m_Lines.end()) { throw InternalException(ExceptionMessage(g_ErrorUnexpectedDocumentEnd, *pLine)); } // Handle value of map Node::eType valueType = (*it)->Type; switch(valueType) { case Node::SequenceType: ParseSequence(childNode, it); break; case Node::MapType: ParseMap(childNode, it); break; case Node::ScalarType: ParseScalar(childNode, it); break; default: break; } // Check next line. if map and correct level, go on, else exit. // if same level but but of type map = error. if(it == m_Lines.end() || ((pNextLine = *it)->Offset < pLine->Offset)) { break; } if(pNextLine->Offset > pLine->Offset) { throw ParsingException(ExceptionMessage(g_ErrorIncorrectOffset, *pNextLine)); } if(pNextLine->Type != pLine->Type) { throw InternalException(ExceptionMessage(g_ErrorDiffEntryNotAllowed, *pNextLine)); } } } /** * @breif Process scalar node. * */ void ParseScalar(Node & node, std::list::iterator & it) { std::string data = ""; ReaderLine * pFirstLine = *it; ReaderLine * pLine = *it; // Check if current line is a block scalar. unsigned char blockFlags = 0; bool isBlockScalar = IsBlockScalar(pLine->Data, pLine->No, blockFlags); const bool newLineFlag = static_cast(blockFlags & ReaderLine::FlagMask[static_cast(ReaderLine::ScalarNewlineFlag)]); const bool foldedFlag = static_cast(blockFlags & ReaderLine::FlagMask[static_cast(ReaderLine::FoldedScalarFlag)]); const bool literalFlag = static_cast(blockFlags & ReaderLine::FlagMask[static_cast(ReaderLine::LiteralScalarFlag)]); size_t parentOffset = 0; // Find parent offset if(it != m_Lines.begin()) { std::list::iterator parentIt = it; --parentIt; parentOffset = (*parentIt)->Offset; } // Move to next iterator/line if current line is a block scalar. if(isBlockScalar) { ++it; if(it == m_Lines.end() || (pLine = *it)->Type != Node::ScalarType) { return; } } // Not a block scalar, cut end spaces/tabs if(isBlockScalar == false) { while(1) { pLine = *it; if(parentOffset != 0 && pLine->Offset <= parentOffset) { throw ParsingException(ExceptionMessage(g_ErrorIncorrectOffset, *pLine)); } const size_t endOffset = pLine->Data.find_last_not_of(" \t"); if(endOffset == std::string::npos) { data += "\n"; } else { data += pLine->Data.substr(0, endOffset + 1); } // Move to next line ++it; if(it == m_Lines.end() || (*it)->Type != Node::ScalarType) { break; } data += " "; } if(ValidateQuote(data) == false) { throw ParsingException(ExceptionMessage(g_ErrorInvalidQuote, *pFirstLine)); } } // Block scalar else { pLine = *it; size_t blockOffset = pLine->Offset; if(blockOffset <= parentOffset) { throw ParsingException(ExceptionMessage(g_ErrorIncorrectOffset, *pLine)); } bool addedSpace = false; while(it != m_Lines.end() && (*it)->Type == Node::ScalarType) { pLine = *it; const size_t endOffset = pLine->Data.find_last_not_of(" \t"); if(endOffset != std::string::npos && pLine->Offset < blockOffset) { throw ParsingException(ExceptionMessage(g_ErrorIncorrectOffset, *pLine)); } if(endOffset == std::string::npos) { if(addedSpace) { data[data.size() - 1] = '\n'; addedSpace = false; } else { data += "\n"; } ++it; continue; } else { if(blockOffset != pLine->Offset && foldedFlag) { if(addedSpace) { data[data.size() - 1] = '\n'; addedSpace = false; } else { data += "\n"; } } data += std::string(pLine->Offset - blockOffset, ' '); data += pLine->Data; } // Move to next line ++it; if(it == m_Lines.end() || (*it)->Type != Node::ScalarType) { if(newLineFlag) { data += "\n"; } break; } if(foldedFlag) { data += " "; addedSpace = true; } else if(literalFlag && endOffset != std::string::npos) { data += "\n"; } } } if(data.size() && (data[0] == '"' || data[0] == '\'')) { data = data.substr(1, data.size() - 2 ); } node = data; } /** * @breif Debug printing. * */ void Print() { for (auto it = m_Lines.begin(); it != m_Lines.end(); it++) { ReaderLine * pLine = *it; // Print type if (pLine->Type == Node::SequenceType) { std::cout << "seq "; } else if (pLine->Type == Node::MapType) { std::cout << "map "; } else if (pLine->Type == Node::ScalarType) { std::cout << "sca "; } else { std::cout << " "; } // Print flags if (pLine->GetFlag(ReaderLine::FoldedScalarFlag)) { std::cout << "f"; } else { std::cout << "-"; } if (pLine->GetFlag(ReaderLine::LiteralScalarFlag)) { std::cout << "l"; } else { std::cout << "-"; } if (pLine->GetFlag(ReaderLine::ScalarNewlineFlag)) { std::cout << "n"; } else { std::cout << "-"; } if (pLine->NextLine == nullptr) { std::cout << "e"; } else { std::cout << "-"; } std::cout << "| "; std::cout << pLine->No << " "; std::cout << std::string(pLine->Offset, ' '); if (pLine->Type == Node::ScalarType) { std::string scalarValue = pLine->Data; for (size_t i = 0; (i = scalarValue.find("\n", i)) != std::string::npos;) { scalarValue.replace(i, 1, "\\n"); i += 2; } std::cout << scalarValue << std::endl; } else if (pLine->Type == Node::MapType) { std::cout << pLine->Data + ":" << std::endl; } else if (pLine->Type == Node::SequenceType) { std::cout << "-" << std::endl; } else { std::cout << "> UNKOWN TYPE <" << std::endl; } } } /** * @breif Clear all read lines. * */ void ClearLines() { for (auto it = m_Lines.begin(); it != m_Lines.end(); it++) { delete *it; } m_Lines.clear(); } void ClearTrailingEmptyLines(std::list::iterator & it) { while(it != m_Lines.end()) { ReaderLine * pLine = *it; if(pLine->Data.size() == 0) { delete *it; it = m_Lines.erase(it); } else { return; } } } static bool IsSequenceStart(const std::string & data) { if (data.size() == 0 || data[0] != '-') { return false; } if (data.size() >= 2 && data[1] != ' ') { return false; } return true; } static bool IsBlockScalar(const std::string & data, const size_t line, unsigned char & flags) { flags = 0; if(data.size() == 0) { return false; } if(data[0] == '|') { if(data.size() >= 2) { if(data[1] != '-' && data[1] != ' ' && data[1] != '\t') { throw ParsingException(ExceptionMessage(g_ErrorInvalidBlockScalar, line, data)); } } else { flags |= ReaderLine::FlagMask[static_cast(ReaderLine::ScalarNewlineFlag)]; } flags |= ReaderLine::FlagMask[static_cast(ReaderLine::LiteralScalarFlag)]; return true; } if(data[0] == '>') { if(data.size() >= 2) { if(data[1] != '-' && data[1] != ' ' && data[1] != '\t') { throw ParsingException(ExceptionMessage(g_ErrorInvalidBlockScalar, line, data)); } } else { flags |= ReaderLine::FlagMask[static_cast(ReaderLine::ScalarNewlineFlag)]; } flags |= ReaderLine::FlagMask[static_cast(ReaderLine::FoldedScalarFlag)]; return true; } return false; } std::list m_Lines; ///< List of lines. }; // Parsing functions void Parse(Node & root, const char * filename) { std::ifstream f(filename, std::ifstream::binary); if (f.is_open() == false) { throw OperationException(g_ErrorCannotOpenFile); } f.seekg(0, f.end); size_t fileSize = static_cast(f.tellg()); f.seekg(0, f.beg); std::unique_ptr data(new char[fileSize]); f.read(data.get(), fileSize); f.close(); Parse(root, data.get(), fileSize); } void Parse(Node & root, std::iostream & stream) { ParseImp * pImp = nullptr; try { pImp = new ParseImp; pImp->Parse(root, stream); delete pImp; } catch (const Exception e) { delete pImp; throw; } } void Parse(Node & root, const std::string & string) { std::stringstream ss(string); Parse(root, ss); } void Parse(Node & root, const char * buffer, const size_t size) { std::stringstream ss(std::string(buffer, size)); Parse(root, ss); } // Serialize configuration structure. SerializeConfig::SerializeConfig(const size_t spaceIndentation, const size_t scalarMaxLength, const bool sequenceMapNewline, const bool mapScalarNewline) : SpaceIndentation(spaceIndentation), ScalarMaxLength(scalarMaxLength), SequenceMapNewline(sequenceMapNewline), MapScalarNewline(mapScalarNewline) { } // Serialization functions void Serialize(const Node & root, const char * filename, const SerializeConfig & config) { std::stringstream stream; Serialize(root, stream, config); std::ofstream f(filename); if (f.is_open() == false) { throw OperationException(g_ErrorCannotOpenFile); } f.write(stream.str().c_str(), stream.str().size()); f.close(); } size_t LineFolding(const std::string & input, std::vector & folded, const size_t maxLength) { folded.clear(); if(input.size() == 0) { return 0; } size_t currentPos = 0; size_t lastPos = 0; size_t spacePos = std::string::npos; while(currentPos < input.size()) { currentPos = lastPos + maxLength; if(currentPos < input.size()) { spacePos = input.find_first_of(' ', currentPos); } if(spacePos == std::string::npos || currentPos >= input.size()) { const std::string endLine = input.substr(lastPos); if(endLine.size()) { folded.push_back(endLine); } return folded.size(); } folded.push_back(input.substr(lastPos, spacePos - lastPos)); lastPos = spacePos + 1; } return folded.size(); } static void SerializeLoop(const Node & node, std::iostream & stream, bool useLevel, const size_t level, const SerializeConfig & config) { const size_t indention = config.SpaceIndentation; switch(node.Type()) { case Node::SequenceType: { for(auto it = node.Begin(); it != node.End(); it++) { const Node & value = (*it).second; if(value.IsNone()) { continue; } stream << std::string(level, ' ') << "- "; useLevel = false; if(value.IsSequence() || (value.IsMap() && config.SequenceMapNewline == true)) { useLevel = true; stream << "\n"; } SerializeLoop(value, stream, useLevel, level + 2, config); } } break; case Node::MapType: { size_t count = 0; for(auto it = node.Begin(); it != node.End(); it++) { const Node & value = (*it).second; if(value.IsNone()) { continue; } if(useLevel || count > 0) { stream << std::string(level, ' '); } std::string key = (*it).first; AddEscapeTokens(key, "\\\""); if(ShouldBeCited(key)) { stream << "\"" << key << "\"" << ": "; } else { stream << key << ": "; } useLevel = false; if(value.IsScalar() == false || (value.IsScalar() && config.MapScalarNewline)) { useLevel = true; stream << "\n"; } SerializeLoop(value, stream, useLevel, level + indention, config); useLevel = true; count++; } } break; case Node::ScalarType: { const std::string value = node.As(); // Empty scalar if(value.size() == 0) { stream << "\n"; break; } // Get lines of scalar. std::string line = ""; std::vector lines; std::istringstream iss(value); while (iss.eof() == false) { std::getline(iss, line); lines.push_back(line); } // Block scalar const std::string & lastLine = lines.back(); const bool endNewline = lastLine.size() == 0; if(endNewline) { lines.pop_back(); } // Literal if(lines.size() > 1) { stream << "|"; } // Folded/plain else { const std::string frontLine = lines.front(); if(config.ScalarMaxLength == 0 || lines.front().size() <= config.ScalarMaxLength || LineFolding(frontLine, lines, config.ScalarMaxLength) == 1) { if(useLevel) { stream << std::string(level, ' '); } if(ShouldBeCited(value)) { stream << "\"" << value << "\"\n"; break; } stream << value << "\n"; break; } else { stream << ">"; } } if(endNewline == false) { stream << "-"; } stream << "\n"; for(auto it = lines.begin(); it != lines.end(); it++) { stream << std::string(level, ' ') << (*it) << "\n"; } } break; default: break; } } void Serialize(const Node & root, std::iostream & stream, const SerializeConfig & config) { if(config.SpaceIndentation < 2) { throw OperationException(g_ErrorIndentation); } SerializeLoop(root, stream, false, 0, config); } void Serialize(const Node & root, std::string & string, const SerializeConfig & config) { std::stringstream stream; Serialize(root, stream, config); string = stream.str(); } // Static function implementations std::string ExceptionMessage(const std::string & message, ReaderLine & line) { return message + std::string(" Line ") + std::to_string(line.No) + std::string(": ") + line.Data; } std::string ExceptionMessage(const std::string & message, ReaderLine & line, const size_t errorPos) { return message + std::string(" Line ") + std::to_string(line.No) + std::string(" column ") + std::to_string(errorPos + 1) + std::string(": ") + line.Data; } std::string ExceptionMessage(const std::string & message, const size_t errorLine, const size_t errorPos) { return message + std::string(" Line ") + std::to_string(errorLine) + std::string(" column ") + std::to_string(errorPos); } std::string ExceptionMessage(const std::string & message, const size_t errorLine, const std::string & data) { return message + std::string(" Line ") + std::to_string(errorLine) + std::string(": ") + data; } bool FindQuote(const std::string & input, size_t & start, size_t & end, size_t searchPos) { start = end = std::string::npos; size_t qPos = searchPos; bool foundStart = false; while(qPos != std::string::npos) { // Find first quote. qPos = input.find_first_of("\"'", qPos); if(qPos == std::string::npos) { return false; } const char token = input[qPos]; if(token == '"' && (qPos == 0 || input[qPos-1] != '\\')) { // Found start quote. if(foundStart == false) { start = qPos; foundStart = true; } // Found end quote else { end = qPos; return true; } } // Check if it's possible for another loop. if(qPos + 1 == input.size()) { return false; } qPos++; } return false; } size_t FindNotCited(const std::string & input, char token, size_t & preQuoteCount) { preQuoteCount = 0; size_t tokenPos = input.find_first_of(token); if(tokenPos == std::string::npos) { return std::string::npos; } // Find all quotes std::vector> quotes; size_t quoteStart = 0; size_t quoteEnd = 0; while(FindQuote(input, quoteStart, quoteEnd, quoteEnd)) { quotes.push_back({quoteStart, quoteEnd}); if(quoteEnd + 1 == input.size()) { break; } quoteEnd++; } if(quotes.size() == 0) { return tokenPos; } size_t currentQuoteIndex = 0; std::pair currentQuote = {0, 0}; while(currentQuoteIndex < quotes.size()) { currentQuote = quotes[currentQuoteIndex]; if(tokenPos < currentQuote.first) { return tokenPos; } preQuoteCount++; if(tokenPos <= currentQuote.second) { // Find next token if(tokenPos + 1 == input.size()) { return std::string::npos; } tokenPos = input.find_first_of(token, tokenPos + 1); if(tokenPos == std::string::npos) { return std::string::npos; } } currentQuoteIndex++; } return tokenPos; } size_t FindNotCited(const std::string & input, char token) { size_t dummy = 0; return FindNotCited(input, token, dummy); } bool ValidateQuote(const std::string & input) { if(input.size() == 0) { return true; } char token = 0; size_t searchPos = 0; if(input[0] == '\"' || input[0] == '\'') { if(input.size() == 1) { return false; } token = input[0]; searchPos = 1; } while(searchPos != std::string::npos && searchPos < input.size() - 1) { searchPos = input.find_first_of("\"'", searchPos + 1); if(searchPos == std::string::npos) { break; } const char foundToken = input[searchPos]; if(input[searchPos] == '\"' || input[searchPos] == '\'') { if(token == 0 && input[searchPos-1] != '\\') { return false; } //if(foundToken == token) //{ /*if(foundToken == token && searchPos == input.size() - 1 && input[searchPos-1] != '\\') { return true; if(searchPos == input.size() - 1) { return true; } return false; } else */ if(foundToken == token && input[searchPos-1] != '\\') { if(searchPos == input.size() - 1) { return true; } return false; } //} } } return token == 0; } void CopyNode(const Node & from, Node & to) { const Node::eType type = from.Type(); switch(type) { case Node::SequenceType: for(auto it = from.Begin(); it != from.End(); it++) { const Node & currentNode = (*it).second; Node & newNode = to.PushBack(); CopyNode(currentNode, newNode); } break; case Node::MapType: for(auto it = from.Begin(); it != from.End(); it++) { const Node & currentNode = (*it).second; Node & newNode = to[(*it).first]; CopyNode(currentNode, newNode); } break; case Node::ScalarType: to = from.As(); break; case Node::None: break; } } bool ShouldBeCited(const std::string & key) { return key.find_first_of("\":{}[],&*#?|-<>=!%@") != std::string::npos; } void AddEscapeTokens(std::string & input, const std::string & tokens) { for(auto it = tokens.begin(); it != tokens.end(); it++) { const char token = *it; const std::string replace = std::string("\\") + std::string(1, token); size_t found = input.find_first_of(token); while(found != std::string::npos) { input.replace(found, 1, replace); found = input.find_first_of(token, found + 2); } } } void RemoveAllEscapeTokens(std::string & input) { size_t found = input.find_first_of("\\"); while(found != std::string::npos) { if(found + 1 == input.size()) { return; } std::string replace(1, input[found + 1]); input.replace(found, 2, replace); found = input.find_first_of("\\", found + 1); } } }