1 /////////////////////////////////////////////////////////////////////////////
3 // This program is free software; you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation; either version 2 of the License, or (at
6 // your option) any later version.
8 // This program is distributed in the hope that it will be useful, but
9 // WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // along with this program; if not, write to the Free Software
15 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
16 /////////////////////////////////////////////////////////////////////////////
18 * @file ProjectFile.cpp
20 * @brief Implementation file for ProjectFile class.
24 #include "ProjectFile.h"
27 #include <Poco/FileStream.h>
28 #include <Poco/XML/XMLWriter.h>
29 #include <Poco/SAX/SAXParser.h>
30 #include <Poco/SAX/ContentHandler.h>
31 #include <Poco/Exception.h>
32 #include "UnicodeString.h"
35 using Poco::FileStream;
36 using Poco::XML::SAXParser;
37 using Poco::XML::ContentHandler;
38 using Poco::XML::Locator;
39 using Poco::XML::XMLWriter;
40 using Poco::XML::XMLChar;
41 using Poco::XML::XMLString;
42 using Poco::XML::Attributes;
43 using Poco::Exception;
47 // Constants for xml element names
48 const char Root_element_name[] = "project";
49 const char Paths_element_name[] = "paths";
50 const char Left_element_name[] = "left";
51 const char Middle_element_name[] = "middle";
52 const char Right_element_name[] = "right";
53 const char Filter_element_name[] = "filter";
54 const char Subfolders_element_name[] = "subfolders";
55 const char Left_ro_element_name[] = "left-readonly";
56 const char Middle_ro_element_name[] = "middle-readonly";
57 const char Right_ro_element_name[] = "right-readonly";
62 String xmlch2tstr(const XMLChar *ch, int length)
64 return toTString(std::string(ch, length));
67 void writeElement(XMLWriter& writer, const std::string& tagname, const std::string& characters)
69 writer.startElement("", "", tagname);
70 writer.characters(characters);
71 writer.endElement("", "", tagname);
76 class ProjectFileHandler: public ContentHandler
79 explicit ProjectFileHandler(std::list<ProjectFileItem> *pProject) : m_pProject(pProject) {}
81 void setDocumentLocator(const Locator* loc) {}
82 void startDocument() {}
84 void startElement(const XMLString& uri, const XMLString& localName, const XMLString& qname, const Attributes& attributes)
86 if (localName == Paths_element_name)
87 m_pProject->push_back(ProjectFileItem{});
88 m_stack.push(localName);
90 void endElement(const XMLString& uri, const XMLString& localName, const XMLString& qname)
94 void characters(const XMLChar ch[], int start, int length)
96 if (m_stack.size() != 3 && m_pProject->size() == 0)
99 ProjectFileItem& currentItem = m_pProject->back();
101 const std::string& nodename = m_stack.top();
102 if (nodename == Left_element_name)
104 currentItem.m_paths.SetLeft(currentItem.m_paths.GetLeft() + xmlch2tstr(ch + start, length), false);
105 currentItem.m_bHasLeft = true;
107 else if (nodename == Middle_element_name)
109 currentItem.m_paths.SetMiddle(currentItem.m_paths.GetMiddle() + xmlch2tstr(ch + start, length), false);
110 currentItem.m_bHasMiddle = true;
112 else if (nodename == Right_element_name)
114 currentItem.m_paths.SetRight(currentItem.m_paths.GetRight() + xmlch2tstr(ch + start, length), false);
115 currentItem.m_bHasRight = true;
117 else if (nodename == Filter_element_name)
119 currentItem.m_filter += xmlch2tstr(ch + start, length);
120 currentItem.m_bHasFilter = true;
122 else if (nodename == Subfolders_element_name)
124 currentItem.m_subfolders = atoi(std::string(ch + start, length).c_str());
125 currentItem.m_bHasSubfolders = true;
127 else if (nodename == Left_ro_element_name)
129 currentItem.m_bLeftReadOnly = atoi(std::string(ch + start, length).c_str()) != 0;
131 else if (nodename == Middle_ro_element_name)
133 currentItem.m_bMiddleReadOnly = atoi(std::string(ch + start, length).c_str()) != 0;
135 else if (nodename == Right_ro_element_name)
137 currentItem.m_bRightReadOnly = atoi(std::string(ch + start, length).c_str()) != 0;
140 void ignorableWhitespace(const XMLChar ch[], int start, int length) {}
141 void processingInstruction(const XMLString& target, const XMLString& data) {}
142 void startPrefixMapping(const XMLString& prefix, const XMLString& uri) {}
143 void endPrefixMapping(const XMLString& prefix) {}
144 void skippedEntity(const XMLString& name) {}
147 std::list<ProjectFileItem> *m_pProject = nullptr;
148 std::stack<std::string> m_stack;
151 /** @brief File extension for path files */
152 const String ProjectFile::PROJECTFILE_EXT = toTString("WinMerge");
155 * @brief Standard constructor.
157 ProjectFileItem::ProjectFileItem()
159 , m_bHasMiddle(false)
161 , m_bHasFilter(false)
162 , m_bHasSubfolders(false)
164 , m_bLeftReadOnly(false)
165 , m_bMiddleReadOnly(false)
166 , m_bRightReadOnly(false)
171 * @brief Returns if left path is defined in project file.
172 * @return true if project file has left path.
174 bool ProjectFileItem::HasLeft() const
180 * @brief Returns if middle path is defined.
182 bool ProjectFileItem::HasMiddle() const
188 * @brief Returns if right path is defined in project file.
189 * @return true if project file has right path.
191 bool ProjectFileItem::HasRight() const
197 * @brief Returns if filter is defined in project file.
198 * @return true if project file has filter.
200 bool ProjectFileItem::HasFilter() const
206 * @brief Returns if subfolder is defined in projectfile.
207 * @return true if project file has subfolder definition.
209 bool ProjectFileItem::HasSubfolders() const
211 return m_bHasSubfolders;
215 * @brief Returns left path.
216 * @param [out] pReadOnly true if readonly was specified for path.
219 String ProjectFileItem::GetLeft(bool * pReadOnly /*= nullptr*/) const
221 if (pReadOnly != nullptr)
222 *pReadOnly = m_bLeftReadOnly;
223 return m_paths.GetLeft();
227 * @brief Returns if left path is specified read-only.
228 * @return true if left path is read-only, false otherwise.
230 bool ProjectFileItem::GetLeftReadOnly() const
232 return m_bLeftReadOnly;
236 * @brief Set left path, returns old left path.
237 * @param [in] sLeft Left path.
238 * @param [in] bReadOnly Will path be recorded read-only?
240 void ProjectFileItem::SetLeft(const String& sLeft, const bool * pReadOnly /*= nullptr*/)
242 m_paths.SetLeft(sLeft, false);
243 if (pReadOnly != nullptr)
244 m_bLeftReadOnly = *pReadOnly;
248 * @brief Returns middle path.
249 * @param [out] pReadOnly true if readonly was specified for path.
251 String ProjectFileItem::GetMiddle(bool * pReadOnly /*= nullptr*/) const
253 if (pReadOnly != nullptr)
254 *pReadOnly = m_bMiddleReadOnly;
255 return m_paths.GetMiddle();
259 * @brief Returns if middle path is specified read-only.
261 bool ProjectFileItem::GetMiddleReadOnly() const
263 return m_bMiddleReadOnly;
267 * @brief Set middle path, returns old middle path.
268 * @param [in] sMiddle Middle path.
269 * @param [in] bReadOnly Will path be recorded read-only?
271 void ProjectFileItem::SetMiddle(const String& sMiddle, const bool * pReadOnly /*= nullptr*/)
273 m_paths.SetMiddle(sMiddle, false);
274 if (pReadOnly != nullptr)
275 m_bMiddleReadOnly = *pReadOnly;
281 * @brief Returns right path.
282 * @param [out] pReadOnly true if readonly was specified for path.
283 * @return Right path.
285 String ProjectFileItem::GetRight(bool * pReadOnly /*= nullptr*/) const
287 if (pReadOnly != nullptr)
288 *pReadOnly = m_bRightReadOnly;
289 return m_paths.GetRight();
293 * @brief Returns if right path is specified read-only.
294 * @return true if right path is read-only, false otherwise.
296 bool ProjectFileItem::GetRightReadOnly() const
298 return m_bRightReadOnly;
302 * @brief Set right path, returns old right path.
303 * @param [in] sRight Right path.
304 * @param [in] bReadOnly Will path be recorded read-only?
306 void ProjectFileItem::SetRight(const String& sRight, const bool * pReadOnly /*= nullptr*/)
308 m_paths.SetRight(sRight, false);
309 if (pReadOnly != nullptr)
310 m_bRightReadOnly = *pReadOnly;
314 * @brief Returns filter.
315 * @return Filter string.
317 String ProjectFileItem::GetFilter() const
324 * @param [in] sFilter New filter string to set.
326 void ProjectFileItem::SetFilter(const String& sFilter)
332 * @brief Returns subfolder included -setting.
333 * @return != 0 if subfolders are included.
335 int ProjectFileItem::GetSubfolders() const
341 * @brief set subfolder.
342 * @param [in] iSubfolder New value for subfolder inclusion.
344 void ProjectFileItem::SetSubfolders(bool bSubfolder)
346 m_subfolders = bSubfolder ? 1 : 0;
352 * @param [in] files Files in project
353 * @param [in] bSubFolders If true subfolders included (recursive compare)
355 void ProjectFileItem::SetPaths(const PathContext& files, bool bSubfolders)
358 m_subfolders = bSubfolders;
362 * @brief Returns left and right paths and recursive from project file
364 * @param [out] files Files in project
365 * @param [out] bSubFolders If true subfolders included (recursive compare)
367 void ProjectFileItem::GetPaths(PathContext& files, bool & bSubfolders) const
371 bSubfolders = (GetSubfolders() == 1);
375 * @brief Open given path-file and read data from it to member variables.
376 * @param [in] path Path to project file.
377 * @param [out] sError Error string if error happened.
378 * @return true if reading succeeded, false if error happened.
380 bool ProjectFile::Read(const String& path)
382 ProjectFileHandler handler(&m_items);
384 parser.setContentHandler(&handler);
385 parser.parse(toUTF8(path));
390 * @brief Save data from member variables to path-file.
391 * @param [in] path Path to project file.
392 * @param [out] sError Error string if error happened.
393 * @return true if saving succeeded, false if error happened.
395 bool ProjectFile::Save(const String& path) const
397 FileStream out(toUTF8(path), FileStream::trunc);
398 XMLWriter writer(out, XMLWriter::WRITE_XML_DECLARATION | XMLWriter::PRETTY_PRINT);
399 writer.startDocument();
400 writer.startElement("", "", Root_element_name);
402 for (auto& item : m_items)
404 writer.startElement("", "", Paths_element_name);
406 if (!item.m_paths.GetLeft().empty())
407 writeElement(writer, Left_element_name, toUTF8(item.m_paths.GetLeft()));
408 if (!item.m_paths.GetMiddle().empty())
409 writeElement(writer, Middle_element_name, toUTF8(item.m_paths.GetMiddle()));
410 if (!item.m_paths.GetRight().empty())
411 writeElement(writer, Right_element_name, toUTF8(item.m_paths.GetRight()));
412 if (!item.m_filter.empty())
413 writeElement(writer, Filter_element_name, toUTF8(item.m_filter));
414 writeElement(writer, Subfolders_element_name, item.m_subfolders != 0 ? "1" : "0");
415 writeElement(writer, Left_ro_element_name, item.m_bLeftReadOnly ? "1" : "0");
416 if (!item.m_paths.GetMiddle().empty())
417 writeElement(writer, Middle_ro_element_name, item.m_bMiddleReadOnly ? "1" : "0");
418 writeElement(writer, Right_ro_element_name, item.m_bRightReadOnly ? "1" : "0");
420 writer.endElement("", "", Paths_element_name);
423 writer.endElement("", "", Root_element_name);
424 writer.endDocument();