--- /dev/null
+# -*- coding: utf-8 -*-
+#######################################################################################
+# Plinn - http://plinn.org #
+# Copyright (C) 2005-2007 BenoƮt PIN <benoit.pin@ensmp.fr> #
+# #
+# This program is free software; you can redistribute it and/or #
+# modify it under the terms of the GNU General Public License #
+# as published by the Free Software Foundation; either version 2 #
+# of the License, or (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program; if not, write to the Free Software #
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #
+#######################################################################################
+
+from Globals import InitializeClass
+from AccessControl import ClassSecurityInfo
+from Products.CMFCore.permissions import View, ModifyPortalContent
+from Products.CMFCore.utils import getToolByName
+from Products.CMFDefault.Document import Document
+from OFS.PropertyManager import PropertyManager
+from OFS.Folder import Folder
+from OFS.Image import File, cookId
+from zope.component.factory import Factory
+from zope.interface import implements
+from Products.Photo import Photo
+from Products.Plinn.utils import makeValidId
+from interfaces import IPlinnDocument
+from cStringIO import StringIO
+from sets import Set
+import xml.dom.minidom as minidom
+import re
+
+imgPattern = re.compile('<img(.*?)>', re.IGNORECASE)
+imgWidthPattern = re.compile('style\s*=\s*".*width\s*:\s*([0-9]+)px')
+imgHeightPattern = re.compile('style\s*=\s*".*height\s*:\s*([0-9]+)px')
+imgSrcPattern = re.compile('src\s*=\s*"(.*)"')
+
+imgOrLinkPattern = re.compile('<img(.*?)src(.*?)=(.*?)"(?P<src>(.*?))"(.*?)>|<a(.*?)href(.*?)=(.*?)"(?P<href>(.*?))"(.*?)>', re.IGNORECASE)
+EMPTY_PLINN_DOCUMENT = '<plinn><rectangle width="800" height="600" elementKey="DIV_ELEMENT" ddOptions="2" ratio="undefined" visibility="visible"><upperLeftCorner><point x="0" y="0"/></upperLeftCorner><rawData/></rectangle></plinn>'
+
+
+def addPlinnDocument(self, id, title='', description='', text=''):
+ """ Add a Plinn Document """
+ o = PlinnDocument(id, title, description, text)
+ self._setObject(id,o)
+
+class PlinnDocument(Document) :
+ """ Plinn document - WYSIWYG editor
+ based on XML and javascript
+ """
+ implements(IPlinnDocument)
+
+ security = ClassSecurityInfo()
+
+ _cookedTexts = {}
+
+ def __init__(self, id, title='', description='', text='') :
+ self.attachments = Folder('attachments')
+ Document.__init__(self, id, title=title, description=description, text_format='html', text=text)
+
+ security.declareProtected(View, 'EditableBody')
+ def EditableBody(self, mergeLayers=True):
+ """ Transforms XML to HTML """
+
+ if self.text :
+ if not self._cookedTexts.has_key(self.absolute_url()) :
+ plinnElement = minidom.parseString(self.text).documentElement
+
+ htmlDom = minidom.parseString('<div class="plinn_document"/>')
+ htmlDomDoc = htmlDom.documentElement
+
+ self._transformRectangles(plinnElement, htmlDomDoc)
+ firstChildStyle = htmlDomDoc.firstChild.getAttribute('style')
+ htmlDomDoc.setAttribute('style', firstChildStyle.replace('absolute', 'relative'))
+
+ if mergeLayers :
+ mergedDom = minidom.parseString('<div class="plinn_document"/>')
+ mergedDomDoc = mergedDom.documentElement
+ for layer in htmlDomDoc.childNodes :
+ for foreignchild in layer.childNodes :
+ child = mergedDom.importNode(foreignchild, True)
+ mergedDomDoc.appendChild(child)
+
+ mergedDomDoc.setAttribute('style', htmlDomDoc.getAttribute('style'))
+ htmlDom = mergedDom
+
+ htmlText = htmlDom.toprettyxml().replace('<', '<').replace('>', '>').replace('"', '"').replace('&', '&')
+ htmlText = htmlText.encode('utf8')
+ htmlText = htmlText.split('\n', 1)[1]
+
+ htmlText = imgOrLinkPattern.sub(self._convertSrcOrHref, htmlText)
+ self._cookedTexts[self.absolute_url()] = htmlText
+ return htmlText
+ else :
+ return self._cookedTexts[self.absolute_url()]
+ else :
+ return ''
+
+ def _convertSrcOrHref(self, m) :
+ dict = m.groupdict()
+ if dict['src'] :
+ tag = m.group().replace(dict['src'], self._genAbsoluteUrl(dict['src']))
+ if not tag.endswith('/>') :
+ tag = tag[:-1] + '/>'
+ return tag
+ elif dict['href'] :
+ return m.group().replace(dict['href'], self._genAbsoluteUrl(dict['href']))
+ else:
+ return m.group()
+
+ def _genAbsoluteUrl(self, relUrl) :
+ if relUrl.find('attachments/') >=0 :
+ return self.absolute_url() + '/' + relUrl[relUrl.rindex('attachments/'):]
+ else :
+ return relUrl
+
+
+ security.declareProtected(ModifyPortalContent, 'XMLBody')
+ def XMLBody(self, REQUEST=None) :
+ """ return raw xml text """
+
+ if REQUEST is not None :
+ RESPONSE = REQUEST['RESPONSE']
+ RESPONSE.setHeader('content-type', 'text/xml; charset=utf-8')
+
+ manager = getToolByName(self, 'caching_policy_manager', None)
+ if manager is not None:
+ view_name = 'XMLBody'
+ headers = manager.getHTTPCachingHeaders(
+ self, view_name, {}
+ )
+
+ for key, value in headers:
+ if key == 'ETag':
+ RESPONSE.setHeader(key, value, literal=1)
+ else:
+ RESPONSE.setHeader(key, value)
+ if headers:
+ RESPONSE.setHeader('X-Cache-Headers-Set-By',
+ 'CachingPolicyManager: %s' %
+ '/'.join(manager.getPhysicalPath()))
+
+
+ return Document.EditableBody(self) or EMPTY_PLINN_DOCUMENT
+
+
+ security.declareProtected(ModifyPortalContent, 'addAttachment')
+ def addAttachment(self, file, formId) :
+ """ Add attachment """
+ id, title = cookId('', '', file)
+
+ id = makeValidId(self.attachments, id)
+
+ if formId == 'ImageUploadForm':
+ fileOb = Photo(id, title, file, thumb_height=300, thumb_width=300)
+ else :
+ fileOb = File(id, title, '')
+ fileOb.manage_upload(file)
+
+ self.attachments._setObject(id, fileOb)
+ fileOb = getattr(self.attachments, id)
+ return fileOb
+
+
+ def _transformRectangles(self, inNode, outNode) :
+
+ for node in [ node for node in inNode.childNodes if node.nodeName == 'rectangle' ] :
+ if node.getAttribute('visibility') == 'hidden' :
+ continue
+
+ divRect = outNode.ownerDocument.createElement('div')
+ outNode.appendChild(divRect)
+
+ styleAttr = 'position:absolute'
+ styleAttr += ';width:%spx' % node.getAttribute('width')
+ styleAttr += ';height:%spx' % node.getAttribute('height')
+
+ for subNode in node.childNodes :
+ if subNode.nodeName == 'upperLeftCorner' :
+ for point in subNode.childNodes :
+ if point.nodeName == 'point' :
+ styleAttr += ';left:%spx' % point.getAttribute('x')
+ styleAttr += ';top:%spx' % point.getAttribute('y')
+ divRect.setAttribute('style', styleAttr)
+ break
+
+ elif subNode.nodeName == 'rawData' :
+ rawData = subNode.firstChild
+ if rawData :
+ textNode = outNode.ownerDocument.createTextNode(self.getElementTransform(node.getAttribute('elementKey'))(node, rawData.nodeValue))
+ divRect.appendChild(textNode)
+
+ self._transformRectangles(node, divRect)
+
+
+ security.declarePrivate('renderImg')
+ def renderImg(self, node, raw) :
+ width = int(node.getAttribute('width'))
+ height = int(node.getAttribute('height'))
+
+ photoId = raw.split('/')[-2]
+ photo = self._resizePhoto(photoId, width, height)
+
+ alt = 'image'
+ return '<img src="%(src)s/getThumbnail" width="%(width)s" height="%(height)s" alt="%(alt)s" />' % \
+ {'src' : photo.absolute_url(), 'width' : width, 'height' : height, 'alt' : alt}
+
+
+ security.declarePrivate('renderEpozImg')
+ def renderEpozImg(self, node, raw):
+ for img in imgPattern.findall(raw) :
+ width = imgWidthPattern.findall(img)
+ if width : width = int(width[0])
+
+ height = imgHeightPattern.findall(img)
+ if height : height = int(height[0])
+
+ if not (width or height) : continue # default size
+
+ photoId = imgSrcPattern.findall(img)[0].split('/')[-2]
+ self._resizePhoto(photoId, width, height)
+
+ return raw
+
+
+ def _resizePhoto(self, photoId, width, height):
+ photo = getattr(self.attachments, photoId)
+
+ nts = [width, height]
+ landscape = width > height
+ if landscape :
+ nts.reverse()
+
+ thumbSize = {'width': photo.thumb_width, 'height': photo.thumb_height}
+
+ if thumbSize['width'] != nts[0] or thumbSize['height'] != nts[1] > 1 :
+ photo.manage_editProperties(thumb_width=nts[0], thumb_height=nts[1])
+
+ return photo
+
+
+ security.declarePrivate('getElementTransform')
+ def getElementTransform(self, elementKey) :
+ transforms = {'IMG_ELEMENT': self.renderImg,
+ 'EPOZ_ELEMENT': self.renderEpozImg}
+ return transforms.get(elementKey, lambda node, raw : raw)
+
+ def _edit(self, text):
+ """ Edit the Document and cook the body.
+ """
+ Document._edit(self, text)
+ self._removeUnusedAttachments()
+ self._cookedTexts = {}
+
+
+ def _removeUnusedAttachments(self) :
+ if not(self.attachments.objectIds() and self.XMLBody()) : return
+
+ reAttachments = re.compile('|'.join( [r'\b%s\b' % id for id in self.attachments.objectIds()] ))
+ xml = self.XMLBody()
+ attSet = Set(self.attachments.objectIds())
+ useSet = Set(reAttachments.findall(xml))
+ self.attachments.manage_delObjects([att for att in attSet - useSet])
+
+
+InitializeClass(PlinnDocument)
+PlinnDocumentFactory = Factory(PlinnDocument)
\ No newline at end of file