menu: added new Keywords tag to .desktop files
[barry.git] / desktop / src / xmlmap.cc
blob6b065313845fcb68bfbeca16bcc1b5925b1c5824
1 ///
2 /// \file xmlmap.cc
3 /// Map an XML node tree according to an XML map file.
4 /// The map is not fully nested, and provides mostly a
5 /// flat view, based on the map.
6 ///
8 /*
9 Copyright (C) 2010-2013, Chris Frey <cdfrey@foursquare.net>
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
20 See the GNU General Public License in the COPYING file at the
21 root directory of this project for more details.
24 #include "xmlmap.h"
25 #include <barry/tzwrapper.h>
26 #include <libxml++/libxml++.h>
27 #include <iostream>
28 #include <fstream>
29 #include <algorithm>
30 #include <functional>
31 #include <stdexcept>
32 using namespace std;
34 //////////////////////////////////////////////////////////////////////////////
35 // XmlNodeSummary
37 XmlNodeSummary::XmlNodeSummary(xmlpp::Node *node, XmlNodeMapping *mapping)
38 : m_node(node)
39 , m_mapping(mapping)
40 , m_name(node->get_name())
42 CreateSummary();
45 void XmlNodeSummary::ProcessListKey(ostream &os, const Glib::ustring &name)
47 xmlpp::Node::NodeList list = m_node->get_children(name);
48 xmlpp::Node::NodeList::iterator i = list.begin();
49 for( int count = 0; i != list.end(); ++i ) {
50 Glib::ustring content = GetContent(*i);
51 if( !content.size() )
52 continue;
54 if( count )
55 os << ", ";
56 os << content;
57 count++;
61 void XmlNodeSummary::ProcessKey(ostream &os, const Glib::ustring &key)
63 if( key.size() == 0 )
64 return;
66 switch( key[0] )
68 case '(':
69 os << "(";
70 ProcessKey(os, key.substr(1));
71 os << ") ";
72 break;
74 case '$': {
75 bool comma = false;
76 Glib::ustring wkey = key;
77 if( key[key.size()-1] == ',' ) {
78 comma = true;
79 wkey = key.substr(0, key.size()-1);
82 Glib::ustring content = GetContent(wkey.substr(1));
83 os << content;
84 if( comma && content.size() )
85 os << ",";
86 os << " ";
88 break;
90 case '%':
91 ProcessListKey(os, key.substr(1));
92 break;
94 default:
95 os << key << " ";
96 break;
100 void XmlNodeSummary::CreateSummary()
102 ostringstream oss;
103 istringstream iss(m_mapping->Format());
104 Glib::ustring key;
105 while( iss >> key ) {
106 ProcessKey(oss, key);
108 m_summary = oss.str();
111 Glib::ustring XmlNodeSummary::GetContent(const Glib::ustring &child_name) const
113 xmlpp::Node::NodeList list = m_node->get_children(child_name);
114 if( list.size() ) {
115 return GetContent(*list.begin());
117 else {
118 return Glib::ustring();
122 Glib::ustring XmlNodeSummary::GetContent(xmlpp::Node *node) const
124 Glib::ustring content_value;
126 xmlpp::ContentNode *content = dynamic_cast<xmlpp::ContentNode*>(node);
127 while( !content ) {
128 xmlpp::Node::NodeList list = node->get_children();
129 if( !list.size() )
130 break;
132 node = *list.begin();
133 content = dynamic_cast<xmlpp::ContentNode*>(node);
136 if( content ) {
137 content_value = content->get_content();
139 return content_value;
142 bool XmlNodeSummary::SummaryCompare(const XmlNodeSummary &a,
143 const XmlNodeSummary &b)
145 return a.Summary() < b.Summary();
148 bool XmlNodeSummary::PathCompare(const XmlNodeSummary &a,
149 const XmlNodeSummary &b)
151 return a.Path() < b.Path();
155 //////////////////////////////////////////////////////////////////////////////
156 // XmlNodeMapping
158 XmlNodeMapping::XmlNodeMapping(const Glib::ustring &node_name,
159 int priority,
160 const Glib::ustring &format)
161 : m_node_key_name(node_name)
162 , m_priority(priority)
163 , m_format(format)
167 void XmlNodeMapping::AddCompareName(const Glib::ustring &node_name)
169 m_compare_node_names.push_back(node_name);
172 void XmlNodeMapping::AddNode(xmlpp::Node *node)
174 XmlNodeSummary summary(node, this);
175 m_summaries.push_back(summary);
178 void XmlNodeMapping::SortBySummary()
180 sort(m_summaries.begin(), m_summaries.end(),
181 &XmlNodeSummary::SummaryCompare);
184 void XmlNodeMapping::SortByPath()
186 sort(m_summaries.begin(), m_summaries.end(),
187 &XmlNodeSummary::PathCompare);
190 const XmlNodeSummary& XmlNodeMapping::operator[] (int index) const
192 return m_summaries[index];
195 // Parse a compare_name in the format of "name[type]" into
196 // its component parts, without the brackets.
197 void XmlNodeMapping::SplitCompareName(const Glib::ustring &compare_name,
198 Glib::ustring &name,
199 Glib::ustring &type)
201 size_t pos = compare_name.find('[');
202 if( pos == Glib::ustring::npos ) {
203 name = compare_name;
204 type.clear();
206 else {
207 name = compare_name.substr(0, pos);
208 pos++;
209 while( compare_name[pos] && compare_name[pos] != ']' ) {
210 type += compare_name[pos];
211 pos++;
216 bool Timestamp2Unix(const Glib::ustring &stamp, time_t &result)
218 struct tm split;
219 bool utc;
220 if( !Barry::Sync::iso_to_tm(stamp.c_str(), &split, utc) )
221 return false;
223 split.tm_isdst = -1;
225 if( utc )
226 result = Barry::Sync::utc_mktime(&split);
227 else
228 result = mktime(&split);
230 return result != (time_t)-1;
233 bool TimestampsEqual(const Glib::ustring &content1,
234 const Glib::ustring &content2)
236 time_t t1, t2;
237 if( !Timestamp2Unix(content1, t1) || !Timestamp2Unix(content2, t2) ) {
238 // could throw there, but this is just used to
239 // pretty up the display, so defaulting to false
240 // is fine
241 return false;
243 return t1 == t2;
246 bool XmlNodeMapping::operator== (const XmlNodeMapping &other) const
248 // make sure mapfile data are the same
249 if( KeyName() != other.KeyName() ||
250 Priority() != other.Priority() ||
251 Format() != other.Format() ||
252 m_compare_node_names != other.m_compare_node_names )
253 return false;
255 // if the node counts are different, not equal
256 if( m_summaries.size() != other.m_summaries.size() )
257 return false;
259 // cycle through all compare names, and compare the
260 // values for those names from each node summary
261 for( compare_list::const_iterator namei = m_compare_node_names.begin();
262 namei != m_compare_node_names.end();
263 ++namei )
265 for( summary_list::const_iterator sumi = m_summaries.begin(),
266 other_sumi = other.m_summaries.begin();
267 sumi != m_summaries.end() &&
268 other_sumi != other.m_summaries.end();
269 ++sumi, ++other_sumi )
271 Glib::ustring name, type;
272 SplitCompareName(*namei, name, type);
274 Glib::ustring
275 content1 = sumi->GetContent(name),
276 content2 = other_sumi->GetContent(name);
278 if( type == "timestamp" ) {
279 // compare content as timestamps
280 if( !TimestampsEqual(content1, content2) )
281 return false;
283 else {
284 // compare as raw strings
285 if( content1 != content2 )
286 return false;
291 // all good!
292 return true;
295 void XmlNodeMapping::Dump(std::ostream &os) const
297 os << m_node_key_name << ": Priority " << m_priority
298 << ", Nodes: " << m_summaries.size() << "\n";
299 os << " Format: " << m_format << "\n";
300 os << " Compare Names (" << m_compare_node_names.size() << "):\n";
301 os << " ";
302 for( compare_list::const_iterator i = m_compare_node_names.begin();
303 i != m_compare_node_names.end();
304 ++i )
306 if( i != m_compare_node_names.begin() )
307 os << ", ";
308 os << (*i);
310 os << "\n";
312 if( m_summaries.size() ) {
313 os << " Summaries (" << m_summaries.size() << " nodes):\n";
314 for( summary_list::const_iterator i = m_summaries.begin();
315 i != m_summaries.end();
316 ++i )
318 os << " " << i->Name() << " > " << i->Summary() << "\n";
322 os << "\n";
325 void XmlNodeMapping::DumpSummaries(std::ostream &os) const
327 for( size_t n = 0; n < size(); n++ ) {
328 os << m_summaries[n].Summary() << endl;
333 //////////////////////////////////////////////////////////////////////////////
334 // XmlNodeMap
336 XmlNodeMap::XmlNodeMap(const std::string &type_name,
337 const std::string &map_filename)
339 LoadMap(type_name, map_filename);
342 XmlNodeMap::XmlNodeMap(const std::string &map_filename)
344 LoadMap(string(), map_filename);
347 // copy constructor - only copies the map data, not the node.
348 // you must Import as usual
349 XmlNodeMap::XmlNodeMap(const XmlNodeMap &other)
351 operator=(other);
354 void XmlNodeMap::LoadMap(const std::string &type_name,
355 const std::string &map_filename)
357 ifstream is(map_filename.c_str());
358 if( !is )
359 throw std::runtime_error("Unable to open: " + map_filename);
361 std::string line;
362 while( getline(is, line) ) {
363 if( line[0] == '#' || line[0] == '\t' )
364 continue;
365 if( line.size() == 0 )
366 continue;
368 string type, key, format;
369 int priority = -1;
370 istringstream iss(line);
371 iss >> type >> key >> priority >> std::ws;
372 getline(iss, format);
374 if( !iss ) {
375 // skip if error
376 continue;
379 if( type_name.size() && type != type_name ) {
380 // skip types not for us
381 continue;
384 ptr_type mapping( new XmlNodeMapping(key, priority, format) );
386 while( is.peek() == '\t' ) {
387 getline(is, line);
388 mapping->AddCompareName(line.substr(1));
391 push_back(mapping);
395 void XmlNodeMap::ImportNodes(xmlpp::Node *node, bool purge)
397 if( XmlNodeMapping *mapping = Find(node->get_path()) ) {
398 mapping->AddNode(node);
399 return;
402 xmlpp::Node::NodeList list = node->get_children();
403 xmlpp::Node::NodeList::iterator i = list.begin();
404 for( ; i != list.end(); ++i ) {
405 ImportNodes(*i, false);
408 if( purge ) {
409 PurgeEmpties();
413 /// Removes all XmlNodeMappings that contain no node summaries.
414 /// This lets the caller remove mappings that do not match the
415 /// XML data that was imported. For example, a map may contain
416 /// mappings for Contacts and Calendar items, but only one
417 /// is parsed and imported at once. This function will remove
418 /// the unused mappings.
419 void XmlNodeMap::PurgeEmpties()
421 // iterator new_end = remove_if(begin(), end(),
422 // mem_fun_ref(&XmlNodeMapping::IsEmpty));
424 // -------------------------------------------------------
425 // not all compilers compile remove_if() for us, so do it
426 // manually: find the first empty
427 iterator new_end = begin();
428 for( ; new_end != end(); ++new_end ) {
429 if( new_end->IsEmpty() )
430 break;
432 if( new_end == end() )
433 return; // nothing is empty, nothing to purge
435 // copy the remainder, skipping the empties
436 iterator good = new_end;
437 ++good;
438 for( ; good != end(); ++good ) {
439 if( !good->IsEmpty() ) {
440 *new_end = *good;
441 ++new_end;
444 // done re-implementation of remove_if()
445 // -------------------------------------------------------
447 // erase the junk at the end (need to call this whether
448 // you use remove_if() or not
449 erase(new_end, end());
452 XmlNodeMapping* XmlNodeMap::Find(const Glib::ustring &node_name)
454 iterator i = begin();
455 for( ; i != end(); ++i ) {
456 const Glib::ustring &xpath = node_name;
457 XmlNodeMapping *mapping = &(*i);
458 const Glib::ustring &key = mapping->KeyName();
460 if( xpath.size() == key.size() && xpath == key ) {
461 return mapping;
463 else if( xpath.size() > key.size() &&
464 xpath.substr(0, key.size()) == key &&
465 xpath[key.size()] == '[' )
467 return mapping;
470 return 0;
473 void XmlNodeMap::SortBySummary()
475 for_each(begin(), end(), mem_fun_ref(&XmlNodeMapping::SortBySummary));
478 void XmlNodeMap::SortByPath()
480 for_each(begin(), end(), mem_fun_ref(&XmlNodeMapping::SortByPath));
483 void XmlNodeMap::Dump(std::ostream &os, int stop_priority) const
485 const_iterator i = begin();
486 for( ; i != end(); ++i ) {
487 if( i->Priority() >= stop_priority )
488 return;
489 i->Dump(os);
493 XmlNodeMap::iterator XmlNodeMap::priority_end(int stop_priority)
495 iterator i = begin();
496 for( ; i != end(); ++i ) {
497 if( i->Priority() >= stop_priority )
498 return i;
500 return i;
503 void XmlNodeMap::clear()
505 for_each(begin(), end(), mem_fun_ref(&XmlNodeMapping::clear));
508 XmlNodeMap& XmlNodeMap::operator=(const XmlNodeMap &other)
510 const_iterator i = other.begin();
511 for( ; i != other.end(); ++i ) {
512 ptr_type p( new XmlNodeMapping(*i) );
513 p->clear();
514 push_back(p);
516 return *this;
519 #ifdef XMLMAP
520 #include <locale>
521 #include <glibmm.h>
522 #include <tr1/functional>
523 using namespace std::tr1;
524 using namespace std::tr1::placeholders;
526 int main(int argc, char *argv[])
528 try {
529 locale loc("");
530 cin.imbue( loc );
531 cout.imbue( loc );
533 xmlpp::DomParser parser;
534 parser.parse_stream(cin);
536 XmlNodeMap np("Contact", "0.22/xmlmap");
537 np.ImportNodes(parser.get_document()->get_root_node());
538 np.Dump(cout, 99);
540 np.SortBySummary();
541 cout << "\n\nCute summary:\n";
542 for_each(np.begin(), np.priority_end(),
543 bind( mem_fn(&XmlNodeMapping::DumpSummaries),
544 _1, ref(cout)));
546 np.SortByPath();
547 cout << "\n\nCute summary:\n";
548 for_each(np.begin(), np.priority_end(),
549 bind( mem_fn(&XmlNodeMapping::DumpSummaries),
550 _1, ref(cout)));
552 catch( Glib::ConvertError &e ) {
553 cerr << e.what() << endl;
554 return 1;
556 catch( std::exception &e ) {
557 cerr << e.what() << endl;
558 return 1;
561 #endif