Allow "" as a valid value in a OptionsBox menu (reported by Guido Schimmels).
[rox-lib.git] / python / rox / AppInfo.py
blob964be2b487d1d2efad1e8702e66a017dd483b1d4
1 """Parse the AppInfo files.
2 Written by Christopher Arndt and Stephen Watson."""
4 import sys
5 from xml.dom import Node, minidom, XML_NAMESPACE
7 def _getlangs(langs):
8 if langs is None:
9 langs = ['en', 'C', '']
10 try:
11 import rox #.i18n
12 langs = rox.i18n.langs + langs
13 except:
14 pass
15 elif type(langs) is type(''):
16 langs = [langs]
17 return langs
19 def _data(node):
20 """Return all the text directly inside this DOM Node."""
21 return ''.join([text.nodeValue for text in node.childNodes
22 if text.nodeType == Node.TEXT_NODE])
24 class AppInfo:
25 """Parsed AppInfo.xml file. Current only deals with the <About>,
26 <Summary> and <AppMenu> elements"""
28 def __init__(self, source):
29 """Read the file and parse the <About> element."""
30 self._doc = minidom.parse(source)
31 self._parseAbout()
32 self._parseSummary()
33 self._parseMenu()
35 def _parseAbout(self):
36 """Collect 'About' info in a dictionary by language."""
37 self.about = {}
38 for ab in self._doc.documentElement.getElementsByTagName('About'):
39 lang = ab.getAttributeNS(XML_NAMESPACE, 'lang')
40 self.about[lang] = {}
42 for node in ab.childNodes:
43 if node.nodeType != Node.ELEMENT_NODE:
44 continue
45 name = node.localName or None
46 label = node.getAttribute('label') or name
47 self.about[lang][name] = label, _data(node)
49 def _parseSummary(self):
50 """Collect 'Summary' info in a dictionary by language."""
51 self.summary = {}
52 for summary in self._doc.documentElement.getElementsByTagName('Summary'):
53 lang=summary.getAttributeNS(XML_NAMESPACE, 'lang')
54 self.summary[lang] = _data(summary)
56 def _parseMenu(self):
57 # XXX do <AppMenu> parsing - data structure?
58 self.menu = []
60 for node in self._doc.getElementsByTagName('AppMenu'):
61 self._parseAppMenu(self.menu, node)
63 def _parseAppMenu(self, menus, node):
64 """Recursivly parse the <AppMenu>s"""
65 for item in node.getElementsByTagName('Item'):
66 opt=item.getAttribute('option')
67 labels={}
68 for label in item.getElementsByTagName('Label'):
69 lang=label.getAttributeNS(XML_NAMESPACE, 'lang')
70 labels[lang]=_data(label)
71 sub_menus=[]
72 for sub in item.getElementsByTagName('AppMenu'):
73 self._parseAppMenu(sub_menus, sub)
74 menus.append({'option': opt, 'label':
75 labels, 'sub-menus': sub_menus})
77 def getAbout(self, elname, langs=None):
78 """Return an entry from the <About> section.
80 elname is the name of the element to return text from
81 langs is a list of acceptable languages, or None to use
82 rox.i18n.langs
83 """
84 langs = _getlangs(langs)
86 for lang in langs:
87 if self.about.has_key(lang):
88 if self.about[lang].has_key(elname):
89 return self.about[lang][elname]
90 else:
91 return None
93 def getAuthors(self, langs=None):
94 """Return the contents of the <Authors> element in the
95 <About> section (also tries <Author> if needed."""
97 auth=self.getAbout('Authors', langs)
98 if auth is None:
99 auth=self.getAbout('Author', langs)
100 if auth is None:
101 return ''
102 return auth[1]
104 def setAbout(self, elname, value, lang):
105 """Set the value of an element in the <About> section.
107 If no element 'elname' is present in the <About> section with the
108 matching 'lang' attribute, append a new one. If no such section
109 exists yet, create it first.
110 value must be a unicode string.
113 if not self.about.has_key(lang):
114 ab = self._doc.createElement('About')
115 ab.setAttributeNS(XML_NAMESPACE, 'xml:lang', lang)
116 self._doc.documentElement.appendChild(ab)
118 for ab in self._doc.documentElement.getElementsByTagName('About'):
119 if ab.getAttributeNS(XML_NAMESPACE, 'lang') == lang:
120 for node in ab.childNodes:
121 if node.nodeType == Node.ELEMENT_NODE and \
122 node.localName == elname:
123 ab.removeChild(node)
124 el = self._doc.createElement(elname)
125 text = self._doc.createTextNode(value)
126 el.appendChild(text)
127 ab.appendChild(el)
128 self._parseAbout()
130 def getSummary(self, langs=None):
131 """Return the content of the <Summary> element.
133 langs is a list of acceptable languages, or None to use
134 rox.i18n.langs
136 langs = _getlangs(langs)
138 for lang in langs:
139 if self.summary.has_key(lang):
140 return self.summary[lang]
141 else:
142 return None
144 def setSummary(self, value, lang):
145 """Set content of the Summary element with matching 'lang' attribute.
147 If no such element is present, append a new one to the DOM object.
148 value must be a unicode string.
151 if self.summary.has_key(lang):
152 for summary in self._doc.documentElement.getElementsByTagName(
153 'Summary'):
154 if summary.getAttributeNS(XML_NAMESPACE, 'lang') == lang:
155 summary.parentNode.removeChild(summary)
156 summary = self._doc.createElement('Summary')
157 summary.setAttributeNS(XML_NAMESPACE, 'xml:lang', lang)
158 text = self._doc.createTextNode(value)
159 summary.appendChild(text)
160 self._doc.documentElement.appendChild(summary)
161 self._parseSummary()
163 def findElements(self, elname, ns=None):
164 """Return all instances of the named element with an optional name
165 space. They are returned as DOM objects."""
166 if ns is None:
167 return self._doc.getElementsByTagName(elname)
168 return self._doc.getElementsByTagNameNS(ns, elname)
170 def _getMenu(self, item, langs):
171 ritem={'option': item['option']}
173 labels=item['label']
175 for lang in langs:
176 #print labels, lang, labels.has_key(lang)
177 if labels.has_key(lang):
178 ritem['label']=labels[lang]
179 break
180 else:
181 try:
182 ritem['label']=labels['']
183 except:
184 ritem['label']=''
186 subs=[]
187 for s in item['sub-menus']:
188 subs.append(self._getMenu(s, langs))
189 ritem['sub-menus']=subs
191 return ritem
193 def getAppMenu(self, langs=None):
194 """Return list of all menu entries. Each item in the list is a
195 dictionary with these keys:
196 'option': the option to pass to the app for each item
197 'label': the label of the item in an appropriate language
198 'sub-menus': list of sub menu items"""
200 langs = _getlangs(langs)
202 menus=[]
203 for item in self.menu:
204 menus.append(self._getMenu(item, langs))
206 return menus
208 def _getTypeList(self, element):
209 types=[]
210 for node in self._doc.getElementsByTagName(element):
211 for t in node.getElementsByTagNameNS(None, 'MimeType'):
212 types.append(t.getAttribute('type'))
213 return types
215 def getCanRun(self):
216 """Return list of named MIME types that this application declares
217 it can handle."""
218 return self._getTypeList('CanRun')
220 def getCanThumbnail(self):
221 """Return list of named MIME types that this application declares
222 it can generate thumbnails for."""
223 return self._getTypeList('CanThumbnail')
225 def __str__(self):
226 return self._doc.toprettyxml('', '', encoding='utf-8')
228 def writeToFile(self, fname):
229 fp = open(fname, 'wb')
230 fp.write(str(self))
231 fp.close()
233 # Some test code
234 if __name__=='__main__':
235 print _getlangs(None)
236 ai=AppInfo(sys.argv[1])
237 #print ai
238 print 'Summary: %s' % ai.getSummary()
239 print ai.getAbout('Authors')
240 print 'Authors: %s' % ai.getAuthors()
241 #print ai.findElements('AppMenu')
242 print ai.menu
243 for menu in ai.getAppMenu():
244 print '%s (%s) -> %d sub' % (menu['label'], menu['option'],
245 len(menu['sub-menus']))
246 #print ai.findElements('CanRun')
247 print ai.getCanRun()
248 print ai.getCanThumbnail()