Skip to content
This repository has been archived by the owner on Oct 31, 2023. It is now read-only.

Commit

Permalink
fix(ceb-templating-parser): parser cannot work on repetitive structure
Browse files Browse the repository at this point in the history
  • Loading branch information
tmorin committed Nov 10, 2021
1 parent 7adf34d commit d9a8b1d
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 59 deletions.
20 changes: 20 additions & 0 deletions packages/ceb-templating-literal/src/literal.complex.spec.ts
Expand Up @@ -20,4 +20,24 @@ describe("literal/complex", function () {
template.render(el)
expect(el.innerHTML).to.be.eq(`<ul><li>header</li><li>itemA</li><li>itemB</li><li>itemC</li><li>footer</li></ul>`)
})
it.only("should handle a bulma card", function () {
let cardName = "The name of the card"
let cardDescription = "The description of the card."
const template = html`
<div class="card">
<div class="card-header">
<p class="card-header-title">
${cardName}
</p>
</div>
<div class="card-content">
<div class="content">
${cardDescription}
</div>
</div>
</div>
`
template.render(el)
//expect(el.innerHTML).to.be.eq(`<ul><li>header</li><li>itemA</li><li>itemB</li><li>itemC</li><li>footer</li></ul>`)
})
})
6 changes: 5 additions & 1 deletion packages/ceb-templating-literal/src/literal.ts
Expand Up @@ -133,6 +133,7 @@ export function html(strings: TemplateStringsArray, ...args: Array<any>): Templa
const operations = new Operations()
parse(template, {
openTag(name: string, attrs: Array<Attribute>, selfClosing: boolean) {
console.log("openTag", name, attrs[0]?.value, selfClosing)
const parameters = generateParameters(attrs, args)
if (PROTECTED_TAGS.indexOf(name) > -1) {
Object.assign(parameters.options, {
Expand All @@ -144,17 +145,20 @@ export function html(strings: TemplateStringsArray, ...args: Array<any>): Templa
operations.push(engine => engine.closeElement())
}
},
closeTag() {
closeTag(name: string) {
console.log("closeTag")
operations.push(engine => engine.closeElement())
},
text(data: string) {
console.log("text")
const values = fromStringToValues(data, args)
operations.push(fromValuesToOperations(
values,
(childOperations, value) => childOperations.push(engine => engine.text(value))
))
},
comment(data: string) {
console.log("comment")
const values = fromStringToValues(data, args)
operations.push(fromValuesToOperations(
values,
Expand Down
50 changes: 50 additions & 0 deletions packages/ceb-templating-parser/src/parser.spec.ts
Expand Up @@ -129,6 +129,56 @@ describe("parser", function () {
expect(parseResult[i].detail.tagName).eq("div")
i++
});
it("should parse bulma card", function () {
const parseResult = executeParse(`<div id="1"><div id="1.1"><div id="1.1.1">1.1.1</div></div><div id="1.2"><div id="1.2.1">1.2.1</div></div></div>`)
// div id="1"
expect(parseResult[i].name).eq("openTag")
expect(parseResult[i].detail.tagName).eq("div")
i++
// div id="1.1"
expect(parseResult[i].name).eq("openTag")
expect(parseResult[i].detail.tagName).eq("div")
i++
// div id="1.1.1"
expect(parseResult[i].name).eq("openTag")
expect(parseResult[i].detail.tagName).eq("div")
i++
// text 1.1.1
expect(parseResult[i].name).eq("text")
expect(parseResult[i].detail.data).eq("1.1.1")
i++
// div id="1.1.1"
expect(parseResult[i].name).eq("closeTag")
expect(parseResult[i].detail.tagName).eq("div")
i++
// div id="1.1"
expect(parseResult[i].name).eq("closeTag")
expect(parseResult[i].detail.tagName).eq("div")
i++
// div id="1.2"
expect(parseResult[i].name).eq("openTag")
expect(parseResult[i].detail.tagName).eq("div")
i++
// div id="1.2.1"
expect(parseResult[i].name).eq("openTag")
expect(parseResult[i].detail.tagName).eq("div")
i++
// text 1.2.1
expect(parseResult[i].name).eq("text")
expect(parseResult[i].detail.data).eq("1.2.1")
i++
// div id="1.2.1"
expect(parseResult[i].name).eq("closeTag")
expect(parseResult[i].detail.tagName).eq("div")
i++
// div id="1.2"
expect(parseResult[i].name).eq("closeTag")
expect(parseResult[i].detail.tagName).eq("div")
i++
// div id="1"
expect(parseResult[i].name).eq("closeTag")
expect(parseResult[i].detail.tagName).eq("div")
});
it("should handle custom element", function () {
const parseResult = executeParse(`<x-a>textA</x-a>`)
// x-a
Expand Down
116 changes: 58 additions & 58 deletions packages/ceb-templating-parser/src/parser.ts
Expand Up @@ -11,16 +11,23 @@ function toMap(str: string) {
}

// Regular Expressions for parsing tags and attributes
const startTag = /^<([-:A-Za-z0-9_]+)((?:\s+[\w:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/
const endTag = /^<\/([-:A-Za-z0-9_]+)[^>]*>/
const startTag = /^<([-A-Za-z0-9_]+)((?:\s+[\w:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/
const endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/
const attr = /([-:A-Za-z0-9_]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g

// 2021-07-26 - https://developer.mozilla.org/en-US/docs/Glossary/empty_element
const empty = toMap("area,base,br,col,embed,hr,img,input,keygen,link,meta,param,source,track,wbr")

// Block Elements - HTML 4.01
const block = toMap("address,applet,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul");

// 2021-07-26 - https://developer.mozilla.org/en-US/docs/Glossary/empty_element
const inline = toMap("a,abbr,acronym,b,bdi,bdo,big,br,button,canvas,cite,code,data,datalist,del,dfn,em,embed,i,iframe,img,input,ins,kbd,label,map,mark,meter,noscript,object,output,picture,progress,q,ruby,s,samp,script,select,slot,small,span,strong,sub,sup,svg,template,textarea,time,u,tt,var,video,wbr")

// Elements that you can, intentionally, leave open
// (and which close themselves)
const closeSelf = toMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");

// Special Elements (can contain anything)
const special = toMap("script,style")

Expand Down Expand Up @@ -50,94 +57,93 @@ function createStack(): Stack {
export function parse(html: string, handler: SaxHandler) {
let index: number
let chars: boolean
let match
let match: RegExpMatchArray | null
let stack = createStack()
let last = html

while (html) {
chars = true

// Make sure we're not in a script or style element
if (!stack["last"]() || !special[stack["last"]()]) {

// Comment
if (html.indexOf("<!--") == 0) {
if (!stack.last() || !special[stack.last()]) {
if (html.indexOf("<!--") == 0) { // Comment
index = html.indexOf("-->")
if (index >= 0) {
if (handler.comment)
if (handler.comment) {
handler.comment(html.substring(4, index))
}
html = html.substring(index + 3)
chars = false
}
// end tag
} else if (html.indexOf("</") == 0) {
} else if (html.indexOf("</") == 0) { // end tag
match = html.match(endTag)
if (match) {
html = html.substring(match[0].length)
match[0].replace(endTag, parseEndTag)
chars = false
}
// start tag
} else if (html.indexOf("<") == 0) {
} else if (html.indexOf("<") == 0) { // start tag
match = html.match(startTag)
if (match) {
html = html.substring(match[0].length)
match[0].replace(startTag, parseStartTag)
chars = false
}
}

if (chars) {
index = html.indexOf("<")
index = html.indexOf("<");
const text = index < 0 ? html : html.substring(0, index)
html = index < 0 ? "" : html.substring(index)
if (handler.text) {
if (handler.text){
handler.text(text)
}
}

} else {
html = html.replace(new RegExp("(.*)<\/" + stack["last"]() + "[^>]*>"), function (all, text) {
text = text.replace(/<!--(.*?)-->/g, "$1").replace(/<!\[CDATA\[(.*?)]]>/g, "$1")
html = html.replace(new RegExp("(.*)<\/" + stack.last() + "[^>]*>"), function (all: string, text: string) {
text = text.replace(/<!--(.*?)-->/g, "$1")
.replace(/<!\[CDATA\[(.*?)]]>/g, "$1")
if (handler.text) {
handler.text(text)
}
return ""
})
parseEndTag("", stack["last"]())
});
parseEndTag("", stack.last());
}

if (html == last) {
throw "Parse Error: " + html
throw "Parse Error: " + html;
}
last = html

last = html;
}

// Clean up any remaining tags
parseEndTag()
parseEndTag();

function parseStartTag(tag: string, tagName: string, rest: string, selfClosing: boolean) {
tagName = tagName.toLowerCase()
function parseStartTag(tag: string, tagName: string, rest: string, unary: any): string {
tagName = tagName.toLowerCase();

if (!empty[tagName] && !selfClosing) {
while (stack["last"]() && inline[stack["last"]()]) {
parseEndTag("", stack["last"]())
//if (!block[tagName]) {
//if (!closeSelf[tagName]) {
while (stack.last() && inline[stack.last()]) {
parseEndTag("", stack.last());
}
}
//}

if (stack["last"]() == tagName) {
parseEndTag("", tagName)
if (closeSelf[tagName] && stack.last() == tagName) {
parseEndTag("", tagName);
}

selfClosing = empty[tagName] || !!selfClosing
unary = empty[tagName] || !!unary;

if (!selfClosing) {
stack.push(tagName)
if (!unary) {
stack.push(tagName);
}

if (handler.openTag) {
const attrs: Array<Attribute> = []
rest.replace(attr, function (match, name) {
const attrs: Array<Attribute> = [];
rest.replace(attr, function (match: string | RegExp, name: string) {
const value = arguments[2]
? arguments[2]
: arguments[3]
Expand All @@ -148,44 +154,38 @@ export function parse(html: string, handler: SaxHandler) {
attrs.push({
name: name,
value: value,
})
});
return ""
})
});

if (handler.openTag)
handler.openTag(tagName, attrs, selfClosing)
if (handler.openTag) {
handler.openTag(tagName, attrs, unary);
}
}

return ""
}

function parseEndTag(tag?: string, tagName?: string) {
let pos: number

function parseEndTag(tag?: string, tagName?: any): string {
// If no tag name is provided, clean shop
if (!tagName) {
// If no tag name is provided, clean shop
pos = 0
} else {
var pos = 0;

// Find the closest opened tag of the same type
for (pos = stack.length - 1; pos >= 0; pos--) {
if (stack[pos] == tagName) {
break
}
}
} else {
for (var pos = stack.length - 1; pos >= 0; pos--)
if (stack[pos] == tagName)
break;
}

if (pos >= 0) {
// Close all the open elements, up the stack
for (let i = stack.length - 1; i >= pos; i--) {
if (handler.closeTag) {
handler.closeTag(stack[i])
}
}
for (let i = stack.length - 1; i >= pos; i--)
if (handler.closeTag)
handler.closeTag(stack[i]);

// Remove the open elements from the stack
stack.length = pos
stack.length = pos;
}

return ""
}
}

0 comments on commit d9a8b1d

Please sign in to comment.