OSDN Git Service

BUG: [2372000] Ampersand in folder names ruins the 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 // RCS ID line follows -- this is updated by CVS
23 // $Id$
24
25 #include "stdafx.h"
26 #include <scew/scew.h>
27
28 #include "ProjectFile.h"
29 #include "Merge.h"
30
31 // ATL conversion macro hack for UTF-8 conversion
32 #define UTF82W(lpa) (\
33         ((_lpa = lpa) == NULL) ? NULL : (\
34                 _convert = (lstrlenA(_lpa)+1),\
35                 AtlA2WHelper((LPWSTR) alloca(_convert*2), _lpa, _convert, CP_UTF8)))
36
37 #define W2UTF8(lpw) (\
38         ((_lpw = lpw) == NULL) ? NULL : (\
39                 _convert = (lstrlenW(_lpw)+1)*6,\
40                 AtlW2AHelper((LPSTR) alloca(_convert), _lpw, _convert, CP_UTF8)))
41
42 #define UTF82A(lpu) W2A(UTF82W(lpu))
43 #define A2UTF8(lpa) W2UTF8(A2W(lpa))
44 #ifdef _UNICODE
45 #  define UTF82T(lpu) UTF82W(lpu)
46 #  define T2UTF8(lpw) W2UTF8(lpw)
47 #else
48 #  define UTF82T(lpu) UTF82A(lpu)
49 #  define T2UTF8(lpw) A2UTF8(lpw)
50 #endif
51
52 // Constants for xml element names
53 const char Root_element_name[] = "project";
54 const char Paths_element_name[] = "paths";
55 const char Left_element_name[] = "left";
56 const char Right_element_name[] = "right";
57 const char Filter_element_name[] = "filter";
58 const char Subfolders_element_name[] = "subfolders";
59 const char Left_ro_element_name[] = "left-readonly";
60 const char Right_ro_element_name[] = "right-readonly";
61
62 /** 
63  * @brief Standard constructor.
64  */
65  ProjectFile::ProjectFile()
66 : m_bHasLeft(FALSE)
67 , m_bHasRight(FALSE)
68 , m_bHasFilter(FALSE)
69 , m_bHasSubfolders(FALSE)
70 , m_subfolders(-1)
71 , m_bLeftReadOnly(FALSE)
72 , m_bRightReadOnly(FALSE)
73 {
74 }
75
76 /** 
77  * @brief Open given path-file and read data from it to member variables.
78  * @param [in] path Path to project file.
79  * @param [out] sError Error string if error happened.
80  * @return TRUE if reading succeeded, FALSE if error happened.
81  */
82 BOOL ProjectFile::Read(LPCTSTR path, String *sError)
83 {
84         BOOL loaded = FALSE;
85     scew_tree* tree = NULL;
86     scew_parser* parser = NULL;
87
88     parser = scew_parser_create();
89     scew_parser_ignore_whitespaces(parser, 1);
90
91         FILE * fp = _tfopen(path, _T("r"));
92         if (fp)
93         {
94                 if (scew_parser_load_file_fp(parser, fp))
95                 {
96                         tree = scew_parser_tree(parser);
97
98                         scew_element * root = GetRootElement(tree);
99                         if (root)
100                         {
101                                 // Currently our content is paths, so expect
102                                 // having paths in valid project file!
103                                 if (GetPathsData(root))
104                                         loaded = TRUE;
105                         };
106                 }
107                 scew_tree_free(tree);
108
109                 /* Frees the SCEW parser */
110                 scew_parser_free(parser);
111                 fclose(fp);
112         }
113         return loaded;
114 }
115
116 /** 
117  * @brief Return project file XML's root element.
118  * @param [in] tree XML tree we got from the parser.
119  * @return Root element pointer.
120  */
121 scew_element* ProjectFile::GetRootElement(scew_tree * tree)
122 {
123         scew_element * root = NULL;
124
125         if (tree != NULL)
126         {
127                 root = scew_tree_root(tree);
128         }
129
130         if (root != NULL)
131         {
132                 // Make sure we have correct root element
133                 if (strcmp(Root_element_name, scew_element_name(root)) != 0)
134                 {
135                         root = NULL;
136                 }
137         }
138         return root;
139 }
140
141 /** 
142  * @brief Reads the paths data from the XML data.
143  * This function reads the paths data inside given element in XML data.
144  * @param [in] parent Parent element for the paths data.
145  * @return TRUE if pathdata was found from the file.
146  */
147 BOOL ProjectFile::GetPathsData(scew_element * parent)
148 {
149         USES_CONVERSION;
150         BOOL bFoundPaths = FALSE;
151         scew_element *paths = NULL;
152
153         if (parent != NULL)
154         {
155                 paths = scew_element_by_name(parent, Paths_element_name);
156         }
157
158         if (paths != NULL)
159         {
160                 bFoundPaths = TRUE;
161                 scew_element *left = NULL;
162                 scew_element *right = NULL;
163                 scew_element *filter = NULL;
164                 scew_element *subfolders = NULL;
165                 scew_element *left_ro = NULL;
166                 scew_element *right_ro = NULL;
167
168                 left = scew_element_by_name(paths, Left_element_name);
169                 right = scew_element_by_name(paths, Right_element_name);
170                 filter = scew_element_by_name(paths, Filter_element_name);
171                 subfolders = scew_element_by_name(paths, Subfolders_element_name);
172                 left_ro = scew_element_by_name(paths, Left_ro_element_name);
173                 right_ro = scew_element_by_name(paths, Right_ro_element_name);
174
175                 if (left)
176                 {
177                         LPCSTR path = NULL;
178                         path = scew_element_contents(left);
179                         m_leftFile = UTF82T(path);
180                         m_bHasLeft = TRUE;
181                 }
182                 if (right)
183                 {
184                         LPCSTR path = NULL;
185                         path = scew_element_contents(right);
186                         m_rightFile = UTF82T(path);
187                         m_bHasRight = TRUE;
188                 }
189                 if (filter)
190                 {
191                         LPCSTR filtername = NULL;
192                         filtername = scew_element_contents(filter);
193                         m_filter = UTF82T(filtername);
194                         m_bHasFilter = TRUE;
195                 }
196                 if (subfolders)
197                 {
198                         LPCSTR folders = NULL;
199                         folders = scew_element_contents(subfolders);
200                         m_subfolders = atoi(folders);
201                         m_bHasSubfolders = TRUE;
202                 }
203                 if (left_ro)
204                 {
205                         LPCSTR readonly = NULL;
206                         readonly = scew_element_contents(left_ro);
207                         m_bLeftReadOnly = (atoi(readonly) != 0);
208                 }
209                 if (right_ro)
210                 {
211                         LPCSTR readonly = NULL;
212                         readonly = scew_element_contents(right_ro);
213                         m_bRightReadOnly = (atoi(readonly) != 0);
214                 }
215         }
216         return bFoundPaths;
217 }
218
219 /** 
220  * @brief Save data from member variables to path-file.
221  * @param [in] path Path to project file.
222  * @param [out] sError Error string if error happened.
223  * @return TRUE if saving succeeded, FALSE if error happened.
224  */
225 BOOL ProjectFile::Save(LPCTSTR path, String *sError)
226 {
227         BOOL success = TRUE;
228         scew_tree* tree = NULL;
229         scew_element* root = NULL;
230         scew_element* paths = NULL;
231
232         tree = scew_tree_create();
233         root = scew_tree_add_root(tree, Root_element_name);
234         if (root != NULL)
235         {
236                 paths = AddPathsElement(root);
237         }
238         else
239                 success = FALSE;
240
241         if (paths != NULL)
242         {
243                 AddPathsContent(paths);
244         }
245         else
246                 success = FALSE;
247         
248         scew_tree_set_xml_encoding(tree, "UTF-8");
249
250         // Set the XML file standalone
251         scew_tree_set_xml_standalone(tree, 1);
252
253         FILE * fp = _tfopen(path, _T("w"));
254         if (fp)
255         {
256                 if (!scew_writer_tree_fp(tree, fp))
257                 {
258                         success = FALSE;
259                         *sError = theApp.LoadString(IDS_FILEWRITE_ERROR);
260                 }
261                 fclose(fp);
262         }
263         else
264         {
265                 success = FALSE;
266         }
267         
268         /* Frees the SCEW tree */
269         scew_tree_free(tree);
270
271         if (success == FALSE)
272         {
273                 *sError = theApp.LoadString(IDS_FILEWRITE_ERROR);
274         }
275         return success;
276 }
277
278 /**
279  * @brief Add paths element into XML tree.
280  * @param [in] parent Parent element for the paths element.
281  * @return pointer to added paths element.
282  */
283 scew_element* ProjectFile::AddPathsElement(scew_element * parent)
284 {
285         scew_element* element = NULL;
286         element = scew_element_add(parent, Paths_element_name);
287         return element;
288 }
289
290 /**
291  * @brief Covert characters that are unsafe for use in XML
292  * @param [in] str The string to be converted
293  * @return The converted string
294  */
295 static String EscapeXML(const String &str)
296 {
297         String escapedStr = str;
298         string_replace(escapedStr, _T("&"), _T("&amp;"));
299         string_replace(escapedStr, _T("<"), _T("&lt;"));
300         string_replace(escapedStr, _T(">"), _T("&gt;"));
301         return escapedStr;
302 }
303
304 /**
305  * @brief Add paths data to the XML tree.
306  * This function adds our paths data to the XML tree.
307  * @param [in] parent Parent element for paths data.
308  * @return TRUE if we succeeded, FALSE otherwise.
309  */
310 BOOL ProjectFile::AddPathsContent(scew_element * parent)
311 {
312         USES_CONVERSION;
313         scew_element* element = NULL;
314
315         if (!m_leftFile.IsEmpty())
316         {
317                 element = scew_element_add(parent, Left_element_name);
318                 String path = m_leftFile;
319                 scew_element_set_contents(element, T2UTF8(EscapeXML(path).c_str()));
320         }
321
322         if (!m_rightFile.IsEmpty())
323         {
324                 element = scew_element_add(parent, Right_element_name);
325                 String path = m_rightFile;
326                 scew_element_set_contents(element, T2UTF8(EscapeXML(path).c_str()));
327         }
328
329         if (!m_filter.IsEmpty())
330         {
331                 element = scew_element_add(parent, Filter_element_name);
332                 String filter = m_filter;
333                 scew_element_set_contents(element, T2UTF8(EscapeXML(filter).c_str()));
334         }
335
336         element = scew_element_add(parent, Subfolders_element_name);
337         if (m_subfolders != 0)
338                 scew_element_set_contents(element, "1");
339         else
340                 scew_element_set_contents(element, "0");
341
342         element = scew_element_add(parent, Left_ro_element_name);
343         if (m_bLeftReadOnly)
344                 scew_element_set_contents(element, "1");
345         else
346                 scew_element_set_contents(element, "0");
347
348         element = scew_element_add(parent, Right_ro_element_name);
349         if (m_bRightReadOnly)
350                 scew_element_set_contents(element, "1");
351         else
352                 scew_element_set_contents(element, "0");
353
354         return TRUE;
355 }
356
357 /** 
358  * @brief Returns if left path is defined in project file.
359  * @return TRUE if project file has left path.
360  */
361 BOOL ProjectFile::HasLeft() const
362 {
363         return m_bHasLeft;
364 }
365
366 /** 
367  * @brief Returns if right path is defined in project file.
368  * @return TRUE if project file has right path.
369  */
370 BOOL ProjectFile::HasRight() const
371 {
372         return m_bHasRight;
373 }
374
375 /** 
376  * @brief Returns if filter is defined in project file.
377  * @return TRUE if project file has filter.
378  */
379 BOOL ProjectFile::HasFilter() const
380 {
381         return m_bHasFilter;
382 }
383
384 /** 
385  * @brief Returns if subfolder is defined in projectfile.
386  * @return TRUE if project file has subfolder definition.
387  */
388 BOOL ProjectFile::HasSubfolders() const
389 {
390         return m_bHasSubfolders;
391 }
392
393 /** 
394  * @brief Returns left path.
395  * @param [out] pReadOnly TRUE if readonly was specified for path.
396  */
397 CString ProjectFile::GetLeft(BOOL * pReadOnly /*=NULL*/) const
398 {
399         if (pReadOnly)
400                 *pReadOnly = m_bLeftReadOnly;
401         return m_leftFile;
402 }
403
404 /** 
405  * @brief Returns if left path is specified read-only.
406  */
407 BOOL ProjectFile::GetLeftReadOnly() const
408 {
409         return m_bLeftReadOnly;
410 }
411
412 /** 
413  * @brief Set left path, returns old left path.
414  * @param [in] sLeft Left path.
415  * @param [in] bReadOnly Will path be recorded read-only?
416  */
417 CString ProjectFile::SetLeft(const CString& sLeft, const BOOL * pReadOnly /*=NULL*/)
418 {
419         CString sLeftOld = GetLeft();
420         m_leftFile = sLeft;
421         if (pReadOnly)
422                 m_bLeftReadOnly = *pReadOnly;
423
424         return sLeftOld;
425 }
426
427 /** 
428  * @brief Returns right path.
429  * @param [out] pReadOnly TRUE if readonly was specified for path.
430  */
431 CString ProjectFile::GetRight(BOOL * pReadOnly /*=NULL*/) const
432 {
433         if (pReadOnly)
434                 *pReadOnly = m_bRightReadOnly;
435         return m_rightFile;
436 }
437
438 /** 
439  * @brief Returns if right path is specified read-only.
440  */
441 BOOL ProjectFile::GetRightReadOnly() const
442 {
443         return m_bRightReadOnly;
444 }
445
446 /** 
447  * @brief Set right path, returns old right path.
448  * @param [in] sRight Right path.
449  * @param [in] bReadOnly Will path be recorded read-only?
450  */
451 CString ProjectFile::SetRight(const CString& sRight, const BOOL * pReadOnly /*=NULL*/)
452 {
453         CString sRightOld = GetRight();
454         m_rightFile = sRight;
455         if (pReadOnly)
456                 m_bRightReadOnly = *pReadOnly;
457
458         return sRightOld;
459 }
460
461 /** 
462  * @brief Returns filter.
463  */
464 CString ProjectFile::GetFilter() const
465 {
466         return m_filter;
467 }
468
469 /** 
470  * @brief Set filter, returns old filter.
471  */
472 CString ProjectFile::SetFilter(const CString& sFilter)
473 {
474         CString sFilterOld = GetFilter();
475         m_filter = sFilter;
476
477         return sFilterOld;
478 }
479
480 /** 
481  * @brief Returns subfolder included -setting.
482  */
483 int ProjectFile::GetSubfolders() const
484 {
485         return m_subfolders;
486 }
487
488 /** 
489  * @brief set subfolder, returns old subfolder value.
490  */
491 int ProjectFile::SetSubfolders(const int iSubfolder)
492 {
493         int iSubfoldersOld = GetSubfolders(); 
494         m_subfolders = iSubfolder ? 1 : 0;
495
496         return iSubfoldersOld;
497 }
498
499 /** 
500  * @brief Returns left and right paths and recursive from project file
501  * 
502  * @param [out] sLeft Left path
503  * @param [out] sRight Right path
504  * @param [out] bSubFolders If TRUE subfolders included (recursive compare)
505  */
506 void ProjectFile::GetPaths(CString & sLeft, CString & sRight,
507         BOOL & bSubfolders) const
508 {
509         if (HasLeft())
510                 sLeft = GetLeft();
511         if (HasRight())
512                 sRight = GetRight();
513         if (HasSubfolders())
514                 bSubfolders = (GetSubfolders() == 1);
515 }