Camera: Define all Properties via an XML data file

Change-Id: I82aad9e1e8c8064373a9881f255ec2eb9d2f023e
diff --git a/camera/docs/metadata_validate.py b/camera/docs/metadata_validate.py
new file mode 100755
index 0000000..8dc6050
--- /dev/null
+++ b/camera/docs/metadata_validate.py
@@ -0,0 +1,220 @@
+#!/usr/bin/python
+
+#
+# Copyright (C) 2012 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""
+Usage:
+  metadata_validate.py <filename.xml>
+  - validates that the metadata properties defined in filename.xml are
+    semantically correct.
+  - does not do any XSD validation, use xmllint for that (in metadata-validate)
+
+Module:
+  A set of helpful functions for dealing with BeautifulSoup element trees.
+  Especially the find_* and fully_qualified_name functions.
+
+Dependencies:
+  BeautifulSoup - an HTML/XML parser available to download from
+                  http://www.crummy.com/software/BeautifulSoup/
+"""
+
+from bs4 import BeautifulSoup
+import sys
+
+
+#####################
+#####################
+
+def fully_qualified_name(entry):
+  """
+  Calculates the fully qualified name for an entry by walking the path
+  to the root node.
+
+  Args:
+    entry: a BeautifulSoup Tag corresponding to an <entry ...> XML node
+
+  Returns:
+    A string with the full name, e.g. "android.lens.info.availableApertureSizes"
+  """
+  filter_tags = ['namespace', 'section']
+  parents = [i['name'] for i in entry.parents if i.name in filter_tags]
+
+  name = entry['name']
+
+  parents.reverse()
+  parents.append(name)
+
+  fqn = ".".join(parents)
+
+  return fqn
+
+def find_parent_by_name(element, names):
+  """
+  Find the ancestor for an element whose name matches one of those
+  in names.
+
+  Args:
+    element: A BeautifulSoup Tag corresponding to an XML node
+
+  Returns:
+    A BeautifulSoup element corresponding to the matched parent, or None.
+
+    For example, assuming the following XML structure:
+      <static>
+        <anything>
+          <entry name="Hello" />   # this is in variable 'Hello'
+        </anything>
+      </static>
+
+      el = find_parent_by_name(Hello, ['static'])
+      # el is now a value pointing to the '<static>' element
+  """
+  matching_parents = [i.name for i in element.parents if i.name in names]
+
+  if matching_parents:
+    return matching_parents[0]
+  else:
+    return None
+
+def find_kind(element):
+  """
+  Finds the kind Tag ancestor for an element.
+
+  Args:
+    element: a BeautifulSoup Tag
+
+  Returns:
+    a BeautifulSoup tag, or None if there was no matches
+
+  Remarks:
+    This function only makes sense to be called for an Entry, Clone, or
+    InnerNamespace XML types. It will always return 'None' for other nodes.
+  """
+  kinds = ['dynamic', 'static', 'controls']
+  parent_kind = find_parent_by_name(element, kinds)
+  return parent_kind
+
+def validate_error(msg):
+  """
+  Print a validation error to stderr.
+
+  Args:
+    msg: a string you want to be printed
+  """
+  print >> sys.stderr, "Validation error: " + msg
+
+
+def validate_clones(soup):
+  """
+  Validate that all <clone> elements point to an existing <entry> element.
+
+  Args:
+    soup - an instance of BeautifulSoup
+
+  Returns:
+    True if the validation succeeds, False otherwise
+  """
+  success = True
+
+  for clone in soup.find_all("clone"):
+    clone_entry = clone['entry']
+    clone_kind = clone['kind']
+
+    parent_kind = find_kind(clone)
+
+    find_entry = lambda x: x.name == 'entry'                           \
+                       and find_kind(x) == clone_kind                  \
+                       and fully_qualified_name(x) == clone_entry
+    matching_entry = soup.find(find_entry)
+
+    if matching_entry is None:
+      error_msg = ("Did not find corresponding clone entry '%s' " +    \
+               "with kind '%s'") %(clone_entry, clone_kind)
+      validate_error(error_msg)
+      success = False
+
+  return success
+
+# All <entry> elements with container=$foo have a <$foo> child
+def validate_entries(soup):
+  """
+  Validate all <entry> elements with the following rules:
+    * If there is a container="$foo" attribute, there is a <$foo> child
+
+  Args:
+    soup - an instance of BeautifulSoup
+
+  Returns:
+    True if the validation succeeds, False otherwise
+  """
+  success = True
+  for entry in soup.find_all("entry"):
+    entry_container = entry.attrs.get('container')
+
+    if entry_container is not None:
+      container_tag = entry.find(entry_container)
+
+      if container_tag is None:
+        success = False
+        validate_error(("Entry '%s' in kind '%s' has type '%s' but " +  \
+                 "missing child element <%s>")                          \
+                 %(fully_qualified_name(entry), find_kind(entry),       \
+                 entry_container, entry_container))
+
+  return success
+
+
+def validate_xml(file_name):
+  """
+  Validate all XML nodes according to the rules in validate_clones and
+  validate_entries.
+
+  Args:
+    file_name - a string path to an XML file we wish to validate
+
+  Returns:
+    a BeautifulSoup instance if validation succeeds, None otherwise
+  """
+
+  xml = file(file_name).read()
+  soup = BeautifulSoup(xml, features='xml')
+
+  succ = validate_clones(soup)
+  succ = validate_entries(soup) and succ
+
+  if succ:
+    return soup
+  else:
+    return None
+
+#####################
+#####################
+
+if __name__ == "__main__":
+  if len(sys.argv) <= 1:
+    print >> sys.stderr, "Usage: %s <filename.xml>" % (sys.argv[0])
+    sys.exit(0)
+
+  file_name = sys.argv[1]
+  succ = validate_xml(file_name) is not None
+
+  if succ:
+    print "%s: SUCCESS! Document validated" %(file_name)
+    sys.exit(0)
+  else:
+    print >> sys.stderr, "%s: ERRORS: Document failed to validate" %(file_name)
+    sys.exit(1)