1 | """ |
---|
2 | This file is part of the web2py Web Framework |
---|
3 | Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> |
---|
4 | License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) |
---|
5 | """ |
---|
6 | import datetime |
---|
7 | import decimal |
---|
8 | from gluon.storage import Storage |
---|
9 | from gluon.html import TAG, XmlComponent, xmlescape |
---|
10 | from gluon.languages import lazyT |
---|
11 | import gluon.contrib.rss2 as rss2 |
---|
12 | import json as json_parser |
---|
13 | from gluon._compat import long, to_native, unicodeT, integer_types |
---|
14 | |
---|
15 | have_yaml = True |
---|
16 | try: |
---|
17 | import yaml as yamlib |
---|
18 | except ImportError: |
---|
19 | have_yaml = False |
---|
20 | |
---|
21 | |
---|
22 | def cast_keys(o, cast=str, encoding="utf-8"): |
---|
23 | """ |
---|
24 | Builds a new object with <cast> type keys. |
---|
25 | Use this function if you are in Python < 2.6.5 |
---|
26 | This avoids syntax errors when unpacking dictionary arguments. |
---|
27 | |
---|
28 | Args: |
---|
29 | o: is the object input |
---|
30 | cast: (defaults to str) is an object type or function |
---|
31 | which supports conversion such as: |
---|
32 | |
---|
33 | converted = cast(o) |
---|
34 | |
---|
35 | encoding: (defaults to utf-8) is the encoding for unicode |
---|
36 | keys. This is not used for custom cast functions |
---|
37 | |
---|
38 | """ |
---|
39 | |
---|
40 | if isinstance(o, (dict, Storage)): |
---|
41 | if isinstance(o, dict): |
---|
42 | newobj = dict() |
---|
43 | else: |
---|
44 | newobj = Storage() |
---|
45 | for k, v in o.items(): |
---|
46 | if (cast == str) and isinstance(k, unicodeT): |
---|
47 | key = k.encode(encoding) |
---|
48 | else: |
---|
49 | key = cast(k) |
---|
50 | newobj[key] = cast_keys(v, cast=cast, encoding=encoding) |
---|
51 | elif isinstance(o, (tuple, set, list)): |
---|
52 | newobj = [] |
---|
53 | for item in o: |
---|
54 | newobj.append(cast_keys(item, cast=cast, encoding=encoding)) |
---|
55 | if isinstance(o, tuple): |
---|
56 | newobj = tuple(newobj) |
---|
57 | elif isinstance(o, set): |
---|
58 | newobj = set(newobj) |
---|
59 | else: |
---|
60 | # no string cast (unknown object) |
---|
61 | newobj = o |
---|
62 | return newobj |
---|
63 | |
---|
64 | |
---|
65 | def loads_json(o, unicode_keys=True, **kwargs): |
---|
66 | # deserialize a json string |
---|
67 | result = json_parser.loads(o, **kwargs) |
---|
68 | if not unicode_keys: |
---|
69 | # filter non-str keys in dictionary objects |
---|
70 | result = cast_keys(result, |
---|
71 | encoding=kwargs.get("encoding", "utf-8")) |
---|
72 | return result |
---|
73 | |
---|
74 | |
---|
75 | def custom_json(o): |
---|
76 | if hasattr(o, 'custom_json') and callable(o.custom_json): |
---|
77 | return o.custom_json() |
---|
78 | if isinstance(o, (datetime.date, |
---|
79 | datetime.datetime, |
---|
80 | datetime.time)): |
---|
81 | return o.isoformat()[:19].replace('T', ' ') |
---|
82 | elif isinstance(o, integer_types): |
---|
83 | return int(o) |
---|
84 | elif isinstance(o, decimal.Decimal): |
---|
85 | return float(o) |
---|
86 | elif isinstance(o, (bytes, bytearray)): |
---|
87 | return str(o) |
---|
88 | elif isinstance(o, lazyT): |
---|
89 | return str(o) |
---|
90 | elif isinstance(o, XmlComponent): |
---|
91 | return to_native(o.xml()) |
---|
92 | elif isinstance(o, set): |
---|
93 | return list(o) |
---|
94 | elif hasattr(o, 'as_list') and callable(o.as_list): |
---|
95 | return o.as_list() |
---|
96 | elif hasattr(o, 'as_dict') and callable(o.as_dict): |
---|
97 | return o.as_dict() |
---|
98 | else: |
---|
99 | raise TypeError(repr(o) + " is not JSON serializable") |
---|
100 | |
---|
101 | |
---|
102 | def xml_rec(value, key, quote=True): |
---|
103 | if hasattr(value, 'custom_xml') and callable(value.custom_xml): |
---|
104 | return value.custom_xml() |
---|
105 | elif isinstance(value, (dict, Storage)): |
---|
106 | return TAG[key](*[TAG[k](xml_rec(v, '', quote)) |
---|
107 | for k, v in value.items()]) |
---|
108 | elif isinstance(value, list): |
---|
109 | return TAG[key](*[TAG.item(xml_rec(item, '', quote)) for item in value]) |
---|
110 | elif hasattr(value, 'as_list') and callable(value.as_list): |
---|
111 | return str(xml_rec(value.as_list(), '', quote)) |
---|
112 | elif hasattr(value, 'as_dict') and callable(value.as_dict): |
---|
113 | return str(xml_rec(value.as_dict(), '', quote)) |
---|
114 | else: |
---|
115 | return xmlescape(value, quote) |
---|
116 | |
---|
117 | |
---|
118 | def xml(value, encoding='UTF-8', key='document', quote=True): |
---|
119 | return ('<?xml version="1.0" encoding="%s"?>' % encoding) + str(xml_rec(value, key, quote)) |
---|
120 | |
---|
121 | |
---|
122 | class JSONEncoderForHTML(json_parser.JSONEncoder): |
---|
123 | """An encoder that produces JSON safe to embed in HTML. |
---|
124 | To embed JSON content in, say, a script tag on a web page, the |
---|
125 | characters &, < and > should be escaped. They cannot be escaped |
---|
126 | with the usual entities (e.g. &) because they are not expanded |
---|
127 | within <script> tags. |
---|
128 | This class also escapes the line separator and paragraph separator |
---|
129 | characters U+2028 and U+2029, irrespective of the ensure_ascii setting, |
---|
130 | as these characters are not valid in JavaScript strings (see |
---|
131 | http://timelessrepo.com/json-isnt-a-javascript-subset). |
---|
132 | """ |
---|
133 | |
---|
134 | def encode(self, o): |
---|
135 | # Override JSONEncoder.encode because it has hacks for |
---|
136 | # performance that make things more complicated. |
---|
137 | chunks = self.iterencode(o, True) |
---|
138 | if self.ensure_ascii: |
---|
139 | return ''.join(chunks) |
---|
140 | else: |
---|
141 | return u''.join(chunks) |
---|
142 | |
---|
143 | def iterencode(self, o, _one_shot=False): |
---|
144 | chunks = super(JSONEncoderForHTML, self).iterencode(o, _one_shot) |
---|
145 | for chunk in chunks: |
---|
146 | chunk = chunk.replace('&', '\\u0026') |
---|
147 | chunk = chunk.replace('<', '\\u003c') |
---|
148 | chunk = chunk.replace('>', '\\u003e') |
---|
149 | |
---|
150 | if not self.ensure_ascii: |
---|
151 | chunk = chunk.replace(u'\u2028', '\\u2028') |
---|
152 | chunk = chunk.replace(u'\u2029', '\\u2029') |
---|
153 | |
---|
154 | yield chunk |
---|
155 | |
---|
156 | |
---|
157 | def json(value, default=custom_json, indent=None, sort_keys=False, cls=JSONEncoderForHTML): |
---|
158 | return json_parser.dumps(value, default=default, cls=cls, sort_keys=sort_keys, indent=indent) |
---|
159 | |
---|
160 | def csv(value): |
---|
161 | return '' |
---|
162 | |
---|
163 | |
---|
164 | def ics(events, title=None, link=None, timeshift=0, calname=True, |
---|
165 | **ignored): |
---|
166 | title = title or '(unknown)' |
---|
167 | if link and not callable(link): |
---|
168 | link = lambda item, prefix=link: prefix.replace( |
---|
169 | '[id]', str(item['id'])) |
---|
170 | s = 'BEGIN:VCALENDAR' |
---|
171 | s += '\nVERSION:2.0' |
---|
172 | if not calname is False: |
---|
173 | s += '\nX-WR-CALNAME:%s' % (calname or title) |
---|
174 | s += '\nSUMMARY:%s' % title |
---|
175 | s += '\nPRODID:Generated by web2py' |
---|
176 | s += '\nCALSCALE:GREGORIAN' |
---|
177 | s += '\nMETHOD:PUBLISH' |
---|
178 | for item in events: |
---|
179 | s += '\nBEGIN:VEVENT' |
---|
180 | s += '\nUID:%s' % item['id'] |
---|
181 | if link: |
---|
182 | s += '\nURL:%s' % link(item) |
---|
183 | shift = datetime.timedelta(seconds=3600 * timeshift) |
---|
184 | start = item['start_datetime'] + shift |
---|
185 | stop = item['stop_datetime'] + shift |
---|
186 | s += '\nDTSTART:%s' % start.strftime('%Y%m%dT%H%M%S') |
---|
187 | s += '\nDTEND:%s' % stop.strftime('%Y%m%dT%H%M%S') |
---|
188 | s += '\nSUMMARY:%s' % item['title'] |
---|
189 | s += '\nEND:VEVENT' |
---|
190 | s += '\nEND:VCALENDAR' |
---|
191 | return s |
---|
192 | |
---|
193 | def safe_encode(text): |
---|
194 | if not isinstance(text, (str, unicodeT)): |
---|
195 | text = str(text) |
---|
196 | try: |
---|
197 | text = text.encode('utf8','replace') |
---|
198 | except ValueError: |
---|
199 | new_text = '' |
---|
200 | for c in text: |
---|
201 | try: |
---|
202 | new_text += c.encode('utf8') |
---|
203 | except: |
---|
204 | new_text += '?' |
---|
205 | text = new_text |
---|
206 | return text |
---|
207 | |
---|
208 | def rss(feed): |
---|
209 | if not 'entries' in feed and 'items' in feed: |
---|
210 | feed['entries'] = feed['items'] |
---|
211 | |
---|
212 | def safestr(obj, key, default=''): |
---|
213 | return safe_encode(obj.get(key,'')) |
---|
214 | |
---|
215 | now = datetime.datetime.now() |
---|
216 | rss = rss2.RSS2(title=safestr(feed,'title'), |
---|
217 | link=safestr(feed,'link'), |
---|
218 | description=safestr(feed,'description'), |
---|
219 | lastBuildDate=feed.get('created_on', now), |
---|
220 | items=[rss2.RSSItem( |
---|
221 | title=safestr(entry,'title','(notitle)'), |
---|
222 | link=safestr(entry,'link'), |
---|
223 | description=safestr(entry,'description'), |
---|
224 | pubDate=entry.get('created_on', now) |
---|
225 | ) for entry in feed.get('entries', [])]) |
---|
226 | return rss.to_xml(encoding='utf8') |
---|
227 | |
---|
228 | |
---|
229 | def yaml(data): |
---|
230 | if have_yaml: |
---|
231 | return yamlib.dump(data) |
---|
232 | else: |
---|
233 | raise ImportError("No YAML serializer available") |
---|
234 | |
---|
235 | |
---|
236 | def loads_yaml(data): |
---|
237 | if have_yaml: |
---|
238 | return yamlib.load(data) |
---|
239 | else: |
---|
240 | raise ImportError("No YAML serializer available") |
---|