Requires Python < 3
[mime-editor.git] / type.py
blobd5570de713aadcc571d7702560e60cce2c336c2f
1 import os
2 import rox
3 from rox import g, basedir
4 from xml.parsers import expat
5 from xml.dom import XML_NAMESPACE, Node
6 import fields
7 import override
8 from override import FREE_NS
10 types = {}
12 home_mime = os.path.join(basedir.xdg_data_home, 'mime')
14 def data(node):
15 return ''.join([text.nodeValue for text in node.childNodes
16 if text.nodeType == Node.TEXT_NODE])
18 def get_type(name):
19 if name not in types:
20 types[name] = MIME_Type(name)
21 return types[name]
23 class MIME_Type:
24 def __init__(self, name):
25 assert name not in types
26 self.media, self.subtype = name.split('/')
28 self.comments = []
29 self.globs = []
30 self.magic = []
31 self.tree_magic = []
32 self.xml = []
33 self.others = []
35 def add_comment(self, lang, comment, user):
36 self.comments.append((lang, comment, user))
38 def add_xml(self, uri, name, user):
39 self.xml.append((uri, name, user))
41 def add_magic(self, prio, root, user):
42 self.magic.append((prio, root, user))
44 def add_tree_magic(self, prio, matches, user):
45 self.tree_magic.append((prio, matches, user))
47 def add_other(self, element, user):
48 self.others.append((element, user))
50 def add_glob(self, pattern, user):
51 self.globs.append((pattern, user))
53 def get_comment(self):
54 best = None
55 for lang, comment, user in self.comments:
56 if not lang:
57 return comment
58 best = comment
59 return best or self.get_name()
61 def get_name(self):
62 return self.media + '/' + self.subtype
64 def make(self, klass, list):
65 return [klass(self, item) for item in list]
67 def get_comments(self): return self.make(fields.Comment, self.comments)
68 def get_globs(self): return self.make(fields.Glob, self.globs)
69 def get_magic(self): return self.make(fields.Magic, self.magic)
70 def get_tree_magic(self): return self.make(fields.TreeMagic, self.tree_magic)
71 def get_xml(self): return self.make(fields.XML, self.xml)
72 def get_others(self): return self.make(fields.Other, self.others)
74 def remove_user(self):
75 for list in ['comments', 'globs', 'magic', 'xml', 'others']:
76 setattr(self, list,
77 [x for x in getattr(self, list) if not x[-1]])
79 class FieldParser:
80 def __init__(self, type, attrs):
81 self.type = type
83 def start(self, element, attrs): pass
84 def data(self, data): pass
85 def end(self): pass
87 class CommentParser(FieldParser):
88 def __init__(self, type, attrs, user):
89 FieldParser.__init__(self, type, attrs)
90 self.lang = attrs.get(XML_NAMESPACE + ' lang', None)
91 self.comment = ''
92 self.user = user
94 def data(self, data):
95 self.comment += data
97 def end(self):
98 self.type.add_comment(self.lang, self.comment, self.user)
100 class Match:
101 def __init__(self, parent, user):
102 self.parent = parent
103 self.matches = []
104 self.user = user
106 self.offset = self.type = self.value = self.mask = None
108 def write_xml(self, node):
109 for m in self.matches:
110 new = node.ownerDocument.createElementNS(FREE_NS, 'match')
111 new.setAttributeNS(None, 'offset', m.offset)
112 new.setAttributeNS(None, 'type', m.type)
113 new.setAttributeNS(None, 'value', m.value)
114 if m.mask:
115 new.setAttributeNS(None, 'mask', m.mask)
116 node.appendChild(new)
117 m.write_xml(new)
119 def equals_node(self, node):
120 if self.parent:
121 if node.localName != 'match' or node.namespaceURI != FREE_NS:
122 return False
123 def get(x, d):
124 a = node.getAttributeNS(None, x)
125 if a is None or a == '': return d
126 return a
127 offset = get('offset', '?')
128 type = get('type', '?')
129 value = get('value', '?')
130 mask = get('mask', None)
131 if self.offset != offset or self.type != type or \
132 self.value != value or self.mask != mask:
133 return False
135 kids = []
136 for k in node.childNodes:
137 if k.nodeType != Node.ELEMENT_NODE: continue
138 if k.localName != 'match' or k.namespaceURI != FREE_NS: continue
139 kids.append(k)
141 if len(self.matches) != len(kids):
142 return False
144 for m, k in zip(self.matches, kids):
145 if not m.equals_node(k): return False
147 return True
149 class MagicParser(FieldParser):
150 def __init__(self, type, attrs, user):
151 FieldParser.__init__(self, type, attrs)
152 self.prio = int(attrs.get('priority', 50))
153 self.match = Match(None, user)
154 self.user = user
156 def start(self, element, attrs):
157 new = Match(self.match, self.user)
158 new.offset = attrs.get('offset', '?')
159 new.type = attrs.get('type', '?')
160 new.value = attrs.get('value', '?')
161 new.mask = attrs.get('mask', None)
162 self.match.matches.append(new)
163 self.match = new
165 def end(self):
166 if self.match.parent:
167 self.match = self.match.parent
168 else:
169 self.type.add_magic(self.prio, self.match, self.user)
171 class TreeMatch:
172 def __init__(self, parent, user):
173 self.parent = parent
174 self.matches = []
175 self.user = user
177 self.path = self.type = self.match_case = self.executable = self.non_empty = self.mimetype = None
179 def __str__(self):
180 return "<%s: %s %s %s %s %s>" % (self.path, self.type, self.match_case, self.executable, self.non_empty, self.mimetype)
182 class TreeMagicParser(FieldParser):
183 def __init__(self, type, attrs, user):
184 FieldParser.__init__(self, type, attrs)
185 self.prio = int(attrs.get('priority', 50))
186 self.parent = None
187 self.match = self
188 self.matches = []
189 self.user = user
191 def start(self, element, attrs):
192 new = TreeMatch(self.match, self.user)
194 new.path = attrs.get('path', '?')
195 new.type = attrs.get('type', '?')
196 new.match_case = attrs.get('match-case', '?')
197 new.executable = attrs.get('executable', '?')
198 new.non_empty = attrs.get('non-empty', '?')
199 new.mimetype = attrs.get('mimetype', '?')
201 self.match.matches.append(new)
202 self.match = new
204 def end(self):
205 if self.match.parent:
206 self.match = self.match.parent
207 else:
208 self.type.add_tree_magic(self.prio, self.matches, self.user)
210 def __str__(self):
211 return "treemagic: " + '\n'.join([str(m) for m in self.matches])
213 class Scanner:
214 def __init__(self):
215 self.level = 0
216 self.type = None
217 self.handler = None
219 def parse(self, path, user):
220 parser = expat.ParserCreate(namespace_separator = ' ')
221 parser.StartElementHandler = self.start
222 parser.EndElementHandler = self.end
223 parser.CharacterDataHandler = self.data
224 self.user = user
225 parser.ParseFile(file(path))
227 def start(self, element, attrs):
228 self.level += 1
229 if self.level == 1:
230 assert element == FREE_NS + ' mime-info'
231 elif self.level == 2:
232 assert element == FREE_NS + ' mime-type'
233 self.type = get_type(attrs['type'])
234 elif self.level == 3:
235 if element == FREE_NS + ' comment':
236 self.handler = CommentParser(self.type, attrs,
237 self.user)
238 elif element == FREE_NS + ' glob':
239 self.type.add_glob(attrs['pattern'], self.user)
240 elif element == FREE_NS + ' magic':
241 self.handler = MagicParser(self.type, attrs,
242 self.user)
243 elif element == FREE_NS + ' root-XML':
244 self.type.add_xml(attrs['namespaceURI'],
245 attrs['localName'],
246 self.user)
247 elif element == FREE_NS + ' treemagic':
248 self.handler = TreeMagicParser(self.type, attrs, self.user)
249 else:
250 self.type.add_other(element, self.user)
251 else:
252 assert self.handler, "Unknown element <%s>" % element
253 self.handler.start(element, attrs)
255 def end(self, element):
256 if self.handler:
257 self.handler.end()
258 self.level -=1
259 if self.level == 1:
260 self.type = None
261 elif self.level == 2:
262 self.handler = None
264 def data(self, data):
265 if self.handler:
266 self.handler.data(data)
268 def scan_file(path, user):
269 if not path.endswith('.xml'): return
270 if not os.path.exists(path):
271 return
272 scanner = Scanner()
273 try:
274 scanner.parse(path, user)
275 except:
276 rox.report_exception()
278 def add_type(name, comment, glob):
279 doc = override.get_override()
281 root = doc.documentElement
282 type_node = doc.createElementNS(FREE_NS, 'mime-type')
283 root.appendChild(type_node)
285 type_node.setAttributeNS(None, 'type', name)
287 f = doc.createElementNS(FREE_NS, 'comment')
288 f.appendChild(doc.createTextNode(comment))
289 type_node.appendChild(f)
291 if glob:
292 f = doc.createElementNS(FREE_NS, 'glob')
293 f.setAttributeNS(None, 'pattern', glob)
294 type_node.appendChild(f)
296 override.write_override(doc)
297 import __main__
298 __main__.box.show_type(name)
300 def delete_type(name):
301 doc = override.get_override()
302 removed = False
303 for c in doc.documentElement.childNodes:
304 if c.nodeType != Node.ELEMENT_NODE: continue
305 if c.nodeName != 'mime-type': continue
306 if c.namespaceURI != FREE_NS: continue
307 if c.getAttributeNS(None, 'type') != name: continue
308 doc.documentElement.removeChild(c)
309 removed = True
310 if not removed:
311 rox.alert(_("No user-provided information about this type -- can't remove anything"))
312 return
313 if not rox.confirm(_("Really remove all user-specified information about type %s?") % name,
314 g.STOCK_DELETE):
315 return
316 override.write_override(doc)
318 # Type names defined even without Override.xml
319 system_types = None
321 def init():
322 "(Re)read the database."
323 global types, system_types
324 if system_types is None:
325 types = {}
326 for mime_dir in basedir.load_data_paths('mime'):
327 packages_dir = os.path.join(mime_dir, 'packages')
328 if not os.path.isdir(packages_dir):
329 continue
330 packages = os.listdir(packages_dir)
331 packages.sort()
332 for package in packages:
333 if package == 'Override.xml' and mime_dir == home_mime: continue
334 scan_file(os.path.join(packages_dir, package), False)
335 system_types = types.keys()
336 else:
337 for t in types.keys():
338 if t not in system_types:
339 del types[t]
340 for t in types.values():
341 t.remove_user()
342 scan_file(override.user_override, True)