-
Notifications
You must be signed in to change notification settings - Fork 4
/
transform.py
217 lines (164 loc) · 6.81 KB
/
transform.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# -*- coding: utf-8 -*-
from lxml import etree
from lxml import html
from plone.app.blocks import panel
from plone.app.blocks import tiles
from plone.tiles import esi
from plone.tiles.interfaces import ESI_HEADER
from plone.transformchain.interfaces import ITransform
from repoze.xmliter.serializer import XMLSerializer
from repoze.xmliter.utils import getHTMLSerializer
from zope.interface import implementer
import six
import re
import logging
try:
# Plone 5.2+
from Products.CMFPlone.utils import safe_bytes
except ImportError:
# BBB for Plone 5.1 and lower
from Products.CMFPlone.utils import safe_encode as safe_bytes
logger = logging.getLogger(__name__)
@implementer(ITransform)
class DisableParsing(object):
"""A no-op transform which sets flags to stop plone.app.blocks
transformations. You may register this for a particular published
object or request as required. By default, it's registered for ESI-
rendered tiles when they are fetched via ESI.
"""
order = 8000
def __init__(self, published, request):
self.published = published
self.request = request
def transformBytes(self, result, encoding):
self.request.set('plone.app.blocks.disabled', True)
return None
def transformUnicode(self, result, encoding):
self.request.set('plone.app.blocks.disabled', True)
return None
def transformIterable(self, result, encoding):
self.request.set('plone.app.blocks.disabled', True)
return None
@implementer(ITransform)
class ParseXML(object):
"""First stage in the 8000's chain: parse the content to an lxml tree
encapsulated in an XMLSerializer.
The subsequent steps in this package will assume their result inputs are
XMLSerializer iterables, and do nothing if it is not. This also gives us
the option to parse the content here, and if we decide it's not HTML,
we can avoid trying to parse it again.
"""
order = 8000
# Tests set this to True
pretty_print = False
def __init__(self, published, request):
self.published = published
self.request = request
def transformBytes(self, result, encoding):
return self.transformIterable([result], encoding)
def transformUnicode(self, result, encoding):
return self.transformIterable([result], encoding)
def transformIterable(self, result, encoding):
if self.request.get('plone.app.blocks.disabled', False):
return None
content_type = self.request.response.getHeader('Content-Type')
if content_type is None or not content_type.startswith('text/html'):
return None
contentEncoding = self.request.response.getHeader('Content-Encoding')
if contentEncoding and contentEncoding in ('zip', 'deflate',
'compress',):
return None
try:
# Fix layouts with CR[+LF] line endings not to lose their heads
# (this has been seen with downloaded themes with CR[+LF] endings)
# The html serializer much prefers only bytes, no unicode/text,
# and it return a serializer that returns bytes.
# So we start with ensuring all items in the iterable are bytes.
iterable = [
re.sub(b' ', b'\n', re.sub(b' \n', b'\n', safe_bytes(item)))
for item in result if item]
result = getHTMLSerializer(
iterable, pretty_print=self.pretty_print, encoding=encoding)
# Fix XHTML layouts with where etree.tostring breaks <![CDATA[
if any([b'<![CDATA[' in item for item in iterable]):
result.serializer = html.tostring
self.request['plone.app.blocks.enabled'] = True
return result
except (AttributeError, TypeError, etree.ParseError) as e:
logger.error(e)
return None
@implementer(ITransform)
class MergePanels(object):
"""Find the site layout and merge panels.
"""
order = 8100
def __init__(self, published, request):
self.published = published
self.request = request
def transformBytes(self, result, encoding):
return None
def transformUnicode(self, result, encoding):
return None
def transformIterable(self, result, encoding):
if not self.request.get('plone.app.blocks.enabled', False) or \
not isinstance(result, XMLSerializer):
return None
tree = panel.merge(self.request, result.tree)
if tree is None:
return None
# Set a marker in the request to let subsequent steps know the merging
# has happened
self.request['plone.app.blocks.merged'] = True
result.tree = tree
# Fix serializer when layout has changed doctype from XHTML to HTML
if (
result.tree.docinfo.doctype and
'XHTML' not in result.tree.docinfo.doctype
):
result.serializer = html.tostring
return result
@implementer(ITransform)
class IncludeTiles(object):
"""Turn a panel-merged page into the final composition by including tiles.
Assumes the input result is an lxml tree and returns an lxml tree for
later serialization.
"""
order = 8500
def __init__(self, published, request):
self.published = published
self.request = request
def transformBytes(self, result, encoding):
return None
def transformUnicode(self, result, encoding):
return None
def transformIterable(self, result, encoding):
if not self.request.get('plone.app.blocks.enabled', False) or \
not isinstance(result, XMLSerializer):
return None
result.tree = tiles.renderTiles(self.request, result.tree)
return result
@implementer(ITransform)
class ESIRender(object):
"""If ESI rendering was used, render the page down to a format that allows
ESI to work.
"""
order = 9900
def __init__(self, published, request):
self.published = published
self.request = request
def transformBytes(self, result, encoding):
if self.request.getHeader(ESI_HEADER, 'false').lower() != 'true':
return None
return esi.substituteESILinks([result])
def transformUnicode(self, result, encoding):
if self.request.getHeader(ESI_HEADER, 'false').lower() != 'true':
return None
return esi.substituteESILinks([result.encode(encoding, 'ignore')])
def transformIterable(self, result, encoding):
if self.request.getHeader(ESI_HEADER, 'false').lower() != 'true':
return None
result = ''.join(str(result))
transformed = esi.substituteESILinks(result)
if transformed != result:
self.request.response.setHeader('X-Esi', '1')
return transformed