1 # -*- coding: utf-8 -*-
2 #######################################################################################
3 # Photo is a part of Plinn - http://plinn.org #
4 # Copyright (C) 2008 Benoît PIN <benoit.pin@ensmp.fr> #
6 # This program is free software; you can redistribute it and/or #
7 # modify it under the terms of the GNU General Public License #
8 # as published by the Free Software Foundation; either version 2 #
9 # of the License, or (at your option) any later version. #
11 # This program is distributed in the hope that it will be useful, #
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14 # GNU General Public License for more details. #
16 # You should have received a copy of the GNU General Public License #
17 # along with this program; if not, write to the Free Software #
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #
19 #######################################################################################
20 """ XMP generation utilities.
22 $Id: xmputils.py 1293 2009-08-14 16:48:18Z pin $
23 $URL: http://svn.luxia.fr/svn/labo/projects/zope/Photo/trunk/xmputils.py $
26 from libxml2
import newNode
, parseDoc
, treeError
27 # prefix <-> namespaces mappings as defined in the official xmp documentation
28 from standards
.xmp
import namespaces
as xmpNs2Prefix
29 from standards
.xmp
import prefix2Ns
as xmpPrefix2Ns
31 TIFF_ORIENTATIONS
= {1 : (0, False)
40 def _getRDFArrayValues(node
, arrayType
):
42 for element
in iterElementChilds(node
):
43 if element
.name
== arrayType
and element
.ns().content
== 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' :
44 for value
in iterElementChilds(element
):
45 if value
.name
== 'li':
46 values
.append(value
.content
)
49 raise ValueError("No %s found" % arrayType
)
51 def getBagValues(node
):
52 return _getRDFArrayValues(node
, 'Bag')
54 def getSeqValues(node
):
55 return _getRDFArrayValues(node
, 'Seq')
58 def createRDFAlt(surrounded
, defaultText
, rootIndex
):
60 returns (as libxml2 node):
63 <rdf:li xml:lang="x-default">defaultText</rdf:li>
67 docNs
= rootIndex
.getDocumentNs()
68 rdfPrefix
= docNs
['http://www.w3.org/1999/02/22-rdf-syntax-ns#']
69 normalizedPrefix
, name
= surrounded
.split(':')
70 ns
= xmpPrefix2Ns
[normalizedPrefix
]
71 actualPrefix
= docNs
[ns
]
73 surrounded
= newNode('%s:%s' % (actualPrefix
, name
))
74 alt
= newNode('%s:Alt' % rdfPrefix
)
75 li
= newNode('%s:li' % rdfPrefix
)
76 li
.newProp('xml:lang', 'x-default')
77 li
.setContent(defaultText
)
79 reduce(lambda a
, b
: a
.addChild(b
), (surrounded
, alt
, li
))
84 def createRDFBag(surrounded
, values
, rootIndex
):
86 returns (as libxml2 node):
89 <rdf:li>values[0]</rdf:li>
91 <rdf:li>values[n]</rdf:li>
95 return _createRDFArray(surrounded
, values
, False, rootIndex
)
97 def createRDFSeq(surrounded
, values
, rootIndex
):
99 returns (as libxml2 node):
102 <rdf:li>values[0]</rdf:li>
104 <rdf:li>values[n]</rdf:li>
108 return _createRDFArray(surrounded
, values
, True, rootIndex
)
110 def _createRDFArray(surrounded
, values
, ordered
, rootIndex
):
111 docNs
= rootIndex
.getDocumentNs()
112 rdfPrefix
= docNs
['http://www.w3.org/1999/02/22-rdf-syntax-ns#']
113 normalizedPrefix
, name
= surrounded
.split(':')
114 ns
= xmpPrefix2Ns
[normalizedPrefix
]
115 actualPrefix
= docNs
[ns
]
118 surrounded
= newNode('%s:%s' % (actualPrefix
, name
))
120 array
= newNode('%s:Seq' % rdfPrefix
)
121 elif ordered
is False :
122 array
= newNode('%s:Bag' % rdfPrefix
)
124 raise ValueError("'ordered' parameter must be a boolean value")
126 surrounded
.addChild(array
)
129 li
= newNode('%s:li' % rdfPrefix
)
135 def createEmptyXmpDoc() :
137 <x:xmpmeta xmlns:x='adobe:ns:meta/'>
138 <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
143 d
= parseDoc(emptyDocument
)
146 def getPathIndex(doc
) :
147 root
= doc
.getRootElement()
148 index
= PathIndex(root
)
154 Class used to provide a convenient tree access to xmp properties by paths.
155 Issues about namespaces and prefixes are normalized during the object
156 instanciation. Ns prefixes used to access elements are those recommended in the
157 official xmp documentation from Adobe.
160 def __init__(self
, element
, parent
=None) :
162 self
.element
= element
165 elementNs
= element
.ns().content
166 elementPrefix
= element
.ns().name
167 recommendedPrefix
= xmpNs2Prefix
.get(elementNs
, elementPrefix
)
169 self
.name
= '%s:%s' % (recommendedPrefix
, element
.name
)
170 self
.namespace
= elementNs
171 self
.prefix
= elementPrefix
174 for prop
in iterElementProperties(element
) :
175 self
.addChildIndex(prop
)
177 for child
in iterElementChilds(element
) :
178 self
.addChildIndex(child
)
180 if self
.parent
is None:
181 self
.nsDeclarations
= self
._namespaceDeclarations
()
183 def addChildIndex(self
, child
) :
189 childPrefix
= ns
.name
190 childRecommendedPrefix
= xmpNs2Prefix
.get(childNs
, childPrefix
)
191 childName
= '%s:%s' % (childRecommendedPrefix
, child
.name
)
193 if not self
._index
.has_key(childName
) :
194 self
._index
[childName
] = PathIndex(child
, parent
=self
)
196 childIndex
= self
._index
[childName
]
197 childIndex
.unique
= False
198 for prop
in iterElementProperties(child
) :
199 childIndex
.addChildIndex(prop
)
201 for c
in iterElementChilds(child
) :
202 childIndex
.addChildIndex(c
)
204 self
._index
[childName
].parent
= self
205 return self
._index
[childName
]
207 def _namespaceDeclarations(self
) :
209 returns ns / prefix pairs as found in xmp packet
212 namespaces
[self
.namespace
] = self
.prefix
213 for child
in self
._index
.values() :
214 for namespace
, prefix
in child
._namespaceDeclarations
().items() :
215 if namespaces
.has_key(namespace
) :
216 assert namespaces
[namespace
] == prefix
, \
217 "using several prefix for the same namespace is forbidden "\
218 "in this implementation"
220 namespaces
[namespace
] = prefix
223 def getDocumentNs(self
) :
224 root
= self
.getRootIndex()
225 return root
.nsDeclarations
227 def exists(self
, path
) :
229 for part
in path
.split('/') :
230 if o
._index
.has_key(part
) :
236 def __getitem__(self
, path
) :
239 for part
in path
.split('/') :
249 def get(self
, path
, default
=None) :
255 def getRootIndex(self
) :
257 while root
.parent
is not None :
261 def createChildAndIndex(self
, name
, rdfType
, nsDeclarationElement
) :
262 recommandedPrefix
, name
= name
.split(':', 1)
264 if rdfType
== 'prop' :
266 node
= self
.element
.newProp(name
, '')
268 raise ValueError, (self
.element
, name
)
271 self
.element
.addChild(node
)
273 # bind namespace to new node
274 uri
= xmpPrefix2Ns
[recommandedPrefix
]
275 docNamespaces
= self
.getDocumentNs()
276 if not docNamespaces
.has_key(uri
) :
278 ns
= nsDeclarationElement
.newNs(uri
, recommandedPrefix
)
280 raise ValueError, (uri
, prefix
, self
.element
, list(nsDeclarationElement
.nsDefs()))
281 docNamespaces
[uri
] = recommandedPrefix
283 actualPrefix
= docNamespaces
[uri
]
285 ns
= self
.element
.searchNs(None, actualPrefix
)
287 # cas d'un xmp verbeux : le nouvel élément n'est pas ajouté
288 # dans le rdf:Description du ns correspondant
289 # (après tout, ce n'est pas une obligation)
291 ns
= nsDeclarationElement
.newNs(uri
, actualPrefix
)
295 return self
.addChildIndex(node
)
297 def getOrCreate(self
, path
, rdfType
, preferedNsDeclaration
='rdf:RDF/rdf:Description') :
298 parts
= path
.split('/')
305 root
= self
.getRootIndex()
306 nsDeclarationElement
= root
[preferedNsDeclaration
].element
310 child
= parent
._index
.get(p
, None)
312 child
= parent
.createChildAndIndex(p
, None, nsDeclarationElement
)
315 child
= parent
._index
.get(name
, None)
317 child
= parent
.createChildAndIndex(name
, rdfType
, nsDeclarationElement
)
327 path
.append(parent
.name
)
328 parent
= parent
.parent
330 path
= '/'.join(path
)
337 for child
in self
._index
.values() :
340 return '\n'.join(out
)
342 def iterElementChilds(parent
) :
343 child
= parent
.children
345 if child
.type == 'element' :
349 def iterElementProperties(element
) :
350 prop
= element
.properties
352 if prop
.type == 'attribute' :