in src/language/semantics/xslLexer.ts [636:1445]
public analyse(xsl: string, keepNameTests?: boolean): BaseToken[] {
if (this.timerOn) {
console.time('xslLexer.analyse')
}
this.globalInstructionData.length = 0
this.globalModeData.length = 0
this.lineNumber = 0
this.lineCharCount = -1
this.charCount = -1
if (keepNameTests) {
this.attributeNameTests = []
this.elementNameTests = []
} else {
this.attributeNameTests = undefined
this.elementNameTests = undefined
}
let currentState: XMLCharState = XMLCharState.init
let currentChar: string = ''
let tokenChars: string[] = []
let result: BaseToken[] = []
let attName: string = ''
let avtExit = false
let xpLexer: XPathLexer = new XPathLexer()
xpLexer.elementNameTests = this.elementNameTests
xpLexer.attributeNameTests = this.attributeNameTests
xpLexer.documentText = xsl
xpLexer.documentTokens = result
xpLexer.debug = this.debug
xpLexer.timerOn = this.timerOn
let xslLength = xsl.length
let storeToken = false
let isNativeElement = false
let tagGlobalInstructionType = GlobalInstructionType.Unknown
let tagInstructionNameAdded = false
let tagMatchToken: BaseToken | null = null
let contextGlobalInstructionType = GlobalInstructionType.Unknown
let isGlobalVersion = false
let isXPathAttribute = false
let isExpandTextAttribute = false
let isGlobalInstructionName = false
let isGlobalInstructionMode = false
let isGlobalParameterName = false
let isGlobalUsePackageVersion = false
let isGlobalInstructionMatch = false
let expandTextValue: boolean | null = false
let xmlElementStack: XmlElement[] = []
let tokenStartChar = -1
let tokenStartLine = -1
let attributeNameTokenAdded = false
let collectParamName = false
let xpathEnded = false
if (tokenStartLine) {
}
if (this.debug) {
console.log('xsl:\n' + xsl)
}
if (xslLength === 0) {
return []
}
while (this.charCount < xslLength) {
this.charCount++
this.lineCharCount++
let nextState: XMLCharState = XMLCharState.init
let nextChar: string = xsl.charAt(this.charCount)
const isLastChar: boolean = this.charCount === xslLength
const resultLengthAtLastChar = isLastChar ? result.length : -1
if (currentChar) {
let isCurrentCharNewLIne = currentChar === '\n'
nextState = this.calcNewState(
isCurrentCharNewLIne,
currentChar,
nextChar,
currentState
)
if (nextState === currentState) {
if (isCurrentCharNewLIne || isLastChar) {
// we must split multi-line tokens:
let addToken: XSLTokenLevelState | null = null
switch (nextState) {
case XMLCharState.lPiValue:
addToken = XSLTokenLevelState.processingInstrValue
this.addNewTokenToResult(
tokenStartChar,
addToken,
result,
nextState
)
break
case XMLCharState.lComment:
addToken = XSLTokenLevelState.xmlComment
tokenStartChar =
tokenStartChar === 0 ? tokenStartChar : tokenStartChar - 2
break
case XMLCharState.lDq:
case XMLCharState.lSq:
addToken = XSLTokenLevelState.attributeValue
break
case XMLCharState.lExclam:
addToken = XSLTokenLevelState.dtd
break
}
if (addToken !== null) {
this.addNewTokenToResult(
tokenStartChar,
addToken,
result,
nextState
)
tokenStartChar = 0
} else if (isLastChar) {
let alreadyDone =
result.length > 0 &&
result[result.length - 1].startCharacter === tokenStartChar
if (!alreadyDone) {
this.addNewTokenToResult(
tokenStartChar,
XSLTokenLevelState.xmlText,
result,
nextState
)
}
}
} else if (storeToken) {
tokenChars.push(currentChar)
}
} else {
if (currentState === XMLCharState.lText) {
if (nextState === XMLCharState.escTvt) {
this.addCharTokenToResult(
tokenStartChar,
this.lineCharCount - tokenStartChar,
XSLTokenLevelState.xmlText,
result,
currentState
)
} else {
this.addCharTokenToResult(
tokenStartChar,
this.lineCharCount - 1 - tokenStartChar,
XSLTokenLevelState.xmlText,
result,
currentState
)
}
} else if (currentState === XMLCharState.escTvt) {
this.addCharTokenToResult(
tokenStartChar,
2,
XSLTokenLevelState.xmlText,
result,
currentState
)
}
if (
currentState === XMLCharState.lEntity &&
nextState !== XMLCharState.rEntity
) {
// recover from syntax error:
this.addNewTokenToResult(
tokenStartChar,
XSLTokenLevelState.entityRef,
result,
nextState
)
switch (this.entityContext) {
case EntityPosition.text:
nextState = XMLCharState.init
break
case EntityPosition.attrSq:
nextState = XMLCharState.lSq
break
case EntityPosition.attrDq:
nextState = XMLCharState.lDq
break
}
}
switch (nextState) {
case XMLCharState.lSt:
this.addCharTokenToResult(
this.lineCharCount - 1,
1,
XSLTokenLevelState.xmlPunctuation,
result,
nextState
)
break
case XMLCharState.lCtName:
case XMLCharState.lEn:
expandTextValue = null
if (tokenChars.length < 5) {
tokenChars.push(currentChar)
storeToken = true
} else {
storeToken = false
}
break
case XMLCharState.rStNoAtt:
expandTextValue = this.addToElementStack(
expandTextValue,
xmlElementStack
)
// cascade, so no-break intentional
case XMLCharState.lsElementNameWs:
case XMLCharState.rSelfCtNoAtt:
case XMLCharState.rCt:
let isCloseTag = nextState === XMLCharState.rCt
let isRootChildStartTag =
!isCloseTag && xmlElementStack.length === 1
let elementProperties = this.getElementProperties(
tokenChars,
isRootChildStartTag
)
isNativeElement = elementProperties.isNative
tagGlobalInstructionType = elementProperties.instructionType
tagInstructionNameAdded = false
tagMatchToken = null
collectParamName = false
if (xmlElementStack.length === 0 && tokenChars.length > 5) {
tagGlobalInstructionType = GlobalInstructionType.RootXSLT
}
if (xmlElementStack.length === 1) {
contextGlobalInstructionType = tagGlobalInstructionType
} else if (
xmlElementStack.length === 2 &&
(contextGlobalInstructionType ===
GlobalInstructionType.Function ||
contextGlobalInstructionType ===
GlobalInstructionType.Template) &&
isNativeElement &&
elementProperties.nativeName === 'param'
) {
collectParamName = true
}
if (isCloseTag) {
if (xmlElementStack.length > 0) {
xmlElementStack.pop()
}
}
storeToken = false
tokenChars = []
let newTokenType = isNativeElement
? XSLTokenLevelState.xslElementName
: XSLTokenLevelState.elementName
this.addNewTokenToResult(
tokenStartChar,
newTokenType,
result,
nextState
)
if (nextState !== XMLCharState.lsElementNameWs) {
let punctuationLength =
nextState === XMLCharState.rCt ||
nextState === XMLCharState.rStNoAtt
? 1
: 2
this.addCharTokenToResult(
this.lineCharCount - 1,
punctuationLength,
XSLTokenLevelState.xmlPunctuation,
result,
nextState
)
}
break
case XMLCharState.rDtd:
this.addCharTokenToResult(
tokenStartChar,
this.lineCharCount - tokenStartChar,
XSLTokenLevelState.dtdEnd,
result,
currentState
)
break
case XMLCharState.rPiName:
this.addNewTokenToResult(
tokenStartChar,
XSLTokenLevelState.processingInstrName,
result,
nextState
)
break
case XMLCharState.rPi:
this.addNewTokenToResult(
tokenStartChar,
XSLTokenLevelState.processingInstrValue,
result,
currentState
)
this.addCharTokenToResult(
this.lineCharCount - 1,
2,
XSLTokenLevelState.xmlPunctuation,
result,
nextState
)
break
case XMLCharState.rPiNameOnly:
this.addNewTokenToResult(
tokenStartChar,
XSLTokenLevelState.processingInstrName,
result,
currentState
)
this.addCharTokenToResult(
this.lineCharCount - 1,
2,
XSLTokenLevelState.xmlPunctuation,
result,
nextState
)
break
case XMLCharState.rComment:
let startChar = tokenStartChar > 0 ? tokenStartChar - 2 : 0
this.addNewTokenToResult(
startChar,
XSLTokenLevelState.xmlComment,
result,
nextState
)
break
case XMLCharState.wsAfterAttName:
case XMLCharState.syntaxError:
storeToken = false
this.addNewTokenToResult(
tokenStartChar,
XSLTokenLevelState.attributeName,
result,
nextState
)
attributeNameTokenAdded = true
break
case XMLCharState.lAn:
tokenChars.push(currentChar)
storeToken = true
attributeNameTokenAdded = false
break
case XMLCharState.lStEq:
let isXMLNSattribute = false
isGlobalInstructionName = false
isGlobalInstructionMode = false
isGlobalParameterName = false
isGlobalInstructionMatch = false
isGlobalUsePackageVersion = false
isGlobalVersion = false
attName = tokenChars.join('')
let attributeNameToken = XSLTokenLevelState.attributeName
if (isNativeElement) {
// if (attName === 'as') {
// isXPathAttribute = true;
// }
if (attName === 'saxon:options') {
isXPathAttribute = true
} else if (this.genericTvtAttributes.indexOf(attName) > -1) {
isXPathAttribute = false
isExpandTextAttribute = true
} else if (attName.startsWith('xmlns')) {
isExpandTextAttribute = false
isXMLNSattribute = true
attributeNameToken = XSLTokenLevelState.xmlnsName
} else if (
tagGlobalInstructionType === GlobalInstructionType.RootXSLT &&
attName === 'version'
) {
isGlobalVersion = true
} else if (
tagGlobalInstructionType !== GlobalInstructionType.Unknown &&
attName === 'name'
) {
isExpandTextAttribute = false
isGlobalInstructionName = true
} else if (
(tagGlobalInstructionType ===
GlobalInstructionType.Template &&
attName === 'mode') ||
(tagGlobalInstructionType === GlobalInstructionType.Mode &&
attName === 'name')
) {
isExpandTextAttribute = false
isGlobalInstructionMode = true
} else if (
tagGlobalInstructionType === GlobalInstructionType.Template &&
attName === 'match'
) {
isExpandTextAttribute = false
isGlobalInstructionMatch = true
isXPathAttribute = true
} else if (collectParamName && attName === 'name') {
isGlobalParameterName = true
} else if (
contextGlobalInstructionType ===
GlobalInstructionType.UsePackage &&
attName === 'package-version'
) {
isExpandTextAttribute = false
isGlobalUsePackageVersion = true
} else {
isExpandTextAttribute = false
isXPathAttribute = this.isExpressionAtt(attName)
}
} else {
if (this.nativeTvtAttributes.indexOf(attName) > -1) {
isExpandTextAttribute = true
} else if (attName.startsWith('xmlns')) {
isExpandTextAttribute = false
isXMLNSattribute = true
attributeNameToken = XSLTokenLevelState.xmlnsName
} else {
isExpandTextAttribute = false
}
}
if (!attributeNameTokenAdded) {
this.addNewTokenToResult(
tokenStartChar,
attributeNameToken,
result,
nextState
)
} else if (isXMLNSattribute) {
result[result.length - 1].tokenType = attributeNameToken
}
this.addCharTokenToResult(
this.lineCharCount - 1,
1,
XSLTokenLevelState.attributeEquals,
result,
nextState
)
tokenChars = []
storeToken = false
break
case XMLCharState.rSt:
if (
tagGlobalInstructionType === GlobalInstructionType.Template &&
!tagInstructionNameAdded &&
tagMatchToken
) {
this.globalInstructionData.push({
type: GlobalInstructionType.TemplateMatch,
name: `${tagMatchToken.value}#${this.globalInstructionData.length}`,
token: tagMatchToken,
idNumber: 0,
})
}
expandTextValue = this.addToElementStack(
expandTextValue,
xmlElementStack
)
this.addCharTokenToResult(
this.lineCharCount - 1,
1,
XSLTokenLevelState.xmlPunctuation,
result,
nextState
)
storeToken = false
tokenChars = []
break
case XMLCharState.lCt:
case XMLCharState.lPi:
this.addCharTokenToResult(
this.lineCharCount - 1,
2,
XSLTokenLevelState.xmlPunctuation,
result,
nextState
)
break
case XMLCharState.rSelfCt:
this.addCharTokenToResult(
this.lineCharCount - 1,
2,
XSLTokenLevelState.xmlPunctuation,
result,
nextState
)
break
case XMLCharState.lCt2:
break
case XMLCharState.rSq:
case XMLCharState.rDq:
case XMLCharState.escDqAvt:
case XMLCharState.escSqAvt:
if (isExpandTextAttribute) {
let attValue = tokenChars.join('')
expandTextValue =
attValue === 'yes' || attValue === 'true' || attValue === '1'
}
if (xpathEnded) {
tokenStartChar++
xpathEnded = false
}
let newToken = this.addNewTokenToResult(
tokenStartChar,
XSLTokenLevelState.attributeValue,
result,
nextState
)
if (isGlobalInstructionName || isGlobalInstructionMode) {
let attValue = tokenChars.join('')
let newTokenCopy = Object.assign({}, newToken)
let globalType = isGlobalInstructionMode
? GlobalInstructionType.Mode
: tagGlobalInstructionType
let targetGlobal
if (isGlobalInstructionMode) {
targetGlobal = this.globalModeData
} else {
targetGlobal = this.globalInstructionData
tagInstructionNameAdded = true
}
const idNumber =
globalType === GlobalInstructionType.Variable
? result.length
: 0
targetGlobal.push({
type: globalType,
name: attValue,
token: newTokenCopy,
idNumber: idNumber,
})
} else if (isGlobalParameterName) {
let attValue = tokenChars.join('')
if (this.globalInstructionData.length > 0) {
let gd =
this.globalInstructionData[
this.globalInstructionData.length - 1
]
if (gd.memberNames) {
if (gd.memberNames.indexOf(attValue) > -1) {
newToken['error'] = ErrorType.DuplicateParameterName
newToken.value = attValue
}
gd.memberNames.push(attValue)
gd.memberTokens?.push({ ...newToken })
} else {
gd['memberNames'] = [attValue]
gd['memberTokens'] = [{ ...newToken }]
}
gd.idNumber++
}
} else if (isGlobalUsePackageVersion) {
let attValue = tokenChars.join('')
if (this.globalInstructionData.length > 0) {
let gd =
this.globalInstructionData[
this.globalInstructionData.length - 1
]
gd['version'] = attValue
}
} else if (isGlobalInstructionMatch) {
let attValue = tokenChars.join('')
tagMatchToken = newToken
tagMatchToken.value = attValue
} else if (isGlobalVersion) {
isGlobalVersion = false
}
tokenChars = []
storeToken = false
break
case XMLCharState.lSq:
case XMLCharState.lDq:
if (
contextGlobalInstructionType ===
GlobalInstructionType.Function ||
contextGlobalInstructionType ===
GlobalInstructionType.Template ||
contextGlobalInstructionType ===
GlobalInstructionType.UsePackage ||
tagGlobalInstructionType === GlobalInstructionType.RootXSLT
) {
storeToken = true
}
if (
isExpandTextAttribute ||
isGlobalInstructionName ||
isGlobalInstructionMode
) {
storeToken = true
} else if (isXPathAttribute) {
this.addCharTokenToResult(
this.lineCharCount - 1,
1,
XSLTokenLevelState.attributeValue,
result,
nextState
)
let p: LexPosition = {
line: this.lineNumber,
startCharacter: this.lineCharCount,
documentOffset: this.charCount,
}
let exit: ExitCondition
if (nextState === XMLCharState.lSq) {
exit = ExitCondition.SingleQuote
} else {
exit = ExitCondition.DoubleQuote
}
xpLexer.analyse('', exit, p)
this.updateNames(result)
// need to process right double-quote/single-quote
this.lineNumber = p.line
let newCharCount = p.documentOffset - 1
if (newCharCount > this.charCount) {
this.charCount = newCharCount
}
xpathEnded = true
this.lineCharCount = p.startCharacter
nextChar = xsl.charAt(this.charCount)
isXPathAttribute = false
}
break
case XMLCharState.sqAvt:
case XMLCharState.dqAvt:
let exit
if (isNativeElement) {
if ((exit = attName.startsWith('_'))) {
exit = ExitCondition.CurlyBrace
} else {
exit = this.isAvtAtt(attName)
? ExitCondition.CurlyBrace
: ExitCondition.None
}
} else if (this.nonNativeAvts) {
exit = ExitCondition.CurlyBrace
} else {
exit = ExitCondition.None
}
if (exit !== ExitCondition.None) {
this.addNewTokenToResult(
tokenStartChar,
XSLTokenLevelState.attributeValue,
result,
nextState
)
let p: LexPosition = {
line: this.lineNumber,
startCharacter: this.lineCharCount,
documentOffset: this.charCount,
}
xpLexer.analyse('', exit, p)
this.updateNames(result)
// need to process right double-quote
this.lineNumber = p.line
let newCharCount = p.documentOffset - 1
if (newCharCount > this.charCount) {
this.charCount = newCharCount
}
// fix issue with last char of xpath
this.lineCharCount = p.startCharacter
nextChar = xsl.charAt(this.charCount)
avtExit = true
}
nextState =
nextState === XMLCharState.sqAvt
? XMLCharState.lSq
: XMLCharState.lDq
break
case XMLCharState.tvt:
this.addCharTokenToResult(
this.lineCharCount - 1,
1,
XSLTokenLevelState.xmlText,
result,
currentState
)
case XMLCharState.tvtCdata:
let useTvt =
xmlElementStack.length > 0 &&
xmlElementStack[xmlElementStack.length - 1].expandText
if (useTvt) {
let p: LexPosition = {
line: this.lineNumber,
startCharacter: this.lineCharCount,
documentOffset: this.charCount,
}
xpLexer.analyse('', ExitCondition.CurlyBrace, p)
this.updateNames(result)
// need to process right double-quote
this.lineNumber = p.line
let newCharCount = p.documentOffset - 1
if (newCharCount > this.charCount) {
this.charCount = newCharCount
}
this.lineCharCount = p.startCharacter
nextChar = xsl.charAt(this.charCount)
if (nextState === XMLCharState.tvtCdata) {
nextState = XMLCharState.awaitingRcdata
} else {
nextState = XMLCharState.init
}
} else if (nextState === XMLCharState.tvtCdata) {
nextState = XMLCharState.awaitingRcdata
}
break
case XMLCharState.lEntity:
if (this.entityContext !== EntityPosition.text) {
this.addCharTokenToResult(
tokenStartChar,
this.lineCharCount - 1 - tokenStartChar,
XSLTokenLevelState.attributeValue,
result,
nextState
)
}
break
case XMLCharState.rEntity:
this.addCharTokenToResult(
tokenStartChar,
this.lineCharCount - tokenStartChar,
XSLTokenLevelState.entityRef,
result,
nextState
)
switch (this.entityContext) {
case EntityPosition.text:
nextState = XMLCharState.init
break
case EntityPosition.attrSq:
nextState = XMLCharState.lSq
avtExit = true
break
case EntityPosition.attrDq:
nextState = XMLCharState.lDq
avtExit = true
break
}
break
case XMLCharState.lCdataEnd:
this.addCharTokenToResult(
tokenStartChar - 2,
9,
XSLTokenLevelState.xmlPunctuation,
result,
nextState
)
break
case XMLCharState.rCdataEnd:
this.addCharTokenToResult(
tokenStartChar,
3,
XSLTokenLevelState.xmlPunctuation,
result,
nextState
)
break
}
tokenStartChar = this.lineCharCount > 0 ? this.lineCharCount - 1 : 0
if (avtExit) {
avtExit = false
tokenStartChar++
}
tokenStartLine = this.lineNumber
} // else ends
if (isCurrentCharNewLIne) {
tokenStartChar = 0
tokenStartLine = 0
this.lineNumber++
this.lineCharCount = 0
}
currentState = nextState
}
currentChar = nextChar
if (
isLastChar &&
resultLengthAtLastChar === result.length &&
!(nextState === XMLCharState.lWs || nextState === XMLCharState.lsEqWs)
) {
let alreadyDone =
result.length > 0 &&
result[result.length - 1].startCharacter === tokenStartChar
if (!alreadyDone) {
this.addCharTokenToResult(
tokenStartChar,
this.lineCharCount - tokenStartChar,
XSLTokenLevelState.xmlText,
result,
currentState
)
}
}
}
if (this.timerOn) {
console.timeEnd('xslLexer.analyse')
}
this.globalInstructionData = this.globalInstructionData.concat(
this.globalModeData
)
return result
}