553 lines
22 KiB
Python
553 lines
22 KiB
Python
# -*- coding: UTF-8 -*-
|
|
|
|
|
|
# Expression
|
|
# By: Frederic Peters <fpeters@entrouvert.com>
|
|
# Emmanuel Raviart <eraviart@entrouvert.com>
|
|
#
|
|
# Copyright (C) 2004 Entr'ouvert, Frederic Peters & Emmanuel Raviart
|
|
#
|
|
# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
|
|
"""DbXmlContainer Module"""
|
|
|
|
|
|
import os
|
|
|
|
import dbxml
|
|
import libxml2
|
|
import urllib
|
|
|
|
import expression.core.dataholders as dataholders
|
|
import expression.core.documents as documents
|
|
import expression.core.elements as elements
|
|
import expression.core.environs as environs
|
|
import expression.core.faults as faults
|
|
import expression.core.html as html
|
|
import expression.core.logs as logs
|
|
import expression.core.modules as modules
|
|
import expression.core.namespaces as namespaces
|
|
import expression.core.stations as stations
|
|
import expression.core.strings as strings
|
|
import expression.core.things as things
|
|
import expression.core.xpaths as xpaths
|
|
|
|
|
|
class DbXmlContainerHolder(dataholders.DataHolder):
|
|
_dbxmlContainerHolderInited = False
|
|
container = None
|
|
defaultFileNameExtension = ".dbxml"
|
|
isTemporary = False
|
|
isUriDirectory = True
|
|
mimeType = "application/x-dbxml"
|
|
queryContext = None
|
|
updateContext = None
|
|
|
|
def __init__(self, container, *arguments, **keywords):
|
|
super(DbXmlContainerHolder, self).__init__(*arguments, **keywords)
|
|
self.container = container
|
|
self.queryContext = container.getManager().createQueryContext()
|
|
for ns in namespaces.nsList:
|
|
self.queryContext.setNamespace(ns.name, ns.uri)
|
|
self.updateContext = container.getManager().createUpdateContext()
|
|
|
|
def addDocument(self, contents, name=""):
|
|
""" appends a document to the container
|
|
|
|
returns the new XmlDocument name
|
|
"""
|
|
try:
|
|
name = self.container.putDocument(name, contents, self.updateContext, name and DBXML_GEN_NAME or 0)
|
|
except RuntimeError, e:
|
|
logs.error("Could not put document in container: %s" % e)
|
|
logs.debug(contents,)
|
|
raise
|
|
return name
|
|
|
|
def deleteDocument(self, name):
|
|
""" removes a document from the container
|
|
|
|
Handles HTTP GET.
|
|
"""
|
|
self.container.deleteDocument(name, self.updateContext)
|
|
return self.doHttpGet()
|
|
|
|
def docSelect(self, xpathExpression, *uriPathFragments):
|
|
"""
|
|
Handles HTTP GET.
|
|
"""
|
|
command = environs.getVar("httpCommand")
|
|
if command != "GET":
|
|
raise faults.PathNotFound("")
|
|
|
|
result = self.getDbXmlDocumentResultFromXpath(xpathExpression)
|
|
return result.checkAccessAndWalk(uriPathFragments, command, environs.getVar("instruction"))
|
|
|
|
def doHttpGet(self):
|
|
if not self.getInRawMode():
|
|
modeNames = self.getConfigString("yep:defaultMode", default = "list")
|
|
for modeName in modeNames.split():
|
|
uriPathFragments = [urllib.unquote(name) for name in modeName.split("/") if name]
|
|
try:
|
|
return self.walk(uriPathFragments, environs.getVar("httpCommand"))
|
|
except (faults.PathForbidden, faults.PathNotFound, faults.PathUnauthorized):
|
|
pass
|
|
return super(DbXmlContainerHolder, self).doHttpGet()
|
|
|
|
def generateXml(self, layout):
|
|
result = self.getDbXmlDocumentResultFromXpath("/*")
|
|
return result.generateXml(layout)
|
|
|
|
def generateXmlDocument(self):
|
|
result = self.getDbXmlDocumentResultFromXpath("/*")
|
|
return result.generateXmlDocument()
|
|
|
|
def getContainer(self):
|
|
return self.container
|
|
|
|
def getCount(self):
|
|
""" returns the number of documents in the container
|
|
"""
|
|
# http://www.sleepycat.com/blogs/bdb-xml/archives/2003/10/index.html
|
|
results = self.getResultCursorFromXpath(".")
|
|
return results.size()
|
|
|
|
def getCount(self, xpathExpression):
|
|
""" returns the number of documents matching xpathExpression
|
|
"""
|
|
results = self.getResultFetchedFromXpath(xpathExpression)
|
|
return results.size()
|
|
|
|
def getDbXmlDocument(self, name):
|
|
""" returns container document <name>
|
|
|
|
the return type is dbxml.XmlDocument
|
|
or None if the document doesn't exist
|
|
"""
|
|
document = None
|
|
try:
|
|
document = self.container.getDocument(name, 0)
|
|
except RuntimeError:
|
|
pass
|
|
return document
|
|
|
|
def getDbXmlDocumentResultFromXpath(self, xpathExpression):
|
|
resultDocument = documents.newTemporaryDocument(previous = self)
|
|
resultNode = resultDocument.node.newTextChild(None, "dbXmlDocumentResult", None)
|
|
namespace = resultNode.newNs(namespaces.yep.uri, namespaces.yep.name)
|
|
resultNode.setNs(namespace)
|
|
results = self.getResultCursorFromXpath(xpathExpression)
|
|
results.reset()
|
|
for value in results:
|
|
document = value.asDocument()
|
|
childNode = resultNode.newChild(None, "item", None)
|
|
childNode.newProp("id", self.getActionUri(document.getName()))
|
|
childNode.newProp("name", document.getName())
|
|
return resultDocument
|
|
|
|
def getDbXmlNodeResultFromXpath(self, xpathExpression):
|
|
resultDocument = documents.newTemporaryDocument(previous = self)
|
|
resultNode = resultDocument.node.newTextChild(None, "dbXmlNodeResult", None)
|
|
namespace = resultNode.newNs(namespaces.yep.uri, namespaces.yep.name)
|
|
resultNode.setNs(namespace)
|
|
results = self.getResultFetchedFromXpath(xpathExpression)
|
|
for value in results:
|
|
childNode = resultNode.newChild(None, "node", None)
|
|
doc = libxml2.readDoc(
|
|
value.asString(),
|
|
self.getAbsolutePath(),
|
|
None,
|
|
libxml2.XML_PARSE_DTDLOAD | libxml2.XML_PARSE_NONET)
|
|
childNode.addChild(doc.getRootElement())
|
|
return resultDocument
|
|
|
|
def getFileSystemPath(self):
|
|
return os.path.normpath(
|
|
os.path.join(
|
|
self.container.getManager().getHome(), self.container.getName()
|
|
)
|
|
)
|
|
|
|
def getConfigDoc(self):
|
|
return None
|
|
|
|
def getIds(self, xpathExpression = "/*"):
|
|
""" returns an id list of all documents
|
|
|
|
id is not the numeric dbxml:id, but the Uri of the document
|
|
|
|
argument xpathExpression can be specified to restrict the list
|
|
"""
|
|
uriBase = self.getUriAbsolutePath()
|
|
results = self.getResultFetchedFromXpath(xpathExpression)
|
|
return ["%s/%s" % (uriBase, value.asDocument().getName()) for value in results]
|
|
|
|
def getNames(self, xpathExpression = "/*"):
|
|
""" returns a hash {id: name} of all documents
|
|
|
|
id is not the numeric dbxml:id, but the Uri of the document
|
|
|
|
argument xpathExpression can be specified to restrict the list
|
|
"""
|
|
uriBase = self.getUriAbsolutePath()
|
|
names = {}
|
|
results = self.getResultFetchedFromXpath(xpathExpression)
|
|
labelValue = dbxml.XmlValue("")
|
|
for value in results:
|
|
document = value.asDocument()
|
|
name = document.getName()
|
|
uri = "%s/%s" % (uriBase, name)
|
|
if document.getMetaData("http://www.sleepycat.com/2002/dbxml", "label", labelValue):
|
|
name = labelValue.asString()
|
|
names[uri] = name
|
|
return names
|
|
|
|
def getPresence(self, xpathExpression):
|
|
""" Returns whether at least one document matches xpathExpression.
|
|
"""
|
|
results = self.getResultCursorFromXpath(xpathExpression)
|
|
for value in results:
|
|
return True
|
|
return False
|
|
|
|
def getIdsAndNames(self, xpathExpression = "/*"):
|
|
""" returns a tuple (id, name) list of all documents
|
|
|
|
id is the Uri of the document
|
|
name is the dbxml:label of the document (if so defined)
|
|
|
|
argument xpathExpression can be specified to restrict the list
|
|
"""
|
|
uriBase = self.getUriAbsolutePath()
|
|
documentList = []
|
|
results = self.getResultFetchedFromXpath(xpathExpression)
|
|
for value in results:
|
|
document = value.asDocument()
|
|
uri = "%s/%s" % (uriBase, document.getName())
|
|
labelValue = dbxml.XmlValue("")
|
|
if document.getMetaData("http://www.sleepycat.com/2002/dbxml", "label", labelValue):
|
|
label = labelValue.asString()
|
|
else:
|
|
label = document.getName()
|
|
documentList.append(
|
|
(uri, label)
|
|
)
|
|
return documentList
|
|
|
|
def getIdsOnly(self, xpathExpression = "/*"):
|
|
""" returns a name list of all documents
|
|
|
|
argument xpathExpression can be specified to restrict the list
|
|
"""
|
|
results = self.getResultFetchedFromXpath(xpathExpression)
|
|
return [value.asDocument().getName() for value in results]
|
|
|
|
def getResultCursorFromXpath(self, xpathExpression, returnType = dbxml.XmlQueryContext.LiveValues):
|
|
""" Returns a raw dbxml.XmlResults instance, leaving the data in the container.
|
|
"""
|
|
context = self.queryContext
|
|
context.setReturnType(returnType)
|
|
context.setEvaluationType(dbxml.XmlQueryContext.Lazy)
|
|
results = self.container.getManager().query("collection('%s')%s" % (self.container.getName(), xpathExpression), context)
|
|
return results
|
|
|
|
def getResultFetchedFromXpath(self, xpathExpression, returnType = dbxml.XmlQueryContext.LiveValues):
|
|
""" Returns a raw dbxml.XmlResults instance, with all data fetched from the container.
|
|
|
|
All data is loaded in memory.
|
|
Consider using getResultCursorFromXpath instead of this method if you expect a large result.
|
|
"""
|
|
context = self.queryContext
|
|
context.setReturnType(returnType)
|
|
context.setEvaluationType(dbxml.XmlQueryContext.Eager)
|
|
results = self.container.getManager().query("collection('%s')%s" % (self.container.getName(), xpathExpression), context)
|
|
results.reset()
|
|
return results
|
|
|
|
def getValues(self, xpathExpression):
|
|
""" returns the list of values extracted with the xpathExpression
|
|
"""
|
|
results = self.getResultFetchedFromXpath(xpathExpression)
|
|
return [value.asString() for value in results]
|
|
|
|
def list(self):
|
|
return self.docSelect("/*")
|
|
|
|
def nodeSelect(self, xpathExpression, *uriPathFragments):
|
|
"""
|
|
Handles HTTP GET.
|
|
"""
|
|
command = environs.getVar("httpCommand")
|
|
if command != "GET":
|
|
raise faults.PathNotFound("")
|
|
result = self.getDbXmlNodeResultFromXpath(xpathExpression)
|
|
return result.checkAccessAndWalk(uriPathFragments, command, environs.getVar("instruction"))
|
|
|
|
def parsableXpath(self, xpath):
|
|
""" returns True if the given xpath expression is parsable, False otherwise
|
|
"""
|
|
try:
|
|
self.container.getManager().prepare("collection('%s')%s" % (self.container.getName(), xpath), self.queryContext)
|
|
except RuntimeError:
|
|
return False
|
|
return True
|
|
|
|
def walkToAction(self, uriPathFragments, command = None, instruction = None):
|
|
actionName = uriPathFragments[0]
|
|
if actionName.startswith("new-"):
|
|
namespaceName, elementName = actionName[len("new-"):].split("-", 1)
|
|
namespaceUri = namespaces.getUri(namespaceName)
|
|
elementFeature = None
|
|
# Try to get element features from config.
|
|
elementNodes, elementOwner = self.getConfigElementNodesAndOwner(
|
|
namespaceUri, elementName, None, ignoreGeneralValue = True)
|
|
if elementNodes:
|
|
elementNode = elementNodes[0]
|
|
elementClassNodes = xpaths.evaluateXpath(
|
|
"yep:pythonClass", contextNode = elementNode)
|
|
if elementClassNodes:
|
|
elementClass = self.convertNodesToPythonClass(
|
|
elementClassNodes, elementOwner, "yep:pythonClass", default = None)
|
|
else:
|
|
elementClass = None
|
|
schemaLocationNodes = xpaths.evaluateXpath("yep:schema", contextNode = elementNode)
|
|
if schemaLocationNodes:
|
|
schemaAbsolutePath = self.convertNodesToAbsolutePath(
|
|
schemaLocationNodes, elementOwner, "yep:schema", default = None)
|
|
else:
|
|
schemaAbsolutePath = None
|
|
descriptionLocationNodes = xpaths.evaluateXpath(
|
|
"yep:description", contextNode = elementNode)
|
|
if descriptionLocationNodes:
|
|
descriptionAbsolutePath = self.convertNodesToAbsolutePath(
|
|
descriptionLocationNodes, elementOwner, "yep:description", default = None)
|
|
else:
|
|
descriptionAbsolutePath = None
|
|
elementFeature = elements.ElementFeature(
|
|
namespaceUri, elementName, elementClass, schemaAbsolutePath,
|
|
descriptionAbsolutePath)
|
|
# Otherwise, try to get element feature from those registered by Python modules.
|
|
if elementFeature is None:
|
|
try:
|
|
elementFeature = modules.getElementFeature(namespaceUri, elementName)
|
|
except (KeyError, ValueError):
|
|
pass
|
|
# If no element feature found, the action is not possible.
|
|
if elementFeature is None:
|
|
return self, uriPathFragments
|
|
# Return the station which will create the element.
|
|
constructorStation = DbXmlDocumentConstructorStation(
|
|
actionName, elementFeature, previous = self)
|
|
return constructorStation, uriPathFragments[1:]
|
|
return super(DbXmlContainerHolder, self).walkToAction(
|
|
uriPathFragments, command, instruction)
|
|
|
|
def walkToItem(self, uriPathFragments, command = None, instruction = None):
|
|
itemName = uriPathFragments[0]
|
|
document = self.getDbXmlDocument(itemName)
|
|
if document:
|
|
documentStation = DbXmlDocumentStation(
|
|
document, previous = self, uriPathFragment = itemName)
|
|
return documentStation, uriPathFragments[1:]
|
|
return super(DbXmlContainerHolder, self).walkToItem(uriPathFragments, command, instruction)
|
|
|
|
|
|
class DbXmlDocumentConstructorStation(stations.HolderConstructorStation):
|
|
def walk(self, uriPathFragments, command = None, instruction = None):
|
|
namespaceUri = self.elementFeature.namespaceUri
|
|
elementName = self.elementFeature.elementName
|
|
document = self.getParent().container.getManager().createDocument()
|
|
document.setContent("""\
|
|
<?xml version="1.0"?>
|
|
<%(elementName)s xmlns="%(namespaceUri)s"/>
|
|
""" % {
|
|
"elementName": elementName,
|
|
"namespaceUri": namespaceUri,
|
|
})
|
|
newDocument = DbXmlDocumentStation(
|
|
document, previous = self.previous, uriPathFragment = self.actionName)
|
|
newDocument.isTemporary = True
|
|
return newDocument.checkAccessAndWalk(uriPathFragments, command, instruction)
|
|
|
|
|
|
class DbXmlDocumentStation(stations.AbstractStation, documents.AbstractDocument):
|
|
_description = None
|
|
_schema = None # Cache for XML schema
|
|
dbxmlDocument = None
|
|
isTemporary = False
|
|
|
|
def __init__(self, document = None, previous = None, parent = None, uriPathFragment = None):
|
|
stations.AbstractStation.__init__(
|
|
self, previous = previous, parent = parent, uriPathFragment = uriPathFragment)
|
|
assert document is not None
|
|
self.dbxmlDocument = document
|
|
try:
|
|
node = libxml2.readDoc(
|
|
self.dbxmlDocument.getContentAsString(), self.getAbsolutePath(), None,
|
|
libxml2.XML_PARSE_DTDLOAD | libxml2.XML_PARSE_NONET)
|
|
self.publicName = self.localId
|
|
except (libxml2.parserError, libxml2.treeError):
|
|
logs.info('Error while parsing XML file at "%s".' % self.getAbsolutePath())
|
|
raise
|
|
node.xincludeProcess()
|
|
documents.AbstractDocument.__init__(self, node)
|
|
|
|
def delete(self):
|
|
""" Deletes the document from its container.
|
|
"""
|
|
self.getParent().getContainer().deleteDocument(self.dbxmlDocument.getName(), self.getParent().updateContext)
|
|
|
|
def doHttpGet(self):
|
|
if not self.getInRawMode():
|
|
rootElement = self.getRootElement()
|
|
if rootElement is not None:
|
|
try:
|
|
return rootElement.checkAccessAndWalk(
|
|
[], environs.getVar("httpCommand"), environs.getVar("instruction"))
|
|
except faults.PathUnauthorized:
|
|
raise
|
|
except (faults.PathForbidden, faults.PathNotFound), fault:
|
|
logs.exception("Ignoring fault %s in %s.doHttpGet()" % (fault, rootElement))
|
|
return super(DbXmlDocumentStation, self).doHttpGet()
|
|
|
|
def doHttpPost(self):
|
|
if not self.getInRawMode():
|
|
rootElement = self.getRootElement()
|
|
if rootElement is not None:
|
|
try:
|
|
return rootElement.checkAccessAndWalk(
|
|
[], environs.getVar("httpCommand"), environs.getVar("instruction"))
|
|
except faults.PathUnauthorized:
|
|
raise
|
|
except (faults.PathForbidden, faults.PathNotFound), fault:
|
|
logs.exception("Ignoring fault %s in %s.doHttpPost()" % (fault, rootElement))
|
|
return super(DbXmlDocumentStation, self).doHttpPost()
|
|
|
|
def generatePlainText(self):
|
|
return self.dbxmlDocument.getContent()
|
|
|
|
def generateXml(self, layout):
|
|
rootElement = self.getRootElement()
|
|
if rootElement is not None:
|
|
return rootElement.generateXml(layout)
|
|
return super(DbXmlDocumentStation, self).generateXml(layout)
|
|
|
|
def generateXmlDocument(self):
|
|
rootElement = self.getRootElement()
|
|
if rootElement is not None:
|
|
return rootElement.generateXmlDocument()
|
|
return super(DbXmlDocumentStation, self).generateXmlDocument()
|
|
|
|
def getAbsolutePath(self):
|
|
return os.path.normpath(os.path.join(self.getParent().getAbsolutePath(), self.dbxmlDocument.getName()))
|
|
|
|
def getBind(self, context, modelContext):
|
|
return None
|
|
|
|
def getChildInstanceDataXpath(self, context, modelContext):
|
|
return ""
|
|
|
|
def getDocument(self):
|
|
return self
|
|
|
|
def getLocalId(self):
|
|
return self.dbxmlDocument.getName()
|
|
|
|
def getModelId(self):
|
|
return None
|
|
|
|
def getPublicName(self):
|
|
publicName = self.node.prop("publicName")
|
|
if publicName is None:
|
|
return None
|
|
if self.isRootElder:
|
|
publicName = os.path.join(self.getDirectoryAbsolutePath(), publicName)
|
|
return publicName
|
|
|
|
def outputHttpSource(self):
|
|
data = self.dbxmlDocument.getContent()
|
|
dataHolder = self.getDataHolder()
|
|
environs.getVar("httpRequestHandler").outputData(
|
|
data, mimeType = dataholders.mimeTypes[".xml"],
|
|
modificationTime = dataHolder.getModificationTime())
|
|
|
|
def save(self):
|
|
self.dbxmlDocument.setContent(self.serialize())
|
|
container = self.getParent().getContainer()
|
|
element = self.getRootElement()
|
|
try:
|
|
label = element.getLabel()
|
|
except AttributeError:
|
|
pass
|
|
else:
|
|
self.dbxmlDocument.setMetaData("http://www.sleepycat.com/2002/dbxml", "label", dbxml.XmlValue(label))
|
|
if self.isTemporary:
|
|
name = self.getPublicName()
|
|
if name is not None:
|
|
name = strings.simplify(name)
|
|
self.dbxmlDocument.setName(name)
|
|
try:
|
|
container.putDocument(
|
|
self.dbxmlDocument,
|
|
self.getParent().updateContext,
|
|
name is None and dbxml.DBXML_GEN_NAME or 0
|
|
)
|
|
except RuntimeError, e:
|
|
logs.error("Could not put document in container: %s" % e)
|
|
logs.debug(self.dbxmlDocument.getContent())
|
|
raise
|
|
self.uriPathFragment = self.getLocalId()
|
|
del self.isTemporary
|
|
else:
|
|
try:
|
|
container.updateDocument(self.dbxmlDocument, self.getParent().updateContext)
|
|
except RuntimeError, e:
|
|
logs.error("Could not update document in container: %s" % e)
|
|
logs.debug(self.dbxmlDocument.getContent())
|
|
raise
|
|
|
|
def setPermanentLocalId(self):
|
|
# We do not need to set a permanentLocalId here.
|
|
# The document will have it's own id once inside the container.
|
|
pass
|
|
|
|
def walk(self, uriPathFragments, command = None, instruction = None):
|
|
try:
|
|
return super(DbXmlDocumentStation, self).walk(uriPathFragments, command, instruction)
|
|
except (faults.PathForbidden, faults.PathNotFound, faults.PathUnauthorized), fault:
|
|
rootElement = self.getRootElement()
|
|
if rootElement is None:
|
|
raise
|
|
return rootElement.checkAccessAndWalk(uriPathFragments, command, instruction)
|
|
|
|
localId = property(getLocalId)
|
|
modelId = property(getModelId)
|
|
|
|
|
|
for type in [
|
|
'DbXmlDocumentResult',
|
|
'DbXmlNodeResult',
|
|
]:
|
|
elements.registerElement(
|
|
namespaces.yep.uri,
|
|
"%s%s" % (type[0].lower(), type[1:]),
|
|
things.Thing,
|
|
"http://www.entrouvert.org/expression/schemas/%s.xsd" % type,
|
|
"http://www.entrouvert.org/expression/descriptions/%s.xml" % type,
|
|
)
|
|
|
|
DbXmlContainerHolder.register()
|