OSDN Git Service

Snap for 4683893 from c8e8062cbeec8f08050867db132fd8d1479ccfef to pi-release
[android-x86/system-media.git] / camera / docs / metadata_validate.py
1 #!/usr/bin/python
2
3 #
4 # Copyright (C) 2012 The Android Open Source Project
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 #      http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 #
18
19 """
20 Usage:
21   metadata_validate.py <filename.xml>
22   - validates that the metadata properties defined in filename.xml are
23     semantically correct.
24   - does not do any XSD validation, use xmllint for that (in metadata-validate)
25
26 Module:
27   A set of helpful functions for dealing with BeautifulSoup element trees.
28   Especially the find_* and fully_qualified_name functions.
29
30 Dependencies:
31   BeautifulSoup - an HTML/XML parser available to download from
32                   http://www.crummy.com/software/BeautifulSoup/
33 """
34
35 from bs4 import BeautifulSoup
36 from bs4 import Tag
37 import sys
38
39
40 #####################
41 #####################
42
43 def fully_qualified_name(entry):
44   """
45   Calculates the fully qualified name for an entry by walking the path
46   to the root node.
47
48   Args:
49     entry: a BeautifulSoup Tag corresponding to an <entry ...> XML node,
50            or a <clone ...> XML node.
51
52   Raises:
53     ValueError: if entry does not correspond to one of the above XML nodes
54
55   Returns:
56     A string with the full name, e.g. "android.lens.info.availableApertureSizes"
57   """
58
59   filter_tags = ['namespace', 'section']
60   parents = [i['name'] for i in entry.parents if i.name in filter_tags]
61
62   if entry.name == 'entry':
63     name = entry['name']
64   elif entry.name == 'clone':
65     name = entry['entry'].split(".")[-1] # "a.b.c" => "c"
66   else:
67     raise ValueError("Unsupported tag type '%s' for element '%s'" \
68                         %(entry.name, entry))
69
70   parents.reverse()
71   parents.append(name)
72
73   fqn = ".".join(parents)
74
75   return fqn
76
77 def find_parent_by_name(element, names):
78   """
79   Find the ancestor for an element whose name matches one of those
80   in names.
81
82   Args:
83     element: A BeautifulSoup Tag corresponding to an XML node
84
85   Returns:
86     A BeautifulSoup element corresponding to the matched parent, or None.
87
88     For example, assuming the following XML structure:
89       <static>
90         <anything>
91           <entry name="Hello" />   # this is in variable 'Hello'
92         </anything>
93       </static>
94
95       el = find_parent_by_name(Hello, ['static'])
96       # el is now a value pointing to the '<static>' element
97   """
98   matching_parents = [i.name for i in element.parents if i.name in names]
99
100   if matching_parents:
101     return matching_parents[0]
102   else:
103     return None
104
105 def find_all_child_tags(element, tag):
106     """
107     Finds all the children that are a Tag (as opposed to a NavigableString),
108     with a name of tag. This is useful to filter out the NavigableString out
109     of the children.
110
111     Args:
112       element: A BeautifulSoup Tag corresponding to an XML node
113       tag: A string representing the name of the tag
114
115     Returns:
116       A list of Tag instances
117
118       For example, given the following XML structure:
119         <enum>                    # This is the variable el
120           Hello world             # NavigableString
121           <value>Apple</value>    # this is the variale apple (Tag)
122           <value>Orange</value>   # this is the variable orange (Tag)
123           Hello world again       # NavigableString
124         </enum>
125
126         lst = find_all_child_tags(el, 'value')
127         # lst is [apple, orange]
128
129     """
130     matching_tags = [i for i in element.children if isinstance(i, Tag) and i.name == tag]
131     return matching_tags
132
133 def find_child_tag(element, tag):
134     """
135     Finds the first child that is a Tag with the matching name.
136
137     Args:
138       element: a BeautifulSoup Tag
139       tag: A String representing the name of the tag
140
141     Returns:
142       An instance of a Tag, or None if there was no matches.
143
144       For example, given the following XML structure:
145         <enum>                    # This is the variable el
146           Hello world             # NavigableString
147           <value>Apple</value>    # this is the variale apple (Tag)
148           <value>Orange</value>   # this is the variable orange (Tag)
149           Hello world again       # NavigableString
150         </enum>
151
152         res = find_child_tag(el, 'value')
153         # res is apple
154     """
155     matching_tags = find_all_child_tags(element, tag)
156     if matching_tags:
157         return matching_tags[0]
158     else:
159         return None
160
161 def find_kind(element):
162   """
163   Finds the kind Tag ancestor for an element.
164
165   Args:
166     element: a BeautifulSoup Tag
167
168   Returns:
169     a BeautifulSoup tag, or None if there was no matches
170
171   Remarks:
172     This function only makes sense to be called for an Entry, Clone, or
173     InnerNamespace XML types. It will always return 'None' for other nodes.
174   """
175   kinds = ['dynamic', 'static', 'controls']
176   parent_kind = find_parent_by_name(element, kinds)
177   return parent_kind
178
179 def validate_error(msg):
180   """
181   Print a validation error to stderr.
182
183   Args:
184     msg: a string you want to be printed
185   """
186   print >> sys.stderr, "ERROR: " + msg
187
188
189 def validate_clones(soup):
190   """
191   Validate that all <clone> elements point to an existing <entry> element.
192
193   Args:
194     soup - an instance of BeautifulSoup
195
196   Returns:
197     True if the validation succeeds, False otherwise
198   """
199   success = True
200
201   for clone in soup.find_all("clone"):
202     clone_entry = clone['entry']
203     clone_kind = clone['kind']
204
205     parent_kind = find_kind(clone)
206
207     find_entry = lambda x: x.name == 'entry'                           \
208                        and find_kind(x) == clone_kind                  \
209                        and fully_qualified_name(x) == clone_entry
210     matching_entry = soup.find(find_entry)
211
212     if matching_entry is None:
213       error_msg = ("Did not find corresponding clone entry '%s' " +    \
214                "with kind '%s'") %(clone_entry, clone_kind)
215       validate_error(error_msg)
216       success = False
217
218     clone_name = fully_qualified_name(clone)
219     if clone_name != clone_entry:
220       error_msg = ("Clone entry target '%s' did not match fully qualified "  + \
221                    "name '%s'.") %(clone_entry, clone_name)
222       validate_error(error_msg)
223       success = False
224
225     if matching_entry is not None:
226       entry_hal_major_version = 3
227       entry_hal_minor_version = 2
228       entry_hal_version = matching_entry.get('hal_version')
229       if entry_hal_version is not None:
230         entry_hal_major_version = int(entry_hal_version.partition('.')[0])
231         entry_hal_minor_version = int(entry_hal_version.partition('.')[2])
232
233       clone_hal_major_version = entry_hal_major_version
234       clone_hal_minor_version = entry_hal_minor_version
235       clone_hal_version = clone.get('hal_version')
236       if clone_hal_version is not None:
237         clone_hal_major_version = int(clone_hal_version.partition('.')[0])
238         clone_hal_minor_version = int(clone_hal_version.partition('.')[2])
239
240       if clone_hal_major_version < entry_hal_major_version or \
241           (clone_hal_major_version == entry_hal_major_version and \
242            clone_hal_minor_version < entry_hal_minor_version):
243         error_msg = ("Clone '%s' HAL version '%d.%d' is older than entry target HAL version '%d.%d'" \
244                    % (clone_name, clone_hal_major_version, clone_hal_minor_version, entry_hal_major_version, entry_hal_minor_version))
245         validate_error(error_msg)
246         success = False
247
248   return success
249
250 # All <entry> elements with container=$foo have a <$foo> child
251 # If type="enum", <enum> tag is present
252 # In <enum> for all <value id="$x">, $x is numeric
253 def validate_entries(soup):
254   """
255   Validate all <entry> elements with the following rules:
256     * If there is a container="$foo" attribute, there is a <$foo> child
257     * If there is a type="enum" attribute, there is an <enum> child
258     * In the <enum> child, all <value id="$x"> have a numeric $x
259
260   Args:
261     soup - an instance of BeautifulSoup
262
263   Returns:
264     True if the validation succeeds, False otherwise
265   """
266   success = True
267   for entry in soup.find_all("entry"):
268     entry_container = entry.attrs.get('container')
269
270     if entry_container is not None:
271       container_tag = entry.find(entry_container)
272
273       if container_tag is None:
274         success = False
275         validate_error(("Entry '%s' in kind '%s' has type '%s' but " +  \
276                  "missing child element <%s>")                          \
277                  %(fully_qualified_name(entry), find_kind(entry),       \
278                  entry_container, entry_container))
279
280     enum = entry.attrs.get('enum')
281     if enum and enum == 'true':
282       if entry.enum is None:
283         validate_error(("Entry '%s' in kind '%s' is missing enum")     \
284                                % (fully_qualified_name(entry), find_kind(entry),
285                                   ))
286         success = False
287
288       else:
289         for value in entry.enum.find_all('value'):
290           value_id = value.attrs.get('id')
291
292           if value_id is not None:
293             try:
294               id_int = int(value_id, 0) #autoguess base
295             except ValueError:
296               validate_error(("Entry '%s' has id '%s', which is not" + \
297                                         " numeric.")                   \
298                              %(fully_qualified_name(entry), value_id))
299               success = False
300     else:
301       if entry.enum:
302         validate_error(("Entry '%s' kind '%s' has enum el, but no enum attr")  \
303                                % (fully_qualified_name(entry), find_kind(entry),
304                                   ))
305         success = False
306
307     deprecated = entry.attrs.get('deprecated')
308     if deprecated and deprecated == 'true':
309       if entry.deprecation_description is None:
310         validate_error(("Entry '%s' in kind '%s' is deprecated, but missing deprecation description") \
311                        % (fully_qualified_name(entry), find_kind(entry),
312                        ))
313         success = False
314     else:
315       if entry.deprecation_description is not None:
316         validate_error(("Entry '%s' in kind '%s' has deprecation description, but is not deprecated") \
317                        % (fully_qualified_name(entry), find_kind(entry),
318                        ))
319         success = False
320
321   return success
322
323 def validate_xml(xml):
324   """
325   Validate all XML nodes according to the rules in validate_clones and
326   validate_entries.
327
328   Args:
329     xml - A string containing a block of XML to validate
330
331   Returns:
332     a BeautifulSoup instance if validation succeeds, None otherwise
333   """
334
335   soup = BeautifulSoup(xml, features='xml')
336
337   succ = validate_clones(soup)
338   succ = validate_entries(soup) and succ
339
340   if succ:
341     return soup
342   else:
343     return None
344
345 #####################
346 #####################
347
348 if __name__ == "__main__":
349   if len(sys.argv) <= 1:
350     print >> sys.stderr, "Usage: %s <filename.xml>" % (sys.argv[0])
351     sys.exit(0)
352
353   file_name = sys.argv[1]
354   succ = validate_xml(file(file_name).read()) is not None
355
356   if succ:
357     print "%s: SUCCESS! Document validated" %(file_name)
358     sys.exit(0)
359   else:
360     print >> sys.stderr, "%s: ERRORS: Document failed to validate" %(file_name)
361     sys.exit(1)