This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
expression/src/core/dataholders.py

1052 lines
41 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.
"""Data Holders Module."""
import errno
import os
import time
import libxml2
import documents
import environs
import faults
import filesystems
import locations
import logs
import modules
import namespaces
import stations
import xpaths
holderClassesByMimeType = {}
mimeTypes = {
".css": "text/css",
".csv": "text/x-csv",
".dbxml": "application/x-dbxml",
".der": "application/x-x509-ca-cert",
".foaf": "application/rdf+xml",
".gif": "image/gif",
".htc": "text/x-component",
".htm": "text/html",
".html": "text/html",
".jpeg": "image/jpeg",
".jpg": "image/jpeg",
".js": "text/javascript",
".pdf": "application/pdf",
".pem": "application/x-x509-ca-cert",
".png": "image/png",
".py": "text/x-python-src",
".rst": "x-text/restructured",
".stc": "application/vnd.sun.xml.calc.template",
".std": "application/vnd.sun.xml.draw.template",
".sti": "application/vnd.sun.xml.impress.template",
".stw": "application/vnd.sun.xml.writer.template",
".svg": "image/svg+xml",
".sxc": "application/vnd.sun.xml.calc",
".sxd": "application/vnd.sun.xml.draw",
".sxg": "application/vnd.sun.xml.writer.global",
".sxi": "application/vnd.sun.xml.impress",
".sxm": "application/vnd.sun.xml.math",
".sxw": "application/vnd.sun.xml.writer",
".txt": "text/plain",
".xhtml": "application/xhtml+xml",
".xml": "text/xml",
".xsd": "text/xml",
".xsl": "text/xml",
".zip": "application/zip",
}
class DataHolder(stations.AbstractStation):
_configDoc = None
_containedFileSystem = None
_data = None
_dataHolderInited = False
_metadata = None
_xslt = None
defaultFileNameExtension = None
mimeType = None
mimeTypes = None
pathFragment = None
def __del__(self):
if self._configDoc not in (None, "none"):
self._configDoc.freeDoc()
del self._configDoc
def __init__(self, pathFragment = None, previous = None, parent = None,
uriPathFragment = None, uriScheme = None, uriAuthority = None,
isRemote = False, remoteUri = None, mimeType = None, isRootElder = False,
containedFileSystem = None):
if self._dataHolderInited:
return
self._dataHolderInited = True
super(DataHolder, self).__init__(
previous = previous, parent = parent, uriPathFragment = uriPathFragment,
uriScheme = uriScheme, uriAuthority = uriAuthority, isRemote = isRemote,
remoteUri = remoteUri)
if pathFragment:
# When self is a root elder, pathFragment must be an absolute path.
self.pathFragment = pathFragment
if mimeType is not None:
self.mimeType = mimeType
if isRootElder:
self.isRootElder = True
if containedFileSystem is not None:
self._containedFileSystem = containedFileSystem
def __new__(cls, *arguments, **keywords):
self = stations.AbstractStation.__new__(cls, *arguments, **keywords)
self.__init__(*arguments, **keywords)
if self.mimeType is None:
fileSystem = self.fileSystem
fileSystemPath = self.getFileSystemPath()
if fileSystem.exists(fileSystemPath) and fileSystem.isdir(fileSystemPath):
self.mimeType = "inode/directory"
if self.mimeType is None:
logs.debug('Missing MIME type for "%s" using StaticDataHolder.' % self)
realClass = StaticDataHolder
else:
logs.debug("MIME: %s" % str(self.mimeType))
realClass = self.getConfigPythonClass(
"yep:pythonClass", default = None, ignoreGeneralValue = True)
if realClass is None:
if self.mimeType in holderClassesByMimeType:
realClass = holderClassesByMimeType[self.mimeType]
else:
logs.debug('Unknown MIME type "%s" using StaticDataHolder.' % self.mimeType)
realClass = StaticDataHolder
if realClass != self.__class__ and issubclass(realClass, self.__class__):
self.__class__ = realClass
return self
def doHttpDelete(self):
content = environs.getVar("submission").readFile()
if content is not None:
logs.debug(content)
fileSystemPath = self.getFileSystemPath()
try:
self.fileSystem.remove(fileSystemPath)
except OSError, error:
if error.errno != errno.EPERM:
raise
return environs.getVar("httpRequestHandler").outputErrorAccessForbidden(
self.getUriAbsolutePath())
environs.getVar("httpRequestHandler").outputSuccessNoContent()
def doHttpDelete1(self, multistatus):
fileSystemPath = self.getFileSystemPath()
try:
self.fileSystem.remove(fileSystemPath)
except OSError, error:
if error.errno != errno.EPERM:
raise
multistatusNode = multistatus.node
responseNode = multistatusNode.newTextChild(None, "response", None)
# The href URI is quoted, because Nautilus requires that the returned href is the
# same as the URL in the PROPFIND command. Otherwise it can not open "untitled
# folder", because it expects "untitled%20folder".
responseNode.newTextChild(None, "href", self.getAbsoluteUri(quote = True))
responseNode.newTextChild(None, "status", "HTTP/1.1 403 Access Forbidden")
return False
return True
def doHttpPut(self):
# FIXME: To complete.
# See RFC 2616 <http://www.ietf.org/rfc/rfc2616.txt>
# and the full thread at
# <http://www.mail-archive.com/www-talk@w3.org/msg00699.html>
# to know how to handle HTTP PUT correctly.
httpRequestHandler = environs.getVar("httpRequestHandler")
# FIXME: We should check whether the save can be done before sending
# a "continue" response.
expect = httpRequestHandler.headers.get("Expect", "")
if expect.lower() == "100-continue":
httpRequestHandler.outputInformationContinue()
submission = environs.getVar("submission")
isCreated = self.saveDataFile(submission.file, submission.length)
if isCreated:
httpRequestHandler.outputSuccessCreated(self.getUriAbsolutePath())
else:
httpRequestHandler.outputSuccessNoContent()
def getAbsolutePath(self):
parent = self.getParent()
if parent is None:
absolutePath = "/"
else:
absolutePath = parent.getAbsolutePath()
if self.pathFragment:
absolutePath = os.path.normpath(os.path.join(absolutePath, self.pathFragment))
return absolutePath
def getAllConfigElementNodesAndOwnerCouples(self, xpath):
relevantNodesAndOwnerCouples = super(
DataHolder, self).getAllConfigElementNodesAndOwnerCouples(xpath)
configDoc = self.getConfigDoc()
elementNodes = xpaths.evaluateXpath("yep:element", contextNode = configDoc)
for elementNode in elementNodes:
nodes = xpaths.evaluateXpath(xpath, contextNode = elementNode)
if not nodes:
continue
nameNodes = xpaths.evaluateXpath("@name", contextNode = elementNode)
if not nameNodes:
continue
name = nameNodes[0].content
namespaceUriNodes = xpaths.evaluateXpath("@namespace", contextNode = elementNode)
if not namespaceUriNodes:
continue
namespaceUri = namespaceUriNodes[0].content
key = "%s / %s" % (namespaceUri, name)
relevantNodesAndOwnerCouples[key] = (nodes, self)
return relevantNodesAndOwnerCouples
def getConfigDataHolderNodesAndOwner(self, mimeType, xpath, ignoreGeneralValue = False):
configDoc = self.getConfigDoc()
dataHolderNodes = xpaths.evaluateXpath(
"yep:inode[@mimeType = '%s']" % mimeType.replace("'", "&apos;"),
contextNode = configDoc)
if dataHolderNodes:
dataHolderNode = dataHolderNodes[0]
nodes = xpaths.evaluateXpath(xpath, contextNode = dataHolderNode)
if nodes:
return nodes, self
if not ignoreGeneralValue:
nodes = xpaths.evaluateXpath(xpath, contextNode = configDoc)
if nodes:
return nodes, self
return super(DataHolder, self).getConfigDataHolderNodesAndOwner(
mimeType, xpath, ignoreGeneralValue = ignoreGeneralValue)
def getConfigDoc(self):
if self._configDoc is None:
fileSystem = self.fileSystem
fileSystemPath = self.getFileSystemPath()
if fileSystem.exists(fileSystemPath) and fileSystem.isdir(fileSystemPath):
fileSystemConfigPath = os.path.join(fileSystemPath, "_config.xml")
else:
fileSystemConfigPath = fileSystemPath + ".config.xml"
absoluteConfigPath = fileSystem.getAbsolutePath(fileSystemConfigPath)
try:
configFile = fileSystem.open(fileSystemConfigPath, "rb")
except IOError, error:
if error.errno in (errno.ENOENT, errno.ENOTDIR):
# No such file or directory
configDoc = "none"
elif error.errno == errno.EACCES:
# Permission denied
logs.info("Can't access configuration file %s: permission denied."
% fileSystemConfigPath)
configDoc = "none"
else:
raise
else:
try:
try:
# FIXME: What to use for URL instead of None (second argument)?
if hasattr(configFile, "fileno"):
configDoc = libxml2.readFd(
configFile.fileno(), absoluteConfigPath, None,
libxml2.XML_PARSE_DTDLOAD | libxml2.XML_PARSE_NONET)
else:
# For zip files, ...
configDoc = libxml2.readDoc(
configFile.read(), absoluteConfigPath, None,
libxml2.XML_PARSE_DTDLOAD | libxml2.XML_PARSE_NONET)
except (libxml2.parserError, libxml2.treeError):
logs.info('[getConfigDog] Error parsing XML file at "%s".' %
fileSystemConfigPath)
raise
finally:
configFile.close()
self._configDoc = configDoc
if self._configDoc == "none":
return None
return self._configDoc
def getConfigElementNodesAndOwner(self, namespaceUri, name, xpath, ignoreGeneralValue = False):
if namespaceUri is None:
return [], self
configDoc = self.getConfigDoc()
elementNodes = xpaths.evaluateXpath(
"yep:element[@namespace = '%s' and @name = '%s']" % (
namespaceUri.replace("'", "&apos;"), name.replace("'", "&apos;")),
contextNode = configDoc)
if elementNodes:
elementNode = elementNodes[0]
nodes = xpaths.evaluateXpath(xpath, contextNode = elementNode)
if nodes:
return nodes, self
if not ignoreGeneralValue:
nodes = xpaths.evaluateXpath(xpath, contextNode = configDoc)
if nodes:
return nodes, self
return super(DataHolder, self).getConfigElementNodesAndOwner(
namespaceUri, name, xpath, ignoreGeneralValue = ignoreGeneralValue)
def getConfigNodesAndOwner(self, xpath, ignoreGeneralValue = False):
if not self.mimeType:
self.mimeType = 'application/octet-stream'
logs.debug('Invalid MIME type for %s' % self)
return self.getConfigDataHolderNodesAndOwner(
self.mimeType, xpath, ignoreGeneralValue = ignoreGeneralValue)
def getConfigStationNodesAndOwner(self, xpath):
nodes = xpaths.evaluateXpath(xpath, contextNode = self.getConfigDoc())
if nodes:
return nodes, self
return super(DataHolder, self).getConfigStationNodesAndOwner(xpath)
def getContainedFileSystem(self):
if self._containedFileSystem is None:
return self.fileSystem
else:
return self._containedFileSystem
def getContainedFileSystemPath(self):
if self._containedFileSystem is not None:
return ""
return super(DataHolder, self).getContainedFileSystemPath()
def getData(self):
if self._data is None:
self._data = self.loadData()
if self._data == "none":
return None
return self._data
def getDataFile(self):
return self.fileSystem.open(self.getFileSystemPath(), "rb")
def getDirectoryAbsolutePath(self):
parent = self.getParent()
if parent is None:
absolutePath = "/"
else:
absolutePath = parent.getAbsolutePath()
if self.pathFragment:
absolutePath = os.path.dirname(os.path.normpath(os.path.join(
absolutePath, self.pathFragment)))
return absolutePath
def getDocument(self):
return self
def getFileSystem(self):
parent = self.getParent()
if parent is None:
return self._containedFileSystem
parentHolder = parent.getDataHolder()
if parentHolder is None:
return self._containedFileSystem
return parentHolder.containedFileSystem
def getFileSystemPath(self):
parent = self.getParent()
if parent is None:
fileSystemPath = ""
else:
fileSystemPath = parent.getContainedFileSystemPath()
if self.pathFragment:
fileSystemPath = os.path.normpath(os.path.join(
fileSystemPath, self.pathFragment))
return fileSystemPath
def getMetadata(self):
if self._metadata is None:
metadata = None
metadataDoc = None
fileSystem = self.fileSystem
fileSystemPath = self.getFileSystemPath()
if fileSystem.exists(fileSystemPath) and fileSystem.isdir(fileSystemPath):
fileSystemMetadataPath = os.path.join(fileSystemPath, "_meta.xml")
metadataHolderPathFragment = "_meta.xml"
metadataHolderParent = None # Use previous (= self).
else:
fileSystemMetadataPath = fileSystemPath + ".meta.xml"
metadataHolderPathFragment = self.pathFragment + ".meta.xml"
metadataHolderParent = self.getParent()
absoluteMetadataPath = fileSystem.getAbsolutePath(fileSystemMetadataPath)
try:
metadataFile = fileSystem.open(fileSystemMetadataPath, "rb")
except IOError, error:
if error.errno in (errno.ENOENT, errno.ENOTDIR):
# No such file or directory
pass
elif error.errno == errno.EACCES:
# Permission denied
logs.info("Can't access metadata file %s: permission denied."
% fileSystemMetadataPath)
metadata = "none"
else:
raise
else:
try:
try:
# FIXME: What to use for URL instead of None (second argument)?
if hasattr(metadataFile, "fileno"):
metadataDoc = libxml2.readFd(
metadataFile.fileno(), absoluteMetadataPath, None,
libxml2.XML_PARSE_DTDLOAD | libxml2.XML_PARSE_NONET)
else:
# For zip files, ...
metadataDoc = libxml2.readDoc(
metadataFile.read(), absoluteMetadataPath, None,
libxml2.XML_PARSE_DTDLOAD | libxml2.XML_PARSE_NONET)
except (libxml2.parserError, libxml2.treeError):
logs.info('[getMetadata] Error parsing XML file at "%s".' %
fileSystemMetadataPath)
finally:
metadataFile.close()
if metadata is None:
if metadataDoc is None:
metadataDoc = libxml2.newDoc("1.0")
metadataNode = metadataDoc.newTextChild(None, "metadata", None)
metadataNamespace = metadataNode.newNs(namespaces.yep.uri, None)
metadataNode.setNs(metadataNamespace)
metadataHolder = XmlHolder(
metadataDoc, pathFragment = metadataHolderPathFragment, previous = self,
parent = metadataHolderParent, uriPathFragment = "metadata")
metadata = metadataHolder.getRootElement()
self._metadata = metadata
if self._metadata == "none":
return None
return self._metadata
def getModificationTime(self):
fileSystemPath = self.getFileSystemPath()
if fileSystemPath is None:
return None
fileSystem = self.fileSystem
if fileSystem.exists(fileSystemPath):
return fileSystem.getModificationTime(fileSystemPath)
else:
return None
def getNewActionsByLabel(self):
actionsByLabel = {}
for namespaceName, name in modules.getHolderConstructorNamespaceAndElementNames():
action = "new-%s-%s" % (namespaceName, name)
label = _("New %s") % name
actionsByLabel[label] = action
import elements
nodesAndOwnerCouples = self.getAllConfigElementNodesAndOwnerCouples(None)
for nodes, owner in nodesAndOwnerCouples.values():
node = nodes[0]
pythonClassNodes = xpaths.evaluateXpath("yep:pythonClass", contextNode = node)
if not pythonClassNodes:
continue
pythonClass = self.convertNodesToPythonClass(
pythonClassNodes, owner, "yep:pythonClass", default = None)
if pythonClass is None:
continue
if not issubclass(pythonClass, elements.RootElement):
continue
nameNodes = xpaths.evaluateXpath("@name", contextNode = node)
if not nameNodes:
continue
name = nameNodes[0].content
namespaceUriNodes = xpaths.evaluateXpath("@namespace", contextNode = node)
if not namespaceUriNodes:
continue
namespaceUri = namespaceUriNodes[0].content
namespaceName = namespaces.getName(namespaceUri)
action = "new-%s-%s" % (namespaceName, name)
label = _("New %s") % name
actionsByLabel[label] = action
for label, action in actionsByLabel.items():
if not self.walk([action], "GET", "existsAndIsAuthorized"):
del actionsByLabel[label]
return actionsByLabel
def getSimpleLabel(self):
return _("Untitled Object #%s") % self.localId
def getSiteXslt(self):
if self._xslt is None:
xslAbsolutePath = self.getConfigAbsolutePath("yep:siteXsltFilePath", default = None)
if xslAbsolutePath is None:
self._xslt == "none"
else:
xsltHolder = DataHolder(
pathFragment = xslAbsolutePath, previous = self, mimeType = "text/xml",
isRootElder = True,
containedFileSystem = filesystems.PartialFileSystem(xslAbsolutePath))
import stylesheets
xsltPrototype = xsltHolder.getRootElement()
self._xslt = stylesheets.StylesheetContext(xsltPrototype, self, previous = self)
if self._xslt == "none":
return None
return self._xslt
def getWebDavContentLength(self):
if self._data is None:
try:
dataFile = self.getDataFile()
except IOError, error:
if error.errno in (errno.ENOENT, errno.ENOTDIR):
# No such file or directory
raise faults.PathNotFound("")
if error.errno == errno.EACCES:
# Permission denied
raise faults.PathForbidden("")
raise
if hasattr(dataFile, "fileno"):
dataSize = os.fstat(dataFile.fileno())[6]
else:
# For StringIO and cStringIO classes.
dataSize = len(dataFile.getvalue())
else:
dataSize = len(self._data)
return dataSize
def getWebDavContentType(self):
return self.mimeType
def getWebDavLastModified(self):
modificationTime = self.getModificationTime()
if modificationTime is None:
return None
else:
return time.strftime("%a, %d %b %Y %H:%M:%S GMT", modificationTime)
def loadData(self):
dataFile = self.getDataFile()
data = dataFile.read()
dataFile.close()
return data
def metadata(self, *uriPathFragments):
metadata = self.getMetadata()
if metadata is None:
raise faults.PathNotFound("metadata")
return metadata.checkAccessAndWalk(
uriPathFragments, environs.getVar("httpCommand"), environs.getVar("instruction"))
def register(cls):
if cls.defaultFileNameExtension and cls.mimeType \
and cls.defaultFileNameExtension not in mimeTypes:
logs.info("Registering extension: %s" % cls.defaultFileNameExtension)
mimeTypes[cls.defaultFileNameExtension] = cls.mimeType
if cls.mimeTypes:
classMimeTypes = cls.mimeTypes[:]
else:
classMimeTypes = []
if cls.mimeType and cls.mimeType not in classMimeTypes:
classMimeTypes.append(cls.mimeType)
for mimeType in classMimeTypes:
if mimeType in holderClassesByMimeType:
if not issubclass(cls, holderClassesByMimeType[mimeType]):
raise Exception("""\
Error while registering class %s: MIME type "%s" is already used for class %s.\
""" % (
cls, mimeType, holderClassesByMimeType[mimeType]))
else:
holderClassesByMimeType[mimeType] = cls
register = classmethod(register)
def saveDataFile(self, sourceDataFile, length = None):
"""Save data into file and return True if the file has been newly created."""
path = self.getFileSystemPath()
isNewlyCreated = not self.fileSystem.access(path, os.F_OK)
if isNewlyCreated:
directoryPath = os.path.dirname(path)
if not self.fileSystem.access(directoryPath, os.F_OK):
self.fileSystem.makedirs(directoryPath)
dataFile = self.fileSystem.open(path, "wb")
if length is None:
while True:
chunk = sourceDataFile.read(1048576) # 1 MB chunk
if not chunk:
break
dataFile.write(chunk)
else:
count = 0
while count < length:
chunkSize = min(1048576, length - count)
chunk = sourceDataFile.read(chunkSize)
if not chunk:
break
dataFile.write(chunk)
count += len(chunk)
dataFile.close()
if self._data is not None:
del self._data
return isNewlyCreated
def setContainedFileSystem(self, containedFileSystem):
self._containedFileSystem = containedFileSystem
containedFileSystem = property(getContainedFileSystem, setContainedFileSystem)
data = property(getData)
fileSystem = property(getFileSystem)
simpleLabel = property(getSimpleLabel)
class StaticDataHolder(DataHolder):
styled = None # By defaut a static object has no HTML output.
unstyled = None # By defaut a static object has no HTML output.
def doHttpGet(self):
return self.source()
def getSimplestDownloadUrl(self):
# By defaut, a data holder can be download.
# A download is like asking for the source, but the download is forced
# at the browser step.
return self.getActionUri("download")
def getSimplestSourceUrl(self):
# By defaut, this is the source of a data holder that is sent.
return self.getUri()
def getSimplestStyledUrl(self):
# By default, a data holder has no X(HT)ML representation.
return None
def outputHttpDownload(self):
# Try to use a file instead of a string (so that it doesn't fail with
# large files).
try:
data = self.getDataFile()
except IOError, error:
if error.errno == errno.ENOENT:
# No such file or directory
raise faults.PathNotFound("")
if error.errno == errno.EACCES:
# Permission denied
raise faults.PathForbidden("")
raise
if data is None:
data = self.data
headers = None
if self.uriPathFragment:
headers = {
"Content-Disposition": "attachment; filename=\"%s\"" % self.uriPathFragment
}
environs.getVar("httpRequestHandler").outputData(
data, mimeType = self.mimeType, modificationTime = self.getModificationTime(),
headers = headers)
def outputHttpSource(self):
# Try to use a file instead of a string (so that it doesn't fail with
# large files).
try:
data = self.getDataFile()
except IOError, error:
if error.errno == errno.ENOENT:
# No such file or directory
raise faults.PathNotFound("")
if error.errno == errno.EACCES:
# Permission denied
raise faults.PathForbidden("")
raise
if data is None:
data = self.data
charset = self.getConfigString("yep:charset", default = None)
environs.getVar("httpRequestHandler").outputData(
data, mimeType = self.mimeType, modificationTime = self.getModificationTime(),
charset = charset)
class ImageHolder(StaticDataHolder):
mimeTypes = ("image/gif", "image/jpeg", "image/png", "image/svg+xml")
def generateXml(self, layout):
import html
layout.append(html.html(html.body(html.img(src = self.getUri()))))
return True
def generateXmlDocument(self):
htmlDocument = documents.newTemporaryHtmlDocument()
self.generateXml(htmlDocument)
return htmlDocument
def getSimplestStyledUrl(self):
return self.getActionUri("styled")
def styled(self):
# Skip StaticDataHolder (un)definition of styled.
super(StaticDataHolder, self).styled()
def unstyled(self):
# Skip StaticDataHolder (un)definition of styled.
super(StaticDataHolder, self).unstyled()
class PythonHolder(StaticDataHolder):
defaultFileNameExtension = ".py"
mimeType = "text/x-python-src"
def walk(self, uriPathFragments, command = None, instruction = None):
if not self.getInRawMode():
modeNames = self.getConfigString("yep:defaultMode", default = "view").lower()
for modeName in modeNames.split():
if modeName == "execute":
return self.fileSystem.execute(self.getFileSystemPath(), globals(), locals())
elif modeName == "view":
break
else:
logs.info('Ignoring unknown mode "%s" for PythonHolder at "%s".' % (
modeName, self.getAbsolutePath()))
return super(PythonHolder, self).walk(uriPathFragments, command, instruction)
class TextPlainHolder(StaticDataHolder):
mimeType = "text/plain"
def generatePlainText(self):
return self.data.decode("UTF-8")
def generateXml(self, layout):
import html
layout.append(html.html(html.body(html.pre(self.data))))
return True
def generateXmlDocument(self):
htmlDocument = documents.newTemporaryHtmlDocument()
self.generateXml(htmlDocument)
return htmlDocument
def getSimplestStyledUrl(self):
return self.getActionUri("styled")
def styled(self):
# Skip StaticDataHolder (un)definition of styled.
super(StaticDataHolder, self).styled()
def unstyled(self):
# Skip StaticDataHolder (un)definition of styled.
super(StaticDataHolder, self).unstyled()
class XmlHolder(DataHolder, documents.AbstractDocument):
_xmlHolderInited = False
defaultFileNameExtension = ".xml"
isTemporary = False
mimeType = "text/xml"
def __del__(self):
DataHolder.__del__(self)
documents.AbstractDocument.__del__(self)
def __init__(self, node = None, *arguments, **keywords):
if self._xmlHolderInited:
return
self._xmlHolderInited = True
DataHolder.__init__(self, *arguments, **keywords)
documents.AbstractDocument.__init__(self, node)
self.changeClass()
def changeClass(self):
try:
node = self.getRootNode()
except libxml2.treeError:
# we got here with "[EX] Parsing failed"; go back to StaticDataHolder and
# set mimetype to text/plain
# Note: maybe we should instead return an error page?
self.__class__ = StaticDataHolder
self.mimeType = "text/plain"
return
if node is not None:
try:
namespaceUri = node.ns().content
except libxml2.treeError:
# The libxml2 Python binding raises this exception when xmlNs returns None.
namespaceUri = None
realClass = modules.getHolderClass(namespaceUri, node.name, default = self.__class__)
if realClass != self.__class__ and issubclass(realClass, self.__class__):
self.__class__ = realClass
def deletePublicName(self):
self.getRootNode().unsetProp("publicName")
def destroy(self):
parent = self.getParent()
if parent is not None:
parent.itemDestroyed(self)
self.fileSystem.remove(self.getFileSystemPath())
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.PathForbidden, faults.PathNotFound, faults.PathUnauthorized), fault:
logs.exception("Ignoring fault %s in %s.doHttpGet()" % (fault, rootElement))
return super(XmlHolder, 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.PathForbidden, faults.PathNotFound, faults.PathUnauthorized), fault:
logs.exception("Ignoring fault %s in %s.doHttpPost()" % (fault, rootElement))
return super(XmlHolder, self).doHttpPost()
def generatePlainText(self):
rootElement = self.getRootElement()
if rootElement is not None:
return rootElement.generateXml(layout)
return super(TemporaryDocument, self).generatePlainText()
def generateXml(self, layout):
rootElement = self.getRootElement()
if rootElement is not None:
return rootElement.generateXml(layout)
return super(XmlHolder, self).generateXml(layout)
def generateXmlDocument(self):
rootElement = self.getRootElement()
if rootElement is not None:
return rootElement.generateXmlDocument()
return super(XmlHolder, self).generateXmlDocument()
def getBind(self, context, modelContext):
return None
def getChildInstanceDataXpath(self, context, modelContext):
return ""
def getDataHolder(self):
return self
def getLocalId(self):
return self.pathFragment
def getModelId(self):
return None
def getNode(self):
if self._node is None:
if self.isRemote:
raise Exception("FIXME, TODO: download data.")
else:
doc = self.parseDataFile()
self._node = doc
if self.isRootElder:
self.publicName = self.getAbsolutePath()
else:
self.publicName = self.localId
doc.xincludeProcess()
# Now that we know what is the root node, we can change the class of the holder.
# Note: The change of class can be done quite late after the instantiation of holder.
self.changeClass()
return self._node
def getPublicName(self):
return self.getRootNode().prop("publicName")
def isAccessAuthorized(self, command):
rootElement = self.getRootElement()
if rootElement is not None:
return rootElement.isAccessAuthorized(command)
return super(XmlHolder, self).isAccessAuthorized(command)
def isContentAccessAuthorized(self, contentName, command):
rootElement = self.getRootElement()
if rootElement is not None:
return rootElement.isContentAccessAuthorized(contentName, command)
return super(XmlHolder, self).isContentAccessAuthorized(contentName, command)
def outputHttpSource(self):
environs.getVar("httpRequestHandler").outputData(
self.getData(), mimeType = self.mimeType,
modificationTime = self.getModificationTime())
def parseDataFile(self):
dataFile = self.getDataFile()
try:
try:
# The code below had several problems :
# - The parseXxx functions are deprecated (use readXxx
# instead).
# - htmlParseDoc doesn't set the namespaces.
# - parseDoc doesn't work with XHTML files containing
# entities like &eacute;, etc.
# if self.mimeType == 'text/html':
# # FIXME: None instead of "UTF-8" doesn't work for
# # unicode files.
# self.node = libxml2.htmlParseDoc(data, "UTF-8")
# else:
# self.node = libxml2.parseDoc(data)
# FIXME: For performance reasons, we should use readFd
# instead of readDoc, whithout reading data first.
# FIXME: I'm not sure what to use as URL argument.
# Option XML_PARSE_DTDLOAD is needed for XHTML documents
# containing entities liek &eacute;, etc. Option
# XML_PARSE_DTDATTR also works, but I don't know which
# option is best.
# Option XML_PARSE_NONET ensures no network access will be
# done (but local XML catalog is still used). Setting this
# option ensures that no "invisible" net access is done
# and a warning is logged when one is required.
if hasattr(dataFile, "fileno"):
doc = libxml2.readFd(
dataFile.fileno(), self.getAbsolutePath(), None,
libxml2.XML_PARSE_DTDLOAD | libxml2.XML_PARSE_NONET)
else:
# For zip files, ...
doc = libxml2.readDoc(
dataFile.read(), self.getAbsolutePath(), None,
libxml2.XML_PARSE_DTDLOAD | libxml2.XML_PARSE_NONET)
except (libxml2.parserError, libxml2.treeError), e:
logs.info('[parseDataFile] Error parsing XML file at "%s".' % self.getAbsolutePath())
# this is an error parsing but libxml2 may raise treeError;
# <grin>; let's modify the message
e.msg = "[EX] Parsing failed"
raise
finally:
dataFile.close()
return doc
def save(self):
publicName = self.publicName
localId = self.localId
if publicName and self.defaultFileNameExtension and "." not in publicName:
publicName += self.defaultFileNameExtension
if publicName and publicName != localId:
isRenamed = True
oldLocalId = localId
oldFileSystemPath = self.getFileSystemPath()
localId = publicName
self.pathFragment = localId
if self.uriPathFragment == oldLocalId:
self.uriPathFragment = localId
else:
isRenamed = False
del self.publicName
data = self.serialize()
self.publicName = publicName
fileSystem = self.fileSystem
fileSystemPath = self.getFileSystemPath()
fileSystemDirectoryPath = os.path.dirname(fileSystemPath)
if not self.fileSystem.access(fileSystemDirectoryPath, os.F_OK):
self.fileSystem.makedirs(fileSystemDirectoryPath)
if isRenamed:
try:
fileSystem.rename(oldFileSystemPath, fileSystemPath)
except OSError:
logs.debug('oldFileSystemPath = "%s", fileSystemPath = "%s"' % (
oldFileSystemPath, fileSystemPath))
raise
dataFile = fileSystem.open(fileSystemPath, "wb")
dataFile.write(data)
dataFile.close()
if isRenamed:
parent = self.getParent()
if parent is not None:
parent.itemRenamed(self, oldLocalId)
def setNode(self, node):
super(XmlHolder, self).setNode(node)
self.changeClass()
def setPermanentLocalId(self):
publicName = self.publicName
if publicName:
localId = publicName
else:
localId = self.getParent().newItemLocalId()
if self.defaultFileNameExtension and "." not in localId:
localId += self.defaultFileNameExtension
if self.uriPathFragment == self.pathFragment:
self.uriPathFragment = localId
self.pathFragment = localId
if self.isTemporary:
del self.isTemporary
def setPublicName(self, publicName):
self.getRootNode().setProp("publicName", publicName)
def walk(self, uriPathFragments, command = None, instruction = None):
try:
return super(XmlHolder, 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)
node = property(getNode, setNode, documents.AbstractDocument.deleteNode)
publicName = property(getPublicName, setPublicName, deletePublicName)
def newXmlHolder(namespaceUri, elementName, directoryHolder, pathFragment = None,
uriPathFragment = None, fileNameExtension = ".xml", temporary = False):
assert isinstance(directoryHolder, DataHolder)
if pathFragment is None:
if temporary:
namespaceName = namespaces.getName(namespaceUri)
itemLocalId = "new-%s-%s" % (namespaceName, elementName)
else:
itemLocalId = directoryHolder.newItemLocalId()
if fileNameExtension:
itemLocalId = itemLocalId + fileNameExtension
if uriPathFragment is None:
uriPathFragment = itemLocalId
pathFragment = itemLocalId
itemDoc = libxml2.newDoc("1.0")
itemRootNode = itemDoc.newTextChild(None, elementName, None)
if namespaceUri == namespaces.yep.uri:
namespacePrefix = None
else:
namespacePrefix = namespaces.getName(namespaceUri)
itemNamespace = itemRootNode.newNs(namespaceUri, namespacePrefix)
itemRootNode.setNs(itemNamespace)
itemHolder = XmlHolder(itemDoc, pathFragment = pathFragment, previous = directoryHolder,
uriPathFragment = uriPathFragment)
itemHolder.isTemporary = temporary
return itemHolder
ImageHolder.register()
PythonHolder.register()
TextPlainHolder.register()
XmlHolder.register()