source: products/quintagroup.transmogrifier.simpleblog2quills/trunk/quintagroup/transmogrifier/simpleblog2quills/adapters.py @ 612

Last change on this file since 612 was 612, checked in by crchemist, 18 years ago

reverting erroneous commit [368] (svn merge -r 368:367 http://svn/products/qPloneComments/tags/2.0)

File size: 13.1 KB
Line 
1import re
2from xml.dom import minidom
3from types import ListType
4from types import TupleType
5
6from zope.interface import implements, classProvides
7from zope.app.annotation.interfaces import IAnnotations
8
9from Products.CMFPlone.Portal import PloneSite
10from Products.CMFCore import utils
11
12from collective.transmogrifier.interfaces import ISection, ISectionBlueprint
13from collective.transmogrifier.utils import defaultMatcher
14
15from quintagroup.transmogrifier.interfaces import IExportDataCorrector, IImportDataCorrector
16from quintagroup.transmogrifier.adapters.exporting import ReferenceExporter
17from quintagroup.transmogrifier.manifest import ManifestExporterSection
18from quintagroup.transmogrifier.logger import VALIDATIONKEY
19
20from quintagroup.transmogrifier.simpleblog2quills.interfaces import IExportItemManipulator, IBlog
21
22# URL of the site, where blog is located (this is needed to fix links in entries)
23SITE_URLS = []
24IMAGE_FOLDER = 'images'
25# this registries are needed to avoid loosing images with equal ids
26IMAGE_IDS = []
27IMAGE_PATHS = {}
28
29class BlogManifest(object):
30    implements(IExportDataCorrector)
31
32    def __init__(self, context):
33        self.context = context
34
35    def __call__(self, data):
36        doc = minidom.parseString(data['data'])
37        root = doc.documentElement
38        for child in root.getElementsByTagName('record'):
39            if child.getAttribute('type') not in  ('BlogEntry', 'BlogFolder'):
40                root.removeChild(child)
41        folder = doc.createElement('record')
42        folder.setAttribute('type', 'Large Plone Folder')
43        folder.appendChild(doc.createTextNode(IMAGE_FOLDER))
44        root.appendChild(folder)
45        data['data'] = doc.toxml('utf-8')
46        return data
47
48class BlogFolderManifest(object):
49    implements(IExportDataCorrector)
50
51    def __init__(self, context):
52        self.context = context
53
54    def __call__(self, data):
55        doc = minidom.parseString(data['data'])
56        root = doc.documentElement
57        for child in root.getElementsByTagName('record'):
58            if child.getAttribute('type') not in  ('BlogEntry', 'BlogFolder'):
59                root.removeChild(child)
60        data['data'] = doc.toxml('utf-8')
61        return data
62
63class BlogEntryManifest(object):
64    implements(IExportItemManipulator)
65
66    def __init__(self, context):
67        self.context = context
68
69    def __call__(self, item, **kw):
70        # remove manifest data from item - content contained in BlogEntry isn't exported
71        if '_files' in item and 'manifest' in item['_files']:
72            del item['_files']['manifest']
73        return item
74
75def recurseToInterface(item, ifaces):
76    """Recurse up the aq_chain until an object providing `iface' is found,
77    and return that.
78    """
79    if not isinstance(ifaces, (ListType, TupleType)):
80        ifaces = [ifaces]
81    parent = item.aq_parent
82    for iface in ifaces:
83        if iface.providedBy(item):
84            return item
85    for iface in ifaces:
86        if iface.providedBy(parent):
87            return parent
88    if isinstance(parent, PloneSite):
89        # Stop when we get to the portal root.
90        return None
91    return recurseToInterface(parent, ifaces)
92
93def getUniqueId(image_id):
94    """ Generate id that is unique in IMAGE_IDS registry.
95    """
96    if '.' in image_id:
97        name, ext = image_id.rsplit('.', 1)
98        ext = '.' + ext
99    else:
100        name, ext = image_id, ''
101    if image_id in IMAGE_IDS:
102        c = 1
103        new_id = name + str(c) + ext
104        while new_id in IMAGE_IDS:
105            c += 1
106            new_id = name + str(c) + ext
107        image_id = new_id
108
109    return image_id
110
111class BlogEntryExporter(ReferenceExporter):
112    implements(IExportDataCorrector)
113
114    SRC = re.compile(r'src="([^"]+)"')
115
116    def __init__(self, context):
117        self.context = context
118        self.portal_url = utils.getToolByName(self.context, 'portal_url')
119        self.portal = self.portal_url.getPortalObject()
120
121    def __call__(self, data):
122        data = super(BlogEntryExporter, self).__call__(data)
123        doc = minidom.parseString(data['data'])
124        try:
125            elem = [i for i in doc.getElementsByTagName('field') if i.getAttribute('name') == 'body'][0]
126        except IndexError:
127            return data
128
129        text = elem.firstChild.nodeValue
130        urls = self.SRC.findall(text)
131        blog = recurseToInterface(self.context, IBlog)
132        blog_url = blog.absolute_url()
133        blog_path = blog.getPhysicalPath()
134        for url in urls:
135            url = str(url)
136            image_id = url.rsplit('/', 1)[-1]
137            # bad link
138            if '://' in url and not url.startswith('http://'):
139                continue
140            if url.startswith('http://'):
141                for site in SITE_URLS:
142                    if url.startswith(site):
143                        # check whether image is stored in blog
144                        relative_url = url[len(site):]
145                        relative_url = relative_url.strip('/')
146                        # if link is broken we'll get an AttributeError
147                        try:
148                            image = self.portal.unrestrictedTraverse(relative_url)
149                        except AttributeError:
150                            break
151                        in_blog = recurseToInterface(image, IBlog) is not None and True or False
152                        if in_blog:
153                            image_id = self.fixImageId(image, image_id, blog_path)
154                            new_url = '/'.join((blog_url, IMAGE_FOLDER, image_id))
155                            text = text.replace(url, new_url, 1)
156                        break
157            else:
158                if url.startswith('/'):
159                    # if link is broken we'll get an AttributeError
160                    try:
161                        image = self.portal.unrestrictedTraverse(url.strip('/'))
162                    except AttributeError:
163                        continue
164                else:
165                    # if link is broken we'll get an AttributeError
166                    try:
167                        image = self.context.unrestrictedTraverse(url)
168                    except AttributeError:
169                        continue
170                in_blog = recurseToInterface(image, IBlog) is not None and True or False
171                if in_blog:
172                    image_id = self.fixImageId(image, image_id, blog_path)
173                    path = self.context.getPhysicalPath()
174                    level = len(path) - len(blog_path) - 1
175                    new_url = '/'.join(['..' for i in range(level)])
176                    new_url = '/'.join([new_url, IMAGE_FOLDER, image_id])
177                    text = text.replace(url, new_url, 1)
178                elif url.startswith('../'):
179                    # remove '../' from the start of sting
180                    new_url = url[3:]
181                    text = text.replace(url, new_url, 1)
182                elif url.startswith('/'):
183                    # these links didn't work so rewrite them with '..'
184                    # find how many level self.context is under portal root
185                    level = len(self.context.getPhysicalPath()) - 3
186                    new_url = '/'.join(['..' for i in range(level)])
187                    new_url  = new_url + url
188                    text = text.replace(url, new_url, 1)
189
190        elem.firstChild.nodeValue = text
191        data['data'] = doc.toxml('utf-8')
192        return data
193
194    def fixImageId(self, image, image_id, blog_path):
195        """ Check whether image is good or generate new if it's bad.
196        """
197        image_path = '/'.join(image.getPhysicalPath())
198        if image_id in IMAGE_IDS and image_path not in IMAGE_PATHS:
199            image_id = getUniqueId(image_id)
200        if image_id not in IMAGE_IDS:
201            IMAGE_IDS.append(image_id)
202            IMAGE_PATHS[image_path] = '/'.join(blog_path[2:] + (IMAGE_FOLDER, image_id))
203
204        return image_id
205
206class PathRewriter(object):
207    implements(IExportItemManipulator)
208
209    def __init__(self, context):
210        self.context = context
211
212    def __call__(self, item, **kw):
213        pathkey = kw.get('path')
214        if pathkey is None:
215            return item
216
217        path = item[pathkey]
218        blog = recurseToInterface(self.context, IBlog)
219        if blog is None:
220            return item
221
222        blog_path = blog.getPhysicalPath()
223        full_path = '/'.join(self.context.getPhysicalPath())
224        image_id = path.rsplit('/', 1)[-1]
225        modified = False
226
227        if full_path in IMAGE_PATHS:
228            new_path = IMAGE_PATHS[full_path]
229        else:
230            unique_id = getUniqueId(image_id)
231            modified = image_id != unique_id
232            new_path = '/'.join(blog_path[2:] + (IMAGE_FOLDER, unique_id))
233
234            IMAGE_IDS.append(image_id)
235            IMAGE_PATHS[full_path] = new_path
236
237        # change item's path
238        item[pathkey] = new_path
239        item['_oldpath'] = path
240
241        # now we need to fix object id in .marshall.xml
242        if modified:
243            if '_files' in item and 'marshall' in item['_files']:
244                doc = minidom.parseString(item['_files']['marshall']['data'])
245                elem = [i for i in doc.getElementsByTagName('field') if i.getAttribute('name') == 'id'][0]
246                elem.firstChild.nodeValue = '\n\t\t%s\n\t' % unique_id
247                item['_files']['marshall']['data'] = doc.toxml('utf-8')
248
249        return item
250
251class ImageFolderSection(object):
252    """ This section will generate manifest files for image folders in blog.
253    """
254    classProvides(ISectionBlueprint)
255    implements(ISection)
256
257    def __init__(self, transmogrifier, name, options, previous):
258        self.previous = previous
259        self.transmogrifier = transmogrifier
260
261        self.flagkey = defaultMatcher(options, 'old-path-key', name, 'oldpath')
262        self.typekey = defaultMatcher(options, 'type-key', name, 'type')
263        self.pathkey = defaultMatcher(options, 'path-key', name, 'path')
264
265
266        site_urls = options.get('site-urls', '')
267        site_urls = filter(None, [i.strip() for i in site_urls.splitlines()])
268        for i in site_urls:
269            SITE_URLS.append(i)
270
271        self.anno = IAnnotations(transmogrifier)
272
273    def __iter__(self):
274        folders = {}
275
276        # safely get logging storage
277        if VALIDATIONKEY in self.anno:
278            log_storage = self.anno[VALIDATIONKEY]
279        else:
280            log_storage = None
281
282        for item in self.previous:
283            item_keys = item.keys()
284            pathkey = self.pathkey(*item_keys)[0]
285            typekey = self.typekey(*item_keys)[0]
286            oldpathkey = self.flagkey(*item_keys)[0]
287
288            # collect data about images moved to folders
289            if pathkey and typekey and oldpathkey:
290                path = item[pathkey]
291                old_path = item[oldpathkey]
292                type_ = item[typekey]
293                folder_path, image_id = path.rsplit('/', 1)
294                folders.setdefault(folder_path, []).append((image_id, type_))
295
296                # update logging data (path) for this item
297                if log_storage and log_storage[-1] == old_path:
298                    log_storage.pop()
299                    log_storage.append(path)
300
301            yield item
302
303        # generate manifests for those image folders
304        items = []
305        for folder, entries in folders.items():
306            items.append({'_entries': entries, pathkey: folder})
307        exporter = ManifestExporterSection(self.transmogrifier, 'manifest', {'blueprint': 'manifest'}, iter(items))
308        for item in exporter:
309            yield item
310
311        # clean registries
312        while IMAGE_IDS: IMAGE_IDS.pop()
313        while SITE_URLS: SITE_URLS.pop()
314        IMAGE_PATHS.clear()
315
316class WorkflowImporter(object):
317    """ This adapter tries to convert all possible workflow histories to
318        simple_publication_workflow history.
319    """
320    implements(IImportDataCorrector)
321
322    def __init__(self, context):
323        self.context = context
324
325    def __call__(self, data):
326        doc = minidom.parseString(data['data'])
327        wh = [i for i in doc.getElementsByTagName('cmf:workflow')]
328        if not wh:
329            # we don't have such workflow history
330            return data
331
332        wh = wh[0]
333        workflow_id = wh.getAttribute('id')
334        if workflow_id == 'simple_publication_workflow':
335            return data
336        wh.setAttribute('id', 'simple_publication_workflow')
337        if workflow_id == 'simpleblog_workflow':
338            self.fixSimpleBlogWorkflow(wh)
339        else:
340            self.fixWorkflow(wh)
341
342        data['data'] = doc.toxml('utf-8')
343        return data
344
345    def fixSimpleBlogWorkflow(self, wh):
346        for history in wh.getElementsByTagName('cmf:history'):
347            for var in history.getElementsByTagName('cmf:var'):
348                id_ = var.getAttribute('id')
349                value = var.getAttribute('value')
350                if id_ == 'review_state' and value == 'draft':
351                    var.setAttribute('value', 'private')
352
353    def fixWorkflow(self, wh):
354        for history in wh.getElementsByTagName('cmf:history'):
355            for var in history.getElementsByTagName('cmf:var'):
356                id_ = var.getAttribute('id')
357                value = var.getAttribute('value')
358                if id_ == 'review_state' and value == 'visible':
359                    var.setAttribute('value', 'published')
Note: See TracBrowser for help on using the repository browser.