Merge pull request #318 from sveltejs/implicitly-closed-elements

handle implicitly closed elements
pull/319/head
Rich Harris 8 years ago committed by GitHub
commit c2275f3064

1
.gitignore vendored

@ -11,3 +11,4 @@ coverage
coverage.lcov coverage.lcov
test/sourcemaps/*/output.js test/sourcemaps/*/output.js
test/sourcemaps/*/output.js.map test/sourcemaps/*/output.js.map
_actual.json

@ -21,9 +21,46 @@ const specials = {
} }
}; };
// based on http://developers.whatwg.org/syntax.html#syntax-tag-omission
const disallowedContents = {
li: [ 'li' ],
dt: [ 'dt', 'dd' ],
dd: [ 'dt', 'dd' ],
p: 'address article aside blockquote div dl fieldset footer form h1 h2 h3 h4 h5 h6 header hgroup hr main menu nav ol p pre section table ul'.split( ' ' ),
rt: [ 'rt', 'rp' ],
rp: [ 'rt', 'rp' ],
optgroup: [ 'optgroup' ],
option: [ 'option', 'optgroup' ],
thead: [ 'tbody', 'tfoot' ],
tbody: [ 'tbody', 'tfoot' ],
tfoot: [ 'tbody' ],
tr: [ 'tr', 'tbody' ],
td: [ 'td', 'th', 'tr' ],
th: [ 'td', 'th', 'tr' ]
};
function stripWhitespace ( element ) {
if ( element.children.length ) {
const firstChild = element.children[0];
const lastChild = element.children[ element.children.length - 1 ];
if ( firstChild.type === 'Text' ) {
firstChild.data = trimStart( firstChild.data );
if ( !firstChild.data ) element.children.shift();
}
if ( lastChild.type === 'Text' ) {
lastChild.data = trimEnd( lastChild.data );
if ( !lastChild.data ) element.children.pop();
}
}
}
export default function tag ( parser ) { export default function tag ( parser ) {
const start = parser.index++; const start = parser.index++;
let parent = parser.current();
if ( parser.eat( '!--' ) ) { if ( parser.eat( '!--' ) ) {
const data = parser.readUntil( /-->/ ); const data = parser.readUntil( /-->/ );
parser.eat( '-->' ); parser.eat( '-->' );
@ -40,8 +77,6 @@ export default function tag ( parser ) {
const isClosingTag = parser.eat( '/' ); const isClosingTag = parser.eat( '/' );
// TODO handle cases like <li>one<li>two
const name = readTagName( parser ); const name = readTagName( parser );
parser.allowWhitespace(); parser.allowWhitespace();
@ -53,28 +88,31 @@ export default function tag ( parser ) {
if ( !parser.eat( '>' ) ) parser.error( `Expected '>'` ); if ( !parser.eat( '>' ) ) parser.error( `Expected '>'` );
const element = parser.current(); // close any elements that don't have their own closing tags, e.g. <div><p></div>
while ( parent.name !== name ) {
// strip leading/trailing whitespace as necessary parent.end = start;
if ( element.children.length ) { parser.stack.pop();
const firstChild = element.children[0];
const lastChild = element.children[ element.children.length - 1 ];
if ( firstChild.type === 'Text' ) {
firstChild.data = trimStart( firstChild.data );
if ( !firstChild.data ) element.children.shift();
}
if ( lastChild.type === 'Text' ) { parent = parser.current();
lastChild.data = trimEnd( lastChild.data );
if ( !lastChild.data ) element.children.pop();
}
} }
element.end = parser.index; // strip leading/trailing whitespace as necessary
stripWhitespace( parent );
parent.end = parser.index;
parser.stack.pop(); parser.stack.pop();
return null; return null;
} else if ( parent.name in disallowedContents ) {
// can this be a child of the parent element, or does it implicitly
// close it, like `<li>one<li>two`?
const disallowed = disallowedContents[ parent.name ];
if ( ~disallowed.indexOf( name ) ) {
stripWhitespace( parent );
parent.end = start;
parser.stack.pop();
}
} }
const attributes = []; const attributes = [];

@ -16,7 +16,8 @@ describe( 'parse', () => {
const input = fs.readFileSync( `test/parser/${dir}/input.html`, 'utf-8' ).replace( /\s+$/, '' ); const input = fs.readFileSync( `test/parser/${dir}/input.html`, 'utf-8' ).replace( /\s+$/, '' );
try { try {
const actual = JSON.parse( JSON.stringify( svelte.parse( input ) ) ); const actual = svelte.parse( input );
fs.writeFileSync( `test/parser/${dir}/_actual.json`, JSON.stringify( actual, null, '\t' ) );
const expected = require( `./parser/${dir}/output.json` ); const expected = require( `./parser/${dir}/output.json` );
assert.deepEqual( actual.html, expected.html ); assert.deepEqual( actual.html, expected.html );

@ -0,0 +1,5 @@
<ul>
<li>a
<li>b
<li>c
</ul>

@ -0,0 +1,66 @@
{
"hash": 3806276940,
"html": {
"start": 0,
"end": 31,
"type": "Fragment",
"children": [
{
"start": 0,
"end": 31,
"type": "Element",
"name": "ul",
"attributes": [],
"children": [
{
"start": 6,
"end": 13,
"type": "Element",
"name": "li",
"attributes": [],
"children": [
{
"start": 10,
"end": 13,
"type": "Text",
"data": "a"
}
]
},
{
"start": 13,
"end": 20,
"type": "Element",
"name": "li",
"attributes": [],
"children": [
{
"start": 17,
"end": 20,
"type": "Text",
"data": "b"
}
]
},
{
"start": 20,
"end": 26,
"type": "Element",
"name": "li",
"attributes": [],
"children": [
{
"start": 24,
"end": 26,
"type": "Text",
"data": "c\n"
}
]
}
]
}
]
},
"css": null,
"js": null
}
Loading…
Cancel
Save