import * as vscode from 'vscode' ; import { XslLexer , LanguageConfiguration , GlobalInstructionData , GlobalInstructionType , DocumentTypes } from './xslLexer' ; import { XsltTokenDiagnostics } from './xsltTokenDiagnostics' ; import { GlobalsProvider } from './globalsProvider' ; import * as path from 'path' ; import { exit } from 'process' ; import { DocumentChangeHandler } from './documentChangeHandler' ; import * as url from 'url' ; import { BaseToken , CharLevelState , ExitCondition , LexPosition , TokenLevelState , XPathLexer } from './xpLexer' ; import { ElementData , VariableData , XPathData , XsltTokenCompletions } from './xsltTokenCompletions' ; interface ImportedGlobals { href : string ; data : GlobalInstructionData [ ] ; error : boolean ; } interface Cleaned { cleanedTokens : TokenWithUnion [ ] ; hasParentAxis : boolean ; } interface GlobalsSummary { globals : ImportedGlobals [ ] ; hrefs : string [ ] ; } export interface XsltPackage { name : string ; path : string ; version ? : string ; } export enum SelectionType { Current , Next , Previous , Parent , FirstChild } enum AxisType { Attribute , Self , Child , Descendant , DescendantOrSelf , Parent , ParentKeep , Ancestor , AncestorOrSelf , FollowingSibling , PrecedingSibling , Union } interface SymbolWithParent extends vscode . DocumentSymbol { parent ? : vscode . DocumentSymbol ; isAttr ? : boolean ; } interface TokenWithUnion extends BaseToken { union ? : boolean ; } type possDocumentSymbol = vscode . DocumentSymbol | null ; type Entry = [ string , number ] ; enum UseSource { none = "none" , latest = "latest" } export class XsltSymbolProvider implements vscode . DocumentSymbolProvider { private readonly xslLexer : XslLexer ; private readonly collection : vscode . DiagnosticCollection | null ; private readonly languageConfig : LanguageConfiguration ; private docType : DocumentTypes ; public static documentSymbols = new Map < vscode . Uri , vscode . DocumentSymbol [ ] > ( ) ; private importHrefs : Map < string , string [ ] > = new Map ( ) ; public static importSymbolHrefs : Map < string , string [ ] > = new Map ( ) ; private static readonly useSourceFile : UseSource = < UseSource > vscode . workspace . getConfiguration ( 'XSLT.resources' ) . get ( 'useSourceFile' ) ; public constructor ( xsltConfiguration , collection ) { this . xslLexer = new XslLexer ( xsltConfiguration ) ; this . xslLexer . provideCharLevelState = true ; this . collection = collection ; this . languageConfig = xsltConfiguration ; this . docType = xsltConfiguration . docType ; } public static getSymbolsForActiveDocument ( ) { if ( vscode . window . activeTextEditor ) { const result = XsltSymbolProvider . documentSymbols . get ( vscode . window . activeTextEditor . document . uri ) ; return result ? result : [ ] ; } else { return [ ] ; } } public async provideDocumentSymbols ( document , token ) { const result = this . getDocumentSymbols ( document , false ) ; return result ; } private static findMatchingParent ( map , path ) { let matchingKey ; for ( const entry of map . entries ( ) ) { if ( entry [ 1 ] . find ( ( x ) => x === path ) ) { matchingKey = entry [ 0 ] ; } } return matchingKey ; } public async getDocumentSymbols ( document , onlyActiveDocument ) { if ( onlyActiveDocument && vscode . window . activeTextEditor ) { if ( document . fileName !== vscode . window . activeTextEditor . document . fileName ) { return [ ] ; } } const allTokens = this . xslLexer . analyse ( document . getText ( ) ) ; const globalInstructionData = this . xslLexer . globalInstructionData ; const xsltPackages = < XsltPackage [ ] > vscode . workspace . getConfiguration ( 'XSLT.resources' ) . get ( 'xsltPackages' ) ; const localImportedHrefs = XsltSymbolProvider . importSymbolHrefs ; let { importedGlobals1 , accumulatedHrefs } = await XsltSymbolProvider . processTopLevelImports ( true , this . xslLexer , localImportedHrefs , document , globalInstructionData , xsltPackages ) ; let topLevelHrefs = XsltSymbolProvider . accumulateImportHrefs ( xsltPackages , importedGlobals1 , [ ] ) ; let globalsSummary0 = { globals : importedGlobals1 , hrefs : accumulatedHrefs } ; const maxImportLevel = 20 ; let processNestedGlobals = async ( ) => { let level = 0 ; while ( globalsSummary0 . hrefs . length > 0 && level < maxImportLevel ) { globalsSummary0 = await XsltSymbolProvider . processImportedGlobals ( xsltPackages , globalsSummary0 . globals , accumulatedHrefs , level === 0 ) ; level ++ ; } } ; await processNestedGlobals ( ) ; return new Promise ( ( resolve , reject ) => { let symbols = [ ] ; let allImportedGlobals = [ ] ; let importErrors = [ ] ; const rootPath = vscode . workspace . rootPath ; globalsSummary0 . globals . forEach ( ( globals ) => { if ( globals . error ) { if ( topLevelHrefs . indexOf ( globals . href ) > - 1 ) { let errorData = globalInstructionData . find ( ( dataObject ) => { let result = false ; if ( dataObject . type === GlobalInstructionType . Import || dataObject . type === GlobalInstructionType . Include ) { let resolvedName = XsltSymbolProvider . resolvePath ( dataObject . name , document . fileName ) ; result = resolvedName === globals . href ; } else if ( dataObject . type === GlobalInstructionType . UsePackage ) { const basePath = path . dirname ( document . fileName ) ; let packageLookup = xsltPackages . find ( ( pkg ) => { return pkg . name === dataObject . name ; } ) ; if ( packageLookup && rootPath ) { let resolvedName = XsltSymbolProvider . resolvePathInSettings ( packageLookup . path , rootPath ) ; result = resolvedName === globals . href ; } else { result = false ; } } return result ; } ) ; if ( errorData ) { importErrors . push ( errorData ) ; } } } else { globals . data . forEach ( ( global ) => { global [ 'href' ] = globals . href ; allImportedGlobals . push ( global ) ; } ) ; } } ) ; let diagnostics = XsltTokenDiagnostics . calculateDiagnostics ( this . languageConfig , this . docType , document , allTokens , globalInstructionData , allImportedGlobals , symbols ) ; if ( vscode . window . activeTextEditor && document . fileName !== vscode . window . activeTextEditor . document . fileName ) { } else { if ( this . collection && this . docType !== DocumentTypes . SCH ) { let importDiagnostics = [ ] ; importErrors . forEach ( ( importError ) => { importDiagnostics . push ( XsltTokenDiagnostics . createImportDiagnostic ( importError ) ) ; } ) ; let allDiagnostics = importDiagnostics . concat ( diagnostics ) ; if ( allDiagnostics . length > 0 ) { this . collection . set ( document . uri , allDiagnostics ) ; } else { this . collection . clear ( ) ; } ; } } XsltSymbolProvider . documentSymbols . set ( document . uri , symbols ) ; resolve ( symbols ) ; } ) ; } public static async processTopLevelImports ( update , xslLexer , localImportedHrefs , document , globalInstructionData , xsltPackages ) { const matchingParent = this . findMatchingParent ( localImportedHrefs , document . fileName ) ; let importedGlobals1 = [ ] ; let accumulatedHrefs ; if ( matchingParent ) { const token = { line : 1 , startCharacter : 0 , length : matchingParent ?. length , value : matchingParent , tokenType : 0 } ; const importInstruction = { type : GlobalInstructionType . Import , name : matchingParent , token : token , idNumber : 0 } ; globalInstructionData . push ( importInstruction ) ; } const importedG = { data : globalInstructionData , href : document . fileName , error : false } ; accumulatedHrefs = [ importedG . href ] ; importedGlobals1 = [ importedG ] ; const inheritHrefs = this . getImportHrefs ( xsltPackages , importedGlobals1 ) ; if ( update ) { if ( matchingParent ) { const newInheritHres = localImportedHrefs . get ( matchingParent ) ?. concat ( inheritHrefs ) ; if ( newInheritHres ) { localImportedHrefs . set ( matchingParent , newInheritHres ) ; } } else { localImportedHrefs . set ( document . fileName , inheritHrefs ) ; } } return { importedGlobals1 , accumulatedHrefs } ; } public static selectTextWithSymbol ( symbol ) { if ( DocumentChangeHandler . lastActiveXMLEditor && symbol ) { const range = symbol . range ; if ( vscode . window . activeTextEditor ) { vscode . window . activeTextEditor . revealRange ( range ) ; } DocumentChangeHandler . lastActiveXMLEditor . selection = new vscode . Selection ( range . start , range . end ) ; } } public static selectXMLElement ( selectionType ) { const selection = vscode . window . activeTextEditor ?. selection ; if ( vscode . window . activeTextEditor && selection ) { const rootSymbol = XsltSymbolProvider . getSymbolsForActiveDocument ( ) [ 0 ] ; const path = [ ] ; const result = this . getChildSymbolForSelection ( selection , rootSymbol , path , selectionType , null , null , null ) ; if ( result !== null ) { XsltSymbolProvider . selectTextWithSymbol ( result ) ; } } } public static getXPathFromSelection ( ) { const editor = vscode . window . activeTextEditor ; if ( editor ) { const selection = editor . selection ; const rootSymbol = XsltSymbolProvider . getSymbolsForActiveDocument ( ) [ 0 ] ; const newPath = [ '/' + rootSymbol . name . split ( ' ' ) [ 0 ] ] ; const result = this . getChildSymbolForSelection ( selection , rootSymbol , newPath , SelectionType . Current , null , null , null ) ; const fullPath = newPath . join ( '' ) ; return fullPath ; } } private static getChildSymbolForSelection ( selection , symbol , path , selectionType , parentSymbol , precedingSymbol , nextSymbol ) { const result = symbol . children . find ( ( sym ) => { const selectionPos = new vscode . Position ( selection . start . line , selection . start . character ) ; return sym . range . contains ( selectionPos ) ; } ) ; if ( result ) { const resultName = result . name . split ( ' ' ) [ 0 ] ; let nextSibling = null ; let precedingSibling = null ; let firstChild = null ; let precedingSymbolNames = 1 ; let breakNext = false ; for ( const sibling of symbol . children ) { if ( breakNext ) { nextSibling = sibling ; break ; } if ( sibling === result ) { if ( selectionType === SelectionType . Next ) { breakNext = true ; } else { break ; } } else { precedingSibling = sibling ; const siblingName = sibling . name . split ( ' ' ) [ 0 ] ; if ( siblingName === resultName ) { precedingSymbolNames ++ ; } } } if ( symbol . kind === vscode . SymbolKind . Array && symbol . name === 'attributes' ) { path . push ( '/@' + resultName ) ; } else if ( ! ( result . kind === vscode . SymbolKind . Array && result . name === 'attributes' ) ) { path . push ( '/' + resultName + `[${ precedingSymbolNames }]` ) ; } return this . getChildSymbolForSelection ( selection , result , path , selectionType , symbol , precedingSibling , nextSibling ) ; } else if ( selectionType === SelectionType . Parent ) { return parentSymbol ; } else if ( selectionType === SelectionType . Previous ) { return precedingSymbol ; } else if ( selectionType === SelectionType . Next ) { return nextSymbol ; } else if ( selectionType === SelectionType . FirstChild ) { const firstChild = symbol . children . length > 0 ? symbol . children [ 0 ] : null ; if ( firstChild ) { if ( firstChild . kind === vscode . SymbolKind . Array && firstChild . name === 'attributes' ) { return symbol . children . length > 1 ? symbol . children [ 1 ] : null ; } else { return firstChild ; } } else { return null ; } } else { return symbol ; } } public static getCompletionNodeNames ( allTokens , globalInstructionData , xsltVariables , xpathVariables , index , xpathStack , symbols , eNames , aNames ) { if ( this . useSourceFile === UseSource . none ) { return [ eNames , aNames ] ; } const { cleanedTokens , hasParentAxis } = XsltSymbolProvider . filterPathTokens ( allTokens , globalInstructionData , xsltVariables , xpathVariables , index , xpathStack ) ; const result = XsltSymbolProvider . getExpectedForXPathLocation ( cleanedTokens , symbols , hasParentAxis ) ; const [ elementNames , attrNames ] = result ; return result ; } public static filterPathTokens ( tokens , globalInstructionData , xsltVariables , xpathVariables , position , xpathStack ) { let cleanedTokens = [ ] ; const bracketTokens = [ ] ; let saveToken = false ; let nesting = 0 ; let exitLoop = false ; let finalSlashToken ; let xpathStacksChecked = 0 ; let lastTokenWasSlash = false ; let lastTokenWasSlash2 = false ; let hasParentAxis = false ; let foundVariableToken = false ; let isNextBracketUnion = false ; let collectBracketTokens = false ; for ( let i = position ; i > - 1 ; i -- ) { const token = tokens [ i ] ; const isXSLToken = token . tokenType >= XsltTokenCompletions . xsltStartTokenNumber ; let xpathCharType = < CharLevelState > token . charType ; let xpathTokenType = < TokenLevelState > token . tokenType ; if ( nesting > 0 ) { if ( xpathTokenType === TokenLevelState . operator && xpathCharType === CharLevelState . lPr ) { nesting -- ; } } else if ( collectBracketTokens ) { switch ( xpathTokenType ) { case TokenLevelState . operator : switch ( xpathCharType ) { case CharLevelState . lB : cleanedTokens = cleanedTokens . concat ( ... bracketTokens ) ; bracketTokens . length = 0 ; isNextBracketUnion = false ; collectBracketTokens = false ; break ; case CharLevelState . sep : exitLoop = ( token . value !== '|' && token . value !== ',' ) ; isNextBracketUnion = ! exitLoop ; break ; case CharLevelState . rPr : nesting ++ ; break ; default : exitLoop = true ; } break ; case TokenLevelState . nodeNameTest : if ( isNextBracketUnion ) { token . union = true ; } bracketTokens . push ( token ) ; break ; default : exitLoop = true ; break ; } } else { switch ( xpathTokenType ) { case TokenLevelState . operator : switch ( xpathCharType ) { case CharLevelState . rPr : nesting ++ ; break ; case CharLevelState . rB : collectBracketTokens = true ; break ; case CharLevelState . dSep : exitLoop = ( token . value !== '//' && token . value !== '::' && token . value !== '()' ) ; saveToken = token . value === '//' ; break ; case CharLevelState . sep : exitLoop = token . value !== '/' ; if ( ! exitLoop && i === 0 ) { cleanedTokens . push ( token ) ; } if ( ! exitLoop ) { finalSlashToken = token ; lastTokenWasSlash = true ; } break ; case CharLevelState . lPr : case CharLevelState . lB : xpathStacksChecked ++ ; break ; default : exitLoop = true ; break ; } break ; case TokenLevelState . nodeNameTest : case TokenLevelState . attributeNameTest : saveToken = true ; break ; case TokenLevelState . axisName : saveToken = token . value !== 'child' ; if ( ! hasParentAxis ) { hasParentAxis = [ 'parent' , 'ancestor' , 'ancestor-or-self' , 'following-sibling' , 'preceding-sibling' ] . indexOf ( token . value ) !== - 1 ; } break ; case TokenLevelState . nodeType : saveToken = [ '*' , '..' , 'node' , 'element' , 'item' ] . indexOf ( token . value ) !== - 1 ; if ( ! hasParentAxis ) { hasParentAxis = token . value === '..' ; } exitLoop = ! ( saveToken || token . value === '.' ) ; break ; case TokenLevelState . variable : foundVariableToken = true ; let xpv = xpathVariables . find ( v => v . token . value === token . value ) ; if ( ! xpv ) { const tv = token . value . substring ( 1 ) ; xpv = xsltVariables . find ( v => v . name === tv ) ; } if ( ! xpv ) { const tv = token . value . substring ( 1 ) ; const gd = globalInstructionData . find ( d => d . type === GlobalInstructionType . Variable && d . name === tv ) ; if ( gd ?. idNumber ) { xpv = { index : gd . idNumber , token : gd . token , name : gd . name } ; } } if ( xpv ) { const lastTokenIndex = XsltSymbolProvider . fetchXPathVariableTokens ( tokens , xpv ) ; const result = XsltSymbolProvider . filterPathTokens ( tokens , globalInstructionData , xsltVariables , xpathVariables , lastTokenIndex , xpathStack ) ; if ( ! hasParentAxis ) { hasParentAxis = result . hasParentAxis ; } cleanedTokens = cleanedTokens . concat ( result . cleanedTokens ) ; } else { const varFromExtXPath = XsltTokenCompletions . extXPathVariables . get ( token . value . substring ( 1 ) ) ; if ( varFromExtXPath ) { const xpLexer = new XPathLexer ( ) ; const lexPosition = { line : 0 , startCharacter : 0 , documentOffset : 0 } ; const extTokens = xpLexer . analyse ( varFromExtXPath , ExitCondition . None , lexPosition ) ; const result = XsltSymbolProvider . filterPathTokens ( extTokens , globalInstructionData , xsltVariables , xpathVariables , extTokens . length - 1 , [ ] ) ; if ( ! hasParentAxis ) { hasParentAxis = result . hasParentAxis ; } cleanedTokens = cleanedTokens . concat ( result . cleanedTokens ) ; } else { cleanedTokens . push ( token ) ; exitLoop = true ; } } default : exitLoop = true ; break ; } } if ( foundVariableToken ) { break ; } else if ( exitLoop ) { exitLoop = false ; let predicateStart = - 1 ; const xpathStackEnd = xpathStack . length - ( xpathStacksChecked + 1 ) ; for ( let x = xpathStackEnd ; x > - 1 ; x -- ) { xpathStacksChecked ++ ; const item = xpathStack [ x ] ; if ( ( item . token . charType === CharLevelState . lPr || item . token . charType === CharLevelState . lB ) && item . tokenIndex ) { predicateStart = item . tokenIndex ; break ; } } if ( predicateStart === - 1 ) { if ( lastTokenWasSlash2 && finalSlashToken ) { cleanedTokens . push ( finalSlashToken ) ; } break ; } else { i = predicateStart ; } } else if ( nesting === 0 && saveToken && bracketTokens . length === 0 ) { cleanedTokens . push ( token ) ; } lastTokenWasSlash2 = lastTokenWasSlash ; lastTokenWasSlash = false ; saveToken = false ; } return { cleanedTokens , hasParentAxis } ; } static fetchXPathVariableTokens ( tokens , xpv ) { let result = - 1 ; let nesting = 0 ; let exitForLoop = false ; let onXsltTokensAtStart = false ; const startPosiiton = xpv . index + 2 ; for ( let index = startPosiiton ; index < tokens . length ; index ++ ) { const token = tokens [ index ] ; const isXSLToken = token . tokenType >= XsltTokenCompletions . xsltStartTokenNumber ; if ( isXSLToken ) { if ( index === startPosiiton ) { onXsltTokensAtStart = true ; } if ( onXsltTokensAtStart ) { result ++ ; continue ; } else { break ; } } else { onXsltTokensAtStart = false ; } let xpathCharType = < CharLevelState > token . charType ; let xpathTokenType = < TokenLevelState > token . tokenType ; switch ( xpathTokenType ) { case TokenLevelState . operator : if ( xpathCharType === CharLevelState . lPr ) { nesting ++ ; } else if ( xpathCharType === CharLevelState . rPr ) { nesting -- ; } else if ( nesting === 0 && xpathCharType === CharLevelState . sep && token . value === ',' ) { exitForLoop = true ; } break ; case TokenLevelState . complexExpression : if ( nesting === 0 ) { exitForLoop = true ; } break ; default : break ; } if ( exitForLoop ) { break ; } else { result ++ ; } } return result + startPosiiton ; } public static getExpectedForXPathLocation ( tokens , symbols , hasParentAxis ) { if ( symbols . length === 0 ) { return [ [ ] , [ ] ] ; } if ( tokens . length === 0 ) { const token = { line : 1 , startCharacter : 0 , length : 1 , value : '//' , tokenType : TokenLevelState . operator , charType : CharLevelState . dSep } ; tokens . push ( token ) ; } else { const tv = tokens [ tokens . length - 1 ] . value ; if ( ! ( tv === '/' || tv === '//' ) ) { const token = { line : 1 , startCharacter : 0 , length : 1 , value : '//' , tokenType : TokenLevelState . operator , charType : CharLevelState . dSep } ; tokens . push ( token ) ; } } const lastTokenIndex = tokens . length - 1 ; let currentSymbols = [ ] ; let isDocumentNode = false ; let nextAxis = AxisType . Child ; let isPathStart = true ; let isPathEnd = false ; let unionElementNames = [ ] ; for ( let i = lastTokenIndex ; i > - 1 ; i -- ) { const token = tokens [ i ] ; const tv = token . value ; let xpathCharType = < CharLevelState > token . charType ; let xpathTokenType = < TokenLevelState > token . tokenType ; let currentAxis = nextAxis ; isPathEnd = i === 0 ; nextAxis = AxisType . Child ; let nextSymbols = [ ] ; let descendantAttrNames = [ ] ; switch ( xpathTokenType ) { case TokenLevelState . operator : switch ( xpathCharType ) { case CharLevelState . sep : if ( isPathStart ) { isDocumentNode = token . value === '/' ; nextSymbols = [ symbols [ 0 ] ] ; } break ; case CharLevelState . dSep : if ( token . value === '//' ) { nextSymbols = isPathStart ? [ symbols [ 0 ] ] : currentSymbols ; const descendantOrSelf = isPathStart ; const { outSymbols , attrNameArray } = XsltSymbolProvider . getDescendantSymbols ( nextSymbols , descendantOrSelf , hasParentAxis ) ; nextSymbols = outSymbols ; descendantAttrNames = attrNameArray ; nextAxis = AxisType . Descendant ; } } break ; case TokenLevelState . nodeNameTest : if ( isDocumentNode ) { isDocumentNode = false ; if ( currentSymbols [ 0 ] . name === token . value ) { nextSymbols = currentSymbols ; } } else if ( token . union ) { unionElementNames . push ( token . value ) ; nextSymbols = currentSymbols ; } else if ( currentAxis !== AxisType . Child && currentAxis !== AxisType . Parent ) { nextSymbols = currentSymbols . filter ( current => current . name === token . value ) ; } else { unionElementNames . push ( token . value ) ; for ( let i = 0 ; i < currentSymbols . length ; i ++ ) { const symbol = currentSymbols [ i ] ; if ( hasParentAxis ) { symbol . children . forEach ( ( child ) => { if ( child . kind !== vscode . SymbolKind . Array && unionElementNames . indexOf ( child . name ) !== - 1 ) { if ( ! child . parent ) { child . parent = symbol ; } nextSymbols . push ( child ) ; } } ) ; } else { const next = symbol . children . filter ( child => child . kind !== vscode . SymbolKind . Array && unionElementNames . indexOf ( child . name ) !== - 1 ) ; nextSymbols = nextSymbols . concat ( next ) ; } } unionElementNames . length = 0 ; } break ; case TokenLevelState . attributeNameTest : const attrTokenValue = token . value . substring ( 1 ) ; currentSymbols . forEach ( symbol => { const attrContainer = symbol . children . length > 0 ? symbol . children [ 0 ] : undefined ; if ( attrContainer && attrContainer . kind === vscode . SymbolKind . Array ) { const attributes = attrContainer . children ; attributes . forEach ( ( attr ) => { if ( attr . name === attrTokenValue ) { attr . isAttr = true ; if ( hasParentAxis ) { attr . parent = symbol ; } nextSymbols . push ( attr ) ; } } ) ; } } ) ; break ; case TokenLevelState . axisName : switch ( token . value ) { case 'self' : nextAxis = AxisType . Self ; nextSymbols = currentSymbols ; break ; case 'descendant-or-self' : case 'descendant' : nextSymbols = isPathStart ? [ symbols [ 0 ] ] : currentSymbols ; nextAxis = AxisType . Descendant ; if ( token . value === 'descendant-or-self' ) { nextAxis = AxisType . DescendantOrSelf ; } const { outSymbols , attrNameArray } = XsltSymbolProvider . getDescendantSymbols ( nextSymbols , nextAxis === AxisType . DescendantOrSelf , hasParentAxis ) ; nextSymbols = outSymbols ; descendantAttrNames = attrNameArray ; break ; case 'attribute' : currentSymbols . forEach ( symbol => { const attrContainer = symbol . children . length > 0 ? symbol . children [ 0 ] : undefined ; if ( attrContainer && attrContainer . kind === vscode . SymbolKind . Array ) { const attributes = attrContainer . children ; attributes . forEach ( ( attr ) => { attr . isAttr = true ; if ( hasParentAxis ) { attr . parent = symbol ; } nextSymbols . push ( attr ) ; } ) ; } } ) ; nextAxis = AxisType . Attribute ; break ; case 'parent' : currentSymbols . forEach ( symbol => { if ( symbol . parent ) nextSymbols . push ( symbol . parent ) ; } ) ; nextAxis = AxisType . ParentKeep ; break ; case 'following-sibling' : nextAxis = AxisType . FollowingSibling ; case 'preceding-sibling' : const isFollowing = nextAxis === AxisType . FollowingSibling ; currentSymbols . forEach ( symbol => { const sParent = symbol . parent ; if ( sParent ) { const pos = symbol . range . start ; sParent . children . forEach ( ( c ) => { if ( c . kind !== vscode . SymbolKind . Array && ( ( isFollowing && c . range . start . isAfter ( pos ) || ( ! isFollowing && c . range . start . isBefore ( pos ) ) ) ) ) { if ( hasParentAxis && ! c . parent ) { c . parent = sParent ; } nextSymbols . push ( c ) ; } } ) ; } } ) ; if ( ! isFollowing ) { nextAxis = AxisType . PrecedingSibling ; } break ; case 'ancestor-or-self' : nextAxis = AxisType . AncestorOrSelf ; case 'ancestor' : const includeSelf = nextAxis === AxisType . AncestorOrSelf ; currentSymbols . forEach ( symbol => { let s = symbol ; if ( includeSelf ) { nextSymbols . push ( s ) ; } while ( s ) { s = s . parent ; if ( s ) { nextSymbols . push ( s ) ; } } } ) ; if ( ! includeSelf ) { nextAxis = AxisType . Ancestor ; } break ; } break ; case TokenLevelState . nodeType : if ( isPathStart ) { currentSymbols = [ symbols [ 0 ] ] ; } if ( [ '*' , 'node' , 'element' , 'item' ] . indexOf ( token . value ) !== - 1 ) { if ( isDocumentNode ) { isDocumentNode = false ; nextSymbols = currentSymbols ; } else if ( currentAxis !== AxisType . Child && currentAxis !== AxisType . Parent ) { nextSymbols = currentSymbols ; } else { currentSymbols . forEach ( symbol => { if ( hasParentAxis ) { symbol . children . forEach ( ( child ) => { if ( child . kind !== vscode . SymbolKind . Array ) { if ( ! child . parent ) { child . parent = symbol ; } nextSymbols . push ( child ) ; } } ) ; } else { const elementChildren = symbol . children . filter ( child => child . kind !== vscode . SymbolKind . Array ) ; nextSymbols = nextSymbols . concat ( elementChildren ) ; } } ) ; } } else if ( token . value === '..' ) { currentSymbols . forEach ( symbol => { if ( symbol . parent ) nextSymbols . push ( symbol . parent ) ; } ) ; nextAxis = AxisType . Parent ; } break ; case TokenLevelState . variable : const descendantOrSelf = true ; nextSymbols = [ symbols [ 0 ] ] ; const { outSymbols , attrNameArray } = XsltSymbolProvider . getDescendantSymbols ( nextSymbols , descendantOrSelf , hasParentAxis ) ; nextSymbols = outSymbols ; descendantAttrNames = attrNameArray ; nextAxis = AxisType . DescendantOrSelf ; break ; case TokenLevelState . operator : break ; } currentSymbols = nextSymbols ; isPathStart = false ; if ( i === 0 ) { let elementNames = new Set < string > ( ) ; let attrNames = new Set < string > ( ) ; if ( isDocumentNode ) { elementNames . add ( currentSymbols [ 0 ] . name ) ; } else if ( nextAxis === AxisType . Attribute ) { currentSymbols . forEach ( current => { if ( current . isAttr ) { attrNames . add ( current . name ) ; } } ) ; } else if ( nextAxis !== AxisType . Child && nextAxis !== AxisType . Parent ) { currentSymbols . forEach ( current => { if ( current . isAttr ) { attrNames . add ( current . name ) ; } else { elementNames . add ( current . name ) ; } } ) ; } else { for ( let x = 0 ; x < currentSymbols . length ; x ++ ) { const curentSymbol = currentSymbols [ x ] ; curentSymbol . children . forEach ( child => { if ( child . kind === vscode . SymbolKind . Array && child . name === 'attributes' ) { child . children . forEach ( child => attrNames . add ( '@' + child . name ) ) ; } else { elementNames . add ( child . name ) ; } } ) ; } } const attrNameArray = ( nextAxis === AxisType . Descendant || nextAxis === AxisType . DescendantOrSelf ) ? descendantAttrNames : [ ... attrNames ] ; return [ [ ... elementNames ] , attrNameArray ] ; } } return [ [ ] , [ ] ] ; } private static getDescendantSymbols ( currentSymbols , descendantOrSelf , hasParentAxis , nodeName ? ) { let newSymbols = [ ] ; let parentSymbols = [ ] ; const isNameTest = nodeName !== undefined ; let outSymbols = descendantOrSelf ? [ ... currentSymbols ] : [ ] ; let attrNames = new Set < string > ( ) ; do { currentSymbols . forEach ( symbol => { symbol . children . forEach ( ( child ) => { if ( child . kind === vscode . SymbolKind . Array ) { child . children . forEach ( ( attr ) => { attrNames . add ( '@' + attr . name ) ; attr . isAttr = true ; if ( hasParentAxis ) { attr . parent = symbol ; } } ) ; } else { if ( hasParentAxis ) { child . parent = symbol ; } outSymbols . push ( child ) ; } } ) ; } ) ; newSymbols = newSymbols . concat ( outSymbols ) ; currentSymbols = outSymbols ; outSymbols = [ ] ; } while ( currentSymbols . length > 0 ) ; if ( isNameTest ) { outSymbols = newSymbols . filter ( symbol => symbol . name === nodeName ) ; } else { outSymbols = newSymbols ; } const attrNameArray = [ ... attrNames ] ; return { outSymbols , attrNameArray } ; } public static getSymbolFromXPathLocator ( rawText , symbols ) { const text = rawText . startsWith ( '/' ) ? rawText . substring ( 1 ) : '' ; const pathParts = text . split ( '/' ) ; if ( symbols . length === 0 ) { return ; } let currentSymbol = symbols [ 0 ] ; pathParts . forEach ( ( item , i ) => { const isLastItem = i === pathParts . length - 1 ; if ( currentSymbol === undefined ) { return ; } else if ( isLastItem && item . startsWith ( '@' ) && currentSymbol . children . length > 0 ) { const attrName = item . substring ( 1 ) ; const attributesSymbol = undefined ; const firstChild = currentSymbol . children [ 0 ] ; if ( firstChild . kind === vscode . SymbolKind . Array && firstChild . name === 'attributes' ) { currentSymbol = firstChild . children . find ( symbol => symbol . name === attrName ) ; } else { currentSymbol = undefined ; } return ; } const pos = item . indexOf ( '[' ) ; let pathName = '' ; let pathIndex = 1 ; if ( pos === - 1 ) { pathName = item ; } else { const predicateString = item . substring ( pos + 1 , item . length - 1 ) ; const predicateNum = Number ( predicateString ) ; pathIndex = - 1 ; if ( predicateNum !== NaN ) { pathIndex = predicateNum ; } pathName = item . substr ( 0 , pos ) ; } if ( i === 0 ) { const spacePos = currentSymbol . name . indexOf ( ' ' ) ; const symbolName = spacePos === - 1 ? currentSymbol . name : currentSymbol . name . substring ( 0 , spacePos ) ; currentSymbol = symbolName === pathName && pathIndex === 1 ? currentSymbol : undefined ; } else { let nameCount = 0 ; currentSymbol = currentSymbol . children . find ( ( symbol ) => { const spacePos = symbol . name . indexOf ( ' ' ) ; const symbolName = spacePos === - 1 ? symbol . name : symbol . name . substring ( 0 , spacePos ) ; if ( symbolName === pathName ) { nameCount ++ ; return pathIndex === nameCount ; } else { return false ; } } ) ; } } ) ; return currentSymbol ; } public static async processImportedGlobals ( xsltPackages , importedGlobals1 , level1Hrefs , topLevel ) { let level2Globals = [ ] ; let level2Hrefs = XsltSymbolProvider . accumulateImportHrefs ( xsltPackages , importedGlobals1 , level1Hrefs ) ; let newGlobals = [ ] ; level2Hrefs . forEach ( ( href ) => { level2Globals . push ( XsltSymbolProvider . fetchImportedGlobals ( [ href ] ) ) ; } ) ; let importedGlobals2Array = await Promise . all ( level2Globals ) ; importedGlobals2Array . forEach ( ( importedGlobals2 ) => { if ( topLevel ) { newGlobals = newGlobals . concat ( importedGlobals2 ) ; } else { importedGlobals1 = importedGlobals1 . concat ( importedGlobals2 ) ; } } ) ; if ( topLevel ) { return { globals : newGlobals , hrefs : level2Hrefs } ; } else { return { globals : importedGlobals1 , hrefs : level2Hrefs } ; } } public static accumulateImportHrefs ( xsltPackages , importedGlobals , existingHrefs ) { let result = [ ] ; const rootPath = vscode . workspace . rootPath ; importedGlobals . forEach ( ( importedG ) => { importedG . data . forEach ( ( data ) => { if ( data . type === GlobalInstructionType . Import || data . type === GlobalInstructionType . Include ) { let resolvedName = XsltSymbolProvider . resolvePath ( data . name , importedG . href ) ; if ( existingHrefs . indexOf ( resolvedName ) < 0 ) { existingHrefs . push ( resolvedName ) ; result . push ( resolvedName ) ; } } else if ( rootPath && data . type === GlobalInstructionType . UsePackage ) { let packageLookup = xsltPackages . find ( ( pkg ) => { return pkg . name === data . name ; } ) ; if ( packageLookup ) { let resolvedName = XsltSymbolProvider . resolvePathInSettings ( packageLookup . path , rootPath ) ; if ( existingHrefs . indexOf ( resolvedName ) < 0 ) { existingHrefs . push ( resolvedName ) ; result . push ( resolvedName ) ; } } } } ) ; } ) ; return result ; } private static getImportHrefs ( xsltPackages , importedGlobals ) { let result = [ ] ; const rootPath = vscode . workspace . rootPath ; importedGlobals . forEach ( ( importedG ) => { importedG . data . forEach ( ( data ) => { if ( data . type === GlobalInstructionType . Import || data . type === GlobalInstructionType . Include ) { let resolvedName = XsltSymbolProvider . resolvePath ( data . name , importedG . href ) ; result . push ( resolvedName ) ; } else if ( rootPath && data . type === GlobalInstructionType . UsePackage ) { let packageLookup = xsltPackages . find ( ( pkg ) => { return pkg . name === data . name ; } ) ; if ( packageLookup ) { let resolvedName = XsltSymbolProvider . resolvePathInSettings ( packageLookup . path , rootPath ) ; result . push ( resolvedName ) ; } } } ) ; } ) ; return result ; } public static resolvePath ( href , documentPath ) { if ( path . isAbsolute ( href ) ) { return href ; } else if ( href . startsWith ( 'file:///' ) ) { return href . substring ( 7 ) ; } else { href = href . startsWith ( 'file:' ) ? href . substring ( 5 ) : href ; let basePath = path . dirname ( documentPath ) ; let joinedPath = path . join ( basePath , href ) ; return path . normalize ( joinedPath ) ; } } public static resolvePathInSettings ( href , workspace ) { if ( path . isAbsolute ( href ) ) { return href ; } else { let joinedPath = path . join ( workspace , href ) ; return path . normalize ( joinedPath ) ; } } public static async fetchImportedGlobals ( inputHrefs ) { let result = [ ] ; let lastIndex = inputHrefs . length - 1 ; if ( lastIndex < 0 ) { return result ; } else { return new Promise ( ( resolve , reject ) => { inputHrefs . forEach ( ( href , index ) => { GlobalsProvider . provideGlobals ( href ) . then ( ( globals ) => { result . push ( { href : href , data : globals . data , error : globals . error } ) ; if ( index === lastIndex ) { resolve ( result ) ; } } ) ; } ) ; } ) ; } } }