From 06727ce17498f3b1285a09a9537f56e282df7d1e Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 13 Sep 2016 10:03:12 +0200 Subject: [PATCH] [html] add endTagStart to Node --- .../server/src/service/parser/htmlParser.ts | 29 ++++++++++++++---- .../server/src/service/test/parser.test.ts | 30 +++++++++---------- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/extensions/html/server/src/service/parser/htmlParser.ts b/extensions/html/server/src/service/parser/htmlParser.ts index 7c01babbc61..8004343e750 100644 --- a/extensions/html/server/src/service/parser/htmlParser.ts +++ b/extensions/html/server/src/service/parser/htmlParser.ts @@ -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) }; } \ No newline at end of file diff --git a/extensions/html/server/src/service/test/parser.test.ts b/extensions/html/server/src/service/test/parser.test.ts index d87d79f3ad7..dfba15a0fee 100644 --- a/extensions/html/server/src/service/test/parser.test.ts +++ b/extensions/html/server/src/service/test/parser.test.ts @@ -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('', [ { tag: 'html', start: 0, end: 13, closed: true, children: []}] ); - assertDocument('', [ { tag: 'html', start: 0, end: 26, closed: true, children: [ { tag: 'body', start: 6, end: 19, closed: true, children: [] }]}] ); - assertDocument('', [ { 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('', [ { tag: 'html', start: 0, end: 13, endTagStart: 6, closed: true, children: []}] ); + assertDocument('', [ { tag: 'html', start: 0, end: 26, endTagStart: 19, closed: true, children: [ { tag: 'body', start: 6, end: 19, endTagStart: 12, closed: true, children: [] }]}] ); + assertDocument('', [ { 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('
', [ { tag: 'br', start: 0, end: 5, closed: true, children: []}] ); - assertDocument('

', [ { 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('
', [ { tag: 'br', start: 0, end: 5, endTagStart: void 0, closed: true, children: []}] ); + assertDocument('

', [ { 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('', [ { tag: 'meta', start: 0, end: 6, closed: true, children: []}] ); - assertDocument('


', [ { 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('', [ { tag: 'meta', start: 0, end: 6, endTagStart: void 0, closed: true, children: []}] ); + assertDocument('


', [ { 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('', [] ); - assertDocument('
', [ { tag: 'div', start: 0, end: 11, closed: true, children: [] }] ); - assertDocument('
', [ { tag: 'div', start: 0, end: 16, closed: false, children: [ { tag: 'div', start: 5, end: 16, closed: true, children: [] } ] }] ); - assertDocument('<div>', [ { tag: 'title', start: 0, end: 20, closed: true, children: [ { tag: 'div', start: 7, end: 12, closed: false, children: [] } ] }] ); - assertDocument('

', [ { 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('
', [ { tag: 'div', start: 0, end: 11, endTagStart: 5, closed: true, children: [] }] ); + assertDocument('
', [ { tag: 'div', start: 0, end: 16, endTagStart: void 0, closed: false, children: [ { tag: 'div', start: 5, end: 16, endTagStart: 10, closed: true, children: [] } ] }] ); + assertDocument('<div>', [ { tag: 'title', start: 0, end: 20, endTagStart: 12, closed: true, children: [ { tag: 'div', start: 7, end: 12, endTagStart: void 0, closed: false, children: [] } ] }] ); + assertDocument('

', [ { 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'); - }); + }); }); \ No newline at end of file