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
RevLine 
[532]1import re
2from xml.dom import minidom
3from types import ListType
4from types import TupleType
5
[533]6from zope.interface import implements, classProvides
[554]7from zope.app.annotation.interfaces import IAnnotations
8
[532]9from Products.CMFPlone.Portal import PloneSite
[533]10from Products.CMFCore import utils
[532]11
[533]12from collective.transmogrifier.interfaces import ISection, ISectionBlueprint
13from collective.transmogrifier.utils import defaultMatcher
14
[612]15from quintagroup.transmogrifier.interfaces import IExportDataCorrector, IImportDataCorrector
[532]16from quintagroup.transmogrifier.adapters.exporting import ReferenceExporter
[533]17from quintagroup.transmogrifier.manifest import ManifestExporterSection
[554]18from quintagroup.transmogrifier.logger import VALIDATIONKEY
[532]19
20from quintagroup.transmogrifier.simpleblog2quills.interfaces import IExportItemManipulator, IBlog
21
[554]22# URL of the site, where blog is located (this is needed to fix links in entries)
23SITE_URLS = []
[532]24IMAGE_FOLDER = 'images'
[554]25# this registries are needed to avoid loosing images with equal ids
26IMAGE_IDS = []
27IMAGE_PATHS = {}
[532]28
[533]29class BlogManifest(object):
[532]30    implements(IExportDataCorrector)
31
32    def __init__(self, context):
33        self.context = context
34
35    def __call__(self, data):
[586]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')
[594]42        folder.setAttribute('type', 'Large Plone Folder')
[586]43        folder.appendChild(doc.createTextNode(IMAGE_FOLDER))
44        root.appendChild(folder)
45        data['data'] = doc.toxml('utf-8')
[532]46        return data
47
[586]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
[533]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
[532]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
[554]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
[532]111class BlogEntryExporter(ReferenceExporter):
112    implements(IExportDataCorrector)
113
114    SRC = re.compile(r'src="([^"]+)"')
115
[533]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
[532]121    def __call__(self, data):
[533]122        data = super(BlogEntryExporter, self).__call__(data)
[532]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:
[533]135            url = str(url)
[532]136            image_id = url.rsplit('/', 1)[-1]
[533]137            # bad link
138            if '://' in url and not url.startswith('http://'):
139                continue
[532]140            if url.startswith('http://'):
[554]141                for site in SITE_URLS:
[533]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:
[554]153                            image_id = self.fixImageId(image, image_id, blog_path)
[533]154                            new_url = '/'.join((blog_url, IMAGE_FOLDER, image_id))
155                            text = text.replace(url, new_url, 1)
156                        break
[532]157            else:
[533]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:
[554]172                    image_id = self.fixImageId(image, image_id, blog_path)
[533]173                    path = self.context.getPhysicalPath()
[534]174                    level = len(path) - len(blog_path) - 1
[533]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)
[586]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)
[532]189
190        elem.firstChild.nodeValue = text
191        data['data'] = doc.toxml('utf-8')
192        return data
193
[554]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
[532]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
[554]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
[532]238        item[pathkey] = new_path
[554]239        item['_oldpath'] = path
[532]240
[554]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
[532]249        return item
[533]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
[554]261        self.flagkey = defaultMatcher(options, 'old-path-key', name, 'oldpath')
[533]262        self.typekey = defaultMatcher(options, 'type-key', name, 'type')
263        self.pathkey = defaultMatcher(options, 'path-key', name, 'path')
264
[554]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
[533]273    def __iter__(self):
274        folders = {}
275
[554]276        # safely get logging storage
277        if VALIDATIONKEY in self.anno:
278            log_storage = self.anno[VALIDATIONKEY]
279        else:
280            log_storage = None
281
[533]282        for item in self.previous:
[554]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]
[533]287
288            # collect data about images moved to folders
[554]289            if pathkey and typekey and oldpathkey:
[533]290                path = item[pathkey]
[554]291                old_path = item[oldpathkey]
[533]292                type_ = item[typekey]
293                folder_path, image_id = path.rsplit('/', 1)
294                folders.setdefault(folder_path, []).append((image_id, type_))
295
[554]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
[533]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
[554]310
311        # clean registries
312        while IMAGE_IDS: IMAGE_IDS.pop()
313        while SITE_URLS: SITE_URLS.pop()
314        IMAGE_PATHS.clear()
[612]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.