OSDN Git Service

autoit.cpp - Macros >> User 1 ..... Variable >> User 2 (#749) (2)
[winmerge-jp/winmerge-jp.git] / Src / ProjectFile.cpp
index fe7b75b..0fc486e 100755 (executable)
@@ -1,53 +1,33 @@
-/////////////////////////////////////////////////////////////////////////////
-//    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.
  */
-// ID line follows -- this is updated by CVS
-// $Id: ProjectFile.cpp 7081 2010-01-01 20:33:30Z kimmov $
 
-#include "stdafx.h"
-#include <scew/scew.h>
-#include "UnicodeString.h"
+#include "pch.h"
 #include "ProjectFile.h"
-#include "Merge.h"
-
-// ATL conversion macro hack for UTF-8 conversion
-#define UTF82W(lpa) (\
-       ((_lpa = lpa) == NULL) ? NULL : (\
-               _convert = (lstrlenA(_lpa)+1),\
-               AtlA2WHelper((LPWSTR) alloca(_convert*2), _lpa, _convert, CP_UTF8)))
-
-#define W2UTF8(lpw) (\
-       ((_lpw = lpw) == NULL) ? NULL : (\
-               _convert = (lstrlenW(_lpw)+1)*6,\
-               AtlW2AHelper((LPSTR) alloca(_convert), _lpw, _convert, CP_UTF8)))
-
-#define UTF82A(lpu) W2A(UTF82W(lpu))
-#define A2UTF8(lpa) W2UTF8(A2W(lpa))
-#ifdef _UNICODE
-#  define UTF82T(lpu) UTF82W(lpu)
-#  define T2UTF8(lpw) W2UTF8(lpw)
-#else
-#  define UTF82T(lpu) UTF82A(lpu)
-#  define T2UTF8(lpw) A2UTF8(lpw)
-#endif
+#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 char Root_element_name[] = "project";
@@ -61,457 +41,161 @@ const char Left_ro_element_name[] = "left-readonly";
 const char Middle_ro_element_name[] = "middle-readonly";
 const char Right_ro_element_name[] = "right-readonly";
 
-/** 
- * @brief Standard constructor.
- */
- ProjectFile::ProjectFile()
-: 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)
+namespace
 {
-}
 
-/** 
- * @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.
- */
-BOOL ProjectFile::Read(LPCTSTR path, String *sError)
+String xmlch2tstr(const XMLChar *ch, int length)
 {
-       BOOL loaded = FALSE;
-    scew_tree* tree = NULL;
-    scew_parser* parser = NULL;
-
-    parser = scew_parser_create();
-    scew_parser_ignore_whitespaces(parser, 1);
-
-       scew_reader *reader = NULL;
-       FILE * fp = _tfopen(path, _T("r"));
-       if (fp)
-       {
-               reader = scew_reader_fp_create(fp);
-               if (reader)
-               {
-                       tree = scew_parser_load (parser, reader);
-
-                       if (tree)
-                       {
-                               scew_element * root = GetRootElement(tree);
-                               if (root)
-                               {
-                                       // Currently our content is paths, so expect
-                                       // having paths in valid project file!
-                                       if (GetPathsData(root))
-                                               loaded = TRUE;
-                               };
-                       }
-               }
-
-               scew_tree_free(tree);
-               scew_reader_free(reader);
-
-               /* Frees the SCEW parser */
-               scew_parser_free(parser);
-               fclose(fp);
-       }
-       return loaded;
+       return toTString(std::string(ch, length));
 }
 
-/** 
- * @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)
+void writeElement(XMLWriter& writer, const std::string& tagname, const std::string& characters)
 {
-       scew_element * root = NULL;
-
-       if (tree != NULL)
-       {
-               root = scew_tree_root(tree);
-       }
+       writer.startElement("", "", tagname);
+       writer.characters(characters);
+       writer.endElement("", "", tagname);
+}
 
-       if (root != NULL)
-       {
-               // Make sure we have correct root element
-               if (strcmp(Root_element_name, scew_element_name(root)) != 0)
-               {
-                       root = NULL;
-               }
-       }
-       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.
- * @return TRUE if pathdata was found from the file.
- */
-BOOL ProjectFile::GetPathsData(scew_element * parent)
+class ProjectFileHandler: public ContentHandler
 {
-       USES_CONVERSION;
-       BOOL bFoundPaths = FALSE;
-       scew_element *paths = NULL;
+public:
+       explicit ProjectFileHandler(std::list<ProjectFileItem> *pProject) : m_pProject(pProject) {}
 
-       if (parent != NULL)
+       void setDocumentLocator(const Locator* loc) {}
+       void startDocument() {}
+       void endDocument() {}
+       void startElement(const XMLString& uri, const XMLString& localName, const XMLString& qname, const Attributes& attributes)
        {
-               paths = scew_element_by_name(parent, Paths_element_name);
+               if (localName == Paths_element_name)
+                       m_pProject->push_back(ProjectFileItem{});
+               m_stack.push(localName);
        }
-
-       if (paths != NULL)
+       void endElement(const XMLString& uri, const XMLString& localName, const XMLString& qname)
        {
-               bFoundPaths = TRUE;
-               scew_element *left = NULL;
-               scew_element *middle = NULL;
-               scew_element *right = NULL;
-               scew_element *filter = NULL;
-               scew_element *subfolders = NULL;
-               scew_element *left_ro = NULL;
-               scew_element *middle_ro = NULL;
-               scew_element *right_ro = NULL;
+               m_stack.pop();
+       }
+       void characters(const XMLChar ch[], int start, int length)
+       {
+               if (m_stack.size() != 3 && m_pProject->size() == 0)
+                       return;
 
-               left = scew_element_by_name(paths, Left_element_name);
-               middle = scew_element_by_name(paths, Middle_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);
-               left_ro = scew_element_by_name(paths, Left_ro_element_name);
-               middle_ro = scew_element_by_name(paths, Middle_ro_element_name);
-               right_ro = scew_element_by_name(paths, Right_ro_element_name);
+               ProjectFileItem& currentItem = m_pProject->back();
 
-               if (left)
+               const std::string& nodename = m_stack.top();
+               if (nodename == Left_element_name)
                {
-                       LPCSTR path = NULL;
-                       path = scew_element_contents(left);
-                       m_paths.SetLeft(UTF82T(path));
-                       m_bHasLeft = TRUE;
+                       currentItem.m_paths.SetLeft(currentItem.m_paths.GetLeft() + xmlch2tstr(ch + start, length), false);
+                       currentItem.m_bHasLeft = true;
                }
-               if (middle)
+               else if (nodename == Middle_element_name)
                {
-                       LPCSTR path = NULL;
-                       path = scew_element_contents(middle);
-                       m_paths.SetMiddle(UTF82T(path));
-                       m_bHasMiddle = TRUE;
+                       currentItem.m_paths.SetMiddle(currentItem.m_paths.GetMiddle() + xmlch2tstr(ch + start, length), false);
+                       currentItem.m_bHasMiddle = true;
                }
-               if (right)
+               else if (nodename == Right_element_name)
                {
-                       LPCSTR path = NULL;
-                       path = scew_element_contents(right);
-                       m_paths.SetRight(UTF82T(path));
-                       m_bHasRight = TRUE;
+                       currentItem.m_paths.SetRight(currentItem.m_paths.GetRight() + xmlch2tstr(ch + start, length), false);
+                       currentItem.m_bHasRight = true;
                }
-               if (filter)
+               else if (nodename == Filter_element_name)
                {
-                       LPCSTR filtername = NULL;
-                       filtername = scew_element_contents(filter);
-                       m_filter = UTF82T(filtername);
-                       m_bHasFilter = TRUE;
+                       currentItem.m_filter += xmlch2tstr(ch + start, length);
+                       currentItem.m_bHasFilter = true;
                }
-               if (subfolders)
+               else if (nodename == Subfolders_element_name)
                {
-                       LPCSTR folders = NULL;
-                       folders = scew_element_contents(subfolders);
-                       m_subfolders = atoi(folders);
-                       m_bHasSubfolders = TRUE;
+                       currentItem.m_subfolders = atoi(std::string(ch + start, length).c_str());
+                       currentItem.m_bHasSubfolders = true;
                }
-               if (left_ro)
+               else if (nodename == Left_ro_element_name)
                {
-                       LPCSTR readonly = NULL;
-                       readonly = scew_element_contents(left_ro);
-                       m_bLeftReadOnly = (atoi(readonly) != 0);
+                       currentItem.m_bLeftReadOnly = atoi(std::string(ch + start, length).c_str()) != 0;
                }
-               if (middle_ro)
+               else if (nodename == Middle_ro_element_name)
                {
-                       LPCSTR readonly = NULL;
-                       readonly = scew_element_contents(middle_ro);
-                       m_bMiddleReadOnly = (atoi(readonly) != 0);
+                       currentItem.m_bMiddleReadOnly = atoi(std::string(ch +  start, length).c_str()) != 0;
                }
-               if (right_ro)
+               else if (nodename == Right_ro_element_name)
                {
-                       LPCSTR readonly = NULL;
-                       readonly = scew_element_contents(right_ro);
-                       m_bRightReadOnly = (atoi(readonly) != 0);
+                       currentItem.m_bRightReadOnly = atoi(std::string(ch + start, length).c_str()) != 0;
                }
        }
-       return bFoundPaths;
-}
-
-/** 
- * @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, String *sError)
-{
-       BOOL success = TRUE;
-       scew_tree* tree = NULL;
-       scew_element* root = NULL;
-       scew_element* paths = NULL;
-
-       tree = scew_tree_create();
-       root = scew_tree_set_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, "UTF-8");
-
-       scew_writer *writer = NULL;
-       scew_printer *printer = NULL;
-       FILE * fp = _tfopen(path, _T("w"));
-       if (fp)
-       {
-               writer = scew_writer_fp_create(fp);
-               if (writer)
-               {
-                       printer = scew_printer_create(writer);
-                       
-                       if (!scew_printer_print_tree(printer, tree) ||
-                               !scew_printf(_XT("\n")))
-                       {
-                               success = FALSE;
-                               *sError = theApp.LoadString(IDS_FILEWRITE_ERROR);
-                       }
-               }
-               else
-               {
-                       success = FALSE;
-                       *sError = theApp.LoadString(IDS_FILEWRITE_ERROR);
-               }
-               fclose(fp);
-       }
-       else
-       {
-               success = FALSE;
-       }
-       
-       /* Frees the SCEW tree */
-       scew_tree_free(tree);
-       scew_writer_free(writer);
-       scew_printer_free(printer);
-
-       if (success == FALSE)
-       {
-               *sError = theApp.LoadString(IDS_FILEWRITE_ERROR);
-       }
-       return success;
-}
-
-/**
- * @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 Covert characters that are unsafe for use in XML
- * @param [in] str The string to be converted
- * @return The converted string
- */
-static String EscapeXML(const String &str)
-{
-       String escapedStr = str;
-       string_replace(escapedStr, _T("&"), _T("&amp;"));
-       string_replace(escapedStr, _T("<"), _T("&lt;"));
-       string_replace(escapedStr, _T(">"), _T("&gt;"));
-       return escapedStr;
-}
-
-/**
- * @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)
-{
-       USES_CONVERSION;
-       scew_element* element = NULL;
-
-       if (!m_paths.GetLeft().empty())
-       {
-               element = scew_element_add(parent, Left_element_name);
-               scew_element_set_contents(element, T2UTF8(EscapeXML(m_paths.GetLeft()).c_str()));
-       }
-
-       if (!m_paths.GetMiddle().empty())
-       {
-               element = scew_element_add(parent, Middle_element_name);
-               scew_element_set_contents(element, T2UTF8(EscapeXML(m_paths.GetMiddle()).c_str()));
-       }
+       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) {}
 
-       if (!m_paths.GetRight().empty())
-       {
-               element = scew_element_add(parent, Right_element_name);
-               scew_element_set_contents(element, T2UTF8(EscapeXML(m_paths.GetRight()).c_str()));
-       }
-
-       if (!m_filter.empty())
-       {
-               element = scew_element_add(parent, Filter_element_name);
-               String filter = m_filter;
-               scew_element_set_contents(element, T2UTF8(EscapeXML(filter).c_str()));
-       }
-
-       element = scew_element_add(parent, Subfolders_element_name);
-       if (m_subfolders != 0)
-               scew_element_set_contents(element, "1");
-       else
-               scew_element_set_contents(element, "0");
-
-       element = scew_element_add(parent, Left_ro_element_name);
-       if (m_bLeftReadOnly)
-               scew_element_set_contents(element, "1");
-       else
-               scew_element_set_contents(element, "0");
-
-       if (!m_paths.GetMiddle().empty())
-       {
-               element = scew_element_add(parent, Middle_ro_element_name);
-               if (m_bMiddleReadOnly)
-                       scew_element_set_contents(element, "1");
-               else
-                       scew_element_set_contents(element, "0");
-       }
-       element = scew_element_add(parent, Right_ro_element_name);
-       if (m_bRightReadOnly)
-               scew_element_set_contents(element, "1");
-       else
-               scew_element_set_contents(element, "0");
-
-       return TRUE;
-}
-
-/** 
- * @brief Returns if left path is defined in project file.
- * @return TRUE if project file has left path.
- */
-BOOL ProjectFile::HasLeft() const
-{
-       return m_bHasLeft;
-}
-
-/** 
- * @brief Returns if middle path is defined.
- */
-BOOL ProjectFile::HasMiddle() const
-{
-       return m_bHasMiddle;
-}
+private:
+       std::list<ProjectFileItem> *m_pProject = nullptr;
+       std::stack<std::string> m_stack;
+};
 
-/** 
- * @brief Returns if right path is defined in project file.
- * @return TRUE if project file has right path.
- */
-BOOL ProjectFile::HasRight() const
-{
-       return m_bHasRight;
-}
+/** @brief File extension for path files */
+const String ProjectFile::PROJECTFILE_EXT = toTString("WinMerge");
 
 /** 
- * @brief Returns if filter is defined in project file.
- * @return TRUE if project file has filter.
- */
-BOOL ProjectFile::HasFilter() const
-{
-       return m_bHasFilter;
-}
-
-/** 
- * @brief Returns if subfolder is defined in projectfile.
- * @return TRUE if project file has subfolder definition.
+ * @brief Standard constructor.
  */
-BOOL ProjectFile::HasSubfolders() 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_bHasSubfolders;
 }
 
 /** 
  * @brief Returns left path.
- * @param [out] pReadOnly TRUE if readonly was specified for path.
+ * @param [out] pReadOnly true if readonly was specified for path.
  * @return Left path.
  */
-String ProjectFile::GetLeft(BOOL * pReadOnly /*=NULL*/) const
+String ProjectFileItem::GetLeft(bool * pReadOnly /*= nullptr*/) const
 {
-       if (pReadOnly)
+       if (pReadOnly != nullptr)
                *pReadOnly = m_bLeftReadOnly;
        return m_paths.GetLeft();
 }
 
 /** 
- * @brief Returns if left path is specified read-only.
- * @return TRUE if left path is read-only, FALSE otherwise.
- */
-BOOL ProjectFile::GetLeftReadOnly() const
-{
-       return m_bLeftReadOnly;
-}
-
-/** 
  * @brief Set left path, returns old left path.
  * @param [in] sLeft Left path.
  * @param [in] bReadOnly Will path be recorded read-only?
  */
-void ProjectFile::SetLeft(const String& sLeft, const BOOL * pReadOnly /*=NULL*/)
+void ProjectFileItem::SetLeft(const String& sLeft, const bool * pReadOnly /*= nullptr*/)
 {
-       m_paths.SetLeft(sLeft.c_str(), false);
-       if (pReadOnly)
+       m_paths.SetLeft(sLeft, false);
+       if (pReadOnly != nullptr)
                m_bLeftReadOnly = *pReadOnly;
 }
 
 /** 
  * @brief Returns middle path.
- * @param [out] pReadOnly TRUE if readonly was specified for path.
+ * @param [out] pReadOnly true if readonly was specified for path.
  */
-String ProjectFile::GetMiddle(BOOL * pReadOnly /*=NULL*/) const
+String ProjectFileItem::GetMiddle(bool * pReadOnly /*= nullptr*/) const
 {
-       if (pReadOnly)
+       if (pReadOnly != nullptr)
                *pReadOnly = m_bMiddleReadOnly;
        return m_paths.GetMiddle();
 }
 
 /** 
- * @brief Returns if middle path is specified read-only.
- */
-BOOL ProjectFile::GetMiddleReadOnly() const
-{
-       return m_bMiddleReadOnly;
-}
-
-/** 
  * @brief Set middle path, returns old middle path.
  * @param [in] sMiddle Middle path.
  * @param [in] bReadOnly Will path be recorded read-only?
  */
-void ProjectFile::SetMiddle(const String& sMiddle, const BOOL * pReadOnly /*=NULL*/)
+void ProjectFileItem::SetMiddle(const String& sMiddle, const bool * pReadOnly /*= nullptr*/)
 {
-       m_paths.SetMiddle(sMiddle.c_str(), false);
-       if (pReadOnly)
+       m_paths.SetMiddle(sMiddle, false);
+       if (pReadOnly != nullptr)
                m_bMiddleReadOnly = *pReadOnly;
 
        return;
@@ -519,94 +203,92 @@ void ProjectFile::SetMiddle(const String& sMiddle, const BOOL * pReadOnly /*=NUL
 
 /** 
  * @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.
  */
-String ProjectFile::GetRight(BOOL * pReadOnly /*=NULL*/) const
+String ProjectFileItem::GetRight(bool * pReadOnly /*= nullptr*/) const
 {
-       if (pReadOnly)
+       if (pReadOnly != nullptr)
                *pReadOnly = m_bRightReadOnly;
        return m_paths.GetRight();
 }
 
 /** 
- * @brief Returns if right path is specified read-only.
- * @return TRUE if right path is read-only, FALSE otherwise.
- */
-BOOL ProjectFile::GetRightReadOnly() const
-{
-       return m_bRightReadOnly;
-}
-
-/** 
  * @brief Set right path, returns old right path.
  * @param [in] sRight Right path.
  * @param [in] bReadOnly Will path be recorded read-only?
  */
-void ProjectFile::SetRight(const String& sRight, const BOOL * pReadOnly /*=NULL*/)
+void ProjectFileItem::SetRight(const String& sRight, const bool * pReadOnly /*= nullptr*/)
 {
-       m_paths.SetRight(sRight.c_str(), false);
-       if (pReadOnly)
+       m_paths.SetRight(sRight, false);
+       if (pReadOnly != nullptr)
                m_bRightReadOnly = *pReadOnly;
 }
 
 /** 
- * @brief Returns filter.
- * @return Filter string.
- */
-String ProjectFile::GetFilter() const
-{
-       return m_filter;
-}
-
-/** 
- * @brief Set filter.
- * @param [in] sFilter New filter string to set.
- */
-void ProjectFile::SetFilter(const String& sFilter)
-{
-       m_filter = sFilter;
-}
-
-/** 
- * @brief Returns subfolder included -setting.
- * @return != 0 if subfolders are included.
+ * @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)
  */
-int ProjectFile::GetSubfolders() const
+void ProjectFileItem::GetPaths(PathContext& files, bool & bSubfolders) const
 {
-       return m_subfolders;
+       files = m_paths;
+       if (HasSubfolders())
+               bSubfolders = (GetSubfolders() == 1);
 }
 
 /** 
- * @brief set subfolder.
- * @param [in] iSubfolder New value for subfolder inclusion.
+ * @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.
  */
-void ProjectFile::SetSubfolders(int iSubfolder)
+bool ProjectFile::Read(const String& path)
 {
-       m_subfolders = iSubfolder ? 1 : 0;
+       ProjectFileHandler handler(&m_items);
+       SAXParser parser;
+       parser.setContentHandler(&handler);
+       parser.parse(toUTF8(path));
+       return true;
 }
 
 /** 
- * @brief 
- *
- * @param [in] files Files in project
- * @param [in] bSubFolders If TRUE subfolders included (recursive compare)
+ * @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.
  */
-void ProjectFile::SetPaths(const PathContext& files, BOOL bSubfolders)
+bool ProjectFile::Save(const String& path) const
 {
-       m_paths = files;
-       m_subfolders = bSubfolders;
+       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] files Files in project
- * @param [out] bSubFolders If TRUE subfolders included (recursive compare)
- */
-void ProjectFile::GetPaths(PathContext& files, BOOL & bSubfolders) const
-{
-       files = m_paths;
-       if (HasSubfolders())
-               bSubfolders = (GetSubfolders() == 1);
-}