Ticket #3195: 3195_5.patch

File 3195_5.patch, 11.3 KB (added by Garry Yao, 11 years ago)
  • _source/core/htmlparser/fragment.js

     
    4040        // Elements which the end tag is marked as optional in the HTML 4.01 DTD
    4141        // (expect empty elements).
    4242        var optionalClose = {colgroup:1,dd:1,dt:1,li:1,option:1,p:1,td:1,tfoot:1,th:1,thead:1,tr:1};
     43       
     44        // Block-level elements whose internal structure should be respected during parser fixing.
     45        var dtd = CKEDITOR.dtd,
     46                noneBreakingBlocks = CKEDITOR.tools.extend( {table:1, ul : 1, ol: 1, dl: 1},
     47                        dtd.table, dtd.ul, dtd.ol, dtd.dl );
     48       
    4349
    4450        /**
    4551         * Creates a {@link CKEDITOR.htmlParser.fragment} from an HTML string.
     
    5662                        html = [],
    5763                        fragment = new CKEDITOR.htmlParser.fragment(),
    5864                        pendingInline = [],
    59                         currentNode = fragment;
     65                        currentNode = fragment,
     66                        returnPoint;
    6067
    61                 var checkPending = function( newTagName )
     68                function checkPending( newTagName )
    6269                {
    6370                        if ( pendingInline.length > 0 )
    6471                        {
     
    7683
    7784                                                // Add it to the current node and make it the current,
    7885                                                // so the new element will be added inside of it.
    79                                                 currentNode.add( pendingElement );
     86                                                pendingElement.parent = currentNode;
    8087                                                currentNode = pendingElement;
    8188
    8289                                                // Remove the pending element (back the index by one
     
    8693                                        }
    8794                                }
    8895                        }
    89                 };
     96                }
     97
     98                function addElement( element, target, enforceCurrent )
     99                {
     100                        target = target || currentNode || fragment;
     101
     102                        // If the target is the fragment and this element can't go inside
     103                        // body (if fixForBody).
     104                        if ( fixForBody && !target.type && !CKEDITOR.dtd.$body[ element.name ] )
     105                        {
     106                                var savedCurrent = currentNode;
     107
     108                                // Create a <p> in the fragment.
     109                                currentNode = target;
     110                                parser.onTagOpen( 'p', {} );
     111
     112                                // The new target now is the <p>.
     113                                target = currentNode;
     114
     115                                if ( enforceCurrent )
     116                                        currentNode = savedCurrent;
     117                        }
     118
     119                        target.add( element );
     120
     121                        if ( element.returnPoint )
     122                        {
     123                                currentNode = element.returnPoint;
     124                                delete element.returnPoint;
     125                        }
     126                }
    90127
    91128                parser.onTagOpen = function( tagName, attributes, selfClosing )
    92129                {
    93                         if ( fixForBody && !currentNode.type && !CKEDITOR.dtd.$body[ tagName ] )
    94                                 this.onTagOpen( 'p', {} );
    95 
    96130                        var element = new CKEDITOR.htmlParser.element( tagName, attributes );
    97131
    98132                        // "isEmpty" will be always "false" for unknown elements, so we
     
    123157                                // If the element name is the same as the current element name,
    124158                                // then just close the current one and append the new one to the
    125159                                // parent. This situation usually happens with <p>, <li>, <dt> and
    126                                 // <dd>, specially in IE.
    127                                 if ( tagName != currentName )
     160                                // <dd>, specially in IE. Do not enter in this if block in this case.
     161                                if ( tagName == currentName )
     162                                {
     163                                        addElement( currentNode, currentNode.parent );
     164                                }
     165                                else
    128166                                {
    129                                         // If it is optional to close the current element, then
    130                                         // close it at this point and simply add the new
    131                                         // element after it.
    132                                         if ( !optionalClose[ currentName ] )
     167                                        if ( noneBreakingBlocks[ currentName ] )
    133168                                        {
    134                                                 // The current element is an inline element, which
    135                                                 // cannot hold the new one. Put it in the pending list,
    136                                                 // and try adding the new one after it.
    137                                                 pendingInline.unshift( currentNode );
     169                                                if ( !returnPoint )
     170                                                        returnPoint = currentNode;
    138171                                        }
     172                                        else
     173                                        {
     174                                                addElement( currentNode, currentNode.parent, true );
     175
     176                                                if ( !optionalClose[ currentName ] )
     177                                                {
     178                                                        // The current element is an inline element, which
     179                                                        // cannot hold the new one. Put it in the pending list,
     180                                                        // and try adding the new one after it.
     181                                                        pendingInline.unshift( currentNode );
     182                                                }
     183                                        }
    139184
    140185                                        reApply = true;
    141186                                }
     
    142187
    143188                                // In any of the above cases, we'll be adding, or trying to
    144189                                // add it to the parent.
    145                                 currentNode = currentNode.parent;
     190                                currentNode = currentNode.returnPoint || currentNode.parent;
    146191
    147192                                if ( reApply )
    148193                                {
     
    153198
    154199                        checkPending( tagName );
    155200
    156                         currentNode.add( element );
     201                        element.parent = currentNode;
     202                        element.returnPoint = returnPoint;
     203                        returnPoint = 0;
    157204
    158                         if ( !element.isEmpty )
     205                        if ( element.isEmpty )
     206                                addElement( element );
     207                        else
    159208                                currentNode = element;
    160209                };
    161210
     
    161210
    162211                parser.onTagClose = function( tagName )
    163212                {
    164                         var closingElement = currentNode,
    165                                 index = 0;
     213                        var index = 0,
     214                                pendingAdd = [],
     215                                candidate = currentNode;
    166216
    167                         while ( closingElement && closingElement.name != tagName )
     217                        while ( candidate.type && candidate.name != tagName )
    168218                        {
    169219                                // If this is an inline element, add it to the pending list, so
    170220                                // it will continue after the closing tag.
    171                                 if ( !closingElement._.isBlockLike )
     221                                if ( !candidate._.isBlockLike )
    172222                                {
    173                                         pendingInline.unshift( closingElement );
     223                                        pendingInline.unshift( candidate );
    174224
    175225                                        // Increase the index, so it will not get checked again in
    176226                                        // the pending list check that follows.
     
    177227                                        index++;
    178228                                }
    179229
    180                                 closingElement = closingElement.parent;
     230                                // This node should be added to it's parent at this point. But,
     231                                // it should happen only if the closing tag is really closing
     232                                // one of the nodes. So, for now, we just cache it.
     233                                pendingAdd.push( candidate );
     234
     235                                candidate = candidate.parent;
    181236                        }
    182237
    183                         if ( closingElement )
    184                                 currentNode = closingElement.parent;
     238                        if ( candidate.type )
     239                        {
     240                                // Add all elements that have been found in the above loop.
     241                                for ( var i = 0 ; i < pendingAdd.length ; i++ )
     242                                {
     243                                        var node = pendingAdd[ i ];
     244                                        addElement( node, node.parent );
     245                                }
     246
     247                                currentNode = candidate;
     248
     249                                addElement( candidate, candidate.parent );
     250
     251                                // The parent should start receiving new nodes now, except if
     252                                // addElement changed the currentNode.
     253                                if ( candidate == currentNode )
     254                                        currentNode = currentNode.parent;
     255                        }
    185256                        else if ( pendingInline.length > index )
    186257                        {
    187258                                // If we didn't find any parent to be closed, let's check the
     
    223294                        currentNode.add( new CKEDITOR.htmlParser.comment( comment ) );
    224295                };
    225296
     297                // Parse it.
    226298                parser.parse( fragmentHtml );
    227299
     300                // Close all pending nodes.
     301                while ( currentNode.type )
     302                {
     303                        var parent = currentNode.parent,
     304                                node = currentNode;
     305
     306                        if ( fixForBody && !parent.type && !CKEDITOR.dtd.$body[ node.name ] )
     307                        {
     308                                currentNode = parent;
     309                                parser.onTagOpen( 'p', {} );
     310                                parent = currentNode;
     311                        }
     312
     313                        parent.add( node );
     314                        currentNode = parent;
     315                }
     316
    228317                return fragment;
    229318        };
    230319
  • _source/tests/core/htmlparser/fragment.html

     
    1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    22<html xmlns="http://www.w3.org/1999/xhtml">
    33<head>
    44        <title>CKEDITOR.htmlParser.fragment</title>
     
    1010        <script type="text/javascript">
    1111        //<![CDATA[
    1212
    13 CKEDITOR.test.addTestCase( (function()
     13var tc;
     14
     15CKEDITOR.test.addTestCase( tc = (function()
    1416{
    1517        // Local reference to the "assert" object.
    1618        var assert = CKEDITOR.test.assert;
     
    1517        // Local reference to the "assert" object.
    1618        var assert = CKEDITOR.test.assert;
    1719
     20        function testParser( input, expected )
     21        {
     22                var fragment = CKEDITOR.htmlParser.fragment.fromHtml( input, true ),
     23                        writer = new CKEDITOR.htmlParser.basicWriter();
     24
     25                fragment.writeHtml( writer );
     26
     27                assert.areSame( expected, writer.getHtml( true ) );
     28        }
     29
    1830        return {
    1931                test_fromHtml_1 : function()
    2032                {
     
    2537                        assert.areSame( 'p', fragment.children[0].name, 'Wrong child name' );
    2638                },
    2739
     40                test_parser_1 : function()
     41                {
     42                        testParser(     '<table><tr><td>1</td><p><b>2</b> Test</p><td>3</td></tr></table>',
     43                                                '<p><b>2</b> Test</p><table><tr><td>1</td><td>3</td></tr></table>' );
     44                },
     45
     46                test_parser_2 : function()
     47                {
     48                        testParser(     '<b><table><tr><td>1</td><td>2</td></tr></table></b>',
     49                                                '<table><tr><td><b>1</b></td><td><b>2</b></td></tr></table>' );
     50                },
     51
     52                test_parser_3_1 : function()
     53                {
     54                        testParser(     '<b><i>Table:<table><tr><td>1</td><td>2</td></tr></table></i></b>',
     55                                                '<p><b><i>Table:</i></b></p><table><tr><td><b><i>1</i></b></td><td><b><i>2</i></b></td></tr></table>' );
     56                },
     57
     58                test_parser_3_2 : function()
     59                {
     60                        testParser(     '<b><i><table><tr><td>1</td><td>2</td></tr></table>Table</i></b>',
     61                                                '<table><tr><td><b><i>1</i></b></td><td><b><i>2</i></b></td></tr></table><p><b><i>Table</i></b></p>' );
     62                },
     63
     64                test_parser_4 : function()
     65                {
     66                        testParser(     '<b><i>Test',
     67                                                '<p><b><i>Test</i></b></p>' );
     68                },
     69
     70                test_parser_5 : function()
     71                {
     72                        testParser(     '<p>Para 1<p>Para 2<p>Para 3',
     73                                                '<p>Para 1</p><p>Para 2</p><p>Para 3</p>' );
     74                },
     75
     76                test_parser_6 : function()
     77                {
     78                        testParser(     '<b>A</b><i>B</i>',
     79                                                '<p><b>A</b><i>B</i></p>' );
     80                },
     81
     82                test_parser_7 : function()
     83                {
     84                        testParser(     '<p>Para 1<hr>Para 2<h1>Para 3',
     85                                                '<p>Para 1</p><hr /><p>Para 2</p><h1>Para 3</h1>' );
     86                },
     87
     88                /**
     89                 * Test remove empty inline element.
     90                 */
     91                test_parser_8 : function()
     92                {
     93                        testParser(     '<p><b></b>text</p>',
     94                                                '<p>text</p>' );
     95                },
     96               
     97                /**
     98                 *  Test remove multiple empty inline elements.
     99                 */
     100                test_parser_8_2 : function()
     101                {
     102                        testParser(     '<p><b><i></b></i>text</p>',
     103                                                '<p>text</p>' );
     104                },
     105
     106                /**
     107                 * Test fixing malformed inline element closing.
     108                 */
     109                test_parser_9 : function()
     110                {
     111                        testParser(     '<p><b>bold<i>ita</b>lic</i></p>',
     112                                                '<p><b>bold<i>ita</i></b><i>lic</i></p>' );
     113                },
     114
     115                test_ticket_10 : function()
     116                {
     117                        testParser(     '<table><tbody><tr><td>A<b></b></td></tr></tbody></table>',
     118                                                '<table><tbody><tr><td>A</td></tr></tbody></table>' );
     119                },
     120               
     121                /**
     122                 *  Test fixing paragraph inside table row.
     123                 */
     124                test_ticket_3195 : function()
     125                {
     126                        testParser(     '<table><tr><td>1</td><p>2</p><td>3</td></tr></table>',
     127                                                '<p>2</p><table><tr><td>1</td><td>3</td></tr></table>' );
     128                },
     129               
     130                /**
     131                 *  Test fixing paragraph inside list.
     132                 */
     133                test_ticket_3195_2 : function()
     134                {
     135                        testParser(     '<ul><li>1</li><p>2</p><li>3</li></ul>',
     136                                                '<p>2</p><ul><li>1</li><li>3</li></ul>' );
     137                },
     138
     139                /**
     140                 *  Test fixing 'div' inside paragraph.
     141                 */
     142                test_ticket_3195_3 : function()
     143                {
     144                        testParser(     '<p>1<div>2</div><span>3</span></p>',
     145                                                '<p>1</p><div>2</div><p><span>3</span></p>' );
     146                },
     147               
    28148                name : document.title
    29149        };
    30150})() );
     
    29149        };
    30150})() );
    31151
     152// Uncomment the following to run a single test.
     153// window.onload = tc.test_ticket_3195_2;
     154
    32155        //]]>
    33156        </script>
    34157</head>
© 2003 – 2019 CKSource – Frederico Knabben. All rights reserved. | Terms of use | Privacy policy