OSDN Git Service

Avoid infinite loops in the RegularExpression::subst() function when the length of...
[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 Left_desc_element_name[] = "left-desc";
39 const char Middle_desc_element_name[] = "middle-desc";
40 const char Right_desc_element_name[] = "right-desc";
41 const char Filter_element_name[] = "filter";
42 const char Subfolders_element_name[] = "subfolders";
43 const char Left_ro_element_name[] = "left-readonly";
44 const char Middle_ro_element_name[] = "middle-readonly";
45 const char Right_ro_element_name[] = "right-readonly";
46 const char Unpacker_element_name[] = "unpacker";
47 const char Prediffer_element_name[] = "prediffer";
48 const char Window_type_element_name[] = "window-type";
49 const char Table_delimiter_element_name[] = "table-delimiter";
50 const char Table_quote_element_name[] = "table-quote";
51 const char Table_allownewlinesinquotes_element_name[] = "table-allownewlinesinquotes";
52 const char White_spaces_element_name[] = "white-spaces";
53 const char Ignore_blank_lines_element_name[] = "ignore-blank-lines";
54 const char Ignore_case_element_name[] = "ignore-case";
55 const char Ignore_cr_diff_element_name[] = "ignore-carriage-return-diff";
56 const char Ignore_numbers_element_name[] = "ignore-numbers";
57 const char Ignore_codepage_diff_element_name[] = "ignore-codepage-diff";
58 const char Ignore_comment_diff_element_name[] = "ignore-comment-diff";
59 const char Compare_method_element_name[] = "compare-method";
60 const char Hidden_list_element_name[] = "hidden-list";
61 const char Hidden_items_element_name[] = "hidden-item";
62
63 namespace
64 {
65
66 String xmlch2tstr(const XMLChar *ch, int length)
67 {
68         return toTString(std::string(ch, length));
69 }
70
71 void writeElement(XMLWriter& writer, const std::string& tagname, const std::string& characters)
72 {
73         writer.startElement("", "", tagname);
74         writer.characters(characters);
75         writer.endElement("", "", tagname);
76 }
77
78 void saveHiddenItems(XMLWriter& writer, const std::vector<String>& hiddenItems) 
79 {
80         writer.startElement("", "", Hidden_list_element_name);
81         for (const auto& hiddenItem : hiddenItems) {
82                 writeElement(writer, Hidden_items_element_name, toUTF8(hiddenItem));
83         }
84         writer.endElement("", "", Hidden_list_element_name);
85 }
86
87 }
88
89 class ProjectFileHandler: public ContentHandler
90 {
91 public:
92         explicit ProjectFileHandler(std::list<ProjectFileItem> *pProject) : m_pProject(pProject) {}
93
94         void setDocumentLocator(const Locator* loc) {}
95         void startDocument() {}
96         void endDocument() {}
97         void startElement(const XMLString& uri, const XMLString& localName, const XMLString& qname, const Attributes& attributes)
98         {
99                 if (localName == Paths_element_name)
100                         m_pProject->push_back(ProjectFileItem{});
101                 m_stack.push(localName);
102         }
103         void endElement(const XMLString& uri, const XMLString& localName, const XMLString& qname)
104         {
105                 m_stack.pop();
106         }
107         void characters(const XMLChar ch[], int start, int length)
108         {
109                 //process .winmerge entries of third level deep only
110                 if (m_stack.size() != 3 && m_pProject->size() == 0)
111                         return;
112
113                 ProjectFileItem& currentItem = m_pProject->back();
114
115                 const std::string& nodename = m_stack.top();
116                 const std::string& token = std::string(ch + start, length);
117
118                 if (nodename == Left_element_name)
119                 {
120                         currentItem.m_paths.SetLeft(currentItem.m_paths.GetLeft() + xmlch2tstr(ch + start, length), false);
121                         currentItem.m_bHasLeft = true;
122                 }
123                 else if (nodename == Middle_element_name)
124                 {
125                         currentItem.m_paths.SetMiddle(currentItem.m_paths.GetMiddle() + xmlch2tstr(ch + start, length), false);
126                         currentItem.m_bHasMiddle = true;
127                 }
128                 else if (nodename == Right_element_name)
129                 {
130                         currentItem.m_paths.SetRight(currentItem.m_paths.GetRight() + xmlch2tstr(ch + start, length), false);
131                         currentItem.m_bHasRight = true;
132                 }
133                 else if (nodename == Left_desc_element_name)
134                 {
135                         currentItem.m_leftDesc += xmlch2tstr(ch + start, length);
136                         currentItem.m_bHasLeftDesc = true;
137                 }
138                 else if (nodename == Middle_desc_element_name)
139                 {
140                         currentItem.m_middleDesc += xmlch2tstr(ch + start, length);
141                         currentItem.m_bHasMiddleDesc = true;
142                 }
143                 else if (nodename == Right_desc_element_name)
144                 {
145                         currentItem.m_rightDesc += xmlch2tstr(ch + start, length);
146                         currentItem.m_bHasRightDesc = true;
147                 }
148                 else if (nodename == Filter_element_name)
149                 {
150                         currentItem.m_filter += xmlch2tstr(ch + start, length);
151                         currentItem.m_bHasFilter = true;
152                 }
153                 else if (nodename == Subfolders_element_name)
154                 {
155                         currentItem.m_subfolders = atoi(token.c_str());
156                         currentItem.m_bHasSubfolders = true;
157                 }
158                 else if (nodename == Left_ro_element_name)
159                 {
160                         currentItem.m_bLeftReadOnly = atoi(token.c_str()) != 0;
161                 }
162                 else if (nodename == Middle_ro_element_name)
163                 {
164                         currentItem.m_bMiddleReadOnly = atoi(token.c_str()) != 0;
165                 }
166                 else if (nodename == Right_ro_element_name)
167                 {
168                         currentItem.m_bRightReadOnly = atoi(token.c_str()) != 0;
169                 }
170                 else if (nodename == Unpacker_element_name)
171                 {
172                         currentItem.m_unpacker += xmlch2tstr(ch + start, length);
173                         currentItem.m_bHasUnpacker = true;
174                 }
175                 else if (nodename == Prediffer_element_name)
176                 {
177                         currentItem.m_prediffer += xmlch2tstr(ch + start, length);
178                         currentItem.m_bHasPrediffer = true;
179                 }
180                 else if (nodename == Window_type_element_name)
181                 {
182                         currentItem.m_nWindowType = atoi(token.c_str());
183                         currentItem.m_bHasWindowType = true;
184                 }
185                 else if (nodename == Table_delimiter_element_name)
186                 {
187                         currentItem.m_cTableDelimiter = token.c_str()[0];
188                         currentItem.m_bHasTableDelimiter = true;
189                 }
190                 else if (nodename == Table_quote_element_name)
191                 {
192                         currentItem.m_cTableQuote = token.c_str()[0];
193                         currentItem.m_bHasTableQuote = true;
194                 }
195                 else if (nodename == Table_allownewlinesinquotes_element_name)
196                 {
197                         currentItem.m_bTableAllowNewLinesInQuotes = atoi(token.c_str());
198                         currentItem.m_bHasTableAllowNewLinesInQuotes = true;
199                 }
200                 else if (nodename == White_spaces_element_name)
201                 {
202                         currentItem.m_nIgnoreWhite = atoi(token.c_str());
203                         currentItem.m_bHasIgnoreWhite = true;
204                 }
205                 else if (nodename == Ignore_blank_lines_element_name)
206                 {
207                         currentItem.m_bIgnoreBlankLines = atoi(token.c_str()) != 0;
208                         currentItem.m_bHasIgnoreBlankLines = true;
209                 }
210                 else if (nodename == Ignore_case_element_name)
211                 {
212                         currentItem.m_bIgnoreCase = atoi(token.c_str()) != 0;
213                         currentItem.m_bHasIgnoreCase = true;
214                 }
215                 else if (nodename == Ignore_cr_diff_element_name)
216                 {
217                         currentItem.m_bIgnoreEol = atoi(token.c_str()) != 0;
218                         currentItem.m_bHasIgnoreEol = true;
219                 }
220                 else if (nodename == Ignore_numbers_element_name)
221                 {
222                         currentItem.m_bIgnoreNumbers = atoi(token.c_str()) != 0;
223                         currentItem.m_bHasIgnoreNumbers = true;
224                 }
225                 else if (nodename == Ignore_codepage_diff_element_name)
226                 {
227                         currentItem.m_bIgnoreCodepage = atoi(token.c_str()) != 0;
228                         currentItem.m_bHasIgnoreCodepage = true;
229                 }
230                 else if (nodename == Ignore_comment_diff_element_name)
231                 {
232                         currentItem.m_bFilterCommentsLines = atoi(token.c_str()) != 0;
233                         currentItem.m_bHasFilterCommentsLines = true;
234                 }
235                 else if (nodename == Compare_method_element_name)
236                 {
237                         currentItem.m_nCompareMethod = atoi(token.c_str());
238                         currentItem.m_bHasCompareMethod = true;
239                 }
240                 //This nodes are under Hidden_list_element_name
241                 else if (nodename ==  Hidden_items_element_name)
242                 {
243                         currentItem.m_vSavedHiddenItems.push_back(toTString(token));
244                         currentItem.m_bHasHiddenItems = true;
245                 }
246         }
247         void ignorableWhitespace(const XMLChar ch[], int start, int length)     {}
248         void processingInstruction(const XMLString& target, const XMLString& data) {}
249         void startPrefixMapping(const XMLString& prefix, const XMLString& uri) {}
250         void endPrefixMapping(const XMLString& prefix) {}
251         void skippedEntity(const XMLString& name) {}
252
253 private:
254         std::list<ProjectFileItem> *m_pProject = nullptr;
255         std::stack<std::string> m_stack;
256 };
257
258 /** @brief File extension for path files */
259 const String ProjectFile::PROJECTFILE_EXT = toTString("WinMerge");
260
261 /** 
262  * @brief Standard constructor.
263  */
264  ProjectFileItem::ProjectFileItem()
265 : m_bHasLeft(false)
266 , m_bHasMiddle(false)
267 , m_bHasRight(false)
268 , m_bHasLeftDesc(false)
269 , m_bHasMiddleDesc(false)
270 , m_bHasRightDesc(false)
271 , m_bHasFilter(false)
272 , m_bHasSubfolders(false)
273 , m_bHasUnpacker(false)
274 , m_bHasPrediffer(false)
275 , m_subfolders(-1)
276 , m_bLeftReadOnly(false)
277 , m_bMiddleReadOnly(false)
278 , m_bRightReadOnly(false)
279 , m_bHasWindowType(false)
280 , m_nWindowType(-1)
281 , m_bHasTableDelimiter(false)
282 , m_cTableDelimiter(',')
283 , m_bHasTableQuote(false)
284 , m_cTableQuote('\"')
285 , m_bHasTableAllowNewLinesInQuotes(false)
286 , m_bTableAllowNewLinesInQuotes(true)
287 , m_bHasIgnoreWhite(false)
288 , m_nIgnoreWhite(0)
289 , m_bHasIgnoreBlankLines(false)
290 , m_bIgnoreBlankLines(false)
291 , m_bHasIgnoreCase(false)
292 , m_bIgnoreCase(false)
293 , m_bHasIgnoreEol(false)
294 , m_bIgnoreEol(false)
295 , m_bHasIgnoreNumbers(false)
296 , m_bIgnoreNumbers(false)
297 , m_bHasIgnoreCodepage(false)
298 , m_bIgnoreCodepage(false)
299 , m_bHasFilterCommentsLines(false)
300 , m_bFilterCommentsLines(false)
301 , m_bHasCompareMethod(false)
302 , m_nCompareMethod(0)
303 , m_bHasHiddenItems(false)
304 , m_bSaveFilter(true)
305 , m_bSaveSubfolders(true)
306 , m_bSaveUnpacker(true)
307 , m_bSavePrediffer(true)
308 , m_bSaveIgnoreWhite(true)
309 , m_bSaveIgnoreBlankLines(true)
310 , m_bSaveIgnoreCase(true)
311 , m_bSaveIgnoreEol(true)
312 , m_bSaveIgnoreNumbers(true)
313 , m_bSaveIgnoreCodepage(true)
314 , m_bSaveFilterCommentsLines(true)
315 , m_bSaveCompareMethod(true)
316 , m_bSaveHiddenItems(true)
317 {
318 }
319
320 /** 
321  * @brief Returns left path.
322  * @param [out] pReadOnly true if readonly was specified for path.
323  * @return Left path.
324  */
325 String ProjectFileItem::GetLeft(bool * pReadOnly /*= nullptr*/) const
326 {
327         if (pReadOnly != nullptr)
328                 *pReadOnly = m_bLeftReadOnly;
329         return m_paths.GetLeft();
330 }
331
332 /** 
333  * @brief Set left path, returns old left path.
334  * @param [in] sLeft Left path.
335  * @param [in] bReadOnly Will path be recorded read-only?
336  */
337 void ProjectFileItem::SetLeft(const String& sLeft, const bool * pReadOnly /*= nullptr*/)
338 {
339         m_paths.SetLeft(sLeft, false);
340         if (pReadOnly != nullptr)
341                 m_bLeftReadOnly = *pReadOnly;
342 }
343
344 /** 
345  * @brief Returns middle path.
346  * @param [out] pReadOnly true if readonly was specified for path.
347  */
348 String ProjectFileItem::GetMiddle(bool * pReadOnly /*= nullptr*/) const
349 {
350         if (pReadOnly != nullptr)
351                 *pReadOnly = m_bMiddleReadOnly;
352         return m_paths.GetMiddle();
353 }
354
355 /** 
356  * @brief Set middle path, returns old middle path.
357  * @param [in] sMiddle Middle path.
358  * @param [in] bReadOnly Will path be recorded read-only?
359  */
360 void ProjectFileItem::SetMiddle(const String& sMiddle, const bool * pReadOnly /*= nullptr*/)
361 {
362         m_paths.SetMiddle(sMiddle, false);
363         if (pReadOnly != nullptr)
364                 m_bMiddleReadOnly = *pReadOnly;
365
366         return;
367 }
368
369 /** 
370  * @brief Returns right path.
371  * @param [out] pReadOnly true if readonly was specified for path.
372  * @return Right path.
373  */
374 String ProjectFileItem::GetRight(bool * pReadOnly /*= nullptr*/) const
375 {
376         if (pReadOnly != nullptr)
377                 *pReadOnly = m_bRightReadOnly;
378         return m_paths.GetRight();
379 }
380
381 /** 
382  * @brief Set right path, returns old right path.
383  * @param [in] sRight Right path.
384  * @param [in] bReadOnly Will path be recorded read-only?
385  */
386 void ProjectFileItem::SetRight(const String& sRight, const bool * pReadOnly /*= nullptr*/)
387 {
388         m_paths.SetRight(sRight, false);
389         if (pReadOnly != nullptr)
390                 m_bRightReadOnly = *pReadOnly;
391 }
392
393 /** 
394  * @brief Returns left and right paths and recursive from project file
395  * 
396  * @param [out] files Files in project
397  * @param [out] bSubFolders If true subfolders included (recursive compare)
398  */
399 void ProjectFileItem::GetPaths(PathContext& files, bool & bSubfolders) const
400 {
401         files = m_paths;
402         if (HasSubfolders())
403                 bSubfolders = (GetSubfolders() == 1);
404 }
405
406 /** 
407  * @brief Open given path-file and read data from it to member variables.
408  * @param [in] path Path to project file.
409  * @param [out] sError Error string if error happened.
410  * @return true if reading succeeded, false if error happened.
411  */
412 bool ProjectFile::Read(const String& path)
413 {
414         ProjectFileHandler handler(&m_items);
415         SAXParser parser;
416         parser.setContentHandler(&handler);
417         parser.parse(toUTF8(path));
418         return true;
419 }
420
421 /** 
422  * @brief Save data from member variables to path-file.
423  * @param [in] path Path to project file.
424  * @param [out] sError Error string if error happened.
425  * @return true if saving succeeded, false if error happened.
426  */
427 bool ProjectFile::Save(const String& path) const
428 {
429         FileStream out(toUTF8(path), FileStream::trunc);
430         XMLWriter writer(out, XMLWriter::WRITE_XML_DECLARATION | XMLWriter::PRETTY_PRINT);
431         writer.startDocument();
432         writer.startElement("", "", Root_element_name);
433         {
434                 for (auto& item : m_items)
435                 {
436                         writer.startElement("", "", Paths_element_name);
437                         {
438                                 if (!item.m_paths.GetLeft().empty())
439                                         writeElement(writer, Left_element_name, toUTF8(item.m_paths.GetLeft()));
440                                 if (!item.m_paths.GetMiddle().empty())
441                                         writeElement(writer, Middle_element_name, toUTF8(item.m_paths.GetMiddle()));
442                                 if (!item.m_paths.GetRight().empty())
443                                         writeElement(writer, Right_element_name, toUTF8(item.m_paths.GetRight()));
444                                 if (!item.m_leftDesc.empty())
445                                         writeElement(writer, Left_desc_element_name, toUTF8(item.m_leftDesc));
446                                 if (!item.m_middleDesc.empty())
447                                         writeElement(writer, Middle_desc_element_name, toUTF8(item.m_middleDesc));
448                                 if (!item.m_rightDesc.empty())
449                                         writeElement(writer, Right_desc_element_name, toUTF8(item.m_rightDesc));
450                                 if (item.m_bSaveFilter && !item.m_filter.empty())
451                                         writeElement(writer, Filter_element_name, toUTF8(item.m_filter));
452                                 if (item.m_bSaveSubfolders)
453                                         writeElement(writer, Subfolders_element_name, item.m_subfolders != 0 ? "1" : "0");
454                                 writeElement(writer, Left_ro_element_name, item.m_bLeftReadOnly ? "1" : "0");
455                                 if (!item.m_paths.GetMiddle().empty())
456                                         writeElement(writer, Middle_ro_element_name, item.m_bMiddleReadOnly ? "1" : "0");
457                                 writeElement(writer, Right_ro_element_name, item.m_bRightReadOnly ? "1" : "0");
458                                 if (item.m_bSaveUnpacker && !item.m_unpacker.empty())
459                                         writeElement(writer, Unpacker_element_name, toUTF8(item.m_unpacker));
460                                 if (item.m_bSavePrediffer && !item.m_prediffer.empty())
461                                         writeElement(writer, Prediffer_element_name, toUTF8(item.m_prediffer));
462                                 if (item.m_nWindowType != -1)
463                                         writeElement(writer, Window_type_element_name, std::to_string(item.m_nWindowType));
464                                 if (item.m_nWindowType == 2 /* table */)
465                                 {
466                                         writeElement(writer, Table_delimiter_element_name, toUTF8(String(&item.m_cTableDelimiter, 1)));
467                                         writeElement(writer, Table_quote_element_name, toUTF8(String(&item.m_cTableQuote, 1)));
468                                         writeElement(writer, Table_allownewlinesinquotes_element_name, item.m_bTableAllowNewLinesInQuotes ? "1" : "0");
469                                 }
470                                 if (item.m_bSaveIgnoreWhite)
471                                         writeElement(writer, White_spaces_element_name, std::to_string(item.m_nIgnoreWhite));
472                                 if (item.m_bSaveIgnoreBlankLines)
473                                         writeElement(writer, Ignore_blank_lines_element_name, item.m_bIgnoreBlankLines ? "1" : "0");
474                                 if (item.m_bSaveIgnoreCase)
475                                         writeElement(writer, Ignore_case_element_name, item.m_bIgnoreCase ? "1" : "0");
476                                 if (item.m_bSaveIgnoreEol)
477                                         writeElement(writer, Ignore_cr_diff_element_name, item.m_bIgnoreEol ? "1" : "0");
478                                 if (item.m_bSaveIgnoreNumbers)
479                                         writeElement(writer, Ignore_numbers_element_name, item.m_bIgnoreNumbers ? "1" : "0");
480                                 if (item.m_bSaveIgnoreCodepage)
481                                         writeElement(writer, Ignore_codepage_diff_element_name, item.m_bIgnoreCodepage ? "1" : "0");
482                                 if (item.m_bSaveFilterCommentsLines)
483                                         writeElement(writer, Ignore_comment_diff_element_name, item.m_bFilterCommentsLines ? "1" : "0");
484                                 if (item.m_bSaveCompareMethod)
485                                         writeElement(writer, Compare_method_element_name, std::to_string(item.m_nCompareMethod));
486                                 if (item.m_bSaveHiddenItems && item.m_vSavedHiddenItems.size() > 0) 
487                                         saveHiddenItems(writer, item.m_vSavedHiddenItems);
488                         }
489                         writer.endElement("", "", Paths_element_name);
490                 }
491         }
492         writer.endElement("", "", Root_element_name);
493         writer.endDocument();
494         return true;
495 }