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 StringIO
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.canvas.data.db import MemoryDB
31 from timelinelib.canvas.data.exceptions import TimelineIOError
32 from timelinelib.canvas.data import Category
33 from timelinelib.canvas.data import Container
34 from timelinelib.canvas.data import Era
35 from timelinelib.canvas.data import Event
36 from timelinelib.canvas.data import Subevent
37 from timelinelib.canvas.data import TimePeriod
38 from timelinelib.canvas.data.milestone import Milestone
39 from timelinelib.db.utils import create_non_exising_path
40 from timelinelib.general.xmlparser import ANY
41 from timelinelib.general.xmlparser import OPTIONAL
42 from timelinelib.general.xmlparser import parse
43 from timelinelib.general.xmlparser import parse_fn_store
44 from timelinelib.general.xmlparser import SINGLE
45 from timelinelib.general.xmlparser import Tag
46 from timelinelib.utils import ex_msg
47
48
56
57
59 """Thrown if parsing of data read from file fails."""
60 pass
61
62
64
66 self.db = db
67 self.path = path
68 self._containers_by_cid = {}
69
72
74 try:
75
76 partial_schema = Tag("timeline", SINGLE, None, [
77 Tag("version", SINGLE, self._parse_version)
78 ])
79 tmp_dict = {
80 "partial_schema": partial_schema,
81 "category_map": {},
82 "hidden_categories": [],
83 }
84 parse(self.path, partial_schema, tmp_dict)
85 except Exception as e:
86 msg = _("Unable to read timeline data from '%s'.")
87 whole_msg = (msg + "\n\n%s") % (abspath(self.path), ex_msg(e))
88 raise TimelineIOError(whole_msg)
89
91 match = re.search(r"^(\d+).(\d+).(\d+)(.*)$", text)
92 if match:
93 (x, y, z) = (int(match.group(1)), int(match.group(2)),
94 int(match.group(3)))
95 self._backup((x, y, z))
96 tmp_dict["version"] = (x, y, z)
97 self._create_rest_of_schema(tmp_dict)
98 else:
99 raise ParseException("Could not parse version number from '%s'."
100 % text)
101
102 - def _backup(self, current_version):
103 (x, _, _) = current_version
104 if x == 0:
105 shutil.copy(self.path,
106 create_non_exising_path(self.path, "pre100bak"))
107
109 """
110 Ensure all versions of the xml format can be parsed with this schema.
111
112 tmp_dict["version"] can be used to create different schemas depending
113 on the version.
114 """
115 tmp_dict["partial_schema"].add_child_tags([
116 Tag("timetype", OPTIONAL, self._parse_timetype),
117 Tag("eras", OPTIONAL, None, [
118 Tag("era", ANY, self._parse_era, [
119 Tag("name", SINGLE, parse_fn_store("tmp_name")),
120 Tag("start", SINGLE, parse_fn_store("tmp_start")),
121 Tag("end", SINGLE, parse_fn_store("tmp_end")),
122 Tag("color", SINGLE, parse_fn_store("tmp_color")),
123 Tag("ends_today", OPTIONAL, parse_fn_store("tmp_ends_today")),
124 ])
125 ]),
126 Tag("categories", SINGLE, None, [
127 Tag("category", ANY, self._parse_category, [
128 Tag("name", SINGLE, parse_fn_store("tmp_name")),
129 Tag("color", SINGLE, parse_fn_store("tmp_color")),
130 Tag("progress_color", OPTIONAL, parse_fn_store("tmp_progress_color")),
131 Tag("done_color", OPTIONAL, parse_fn_store("tmp_done_color")),
132 Tag("font_color", OPTIONAL, parse_fn_store("tmp_font_color")),
133 Tag("parent", OPTIONAL, parse_fn_store("tmp_parent")),
134 ])
135 ]),
136 Tag("events", SINGLE, None, [
137 Tag("event", ANY, self._parse_event, [
138 Tag("start", SINGLE, parse_fn_store("tmp_start")),
139 Tag("end", SINGLE, parse_fn_store("tmp_end")),
140 Tag("text", SINGLE, parse_fn_store("tmp_text")),
141 Tag("progress", OPTIONAL, parse_fn_store("tmp_progress")),
142 Tag("fuzzy", OPTIONAL, parse_fn_store("tmp_fuzzy")),
143 Tag("locked", OPTIONAL, parse_fn_store("tmp_locked")),
144 Tag("ends_today", OPTIONAL, parse_fn_store("tmp_ends_today")),
145 Tag("category", OPTIONAL, parse_fn_store("tmp_category")),
146 Tag("description", OPTIONAL, parse_fn_store("tmp_description")),
147 Tag("alert", OPTIONAL, parse_fn_store("tmp_alert")),
148 Tag("hyperlink", OPTIONAL, parse_fn_store("tmp_hyperlink")),
149 Tag("icon", OPTIONAL, parse_fn_store("tmp_icon")),
150 Tag("default_color", OPTIONAL, parse_fn_store("tmp_default_color")),
151 Tag("milestone", OPTIONAL, parse_fn_store("tmp_milestone")),
152 ])
153 ]),
154 Tag("view", SINGLE, None, [
155 Tag("displayed_period", OPTIONAL,
156 self._parse_displayed_period, [
157 Tag("start", SINGLE, parse_fn_store("tmp_start")),
158 Tag("end", SINGLE, parse_fn_store("tmp_end")),
159 ]),
160 Tag("hidden_categories", OPTIONAL,
161 self._parse_hidden_categories, [
162 Tag("name", ANY, self._parse_hidden_category),
163 ]),
164 ]),
165 Tag("now", OPTIONAL, self._parse_saved_now),
166 ])
167
177
179 name = tmp_dict.pop("tmp_name")
180 color = parse_color(tmp_dict.pop("tmp_color"))
181 progress_color = self._parse_optional_color(tmp_dict, "tmp_progress_color", None)
182 done_color = self._parse_optional_color(tmp_dict, "tmp_done_color", None)
183 font_color = self._parse_optional_color(tmp_dict, "tmp_font_color")
184 parent_name = tmp_dict.pop("tmp_parent", None)
185 if parent_name:
186 parent = tmp_dict["category_map"].get(parent_name, None)
187 if parent is None:
188 raise ParseException("Parent category '%s' not found." % parent_name)
189 else:
190 parent = None
191 category = Category().update(name, color, font_color, parent=parent)
192 if progress_color:
193 category.set_progress_color(progress_color)
194 if done_color:
195 category.set_done_color(done_color)
196 old_category = self.db.get_category_by_name(name)
197 if old_category is not None:
198 category = old_category
199 if name not in tmp_dict["category_map"]:
200 tmp_dict["category_map"][name] = category
201 self.db.save_category(category)
202
204 start = self._parse_time(tmp_dict.pop("tmp_start"))
205 end = self._parse_time(tmp_dict.pop("tmp_end"))
206 text = tmp_dict.pop("tmp_text")
207 progress = self._parse_optional_int(tmp_dict, "tmp_progress")
208 fuzzy = self._parse_optional_bool(tmp_dict, "tmp_fuzzy")
209 locked = self._parse_optional_bool(tmp_dict, "tmp_locked")
210 ends_today = self._parse_optional_bool(tmp_dict, "tmp_ends_today")
211 category_text = tmp_dict.pop("tmp_category", None)
212 if category_text is None:
213 category = None
214 else:
215 category = tmp_dict["category_map"].get(category_text, None)
216 if category is None:
217 raise ParseException("Category '%s' not found." % category_text)
218 description = tmp_dict.pop("tmp_description", None)
219 alert_string = tmp_dict.pop("tmp_alert", None)
220 alert = parse_alert_string(self.db.get_time_type(), alert_string)
221 icon_text = tmp_dict.pop("tmp_icon", None)
222 if icon_text is None:
223 icon = None
224 else:
225 icon = parse_icon(icon_text)
226 hyperlink = tmp_dict.pop("tmp_hyperlink", None)
227 milestone = self._parse_optional_bool(tmp_dict, "tmp_milestone")
228 if self._is_container_event(text):
229 cid, text = self._extract_container_id(text)
230 event = Container().update(start, end, text, category)
231 self._containers_by_cid[cid] = event
232 elif self._is_subevent(text):
233 cid, text = self._extract_subid(text)
234 event = Subevent().update(
235 start,
236 end,
237 text,
238 category,
239 locked=locked,
240 ends_today=ends_today
241 )
242 event.container = self._containers_by_cid[cid]
243 elif milestone:
244 event = Milestone().update(start, start, text)
245 event.set_category(category)
246 else:
247 if self._text_starts_with_added_space(text):
248 text = self._remove_added_space(text)
249 event = Event().update(start, end, text, category, fuzzy, locked, ends_today)
250 default_color = tmp_dict.pop("tmp_default_color", "200,200,200")
251 event.set_data("description", description)
252 event.set_data("icon", icon)
253 event.set_data("alert", alert)
254 event.set_data("hyperlink", hyperlink)
255 event.set_data("progress", int(progress))
256 event.set_data("default_color", parse_color(default_color))
257 self.db.save_event(event)
258
268
270 return text[0:2] in (" (", " [")
271
274
276 return text.startswith("[")
277
279 return text.startswith("(")
280
282 str_id, text = text.split("]", 1)
283 try:
284 str_id = str_id[1:]
285 cid = int(str_id)
286 except:
287 cid = -1
288 return cid, text
289
291 cid, text = text.split(")", 1)
292 try:
293 cid = int(cid[1:])
294 except:
295 cid = -1
296 return cid, text
297
299 if cid in tmp_dict:
300 return tmp_dict.pop(cid) == "True"
301 else:
302 return False
303
305 if cid in tmp_dict:
306 return int(tmp_dict.pop(cid))
307 else:
308 return 0
309
311 if cid in tmp_dict:
312 return parse_color(tmp_dict.pop(cid))
313 else:
314 return missing_value
315
320
326
329
332
336
337
339 """
340 Expected format 'r,g,b'.
341
342 Return a tuple (r, g, b).
343 """
344 def verify_255_number(num):
345 if num < 0 or num > 255:
346 raise ParseException("Color number not in range [0, 255], "
347 "color string = '%s'" % color_string)
348 match = re.search(r"^(\d+),(\d+),(\d+)$", color_string)
349 if match:
350 r, g, b = int(match.group(1)), int(match.group(2)), int(match.group(3))
351 verify_255_number(r)
352 verify_255_number(g)
353 verify_255_number(b)
354 return (r, g, b)
355 else:
356 raise ParseException("Color not on correct format, color string = '%s'"
357 % color_string)
358
359
361 """
362 Expected format: base64 encoded png image.
363
364 Return a wx.Bitmap.
365 """
366 try:
367 icon_string = StringIO.StringIO(base64.b64decode(string))
368 image = wx.ImageFromStream(icon_string, wx.BITMAP_TYPE_PNG)
369 return image.ConvertToBitmap()
370 except:
371 raise ParseException("Could not parse icon from '%s'." % string)
372
373
385