Ticket #3195: 3195_5.patch
File 3195_5.patch, 11.3 KB (added by , 15 years ago) |
---|
-
_source/core/htmlparser/fragment.js
40 40 // Elements which the end tag is marked as optional in the HTML 4.01 DTD 41 41 // (expect empty elements). 42 42 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 43 49 44 50 /** 45 51 * Creates a {@link CKEDITOR.htmlParser.fragment} from an HTML string. … … 56 62 html = [], 57 63 fragment = new CKEDITOR.htmlParser.fragment(), 58 64 pendingInline = [], 59 currentNode = fragment; 65 currentNode = fragment, 66 returnPoint; 60 67 61 var checkPending = function( newTagName )68 function checkPending( newTagName ) 62 69 { 63 70 if ( pendingInline.length > 0 ) 64 71 { … … 76 83 77 84 // Add it to the current node and make it the current, 78 85 // so the new element will be added inside of it. 79 currentNode.add( pendingElement );86 pendingElement.parent = currentNode; 80 87 currentNode = pendingElement; 81 88 82 89 // Remove the pending element (back the index by one … … 86 93 } 87 94 } 88 95 } 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 } 90 127 91 128 parser.onTagOpen = function( tagName, attributes, selfClosing ) 92 129 { 93 if ( fixForBody && !currentNode.type && !CKEDITOR.dtd.$body[ tagName ] )94 this.onTagOpen( 'p', {} );95 96 130 var element = new CKEDITOR.htmlParser.element( tagName, attributes ); 97 131 98 132 // "isEmpty" will be always "false" for unknown elements, so we … … 123 157 // If the element name is the same as the current element name, 124 158 // then just close the current one and append the new one to the 125 159 // 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 128 166 { 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 ] ) 133 168 { 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; 138 171 } 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 } 139 184 140 185 reApply = true; 141 186 } … … 142 187 143 188 // In any of the above cases, we'll be adding, or trying to 144 189 // add it to the parent. 145 currentNode = currentNode. parent;190 currentNode = currentNode.returnPoint || currentNode.parent; 146 191 147 192 if ( reApply ) 148 193 { … … 153 198 154 199 checkPending( tagName ); 155 200 156 currentNode.add( element ); 201 element.parent = currentNode; 202 element.returnPoint = returnPoint; 203 returnPoint = 0; 157 204 158 if ( !element.isEmpty ) 205 if ( element.isEmpty ) 206 addElement( element ); 207 else 159 208 currentNode = element; 160 209 }; 161 210 … … 161 210 162 211 parser.onTagClose = function( tagName ) 163 212 { 164 var closingElement = currentNode, 165 index = 0; 213 var index = 0, 214 pendingAdd = [], 215 candidate = currentNode; 166 216 167 while ( c losingElement && closingElement.name != tagName )217 while ( candidate.type && candidate.name != tagName ) 168 218 { 169 219 // If this is an inline element, add it to the pending list, so 170 220 // it will continue after the closing tag. 171 if ( !c losingElement._.isBlockLike )221 if ( !candidate._.isBlockLike ) 172 222 { 173 pendingInline.unshift( c losingElement);223 pendingInline.unshift( candidate ); 174 224 175 225 // Increase the index, so it will not get checked again in 176 226 // the pending list check that follows. … … 177 227 index++; 178 228 } 179 229 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; 181 236 } 182 237 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 } 185 256 else if ( pendingInline.length > index ) 186 257 { 187 258 // If we didn't find any parent to be closed, let's check the … … 223 294 currentNode.add( new CKEDITOR.htmlParser.comment( comment ) ); 224 295 }; 225 296 297 // Parse it. 226 298 parser.parse( fragmentHtml ); 227 299 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 228 317 return fragment; 229 318 }; 230 319 -
_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"> 2 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 3 <head> 4 4 <title>CKEDITOR.htmlParser.fragment</title> … … 10 10 <script type="text/javascript"> 11 11 //<![CDATA[ 12 12 13 CKEDITOR.test.addTestCase( (function() 13 var tc; 14 15 CKEDITOR.test.addTestCase( tc = (function() 14 16 { 15 17 // Local reference to the "assert" object. 16 18 var assert = CKEDITOR.test.assert; … … 15 17 // Local reference to the "assert" object. 16 18 var assert = CKEDITOR.test.assert; 17 19 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 18 30 return { 19 31 test_fromHtml_1 : function() 20 32 { … … 25 37 assert.areSame( 'p', fragment.children[0].name, 'Wrong child name' ); 26 38 }, 27 39 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 28 148 name : document.title 29 149 }; 30 150 })() ); … … 29 149 }; 30 150 })() ); 31 151 152 // Uncomment the following to run a single test. 153 // window.onload = tc.test_ticket_3195_2; 154 32 155 //]]> 33 156 </script> 34 157 </head>