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