[silva.core.editor][Emiliano D'Alterio] Added new function to ch...

[email protected] Tue, 17 Sep 2013 11:21:38 +0200
Newsgroups gmane.comp.web.zope.silva.cvs
Message-ID <[email protected]>
author:    Emiliano D'Alterio
date:      Tue Sep 17 11:21:13 2013 +0200
revision:  265:58e796014b19 in silva.core.editor
branch:    
details:   https://hg.infrae.com/silva.core.editor?cmd=changeset;node=58e796014b19
modified:  src/silva/core/editor/tests/test_transform_output_image.py src/silva/core/editor/transform/editor/output.py
added:     src/silva/core/editor/tests/test_transform_output_image.py
removed:   
log:       Added new function to check and fix invalid image block structures
	created in the editor. Added 7 new tests to test the new function.


diffstat:

 src/silva/core/editor/tests/test_transform_output_image.py |  316 +++++++++++++
 src/silva/core/editor/transform/editor/output.py           |   66 ++
 2 files changed, 382 insertions(+), 0 deletions(-)

diffs (411 lines):

diff -r 3e0394d678a8 -r 58e796014b19 src/silva/core/editor/tests/test_transform_output_image.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/silva/core/editor/tests/test_transform_output_image.py	Tue Sep 17 11:21:13 2013 +0200
@@ -0,0 +1,316 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2011-2013 Infrae. All rights reserved.
+# See also LICENSE.txt
+
+import unittest
+
+from Products.Silva.testing import TestCase
+from Products.Silva.testing import TestRequest
+
+from zope.component import getMultiAdapter
+
+
+from ..testing import FunctionalLayer
+from ..text import Text
+from ..transform.interfaces import ISaveEditorFilter
+from ..transform.interfaces import ITransformerFactory
+
+
+class OutputTransformTestCase(TestCase):
+    layer = FunctionalLayer
+
+    def setUp(self):
+        self.root = self.layer.get_application()
+        self.layer.login('author')
+
+        factory = self.root.manage_addProduct['Silva']
+        factory.manage_addMockupVersionedContent('document', 'Document')
+        factory.manage_addMockupVersionedContent('target', 'Document Target')
+
+        version = self.root.document.get_editable()
+        version.test = Text('test')
+
+    def transform(self, text, filter):
+        """Helper to call transform.
+        """
+        version = self.root.document.get_editable()
+        request = TestRequest()
+        factory = getMultiAdapter((version, request), ITransformerFactory)
+        transformer = factory('test', version.test, text, filter)
+        return unicode(transformer)
+
+    def test_link_in_caption(self):
+        """ There should be no link wrapped around caption text.
+        """
+        editor_image_block = """
+        <div class="image ">
+            <a class="image-link"
+               data-silva-anchor="Infrae"
+               data-silva-url="http://infrae.com"
+               target="_self"
+               title="Silva 3.0 Documentation">
+                   <img alt="IMAGE ALT TEXT" data-silva-url="http://infrae.com/contact/infrae_qr_vcard.png" />
+            </a>
+            <span class="image-caption">
+                <a class="image-link"
+                   data-silva-anchor="Infrae"
+                   data-silva-url="http://infrae.com"
+                   target="_self"
+                   title="Silva 3.0 Documentation">THIS CAPTION TEXT IS WRAPPED IN A LINK, IT SHOULDN'T.
+                </a>
+            </span>
+        </div>
+        """
+
+        actual_image_block = self.transform(editor_image_block,
+                                            ISaveEditorFilter)
+
+        expected_image_block = """
+        <div class="image ">
+            <a class="image-link"
+               target="_self"
+               title="Silva 3.0 Documentation"
+               href="http://infrae.com"
+               anchor="Infrae">
+                <img alt="IMAGE ALT TEXT" src="http://infrae.com/contact/infrae_qr_vcard.png">
+            </a>
+            <span class="image-caption">THIS CAPTION TEXT IS WRAPPED IN A LINK, IT SHOULDN'T.</span>
+        </div>
+        """
+        self.assertXMLEqual(expected_image_block, actual_image_block)
+
+    def test_double_image_wrapper(self):
+        """ The div image block (@class="image")
+            shouldn't be nested in a div with the same class.
+        """
+        editor_image_block = """
+        <div class="image ">
+            <div class="image ">
+                <a class="image-link"
+                   data-silva-anchor="Infrae"
+                   data-silva-url="http://infrae.com"
+                   target="_self"
+                   title="Silva 3.0 Documentation">
+                       <img alt="IMAGE ALT TEXT" data-silva-url="http://infrae.com/contact/infrae_qr_vcard.png" />
+                </a>
+                <span class="image-caption">CAPTION</span>
+            </div>
+        </div>
+        """
+
+        actual_image_block = self.transform(editor_image_block,
+                                            ISaveEditorFilter)
+
+        expected_image_block = """
+        <div class="image ">
+            <a class="image-link"
+               target="_self"
+               title="Silva 3.0 Documentation"
+               href="http://infrae.com"
+               anchor="Infrae">
+                <img alt="IMAGE ALT TEXT" src="http://infrae.com/contact/infrae_qr_vcard.png">
+            </a>
+            <span class="image-caption">CAPTION</span>
+        </div>
+        """
+        self.assertXMLEqual(expected_image_block, actual_image_block)
+
+    def test_multiple_links(self):
+        """ Only one link is allowed inside an image block.
+        """
+        editor_image_block = """
+            <div class="image ">
+                <a class="image-link"
+                   data-silva-anchor="Infrae"
+                   data-silva-url="http://infrae.com"
+                   target="_self"
+                   title="Silva 3.0 Documentation">
+                <a class="image-link"
+                   data-silva-anchor="Infrae"
+                   data-silva-url="http://infrae.com"
+                   target="_self"
+                   title="Silva 3.0 Documentation">
+                <a class="image-link"
+                   data-silva-anchor="Infrae"
+                   data-silva-url="http://infrae.com"
+                   target="_self"
+                   title="Silva 3.0 Documentation">
+                       <img alt="IMAGE ALT TEXT" data-silva-url="http://infrae.com/contact/infrae_qr_vcard.png" />
+                </a>
+                </a>
+                </a>
+                <a class="image-link"
+                   data-silva-anchor="Infrae"
+                   data-silva-url="http://infrae.com"
+                   target="_self"
+                   title="Silva 3.0 Documentation">
+                <span class="image-caption">CAPTION</span>
+                </a>
+                <a class="image-link"
+                   data-silva-anchor="Infrae"
+                   data-silva-url="http://infrae.com"
+                   target="_self"
+                   title="Silva 3.0 Documentation">
+                </a>
+            </div>
+        """
+
+        actual_image_block = self.transform(editor_image_block,
+                                            ISaveEditorFilter)
+
+        expected_image_block = """
+        <div class="image ">
+            <a class="image-link"
+               target="_self"
+               title="Silva 3.0 Documentation"
+               href="http://infrae.com"
+               anchor="Infrae">
+                <img alt="IMAGE ALT TEXT" src="http://infrae.com/contact/infrae_qr_vcard.png">
+            </a>
+            <span class="image-caption">CAPTION</span>
+        </div>
+        """
+        self.assertXMLEqual(expected_image_block, actual_image_block)
+
+    def test_multiple_images(self):
+            """ Only one image is allowed inside an image block.
+            """
+            editor_image_block = """
+            <div class="image ">
+                <img alt="IMAGE ALT TEXT" data-silva-url="http://infrae.com/contact/infrae_qr_vcard.png" />
+                    <a class="image-link"
+                       data-silva-anchor="Infrae"
+                       data-silva-url="http://infrae.com"
+                       target="_self"
+                       title="Silva 3.0 Documentation">
+                           <img alt="IMAGE ALT TEXT" data-silva-url="http://infrae.com/contact/infrae_qr_vcard.png" />
+                    </a>
+                    <img alt="IMAGE ALT TEXT" data-silva-url="http://infrae.com/contact/infrae_qr_vcard.png" />
+                    <span class="image-caption">CAPTION</span>
+                    <img alt="IMAGE ALT TEXT" data-silva-url="http://infrae.com/contact/infrae_qr_vcard.png" />
+                    <img alt="IMAGE ALT TEXT" data-silva-url="http://infrae.com/contact/infrae_qr_vcard.png" />
+            </div>
+            """
+
+            actual_image_block = self.transform(editor_image_block,
+                                                ISaveEditorFilter)
+
+            expected_image_block = """
+            <div class="image ">
+                <img alt="IMAGE ALT TEXT" src="http://infrae.com/contact/infrae_qr_vcard.png">
+                <span class="image-caption">CAPTION</span>
+            </div>
+            """
+            self.assertXMLEqual(expected_image_block, actual_image_block)
+
+    def test_nested_paragraphs(self):
+            """ Only one <a>, one <img> and one <span> are allowed inside an image block.
+            """
+            editor_image_block = """
+            <div class="image ">
+                    <p>This P is nested inside the image block structure, it shouldn't be here.</p>
+                    <a class="image-link"
+                       data-silva-anchor="Infrae"
+                       data-silva-url="http://infrae.com"
+                       target="_self"
+                       title="Silva 3.0 Documentation">
+                           <img alt="IMAGE ALT TEXT" data-silva-url="http://infrae.com/contact/infrae_qr_vcard.png" />
+                    </a>
+                    <p>This P is nested inside the image block structure, it shouldn't be here.</p>
+                    <span class="image-caption">CAPTION</span>
+            </div>
+            """
+
+            actual_image_block = self.transform(editor_image_block,
+                                                ISaveEditorFilter)
+
+            expected_image_block = """
+            <div class="image ">
+                    <a class="image-link"
+                       target="_self"
+                       title="Silva 3.0 Documentation"
+                       href="http://infrae.com"
+                       anchor="Infrae">
+                       <img alt="IMAGE ALT TEXT" src="http://infrae.com/contact/infrae_qr_vcard.png">
+                    </a>
+                <span class="image-caption">CAPTION</span>
+            </div>
+            """
+            self.assertXMLEqual(expected_image_block, actual_image_block)
+
+    def test_multiple_wrappers(self):
+            """ There should be only one div image block wrapper.
+            """
+            editor_image_block = """
+            <div class="image ">
+                <div class="image "><div class="image "></div>
+                <div class="image "><div class="image "><div class="image "></div></div>
+                    <a class="image-link"
+                       data-silva-anchor="Infrae"
+                       data-silva-url="http://infrae.com"
+                       target="_self"
+                       title="Silva 3.0 Documentation">
+                           <img alt="IMAGE ALT TEXT" data-silva-url="http://infrae.com/contact/infrae_qr_vcard.png" />
+                           <div class="image "><img alt="IMAGE ALT TEXT" data-silva-url="http://infrae.com/contact/infrae_qr_vcard.png" /><div class="image "></div>
+                    </a>
+                </div>
+            </div>
+            """
+
+            actual_image_block = self.transform(editor_image_block,
+                                                ISaveEditorFilter)
+
+            expected_image_block = """
+            <div class="image ">
+                    <a class="image-link"
+                       target="_self"
+                       title="Silva 3.0 Documentation"
+                       href="http://infrae.com"
+                       anchor="Infrae">
+                       <img alt="IMAGE ALT TEXT" src="http://infrae.com/contact/infrae_qr_vcard.png">
+                    </a>
+            </div>
+            """
+
+            self.assertXMLEqual(expected_image_block, actual_image_block)
+
+    def test_wrong_classes(self):
+            """ Link inside image block should have 'image-link' class
+                while caption span should have 'image-caption' class
+            """
+            editor_image_block = """
+            <div class="image ">
+                    <a class="wrong-class"
+                       data-silva-anchor="Infrae"
+                       data-silva-url="http://infrae.com"
+                       target="_self"
+                       title="Silva 3.0 Documentation">
+                           <img alt="IMAGE ALT TEXT" data-silva-url="http://infrae.com/contact/infrae_qr_vcard.png" />
+                    </a>
+                    <span class="wrong-class">CAPTION</span>
+            </div>
+            """
+
+            actual_image_block = self.transform(editor_image_block,
+                                                ISaveEditorFilter)
+
+            expected_image_block = """
+            <div class="image ">
+                    <a class="image-link"
+                       target="_self"
+                       title="Silva 3.0 Documentation"
+                       href="http://infrae.com"
+                       anchor="Infrae">
+                       <img alt="IMAGE ALT TEXT" src="http://infrae.com/contact/infrae_qr_vcard.png">
+                    </a>
+                    <span class="image-caption">CAPTION</span>
+            </div>
+            """
+
+            self.assertXMLEqual(expected_image_block, actual_image_block)
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(OutputTransformTestCase))
+    return suite
diff -r 3e0394d678a8 -r 58e796014b19 src/silva/core/editor/transform/editor/output.py
--- a/src/silva/core/editor/transform/editor/output.py	Mon Sep 02 14:25:22 2013 +0200
+++ b/src/silva/core/editor/transform/editor/output.py	Tue Sep 17 11:21:13 2013 +0200
@@ -3,6 +3,7 @@
 # See also LICENSE.txt
 
 import urlparse
+from lxml import etree
 
 from five import grok
 from zope.component import queryUtility
@@ -53,6 +54,60 @@
         del tag.attrib[name]
 
 
+def clean_image_block(block_tree):
+    """ Cleans up an image block to assure that
+        it has the correct structure.
+    """
+    image = None
+    img_wrapper = None
+    caption = None
+    image_found = False
+    caption_found = False
+
+    ## We get all the block descendants using lxml (should be "depth-first")
+    ## in order to get image and caption elements, if any.
+    for des in block_tree.iterdescendants():
+        ## We only take the first img element found.
+        if des.tag == 'img' and not image_found:
+            image_found = True
+            ## We set the image element.
+            image = des
+            ## If the img element is wrapped by a link
+            ## we set the image_wrapper too.
+            if des.getparent().tag == 'a':
+                img_wrapper = des.getparent()
+                ## If the class has been modified we put the correct one.
+                img_wrapper.attrib['class'] = 'image-link'
+
+        ## We only take the first span element (caption) found.
+        if des.tag == 'span' and not caption_found:
+            caption_found = True
+            ## We set the caption element.
+            caption = des
+            ## If the class has been modified we put the correct one.
+            caption.attrib['class'] = 'image-caption'
+
+    ## Sanitazing the caption, we strip out every element inside the span
+    ## preserving the content and thus all the texts present.
+    if caption is not None:
+        etree.strip_tags(caption, '*')
+
+    ## We go through the descendants again to mark invalid elements.
+    for des in block_tree.iterdescendants():
+        ## Invalid elements are all those elements which are neither the image
+        ## nor the caption, nor the image_wrapper.
+        if des is image or des is img_wrapper or des is caption:
+            continue
+        ## We remove invalid tags texts.
+        des.text = ''
+        ## We mark invalid tags for removal.
+        des.tag = 'tag_to_be_stripped_out'
+
+    ## We finally strip out tags marked as invalid
+    ## now the image block should have the correct structure.
+    etree.strip_tags(block_tree, 'tag_to_be_stripped_out')
+
+
 class SilvaReferenceTransformationFilter(ReferenceTransformationFilter):
     """Base class to update a reference information out of data-silva-
     tags.
@@ -120,7 +175,18 @@
 
     def __call__(self, tree):
         for block in tree.xpath('//div[contains(@class, "image")]'):
+            ## we clean this block up
+            clean_image_block(block)
+
+            ## In some cases there can be invalid nested blocks
+            ## we can skip these blocks since the cleaner routine
+            ## has already taken care of them.
+            ## So the reference we have is pointing to
+            ## an already stripped out block.
             images = block.xpath('descendant::img')
+            if len(images) < 1:
+                continue
+
             assert len(images) == 1, u"Invalid image construction"
             image = images[0]
             if 'src' in image.attrib: