--- /dev/null
+# -*- 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