appy.pod: xhtml2odt: ability to include images from img tags (anonymously). Non-anonymous solution for a Appy/Zope server only; function 'document': allow to specify size of images in cm or px, or via a 'style' tag; appy.gen: allow to upload images in ckeditor fields; improved error management.
This commit is contained in:
parent
98fafad14a
commit
2bd3fe1eeb
|
@ -2138,7 +2138,8 @@ class Pod(Type):
|
||||||
stylesMapping = self.stylesMapping
|
stylesMapping = self.stylesMapping
|
||||||
rendererParams = {'template': StringIO.StringIO(template.content),
|
rendererParams = {'template': StringIO.StringIO(template.content),
|
||||||
'context': podContext, 'result': tempFileName,
|
'context': podContext, 'result': tempFileName,
|
||||||
'stylesMapping': stylesMapping}
|
'stylesMapping': stylesMapping,
|
||||||
|
'imageResolver': tool.o.getApp()}
|
||||||
if tool.unoEnabledPython:
|
if tool.unoEnabledPython:
|
||||||
rendererParams['pythonWithUnoPath'] = tool.unoEnabledPython
|
rendererParams['pythonWithUnoPath'] = tool.unoEnabledPython
|
||||||
if tool.openOfficePort:
|
if tool.openOfficePort:
|
||||||
|
|
|
@ -21,7 +21,8 @@ homePage = '''
|
||||||
</tal:main>
|
</tal:main>
|
||||||
'''
|
'''
|
||||||
errorPage = '''
|
errorPage = '''
|
||||||
<tal:main define="tool python: context.config">
|
<tal:main define="tool python: context.config"
|
||||||
|
on-error="string: ServerError">
|
||||||
<html metal:use-macro="context/ui/template/macros/main">
|
<html metal:use-macro="context/ui/template/macros/main">
|
||||||
<div metal:fill-slot="content" tal:define="o python:options">
|
<div metal:fill-slot="content" tal:define="o python:options">
|
||||||
<p tal:condition="o/error_message"
|
<p tal:condition="o/error_message"
|
||||||
|
|
|
@ -1387,31 +1387,71 @@ class BaseMixin:
|
||||||
for name in templateName.split('/'): res = getattr(res, name)
|
for name in templateName.split('/'): res = getattr(res, name)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def download(self):
|
def download(self, name=None):
|
||||||
'''Downloads the content of the file that is in the File field named
|
'''Downloads the content of the file that is in the File field whose
|
||||||
p_name.'''
|
name is in the request. This name can also represent an attribute
|
||||||
|
storing an image within a rich text field. If p_name is not given, it is retrieved
|
||||||
|
from the request.'''
|
||||||
name = self.REQUEST.get('name')
|
name = self.REQUEST.get('name')
|
||||||
if not name: return
|
if not name: return
|
||||||
appyType = self.getAppyType(name)
|
if '_img_' not in name:
|
||||||
if (not appyType.type =='File') or not appyType.isShowable(self,'view'):
|
appyType = self.getAppyType(name)
|
||||||
return
|
else:
|
||||||
|
appyType = self.getAppyType(name.split('_img_')[0])
|
||||||
|
if not appyType.isShowable(self, 'view'):
|
||||||
|
from zExceptions import NotFound
|
||||||
|
raise NotFound()
|
||||||
theFile = getattr(self.aq_base, name, None)
|
theFile = getattr(self.aq_base, name, None)
|
||||||
if theFile:
|
if theFile:
|
||||||
response = self.REQUEST.RESPONSE
|
response = self.REQUEST.RESPONSE
|
||||||
response.setHeader('Content-Disposition', 'inline;filename="%s"' % \
|
response.setHeader('Content-Disposition', 'inline;filename="%s"' % \
|
||||||
theFile.filename)
|
theFile.filename)
|
||||||
|
# Define content type
|
||||||
|
if theFile.content_type:
|
||||||
|
response.setHeader('Content-Type', theFile.content_type)
|
||||||
response.setHeader('Cachecontrol', 'no-cache')
|
response.setHeader('Cachecontrol', 'no-cache')
|
||||||
response.setHeader('Expires', 'Thu, 11 Dec 1975 12:05:05 GMT')
|
response.setHeader('Expires', 'Thu, 11 Dec 1975 12:05:05 GMT')
|
||||||
return theFile.index_html(self.REQUEST, self.REQUEST.RESPONSE)
|
return theFile.index_html(self.REQUEST, self.REQUEST.RESPONSE)
|
||||||
|
|
||||||
def allows(self, permission):
|
def upload(self):
|
||||||
|
'''Receives an image uploaded by the user via ckeditor and stores it in
|
||||||
|
a special field on this object.'''
|
||||||
|
# Get the name of the rich text field for which an image must be stored.
|
||||||
|
params = self.REQUEST['QUERY_STRING'].split('&')
|
||||||
|
fieldName = params[0].split('=')[1]
|
||||||
|
ckNum = params[1].split('=')[1]
|
||||||
|
# We will store the image in a field named [fieldName]_img_[nb].
|
||||||
|
i = 1
|
||||||
|
attrName = '%s_img_%d' % (fieldName, i)
|
||||||
|
while True:
|
||||||
|
if not hasattr(self.aq_base, attrName):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
i += 1
|
||||||
|
attrName = '%s_img_%d' % (fieldName, i)
|
||||||
|
# Store the image. Create a fake File instance for doing the job.
|
||||||
|
fakeFile = gen.File(isImage=True)
|
||||||
|
fakeFile.name = attrName
|
||||||
|
fakeFile.store(self, self.REQUEST['upload'])
|
||||||
|
# Return the URL of the image.
|
||||||
|
url = '%s/download?name=%s' % (self.absolute_url(), attrName)
|
||||||
|
resp = "<script type='text/javascript'>window.parent.CKEDITOR.tools" \
|
||||||
|
".callFunction(%s, '%s');</script>" % (ckNum, url)
|
||||||
|
self.REQUEST.RESPONSE.write(resp)
|
||||||
|
|
||||||
|
def allows(self, permission, raiseError=False):
|
||||||
'''Has the logged user p_permission on p_self ?'''
|
'''Has the logged user p_permission on p_self ?'''
|
||||||
return self.getUser().has_permission(permission, self)
|
hasPermission = self.getUser().has_permission(permission, self)
|
||||||
|
if not hasPermission and raiseError:
|
||||||
|
from AccessControl import Unauthorized
|
||||||
|
raise Unauthorized
|
||||||
|
return hasPermission
|
||||||
|
|
||||||
def getEditorInit(self, name):
|
def getEditorInit(self, name):
|
||||||
'''Gets the Javascrit init code for displaying a rich editor for
|
'''Gets the Javascript init code for displaying a rich editor for
|
||||||
field named p_name.'''
|
field named p_name.'''
|
||||||
return "CKEDITOR.replace('%s', {toolbar: 'Appy'})" % name
|
return "CKEDITOR.replace('%s', {toolbar: 'Appy', filebrowserUploadUrl:"\
|
||||||
|
"'%s/upload'})" % (name, self.absolute_url())
|
||||||
|
|
||||||
def getCalendarInit(self, name, years):
|
def getCalendarInit(self, name, years):
|
||||||
'''Gets the Javascript init code for displaying a calendar popup for
|
'''Gets the Javascript init code for displaying a calendar popup for
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<html metal:use-macro="context/ui/template/macros/main">
|
<html metal:use-macro="context/ui/template/macros/main">
|
||||||
<metal:fill fill-slot="content"
|
<metal:fill fill-slot="content"
|
||||||
tal:define="contextObj context/getParentNode;
|
tal:define="contextObj context/getParentNode;
|
||||||
|
dummy python: contextObj.allows('Modify portal content', raiseError=True);
|
||||||
errors request/errors | python:{};
|
errors request/errors | python:{};
|
||||||
layoutType python:'edit';
|
layoutType python:'edit';
|
||||||
layout python: contextObj.getPageLayout(layoutType);
|
layout python: contextObj.getPageLayout(layoutType);
|
||||||
|
@ -9,7 +10,8 @@
|
||||||
phase phaseInfo/name;
|
phase phaseInfo/name;
|
||||||
page request/page|python:'main';
|
page request/page|python:'main';
|
||||||
cssJs python: contextObj.getCssAndJs(contextObj.getAppyTypes(layoutType, page), layoutType);
|
cssJs python: contextObj.getCssAndJs(contextObj.getAppyTypes(layoutType, page), layoutType);
|
||||||
confirmMsg request/confirmMsg | nothing;">
|
confirmMsg request/confirmMsg | nothing;"
|
||||||
|
tal:on-error="structure python: tool.manageError(error)">
|
||||||
|
|
||||||
<tal:comment replace="nothing">Include type-specific CSS and JS.</tal:comment>
|
<tal:comment replace="nothing">Include type-specific CSS and JS.</tal:comment>
|
||||||
<link tal:repeat="cssFile cssJs/css" rel="stylesheet" type="text/css"
|
<link tal:repeat="cssFile cssJs/css" rel="stylesheet" type="text/css"
|
||||||
|
|
|
@ -2,13 +2,15 @@
|
||||||
<html metal:use-macro="context/ui/template/macros/main">
|
<html metal:use-macro="context/ui/template/macros/main">
|
||||||
<metal:fill fill-slot="content"
|
<metal:fill fill-slot="content"
|
||||||
tal:define="contextObj python: context.getParentNode();
|
tal:define="contextObj python: context.getParentNode();
|
||||||
|
dummy python: contextObj.allows('View', raiseError=True);
|
||||||
portal_type python: context.portal_type.lower().replace(' ', '_');
|
portal_type python: context.portal_type.lower().replace(' ', '_');
|
||||||
errors python: req.get('errors', {});
|
errors python: req.get('errors', {});
|
||||||
layoutType python:'view';
|
layoutType python:'view';
|
||||||
layout python: contextObj.getPageLayout(layoutType);
|
layout python: contextObj.getPageLayout(layoutType);
|
||||||
phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType='view');
|
phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType='view');
|
||||||
page req/page|python:'main';
|
page req/page|python:'main';
|
||||||
phase phaseInfo/name;">
|
phase phaseInfo/name;"
|
||||||
|
tal:on-error="structure python: tool.manageError(error)">
|
||||||
<metal:prologue use-macro="context/ui/page/macros/prologue"/>
|
<metal:prologue use-macro="context/ui/page/macros/prologue"/>
|
||||||
<metal:show use-macro="context/ui/page/macros/show"/>
|
<metal:show use-macro="context/ui/page/macros/show"/>
|
||||||
<metal:footer use-macro="context/ui/page/macros/footer"/>
|
<metal:footer use-macro="context/ui/page/macros/footer"/>
|
||||||
|
|
|
@ -17,10 +17,12 @@
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA.
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA.
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import os, os.path, time, shutil, struct, random
|
import os, os.path, time, shutil, struct, random, urlparse
|
||||||
from appy.pod import PodError
|
from appy.pod import PodError
|
||||||
from appy.pod.odf_parser import OdfEnvironment
|
from appy.pod.odf_parser import OdfEnvironment
|
||||||
|
from appy.shared import mimeTypesExts
|
||||||
from appy.shared.utils import FileWrapper
|
from appy.shared.utils import FileWrapper
|
||||||
|
from appy.shared.dav import Resource
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
FILE_NOT_FOUND = "'%s' does not exist or is not a file."
|
FILE_NOT_FOUND = "'%s' does not exist or is not a file."
|
||||||
|
@ -32,28 +34,30 @@ PDF_TO_IMG_ERROR = 'A PDF file could not be converted into images. Please ' \
|
||||||
class DocImporter:
|
class DocImporter:
|
||||||
'''Base class used for importing external content into a pod template (an
|
'''Base class used for importing external content into a pod template (an
|
||||||
image, another pod template, another odt document...'''
|
image, another pod template, another odt document...'''
|
||||||
def __init__(self, content, at, format, tempFolder, ns, fileNames):
|
def __init__(self, content, at, format, renderer):
|
||||||
self.content = content
|
self.content = content
|
||||||
# If content is None, p_at tells us where to find it (file system path,
|
# If content is None, p_at tells us where to find it (file system path,
|
||||||
# url, etc)
|
# url, etc)
|
||||||
self.at = at
|
self.at = at
|
||||||
# Ensure this path exists.
|
# Ensure this path exists, if it is a local path.
|
||||||
if at and not os.path.isfile(at): raise PodError(FILE_NOT_FOUND % at)
|
if at and not at.startswith('http') and not os.path.isfile(at):
|
||||||
|
raise PodError(FILE_NOT_FOUND % at)
|
||||||
self.format = format
|
self.format = format
|
||||||
self.res = u''
|
self.res = u''
|
||||||
self.ns = ns
|
self.renderer = renderer
|
||||||
|
self.ns = renderer.currentParser.env.namespaces
|
||||||
# Unpack some useful namespaces
|
# Unpack some useful namespaces
|
||||||
self.textNs = ns[OdfEnvironment.NS_TEXT]
|
self.textNs = self.ns[OdfEnvironment.NS_TEXT]
|
||||||
self.linkNs = ns[OdfEnvironment.NS_XLINK]
|
self.linkNs = self.ns[OdfEnvironment.NS_XLINK]
|
||||||
self.drawNs = ns[OdfEnvironment.NS_DRAW]
|
self.drawNs = self.ns[OdfEnvironment.NS_DRAW]
|
||||||
self.svgNs = ns[OdfEnvironment.NS_SVG]
|
self.svgNs = self.ns[OdfEnvironment.NS_SVG]
|
||||||
self.tempFolder = tempFolder
|
self.tempFolder = renderer.tempFolder
|
||||||
self.importFolder = self.getImportFolder()
|
self.importFolder = self.getImportFolder()
|
||||||
# Create the import folder if it does not exist.
|
# Create the import folder if it does not exist.
|
||||||
if not os.path.exists(self.importFolder): os.mkdir(self.importFolder)
|
if not os.path.exists(self.importFolder): os.mkdir(self.importFolder)
|
||||||
self.importPath = self.getImportPath(at, format)
|
self.importPath = self.getImportPath(at, format)
|
||||||
# A link to the global fileNames dict (explained in renderer.py)
|
# A link to the global fileNames dict (explained in renderer.py)
|
||||||
self.fileNames = fileNames
|
self.fileNames = renderer.fileNames
|
||||||
if at:
|
if at:
|
||||||
# Move the file within the ODT, if it is an image and if this image
|
# Move the file within the ODT, if it is an image and if this image
|
||||||
# has not already been imported.
|
# has not already been imported.
|
||||||
|
@ -84,7 +88,10 @@ class DocImporter:
|
||||||
'''Gets the path name of the file to dump on disk (within the ODT for
|
'''Gets the path name of the file to dump on disk (within the ODT for
|
||||||
images, in a temp folder for docs).'''
|
images, in a temp folder for docs).'''
|
||||||
if not format:
|
if not format:
|
||||||
format = os.path.splitext(at)[1][1:]
|
if at.startswith('http'):
|
||||||
|
format = '' # We will know it only after the HTTP GET.
|
||||||
|
else:
|
||||||
|
format = os.path.splitext(at)[1][1:]
|
||||||
fileName = 'f.%d.%f.%s' % (random.randint(0,10), time.time(), format)
|
fileName = 'f.%d.%f.%s' % (random.randint(0,10), time.time(), format)
|
||||||
return os.path.abspath('%s/%s' % (self.importFolder, fileName))
|
return os.path.abspath('%s/%s' % (self.importFolder, fileName))
|
||||||
|
|
||||||
|
@ -136,8 +143,7 @@ class PdfImporter(DocImporter):
|
||||||
nextImage = '%s/%s%d.jpg' % (imagesFolder, self.imagePrefix, i)
|
nextImage = '%s/%s%d.jpg' % (imagesFolder, self.imagePrefix, i)
|
||||||
if os.path.exists(nextImage):
|
if os.path.exists(nextImage):
|
||||||
# Use internally an Image importer for doing this job.
|
# Use internally an Image importer for doing this job.
|
||||||
imgImporter = ImageImporter(None, nextImage, 'jpg',
|
imgImporter =ImageImporter(None, nextImage, 'jpg',self.renderer)
|
||||||
self.tempFolder, self.ns, self.fileNames)
|
|
||||||
imgImporter.setAnchor('paragraph')
|
imgImporter.setAnchor('paragraph')
|
||||||
self.res += imgImporter.run()
|
self.res += imgImporter.run()
|
||||||
os.remove(nextImage)
|
os.remove(nextImage)
|
||||||
|
@ -199,17 +205,70 @@ class ImageImporter(DocImporter):
|
||||||
# Yes!
|
# Yes!
|
||||||
i = importPath.rfind(self.pictFolder) + 1
|
i = importPath.rfind(self.pictFolder) + 1
|
||||||
return importPath[:i] + imagePath
|
return importPath[:i] + imagePath
|
||||||
# If I am here, the image has not already been imported: copy it.
|
# The image has not already been imported: copy it.
|
||||||
shutil.copy(at, importPath)
|
if not at.startswith('http'):
|
||||||
|
shutil.copy(at, importPath)
|
||||||
|
return importPath
|
||||||
|
# The image must be retrieved via a URL. Try to perform a HTTP GET.
|
||||||
|
response = Resource(at).get()
|
||||||
|
if response.code == 200:
|
||||||
|
# At last, I can get the file format.
|
||||||
|
self.format = mimeTypesExts[response.headers['Content-Type']]
|
||||||
|
importPath += self.format
|
||||||
|
f = file(importPath, 'wb')
|
||||||
|
f.write(response.body)
|
||||||
|
f.close()
|
||||||
|
return importPath
|
||||||
|
# The HTTP GET did not work, maybe for security reasons (we probably
|
||||||
|
# have no permission to get the file). But maybe the URL was a local
|
||||||
|
# one, from an application server running this POD code. In this case,
|
||||||
|
# if an image resolver has been given to POD, use it to retrieve the
|
||||||
|
# image.
|
||||||
|
imageResolver = self.renderer.imageResolver
|
||||||
|
if not imageResolver:
|
||||||
|
# Return some default image explaining that the image wasn't found.
|
||||||
|
import appy.pod
|
||||||
|
podFolder = os.path.dirname(appy.pod.__file__)
|
||||||
|
img = os.path.join(podFolder, 'imageNotFound.jpg')
|
||||||
|
self.format = 'jpg'
|
||||||
|
importPath += self.format
|
||||||
|
f = file(img)
|
||||||
|
imageContent = f.read()
|
||||||
|
f.close()
|
||||||
|
f = file(importPath, 'wb')
|
||||||
|
f.write(imageContent)
|
||||||
|
f.close()
|
||||||
|
else:
|
||||||
|
# The imageResolver is a Zope application. From it, we will
|
||||||
|
# retrieve the object on which the image is stored and get
|
||||||
|
# the file to download.
|
||||||
|
urlParts = urlparse.urlsplit(at)
|
||||||
|
path = urlParts[2][1:]
|
||||||
|
obj = imageResolver.unrestrictedTraverse(path.split('/')[:-1])
|
||||||
|
zopeFile = getattr(obj, urlParts[3].split('=')[1])
|
||||||
|
appyFile = FileWrapper(zopeFile)
|
||||||
|
self.format = mimeTypesExts[appyFile.mimeType]
|
||||||
|
importPath += self.format
|
||||||
|
appyFile.dump(importPath)
|
||||||
return importPath
|
return importPath
|
||||||
|
|
||||||
def setImageInfo(self, anchor, wrapInPara, size):
|
def setImageInfo(self, anchor, wrapInPara, size, sizeUnit, style):
|
||||||
# Initialise anchor
|
# Initialise anchor
|
||||||
if anchor not in self.anchorTypes:
|
if anchor not in self.anchorTypes:
|
||||||
raise PodError(self.WRONG_ANCHOR % str(self.anchorTypes))
|
raise PodError(self.WRONG_ANCHOR % str(self.anchorTypes))
|
||||||
self.anchor = anchor
|
self.anchor = anchor
|
||||||
self.wrapInPara = wrapInPara
|
self.wrapInPara = wrapInPara
|
||||||
self.size = size
|
self.size = size
|
||||||
|
self.sizeUnit = sizeUnit
|
||||||
|
# Put CSS attributes from p_style in a dict.
|
||||||
|
self.cssAttrs = {}
|
||||||
|
for attr in style.split(';'):
|
||||||
|
if not attr.strip(): continue
|
||||||
|
name, value = attr.strip().split(':')
|
||||||
|
value = value.strip()
|
||||||
|
if value.endswith('px'): value = value[:-2]
|
||||||
|
if value.isdigit(): value=int(value)
|
||||||
|
self.cssAttrs[name.strip()] = value
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
# Some shorcuts for the used xml namespaces
|
# Some shorcuts for the used xml namespaces
|
||||||
|
@ -222,19 +281,37 @@ class ImageImporter(DocImporter):
|
||||||
i = self.importPath.rfind(self.pictFolder)
|
i = self.importPath.rfind(self.pictFolder)
|
||||||
imagePath = self.importPath[i+1:].replace('\\', '/')
|
imagePath = self.importPath[i+1:].replace('\\', '/')
|
||||||
self.fileNames[imagePath] = self.at
|
self.fileNames[imagePath] = self.at
|
||||||
# Compute image size, or retrieve it from self.size if given
|
# Retrieve image size from self.size.
|
||||||
|
width = height = None
|
||||||
if self.size:
|
if self.size:
|
||||||
width, height = self.size
|
width, height = self.size
|
||||||
else:
|
if self.sizeUnit == 'px':
|
||||||
|
# Convert it to cm
|
||||||
|
width = float(width) / pxToCm
|
||||||
|
height = float(height) / pxToCm
|
||||||
|
# Override self.size if 'height' or 'width' is found in self.cssAttrs
|
||||||
|
if 'width' in self.cssAttrs:
|
||||||
|
width = float(self.cssAttrs['width']) / pxToCm
|
||||||
|
if 'height' in self.cssAttrs:
|
||||||
|
height = float(self.cssAttrs['height']) / pxToCm
|
||||||
|
# If width and/or height is missing, compute it.
|
||||||
|
if not width or not height:
|
||||||
width, height = getSize(self.importPath, self.format)
|
width, height = getSize(self.importPath, self.format)
|
||||||
if width != None:
|
if width != None:
|
||||||
size = ' %s:width="%fcm" %s:height="%fcm"' % (s, width, s, height)
|
size = ' %s:width="%fcm" %s:height="%fcm"' % (s, width, s, height)
|
||||||
else:
|
else:
|
||||||
size = ''
|
size = ''
|
||||||
image = '<%s:frame %s:name="%s" %s:z-index="0" %s:anchor-type="%s"%s>' \
|
if 'float' in self.cssAttrs:
|
||||||
'<%s:image %s:type="simple" %s:show="embed" %s:href="%s" ' \
|
floatValue = self.cssAttrs['float'].capitalize()
|
||||||
'%s:actuate="onLoad"/></%s:frame>' % (d, d, imageName, d, t, \
|
styleInfo = '%s:style-name="podImage%s" ' % (d, floatValue)
|
||||||
self.anchor, size, d, x, x, x, imagePath, x, d)
|
self.anchor = 'char'
|
||||||
|
else:
|
||||||
|
styleInfo = ''
|
||||||
|
image = '<%s:frame %s%s:name="%s" %s:z-index="0" ' \
|
||||||
|
'%s:anchor-type="%s"%s><%s:image %s:type="simple" ' \
|
||||||
|
'%s:show="embed" %s:href="%s" %s:actuate="onLoad"/>' \
|
||||||
|
'</%s:frame>' % (d, styleInfo, d, imageName, d, t, self.anchor,
|
||||||
|
size, d, x, x, x, imagePath, x, d)
|
||||||
if hasattr(self, 'wrapInPara') and self.wrapInPara:
|
if hasattr(self, 'wrapInPara') and self.wrapInPara:
|
||||||
image = '<%s:p>%s</%s:p>' % (t, image, t)
|
image = '<%s:p>%s</%s:p>' % (t, image, t)
|
||||||
self.res += image
|
self.res += image
|
||||||
|
|
BIN
pod/imageNotFound.jpg
Normal file
BIN
pod/imageNotFound.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 861 B |
|
@ -94,7 +94,8 @@ STYLES_POD_FONTS = '<@style@:font-face @style@:name="PodStarSymbol" ' \
|
||||||
class Renderer:
|
class Renderer:
|
||||||
def __init__(self, template, context, result, pythonWithUnoPath=None,
|
def __init__(self, template, context, result, pythonWithUnoPath=None,
|
||||||
ooPort=2002, stylesMapping={}, forceOoCall=False,
|
ooPort=2002, stylesMapping={}, forceOoCall=False,
|
||||||
finalizeFunction=None, overwriteExisting=False):
|
finalizeFunction=None, overwriteExisting=False,
|
||||||
|
imageResolver=None):
|
||||||
'''This Python Open Document Renderer (PodRenderer) loads a document
|
'''This Python Open Document Renderer (PodRenderer) loads a document
|
||||||
template (p_template) which is an ODT file with some elements
|
template (p_template) which is an ODT file with some elements
|
||||||
written in Python. Based on this template and some Python objects
|
written in Python. Based on this template and some Python objects
|
||||||
|
@ -128,7 +129,13 @@ class Renderer:
|
||||||
|
|
||||||
- If you set p_overwriteExisting to True, the renderer will overwrite
|
- If you set p_overwriteExisting to True, the renderer will overwrite
|
||||||
the result file. Else, an exception will be thrown if the result file
|
the result file. Else, an exception will be thrown if the result file
|
||||||
already exists.'''
|
already exists.
|
||||||
|
|
||||||
|
- p_imageResolver allows POD to retrieve images, from "img" tags within
|
||||||
|
XHTML content. Indeed, POD may not be able (ie, may not have the
|
||||||
|
permission to) perform a HTTP GET on those images. Currently, the
|
||||||
|
resolver can only be a Zope application object.
|
||||||
|
'''
|
||||||
self.template = template
|
self.template = template
|
||||||
self.templateZip = zipfile.ZipFile(template)
|
self.templateZip = zipfile.ZipFile(template)
|
||||||
self.result = result
|
self.result = result
|
||||||
|
@ -143,6 +150,7 @@ class Renderer:
|
||||||
self.forceOoCall = forceOoCall
|
self.forceOoCall = forceOoCall
|
||||||
self.finalizeFunction = finalizeFunction
|
self.finalizeFunction = finalizeFunction
|
||||||
self.overwriteExisting = overwriteExisting
|
self.overwriteExisting = overwriteExisting
|
||||||
|
self.imageResolver = imageResolver
|
||||||
# Remember potential files or images that will be included through
|
# Remember potential files or images that will be included through
|
||||||
# "do ... from document" statements: we will need to declare them in
|
# "do ... from document" statements: we will need to declare them in
|
||||||
# META-INF/manifest.xml. Keys are file names as they appear within the
|
# META-INF/manifest.xml. Keys are file names as they appear within the
|
||||||
|
@ -235,13 +243,12 @@ class Renderer:
|
||||||
for converting a chunk of XHTML content (p_xhtmlString) into a chunk
|
for converting a chunk of XHTML content (p_xhtmlString) into a chunk
|
||||||
of ODT content.'''
|
of ODT content.'''
|
||||||
stylesMapping = self.stylesManager.checkStylesMapping(stylesMapping)
|
stylesMapping = self.stylesManager.checkStylesMapping(stylesMapping)
|
||||||
ns = self.currentParser.env.namespaces
|
|
||||||
# xhtmlString can only be a chunk of XHTML. So we must surround it a
|
# xhtmlString can only be a chunk of XHTML. So we must surround it a
|
||||||
# tag in order to get a XML-compliant file (we need a root tag).
|
# tag in order to get a XML-compliant file (we need a root tag).
|
||||||
if xhtmlString == None: xhtmlString = ''
|
if xhtmlString == None: xhtmlString = ''
|
||||||
xhtmlContent = '<p>%s</p>' % xhtmlString
|
xhtmlContent = '<p>%s</p>' % xhtmlString
|
||||||
return Xhtml2OdtConverter(xhtmlContent, encoding, self.stylesManager,
|
return Xhtml2OdtConverter(xhtmlContent, encoding, self.stylesManager,
|
||||||
stylesMapping, ns).run()
|
stylesMapping, self).run()
|
||||||
|
|
||||||
def renderText(self, text, encoding='utf-8', stylesMapping={}):
|
def renderText(self, text, encoding='utf-8', stylesMapping={}):
|
||||||
'''Method that can be used (under the name 'text') into a pod template
|
'''Method that can be used (under the name 'text') into a pod template
|
||||||
|
@ -262,7 +269,8 @@ class Renderer:
|
||||||
imageFormats = ('png', 'jpeg', 'jpg', 'gif')
|
imageFormats = ('png', 'jpeg', 'jpg', 'gif')
|
||||||
ooFormats = ('odt',)
|
ooFormats = ('odt',)
|
||||||
def importDocument(self, content=None, at=None, format=None,
|
def importDocument(self, content=None, at=None, format=None,
|
||||||
anchor='as-char', wrapInPara=True, size=None):
|
anchor='as-char', wrapInPara=True, size=None,
|
||||||
|
sizeUnit='cm', style=None):
|
||||||
'''If p_at is not None, it represents a path or url allowing to find
|
'''If p_at is not None, it represents a path or url allowing to find
|
||||||
the document. If p_at is None, the content of the document is
|
the document. If p_at is None, the content of the document is
|
||||||
supposed to be in binary format in p_content. The document
|
supposed to be in binary format in p_content. The document
|
||||||
|
@ -274,9 +282,14 @@ class Renderer:
|
||||||
* p_wrapInPara, if true, wraps the resulting 'image' tag into a 'p'
|
* p_wrapInPara, if true, wraps the resulting 'image' tag into a 'p'
|
||||||
tag;
|
tag;
|
||||||
* p_size, if specified, is a tuple of float or integers
|
* p_size, if specified, is a tuple of float or integers
|
||||||
(width, height) expressing size in centimeters. If not
|
(width, height) expressing size in p_sizeUnit (see below).
|
||||||
specified, size will be computed from image info.'''
|
If not specified, size will be computed from image info.
|
||||||
ns = self.currentParser.env.namespaces
|
* p_sizeUnit is the unit for p_size elements, it can be "cm"
|
||||||
|
(centimeters) or "px" (pixels).
|
||||||
|
* If p_style is given, it is the content of a "style" attribute,
|
||||||
|
containing CSS attributes. If "width" and "heigth" attributes are
|
||||||
|
found there, they will override p_size and p_sizeUnit.
|
||||||
|
'''
|
||||||
importer = None
|
importer = None
|
||||||
# Is there someting to import?
|
# Is there someting to import?
|
||||||
if not content and not at:
|
if not content and not at:
|
||||||
|
@ -297,16 +310,17 @@ class Renderer:
|
||||||
if format in self.ooFormats:
|
if format in self.ooFormats:
|
||||||
importer = OdtImporter
|
importer = OdtImporter
|
||||||
self.forceOoCall = True
|
self.forceOoCall = True
|
||||||
elif format in self.imageFormats:
|
elif (format in self.imageFormats) or not format:
|
||||||
|
# If the format can't be guessed, we suppose it is an image.
|
||||||
importer = ImageImporter
|
importer = ImageImporter
|
||||||
isImage = True
|
isImage = True
|
||||||
elif format == 'pdf':
|
elif format == 'pdf':
|
||||||
importer = PdfImporter
|
importer = PdfImporter
|
||||||
else:
|
else:
|
||||||
raise PodError(DOC_WRONG_FORMAT % format)
|
raise PodError(DOC_WRONG_FORMAT % format)
|
||||||
imp = importer(content, at, format, self.tempFolder, ns, self.fileNames)
|
imp = importer(content, at, format, self)
|
||||||
# Initialise image-specific parameters
|
# Initialise image-specific parameters
|
||||||
if isImage: imp.setImageInfo(anchor, wrapInPara, size)
|
if isImage: imp.setImageInfo(anchor, wrapInPara, size, sizeUnit, style)
|
||||||
res = imp.run()
|
res = imp.run()
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
|
@ -113,3 +113,9 @@
|
||||||
<@style@:list-level-properties @text@:space-before="2.5in" @text@:min-label-width="0.25in"/>
|
<@style@:list-level-properties @text@:space-before="2.5in" @text@:min-label-width="0.25in"/>
|
||||||
</@text@:list-level-style-number>
|
</@text@:list-level-style-number>
|
||||||
</@text@:list-style>
|
</@text@:list-style>
|
||||||
|
<@style@:style @style@:name="podImageLeft" @style@:family="graphic" @style@:parent-style-name="Graphics">
|
||||||
|
<@style@:graphic-properties @style@:run-through="foreground" @style@:wrap="parallel" @style@:number-wrapped-paragraphs="no-limit" @style@:wrap-contour="false" @style@:vertical-pos="top" @style@:vertical-rel="paragraph" @style@:horizontal-pos="left" @style@:horizontal-rel="paragraph" @style@:mirror="none" @fo@:clip="rect(0cm, 0.3cm, 0cm, 0cm)"/>
|
||||||
|
</@style@:style>
|
||||||
|
<@style@:style @style@:name="podImageRight" @style@:family="graphic" @style@:parent-style-name="Graphics">
|
||||||
|
<@style@:graphic-properties @style@:run-through="foreground" @style@:wrap="parallel" @style@:number-wrapped-paragraphs="no-limit" @style@:wrap-contour="false" @style@:vertical-pos="top" @style@:vertical-rel="paragraph" @style@:horizontal-pos="right" @style@:horizontal-rel="paragraph" @style@:mirror="none" @fo@:clip="rect(0cm, 0.3cm, 0cm, 0cm)"/>
|
||||||
|
</@style@:style>
|
||||||
|
|
|
@ -214,16 +214,18 @@ class XhtmlEnvironment(XmlEnvironment):
|
||||||
'ul_kwn': 'podBulletItemKeepWithNext',
|
'ul_kwn': 'podBulletItemKeepWithNext',
|
||||||
'ol_kwn': 'podNumberItemKeepWithNext'}
|
'ol_kwn': 'podNumberItemKeepWithNext'}
|
||||||
listStyles = {'ul': 'podBulletedList', 'ol': 'podNumberedList'}
|
listStyles = {'ul': 'podBulletedList', 'ol': 'podNumberedList'}
|
||||||
def __init__(self, ns):
|
def __init__(self, renderer):
|
||||||
XmlEnvironment.__init__(self)
|
XmlEnvironment.__init__(self)
|
||||||
|
self.renderer = renderer
|
||||||
|
self.ns = renderer.currentParser.env.namespaces
|
||||||
self.res = u''
|
self.res = u''
|
||||||
self.currentContent = u''
|
self.currentContent = u''
|
||||||
self.currentElements = [] # Stack of currently walked elements
|
self.currentElements = [] # Stack of currently walked elements
|
||||||
self.currentLists = [] # Stack of currently walked lists (ul or ol)
|
self.currentLists = [] # Stack of currently walked lists (ul or ol)
|
||||||
self.currentTables = [] # Stack of currently walked tables
|
self.currentTables = [] # Stack of currently walked tables
|
||||||
self.textNs = ns[OdfEnvironment.NS_TEXT]
|
self.textNs = self.ns[OdfEnvironment.NS_TEXT]
|
||||||
self.linkNs = ns[OdfEnvironment.NS_XLINK]
|
self.linkNs = self.ns[OdfEnvironment.NS_XLINK]
|
||||||
self.tableNs = ns[OdfEnvironment.NS_TABLE]
|
self.tableNs = self.ns[OdfEnvironment.NS_TABLE]
|
||||||
self.ignore = False # Will be True when parsing parts of the XHTML that
|
self.ignore = False # Will be True when parsing parts of the XHTML that
|
||||||
# must be ignored.
|
# must be ignored.
|
||||||
|
|
||||||
|
@ -445,6 +447,12 @@ class XhtmlParser(XmlParser):
|
||||||
e.dumpString(' %s:number-columns-spanned="%s"' % \
|
e.dumpString(' %s:number-columns-spanned="%s"' % \
|
||||||
(e.tableNs, attrs['colspan']))
|
(e.tableNs, attrs['colspan']))
|
||||||
e.dumpString('>')
|
e.dumpString('>')
|
||||||
|
elif elem == 'img':
|
||||||
|
style = None
|
||||||
|
if attrs.has_key('style'): style = attrs['style']
|
||||||
|
imgCode = e.renderer.importDocument(at=attrs['src'],
|
||||||
|
wrapInPara=False, style=style)
|
||||||
|
e.dumpString(imgCode)
|
||||||
elif elem in IGNORABLE_TAGS:
|
elif elem in IGNORABLE_TAGS:
|
||||||
e.ignore = True
|
e.ignore = True
|
||||||
|
|
||||||
|
@ -483,7 +491,8 @@ class XhtmlParser(XmlParser):
|
||||||
class Xhtml2OdtConverter:
|
class Xhtml2OdtConverter:
|
||||||
'''Converts a chunk of XHTML into a chunk of ODT.'''
|
'''Converts a chunk of XHTML into a chunk of ODT.'''
|
||||||
def __init__(self, xhtmlString, encoding, stylesManager, localStylesMapping,
|
def __init__(self, xhtmlString, encoding, stylesManager, localStylesMapping,
|
||||||
ns):
|
renderer):
|
||||||
|
self.renderer = renderer
|
||||||
self.xhtmlString = xhtmlString
|
self.xhtmlString = xhtmlString
|
||||||
self.encoding = encoding # Todo: manage encoding that is not utf-8
|
self.encoding = encoding # Todo: manage encoding that is not utf-8
|
||||||
self.stylesManager = stylesManager
|
self.stylesManager = stylesManager
|
||||||
|
@ -491,7 +500,7 @@ class Xhtml2OdtConverter:
|
||||||
self.globalStylesMapping = stylesManager.stylesMapping
|
self.globalStylesMapping = stylesManager.stylesMapping
|
||||||
self.localStylesMapping = localStylesMapping
|
self.localStylesMapping = localStylesMapping
|
||||||
self.odtChunk = None
|
self.odtChunk = None
|
||||||
self.xhtmlParser = XhtmlParser(XhtmlEnvironment(ns), self)
|
self.xhtmlParser = XhtmlParser(XhtmlEnvironment(renderer), self)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.xhtmlParser.parse(self.xhtmlString)
|
self.xhtmlParser.parse(self.xhtmlString)
|
||||||
|
|
Loading…
Reference in a new issue