OSDN Git Service

Fix issue #784: Error on try to show differences between two different gif
[winmerge-jp/winmerge-jp.git] / Src / ProjectFile.cpp
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /** 
3  * @file  ProjectFile.cpp
4  *
5  * @brief Implementation file for ProjectFile class.
6  */
7
8 #include "pch.h"
9 #include "ProjectFile.h"
10 #include <stack>
11 #include <string>
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"
18 #include "unicoder.h"
19
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;
29 using ucr::toTString;
30 using ucr::toUTF8;
31
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";
43
44 namespace
45 {
46
47 String xmlch2tstr(const XMLChar *ch, int length)
48 {
49         return toTString(std::string(ch, length));
50 }
51
52 void writeElement(XMLWriter& writer, const std::string& tagname, const std::string& characters)
53 {
54         writer.startElement("", "", tagname);
55         writer.characters(characters);
56         writer.endElement("", "", tagname);
57 }
58
59 }
60
61 class ProjectFileHandler: public ContentHandler
62 {
63 public:
64         explicit ProjectFileHandler(std::list<ProjectFileItem> *pProject) : m_pProject(pProject) {}
65
66         void setDocumentLocator(const Locator* loc) {}
67         void startDocument() {}
68         void endDocument() {}
69         void startElement(const XMLString& uri, const XMLString& localName, const XMLString& qname, const Attributes& attributes)
70         {
71                 if (localName == Paths_element_name)
72                         m_pProject->push_back(ProjectFileItem{});
73                 m_stack.push(localName);
74         }
75         void endElement(const XMLString& uri, const XMLString& localName, const XMLString& qname)
76         {
77                 m_stack.pop();
78         }
79         void characters(const XMLChar ch[], int start, int length)
80         {
81                 if (m_stack.size() != 3 && m_pProject->size() == 0)
82                         return;
83
84                 ProjectFileItem& currentItem = m_pProject->back();
85
86                 const std::string& nodename = m_stack.top();
87                 if (nodename == Left_element_name)
88                 {
89                         currentItem.m_paths.SetLeft(currentItem.m_paths.GetLeft() + xmlch2tstr(ch + start, length), false);
90                         currentItem.m_bHasLeft = true;
91                 }
92                 else if (nodename == Middle_element_name)
93                 {
94                         currentItem.m_paths.SetMiddle(currentItem.m_paths.GetMiddle() + xmlch2tstr(ch + start, length), false);
95                         currentItem.m_bHasMiddle = true;
96                 }
97                 else if (nodename == Right_element_name)
98                 {
99                         currentItem.m_paths.SetRight(currentItem.m_paths.GetRight() + xmlch2tstr(ch + start, length), false);
100                         currentItem.m_bHasRight = true;
101                 }
102                 else if (nodename == Filter_element_name)
103                 {
104                         currentItem.m_filter += xmlch2tstr(ch + start, length);
105                         currentItem.m_bHasFilter = true;
106                 }
107                 else if (nodename == Subfolders_element_name)
108                 {
109                         currentItem.m_subfolders = atoi(std::string(ch + start, length).c_str());
110                         currentItem.m_bHasSubfolders = true;
111                 }
112                 else if (nodename == Left_ro_element_name)
113                 {
114                         currentItem.m_bLeftReadOnly = atoi(std::string(ch + start, length).c_str()) != 0;
115                 }
116                 else if (nodename == Middle_ro_element_name)
117                 {
118                         currentItem.m_bMiddleReadOnly = atoi(std::string(ch +  start, length).c_str()) != 0;
119                 }
120                 else if (nodename == Right_ro_element_name)
121                 {
122                         currentItem.m_bRightReadOnly = atoi(std::string(ch + start, length).c_str()) != 0;
123                 }
124         }
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) {}
130
131 private:
132         std::list<ProjectFileItem> *m_pProject = nullptr;
133         std::stack<std::string> m_stack;
134 };
135
136 /** @brief File extension for path files */
137 const String ProjectFile::PROJECTFILE_EXT = toTString("WinMerge");
138
139 /** 
140  * @brief Standard constructor.
141  */
142  ProjectFileItem::ProjectFileItem()
143 : m_bHasLeft(false)
144 , m_bHasMiddle(false)
145 , m_bHasRight(false)
146 , m_bHasFilter(false)
147 , m_bHasSubfolders(false)
148 , m_subfolders(-1)
149 , m_bLeftReadOnly(false)
150 , m_bMiddleReadOnly(false)
151 , m_bRightReadOnly(false)
152 {
153 }
154
155 /** 
156  * @brief Returns left path.
157  * @param [out] pReadOnly true if readonly was specified for path.
158  * @return Left path.
159  */
160 String ProjectFileItem::GetLeft(bool * pReadOnly /*= nullptr*/) const
161 {
162         if (pReadOnly != nullptr)
163                 *pReadOnly = m_bLeftReadOnly;
164         return m_paths.GetLeft();
165 }
166
167 /** 
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?
171  */
172 void ProjectFileItem::SetLeft(const String& sLeft, const bool * pReadOnly /*= nullptr*/)
173 {
174         m_paths.SetLeft(sLeft, false);
175         if (pReadOnly != nullptr)
176                 m_bLeftReadOnly = *pReadOnly;
177 }
178
179 /** 
180  * @brief Returns middle path.
181  * @param [out] pReadOnly true if readonly was specified for path.
182  */
183 String ProjectFileItem::GetMiddle(bool * pReadOnly /*= nullptr*/) const
184 {
185         if (pReadOnly != nullptr)
186                 *pReadOnly = m_bMiddleReadOnly;
187         return m_paths.GetMiddle();
188 }
189
190 /** 
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?
194  */
195 void ProjectFileItem::SetMiddle(const String& sMiddle, const bool * pReadOnly /*= nullptr*/)
196 {
197         m_paths.SetMiddle(sMiddle, false);
198         if (pReadOnly != nullptr)
199                 m_bMiddleReadOnly = *pReadOnly;
200
201         return;
202 }
203
204 /** 
205  * @brief Returns right path.
206  * @param [out] pReadOnly true if readonly was specified for path.
207  * @return Right path.
208  */
209 String ProjectFileItem::GetRight(bool * pReadOnly /*= nullptr*/) const
210 {
211         if (pReadOnly != nullptr)
212                 *pReadOnly = m_bRightReadOnly;
213         return m_paths.GetRight();
214 }
215
216 /** 
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?
220  */
221 void ProjectFileItem::SetRight(const String& sRight, const bool * pReadOnly /*= nullptr*/)
222 {
223         m_paths.SetRight(sRight, false);
224         if (pReadOnly != nullptr)
225                 m_bRightReadOnly = *pReadOnly;
226 }
227
228 /** 
229  * @brief Returns left and right paths and recursive from project file
230  * 
231  * @param [out] files Files in project
232  * @param [out] bSubFolders If true subfolders included (recursive compare)
233  */
234 void ProjectFileItem::GetPaths(PathContext& files, bool & bSubfolders) const
235 {
236         files = m_paths;
237         if (HasSubfolders())
238                 bSubfolders = (GetSubfolders() == 1);
239 }
240
241 /** 
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.
246  */
247 bool ProjectFile::Read(const String& path)
248 {
249         ProjectFileHandler handler(&m_items);
250         SAXParser parser;
251         parser.setContentHandler(&handler);
252         parser.parse(toUTF8(path));
253         return true;
254 }
255
256 /** 
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.
261  */
262 bool ProjectFile::Save(const String& path) const
263 {
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);
268         {
269                 for (auto& item : m_items)
270                 {
271                         writer.startElement("", "", Paths_element_name);
272                         {
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");
286                         }
287                         writer.endElement("", "", Paths_element_name);
288                 }
289         }
290         writer.endElement("", "", Root_element_name);
291         writer.endDocument();
292         return true;
293 }
294