1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
22 #include "boost/noncopyable.hpp"
23 #include "osl/process.h"
24 #include "rtl/ustring.hxx"
25 #include "rtl/string.hxx"
26 #include "rtl/strbuf.hxx"
28 #include "osl/thread.h"
29 #include <osl/diagnose.h>
30 #include "recently_used_file.hxx"
32 #include "internal/xml_parser.hxx"
33 #include "internal/i_xml_parser_event_handler.hxx"
42 namespace /* private */ {
43 typedef std::vector
<string_t
> string_container_t
;
45 #define TAG_RECENT_FILES "RecentFiles"
46 #define TAG_RECENT_ITEM "RecentItem"
48 #define TAG_MIME_TYPE "Mime-Type"
49 #define TAG_TIMESTAMP "Timestamp"
50 #define TAG_PRIVATE "Private"
51 #define TAG_GROUPS "Groups"
52 #define TAG_GROUP "Group"
55 // compare two string_t's case insensitive, may also be done
56 // by specifying special traits for the string type but in this
57 // case it's easier to do it this way
58 struct str_icase_cmp
:
59 public std::binary_function
<string_t
, string_t
, bool>
61 bool operator() (const string_t
& s1
, const string_t
& s2
) const
62 { return (0 == strcasecmp(s1
.c_str(), s2
.c_str())); }
66 struct recently_used_item
75 const string_t
& mime_type
,
76 const string_container_t
& groups
,
77 bool is_private
= false) :
79 mime_type_(mime_type
),
80 is_private_(is_private
),
83 timestamp_
= time(NULL
);
86 void set_uri(const string_t
& character
)
89 void set_mime_type(const string_t
& character
)
90 { mime_type_
= character
; }
92 void set_timestamp(const string_t
& character
)
95 if (sscanf(character
.c_str(), "%ld", &t
) != 1)
101 void set_is_private(SAL_UNUSED_PARAMETER
const string_t
& /*character*/)
102 { is_private_
= true; }
104 void set_groups(const string_t
& character
)
105 { groups_
.push_back(character
); }
107 void set_nothing(SAL_UNUSED_PARAMETER
const string_t
& /*character*/)
110 bool has_groups() const
112 return !groups_
.empty();
115 bool has_group(const string_t
& name
) const
117 string_container_t::const_iterator iter_end
= groups_
.end();
118 return (has_groups() &&
119 iter_end
!= std::find_if(
120 groups_
.begin(), iter_end
,
121 std::bind2nd(str_icase_cmp(), name
)));
124 void write_xml(const recently_used_file
& file
) const
126 write_xml_start_tag(TAG_RECENT_ITEM
, file
, true);
127 write_xml_tag(TAG_URI
, uri_
, file
);
128 write_xml_tag(TAG_MIME_TYPE
, mime_type_
, file
);
130 OString ts
= OString::number(timestamp_
);
131 write_xml_tag(TAG_TIMESTAMP
, ts
.getStr(), file
);
134 write_xml_tag(TAG_PRIVATE
, file
);
138 write_xml_start_tag(TAG_GROUPS
, file
, true);
140 string_container_t::const_iterator iter
= groups_
.begin();
141 string_container_t::const_iterator iter_end
= groups_
.end();
143 for ( ; iter
!= iter_end
; ++iter
)
144 write_xml_tag(TAG_GROUP
, (*iter
), file
);
146 write_xml_end_tag(TAG_GROUPS
, file
);
148 write_xml_end_tag(TAG_RECENT_ITEM
, file
);
151 static OString
escape_content(const string_t
&text
)
154 for (sal_uInt32 i
= 0; i
< text
.length(); i
++)
158 case '&': aBuf
.append("&"); break;
159 case '<': aBuf
.append("<"); break;
160 case '>': aBuf
.append(">"); break;
161 case '\'': aBuf
.append("'"); break;
162 case '"': aBuf
.append("""); break;
163 default: aBuf
.append(text
[i
]); break;
166 return aBuf
.makeStringAndClear();
169 void write_xml_tag(const string_t
& name
, const string_t
& value
, const recently_used_file
& file
) const
171 write_xml_start_tag(name
, file
);
172 OString escaped
= escape_content (value
);
173 file
.write(escaped
.getStr(), escaped
.getLength());
174 write_xml_end_tag(name
, file
);
177 void write_xml_tag(const string_t
& name
, const recently_used_file
& file
) const
180 file
.write(name
.c_str(), name
.length());
181 file
.write("/>\n", 3);
184 void write_xml_start_tag(const string_t
& name
, const recently_used_file
& file
, bool linefeed
= false) const
187 file
.write(name
.c_str(), name
.length());
189 file
.write(">\n", 2);
194 void write_xml_end_tag(const string_t
& name
, const recently_used_file
& file
) const
197 file
.write(name
.c_str(), name
.length());
198 file
.write(">\n", 2);
205 string_container_t groups_
;
208 typedef std::vector
<recently_used_item
*> recently_used_item_list_t
;
209 typedef void (recently_used_item::* SET_COMMAND
)(const string_t
&);
211 // thrown if we encounter xml tags that we do not know
212 class unknown_xml_format_exception
{};
214 class recently_used_file_filter
:
215 public i_xml_parser_event_handler
, private boost::noncopyable
218 recently_used_file_filter(recently_used_item_list_t
& item_list
) :
220 item_list_(item_list
)
222 named_command_map_
[TAG_RECENT_FILES
] = &recently_used_item::set_nothing
;
223 named_command_map_
[TAG_RECENT_ITEM
] = &recently_used_item::set_nothing
;
224 named_command_map_
[TAG_URI
] = &recently_used_item::set_uri
;
225 named_command_map_
[TAG_MIME_TYPE
] = &recently_used_item::set_mime_type
;
226 named_command_map_
[TAG_TIMESTAMP
] = &recently_used_item::set_timestamp
;
227 named_command_map_
[TAG_PRIVATE
] = &recently_used_item::set_is_private
;
228 named_command_map_
[TAG_GROUPS
] = &recently_used_item::set_nothing
;
229 named_command_map_
[TAG_GROUP
] = &recently_used_item::set_groups
;
232 virtual void start_element(
233 const string_t
& /*raw_name*/,
234 const string_t
& local_name
,
235 const xml_tag_attribute_container_t
& /*attributes*/) SAL_OVERRIDE
237 if ((local_name
== TAG_RECENT_ITEM
) && (NULL
== item_
))
238 item_
= new recently_used_item
;
241 virtual void end_element(const string_t
& /*raw_name*/, const string_t
& local_name
) SAL_OVERRIDE
243 // check for end tags w/o start tag
244 if( local_name
!= TAG_RECENT_FILES
&& NULL
== item_
)
245 return; // will result in an XML parser error anyway
247 if (named_command_map_
.find(local_name
) != named_command_map_
.end())
248 (item_
->*named_command_map_
[local_name
])(current_element_
);
252 throw unknown_xml_format_exception();
255 if (local_name
== TAG_RECENT_ITEM
)
257 item_list_
.push_back(item_
);
260 current_element_
.clear();
263 virtual void characters(const string_t
& character
) SAL_OVERRIDE
265 if (character
!= "\n")
266 current_element_
+= character
;
269 virtual void start_document() SAL_OVERRIDE
{}
270 virtual void end_document() SAL_OVERRIDE
{}
272 virtual void ignore_whitespace(const string_t
& /*whitespaces*/) SAL_OVERRIDE
275 virtual void processing_instruction(
276 const string_t
& /*target*/, const string_t
& /*data*/) SAL_OVERRIDE
279 virtual void comment(const string_t
& /*comment*/) SAL_OVERRIDE
282 recently_used_item
* item_
;
283 std::map
<string_t
, SET_COMMAND
> named_command_map_
;
284 string_t current_element_
;
285 recently_used_item_list_t
& item_list_
;
289 void read_recently_used_items(
290 recently_used_file
& file
,
291 recently_used_item_list_t
& item_list
)
294 recently_used_file_filter
ruff(item_list
);
296 xparser
.set_document_handler(&ruff
);
301 if (size_t length
= file
.read(buff
, sizeof(buff
)))
302 xparser
.parse(buff
, length
, file
.eof());
307 // The file ~/.recently_used shall not contain more than 500
308 // entries (see www.freedesktop.org)
309 const int MAX_RECENTLY_USED_ITEMS
= 500;
311 class recent_item_writer
315 recently_used_file
& file
,
316 int max_items_to_write
= MAX_RECENTLY_USED_ITEMS
) :
318 max_items_to_write_(max_items_to_write
),
322 void operator() (const recently_used_item
* item
)
324 if (items_written_
++ < max_items_to_write_
)
325 item
->write_xml(file_
);
328 recently_used_file
& file_
;
329 int max_items_to_write_
;
334 const char* XML_HEADER
= "<?xml version=\"1.0\"?>\n<RecentFiles>\n";
335 const char* XML_FOOTER
= "</RecentFiles>";
338 // assumes that the list is ordered decreasing
339 void write_recently_used_items(
340 recently_used_file
& file
,
341 recently_used_item_list_t
& item_list
)
343 if (!item_list
.empty())
348 file
.write(XML_HEADER
, strlen(XML_HEADER
));
353 recent_item_writer(file
));
355 file
.write(XML_FOOTER
, strlen(XML_FOOTER
));
360 struct delete_recently_used_item
362 void operator() (const recently_used_item
* item
) const
367 void recently_used_item_list_clear(recently_used_item_list_t
& item_list
)
372 delete_recently_used_item());
377 class find_item_predicate
380 find_item_predicate(const string_t
& uri
) :
384 bool operator() (const recently_used_item
* item
) const
385 { return (item
->uri_
== uri_
); }
391 struct greater_recently_used_item
393 bool operator ()(const recently_used_item
* lhs
, const recently_used_item
* rhs
) const
394 { return (lhs
->timestamp_
> rhs
->timestamp_
); }
398 const char* GROUP_OOO
= "openoffice.org";
399 const char* GROUP_STAR_OFFICE
= "staroffice";
400 const char* GROUP_STAR_SUITE
= "starsuite";
403 void recently_used_item_list_add(
404 recently_used_item_list_t
& item_list
, const OUString
& file_url
, const OUString
& mime_type
)
406 OString f
= OUStringToOString(file_url
, RTL_TEXTENCODING_UTF8
);
408 recently_used_item_list_t::iterator iter
=
412 find_item_predicate(f
.getStr()));
414 if (iter
!= item_list
.end())
416 (*iter
)->timestamp_
= time(NULL
);
418 if (!(*iter
)->has_group(GROUP_OOO
))
419 (*iter
)->groups_
.push_back(GROUP_OOO
);
420 if (!(*iter
)->has_group(GROUP_STAR_OFFICE
))
421 (*iter
)->groups_
.push_back(GROUP_STAR_OFFICE
);
422 if (!(*iter
)->has_group(GROUP_STAR_SUITE
))
423 (*iter
)->groups_
.push_back(GROUP_STAR_SUITE
);
427 string_container_t groups
;
428 groups
.push_back(GROUP_OOO
);
429 groups
.push_back(GROUP_STAR_OFFICE
);
430 groups
.push_back(GROUP_STAR_SUITE
);
432 string_t
uri(f
.getStr());
433 string_t
mimetype(OUStringToOString(mime_type
, osl_getThreadTextEncoding()).getStr());
435 if (mimetype
.length() == 0)
436 mimetype
= "application/octet-stream";
438 item_list
.push_back(new recently_used_item(uri
, mimetype
, groups
));
441 // sort decreasing after the timestamp
442 // so that the newest items appear first
446 greater_recently_used_item());
452 cleanup_guard(recently_used_item_list_t
& item_list
) :
453 item_list_(item_list
)
456 { recently_used_item_list_clear(item_list_
); }
458 recently_used_item_list_t
& item_list_
;
461 } // namespace private
464 example (see http::www.freedesktop.org):
465 <?xml version="1.0"?>
468 <URI>file:///home/federico/gedit.txt</URI>
469 <Mime-Type>text/plain</Mime-Type>
470 <Timestamp>1046485966</Timestamp>
476 <URI>file:///home/federico/gedit-2.2.0.tar.bz2</URI>
477 <Mime-Type>application/x-bzip</Mime-Type>
478 <Timestamp>1046209851</Timestamp>
486 extern "C" SAL_DLLPUBLIC_EXPORT
487 void add_to_recently_used_file_list(const OUString
& file_url
,
488 const OUString
& mime_type
)
492 recently_used_file ruf
;
493 recently_used_item_list_t item_list
;
494 cleanup_guard
guard(item_list
);
496 read_recently_used_items(ruf
, item_list
);
497 recently_used_item_list_add(item_list
, file_url
, mime_type
);
498 write_recently_used_items(ruf
, item_list
);
500 catch(const char* ex
)
504 catch(const xml_parser_exception
&)
506 OSL_FAIL("XML parser error");
508 catch(const unknown_xml_format_exception
&)
510 OSL_FAIL("XML format unknown");
515 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */