1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """UIDL target"""
17
18 import logging
19
20 from warnings import warn
21
22 from collections import deque
23 from muntjac.util import getSuperClass
24 from muntjac.util import clsname
25
26 try:
27 from cStringIO import StringIO
28 except ImportError, e:
29 from StringIO import StringIO
30
31 from muntjac.ui.custom_layout import CustomLayout
32 from muntjac.terminal.resource import IResource
33 from muntjac.terminal.external_resource import ExternalResource
34 from muntjac.terminal.application_resource import IApplicationResource
35 from muntjac.terminal.theme_resource import ThemeResource
36 from muntjac.ui.alignment import Alignment
37 from muntjac.terminal.stream_variable import IStreamVariable
38 from muntjac.terminal.paintable import IPaintable, IRepaintRequestListener
39 from muntjac.terminal.paint_target import IPaintTarget
40 from muntjac.terminal.paint_exception import PaintException
41
42
43 logger = logging.getLogger(__name__)
47 """User Interface Description Language Target.
48
49 TODO: document better: role of this class, UIDL format,
50 attributes, variables, etc.
51
52 @author: Vaadin Ltd.
53 @author: Richard Lincoln
54 @version: 1.1.2
55 """
56
57 _UIDL_ARG_NAME = 'name'
58
59 - def __init__(self, manager, outWriter, cachingRequired):
60 """Creates a new XMLPrintWriter, without automatic line flushing.
61
62 @param manager:
63 @param outWriter:
64 A character-output stream.
65 @param cachingRequired:
66 True if this is not a full repaint, i.e. caches are to
67 be used.
68 @raise PaintException:
69 if the paint operation failed.
70 """
71 self._manager = manager
72
73
74 self._uidlBuffer = outWriter
75
76
77 self._mOpenTags = deque()
78
79 self._openJsonTags = deque()
80
81 self._cacheEnabled = cachingRequired
82
83 self._closed = False
84 self._changes = 0
85 self._usedResources = set()
86 self._customLayoutArgumentsOpen = False
87 self._tag = None
88 self._errorsOpen = 0
89 self._paintedComponents = set()
90 self._identifiersCreatedDueRefPaint = None
91 self._usedPaintableTypes = list()
92
93
95 """Prints the element start tag.
96
97 @param arg:
98 paintable or the name of the start tag
99 @param arg2:
100 the name of the start tag for the given paintable
101 @raise PaintException:
102 if the paint operation failed.
103 """
104 if isinstance(arg, IPaintable):
105 paintable, tagName = arg, arg2
106 self.startTag(tagName, True)
107 isPreviouslyPainted = (self._manager.hasPaintableId(paintable)
108 and (self._identifiersCreatedDueRefPaint is None
109 or paintable not in self._identifiersCreatedDueRefPaint))
110 idd = self._manager.getPaintableId(paintable)
111 paintable.addListener(self._manager, IRepaintRequestListener)
112 self.addAttribute('id', idd)
113 self._paintedComponents.add(paintable)
114
115 if isinstance(paintable, CustomLayout):
116 self._customLayoutArgumentsOpen = True
117
118 return self._cacheEnabled and isPreviouslyPainted
119 else:
120 tagName, _ = arg, arg2
121 if tagName is None:
122 raise ValueError
123
124
125 if self._closed:
126 raise PaintException, \
127 'Attempted to write to a closed IPaintTarget.'
128
129 if self._tag is not None:
130 self._openJsonTags.append(self._tag)
131
132
133 self._mOpenTags.append(tagName)
134
135 self._tag = JsonTag(tagName, self)
136
137 if 'error' == tagName:
138 self._errorsOpen += 1
139
140 self._customLayoutArgumentsOpen = False
141
142
144 """Prints the element end tag.
145
146 If the parent tag is closed before every child tag is closed an
147 PaintException is raised.
148
149 @param tagName:
150 the name of the end tag.
151 @raise Paintexception:
152 if the paint operation failed.
153 """
154
155 if tagName is None:
156 raise ValueError
157
158
159 if self._closed:
160 raise PaintException, 'Attempted to write to a closed IPaintTarget.'
161
162 if len(self._openJsonTags) > 0:
163 parent = self._openJsonTags.pop()
164
165 lastTag = ''
166
167 lastTag = self._mOpenTags.pop()
168 if tagName.lower() != lastTag.lower():
169 raise PaintException, ('Invalid UIDL: wrong ending tag: \''
170 + tagName + '\' expected: \'' + lastTag + '\'.')
171
172
173 if 'error' == lastTag:
174 if self._errorsOpen == 1:
175 parent.addAttribute(('\"error\":[\"error\",{}'
176 + self._tag.getData() + ']'))
177 else:
178
179 parent.addData(self._tag.getJSON())
180
181 self._errorsOpen -= 1
182 else:
183 parent.addData(self._tag.getJSON())
184
185 self._tag = parent
186 else:
187 self._changes += 1
188 self._uidlBuffer.write((',' if self._changes > 1 else '')
189 + self._tag.getJSON())
190 self._tag = None
191
192
193 @classmethod
195 """Substitutes the XML sensitive characters with predefined XML
196 entities.
197
198 @param xml:
199 the string to be substituted.
200 @return: A new string instance where all occurrences of XML
201 sensitive characters are substituted with entities.
202 """
203 if xml is None or len(xml) <= 0:
204 return ''
205
206 return cls._escapeXML(xml)
207
208
209 @classmethod
211 """Substitutes the XML sensitive characters with predefined XML
212 entities.
213
214 @param xml:
215 the string to be substituted.
216 @return: A new StringBuilder instance where all occurrences of XML
217 sensitive characters are substituted with entities.
218 """
219 if xml is None or len(xml) <= 0:
220 return ''
221
222 buff = StringIO()
223
224 for c in xml:
225 s = cls.toXmlChar(c)
226 if s is not None:
227 buff.write(s)
228 else:
229 buff.write(c)
230
231 result = buff.getvalue()
232 buff.close()
233 return result
234
235
236 @staticmethod
238 if ch >= '\u0000' and ch <= '\u001F':
239 ss = hex( int(ch) )
240 sb.write('\\u')
241 for _ in range(4 - len(ss)):
242 sb.write('0')
243 sb.write( ss.upper() )
244 else:
245 sb.write(ch)
246
247
248 _json_map = {
249 '"': lambda ch, sb: sb.write('\\\"'),
250 '\\': lambda ch, sb: sb.write('\\\\'),
251 '\b': lambda ch, sb: sb.write('\\b'),
252 '\f': lambda ch, sb: sb.write('\\f'),
253 '\n': lambda ch, sb: sb.write('\\n'),
254 '\r': lambda ch, sb: sb.write('\\r'),
255 '\t': lambda ch, sb: sb.write('\\t'),
256 '/' : lambda ch, sb: sb.write('\\/')
257 }
258
259
260 @classmethod
262 """Escapes the given string so it can safely be used as a JSON string.
263
264 @param s: The string to escape
265 @return: Escaped version of the string
266 """
267
268
269 if s is None:
270 return ''
271
272 sb = StringIO()
273
274 for c in s:
275 cls._json_map.get(c, cls._default)(c, sb)
276
277 result = sb.getvalue()
278 sb.close()
279 return result
280
281
282 @classmethod
284 """Substitutes a XML sensitive character with predefined XML entity.
285
286 @param c:
287 the character to be replaced with an entity.
288 @return: String of the entity or null if character is not to be
289 replaced with an entity.
290 """
291 return {
292 '&': '&',
293 '>': '>',
294 '<': '<',
295 '"': '"',
296 "'": '''
297 }.get(c)
298
299
300 - def addText(self, s):
301 """Prints XML-escaped text.
302
303 @raise PaintException:
304 if the paint operation failed.
305 """
306 self._tag.addData('\"' + self.escapeJSON(s) + '\"')
307
308
310 if isinstance(value, list):
311 values = value
312
313 if values is None or name is None:
314 raise ValueError, 'Parameters must be non-null strings'
315 buf = StringIO()
316 buf.write('\"' + name + '\":[')
317 for i in range(len(values)):
318 if i > 0:
319 buf.write(',')
320 buf.write('\"')
321 buf.write(self.escapeJSON( str(values[i]) ))
322 buf.write('\"')
323 buf.write(']')
324 self._tag.addAttribute(buf.getvalue())
325 buf.close()
326 elif isinstance(value, IPaintable):
327 idd = self.getPaintIdentifier(value)
328 self.addAttribute(name, idd)
329 elif isinstance(value, IResource):
330
331 if isinstance(value, ExternalResource):
332 self.addAttribute(name, value.getURL())
333 elif isinstance(value, IApplicationResource):
334 r = value
335 a = r.getApplication()
336 if a is None:
337 raise PaintException, ('Application not specified '
338 'for resource ' + value.__class__.__name__)
339 uri = a.getRelativeLocation(r)
340 self.addAttribute(name, uri)
341 elif isinstance(value, ThemeResource):
342 uri = 'theme://' + value.getResourceId()
343 self.addAttribute(name, uri)
344 else:
345 raise PaintException, ('Ajax adapter does not support '
346 'resources of type: ' + value.__class__.__name__)
347 elif isinstance(value, bool):
348 self._tag.addAttribute(('\"' + name + '\":'
349 + ('true' if value else 'false')))
350 elif isinstance(value, dict):
351 sb = StringIO()
352 sb.write('\"')
353 sb.write(name)
354 sb.write('\": ')
355 sb.write('{')
356 i = 0
357 for key, mapValue in value.iteritems():
358 sb.write('\"')
359 if isinstance(key, IPaintable):
360 paintable = key
361 sb.write( self.getPaintIdentifier(paintable) )
362 else:
363 sb.write( self.escapeJSON(str(key)) )
364 sb.write('\":')
365 if isinstance(mapValue, (float, int, float, bool, Alignment)):
366 sb.write( str(mapValue) )
367 else:
368 sb.write('\"')
369 sb.write( self.escapeJSON(str(mapValue)) )
370 sb.write('\"')
371 if i < len(value) - 1:
372 sb.write(',')
373 i += 1
374 sb.write('}')
375 self._tag.addAttribute(sb.getvalue())
376 sb.close()
377 elif isinstance(value, str):
378
379 if (value is None) or (name is None):
380 raise ValueError, 'Parameters must be non-null strings'
381
382 self._tag.addAttribute(('\"' + name + '\": \"'
383 + self.escapeJSON(value) + '\"'))
384
385 if self._customLayoutArgumentsOpen and 'template' == name:
386 self.getUsedResources().add('layouts/' + value + '.html')
387
388 if name == 'locale':
389 self._manager.requireLocale(value)
390 else:
391 self._tag.addAttribute('\"' + name + '\":' + str(value))
392
393
395 if value is None:
396 value = ''
397
398 if isinstance(value, IPaintable):
399 var = StringVariable(owner, name, self.getPaintIdentifier(value))
400 self._tag.addVariable(var)
401 elif isinstance(value, IStreamVariable):
402 url = self._manager.getStreamVariableTargetUrl(owner, name, value)
403 if url is not None:
404 self.addVariable(owner, name, url)
405
406 elif isinstance(value, bool):
407 self._tag.addVariable( BooleanVariable(owner, name, value) )
408 elif isinstance(value, float):
409 self._tag.addVariable( DoubleVariable(owner, name, value) )
410
411
412 elif isinstance(value, int):
413 self._tag.addVariable( IntVariable(owner, name, value) )
414 elif isinstance(value, long):
415 self._tag.addVariable( LongVariable(owner, name, value) )
416 elif isinstance(value, basestring):
417 var = StringVariable(owner, name, self.escapeJSON(value))
418 self._tag.addVariable(var)
419 elif isinstance(value, list):
420 self._tag.addVariable( ArrayVariable(owner, name, value) )
421 else:
422 raise ValueError, ('%s %s %s' % (str(owner), name, value))
423
424
426 """Adds a upload stream type variable.
427
428 @param owner:
429 the Listener for variable changes.
430 @param name:
431 the Variable name.
432 @raise PaintException:
433 if the paint operation failed.
434 """
435 self.startTag('uploadstream')
436 self.addAttribute(self._UIDL_ARG_NAME, name)
437 self.endTag('uploadstream')
438
439
440 - def addSection(self, sectionTagName, sectionData):
441 """Prints the single text section.
442
443 Prints full text section. The section data is escaped
444
445 @param sectionTagName:
446 the name of the tag.
447 @param sectionData:
448 the section data to be printed.
449 @raise PaintException:
450 if the paint operation failed.
451 """
452 self._tag.addData(('{\"' + sectionTagName + '\":\"'
453 + self.escapeJSON(sectionData) + '\"}'))
454
455
457 """Adds XML directly to UIDL.
458
459 @param xml:
460 the Xml to be added.
461 @raise PaintException:
462 if the paint operation failed.
463 """
464
465 if self._closed:
466 raise PaintException, 'Attempted to write to a closed IPaintTarget.'
467
468
469
470
471
472 if xml is not None:
473 self._tag.addData('\"' + self.escapeJSON(xml) + '\"')
474
475
476 - def addXMLSection(self, sectionTagName, sectionData, namespace):
477 """Adds XML section with namespace.
478
479 @param sectionTagName:
480 the name of the tag.
481 @param sectionData:
482 the section data.
483 @param namespace:
484 the namespace to be added.
485 @raise PaintException:
486 if the paint operation failed.
487 @see: L{IPaintTarget.addXMLSection}
488 """
489
490 if self._closed:
491 raise PaintException, 'Attempted to write to a closed IPaintTarget.'
492
493 self.startTag(sectionTagName)
494
495 if namespace is not None:
496 self.addAttribute('xmlns', namespace)
497
498 if sectionData is not None:
499 self._tag.addData('\"' + self.escapeJSON(sectionData) + '\"')
500
501 self.endTag(sectionTagName)
502
503
505 """Gets the UIDL already printed to stream. Paint target must be
506 closed before the C{getUIDL} can be called.
507
508 @return: the UIDL.
509 """
510 if self._closed:
511 return self._uidlBuffer.getvalue()
512
513 raise ValueError, 'Tried to read UIDL from open IPaintTarget'
514
515
517 """Closes the paint target. Paint target must be closed before the
518 C{getUIDL} can be called. Subsequent attempts to write to paint
519 target. If the target was already closed, call to this function
520 is ignored. will generate an exception.
521
522 @raise PaintException:
523 if the paint operation failed.
524 """
525 if self._tag is not None:
526 self._uidlBuffer.write(self._tag.getJSON())
527 self.flush()
528 self._closed = True
529
530
533
534
535
539
540
548
549
551 if text is not None:
552 self._tag.addData(text)
553
554
556 return self._usedResources
557
558
560 """Method to check if paintable is already painted into this target.
561
562 @return: true if is not yet painted into this target and is connected
563 to app
564 """
565 if p in self._paintedComponents:
566 return False
567 elif p.getApplication() is None:
568 return False
569 else:
570 return True
571
572
573 _widgetMappingCache = dict()
574
575
601
602
605
606
608 return self._usedPaintableTypes
609
610
612 """@see L{PaintTarget.isFullRepaint}"""
613 return not self._cacheEnabled
614
617 """This is basically a container for UI components variables, that
618 will be added at the end of JSON object.
619
620 @author: mattitahvonen
621 """
622
624
625 self._firstField = False
626 self._variables = list()
627 self._children = list()
628 self._attr = list()
629 self._data = StringIO()
630 self.childrenArrayOpen = False
631 self._childNode = False
632 self._tagClosed = False
633
634 self._data.write('[\"' + tagName + '\"')
635
636 self._target = target
637
638
646
647
652
653
655 if not self.childrenArrayOpen:
656
657 self.childrenArrayOpen = True
658
659
660
665
666
669
670
672 return self._childNode
673
674
676 if self._firstField:
677 self._firstField = False
678 return ''
679 else:
680 return ','
681
682
684 """@param s: json string, object or array"""
685 self._children.append(s)
686
687
696
697
699 self._attr.append(jsonNode)
700
701
715
716
719
720
722 if len(self._variables) == 0:
723 return ''
724
725 buf = StringIO()
726 buf.write(self.startField())
727 buf.write('\"v\":{')
728 for i, element in enumerate(self._variables):
729 buf.write(element.getJsonPresentation())
730 if i != len(self._variables) - 1:
731 buf.write(',')
732 buf.write('}')
733 result = buf.getvalue()
734 buf.close()
735 return result
736
742
745
747 self._value = v
748 self.name = name
749
750
752 return '\"' + self.name + '\":' \
753 + ('true' if self._value == True else 'false')
754
757
759 self._value = v
760 self.name = name
761
762
764 return '\"' + self.name + '\":\"' + self._value + '\"'
765
768
770 self._value = v
771 self.name = name
772
773
775 return '\"' + self.name + '\":' + str(self._value)
776
779
781 self._value = v
782 self.name = name
783
784
786 return '\"' + self.name + '\":' + str(self._value)
787
790
792 self._value = v
793 self.name = name
794
795
797 return '\"' + self.name + '\":' + str(self._value)
798
801
803 self._value = v
804 self.name = name
805
806
808 return '\"' + self.name + '\":' + str(self._value)
809
812
814 self._value = v
815 self.name = name
816
817
833