import * as fs from "fs" ; import * as path from "path" ; import { Diagnostic , DiagnosticSeverity , IConnection , PublishDiagnosticsParams , Range , TextDocument } from "vscode-languageserver" ; import * as xml2js from "xml2js" ; import * as xmlChecker from "xmlchecker" ; import { Log , LogLevel } from "../Log" ; import { getLine , getLineCount , getPositionFromIndex , getRange , Global } from "../server" ; import { IFoundAttribute , IFoundElementHeader , XmlBaseHandler , IXmlCheckerError , XmlStorage } from "../xmltypes" ; enum DiagnosticCodes { DoubleAttribute } interface IDiagnostic { diagnose ( doc ) : Promise < Diagnostic [ ] > ; } export class DiagnosticCollection { public diagnostics : { [ uri ] : PublishDiagnosticsParams ; } ; private delete ( uri ) { this . diagnostics = { } ; } private set ( uri , diag ) { if ( this . diagnostics [ uri ] ) this . diagnostics [ uri ] . diagnostics = this . diagnostics [ uri ] . diagnostics . concat ( diag ) ; else this . diagnostics [ uri ] . diagnostics = diag ; } } export class XmlWellFormedDiagnosticProvider extends Log implements IDiagnostic { private doc : TextDocument ; async diagnose ( doc ) { this . doc = doc ; let text = doc . getText ( ) ; let items = [ ] ; try { items = items . concat ( await this . diagXml2Js ( text ) ) ; } catch ( error ) { console . log ( error . toString ( ) ) ; } try { items = items . concat ( this . diagXmlChecker ( text ) ) ; } catch ( error ) { console . log ( error . toString ( ) ) ; } try { items = items . concat ( await this . getNamespaces ( text ) ) ; } catch ( error ) { console . log ( error . toString ( ) ) ; } return items ; } diagXmlChecker ( text ) { try { xmlChecker . check ( text ) ; return [ ] ; } catch ( error ) { let err = error as IXmlCheckerError ; err . line -- ; err . column -- ; console . log ( JSON . stringify ( error ) ) ; return [ { range : { start : { line : err . line , character : err . column } , end : { line : err . line , character : getLine ( text , ( err . line ) ) . length - 1 } } , severity : DiagnosticSeverity . Warning , message : err . message , source : "xmlLint" } ] ; } } diagXml2Js ( text ) { return new Promise < Diagnostic [ ] > ( ( resolve , reject ) => { xml2js . parseString ( text , { xmlns : true } , ( error , result ) => { if ( error ) { let errorlines = error . message . split ( "\n" ) ; error . message = errorlines [ 0 ] ; error . Line = Number ( errorlines [ 1 ] . match ( /\d+/ ) ) ; error . Column = Number ( errorlines [ 2 ] . match ( /\d+/ ) ) ; let char = errorlines [ 3 ] . split ( ":" ) [ 1 ] ; if ( char ) error . message += "Character: '" + char + "'" ; let x = { range : { start : { line : error . Line as number , character : error . Column as number } , end : { line : getLineCount ( text ) , character : getLine ( text , getLineCount ( text ) ) . length - 1 } } , message : error . message , severity : DiagnosticSeverity . Error } ; resolve ( [ x ] ) ; } else resolve ( [ ] ) ; } ) ; } ) ; } async getNamespaces ( text ) { let match ; let xmlnsregex = /xmlns:?(.*?)=['"](.*?)['"]/g ; let doc = text ; let hits = [ ] ; while ( match = xmlnsregex . exec ( doc ) ) { if ( Global . schemastore . schemas [ match [ 2 ] ] ) { hits . push ( ) ; } } return hits ; } } export class XmlAttributeDiagnosticProvider extends XmlBaseHandler implements IDiagnostic { private text : string ; private document : TextDocument ; constructor ( schemastorage , connection , logLevel , private diagnostics ? ) { super ( schemastorage , connection , logLevel ) ; if ( ! this . diagnostics ) this . diagnostics = [ ] ; } async diagnose ( doc ) { return new Promise < Diagnostic [ ] > ( ( resolve , reject ) => { try { this . document = doc ; this . text = doc . getText ( ) ; let baselement = this . textGetElements ( this . text ) ; this . checkAllElementsForAttributes ( baselement ) ; resolve ( this . diagnostics ) ; } catch ( error ) { this . logError ( "Could not diagnose Attributes: " + error . toString ( ) ) ; reject ( error ) ; } } ) ; } param { FoundElementHeader } element memberOf checkDoubleAttributes ( element ) { let doubles = { } ; this . logDebug ( "Checking " + ( element . attributes ? element . attributes . length : 0 ) + " attributes" ) ; for ( let attribute of element . attributes ) { if ( doubles [ attribute . name ] ) { this . logDebug ( ( ) => "Found double attribute '" + attribute . name + "'" ) ; this . diagnostics . push ( { code : DiagnosticCodes . DoubleAttribute , message : "Double attribute '" + attribute . name + "'" , range : { start : this . document . positionAt ( element . startindex + attribute . startpos ) , end : this . document . positionAt ( element . startindex + attribute . endpos ) } , severity : DiagnosticSeverity . Error , source : "xmllint" } ) ; } else { this . logDebug ( ( ) => "Added attribute '" + attribute . name + "' to the doubles dictionary" ) ; doubles [ attribute . name ] = attribute ; } } } param { FoundElementHeader } baseelement memberOf checkAllElementsForAttributes ( baseelement ) { this . checkDoubleAttributes ( baseelement ) ; if ( baseelement . children ) for ( let el of baseelement . children ) { this . logDebug ( "Checking element '" + el . fullName + "'" ) ; this . checkAllElementsForAttributes ( el ) ; } } }