OSDN Git Service

Allow multiple <paths> in project file
[winmerge-jp/winmerge-jp.git] / Src / ProjectFile.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 //    License (GPLv2+):
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.
7 //    
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.
12 //
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 /////////////////////////////////////////////////////////////////////////////
17 /** 
18  * @file  ProjectFile.cpp
19  *
20  * @brief Implementation file for ProjectFile class.
21  */
22
23 #include "pch.h"
24 #include "ProjectFile.h"
25 #include <stack>
26 #include <string>
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"
33 #include "unicoder.h"
34
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;
44 using ucr::toTString;
45 using ucr::toUTF8;
46
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";
58
59 namespace
60 {
61
62 String xmlch2tstr(const XMLChar *ch, int length)
63 {
64         return toTString(std::string(ch, length));
65 }
66
67 void writeElement(XMLWriter& writer, const std::string& tagname, const std::string& characters)
68 {
69         writer.startElement("", "", tagname);
70         writer.characters(characters);
71         writer.endElement("", "", tagname);
72 }
73
74 }
75
76 class ProjectFileHandler: public ContentHandler
77 {
78 public:
79         explicit ProjectFileHandler(std::list<ProjectFileItem> *pProject) : m_pProject(pProject) {}
80
81         void setDocumentLocator(const Locator* loc) {}
82         void startDocument() {}
83         void endDocument() {}
84         void startElement(const XMLString& uri, const XMLString& localName, const XMLString& qname, const Attributes& attributes)
85         {
86                 if (localName == Paths_element_name)
87                         m_pProject->push_back(ProjectFileItem{});
88                 m_stack.push(localName);
89         }
90         void endElement(const XMLString& uri, const XMLString& localName, const XMLString& qname)
91         {
92                 m_stack.pop();
93         }
94         void characters(const XMLChar ch[], int start, int length)
95         {
96                 if (m_stack.size() != 3 && m_pProject->size() == 0)
97                         return;
98
99                 ProjectFileItem& currentItem = m_pProject->back();
100
101                 const std::string& nodename = m_stack.top();
102                 if (nodename == Left_element_name)
103                 {
104                         currentItem.m_paths.SetLeft(currentItem.m_paths.GetLeft() + xmlch2tstr(ch + start, length), false);
105                         currentItem.m_bHasLeft = true;
106                 }
107                 else if (nodename == Middle_element_name)
108                 {
109                         currentItem.m_paths.SetMiddle(currentItem.m_paths.GetMiddle() + xmlch2tstr(ch + start, length), false);
110                         currentItem.m_bHasMiddle = true;
111                 }
112                 else if (nodename == Right_element_name)
113                 {
114                         currentItem.m_paths.SetRight(currentItem.m_paths.GetRight() + xmlch2tstr(ch + start, length), false);
115                         currentItem.m_bHasRight = true;
116                 }
117                 else if (nodename == Filter_element_name)
118                 {
119                         currentItem.m_filter += xmlch2tstr(ch + start, length);
120                         currentItem.m_bHasFilter = true;
121                 }
122                 else if (nodename == Subfolders_element_name)
123                 {
124                         currentItem.m_subfolders = atoi(std::string(ch + start, length).c_str());
125                         currentItem.m_bHasSubfolders = true;
126                 }
127                 else if (nodename == Left_ro_element_name)
128                 {
129                         currentItem.m_bLeftReadOnly = atoi(std::string(ch + start, length).c_str()) != 0;
130                 }
131                 else if (nodename == Middle_ro_element_name)
132                 {
133                         currentItem.m_bMiddleReadOnly = atoi(std::string(ch +  start, length).c_str()) != 0;
134                 }
135                 else if (nodename == Right_ro_element_name)
136                 {
137                         currentItem.m_bRightReadOnly = atoi(std::string(ch + start, length).c_str()) != 0;
138                 }
139         }
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) {}
145
146 private:
147         std::list<ProjectFileItem> *m_pProject = nullptr;
148         std::stack<std::string> m_stack;
149 };
150
151 /** @brief File extension for path files */
152 const String ProjectFile::PROJECTFILE_EXT = toTString("WinMerge");
153
154 /** 
155  * @brief Standard constructor.
156  */
157  ProjectFileItem::ProjectFileItem()
158 : m_bHasLeft(false)
159 , m_bHasMiddle(false)
160 , m_bHasRight(false)
161 , m_bHasFilter(false)
162 , m_bHasSubfolders(false)
163 , m_subfolders(-1)
164 , m_bLeftReadOnly(false)
165 , m_bMiddleReadOnly(false)
166 , m_bRightReadOnly(false)
167 {
168 }
169
170 /** 
171  * @brief Returns if left path is defined in project file.
172  * @return true if project file has left path.
173  */
174 bool ProjectFileItem::HasLeft() const
175 {
176         return m_bHasLeft;
177 }
178
179 /** 
180  * @brief Returns if middle path is defined.
181  */
182 bool ProjectFileItem::HasMiddle() const
183 {
184         return m_bHasMiddle;
185 }
186
187 /** 
188  * @brief Returns if right path is defined in project file.
189  * @return true if project file has right path.
190  */
191 bool ProjectFileItem::HasRight() const
192 {
193         return m_bHasRight;
194 }
195
196 /** 
197  * @brief Returns if filter is defined in project file.
198  * @return true if project file has filter.
199  */
200 bool ProjectFileItem::HasFilter() const
201 {
202         return m_bHasFilter;
203 }
204
205 /** 
206  * @brief Returns if subfolder is defined in projectfile.
207  * @return true if project file has subfolder definition.
208  */
209 bool ProjectFileItem::HasSubfolders() const
210 {
211         return m_bHasSubfolders;
212 }
213
214 /** 
215  * @brief Returns left path.
216  * @param [out] pReadOnly true if readonly was specified for path.
217  * @return Left path.
218  */
219 String ProjectFileItem::GetLeft(bool * pReadOnly /*= nullptr*/) const
220 {
221         if (pReadOnly != nullptr)
222                 *pReadOnly = m_bLeftReadOnly;
223         return m_paths.GetLeft();
224 }
225
226 /** 
227  * @brief Returns if left path is specified read-only.
228  * @return true if left path is read-only, false otherwise.
229  */
230 bool ProjectFileItem::GetLeftReadOnly() const
231 {
232         return m_bLeftReadOnly;
233 }
234
235 /** 
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?
239  */
240 void ProjectFileItem::SetLeft(const String& sLeft, const bool * pReadOnly /*= nullptr*/)
241 {
242         m_paths.SetLeft(sLeft, false);
243         if (pReadOnly != nullptr)
244                 m_bLeftReadOnly = *pReadOnly;
245 }
246
247 /** 
248  * @brief Returns middle path.
249  * @param [out] pReadOnly true if readonly was specified for path.
250  */
251 String ProjectFileItem::GetMiddle(bool * pReadOnly /*= nullptr*/) const
252 {
253         if (pReadOnly != nullptr)
254                 *pReadOnly = m_bMiddleReadOnly;
255         return m_paths.GetMiddle();
256 }
257
258 /** 
259  * @brief Returns if middle path is specified read-only.
260  */
261 bool ProjectFileItem::GetMiddleReadOnly() const
262 {
263         return m_bMiddleReadOnly;
264 }
265
266 /** 
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?
270  */
271 void ProjectFileItem::SetMiddle(const String& sMiddle, const bool * pReadOnly /*= nullptr*/)
272 {
273         m_paths.SetMiddle(sMiddle, false);
274         if (pReadOnly != nullptr)
275                 m_bMiddleReadOnly = *pReadOnly;
276
277         return;
278 }
279
280 /** 
281  * @brief Returns right path.
282  * @param [out] pReadOnly true if readonly was specified for path.
283  * @return Right path.
284  */
285 String ProjectFileItem::GetRight(bool * pReadOnly /*= nullptr*/) const
286 {
287         if (pReadOnly != nullptr)
288                 *pReadOnly = m_bRightReadOnly;
289         return m_paths.GetRight();
290 }
291
292 /** 
293  * @brief Returns if right path is specified read-only.
294  * @return true if right path is read-only, false otherwise.
295  */
296 bool ProjectFileItem::GetRightReadOnly() const
297 {
298         return m_bRightReadOnly;
299 }
300
301 /** 
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?
305  */
306 void ProjectFileItem::SetRight(const String& sRight, const bool * pReadOnly /*= nullptr*/)
307 {
308         m_paths.SetRight(sRight, false);
309         if (pReadOnly != nullptr)
310                 m_bRightReadOnly = *pReadOnly;
311 }
312
313 /** 
314  * @brief Returns filter.
315  * @return Filter string.
316  */
317 String ProjectFileItem::GetFilter() const
318 {
319         return m_filter;
320 }
321
322 /** 
323  * @brief Set filter.
324  * @param [in] sFilter New filter string to set.
325  */
326 void ProjectFileItem::SetFilter(const String& sFilter)
327 {
328         m_filter = sFilter;
329 }
330
331 /** 
332  * @brief Returns subfolder included -setting.
333  * @return != 0 if subfolders are included.
334  */
335 int ProjectFileItem::GetSubfolders() const
336 {
337         return m_subfolders;
338 }
339
340 /** 
341  * @brief set subfolder.
342  * @param [in] iSubfolder New value for subfolder inclusion.
343  */
344 void ProjectFileItem::SetSubfolders(bool bSubfolder)
345 {
346         m_subfolders = bSubfolder ? 1 : 0;
347 }
348
349 /** 
350  * @brief 
351  *
352  * @param [in] files Files in project
353  * @param [in] bSubFolders If true subfolders included (recursive compare)
354  */
355 void ProjectFileItem::SetPaths(const PathContext& files, bool bSubfolders)
356 {
357         m_paths = files;
358         m_subfolders = bSubfolders;
359 }
360
361 /** 
362  * @brief Returns left and right paths and recursive from project file
363  * 
364  * @param [out] files Files in project
365  * @param [out] bSubFolders If true subfolders included (recursive compare)
366  */
367 void ProjectFileItem::GetPaths(PathContext& files, bool & bSubfolders) const
368 {
369         files = m_paths;
370         if (HasSubfolders())
371                 bSubfolders = (GetSubfolders() == 1);
372 }
373
374 /** 
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.
379  */
380 bool ProjectFile::Read(const String& path)
381 {
382         ProjectFileHandler handler(&m_items);
383         SAXParser parser;
384         parser.setContentHandler(&handler);
385         parser.parse(toUTF8(path));
386         return true;
387 }
388
389 /** 
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.
394  */
395 bool ProjectFile::Save(const String& path) const
396 {
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);
401         {
402                 for (auto& item : m_items)
403                 {
404                         writer.startElement("", "", Paths_element_name);
405                         {
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");
419                         }
420                         writer.endElement("", "", Paths_element_name);
421                 }
422         }
423         writer.endElement("", "", Root_element_name);
424         writer.endDocument();
425         return true;
426 }
427