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.
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.
25 #include <barry/tzwrapper.h>
26 #include <libxml++/libxml++.h>
34 //////////////////////////////////////////////////////////////////////////////
37 XmlNodeSummary::XmlNodeSummary(xmlpp::Node
*node
, XmlNodeMapping
*mapping
)
40 , m_name(node
->get_name())
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
);
61 void XmlNodeSummary::ProcessKey(ostream
&os
, const Glib::ustring
&key
)
70 ProcessKey(os
, key
.substr(1));
76 Glib::ustring wkey
= key
;
77 if( key
[key
.size()-1] == ',' ) {
79 wkey
= key
.substr(0, key
.size()-1);
82 Glib::ustring content
= GetContent(wkey
.substr(1));
84 if( comma
&& content
.size() )
91 ProcessListKey(os
, key
.substr(1));
100 void XmlNodeSummary::CreateSummary()
103 istringstream
iss(m_mapping
->Format());
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
);
115 return GetContent(*list
.begin());
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
);
128 xmlpp::Node::NodeList list
= node
->get_children();
132 node
= *list
.begin();
133 content
= dynamic_cast<xmlpp::ContentNode
*>(node
);
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 //////////////////////////////////////////////////////////////////////////////
158 XmlNodeMapping::XmlNodeMapping(const Glib::ustring
&node_name
,
160 const Glib::ustring
&format
)
161 : m_node_key_name(node_name
)
162 , m_priority(priority
)
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
,
201 size_t pos
= compare_name
.find('[');
202 if( pos
== Glib::ustring::npos
) {
207 name
= compare_name
.substr(0, pos
);
209 while( compare_name
[pos
] && compare_name
[pos
] != ']' ) {
210 type
+= compare_name
[pos
];
216 bool Timestamp2Unix(const Glib::ustring
&stamp
, time_t &result
)
220 if( !Barry::Sync::iso_to_tm(stamp
.c_str(), &split
, utc
) )
226 result
= Barry::Sync::utc_mktime(&split
);
228 result
= mktime(&split
);
230 return result
!= (time_t)-1;
233 bool TimestampsEqual(const Glib::ustring
&content1
,
234 const Glib::ustring
&content2
)
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
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
)
255 // if the node counts are different, not equal
256 if( m_summaries
.size() != other
.m_summaries
.size() )
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();
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
);
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
) )
284 // compare as raw strings
285 if( content1
!= content2
)
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";
302 for( compare_list::const_iterator i
= m_compare_node_names
.begin();
303 i
!= m_compare_node_names
.end();
306 if( i
!= m_compare_node_names
.begin() )
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();
318 os
<< " " << i
->Name() << " > " << i
->Summary() << "\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 //////////////////////////////////////////////////////////////////////////////
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
)
354 void XmlNodeMap::LoadMap(const std::string
&type_name
,
355 const std::string
&map_filename
)
357 ifstream
is(map_filename
.c_str());
359 throw std::runtime_error("Unable to open: " + map_filename
);
362 while( getline(is
, line
) ) {
363 if( line
[0] == '#' || line
[0] == '\t' )
365 if( line
.size() == 0 )
368 string type
, key
, format
;
370 istringstream
iss(line
);
371 iss
>> type
>> key
>> priority
>> std::ws
;
372 getline(iss
, format
);
379 if( type_name
.size() && type
!= type_name
) {
380 // skip types not for us
384 ptr_type
mapping( new XmlNodeMapping(key
, priority
, format
) );
386 while( is
.peek() == '\t' ) {
388 mapping
->AddCompareName(line
.substr(1));
395 void XmlNodeMap::ImportNodes(xmlpp::Node
*node
, bool purge
)
397 if( XmlNodeMapping
*mapping
= Find(node
->get_path()) ) {
398 mapping
->AddNode(node
);
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);
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() )
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
;
438 for( ; good
!= end(); ++good
) {
439 if( !good
->IsEmpty() ) {
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
) {
463 else if( xpath
.size() > key
.size() &&
464 xpath
.substr(0, key
.size()) == key
&&
465 xpath
[key
.size()] == '[' )
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
)
493 XmlNodeMap::iterator
XmlNodeMap::priority_end(int stop_priority
)
495 iterator i
= begin();
496 for( ; i
!= end(); ++i
) {
497 if( i
->Priority() >= stop_priority
)
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
) );
522 #include <tr1/functional>
523 using namespace std::tr1
;
524 using namespace std::tr1::placeholders
;
526 int main(int argc
, char *argv
[])
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());
541 cout
<< "\n\nCute summary:\n";
542 for_each(np
.begin(), np
.priority_end(),
543 bind( mem_fn(&XmlNodeMapping::DumpSummaries
),
547 cout
<< "\n\nCute summary:\n";
548 for_each(np
.begin(), np
.priority_end(),
549 bind( mem_fn(&XmlNodeMapping::DumpSummaries
),
552 catch( Glib::ConvertError
&e
) {
553 cerr
<< e
.what() << endl
;
556 catch( std::exception
&e
) {
557 cerr
<< e
.what() << endl
;