X-Git-Url: https://scm.cri.minesparis.psl.eu/git/Photo.git/blobdiff_plain/b0a7e10b4f32cf74864bb53268ca4d3080f23bc0..6c41809185e322ce2d30e98234f71144f78f06c0:/Products/Photo/xmputils.py?ds=inline diff --git a/Products/Photo/xmputils.py b/Products/Photo/xmputils.py new file mode 100755 index 0000000..37b3e6a --- /dev/null +++ b/Products/Photo/xmputils.py @@ -0,0 +1,354 @@ +# -*- coding: utf-8 -*- +####################################################################################### +# Photo is a part of Plinn - http://plinn.org # +# Copyright (C) 2008 Benoît PIN # +# # +# 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): + + + defaultText + + + """ + 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): + + + values[0] + ... + values[n] + + + """ + return _createRDFArray(surrounded, values, False, rootIndex) + +def createRDFSeq(surrounded, values, rootIndex): + """ + returns (as libxml2 node): + + + values[0] + ... + values[n] + + + """ + 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 = """ + + + + + + """ + 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