Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 70 additions & 57 deletions src/transpiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ const AttribNode = (name, value) => {
})
}

// Frame factory for stack items in parseXML
function Frame(node, scopingElement) {
return {
node,
scopingElement,
children: node.value.children
}
}

const parseXML = (lexer) => {
/*
How does the grammar look?
Expand All @@ -48,89 +57,93 @@ const parseXML = (lexer) => {
| AttributeName: String
| AttributeValue: String
*/
const rootNode = Node(ROOT, {
children: parseExpr(lexer, Token(ROOT, 'ROOT'))
})
return rootNode
}

const parseExpr = (lexer, scopingElement) => {
const children = []
const rootNode = Node(ROOT, { children: [] })
const stack = [
Frame(rootNode, Token(ROOT, 'ROOT'))
]
let currentFrame = stack[stack.length - 1]
while (lexer.hasNext()) {
const lexem = lexer.next()
switch (lexem.type) {
case TOKEN_TYPE.OPEN_BRACKET: {
const elementLexem = lexer.next()
const [elementAttributes, currentToken] =
parseElementAttributes(lexer)
let elementChildren = []
if (currentToken.type !== TOKEN_TYPE.CLOSE_ELEMENT) {
elementChildren = parseExpr(lexer, elementLexem)
}
if (
elementChildren &&
elementChildren.length > 0 &&
elementChildren[0].type === TOKEN_TYPE.CONTENT
) {
elementChildren = reduceChildrenElements(elementChildren)
}
children.push(
ElementNode(
elementLexem.value,
elementAttributes,
elementChildren
)
)
case TOKEN_TYPE.OPEN_BRACKET:
handleOpenBracket(lexer, stack)
currentFrame = stack[stack.length - 1]
break
}
case TOKEN_TYPE.CLOSE_ELEMENT: {
if (lexem.value === scopingElement.value) return children
case TOKEN_TYPE.CLOSE_ELEMENT:
handleCloseElement(lexem, stack)
currentFrame = stack[stack.length - 1]
break
}
case TOKEN_TYPE.CONTENT: {
children.push(ContentNode(lexem.value))
case TOKEN_TYPE.CONTENT:
handleContent(lexem, currentFrame)
break
}
case TOKEN_TYPE.EOF: {
return children
}
default: {
case TOKEN_TYPE.EOF:
break
default:
throw new Error(
`Unknown Lexem type: ${lexem.type} "${lexem.value}, scoping element: ${scopingElement.value}"`
`Unknown Lexem type: ${lexem.type} "${lexem.value}, scoping element: ${currentFrame.scopingElement.value}"`
)
}
}
}
return children
return rootNode
}

const parseElementAttributes = (lexer) => {
const attribs = []
function handleOpenBracket(lexer, stack) {
const currentFrame = stack[stack.length - 1]
const elementLexem = lexer.next()
let attribs = []
let currentToken = lexer.peek()
if (
!lexer.hasNext() ||
(currentToken && currentToken.type === TOKEN_TYPE.CLOSE_BRACKET) ||
(currentToken && currentToken.type === TOKEN_TYPE.CLOSE_ELEMENT)
) {
return [attribs, currentToken]
const areAttributesExpected = lexer.hasNext() &&
(currentToken && currentToken.type !== TOKEN_TYPE.CLOSE_BRACKET) &&
(currentToken && currentToken.type !== TOKEN_TYPE.CLOSE_ELEMENT)
if (areAttributesExpected) {
[attribs, currentToken] = collectAttributes(lexer)
}
currentToken = lexer.next()
const elementNode = ElementNode(elementLexem.value, attribs, [])
currentFrame.children.push(elementNode)
if (currentToken && currentToken.type === TOKEN_TYPE.CLOSE_ELEMENT) {
return
}
stack.push(Frame(elementNode, elementLexem))
}

function collectAttributes(lexer) {
const attribs = []
let currentToken = lexer.next()
while (
lexer.hasNext() &&
currentToken &&
currentToken.type !== TOKEN_TYPE.CLOSE_BRACKET &&
currentToken.type !== TOKEN_TYPE.CLOSE_ELEMENT
) {
const attribName = currentToken
lexer.next() //assignment token
lexer.next() // assignment token
const attribValue = lexer.next()
const attributeNode = AttribNode(attribName.value, attribValue.value)
attribs.push(attributeNode)
attribs.push(AttribNode(attribName.value, attribValue.value))
currentToken = lexer.next()
}
return [attribs, currentToken]
}

function handleCloseElement(lexem, stack) {
const currentFrame = stack[stack.length - 1]
if (lexem.value === currentFrame.scopingElement.value) {
let children = currentFrame.children
if (
children &&
children.length > 0 &&
children[0].type === TOKEN_TYPE.CONTENT
) {
children = reduceChildrenElements(children)
currentFrame.node.value.children = children
}
stack.pop()
}
}

function handleContent(lexem, currentFrame) {
currentFrame.children.push(ContentNode(lexem.value))
}

function reduceChildrenElements(elementChildren) {
let reduced = [],
buffer = ''
Expand All @@ -154,7 +167,7 @@ function reduceChildrenElements(elementChildren) {

function transpile(xmlAsString, astConverter) {
const lexer = createLexer(xmlAsString)
const ast = parseXML(lexer, xmlAsString)
const ast = parseXML(lexer)
if (astConverter) {
return astConverter.convert(ast)
}
Expand Down