public analyse()

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
  }