# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Rickard Lindberg, Roger Lindberg
#
# This file is part of Timeline.
#
# Timeline is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Timeline is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Timeline. If not, see <http://www.gnu.org/licenses/>.
from xml.sax.saxutils import escape as xmlescape
import base64
import io
from timelinelib.db.utils import safe_write
from timelinelib.meta.version import get_full_version
ENCODING = "utf-8"
INDENT1 = " " * 2
INDENT2 = " " * 4
INDENT3 = " " * 6
INDENT4 = " " * 8
[docs]def export_db_to_timeline_xml(db, path):
Exporter(db).export(path)
[docs]def wrap_in_tag(func, name, indent=""):
def wrapper(*args, **kwargs):
dbfile = args[1] # 1st argument is self, 2nd argument is dbfile
dbfile.write(indent)
dbfile.write("<")
dbfile.write(name)
dbfile.write(">\n")
func(*args, **kwargs)
dbfile.write(indent)
dbfile.write("</")
dbfile.write(name)
dbfile.write(">\n")
return wrapper
[docs]class Exporter:
[docs] def __init__(self, db):
self.db = db
[docs] def export(self, path):
safe_write(path, ENCODING, self._write_xml_doc)
def _time_string(self, time):
return self.db.get_time_type().time_string(time)
def _write_xml_doc(self, xmlfile):
xmlfile.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
self._write_timeline(xmlfile)
def _write_timeline(self, xmlfile):
write_simple_tag(xmlfile, "version", get_full_version(), INDENT1)
write_simple_tag(xmlfile, "timetype", self.db.get_time_type().get_name(), INDENT1)
if len(self.db.get_all_eras()) > 0:
self._write_eras(xmlfile)
self._write_categories(xmlfile)
self._write_events(xmlfile)
self._write_view(xmlfile)
self._write_now_value(xmlfile)
_write_timeline = wrap_in_tag(_write_timeline, "timeline")
def _write_categories(self, xmlfile):
def write_with_parent(categories, parent):
for cat in categories:
if cat._get_parent() == parent:
self._write_category(xmlfile, cat)
write_with_parent(categories, cat)
write_with_parent(self.db.get_categories(), None)
_write_categories = wrap_in_tag(_write_categories, "categories", INDENT1)
def _write_category(self, xmlfile, cat):
write_simple_tag(xmlfile, "name", cat.get_name(), INDENT3)
write_simple_tag(xmlfile, "color", color_string(cat.get_color()), INDENT3)
write_simple_tag(xmlfile, "progress_color", color_string(cat.get_progress_color()), INDENT3)
write_simple_tag(xmlfile, "done_color", color_string(cat.get_done_color()), INDENT3)
write_simple_tag(xmlfile, "font_color", color_string(cat.get_font_color()), INDENT3)
if cat.parent:
write_simple_tag(xmlfile, "parent", cat.parent.get_name(), INDENT3)
_write_category = wrap_in_tag(_write_category, "category", INDENT2)
def _write_events(self, xmlfile):
all_events = self.db.get_all_events()
containers = [event for event in all_events if event.is_container()]
rest = [event for event in all_events if not event.is_container()]
for evt in containers + rest:
self._write_event(xmlfile, evt)
_write_events = wrap_in_tag(_write_events, "events", INDENT1)
def _write_event(self, xmlfile, evt):
write_simple_tag(xmlfile, "start",
self._time_string(evt.get_time_period().start_time), INDENT3)
write_simple_tag(xmlfile, "end",
self._time_string(evt.get_time_period().end_time), INDENT3)
if evt.is_container():
write_simple_tag(xmlfile, "text", "[%d]%s" % (evt.id, evt.get_text()), INDENT3)
elif evt.is_subevent():
write_simple_tag(xmlfile, "text", "(%d)%s" % (evt.container.id, evt.get_text()), INDENT3)
else:
text = evt.get_text()
if self._text_starts_with_container_tag(evt.get_text()):
text = self._add_leading_space_to_text(evt.get_text())
write_simple_tag(xmlfile, "text", text, INDENT3)
if evt.get_data("progress") is not None:
write_simple_tag(xmlfile, "progress", "%s" % evt.get_data("progress"), INDENT3)
write_simple_tag(xmlfile, "fuzzy", "%s" % evt.get_fuzzy() or (evt.fuzzy_start and evt.fuzzy_end), INDENT3)
write_simple_tag(xmlfile, "fuzzy_start", "%s" % evt.fuzzy_start, INDENT3)
write_simple_tag(xmlfile, "fuzzy_end", "%s" % evt.fuzzy_end, INDENT3)
write_simple_tag(xmlfile, "locked", "%s" % evt.get_locked(), INDENT3)
write_simple_tag(xmlfile, "ends_today", "%s" % evt.get_ends_today(), INDENT3)
if evt.get_category() is not None:
write_simple_tag(xmlfile, "category", evt.get_category().get_name(), INDENT3)
if evt.get_categories():
self._write_event_categories(xmlfile, evt)
if evt.get_data("description") is not None:
write_simple_tag(xmlfile, "description", evt.get_data("description"), INDENT3)
if evt.get_data("labels") is not None:
write_simple_tag(xmlfile, "labels", evt.get_data("labels"), INDENT3)
alert = evt.get_data("alert")
if alert is not None:
write_simple_tag(xmlfile, "alert", alert_string(self.db.get_time_type(), alert),
INDENT3)
hyperlink = evt.get_data("hyperlink")
if hyperlink is not None:
write_simple_tag(xmlfile, "hyperlink", hyperlink, INDENT3)
if evt.get_data("icon") is not None:
icon_text = icon_string(evt.get_data("icon"))
write_simple_tag(xmlfile, "icon", icon_text, INDENT3)
default_color = evt.get_data("default_color")
if default_color is not None:
write_simple_tag(xmlfile, "default_color", color_string(default_color), INDENT3)
if evt.is_milestone():
write_simple_tag(xmlfile, "milestone", "True", INDENT3)
_write_event = wrap_in_tag(_write_event, "event", INDENT2)
def _write_event_categories(self, xmlfile, event):
for category in event.get_categories():
write_simple_tag(xmlfile, "category", category.get_name(), INDENT4)
_write_event_categories = wrap_in_tag(_write_event_categories, "categories", INDENT3)
def _write_eras(self, xmlfile):
for era in self.db.get_all_eras():
self._write_era(xmlfile, era)
_write_eras = wrap_in_tag(_write_eras, "eras", INDENT1)
def _write_era(self, xmlfile, era):
write_simple_tag(xmlfile, "name", era.get_name(), INDENT3)
write_simple_tag(xmlfile, "start", self._time_string(era.get_time_period().start_time), INDENT3)
write_simple_tag(xmlfile, "end", self._time_string(era.get_time_period().end_time), INDENT3)
write_simple_tag(xmlfile, "color", color_string(era.get_color()), INDENT3)
write_simple_tag(xmlfile, "ends_today", "%s" % era.ends_today(), INDENT3)
_write_era = wrap_in_tag(_write_era, "era", INDENT2)
def _text_starts_with_container_tag(self, text):
if len(text) > 0:
return text[0] in ('(', '[')
else:
return False
def _add_leading_space_to_text(self, text):
return " %s" % text
def _write_view(self, xmlfile):
if self.db.get_displayed_period() is not None:
self._write_displayed_period(xmlfile)
self._write_hidden_categories(xmlfile)
_write_view = wrap_in_tag(_write_view, "view", INDENT1)
def _write_displayed_period(self, xmlfile):
period = self.db.get_displayed_period()
write_simple_tag(xmlfile, "start",
self._time_string(period.start_time), INDENT3)
write_simple_tag(xmlfile, "end",
self._time_string(period.end_time), INDENT3)
_write_displayed_period = wrap_in_tag(_write_displayed_period,
"displayed_period", INDENT2)
def _write_hidden_categories(self, xmlfile):
for cat in self.db.get_hidden_categories():
write_simple_tag(xmlfile, "name", cat.get_name(), INDENT3)
_write_hidden_categories = wrap_in_tag(_write_hidden_categories,
"hidden_categories", INDENT2)
def _write_now_value(self, xmlfile):
if self.db.get_time_type().supports_saved_now():
time = self.db.get_time_type().time_string(self.db.time_type.now())
write_simple_tag(xmlfile, "now", time, INDENT1)
[docs]def write_simple_tag(xmlfile, name, content, indent=""):
xmlfile.write(indent)
xmlfile.write("<")
xmlfile.write(name)
xmlfile.write(">")
if type(content) == bytes:
content = content.decode()
xmlfile.write(xmlescape(content))
xmlfile.write("</")
xmlfile.write(name)
xmlfile.write(">\n")
[docs]def color_string(color):
return "%i,%i,%i" % color[:3]
[docs]def icon_string(bitmap):
stream = io.BytesIO()
image = bitmap.ConvertToImage()
image.SaveFile(stream, 'image/png')
return base64.b64encode(stream.getvalue()).decode()
[docs]def alert_string(time_type, alert):
time, text = alert
time_string = time_type.time_string(time)
return "%s;%s" % (time_string, text)