1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 from os.path import abspath
20 import base64
21 import re
22 import shutil
23 import io
24
25 import wx
26
27 from timelinelib.calendar.bosparanian.timetype import BosparanianTimeType
28 from timelinelib.calendar.gregorian.timetype import GregorianTimeType
29 from timelinelib.calendar.num.timetype import NumTimeType
30 from timelinelib.calendar.coptic.timetype import CopticTimeType
31 from timelinelib.calendar.pharaonic.timetype import PharaonicTimeType
32 from timelinelib.canvas.data.db import MemoryDB
33 from timelinelib.canvas.data.exceptions import TimelineIOError
34 from timelinelib.canvas.data import Category
35 from timelinelib.canvas.data import Container
36 from timelinelib.canvas.data import Era
37 from timelinelib.canvas.data import Event
38 from timelinelib.canvas.data import Subevent
39 from timelinelib.canvas.data import TimePeriod
40 from timelinelib.canvas.data.milestone import Milestone
41 from timelinelib.db.utils import create_non_exising_path
42 from timelinelib.general.xmlparser import ANY
43 from timelinelib.general.xmlparser import OPTIONAL
44 from timelinelib.general.xmlparser import parse
45 from timelinelib.general.xmlparser import parse_fn_store
46 from timelinelib.general.xmlparser import parse_fn_store_to_list
47 from timelinelib.general.xmlparser import SINGLE
48 from timelinelib.general.xmlparser import Tag
49 from timelinelib.utils import ex_msg
50
51
59
60
62 """Thrown if parsing of data read from file fails."""
63 pass
64
65
67
69 self.db = db
70 self.path = path
71 self._containers_by_cid = {}
72
75
77 try:
78
79 partial_schema = Tag("timeline", SINGLE, None, [
80 Tag("version", SINGLE, self._parse_version)
81 ])
82 tmp_dict = {
83 "partial_schema": partial_schema,
84 "category_map": {},
85 "hidden_categories": [],
86 }
87 parse(self.path, partial_schema, tmp_dict)
88 except Exception as e:
89 msg = _("Unable to read timeline data from '%s'.")
90 whole_msg = (msg + "\n\n%s") % (abspath(self.path), ex_msg(e))
91 raise TimelineIOError(whole_msg)
92
94 match = re.search(r"^(\d+).(\d+).(\d+)(.*)$", text)
95 if match:
96 (x, y, z) = (int(match.group(1)), int(match.group(2)),
97 int(match.group(3)))
98 self._backup((x, y, z))
99 tmp_dict["version"] = (x, y, z)
100 self._create_rest_of_schema(tmp_dict)
101 else:
102 raise ParseException("Could not parse version number from '%s'."
103 % text)
104
105 - def _backup(self, current_version):
106 (x, _, _) = current_version
107 if x == 0:
108 shutil.copy(self.path,
109 create_non_exising_path(self.path, "pre100bak"))
110
112 """
113 Ensure all versions of the xml format can be parsed with this schema.
114
115 tmp_dict["version"] can be used to create different schemas depending
116 on the version.
117 """
118 tmp_dict["partial_schema"].add_child_tags([
119 Tag("timetype", OPTIONAL, self._parse_timetype),
120 Tag("eras", OPTIONAL, None, [
121 Tag("era", ANY, self._parse_era, [
122 Tag("name", SINGLE, parse_fn_store("tmp_name")),
123 Tag("start", SINGLE, parse_fn_store("tmp_start")),
124 Tag("end", SINGLE, parse_fn_store("tmp_end")),
125 Tag("color", SINGLE, parse_fn_store("tmp_color")),
126 Tag("ends_today", OPTIONAL, parse_fn_store("tmp_ends_today")),
127 ])
128 ]),
129 Tag("categories", SINGLE, None, [
130 Tag("category", ANY, self._parse_category, [
131 Tag("name", SINGLE, parse_fn_store("tmp_name")),
132 Tag("color", SINGLE, parse_fn_store("tmp_color")),
133 Tag("progress_color", OPTIONAL, parse_fn_store("tmp_progress_color")),
134 Tag("done_color", OPTIONAL, parse_fn_store("tmp_done_color")),
135 Tag("font_color", OPTIONAL, parse_fn_store("tmp_font_color")),
136 Tag("parent", OPTIONAL, parse_fn_store("tmp_parent")),
137 ])
138 ]),
139 Tag("events", SINGLE, None, [
140 Tag("event", ANY, self._parse_event, [
141 Tag("start", SINGLE, parse_fn_store("tmp_start")),
142 Tag("end", SINGLE, parse_fn_store("tmp_end")),
143 Tag("text", SINGLE, parse_fn_store("tmp_text")),
144 Tag("progress", OPTIONAL, parse_fn_store("tmp_progress")),
145 Tag("fuzzy", OPTIONAL, parse_fn_store("tmp_fuzzy")),
146 Tag("locked", OPTIONAL, parse_fn_store("tmp_locked")),
147 Tag("ends_today", OPTIONAL, parse_fn_store("tmp_ends_today")),
148 Tag("category", OPTIONAL, parse_fn_store("tmp_category")),
149 Tag("categories", OPTIONAL, None, [
150 Tag("category", ANY, parse_fn_store_to_list("tmp_categories"))
151 ]),
152 Tag("description", OPTIONAL, parse_fn_store("tmp_description")),
153 Tag("alert", OPTIONAL, parse_fn_store("tmp_alert")),
154 Tag("hyperlink", OPTIONAL, parse_fn_store("tmp_hyperlink")),
155 Tag("icon", OPTIONAL, parse_fn_store("tmp_icon")),
156 Tag("default_color", OPTIONAL, parse_fn_store("tmp_default_color")),
157 Tag("milestone", OPTIONAL, parse_fn_store("tmp_milestone")),
158 ])
159 ]),
160 Tag("view", SINGLE, None, [
161 Tag("displayed_period", OPTIONAL,
162 self._parse_displayed_period, [
163 Tag("start", SINGLE, parse_fn_store("tmp_start")),
164 Tag("end", SINGLE, parse_fn_store("tmp_end")),
165 ]),
166 Tag("hidden_categories", OPTIONAL,
167 self._parse_hidden_categories, [
168 Tag("name", ANY, self._parse_hidden_category),
169 ]),
170 ]),
171 Tag("now", OPTIONAL, self._parse_saved_now),
172 ])
173
183
185 name = tmp_dict.pop("tmp_name")
186 color = parse_color(tmp_dict.pop("tmp_color"))
187 progress_color = self._parse_optional_color(tmp_dict, "tmp_progress_color", None)
188 done_color = self._parse_optional_color(tmp_dict, "tmp_done_color", None)
189 font_color = self._parse_optional_color(tmp_dict, "tmp_font_color")
190 parent_name = tmp_dict.pop("tmp_parent", None)
191 if parent_name:
192 parent = tmp_dict["category_map"].get(parent_name, None)
193 if parent is None:
194 raise ParseException("Parent category '%s' not found." % parent_name)
195 else:
196 parent = None
197 category = Category().update(name, color, font_color, parent=parent)
198 if progress_color:
199 category.set_progress_color(progress_color)
200 if done_color:
201 category.set_done_color(done_color)
202 old_category = self.db.get_category_by_name(name)
203 if old_category is not None:
204 category = old_category
205 if name not in tmp_dict["category_map"]:
206 tmp_dict["category_map"][name] = category
207 self.db.save_category(category)
208
210 start = self._parse_time(tmp_dict.pop("tmp_start"))
211 end = self._parse_time(tmp_dict.pop("tmp_end"))
212 text = tmp_dict.pop("tmp_text")
213 progress = self._parse_optional_int(tmp_dict, "tmp_progress")
214 fuzzy = self._parse_optional_bool(tmp_dict, "tmp_fuzzy")
215 locked = self._parse_optional_bool(tmp_dict, "tmp_locked")
216 ends_today = self._parse_optional_bool(tmp_dict, "tmp_ends_today")
217 category_text = tmp_dict.pop("tmp_category", None)
218 if category_text is None:
219 category = None
220 else:
221 category = self.get_category(tmp_dict, category_text)
222 categories = []
223 if "tmp_categories" in tmp_dict:
224
225 dic = {k: 0 for k in tmp_dict.pop("tmp_categories", None)}
226 for category_text in dic:
227 if category_text is not None:
228 cat = self.get_category(tmp_dict, category_text)
229 if category is None:
230 category = cat
231 else:
232 categories.append(cat)
233 description = tmp_dict.pop("tmp_description", None)
234 alert_string = tmp_dict.pop("tmp_alert", None)
235 alert = parse_alert_string(self.db.get_time_type(), alert_string)
236 icon_text = tmp_dict.pop("tmp_icon", None)
237 if icon_text is None:
238 icon = None
239 else:
240 icon = parse_icon(icon_text)
241 hyperlink = tmp_dict.pop("tmp_hyperlink", None)
242 milestone = self._parse_optional_bool(tmp_dict, "tmp_milestone")
243 if self._is_container_event(text):
244 cid, text = self._extract_container_id(text)
245 event = Container().update(start, end, text, category)
246 self._containers_by_cid[cid] = event
247 elif self._is_subevent(text):
248 cid, text = self._extract_subid(text)
249 event = Subevent().update(
250 start,
251 end,
252 text,
253 category,
254 locked=locked,
255 ends_today=ends_today
256 )
257 event.container = self._containers_by_cid[cid]
258 elif milestone:
259 event = Milestone().update(start, start, text)
260 event.set_category(category)
261 else:
262 if self._text_starts_with_added_space(text):
263 text = self._remove_added_space(text)
264 event = Event().update(start, end, text, category, fuzzy, locked, ends_today)
265 if categories:
266 event.set_categories(categories)
267 default_color = tmp_dict.pop("tmp_default_color", "200,200,200")
268 event.set_data("description", description)
269 event.set_data("icon", icon)
270 event.set_data("alert", alert)
271 event.set_data("hyperlink", hyperlink)
272 event.set_data("progress", int(progress))
273 event.set_data("default_color", parse_color(default_color))
274 self.db.save_event(event)
275
277 cat = tmp_dict["category_map"].get(category_text, None)
278 if cat is None:
279 raise ParseException("Category '%s' not found." % category_text)
280 return cat
281
291
293 return text[0:2] in (" (", " [")
294
297
299 return text.startswith("[")
300
302 return text.startswith("(")
303
305 str_id, text = text.split("]", 1)
306 try:
307 str_id = str_id[1:]
308 cid = int(str_id)
309 except:
310 cid = -1
311 return cid, text
312
314 cid, text = text.split(")", 1)
315 try:
316 cid = int(cid[1:])
317 except:
318 cid = -1
319 return cid, text
320
322 if cid in tmp_dict:
323 return tmp_dict.pop(cid) == "True"
324 else:
325 return False
326
328 if cid in tmp_dict:
329 return int(tmp_dict.pop(cid))
330 else:
331 return 0
332
334 if cid in tmp_dict:
335 return parse_color(tmp_dict.pop(cid))
336 else:
337 return missing_value
338
343
349
352
355
359
360
362 """
363 Expected format 'r,g,b'.
364
365 Return a tuple (r, g, b).
366 """
367 def verify_255_number(num):
368 if num < 0 or num > 255:
369 raise ParseException("Color number not in range [0, 255], "
370 "color string = '%s'" % color_string)
371 match = re.search(r"^(\d+),(\d+),(\d+)$", color_string)
372 if match:
373 r, g, b = int(match.group(1)), int(match.group(2)), int(match.group(3))
374 verify_255_number(r)
375 verify_255_number(g)
376 verify_255_number(b)
377 return (r, g, b)
378 else:
379 raise ParseException("Color not on correct format, color string = '%s'"
380 % color_string)
381
382
384 """
385 Expected format: base64 encoded png image.
386
387 Return a wx.Bitmap.
388 """
389 try:
390 icon_string = io.StringIO(base64.b64decode(string))
391 image = wx.ImageFromStream(icon_string, wx.BITMAP_TYPE_PNG)
392 return image.ConvertToBitmap()
393 except:
394 raise ParseException("Could not parse icon from '%s'." % string)
395
396
408