OSDN Git Service

autoit.cpp - Macros >> User 1 ..... Variable >> User 2 (#749) (2)
[winmerge-jp/winmerge-jp.git] / Src / ProjectFile.cpp
index b0f23e5..0fc486e 100755 (executable)
-/////////////////////////////////////////////////////////////////////////////
-//    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
+ * @brief Implementation file for ProjectFile class.
  */
-// RCS ID line follows -- this is updated by CVS
-// $Id$
 
-#include "stdafx.h"
+#include "pch.h"
 #include "ProjectFile.h"
-#include "markdown.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"
 
-ProjectFile::ProjectFile()
+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";
+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
 {
-       m_subfolders = -1;
-}
 
-/** 
- * @brief Get message from exception into sError, or else throw it.
- *
- * If caller provided the address of an error string (sError),
- *  this populates the error string (if possible) and returns FALSE
- *
- * If caller did not provide the address of an error string (sError==NULL)
- *  this rethrows the error
- */
-static BOOL NTAPI False(CException *e, CString *sError)
+String xmlch2tstr(const XMLChar *ch, int length)
 {
-       if (sError == NULL)
-               throw e;
-       TCHAR szError[4096] = _T("");
-       e->GetErrorMessage(szError, sizeof(szError)/sizeof(szError[0]));
-       *sError = szError;
-       e->Delete();
-       return FALSE;
+       return toTString(std::string(ch, length));
 }
 
-/** 
- * @brief Open given path-file and read data from it to member variables.
- *
- * Errors are returned in sError, unless it is NULL, in which case they are thrown
- */
-BOOL ProjectFile::Read(LPCTSTR path, CString *sError)
+void writeElement(XMLWriter& writer, const std::string& tagname, const std::string& characters)
 {
-       try
-       {
-               CMarkdown::EntityMap entities;
-               entities.Load();
-               CMarkdown::File xmlfile = path;
-               if (xmlfile.pImage == NULL)
-               {
-                       CFileException::ThrowOsError(GetLastError(), path);
-               }
-               // If encoding is other than UTF-8, assume CP_ACP
-               CMarkdown::String encoding = CMarkdown(xmlfile).Move("?xml").GetAttribute("encoding");
-               UINT codepage = lstrcmpiA(encoding.A, "UTF-8") == 0 ? CP_UTF8 : CP_ACP;
+       writer.startElement("", "", tagname);
+       writer.characters(characters);
+       writer.endElement("", "", tagname);
+}
 
-               CMarkdown project = CMarkdown(xmlfile).Move("project").Pop();
-               CMarkdown paths = CMarkdown(project).Move("paths").Pop();
-               m_leftFile = CMarkdown::String(CMarkdown(paths).Move("left").GetInnerText()->Unicode(codepage)->Resolve(entities)).W;
-               m_rightFile = CMarkdown::String(CMarkdown(paths).Move("right").GetInnerText()->Unicode(codepage)->Resolve(entities)).W;
-               m_filter = CMarkdown::String(CMarkdown(paths).Move("filter").GetInnerText()->Unicode(codepage)->Resolve(entities)).W;
-               sscanf(CMarkdown::String(CMarkdown(paths).Move("subfolders").GetInnerText()).A, "%d", &m_subfolders);
-       }
-       catch (CException *e)
-       {
-               return False(e, sError);
-       }
-       return TRUE;
 }
 
-/** 
- * @brief Save data from member variables to path-file.
- * @note paths are converted to UTF-8
- */
-BOOL ProjectFile::Save(LPCTSTR path, CString *sError)
+class ProjectFileHandler: public ContentHandler
 {
-       try
+public:
+       explicit ProjectFileHandler(std::list<ProjectFileItem> *pProject) : m_pProject(pProject) {}
+
+       void setDocumentLocator(const Locator* loc) {}
+       void startDocument() {}
+       void endDocument() {}
+       void startElement(const XMLString& uri, const XMLString& localName, const XMLString& qname, const Attributes& attributes)
        {
-               static const char szFormat[]
-               (
-                       "<?xml version='1.0' encoding='UTF-8'?>\n"
-                       "<project>\n"
-                       "\t<paths>\n"
-                       "\t\t<left>%s</left>\n"
-                       "\t\t<right>%s</right>\n"
-                       "\t\t<filter>%s</filter>\n"
-                       "\t\t<subfolders>%d</subfolders>\n"
-                       "\t</paths>\n"
-                       "</project>\n"
-               );
-               fprintf
-               (
-                       CStdioFile(path, CFile::modeCreate|CFile::modeWrite|CFile::typeText).m_pStream,
-                       szFormat,
-                       CMarkdown::String(CMarkdown::HSTR(GetLeft().AllocSysString())->Entities()->Octets(CP_UTF8)).A,
-                       CMarkdown::String(CMarkdown::HSTR(GetRight().AllocSysString())->Entities()->Octets(CP_UTF8)).A,
-                       CMarkdown::String(CMarkdown::HSTR(GetFilter().AllocSysString())->Entities()->Octets(CP_UTF8)).A,
-                       GetSubfolders() ? 1 : 0
-               );
+               if (localName == Paths_element_name)
+                       m_pProject->push_back(ProjectFileItem{});
+               m_stack.push(localName);
        }
-       catch (CException *e)
+       void endElement(const XMLString& uri, const XMLString& localName, const XMLString& qname)
        {
-               return False(e, sError);
+               m_stack.pop();
        }
-       return TRUE;
-}
+       void characters(const XMLChar ch[], int start, int length)
+       {
+               if (m_stack.size() != 3 && m_pProject->size() == 0)
+                       return;
 
-/** 
- * @brief Returns if left path is defined.
- */
-BOOL ProjectFile::HasLeft() const
-{
-       return !m_leftFile.IsEmpty();
-}
+               ProjectFileItem& currentItem = m_pProject->back();
 
-/** 
- * @brief Returns if right path is defined.
- */
-BOOL ProjectFile::HasRight() const
-{
-       return !m_rightFile.IsEmpty();
-}
+               const std::string& nodename = m_stack.top();
+               if (nodename == Left_element_name)
+               {
+                       currentItem.m_paths.SetLeft(currentItem.m_paths.GetLeft() + xmlch2tstr(ch + start, length), false);
+                       currentItem.m_bHasLeft = true;
+               }
+               else if (nodename == Middle_element_name)
+               {
+                       currentItem.m_paths.SetMiddle(currentItem.m_paths.GetMiddle() + xmlch2tstr(ch + start, length), false);
+                       currentItem.m_bHasMiddle = true;
+               }
+               else if (nodename == Right_element_name)
+               {
+                       currentItem.m_paths.SetRight(currentItem.m_paths.GetRight() + xmlch2tstr(ch + start, length), false);
+                       currentItem.m_bHasRight = true;
+               }
+               else if (nodename == Filter_element_name)
+               {
+                       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;
+               }
+       }
+       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) {}
 
-/** 
- * @brief Returns if filter is defined.
- */
-BOOL ProjectFile::HasFilter() const
-{
-       return !m_filter.IsEmpty();
-}
+private:
+       std::list<ProjectFileItem> *m_pProject = nullptr;
+       std::stack<std::string> m_stack;
+};
+
+/** @brief File extension for path files */
+const String ProjectFile::PROJECTFILE_EXT = toTString("WinMerge");
 
 /** 
- * @brief Returns if subfolder is included.
+ * @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_subfolders != -1);
 }
 
 /** 
  * @brief Returns left path.
+ * @param [out] pReadOnly true if readonly was specified for path.
+ * @return Left path.
  */
-CString ProjectFile::GetLeft() const
+String ProjectFileItem::GetLeft(bool * pReadOnly /*= nullptr*/) const
 {
-       return m_leftFile;
+       if (pReadOnly != nullptr)
+               *pReadOnly = m_bLeftReadOnly;
+       return m_paths.GetLeft();
 }
 
 /** 
  * @brief Set left path, returns old left path.
+ * @param [in] sLeft Left path.
+ * @param [in] bReadOnly Will path be recorded read-only?
  */
-CString ProjectFile::SetLeft(const CString& sLeft)
+void ProjectFileItem::SetLeft(const String& sLeft, const bool * pReadOnly /*= nullptr*/)
 {
-       CString sLeftOld = GetLeft();
-       m_leftFile = sLeft;
-
-       return sLeftOld;
+       m_paths.SetLeft(sLeft, false);
+       if (pReadOnly != nullptr)
+               m_bLeftReadOnly = *pReadOnly;
 }
 
 /** 
- * @brief Returns right path.
+ * @brief Returns middle path.
+ * @param [out] pReadOnly true if readonly was specified for path.
  */
-CString ProjectFile::GetRight() const
+String ProjectFileItem::GetMiddle(bool * pReadOnly /*= nullptr*/) const
 {
-       return m_rightFile;
+       if (pReadOnly != nullptr)
+               *pReadOnly = m_bMiddleReadOnly;
+       return m_paths.GetMiddle();
 }
 
 /** 
- * @brief Set right path, returns old right 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::SetRight(const CString& sRight)
+void ProjectFileItem::SetMiddle(const String& sMiddle, const bool * pReadOnly /*= nullptr*/)
 {
-       CString sRightOld = GetRight();
-       m_rightFile = sRight;
+       m_paths.SetMiddle(sMiddle, false);
+       if (pReadOnly != nullptr)
+               m_bMiddleReadOnly = *pReadOnly;
 
-       return sRightOld;
+       return;
 }
 
 /** 
- * @brief Returns filter.
+ * @brief Returns right path.
+ * @param [out] pReadOnly true if readonly was specified for path.
+ * @return Right path.
  */
-CString ProjectFile::GetFilter() const
+String ProjectFileItem::GetRight(bool * pReadOnly /*= nullptr*/) const
 {
-       return m_filter;
+       if (pReadOnly != nullptr)
+               *pReadOnly = m_bRightReadOnly;
+       return m_paths.GetRight();
 }
 
 /** 
- * @brief Set filter, returns old filter.
+ * @brief Set right path, returns old right path.
+ * @param [in] sRight Right path.
+ * @param [in] bReadOnly Will path be recorded read-only?
  */
-CString ProjectFile::SetFilter(const CString& sFilter)
+void ProjectFileItem::SetRight(const String& sRight, const bool * pReadOnly /*= nullptr*/)
 {
-       CString sFilterOld = GetFilter();
-       m_filter = sFilter;
-
-       return sFilterOld;
+       m_paths.SetRight(sRight, false);
+       if (pReadOnly != nullptr)
+               m_bRightReadOnly = *pReadOnly;
 }
 
 /** 
- * @brief Returns subfolder included -setting.
+ * @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, returns old subfolder value.
+ * @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::SetSubfolders(const int iSubfolder)
+bool ProjectFile::Read(const String& path)
 {
-       int iSubfoldersOld = GetSubfolders(); 
-       m_subfolders = iSubfolder ? 1 : 0;
-
-       return iSubfoldersOld;
+       ProjectFileHandler handler(&m_items);
+       SAXParser parser;
+       parser.setContentHandler(&handler);
+       parser.parse(toUTF8(path));
+       return true;
 }
 
 /** 
- * @brief Reads one value from XML data.
+ * @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::GetVal(TCHAR *pPaths, TCHAR *pVal, CString * sval,
-               TCHAR *ptag1, TCHAR *ptag2, TCHAR *pbuf)
+bool ProjectFile::Save(const String& path) const
 {
-       if (pPaths && pVal && pVal > pPaths)
+       FileStream out(toUTF8(path), FileStream::trunc);
+       XMLWriter writer(out, XMLWriter::WRITE_XML_DECLARATION | XMLWriter::PRETTY_PRINT);
+       writer.startDocument();
+       writer.startElement("", "", Root_element_name);
        {
-               TCHAR tmpPath[MAX_PATH] = {0};
-               TCHAR *pTagEnd = _tcsstr(pbuf, ptag2);
-               if ((pTagEnd - pVal) < (MAX_PATH * sizeof(TCHAR)))
+               for (auto& item : m_items)
                {
-                       pVal += _tcslen(ptag1);
-                       _tcsncpy(tmpPath, pVal, pTagEnd - pVal);
-                       *sval = tmpPath;
-                       return TRUE;
+                       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);
                }
        }
-       return FALSE;
+       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);
-}