1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * @file ProjectFile.cpp
5 * @brief Implementation file for ProjectFile class.
9 #include "ProjectFile.h"
12 #include <Poco/FileStream.h>
13 #include <Poco/XML/XMLWriter.h>
14 #include <Poco/SAX/SAXParser.h>
15 #include <Poco/SAX/ContentHandler.h>
16 #include <Poco/Exception.h>
17 #include "UnicodeString.h"
20 using Poco::FileStream;
21 using Poco::XML::SAXParser;
22 using Poco::XML::ContentHandler;
23 using Poco::XML::Locator;
24 using Poco::XML::XMLWriter;
25 using Poco::XML::XMLChar;
26 using Poco::XML::XMLString;
27 using Poco::XML::Attributes;
28 using Poco::Exception;
32 // Constants for xml element names
33 const char Root_element_name[] = "project";
34 const char Paths_element_name[] = "paths";
35 const char Left_element_name[] = "left";
36 const char Middle_element_name[] = "middle";
37 const char Right_element_name[] = "right";
38 const char Filter_element_name[] = "filter";
39 const char Subfolders_element_name[] = "subfolders";
40 const char Left_ro_element_name[] = "left-readonly";
41 const char Middle_ro_element_name[] = "middle-readonly";
42 const char Right_ro_element_name[] = "right-readonly";
47 String xmlch2tstr(const XMLChar *ch, int length)
49 return toTString(std::string(ch, length));
52 void writeElement(XMLWriter& writer, const std::string& tagname, const std::string& characters)
54 writer.startElement("", "", tagname);
55 writer.characters(characters);
56 writer.endElement("", "", tagname);
61 class ProjectFileHandler: public ContentHandler
64 explicit ProjectFileHandler(std::list<ProjectFileItem> *pProject) : m_pProject(pProject) {}
66 void setDocumentLocator(const Locator* loc) {}
67 void startDocument() {}
69 void startElement(const XMLString& uri, const XMLString& localName, const XMLString& qname, const Attributes& attributes)
71 if (localName == Paths_element_name)
72 m_pProject->push_back(ProjectFileItem{});
73 m_stack.push(localName);
75 void endElement(const XMLString& uri, const XMLString& localName, const XMLString& qname)
79 void characters(const XMLChar ch[], int start, int length)
81 if (m_stack.size() != 3 && m_pProject->size() == 0)
84 ProjectFileItem& currentItem = m_pProject->back();
86 const std::string& nodename = m_stack.top();
87 if (nodename == Left_element_name)
89 currentItem.m_paths.SetLeft(currentItem.m_paths.GetLeft() + xmlch2tstr(ch + start, length), false);
90 currentItem.m_bHasLeft = true;
92 else if (nodename == Middle_element_name)
94 currentItem.m_paths.SetMiddle(currentItem.m_paths.GetMiddle() + xmlch2tstr(ch + start, length), false);
95 currentItem.m_bHasMiddle = true;
97 else if (nodename == Right_element_name)
99 currentItem.m_paths.SetRight(currentItem.m_paths.GetRight() + xmlch2tstr(ch + start, length), false);
100 currentItem.m_bHasRight = true;
102 else if (nodename == Filter_element_name)
104 currentItem.m_filter += xmlch2tstr(ch + start, length);
105 currentItem.m_bHasFilter = true;
107 else if (nodename == Subfolders_element_name)
109 currentItem.m_subfolders = atoi(std::string(ch + start, length).c_str());
110 currentItem.m_bHasSubfolders = true;
112 else if (nodename == Left_ro_element_name)
114 currentItem.m_bLeftReadOnly = atoi(std::string(ch + start, length).c_str()) != 0;
116 else if (nodename == Middle_ro_element_name)
118 currentItem.m_bMiddleReadOnly = atoi(std::string(ch + start, length).c_str()) != 0;
120 else if (nodename == Right_ro_element_name)
122 currentItem.m_bRightReadOnly = atoi(std::string(ch + start, length).c_str()) != 0;
125 void ignorableWhitespace(const XMLChar ch[], int start, int length) {}
126 void processingInstruction(const XMLString& target, const XMLString& data) {}
127 void startPrefixMapping(const XMLString& prefix, const XMLString& uri) {}
128 void endPrefixMapping(const XMLString& prefix) {}
129 void skippedEntity(const XMLString& name) {}
132 std::list<ProjectFileItem> *m_pProject = nullptr;
133 std::stack<std::string> m_stack;
136 /** @brief File extension for path files */
137 const String ProjectFile::PROJECTFILE_EXT = toTString("WinMerge");
140 * @brief Standard constructor.
142 ProjectFileItem::ProjectFileItem()
144 , m_bHasMiddle(false)
146 , m_bHasFilter(false)
147 , m_bHasSubfolders(false)
149 , m_bLeftReadOnly(false)
150 , m_bMiddleReadOnly(false)
151 , m_bRightReadOnly(false)
156 * @brief Returns left path.
157 * @param [out] pReadOnly true if readonly was specified for path.
160 String ProjectFileItem::GetLeft(bool * pReadOnly /*= nullptr*/) const
162 if (pReadOnly != nullptr)
163 *pReadOnly = m_bLeftReadOnly;
164 return m_paths.GetLeft();
168 * @brief Set left path, returns old left path.
169 * @param [in] sLeft Left path.
170 * @param [in] bReadOnly Will path be recorded read-only?
172 void ProjectFileItem::SetLeft(const String& sLeft, const bool * pReadOnly /*= nullptr*/)
174 m_paths.SetLeft(sLeft, false);
175 if (pReadOnly != nullptr)
176 m_bLeftReadOnly = *pReadOnly;
180 * @brief Returns middle path.
181 * @param [out] pReadOnly true if readonly was specified for path.
183 String ProjectFileItem::GetMiddle(bool * pReadOnly /*= nullptr*/) const
185 if (pReadOnly != nullptr)
186 *pReadOnly = m_bMiddleReadOnly;
187 return m_paths.GetMiddle();
191 * @brief Set middle path, returns old middle path.
192 * @param [in] sMiddle Middle path.
193 * @param [in] bReadOnly Will path be recorded read-only?
195 void ProjectFileItem::SetMiddle(const String& sMiddle, const bool * pReadOnly /*= nullptr*/)
197 m_paths.SetMiddle(sMiddle, false);
198 if (pReadOnly != nullptr)
199 m_bMiddleReadOnly = *pReadOnly;
205 * @brief Returns right path.
206 * @param [out] pReadOnly true if readonly was specified for path.
207 * @return Right path.
209 String ProjectFileItem::GetRight(bool * pReadOnly /*= nullptr*/) const
211 if (pReadOnly != nullptr)
212 *pReadOnly = m_bRightReadOnly;
213 return m_paths.GetRight();
217 * @brief Set right path, returns old right path.
218 * @param [in] sRight Right path.
219 * @param [in] bReadOnly Will path be recorded read-only?
221 void ProjectFileItem::SetRight(const String& sRight, const bool * pReadOnly /*= nullptr*/)
223 m_paths.SetRight(sRight, false);
224 if (pReadOnly != nullptr)
225 m_bRightReadOnly = *pReadOnly;
229 * @brief Returns left and right paths and recursive from project file
231 * @param [out] files Files in project
232 * @param [out] bSubFolders If true subfolders included (recursive compare)
234 void ProjectFileItem::GetPaths(PathContext& files, bool & bSubfolders) const
238 bSubfolders = (GetSubfolders() == 1);
242 * @brief Open given path-file and read data from it to member variables.
243 * @param [in] path Path to project file.
244 * @param [out] sError Error string if error happened.
245 * @return true if reading succeeded, false if error happened.
247 bool ProjectFile::Read(const String& path)
249 ProjectFileHandler handler(&m_items);
251 parser.setContentHandler(&handler);
252 parser.parse(toUTF8(path));
257 * @brief Save data from member variables to path-file.
258 * @param [in] path Path to project file.
259 * @param [out] sError Error string if error happened.
260 * @return true if saving succeeded, false if error happened.
262 bool ProjectFile::Save(const String& path) const
264 FileStream out(toUTF8(path), FileStream::trunc);
265 XMLWriter writer(out, XMLWriter::WRITE_XML_DECLARATION | XMLWriter::PRETTY_PRINT);
266 writer.startDocument();
267 writer.startElement("", "", Root_element_name);
269 for (auto& item : m_items)
271 writer.startElement("", "", Paths_element_name);
273 if (!item.m_paths.GetLeft().empty())
274 writeElement(writer, Left_element_name, toUTF8(item.m_paths.GetLeft()));
275 if (!item.m_paths.GetMiddle().empty())
276 writeElement(writer, Middle_element_name, toUTF8(item.m_paths.GetMiddle()));
277 if (!item.m_paths.GetRight().empty())
278 writeElement(writer, Right_element_name, toUTF8(item.m_paths.GetRight()));
279 if (!item.m_filter.empty())
280 writeElement(writer, Filter_element_name, toUTF8(item.m_filter));
281 writeElement(writer, Subfolders_element_name, item.m_subfolders != 0 ? "1" : "0");
282 writeElement(writer, Left_ro_element_name, item.m_bLeftReadOnly ? "1" : "0");
283 if (!item.m_paths.GetMiddle().empty())
284 writeElement(writer, Middle_ro_element_name, item.m_bMiddleReadOnly ? "1" : "0");
285 writeElement(writer, Right_ro_element_name, item.m_bRightReadOnly ? "1" : "0");
287 writer.endElement("", "", Paths_element_name);
290 writer.endElement("", "", Root_element_name);
291 writer.endDocument();