[html] add endTagStart to Node

This commit is contained in:
Martin Aeschlimann 2016-09-13 10:03:12 +02:00
parent 49dfae7e00
commit 06727ce174
2 changed files with 38 additions and 21 deletions

View file

@ -11,10 +11,12 @@ import { isEmptyElement, isSameTag } from './htmlTags';
export class Node {
public tag: string;
public closed: boolean;
public endTagStart: number;
constructor(public start: number, public end: number, public children: Node[], public parent: Node) {
this.closed = false;
}
private get lastChild(): Node { return this.children.length ? this.children[this.children.length - 1] : void 0; }
public get firstChild(): Node { return this.children[0]; }
public get lastChild(): Node { return this.children.length ? this.children[this.children.length - 1] : void 0; }
public findNodeBefore(offset:number) : Node {
let idx = findFirst(this.children, c => offset <= c.start) - 1;
@ -33,11 +35,23 @@ export class Node {
}
return this;
}
public findNodeAt(offset:number) : Node {
let idx = findFirst(this.children, c => offset <= c.start) - 1;
if (idx >= 0) {
let child = this.children[idx];
if (offset > child.start && offset <= child.end) {
return child.findNodeBefore(offset);
}
}
return this;
}
}
export interface HTMLDocument {
roots: Node[];
findNodeBefore(offset:number) : Node;
findNodeAt(offset:number) : Node;
}
export function parse(text: string) : HTMLDocument {
@ -76,13 +90,16 @@ export function parse(text: string) : HTMLDocument {
}
if (curr !== htmlDocument) {
curr.closed = true;
curr.endTagStart = endTagStart;
}
break;
case TokenType.StartTagSelfClose:
if (curr !== htmlDocument) {
curr.closed = true;
curr.end = scanner.getTokenEnd();
curr = curr.parent;
}
// fallthrough
break;
case TokenType.EndTagClose:
if (curr !== htmlDocument) {
curr.end = scanner.getTokenEnd();
@ -94,13 +111,13 @@ export function parse(text: string) : HTMLDocument {
}
while (curr !== htmlDocument) {
curr.end = text.length;
curr.closed = false;
curr = curr.parent;
}
return {
roots: htmlDocument.children,
findNodeBefore: (offset:number) => {
return htmlDocument.findNodeBefore(offset);
}
findNodeBefore: htmlDocument.findNodeBefore.bind(htmlDocument),
findNodeAt: htmlDocument.findNodeAt.bind(htmlDocument)
};
}

View file

@ -10,7 +10,7 @@ import {Node, HTMLDocument, parse} from '../parser/htmlParser';
suite('HTML Parser', () => {
function toJSON(node: Node): any {
return { tag: node.tag, start: node.start, end: node.end, closed: node.closed, children: node.children.map(toJSON) };
return { tag: node.tag, start: node.start, end: node.end, endTagStart: node.endTagStart, closed: node.closed, children: node.children.map(toJSON) };
}
function assertDocument(input: string, expected: any) {
@ -25,29 +25,29 @@ suite('HTML Parser', () => {
}
test('Simple', () => {
assertDocument('<html></html>', [ { tag: 'html', start: 0, end: 13, closed: true, children: []}] );
assertDocument('<html><body></body></html>', [ { tag: 'html', start: 0, end: 26, closed: true, children: [ { tag: 'body', start: 6, end: 19, closed: true, children: [] }]}] );
assertDocument('<html><head></head><body></body></html>', [ { tag: 'html', start: 0, end: 39, closed: true, children: [ { tag: 'head', start: 6, end: 19, closed: true, children: [] }, { tag: 'body', start: 19, end: 32, closed: true, children: [] }]}] );
assertDocument('<html></html>', [ { tag: 'html', start: 0, end: 13, endTagStart: 6, closed: true, children: []}] );
assertDocument('<html><body></body></html>', [ { tag: 'html', start: 0, end: 26, endTagStart: 19, closed: true, children: [ { tag: 'body', start: 6, end: 19, endTagStart: 12, closed: true, children: [] }]}] );
assertDocument('<html><head></head><body></body></html>', [ { tag: 'html', start: 0, end: 39, endTagStart: 32, closed: true, children: [ { tag: 'head', start: 6, end: 19, endTagStart: 12, closed: true, children: [] }, { tag: 'body', start: 19, end: 32, endTagStart: 25, closed: true, children: [] }]}] );
});
test('SelfClose', () => {
assertDocument('<br/>', [ { tag: 'br', start: 0, end: 5, closed: true, children: []}] );
assertDocument('<div><br/><span></span></div>', [ { tag: 'div', start: 0, end: 29, closed: true, children: [{ tag: 'br', start: 5, end: 10, closed: true, children: [] }, { tag: 'span', start: 10, end: 23, closed: true, children: [] }]}] );
assertDocument('<br/>', [ { tag: 'br', start: 0, end: 5, endTagStart: void 0, closed: true, children: []}] );
assertDocument('<div><br/><span></span></div>', [ { tag: 'div', start: 0, end: 29, endTagStart: 23, closed: true, children: [{ tag: 'br', start: 5, end: 10, endTagStart: void 0, closed: true, children: [] }, { tag: 'span', start: 10, end: 23, endTagStart: 16, closed: true, children: [] }]}] );
});
test('EmptyTag', () => {
assertDocument('<meta>', [ { tag: 'meta', start: 0, end: 6, closed: true, children: []}] );
assertDocument('<div><input type="button"><span><br><br></span></div>', [ { tag: 'div', start: 0, end: 53, closed: true, children: [
{ tag: 'input', start: 5, end: 26, closed: true, children: [] },
{ tag: 'span', start: 26, end: 47, closed: true, children: [{ tag: 'br', start: 32, end: 36, closed: true, children: [] }, { tag: 'br', start: 36, end: 40, closed: true, children: [] }] }
assertDocument('<meta>', [ { tag: 'meta', start: 0, end: 6, endTagStart: void 0, closed: true, children: []}] );
assertDocument('<div><input type="button"><span><br><br></span></div>', [ { tag: 'div', start: 0, end: 53, endTagStart: 47, closed: true, children: [
{ tag: 'input', start: 5, end: 26, endTagStart: void 0, closed: true, children: [] },
{ tag: 'span', start: 26, end: 47, endTagStart: 40, closed: true, children: [{ tag: 'br', start: 32, end: 36, endTagStart: void 0, closed: true, children: [] }, { tag: 'br', start: 36, end: 40, endTagStart: void 0, closed: true, children: [] }] }
]}] );
});
test('MissingTags', () => {
assertDocument('</meta>', [] );
assertDocument('<div></div></div>', [ { tag: 'div', start: 0, end: 11, closed: true, children: [] }] );
assertDocument('<div><div></div>', [ { tag: 'div', start: 0, end: 16, closed: false, children: [ { tag: 'div', start: 5, end: 16, closed: true, children: [] } ] }] );
assertDocument('<title><div></title>', [ { tag: 'title', start: 0, end: 20, closed: true, children: [ { tag: 'div', start: 7, end: 12, closed: false, children: [] } ] }] );
assertDocument('<h1><div><span></h1>', [ { tag: 'h1', start: 0, end: 20, closed: true, children: [ { tag: 'div', start: 4, end: 15, closed: false, children: [ { tag: 'span', start: 9, end: 15, closed: false, children: [] }] } ] }] );
assertDocument('<div></div></div>', [ { tag: 'div', start: 0, end: 11, endTagStart: 5, closed: true, children: [] }] );
assertDocument('<div><div></div>', [ { tag: 'div', start: 0, end: 16, endTagStart: void 0, closed: false, children: [ { tag: 'div', start: 5, end: 16, endTagStart: 10, closed: true, children: [] } ] }] );
assertDocument('<title><div></title>', [ { tag: 'title', start: 0, end: 20, endTagStart: 12, closed: true, children: [ { tag: 'div', start: 7, end: 12, endTagStart: void 0, closed: false, children: [] } ] }] );
assertDocument('<h1><div><span></h1>', [ { tag: 'h1', start: 0, end: 20, endTagStart: 15, closed: true, children: [ { tag: 'div', start: 4, end: 15, endTagStart: void 0, closed: false, children: [ { tag: 'span', start: 9, end: 15, endTagStart: void 0, closed: false, children: [] }] } ] }] );
});
@ -78,6 +78,6 @@ suite('HTML Parser', () => {
assertNodeBefore(str, 15, 'br');
assertNodeBefore(str, 18, 'br');
assertNodeBefore(str, 21, 'div');
});
});
});