-/////////////////////////////////////////////////////////////////////////////
-// License (GPLv2+):
-// This program 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; either version 2 of the License, or (at
-// your option) any later version.
-//
-// This program 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 this program; if not, write to the Free Software
-// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-/////////////////////////////////////////////////////////////////////////////
+// SPDX-License-Identifier: GPL-2.0-or-later
/**
* @file ProjectFile.cpp
*
* @brief Implementation file for ProjectFile class.
*/
-// RCS ID line follows -- this is updated by CVS
-// $Id$
-
-#include "stdafx.h"
-#include <scew/scew.h>
+#include "pch.h"
#include "ProjectFile.h"
+#include <stack>
+#include <string>
+#include <Poco/FileStream.h>
+#include <Poco/XML/XMLWriter.h>
+#include <Poco/SAX/SAXParser.h>
+#include <Poco/SAX/ContentHandler.h>
+#include <Poco/Exception.h>
+#include "UnicodeString.h"
+#include "unicoder.h"
+
+using Poco::FileStream;
+using Poco::XML::SAXParser;
+using Poco::XML::ContentHandler;
+using Poco::XML::Locator;
+using Poco::XML::XMLWriter;
+using Poco::XML::XMLChar;
+using Poco::XML::XMLString;
+using Poco::XML::Attributes;
+using Poco::Exception;
+using ucr::toTString;
+using ucr::toUTF8;
// Constants for xml element names
-const TCHAR Root_element_name[] = _T("project");
-const TCHAR Paths_element_name[] = _T("paths");
-const TCHAR Left_element_name[] = _T("left");
-const TCHAR Right_element_name[] = _T("right");
-const TCHAR Filter_element_name[] = _T("filter");
-const TCHAR Subfolders_element_name[] = _T("subfolders");
+const char Root_element_name[] = "project";
+const char Paths_element_name[] = "paths";
+const char Left_element_name[] = "left";
+const char Middle_element_name[] = "middle";
+const char Right_element_name[] = "right";
+const char Filter_element_name[] = "filter";
+const char Subfolders_element_name[] = "subfolders";
+const char Left_ro_element_name[] = "left-readonly";
+const char Middle_ro_element_name[] = "middle-readonly";
+const char Right_ro_element_name[] = "right-readonly";
+
+namespace
+{
-/**
- * @brief Standard constructor.
- */
- ProjectFile::ProjectFile()
-: m_subfolders(-1)
-, m_bLeftReadOnly(FALSE)
-, m_bRightReadOnly(FALSE)
+String xmlch2tstr(const XMLChar *ch, int length)
{
+ return toTString(std::string(ch, length));
}
-/**
- * @brief Open given path-file and read data from it to member variables.
- * @param [in] path Path to project file.
- * @param [out] sError Error string if error happened.
- * @return TRUE if reading succeeded, FALSE if error happened.
- * @bug This doesn't handle unicode paths!
- */
-BOOL ProjectFile::Read(LPCTSTR path, CString *sError)
+void writeElement(XMLWriter& writer, const std::string& tagname, const std::string& characters)
{
- BOOL loaded = FALSE;
- LPCSTR pathAnsi;
-
-#ifdef UNICODE
- USES_CONVERSION;
- pathAnsi = T2A(path);
-#else
- pathAnsi = path;
-#endif
-
- scew_tree* tree = NULL;
- scew_parser* parser = NULL;
-
- parser = scew_parser_create();
- scew_parser_ignore_whitespaces(parser, 1);
-
- if (scew_parser_load_file(parser, pathAnsi))
- {
- tree = scew_parser_tree(parser);
-
- scew_element * root = GetRootElement(tree);
- if (root)
- {
- loaded = TRUE;
- GetPathsData(root);
- };
- }
- scew_tree_free(tree);
-
- /* Frees the SCEW parser */
- scew_parser_free(parser);
+ writer.startElement("", "", tagname);
+ writer.characters(characters);
+ writer.endElement("", "", tagname);
+}
- return loaded;
}
-/**
- * @brief Return project file XML's root element.
- * @param [in] tree XML tree we got from the parser.
- * @return Root element pointer.
- */
-scew_element* ProjectFile::GetRootElement(scew_tree * tree)
+class ProjectFileHandler: public ContentHandler
{
- scew_element * root = NULL;
+public:
+ explicit ProjectFileHandler(std::list<ProjectFileItem> *pProject) : m_pProject(pProject) {}
- if (tree != NULL)
+ void setDocumentLocator(const Locator* loc) {}
+ void startDocument() {}
+ void endDocument() {}
+ void startElement(const XMLString& uri, const XMLString& localName, const XMLString& qname, const Attributes& attributes)
{
- root = scew_tree_root(tree);
+ if (localName == Paths_element_name)
+ m_pProject->push_back(ProjectFileItem{});
+ m_stack.push(localName);
}
-
- if (root != NULL)
+ void endElement(const XMLString& uri, const XMLString& localName, const XMLString& qname)
{
- // Make sure we have correct root element
- if (_tcscmp(Root_element_name, scew_element_name(root)) != 0)
- {
- root = NULL;
- }
+ m_stack.pop();
}
- return root;
-}
-
-/**
- * @brief Reads the paths data from the XML data.
- * This function reads the paths data inside given element in XML data.
- * @param [in] parent Parent element for the paths data.
- */
-void ProjectFile::GetPathsData(scew_element * parent)
-{
- scew_element *paths = NULL;
-
- if (parent != NULL)
+ void characters(const XMLChar ch[], int start, int length)
{
- paths = scew_element_by_name(parent, Paths_element_name);
- }
-
- if (paths != NULL)
- {
- scew_element *left = NULL;
- scew_element *right = NULL;
- scew_element *filter = NULL;
- scew_element *subfolders = NULL;
+ if (m_stack.size() != 3 && m_pProject->size() == 0)
+ return;
- left = scew_element_by_name(paths, Left_element_name);
- right = scew_element_by_name(paths, Right_element_name);
- filter = scew_element_by_name(paths, Filter_element_name);
- subfolders = scew_element_by_name(paths, Subfolders_element_name);
+ ProjectFileItem& currentItem = m_pProject->back();
- if (left)
+ const std::string& nodename = m_stack.top();
+ if (nodename == Left_element_name)
{
- LPCTSTR path = NULL;
- path = scew_element_contents(right);
- m_leftFile = path;
+ currentItem.m_paths.SetLeft(currentItem.m_paths.GetLeft() + xmlch2tstr(ch + start, length), false);
+ currentItem.m_bHasLeft = true;
}
- if (right)
+ else if (nodename == Middle_element_name)
{
- LPCTSTR path = NULL;
- path = scew_element_contents(right);
- m_rightFile = path;
+ currentItem.m_paths.SetMiddle(currentItem.m_paths.GetMiddle() + xmlch2tstr(ch + start, length), false);
+ currentItem.m_bHasMiddle = true;
}
- if (filter)
+ else if (nodename == Right_element_name)
{
- LPCTSTR filtername = NULL;
- filtername = scew_element_contents(filter);
- m_filter = filtername;
+ currentItem.m_paths.SetRight(currentItem.m_paths.GetRight() + xmlch2tstr(ch + start, length), false);
+ currentItem.m_bHasRight = true;
}
- if (subfolders)
+ else if (nodename == Filter_element_name)
{
- LPCTSTR folders = NULL;
- folders = scew_element_contents(subfolders);
- m_subfolders = _ttoi(folders);
+ currentItem.m_filter += xmlch2tstr(ch + start, length);
+ currentItem.m_bHasFilter = true;
+ }
+ else if (nodename == Subfolders_element_name)
+ {
+ currentItem.m_subfolders = atoi(std::string(ch + start, length).c_str());
+ currentItem.m_bHasSubfolders = true;
+ }
+ else if (nodename == Left_ro_element_name)
+ {
+ currentItem.m_bLeftReadOnly = atoi(std::string(ch + start, length).c_str()) != 0;
+ }
+ else if (nodename == Middle_ro_element_name)
+ {
+ currentItem.m_bMiddleReadOnly = atoi(std::string(ch + start, length).c_str()) != 0;
+ }
+ else if (nodename == Right_ro_element_name)
+ {
+ currentItem.m_bRightReadOnly = atoi(std::string(ch + start, length).c_str()) != 0;
}
}
-}
-
-/**
- * @brief Save data from member variables to path-file.
- * @param [in] path Path to project file.
- * @param [out] sError Error string if error happened.
- * @return TRUE if saving succeeded, FALSE if error happened.
- */
-BOOL ProjectFile::Save(LPCTSTR path, CString *sError)
-{
- BOOL success = TRUE;
- LPCSTR pathAnsi;
-
-#ifdef UNICODE
- USES_CONVERSION;
- pathAnsi = T2A(path);
-#else
- pathAnsi = path;
-#endif
-
- scew_tree* tree = NULL;
- scew_element* root = NULL;
- scew_element* paths = NULL;
-
- tree = scew_tree_create();
- root = scew_tree_add_root(tree, Root_element_name);
- if (root != NULL)
- {
- paths = AddPathsElement(root);
- }
- else
- success = FALSE;
-
- if (paths != NULL)
- {
- AddPathsContent(paths);
- }
- else
- success = FALSE;
-
- scew_tree_set_xml_encoding(tree, _T("UTF-8"));
-
- // Set the XML file standalone
- scew_tree_set_xml_standalone(tree, 1);
-
- if (!scew_writer_tree_file(tree, pathAnsi))
- success = FALSE;
-
- /* Frees the SCEW tree */
- scew_tree_free(tree);
-
- return success;
- // return Serialize(true, path, sError);
-}
-
-/**
- * @brief Add paths element into XML tree.
- * @param [in] parent Parent element for the paths element.
- * @return pointer to added paths element.
- */
-scew_element* ProjectFile::AddPathsElement(scew_element * parent)
-{
- scew_element* element = NULL;
- element = scew_element_add(parent, Paths_element_name);
- return element;
-}
-
-/**
- * @brief Add paths data to the XML tree.
- * This function adds our paths data to the XML tree.
- * @param [in] parent Parent element for paths data.
- * @return TRUE if we succeeded, FALSE otherwise.
- */
-BOOL ProjectFile::AddPathsContent(scew_element * parent)
-{
- scew_element* element = NULL;
-
- if (!m_leftFile.IsEmpty())
- {
- LPCTSTR path;
- element = scew_element_add(parent, Left_element_name);
- path = m_leftFile.GetBuffer(MAX_PATH);
- scew_element_set_contents(element, path);
- m_leftFile.ReleaseBuffer();
- }
-
- if (!m_rightFile.IsEmpty())
- {
- LPCTSTR path;
- element = scew_element_add(parent, Right_element_name);
- path = m_rightFile.GetBuffer(MAX_PATH);
- scew_element_set_contents(element, path);
- m_rightFile.ReleaseBuffer();
- }
-
- if (!m_filter.IsEmpty())
- {
- LPCTSTR filter;
- element = scew_element_add(parent, Filter_element_name);
- filter = m_filter.GetBuffer(MAX_PATH);
- scew_element_set_contents(element, filter);
- m_filter.ReleaseBuffer();
- }
+ void ignorableWhitespace(const XMLChar ch[], int start, int length) {}
+ void processingInstruction(const XMLString& target, const XMLString& data) {}
+ void startPrefixMapping(const XMLString& prefix, const XMLString& uri) {}
+ void endPrefixMapping(const XMLString& prefix) {}
+ void skippedEntity(const XMLString& name) {}
- LPCTSTR path;
- element = scew_element_add(parent, Subfolders_element_name);
- if (m_subfolders != 0)
- {
- scew_element_set_contents(element, _T("1"));
- }
- else
- {
- scew_element_set_contents(element, _T("0"));
- }
- return TRUE;
-}
+private:
+ std::list<ProjectFileItem> *m_pProject = nullptr;
+ std::stack<std::string> m_stack;
+};
-/**
- * @brief Returns if left path is defined.
- */
-BOOL ProjectFile::HasLeft() const
-{
- return !m_leftFile.IsEmpty();
-}
+/** @brief File extension for path files */
+const String ProjectFile::PROJECTFILE_EXT = toTString("WinMerge");
/**
- * @brief Returns if right path is defined.
- */
-BOOL ProjectFile::HasRight() const
-{
- return !m_rightFile.IsEmpty();
-}
-
-/**
- * @brief Returns if filter is defined.
+ * @brief Standard constructor.
*/
-BOOL ProjectFile::HasFilter() const
+ ProjectFileItem::ProjectFileItem()
+: m_bHasLeft(false)
+, m_bHasMiddle(false)
+, m_bHasRight(false)
+, m_bHasFilter(false)
+, m_bHasSubfolders(false)
+, m_subfolders(-1)
+, m_bLeftReadOnly(false)
+, m_bMiddleReadOnly(false)
+, m_bRightReadOnly(false)
{
- return !m_filter.IsEmpty();
}
/**
- * @brief Returns if subfolder is included.
+ * @brief Returns left path.
+ * @param [out] pReadOnly true if readonly was specified for path.
+ * @return Left path.
*/
-BOOL ProjectFile::HasSubfolders() const
+String ProjectFileItem::GetLeft(bool * pReadOnly /*= nullptr*/) const
{
- return (m_subfolders != -1);
+ if (pReadOnly != nullptr)
+ *pReadOnly = m_bLeftReadOnly;
+ return m_paths.GetLeft();
}
/**
- * @brief Returns left path.
- * @param [out] pReadOnly TRUE if readonly was specified for path.
+ * @brief Set left path, returns old left path.
+ * @param [in] sLeft Left path.
+ * @param [in] bReadOnly Will path be recorded read-only?
*/
-CString ProjectFile::GetLeft(BOOL * pReadOnly /*=NULL*/) const
+void ProjectFileItem::SetLeft(const String& sLeft, const bool * pReadOnly /*= nullptr*/)
{
- if (pReadOnly)
- *pReadOnly = m_bLeftReadOnly;
- return m_leftFile;
+ m_paths.SetLeft(sLeft, false);
+ if (pReadOnly != nullptr)
+ m_bLeftReadOnly = *pReadOnly;
}
/**
- * @brief Returns if left path is specified read-only.
+ * @brief Returns middle path.
+ * @param [out] pReadOnly true if readonly was specified for path.
*/
-BOOL ProjectFile::GetLeftReadOnly() const
+String ProjectFileItem::GetMiddle(bool * pReadOnly /*= nullptr*/) const
{
- return m_bLeftReadOnly;
+ if (pReadOnly != nullptr)
+ *pReadOnly = m_bMiddleReadOnly;
+ return m_paths.GetMiddle();
}
/**
- * @brief Set left path, returns old left path.
- * @param [in] sLeft Left path.
+ * @brief Set middle path, returns old middle path.
+ * @param [in] sMiddle Middle path.
* @param [in] bReadOnly Will path be recorded read-only?
*/
-CString ProjectFile::SetLeft(const CString& sLeft, const BOOL * pReadOnly /*=NULL*/)
+void ProjectFileItem::SetMiddle(const String& sMiddle, const bool * pReadOnly /*= nullptr*/)
{
- CString sLeftOld = GetLeft();
- m_leftFile = sLeft;
- if (pReadOnly)
- m_bLeftReadOnly = *pReadOnly;
+ m_paths.SetMiddle(sMiddle, false);
+ if (pReadOnly != nullptr)
+ m_bMiddleReadOnly = *pReadOnly;
- return sLeftOld;
+ return;
}
/**
* @brief Returns right path.
- * @param [out] pReadOnly TRUE if readonly was specified for path.
+ * @param [out] pReadOnly true if readonly was specified for path.
+ * @return Right path.
*/
-CString ProjectFile::GetRight(BOOL * pReadOnly /*=NULL*/) const
+String ProjectFileItem::GetRight(bool * pReadOnly /*= nullptr*/) const
{
- if (pReadOnly)
+ if (pReadOnly != nullptr)
*pReadOnly = m_bRightReadOnly;
- return m_rightFile;
-}
-
-/**
- * @brief Returns if right path is specified read-only.
- */
-BOOL ProjectFile::GetRightReadOnly() const
-{
- return m_bRightReadOnly;
+ return m_paths.GetRight();
}
/**
* @param [in] sRight Right path.
* @param [in] bReadOnly Will path be recorded read-only?
*/
-CString ProjectFile::SetRight(const CString& sRight, const BOOL * pReadOnly /*=NULL*/)
+void ProjectFileItem::SetRight(const String& sRight, const bool * pReadOnly /*= nullptr*/)
{
- CString sRightOld = GetRight();
- m_rightFile = sRight;
- if (pReadOnly)
+ m_paths.SetRight(sRight, false);
+ if (pReadOnly != nullptr)
m_bRightReadOnly = *pReadOnly;
-
- return sRightOld;
}
/**
- * @brief Returns filter.
- */
-CString ProjectFile::GetFilter() const
-{
- return m_filter;
-}
-
-/**
- * @brief Set filter, returns old filter.
+ * @brief Returns left and right paths and recursive from project file
+ *
+ * @param [out] files Files in project
+ * @param [out] bSubFolders If true subfolders included (recursive compare)
*/
-CString ProjectFile::SetFilter(const CString& sFilter)
+void ProjectFileItem::GetPaths(PathContext& files, bool & bSubfolders) const
{
- CString sFilterOld = GetFilter();
- m_filter = sFilter;
-
- return sFilterOld;
+ files = m_paths;
+ if (HasSubfolders())
+ bSubfolders = (GetSubfolders() == 1);
}
/**
- * @brief Returns subfolder included -setting.
+ * @brief Open given path-file and read data from it to member variables.
+ * @param [in] path Path to project file.
+ * @param [out] sError Error string if error happened.
+ * @return true if reading succeeded, false if error happened.
*/
-int ProjectFile::GetSubfolders() const
+bool ProjectFile::Read(const String& path)
{
- return m_subfolders;
+ ProjectFileHandler handler(&m_items);
+ SAXParser parser;
+ parser.setContentHandler(&handler);
+ parser.parse(toUTF8(path));
+ return true;
}
/**
- * @brief set subfolder, returns old subfolder value.
+ * @brief Save data from member variables to path-file.
+ * @param [in] path Path to project file.
+ * @param [out] sError Error string if error happened.
+ * @return true if saving succeeded, false if error happened.
*/
-int ProjectFile::SetSubfolders(const int iSubfolder)
+bool ProjectFile::Save(const String& path) const
{
- int iSubfoldersOld = GetSubfolders();
- m_subfolders = iSubfolder ? 1 : 0;
-
- return iSubfoldersOld;
+ FileStream out(toUTF8(path), FileStream::trunc);
+ XMLWriter writer(out, XMLWriter::WRITE_XML_DECLARATION | XMLWriter::PRETTY_PRINT);
+ writer.startDocument();
+ writer.startElement("", "", Root_element_name);
+ {
+ for (auto& item : m_items)
+ {
+ writer.startElement("", "", Paths_element_name);
+ {
+ if (!item.m_paths.GetLeft().empty())
+ writeElement(writer, Left_element_name, toUTF8(item.m_paths.GetLeft()));
+ if (!item.m_paths.GetMiddle().empty())
+ writeElement(writer, Middle_element_name, toUTF8(item.m_paths.GetMiddle()));
+ if (!item.m_paths.GetRight().empty())
+ writeElement(writer, Right_element_name, toUTF8(item.m_paths.GetRight()));
+ if (!item.m_filter.empty())
+ writeElement(writer, Filter_element_name, toUTF8(item.m_filter));
+ writeElement(writer, Subfolders_element_name, item.m_subfolders != 0 ? "1" : "0");
+ writeElement(writer, Left_ro_element_name, item.m_bLeftReadOnly ? "1" : "0");
+ if (!item.m_paths.GetMiddle().empty())
+ writeElement(writer, Middle_ro_element_name, item.m_bMiddleReadOnly ? "1" : "0");
+ writeElement(writer, Right_ro_element_name, item.m_bRightReadOnly ? "1" : "0");
+ }
+ writer.endElement("", "", Paths_element_name);
+ }
+ }
+ writer.endElement("", "", Root_element_name);
+ writer.endDocument();
+ return true;
}
-/**
- * @brief Returns left and right paths and recursive from project file
- *
- * @param [out] sLeft Left path
- * @param [out] sRight Right path
- * @param [out] bSubFolders If TRUE subfolders included (recursive compare)
- */
-void ProjectFile::GetPaths(CString & sLeft, CString & sRight,
- BOOL & bSubfolders) const
-{
- if (HasLeft())
- sLeft = GetLeft();
- if (HasRight())
- sRight = GetRight();
- if (HasSubfolders())
- bSubfolders = (GetSubfolders() == 1);
-}