]> CRI, Mines Paris - PSL - Photo.git/blobdiff - Products/Photo/xmputils.py
eggification
[Photo.git] / Products / Photo / xmputils.py
diff --git a/Products/Photo/xmputils.py b/Products/Photo/xmputils.py
new file mode 100755 (executable)
index 0000000..37b3e6a
--- /dev/null
@@ -0,0 +1,354 @@
+# -*- coding: utf-8 -*-
+#######################################################################################
+#   Photo is a part of Plinn - http://plinn.org                                       #
+#   Copyright (C) 2008 Benoît PIN <benoit.pin@ensmp.fr>                               #
+#                                                                                     #
+#   This program is free software; you can redistribute it and/or                     #
+#   modify it under the terms of the GNU General Public License                       #
+#   as published by the Free Software Foundation; either version 2                    #
+#   of the License, or (at your option) any later version.                            #
+#                                                                                     #
+#   This program is distributed in the hope that it will be useful,                   #
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of                    #
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                     #
+#   GNU General Public License for more details.                                      #
+#                                                                                     #
+#   You should have received a copy of the GNU General Public License                 #
+#   along with this program; if not, write to the Free Software                       #
+#   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.   #
+#######################################################################################
+""" XMP generation utilities.
+
+
+
+"""
+
+from libxml2 import newNode, parseDoc, treeError
+# prefix <-> namespaces mappings as defined in the official xmp documentation
+from standards.xmp import namespaces as xmpNs2Prefix
+from standards.xmp import prefix2Ns as xmpPrefix2Ns
+
+TIFF_ORIENTATIONS = {1 : (0, False)
+                                       ,2 : (0, True)
+                                       ,3 : (180, False)
+                                       ,4 : (180, True)
+                                       ,5 : (90, True)
+                                       ,6 : (90, False)
+                                       ,7 : (270, True)
+                                       ,8 : (270, False)}
+
+def _getRDFArrayValues(node, arrayType):
+       values = []
+       for element in iterElementChilds(node):
+               if element.name == arrayType and element.ns().content == 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' :
+                       for value in iterElementChilds(element):
+                               if value.name == 'li':
+                                       values.append(value.content)
+                       return tuple(values)
+       else :
+               raise ValueError("No %s found" % arrayType )
+
+def getBagValues(node):
+       return _getRDFArrayValues(node, 'Bag')
+
+def getSeqValues(node):
+       return _getRDFArrayValues(node, 'Seq')
+
+
+def createRDFAlt(surrounded, defaultText, rootIndex):
+       """
+       returns (as libxml2 node):
+       <surrounded>
+          <rdf:Alt>
+             <rdf:li xml:lang="x-default">defaultText</rdf:li>
+          </rdf:Alt>
+       <surrounded>
+       """
+       docNs = rootIndex.getDocumentNs()
+       rdfPrefix = docNs['http://www.w3.org/1999/02/22-rdf-syntax-ns#']
+       normalizedPrefix, name = surrounded.split(':')
+       ns = xmpPrefix2Ns[normalizedPrefix]
+       actualPrefix = docNs[ns]
+       
+       surrounded = newNode('%s:%s' % (actualPrefix, name))
+       alt = newNode('%s:Alt' % rdfPrefix)
+       li = newNode('%s:li' % rdfPrefix)
+       li.newProp('xml:lang', 'x-default')
+       li.setContent(defaultText)
+       
+       reduce(lambda a, b: a.addChild(b), (surrounded, alt, li))
+       
+       return surrounded
+
+
+def createRDFBag(surrounded, values, rootIndex):
+       """
+       returns (as libxml2 node):
+       <surrounded>
+       <rdf:Bag>
+          <rdf:li>values[0]</rdf:li>
+                       ...
+          <rdf:li>values[n]</rdf:li>
+       </rdf:Bag>
+    <surrounded>
+    """
+       return _createRDFArray(surrounded, values, False, rootIndex)
+
+def createRDFSeq(surrounded, values, rootIndex):
+       """
+       returns (as libxml2 node):
+       <surrounded>
+       <rdf:Seq>
+          <rdf:li>values[0]</rdf:li>
+                       ...
+          <rdf:li>values[n]</rdf:li>
+       </rdf:Seq>
+    <surrounded>
+    """
+       return _createRDFArray(surrounded, values, True, rootIndex)
+
+def _createRDFArray(surrounded, values, ordered, rootIndex):
+       docNs = rootIndex.getDocumentNs()
+       rdfPrefix = docNs['http://www.w3.org/1999/02/22-rdf-syntax-ns#']
+       normalizedPrefix, name = surrounded.split(':')
+       ns = xmpPrefix2Ns[normalizedPrefix]
+       actualPrefix = docNs[ns]
+       
+       
+       surrounded = newNode('%s:%s' % (actualPrefix, name))
+       if ordered is True :
+               array = newNode('%s:Seq' % rdfPrefix)
+       elif ordered is False :
+               array = newNode('%s:Bag' % rdfPrefix)
+       else :
+               raise ValueError("'ordered' parameter must be a boolean value")
+       
+       surrounded.addChild(array)
+       
+       for v in values :
+               li = newNode('%s:li' % rdfPrefix)
+               li.setContent(v)
+               array.addChild(li)
+       
+       return surrounded
+
+def createEmptyXmpDoc() :
+       emptyDocument = """
+<x:xmpmeta xmlns:x='adobe:ns:meta/'>
+  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+    <rdf:Description/>
+  </rdf:RDF>
+</x:xmpmeta>
+       """
+       d = parseDoc(emptyDocument)
+       return d
+
+def getPathIndex(doc) :
+       root = doc.getRootElement()
+       index = PathIndex(root)
+       return index
+
+
+class PathIndex :
+       """\
+       Class used to provide a convenient tree access to xmp properties by paths.
+       Issues about namespaces and prefixes are normalized during the object
+       instanciation. Ns prefixes used to access elements are those recommended in the
+       official xmp documentation from Adobe.
+       """
+       
+       def __init__(self, element, parent=None) :
+               self.unique = True
+               self.element = element
+               self.parent = parent
+               
+               elementNs = element.ns().content
+               elementPrefix = element.ns().name
+               recommendedPrefix = xmpNs2Prefix.get(elementNs, elementPrefix)
+
+               self.name = '%s:%s' % (recommendedPrefix, element.name)
+               self.namespace = elementNs
+               self.prefix = elementPrefix
+               self._index = {}
+               
+               for prop in iterElementProperties(element) :
+                       self.addChildIndex(prop)
+               
+               for child in iterElementChilds(element) :
+                       self.addChildIndex(child)
+               
+               if self.parent is None:
+                       self.nsDeclarations = self._namespaceDeclarations()
+               
+       def addChildIndex(self, child) :
+               ns = child.ns()
+               if not ns :
+                       return
+                       
+               childNs = ns.content
+               childPrefix = ns.name
+               childRecommendedPrefix = xmpNs2Prefix.get(childNs, childPrefix)
+               childName = '%s:%s' % (childRecommendedPrefix, child.name)
+
+               if not self._index.has_key(childName) :
+                       self._index[childName] = PathIndex(child, parent=self)
+               else :
+                       childIndex = self._index[childName]
+                       childIndex.unique = False
+                       for prop in iterElementProperties(child) :
+                               childIndex.addChildIndex(prop)
+
+                       for c in iterElementChilds(child) :
+                               childIndex.addChildIndex(c)
+
+               self._index[childName].parent = self
+               return self._index[childName]
+       
+       def _namespaceDeclarations(self) :
+               """\
+               returns ns / prefix pairs as found in xmp packet
+               """
+               namespaces = {}
+               namespaces[self.namespace] = self.prefix
+               for child in self._index.values() :
+                       for namespace, prefix in child._namespaceDeclarations().items() :
+                               if namespaces.has_key(namespace) :
+                                       assert namespaces[namespace] == prefix, \
+                                                       "using several prefix for the same namespace is forbidden "\
+                                                       "in this implementation"
+                               else :
+                                       namespaces[namespace] = prefix
+               return namespaces
+       
+       def getDocumentNs(self) :
+               root = self.getRootIndex()
+               return root.nsDeclarations
+       
+       def exists(self, path) :
+               o = self
+               for part in path.split('/') :
+                       if o._index.has_key(part) :
+                               o = o._index[part]
+                       else :
+                               return False
+               return True
+       
+       def __getitem__(self, path) :
+               o = self
+               try :
+                       for part in path.split('/') :
+                               if part == '.' :
+                                       continue
+                               elif part == '..' :
+                                       o = o.parent
+                               o = o._index[part]
+               except ValueError :
+                       raise KeyError, path
+               return o
+       
+       def get(self, path, default=None) :
+               try :
+                       return self[path]
+               except KeyError :
+                       return default
+       
+       def getRootIndex(self) :
+               root = self
+               while root.parent is not None :
+                       root = root.parent
+               return root
+       
+       def createChildAndIndex(self, name, rdfType, nsDeclarationElement) :
+               recommandedPrefix, name = name.split(':', 1)
+
+               if rdfType == 'prop' :
+                       try :
+                               node = self.element.newProp(name, '')
+                       except treeError :
+                               raise ValueError, (self.element, name)
+               else :
+                       node = newNode(name)
+                       self.element.addChild(node)
+               
+               # bind namespace to new node
+               uri = xmpPrefix2Ns[recommandedPrefix]
+               docNamespaces = self.getDocumentNs()
+               if not docNamespaces.has_key(uri) :
+                       try :
+                               ns = nsDeclarationElement.newNs(uri, recommandedPrefix)
+                       except treeError :
+                               raise ValueError, (uri, prefix, self.element, list(nsDeclarationElement.nsDefs()))
+                       docNamespaces[uri] = recommandedPrefix
+               else :
+                       actualPrefix = docNamespaces[uri]
+                       try :
+                               ns = self.element.searchNs(None, actualPrefix)
+                       except treeError:
+                               # cas d'un xmp verbeux : le nouvel élément n'est pas ajouté
+                               # dans le rdf:Description du ns correspondant
+                               # (après tout, ce n'est pas une obligation)
+                               # => on ajoute le ns
+                               ns = nsDeclarationElement.newNs(uri, actualPrefix)
+                       
+               
+               node.setNs(ns)
+               return self.addChildIndex(node)
+
+       def getOrCreate(self, path, rdfType, preferedNsDeclaration='rdf:RDF/rdf:Description') :
+               parts = path.split('/')
+
+               if not parts :
+                       return self
+
+               name = parts[-1]
+               parts = parts[:-1]
+               root = self.getRootIndex()
+               nsDeclarationElement = root[preferedNsDeclaration].element
+               
+               parent = self
+               for p in parts :
+                       child = parent._index.get(p, None)
+                       if child is None :
+                               child = parent.createChildAndIndex(p, None, nsDeclarationElement)
+                       parent = child
+               
+               child = parent._index.get(name, None)
+               if child is None :
+                       child = parent.createChildAndIndex(name, rdfType, nsDeclarationElement)
+
+               return child
+               
+       def __str__(self) :
+               out = []
+               pr = out.append
+               path = [self.name]
+               parent = self.parent
+               while parent :
+                       path.append(parent.name)
+                       parent = parent.parent
+               path.reverse()
+               path = '/'.join(path)
+               pr(path)
+               pr(self.name)
+               pr(self.namespace)
+               pr(str(self.unique))
+               pr('-------')
+               
+               for child in self._index.values() :
+                       pr(str(child))
+
+               return '\n'.join(out)
+
+def iterElementChilds(parent) :
+       child  = parent.children
+       while child :
+               if child.type == 'element' :
+                       yield child
+               child = child.next
+
+def iterElementProperties(element) :
+       prop = element.properties
+       while prop :
+               if prop.type == 'attribute' :
+                       yield prop
+               prop = prop.next