PageRenderTime 52ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/xml_parse.py

https://bitbucket.org/agriggio/wxglade
Python | 1017 lines | 950 code | 28 blank | 39 comment | 55 complexity | a95848d0adfc57b25cd886e2078e0b4d MD5 | raw file
  1. """
  2. Parsers used to load an app and to generate the code from an xml file.
  3. NOTE: custom tag handler interface (called by XmlWidgetBuilder)::
  4. class CustomTagHandler:
  5. def start_elem(self, name, attrs):
  6. pass
  7. def end_elem(self, name):
  8. return True -> the handler must be removed from the Stack
  9. def char_data(self, data):
  10. return False -> no further processing needed
  11. @copyright: 2002-2007 Alberto Griggio <agriggio@users.sourceforge.net>
  12. @license: MIT (see license.txt) - THIS PROGRAM COMES WITH NO WARRANTY
  13. """
  14. import os
  15. from cStringIO import StringIO
  16. from xml.sax import SAXException, make_parser
  17. from xml.sax.handler import ContentHandler
  18. import common
  19. import config
  20. import edit_sizers
  21. import errors
  22. # ALB 2005-03-10: importing the module here prevents a segfault with python 2.4
  23. # hmmm... need to investigate this more (it seems that import of
  24. # xml.sax.expatreader should happen before something else... but what?)
  25. import xml.sax.expatreader
  26. if common.use_gui:
  27. import wx
  28. class XmlParsingError(SAXException):
  29. """\
  30. Custom exception to report problems during parsing
  31. """
  32. locator = None
  33. def __init__(self, msg):
  34. if self.locator:
  35. l = self.locator
  36. msg += ' _((line: %s, column: %s))' % (l.getLineNumber(),
  37. l.getColumnNumber())
  38. SAXException.__init__(self, msg)
  39. # end of class XmlParsingError
  40. class XmlParser(ContentHandler):
  41. """\
  42. 'abstract' base class of the parsers used to load an app and to generate
  43. the code
  44. @ivar _curr_prop: Name of the current property
  45. @ivar _curr_prop_val: Value of the current property (list into which the
  46. various pieces of char data collected are inserted)
  47. @ivar _objects: Stack of 'alive' objects
  48. @ivar _sizer_item: Stack of sizer items
  49. @ivar _sizers: Stack of sizer objects
  50. @ivar _windows: Stack of window objects (derived by wxWindow)
  51. @ivar locator: Document locator
  52. """
  53. def __init__(self):
  54. self._objects = Stack()
  55. self._windows = Stack()
  56. self._sizers = Stack()
  57. self._sizer_item = Stack()
  58. self._curr_prop = None
  59. self._curr_prop_val = []
  60. self._appl_started = False
  61. self.top = self._objects.top
  62. self.parser = make_parser()
  63. self.parser.setContentHandler(self)
  64. self.locator = None
  65. def parse(self, source):
  66. # Permanent workaround for Python bug "Sax parser crashes if given
  67. # unicode file name" (http://bugs.python.org/issue11159).
  68. #
  69. # This bug causes a UnicodeEncodeError if the SAX XML parser wants to
  70. # store an unicode filename internally.
  71. #
  72. # That's not a general file handling issue because the parameter
  73. # source is an open file already.
  74. source = StringIO(source.read())
  75. self.parser.parse(source)
  76. source.close()
  77. def parse_string(self, source):
  78. source = StringIO(source)
  79. self.parser.parse(source)
  80. source.close()
  81. def setDocumentLocator(self, locator):
  82. self.locator = locator
  83. XmlParsingError.locator = locator
  84. def startElement(self, name, attrs):
  85. raise NotImplementedError
  86. def endElement(self, name, attrs):
  87. raise NotImplementedError
  88. def characters(self, data):
  89. raise NotImplementedError
  90. def pop(self):
  91. try:
  92. return self._objects.pop().pop()
  93. except AttributeError:
  94. return None
  95. # end of class XmlParser
  96. class XmlWidgetBuilder(XmlParser):
  97. """\
  98. parser used to build the tree of widgets from an xml file
  99. """
  100. def startElement(self, name, attrs):
  101. if name == 'application':
  102. # get properties of the app
  103. self._appl_started = True
  104. app = common.app_tree.app
  105. encoding = attrs.get("encoding")
  106. if encoding:
  107. try:
  108. unicode('a', encoding)
  109. except LookupError:
  110. pass
  111. else:
  112. app.encoding = encoding
  113. app.encoding_prop.set_value(encoding)
  114. path = attrs.get("path")
  115. if path:
  116. app.output_path = path
  117. app.outpath_prop.set_value(path)
  118. name = attrs.get("name")
  119. if name:
  120. app.name = name
  121. app.name_prop.toggle_active(True)
  122. app.name_prop.set_value(name)
  123. klass = attrs.get("class")
  124. if klass:
  125. app.klass = klass
  126. app.klass_prop.toggle_active(True)
  127. app.klass_prop.set_value(klass)
  128. option = attrs.get("option")
  129. if option:
  130. try:
  131. option = int(option)
  132. except ValueError:
  133. option = config.default_multiple_files
  134. app.codegen_opt = option
  135. app.codegen_prop.set_value(option)
  136. language = attrs.get('language')
  137. if language:
  138. app.codewriters_prop.set_str_value(language)
  139. app.set_language(language)
  140. top_win = attrs.get("top_window")
  141. if top_win:
  142. self.top_window = top_win
  143. try:
  144. use_gettext = int(attrs["use_gettext"])
  145. except (KeyError, ValueError):
  146. use_gettext = config.default_use_gettext
  147. if use_gettext:
  148. app.use_gettext = True
  149. app.use_gettext_prop.set_value(True)
  150. try:
  151. is_template = int(attrs["is_template"])
  152. except (KeyError, ValueError):
  153. is_template = False
  154. app.is_template = is_template
  155. try:
  156. overwrite = int(attrs['overwrite'])
  157. except (KeyError, ValueError):
  158. overwrite = config.default_overwrite
  159. if overwrite:
  160. app.overwrite = True
  161. app.overwrite_prop.set_value(True)
  162. else:
  163. app.overwrite = False
  164. app.overwrite_prop.set_value(False)
  165. try:
  166. use_new_namespace = int(attrs['use_new_namespace'])
  167. except (KeyError, ValueError):
  168. use_new_namespace = False
  169. app.set_use_old_namespace(not use_new_namespace)
  170. app.use_old_namespace_prop.set_value(not use_new_namespace)
  171. indent_symbol = attrs.get("indent_symbol")
  172. if indent_symbol == 'space':
  173. app.indent_mode = 1
  174. elif indent_symbol == 'tab':
  175. app.indent_mode = 0
  176. app.indent_mode_prop.set_value(app.indent_mode)
  177. indent = attrs.get("indent_amount")
  178. if indent:
  179. try:
  180. indent_amount = int(indent)
  181. except (KeyError, ValueError):
  182. indent_amount = config.default_indent_amount
  183. else:
  184. app.indent_amount = indent_amount
  185. app.indent_amount_prop.set_value(indent_amount)
  186. source_extension = attrs.get("source_extension")
  187. if source_extension and source_extension[0] == '.':
  188. app.source_ext = source_extension[1:]
  189. app.source_ext_prop.set_value(source_extension[1:])
  190. header_extension = attrs.get("header_extension")
  191. if header_extension and header_extension[0] == '.':
  192. app.header_ext = header_extension[1:]
  193. app.header_ext_prop.set_value(header_extension[1:])
  194. try:
  195. for_version = attrs['for_version']
  196. app.for_version = for_version
  197. app.for_version_prop.set_str_value(for_version)
  198. except KeyError:
  199. pass
  200. return
  201. if not self._appl_started:
  202. raise XmlParsingError(
  203. _("the root of the tree must be <application>")
  204. )
  205. if name == 'object':
  206. # create the object and push it on the appropriate stacks
  207. XmlWidgetObject(attrs, self)
  208. else:
  209. # handling of the various properties
  210. try:
  211. # look for a custom handler to push on the stack
  212. handler = self.top().obj.get_property_handler(name)
  213. if handler:
  214. self.top().prop_handlers.push(handler)
  215. # get the top custom handler and use it if there's one
  216. handler = self.top().prop_handlers.top()
  217. if handler:
  218. handler.start_elem(name, attrs)
  219. except AttributeError:
  220. pass
  221. self._curr_prop = name
  222. def endElement(self, name):
  223. if name == 'application':
  224. self._appl_started = False
  225. if hasattr(self, 'top_window'):
  226. common.app_tree.app.top_window = self.top_window
  227. common.app_tree.app.top_win_prop.SetStringSelection(
  228. self.top_window)
  229. return
  230. if name == 'object':
  231. # remove last object from the stack
  232. obj = self.pop()
  233. if obj.klass in ('sizeritem', 'sizerslot'):
  234. return
  235. si = self._sizer_item.top()
  236. if si is not None and si.parent == obj.parent:
  237. sprop = obj.obj.sizer_properties
  238. # update the values
  239. sprop['option'].set_value(si.obj.option)
  240. sprop['flag'].set_value(si.obj.flag_str())
  241. sprop['border'].set_value(si.obj.border)
  242. # call the setter functions
  243. obj.obj['option'][1](si.obj.option)
  244. obj.obj['flag'][1](si.obj.flag_str())
  245. obj.obj['border'][1](si.obj.border)
  246. else:
  247. # end of a property or error
  248. # 1: set _curr_prop value
  249. data = common._encode_from_xml("".join(self._curr_prop_val))
  250. if data:
  251. try:
  252. handler = self.top().prop_handlers.top()
  253. if not handler or handler.char_data(data):
  254. # if char_data returned False,
  255. # we don't have to call add_property
  256. self.top().add_property(self._curr_prop, data)
  257. except AttributeError:
  258. pass
  259. # 2: call custom end_elem handler
  260. try:
  261. # if there is a custom handler installed for this property,
  262. # call its end_elem function: if this returns True, remove
  263. # the handler from the Stack
  264. handler = self.top().prop_handlers.top()
  265. if handler.end_elem(name):
  266. self.top().prop_handlers.pop()
  267. except AttributeError:
  268. pass
  269. self._curr_prop = None
  270. self._curr_prop_val = []
  271. def characters(self, data):
  272. if not data or data.isspace():
  273. return
  274. if self._curr_prop is None:
  275. raise XmlParsingError(_("character data can be present only "
  276. "inside properties"))
  277. self._curr_prop_val.append(data)
  278. # end of class XmlWidgetBuilder
  279. class ProgressXmlWidgetBuilder(XmlWidgetBuilder):
  280. """\
  281. Adds support for a progress dialog to the widget builder parser
  282. """
  283. def __init__(self, *args, **kwds):
  284. self.input_file = kwds.get('input_file')
  285. if self.input_file:
  286. del kwds['input_file']
  287. self.size = len(self.input_file.readlines())
  288. self.input_file.seek(0)
  289. self.progress = wx.ProgressDialog(
  290. _("Loading..."),
  291. _("Please wait while loading the app"),
  292. 20
  293. )
  294. self.step = 4
  295. self.i = 1
  296. else:
  297. self.size = 0
  298. self.progress = None
  299. XmlWidgetBuilder.__init__(self, *args, **kwds)
  300. def endElement(self, name):
  301. if self.progress:
  302. if name == 'application':
  303. self.progress.Destroy()
  304. else:
  305. if self.locator:
  306. where = self.locator.getLineNumber()
  307. value = int(round(where * 20.0 / self.size))
  308. else:
  309. # we don't have any information, so we update the progress
  310. # bar ``randomly''
  311. value = (self.step * self.i) % 20
  312. self.i += 1
  313. self.progress.Update(value)
  314. XmlWidgetBuilder.endElement(self, name)
  315. def parse(self, *args):
  316. try:
  317. XmlWidgetBuilder.parse(self, *args)
  318. finally:
  319. if self.progress:
  320. self.progress.Destroy()
  321. def parse_string(self, *args):
  322. try:
  323. XmlWidgetBuilder.parse_string(self, *args)
  324. finally:
  325. if self.progress:
  326. self.progress.Destroy()
  327. # end of class ProgressXmlWidgetBuilder
  328. class ClipboardXmlWidgetBuilder(XmlWidgetBuilder):
  329. """\
  330. Parser used to cut&paste widgets. The differences with XmlWidgetBuilder
  331. are:
  332. - No <application> tag in the piece of xml to parse
  333. - Fake parent, sizer and sizeritem objects to push on the three stacks:
  334. they keep info about the destination of the hierarchy of widgets (i.e.
  335. the target of the 'paste' command)
  336. - The first widget built must be hidden and shown again at the end of
  337. the operation
  338. """
  339. def __init__(self, parent, sizer, pos, option, flag, border):
  340. XmlWidgetBuilder.__init__(self)
  341. self.parent_node = parent.node
  342. class XmlClipboardObject(object):
  343. def __init__(self, **kwds):
  344. self.__dict__.update(kwds)
  345. par = XmlClipboardObject(obj=parent, parent=parent) # fake window obj
  346. if sizer is not None:
  347. # fake sizer object
  348. szr = XmlClipboardObject(obj=sizer, parent=parent)
  349. sizeritem = Sizeritem()
  350. sizeritem.option = option
  351. sizeritem.flag = flag
  352. sizeritem.border = border
  353. sizeritem.pos = pos
  354. # fake sizer item
  355. si = XmlClipboardObject(obj=sizeritem, parent=parent)
  356. # push the fake objects on the stacks
  357. self._objects.push(par)
  358. self._windows.push(par)
  359. if sizer is not None:
  360. self._objects.push(szr)
  361. self._sizers.push(szr)
  362. self._objects.push(si)
  363. self._sizer_item.push(si)
  364. self.depth_level = 0
  365. self._appl_started = True # no application tag when parsing from the
  366. # clipboard
  367. def startElement(self, name, attrs):
  368. if name == 'object' and attrs.has_key('name'):
  369. # generate a unique name for the copy
  370. oldname = str(attrs['name'])
  371. newname = oldname
  372. i = 0
  373. while common.app_tree.has_name(newname,node=self.parent_node ):
  374. if not i:
  375. newname = '%s_copy' % oldname
  376. else:
  377. newname = '%s_copy_%s' % (oldname, i)
  378. i += 1
  379. attrs = dict(attrs)
  380. attrs['name'] = newname
  381. XmlWidgetBuilder.startElement(self, name, attrs)
  382. if name == 'object':
  383. if not self.depth_level:
  384. common.app_tree.auto_expand = False
  385. try:
  386. self.top_obj = self.top().obj
  387. except AttributeError:
  388. common.message.exception(
  389. _('Exception! obj: %s') % self.top_obj
  390. )
  391. self.depth_level += 1
  392. def endElement(self, name):
  393. if name == 'object':
  394. obj = self.top()
  395. self.depth_level -= 1
  396. if not self.depth_level:
  397. common.app_tree.auto_expand = True
  398. try:
  399. # show the first object and update its layout
  400. common.app_tree.show_widget(self.top_obj.node)
  401. self.top_obj.show_properties()
  402. common.app_tree.select_item(self.top_obj.node)
  403. except AttributeError:
  404. common.message.exception(
  405. _('Exception! obj: %s') % self.top_obj
  406. )
  407. XmlWidgetBuilder.endElement(self, name)
  408. # end of class ClipboardXmlWidgetBuilder
  409. class XmlWidgetObject(object):
  410. """\
  411. A class to encapsulate a widget read from an xml file: its purpose is to
  412. store various widget attributes until the widget can be created
  413. @ivar in_sizers: If True, the widget is a sizer, opposite of L{in_windows}
  414. @type in_sizers: Boolean
  415. @ivar in_windows: If True, the wiget is not a sizer, pposite of
  416. L{in_sizers}
  417. @type in_windows: Boolean
  418. @ivar prop_handlers: Is a stack of custom handler functions to set
  419. properties of this object
  420. """
  421. def __init__(self, attrs, parser):
  422. self.prop_handlers = Stack()
  423. self.parser = parser
  424. self.in_windows = self.in_sizers = False
  425. try:
  426. base = attrs.get('base', None)
  427. self.klass = attrs['class']
  428. except KeyError:
  429. raise XmlParsingError(_("'object' items must have a 'class' "
  430. "attribute"))
  431. if base is not None:
  432. # if base is not None, the object is a widget (or sizer), and
  433. # not a sizeritem
  434. sizer = self.parser._sizers.top()
  435. parent = self.parser._windows.top()
  436. if parent is not None:
  437. parent = self.parent = parent.obj
  438. else:
  439. self.parent = None
  440. sizeritem = self.parser._sizer_item.top()
  441. if sizeritem is not None:
  442. sizeritem = sizeritem.obj
  443. if sizer is not None:
  444. # we must check if the sizer on the top of the stack is
  445. # really the one we are looking for: to check this
  446. if sizer.parent != parent:
  447. sizer = None
  448. else:
  449. sizer = sizer.obj
  450. if hasattr(sizeritem, 'pos'):
  451. pos = sizeritem.pos
  452. else:
  453. pos = None
  454. if parent and hasattr(parent, 'virtual_sizer') and \
  455. parent.virtual_sizer:
  456. sizer = parent.virtual_sizer
  457. sizer.node = parent.node
  458. sizeritem = Sizeritem()
  459. if pos is None:
  460. pos = sizer.get_itempos(attrs)
  461. # build the widget
  462. if pos is not None:
  463. pos = int(pos)
  464. self.obj = common.widgets_from_xml[base](attrs, parent, sizer,
  465. sizeritem, pos)
  466. try:
  467. #self.obj.klass = self.klass
  468. self.obj.set_klass(self.klass)
  469. self.obj.klass_prop.set_value(self.klass)
  470. except AttributeError:
  471. pass
  472. # push the object on the appropriate stack
  473. if isinstance(self.obj, edit_sizers.SizerBase):
  474. self.parser._sizers.push(self)
  475. self.in_sizers = True
  476. else:
  477. self.parser._windows.push(self)
  478. self.in_windows = True
  479. elif self.klass == 'sizeritem':
  480. self.obj = Sizeritem()
  481. self.parent = self.parser._windows.top().obj
  482. self.parser._sizer_item.push(self)
  483. elif self.klass == 'sizerslot':
  484. sizer = self.parser._sizers.top().obj
  485. assert sizer is not None, \
  486. _("malformed wxg file: slots can only be inside sizers!")
  487. sizer.add_slot()
  488. self.parser._sizer_item.push(self)
  489. # push the object on the _objects stack
  490. self.parser._objects.push(self)
  491. def pop(self):
  492. if self.in_windows:
  493. return self.parser._windows.pop()
  494. elif self.in_sizers:
  495. return self.parser._sizers.pop()
  496. else:
  497. return self.parser._sizer_item.pop()
  498. def add_property(self, name, val):
  499. """\
  500. adds a property to this widget. This method is not called if there
  501. was a custom handler for this property, and its char_data method
  502. returned False
  503. """
  504. if name == 'pos': # sanity check, this shouldn't happen...
  505. print 'add_property pos'
  506. return
  507. try:
  508. self.obj[name][1](val) # call the setter for this property
  509. try:
  510. prop = self.obj.properties[name]
  511. prop.set_value(val)
  512. prop.toggle_active(True)
  513. except AttributeError:
  514. pass
  515. except KeyError:
  516. # unknown property for this object
  517. # issue a warning and ignore the property
  518. import sys
  519. print >> sys.stderr, _("Warning: property '%s' not supported "
  520. "by this object ('%s') ") % (name, self.obj)
  521. #end of class XmlWidgetObject
  522. class CodeWriter(XmlParser):
  523. """\
  524. Parser used to produce the source from a given XML file
  525. @ivar _toplevels: Toplevel objects, i.e. instances of a custom class
  526. @ivar app_attrs: Attributes of the app (name, class, top_window)
  527. @type app_attrs: Dictionary
  528. @ivar top_win: Class name of the top window of the app (if any)
  529. @type top_win: String
  530. @ivar out_path: This allows to override the output path specified in the
  531. XML file
  532. @ivar preview: If True, we are generating the code for the preview
  533. @type preview: Boolean
  534. """
  535. def __init__(self, writer, input, from_string=False, out_path=None,
  536. preview=False, class_names=None):
  537. # writer: object that actually writes the code
  538. XmlParser.__init__(self)
  539. self._toplevels = Stack()
  540. self.app_attrs = {}
  541. self.top_win = ''
  542. self.out_path = out_path
  543. self.code_writer = writer
  544. self.preview = preview
  545. # used in the CustomWidget preview code, to generate better previews
  546. # (see widgets/custom_widget/codegen.py)
  547. self.class_names = class_names
  548. if self.class_names is None:
  549. self.class_names = set()
  550. if from_string:
  551. self.parse_string(input)
  552. else:
  553. inputfile = None
  554. try:
  555. inputfile = open(input)
  556. self.parse(inputfile)
  557. finally:
  558. if inputfile:
  559. inputfile.close()
  560. def startElement(self, name, attrs_impl):
  561. attrs = {}
  562. try:
  563. encoding = self.app_attrs['encoding']
  564. unicode('a', encoding)
  565. except (KeyError, LookupError):
  566. if name == 'application':
  567. encoding = str(attrs_impl.get(
  568. 'encoding',
  569. config.default_encoding
  570. ))
  571. else:
  572. encoding = config.default_encoding
  573. # turn all the attribute values from unicode to str objects
  574. for attr, val in attrs_impl.items():
  575. attrs[attr] = common._encode_from_xml(val, encoding)
  576. if name == 'application':
  577. # get the code generation options
  578. self._appl_started = True
  579. self.app_attrs = attrs
  580. try:
  581. attrs['option'] = bool(int(attrs['option']))
  582. use_multiple_files = attrs['option']
  583. except (KeyError, ValueError):
  584. use_multiple_files = attrs['option'] = \
  585. config.default_multiple_files
  586. if self.out_path is None:
  587. try:
  588. self.out_path = attrs['path']
  589. except KeyError:
  590. raise XmlParsingError(_("'path' attribute missing: could "
  591. "not generate code"))
  592. else:
  593. attrs['path'] = self.out_path
  594. # Prevent empty output path
  595. if not self.out_path:
  596. raise XmlParsingError(
  597. _("'path' attribute empty: could not generate code")
  598. )
  599. # Check if the values of use_multiple_files and out_path agree
  600. if use_multiple_files:
  601. if not os.path.isdir(self.out_path):
  602. raise errors.WxgOutputDirectoryNotExist(self.out_path)
  603. if not os.access(self.out_path, os.W_OK):
  604. raise errors.WxgOutputDirectoryNotWritable(self.out_path)
  605. else:
  606. if os.path.isdir(self.out_path):
  607. raise errors.WxgOutputPathIsDirectory(self.out_path)
  608. directory = os.path.dirname(self.out_path)
  609. if directory:
  610. if not os.path.isdir(directory):
  611. raise errors.WxgOutputDirectoryNotExist(directory)
  612. if not os.access(directory, os.W_OK):
  613. raise errors.WxgOutputDirectoryNotWritable(directory)
  614. # initialize the writer
  615. self.code_writer.initialize(attrs)
  616. return
  617. if not self._appl_started:
  618. raise XmlParsingError(
  619. _("the root of the tree must be <application>")
  620. )
  621. if name == 'object':
  622. # create the CodeObject which stores info about the current widget
  623. CodeObject(attrs, self, preview=self.preview)
  624. if attrs.has_key('name') and \
  625. attrs['name'] == self.app_attrs.get('top_window', ''):
  626. self.top_win = attrs['class']
  627. else:
  628. # handling of the various properties
  629. try:
  630. # look for a custom handler to push on the stack
  631. w = self.top()
  632. handler = self.code_writer.get_property_handler(name, w.base)
  633. if handler:
  634. w.prop_handlers.push(handler)
  635. # get the top custom handler and use it if there's one
  636. handler = w.prop_handlers.top()
  637. if handler:
  638. handler.start_elem(name, attrs)
  639. except AttributeError:
  640. common.message.exception(_('ATTRIBUTE ERROR!!'))
  641. self._curr_prop = name
  642. def endElement(self, name):
  643. if name == 'application':
  644. self._appl_started = False
  645. if self.app_attrs:
  646. self.code_writer.add_app(self.app_attrs, self.top_win)
  647. # call the finalization function of the code writer
  648. self.code_writer.finalize()
  649. return
  650. if name == 'object':
  651. obj = self.pop()
  652. if obj.klass in ('sizeritem', 'sizerslot'):
  653. return
  654. # at the end of the object, we have all the information to add it
  655. # to its toplevel parent, or to generate the code for the custom
  656. # class
  657. if obj.is_toplevel and not obj.in_sizers:
  658. self.code_writer.add_class(obj)
  659. topl = self._toplevels.top()
  660. if topl:
  661. self.code_writer.add_object(topl, obj)
  662. # if the object is not a sizeritem, check whether it
  663. # belongs to some sizer (in this case,
  664. # self._sizer_item.top() doesn't return None): if so,
  665. # write the code to add it to the sizer at the top of
  666. # the stack
  667. si = self._sizer_item.top()
  668. if si is not None and si.parent == obj.parent:
  669. szr = self._sizers.top()
  670. if not szr:
  671. return
  672. self.code_writer.add_sizeritem(topl, szr, obj,
  673. si.obj.option,
  674. si.obj.flag_str(),
  675. si.obj.border)
  676. else:
  677. # end of a property or error
  678. # 1: set _curr_prop value
  679. try:
  680. encoding = self.app_attrs['encoding']
  681. unicode('a', encoding)
  682. except (KeyError, LookupError):
  683. encoding = config.default_encoding
  684. data = common._encode_from_xml(u"".join(self._curr_prop_val),
  685. encoding)
  686. if data:
  687. handler = self.top().prop_handlers.top()
  688. if not handler or handler.char_data(data):
  689. # if char_data returned False,
  690. # we don't have to call add_property
  691. self.top().add_property(self._curr_prop, data)
  692. # 2: call custom end_elem handler
  693. try:
  694. # if there is a custom handler installed for this property,
  695. # call its end_elem function: if this returns True, remove
  696. # the handler from the stack
  697. obj = self.top()
  698. handler = obj.prop_handlers.top()
  699. if handler.end_elem(name, obj):
  700. obj.prop_handlers.pop()
  701. except AttributeError:
  702. pass
  703. self._curr_prop = None
  704. self._curr_prop_val = []
  705. def characters(self, data):
  706. if not data or data.isspace():
  707. return
  708. if self._curr_prop is None:
  709. raise XmlParsingError(_("character data can only appear inside "
  710. "properties"))
  711. self._curr_prop_val.append(data)
  712. # end of class CodeWriter
  713. class CodeObject(object):
  714. """\
  715. A class to store information needed to generate the code for a given
  716. object.
  717. @ivar in_sizers: If True, the widget is a sizer, opposite of L{in_windows}
  718. @type in_sizers: Boolean
  719. @ivar in_windows: If True, the wiget is not a sizer, pposite of
  720. L{in_sizers}
  721. @type in_windows: Boolean
  722. @ivar is_container: If True, the widget is a container (frame, dialog,
  723. panel, ...)
  724. @type is_container: Boolean
  725. @ivar is_toplevel: If True, the object is a toplevel one: for window
  726. objects, this means that they are instances of a
  727. custom class, for sizers, that they are at the top
  728. of the hierarchy.
  729. @type is_toplevel: Boolean
  730. @ivar properties: Properties of the widget sizer
  731. @type properties: Dictionary
  732. @ivar prop_handlers: Is a stack of custom handler functions to set
  733. properties of this object
  734. """
  735. def __init__(self, attrs, parser, preview=False):
  736. self.parser = parser
  737. self.in_windows = self.in_sizers = False
  738. self.is_toplevel = False
  739. self.is_container = False
  740. self.properties = {}
  741. self.prop_handlers = Stack()
  742. self.preview = preview
  743. try:
  744. base = attrs.get('base', None)
  745. self.klass = attrs['class']
  746. except KeyError:
  747. raise XmlParsingError(_("'object' items must have a 'class' "
  748. "attribute"))
  749. self.parser._objects.push(self)
  750. self.parent = self.parser._windows.top()
  751. if self.parent is not None:
  752. self.parent.is_container = True
  753. self.base = None
  754. if base is not None: # this is a ``real'' object, not a sizeritem
  755. self.name = attrs['name']
  756. self.base = common.class_names[base]
  757. can_be_toplevel = common.toplevels.has_key(base)
  758. if (self.parent is None or self.klass != self.base) and \
  759. can_be_toplevel:
  760. #self.base != 'CustomWidget':
  761. self.is_toplevel = True
  762. # ALB 2005-11-19: for panel objects, if the user sets a
  763. # custom class but (s)he doesn't want the code
  764. # to be generated...
  765. if int(attrs.get('no_custom_class', False)) and \
  766. not self.preview:
  767. self.is_toplevel = False
  768. #print 'OK:', str(self)
  769. #self.in_windows = True
  770. #self.parser._windows.push(self)
  771. else:
  772. self.parser._toplevels.push(self)
  773. elif self.preview and not can_be_toplevel and \
  774. self.base != 'CustomWidget':
  775. # if this is a custom class, but not a toplevel one,
  776. # for the preview we have to use the "real" class
  777. #
  778. # ALB 2007-08-04: CustomWidgets handle this in a special way
  779. # (see widgets/custom_widget/codegen.py)
  780. self.klass = self.base
  781. # temporary hack: to detect a sizer, check whether the name
  782. # of its class contains the string 'Sizer': TODO: find a
  783. # better way!!
  784. if base.find('Sizer') != -1:
  785. self.in_sizers = True
  786. if not self.parser._sizers.count():
  787. self.is_toplevel = True
  788. else:
  789. # the sizer is a toplevel one if its parent has not a
  790. # sizer yet
  791. sz = self.parser._sizers.top()
  792. if sz.parent != self.parent:
  793. self.is_toplevel = True
  794. self.parser._sizers.push(self)
  795. else:
  796. self.parser._windows.push(self)
  797. self.in_windows = True
  798. else: # the object is a sizeritem
  799. self.obj = Sizeritem()
  800. self.obj.flag_s = '0'
  801. self.parser._sizer_item.push(self)
  802. def __str__(self):
  803. return "<xml_code_object: %s, %s, %s>" % (self.name, self.base,
  804. self.klass)
  805. def add_property(self, name, value):
  806. if hasattr(self, 'obj'): # self is a sizeritem
  807. try:
  808. if name == 'flag':
  809. ## flag = 0
  810. ## for f in value.split('|'):
  811. ## flag |= Sizeritem.flags[f.strip()]
  812. ## setattr(self.obj, name, flag)
  813. self.obj.flag_s = value.strip()
  814. else:
  815. setattr(self.obj, name, int(value))
  816. except:
  817. raise XmlParsingError(_("property '%s' not supported by "
  818. "'%s' objects") % (name, self.klass))
  819. self.properties[name] = value
  820. def pop(self):
  821. if self.is_toplevel and not self.in_sizers:
  822. self.parser._toplevels.pop()
  823. if self.in_windows:
  824. return self.parser._windows.pop()
  825. elif self.in_sizers:
  826. return self.parser._sizers.pop()
  827. else:
  828. return self.parser._sizer_item.pop()
  829. # end of class CodeObject
  830. class Stack(object):
  831. def __init__(self):
  832. self._repr = []
  833. def push(self, elem):
  834. self._repr.append(elem)
  835. def pop(self):
  836. try:
  837. return self._repr.pop()
  838. except IndexError:
  839. return None
  840. def top(self):
  841. try:
  842. return self._repr[-1]
  843. except IndexError:
  844. return None
  845. def count(self):
  846. return len(self._repr)
  847. # end of class Stack
  848. class Sizeritem(object):
  849. if common.use_gui:
  850. flags = {'wxALL': wx.ALL,
  851. 'wxEXPAND': wx.EXPAND, 'wxALIGN_RIGHT': wx.ALIGN_RIGHT,
  852. 'wxALIGN_BOTTOM': wx.ALIGN_BOTTOM,
  853. 'wxALIGN_CENTER_HORIZONTAL': wx.ALIGN_CENTER_HORIZONTAL,
  854. 'wxALIGN_CENTER_VERTICAL': wx.ALIGN_CENTER_VERTICAL,
  855. 'wxLEFT': wx.LEFT, 'wxRIGHT': wx.RIGHT,
  856. 'wxTOP': wx.TOP,
  857. 'wxBOTTOM': wx.BOTTOM,
  858. 'wxSHAPED': wx.SHAPED,
  859. 'wxADJUST_MINSIZE': wx.ADJUST_MINSIZE, }
  860. flags['wxFIXED_MINSIZE'] = wx.FIXED_MINSIZE
  861. def __init__(self):
  862. self.option = self.border = 0
  863. self.flag = 0
  864. def __getitem__(self, name):
  865. if name != 'flag':
  866. return (None, lambda v: setattr(self, name, v))
  867. def get_flag(v):
  868. val = reduce(lambda a, b: a | b,
  869. [Sizeritem.flags[t] for t in v.split("|")])
  870. setattr(self, name, val)
  871. return (None, get_flag)
  872. ## lambda v: setattr(self, name,
  873. ## reduce(lambda a,b: a|b,
  874. ## [Sizeritem.flags[t] for t in
  875. ## v.split("|")])))
  876. def flag_str(self):
  877. """\
  878. Returns the flag attribute as a string of tokens separated by a '|'
  879. (used during the code generation)
  880. """
  881. if hasattr(self, 'flag_s'):
  882. return self.flag_s
  883. else:
  884. try:
  885. tmp = {}
  886. for k in self.flags:
  887. if self.flags[k] & self.flag:
  888. tmp[k] = 1
  889. # patch to make wxALL work
  890. remove_wxall = 4
  891. for k in ('wxLEFT', 'wxRIGHT', 'wxTOP', 'wxBOTTOM'):
  892. if k in tmp:
  893. remove_wxall -= 1
  894. if remove_wxall:
  895. try:
  896. del tmp['wxALL']
  897. except KeyError:
  898. pass
  899. else:
  900. for k in ('wxLEFT', 'wxRIGHT', 'wxTOP', 'wxBOTTOM'):
  901. try:
  902. del tmp[k]
  903. except KeyError:
  904. pass
  905. tmp['wxALL'] = 1
  906. tmp = '|'.join(tmp.keys())
  907. except:
  908. print 'EXCEPTION: self.flags = %s, self.flag = %s' % \
  909. (self.flags, repr(self.flag))
  910. raise
  911. if tmp:
  912. return tmp
  913. else:
  914. return '0'
  915. # end of class Sizeritem