import { Dictionary } from '../model' ; import { FilterParams } from '../provider' ; import { getUriLocalName } from '../utils' ; import { SparqlDataProviderSettings } from './sparqlDataProviderSettings' ; import { ElementBinding , LinkBinding , BlankBinding , FilterBinding , LinkCountBinding , ElementTypeBinding , SparqlResponse , RdfLiteral , isRdfIri , isRdfBlank , isBlankBinding , } from './sparqlModels' ; export const MAX_RECURSION_DEEP = 3 ; export const ENCODED_PREFIX = 'sparql-blank:' ; export const BLANK_NODE_QUERY_PARAMETERS = '?blankTrgProp ?blankTrg ?blankSrc ?blankSrcProp ?listHead' ; export const BLANK_NODE_QUERY = `
    OPTIONAL {
        FILTER (ISBLANK(?inst)).
        {
            ?inst ?blankTrgProp ?blankTrg.
            ?blankSrc ?blankSrcProp ?inst.
            FILTER NOT EXISTS { ?inst rdf:first _:smth1 }.
            BIND("blankNode" as ?blankType)
        } UNION {
            ?inst rdf:rest*/rdf:first ?blankTrg.
            ?blankSrc ?blankSrcProp ?inst.
            _:smth2 rdf:first ?blankTrg.
            BIND(?blankSrcProp as ?blankTrgProp)
            BIND("listHead" as ?blankType)
            FILTER NOT EXISTS { _:smth3 rdf:rest ?inst }.
        } UNION {
            ?listHead rdf:rest* ?inst.
            FILTER NOT EXISTS { _:smth4 rdf:rest ?listHead }.

            ?listHead rdf:rest*/rdf:first ?blankTrg.
            ?blankSrc ?blankSrcProp ?listHead.
            _:smth5 rdf:first ?blankTrg.
            BIND(?blankSrcProp as ?blankTrgProp)
            BIND("listHead" as ?blankType)
        }
    }
` ; export function isEncodedBlank ( id ) { return id . startsWith ( ENCODED_PREFIX ) ; } export class QueryExecutor { queryDictionary : Dictionary < Promise < SparqlResponse < BlankBinding > > > = { } ; constructor ( public queryFunction ) { } executeQuery ( query ) { const execution = this . queryDictionary [ query ] ; if ( execution ) { return execution ; } else { this . queryDictionary [ query ] = this . queryFunction ( query ) . then ( ( response ) => { delete this . queryDictionary [ query ] ; return response ; } ) ; return this . queryDictionary [ query ] ; } } } export function updateFilterResults ( result , queryFunction , settings ) { const completeBindings = [ ] ; const blankBindings = [ ] ; for ( const binding of result . results . bindings ) { if ( isBlankBinding ( binding ) ) { blankBindings . push ( binding ) ; } else { completeBindings . push ( binding ) ; } } return processBlankBindings ( blankBindings , ( callBackQuery ) => { return queryFunction ( callBackQuery ) ; } , settings ) . then ( ( processedBindings ) => { result . results . bindings = completeBindings . concat ( processedBindings ) ; return result ; } ) ; } export function processBlankBindings ( blankBindings , queryFunction , settings ) { const bindingGroupsById = { } ; for ( const binding of blankBindings ) { if ( binding . newInst ) { binding . inst = binding . newInst ; } if ( ! bindingGroupsById [ binding . inst . value ] ) { bindingGroupsById [ binding . inst . value ] = [ ] ; } bindingGroupsById [ binding . inst . value ] . push ( binding ) ; } const relatedBlankBindnings = [ ] ; for ( const b of blankBindings ) { if ( isRdfBlank ( b . blankTrg ) ) { relatedBlankBindnings . push ( [ b ] ) ; } } const queryExecutor = new QueryExecutor ( queryFunction ) ; return loadRelatedBlankNodes ( relatedBlankBindnings , queryExecutor , settings ) . then ( ( loadedGroupsById ) => { const idsMap = getEncodedIdDictionary ( loadedGroupsById ) ; const groups = Object . keys ( bindingGroupsById ) . map ( ( key ) => bindingGroupsById [ key ] ) ; for ( const group of groups ) { for ( const blankBinding of group ) { if ( ! blankBinding . label ) { blankBinding . label = createLabelForBlankBinding ( blankBinding ) ; } const encodedId4LoadedElement = idsMap [ blankBinding . blankTrg . value ] ; if ( encodedId4LoadedElement ) { blankBinding . blankTrg . value = encodedId4LoadedElement ; } } const encodedId = encodeId ( group ) ; updateGroupIds ( group , encodedId ) ; } return blankBindings ; } ) ; } function getEncodedIdDictionary ( blankBindingGroups ) { const idDictionary = { } ; const keys = Object . keys ( blankBindingGroups ) ; for ( const key of keys ) { idDictionary [ key ] = encodeId ( blankBindingGroups [ key ] ) ; updateGroupIds ( blankBindingGroups [ key ] , idDictionary [ key ] ) ; } return idDictionary ; } function updateGroupIds ( group , newId ) { for ( const loadedBlankBinding of group ) { loadedBlankBinding . inst . value = newId ; } } export function encodeId ( blankBindings ) { const bindingSet = { } ; for ( const binding of blankBindings ) { const { inst , ... exceptInst } = binding ; const encodedBinding = JSON . stringify ( exceptInst ) ; bindingSet [ encodedBinding ] = exceptInst as BlankBinding ; } const normalizedBindings = Object . keys ( bindingSet ) . sort ( ) . map ( ( key ) => bindingSet [ key ] ) ; return ENCODED_PREFIX + encodeURI ( JSON . stringify ( normalizedBindings ) ) ; } export function decodeId ( id ) { if ( ! isEncodedBlank ( id ) ) { return undefined ; } try { const clearId = id . substring ( ENCODED_PREFIX . length , id . length ) ; const parsedBindings = JSON . parse ( decodeURI ( clearId ) ) ; const bindings = parsedBindings . map ( ( binding ) => { binding . inst = { type : 'uri' , value : id } ; return binding ; } ) ; return bindings ; } catch ( error ) { return undefined ; } } export function createLabelForBlankBinding ( bn ) { if ( bn . blankType . value === 'listHead' ) { return { type : 'literal' , value : 'RDFList' , 'xml:lang' : '' , } ; } else { return { type : 'literal' , value : bn . class ? getUriLocalName ( bn . class . value ) || bn . class . value : 'anonymous' , 'xml:lang' : '' , } ; } } function loadRelatedBlankNodes ( blankChains , queryExecutor , settings , recursionDeep ? ) { recursionDeep = recursionDeep || 1 ; if ( recursionDeep > MAX_RECURSION_DEEP ) { return Promise . resolve ( { } ) ; } const queryPairs = blankChains . map ( ( chain ) => ( { query : getQueryForChain ( chain , settings ) , chain : chain , } ) ) ; const promises = queryPairs . map ( ( pair ) => queryExecutor . executeQuery ( pair . query ) . then ( ( response ) => ( { response : response , chain : pair . chain , } ) ) ) ; return Promise . all ( promises ) . then ( ( results ) => { const recursionPromises = [ ] ; const loadedBlankBindings = { } ; for ( const result of results ) { const bindings = result . response . results . bindings ; if ( bindings . length > 0 ) { const relatedBlankBindings = [ ] ; for ( const binding of bindings ) { if ( isRdfBlank ( binding . blankTrg ) ) { relatedBlankBindings . push ( result . chain . concat ( [ binding ] ) ) ; } } recursionPromises . push ( loadRelatedBlankNodes ( relatedBlankBindings , queryExecutor , settings , recursionDeep + 1 ) . then ( ( loadedGroupsById ) => { const idsMap = getEncodedIdDictionary ( loadedGroupsById ) ; const mergedResults = { } ; for ( const binding of bindings ) { binding . label = createLabelForBlankBinding ( binding ) ; const encodedId = idsMap [ binding . blankTrg . value ] ; if ( encodedId ) { binding . blankTrg . value = encodedId ; } if ( ! mergedResults [ binding . inst . value ] ) { mergedResults [ binding . inst . value ] = [ ] ; } mergedResults [ binding . inst . value ] . push ( binding ) ; } Object . keys ( mergedResults ) . forEach ( ( key ) => { const group = mergedResults [ key ] ; const originalId = group [ 0 ] . inst . value ; loadedBlankBindings [ originalId ] = group ; } ) ; return true ; } ) ) ; } } return Promise . all ( recursionPromises ) . then ( ( ) => { return loadedBlankBindings ; } ) ; } ) ; } function getQueryForChain ( blankNodes , sparqlDataProviderSettings ) { function getQueryBlock ( blankNode , index , maxIndex ) { const trustableTrgProp = index === 0 || blankNode . blankType . value !== 'listHead' ; const sourceId = index > 0 ? '?inst' + ( index - 1 ) : '<' + blankNode . blankSrc . value + '>' ; const sourcePropId = trustableTrgProp ? index > 0 ? '?blankTrgProp' + ( index - 1 ) : '<' + blankNode . blankSrcProp . value + '>' : '?anyType' + index ; const instPostfix = index === maxIndex ? '' : index . toString ( ) ; const targetPropId = trustableTrgProp ? '<' + blankNode . blankTrgProp . value + '>' : '?anyType0' + index ; const firstRelation = index === 0 && blankNode . blankType . value === 'listHead' ? `
            ?blankSrc${ index } rdf:rest*/rdf:first ?inst${ instPostfix }.
            ` : `?blankSrc${ index } ${ targetPropId } ?inst${ instPostfix }.` ; return `
        # ======================
        ${ sourceId } ${ sourcePropId } ?blankSrc${ index }.
        ${ firstRelation }
        BIND (<${ blankNode . blankTrgProp . value }> as ?blankSrcProp${ index }).
        FILTER (ISBLANK(?inst${ instPostfix })).
        {
            ?inst${ instPostfix } ?blankTrgProp${ instPostfix } ?blankTrg${ instPostfix }.
            BIND("blankNode" as ?blankType${ instPostfix }).
            FILTER NOT EXISTS { ?inst${ instPostfix } rdf:first _:smth1${ index } }.
        } UNION {
            ?inst${ instPostfix } rdf:rest*/rdf:first ?blankTrg${ instPostfix }.
            ?blankSrc${ index } ?blankSrcProp${ index } ?inst${ instPostfix }.
            _:smth2${ index } rdf:first ?blankTrg${ instPostfix }.
            BIND(?blankSrcProp${ index } as ?blankTrgProp${ instPostfix })
            BIND("listHead" as ?blankType${ instPostfix })
            FILTER NOT EXISTS { _:smth3${ index } rdf:rest ?inst${ instPostfix } }.
        }
        OPTIONAL {
            ?inst${ instPostfix } rdf:type ?class${ instPostfix }.
        }
    ` ; } const body = blankNodes . map ( ( bn , index ) => getQueryBlock ( bn , index , blankNodes . length - 1 ) ) . join ( '\n' ) ; const query = `${ sparqlDataProviderSettings . defaultPrefix }
    SELECT ?inst ?class ?label ?blankTrgProp ?blankTrg ?blankType
        WHERE {
           ${ body }
        }
    ` ; return query ; } export function elementInfo ( elementIds ) { const ids = elementIds . filter ( ( id ) => isEncodedBlank ( id ) ) ; return { head : undefined , results : { bindings : getElementBindings ( ids ) } , } ; } export function linksInfo ( elementIds ) { return { head : undefined , results : { bindings : getLinkBinding ( elementIds ) } , } ; } export function linkTypesOf ( params ) { return { head : undefined , results : { bindings : getLinkCountBinding ( params . elementId ) } , } ; } export function filter ( params ) { const filterResponse = { head : undefined , results : { bindings : [ ] } , } ; if ( params . limit === 0 ) { params . limit = 100 ; } if ( params . elementTypeId ) { filterResponse . results . bindings = [ ] ; } else if ( params . refElementId && params . refElementLinkId ) { filterResponse . results . bindings = getAllRelatedByLinkTypeElements ( params . refElementId , params . refElementLinkId , params . linkDirection ) ; } else if ( params . refElementId ) { filterResponse . results . bindings = getAllRelatedElements ( params . refElementId ) ; } if ( params . text && filterResponse . results . bindings . length !== 0 ) { filterResponse . results . bindings = filterResponse . results . bindings . filter ( ( be ) => be . inst . value . toLowerCase ( ) . indexOf ( params . text ) !== - 1 ) ; } return filterResponse ; } export function getElementTypes ( elementIds ) { const bindings = [ ] ; for ( const id of elementIds ) { const blankBindings = decodeId ( id ) ; if ( blankBindings ) { for ( const be of blankBindings ) { if ( isRdfIri ( be . inst ) && be . class ) { bindings . push ( { inst : be . inst , class : be . class } ) ; } } } } return { head : undefined , results : { bindings } } ; } function getAllRelatedByLinkTypeElements ( refElementId , refElementLinkId , linkDirection ) { const blankElements = ( decodeId ( refElementId ) || [ ] ) . concat ( decodeId ( refElementLinkId ) || [ ] ) ; let bindings = [ ] ; if ( blankElements . length > 0 ) { for ( const be of blankElements ) { if ( linkDirection === 'in' ) { if ( be . inst . value === refElementId && ( isRdfIri ( be . blankSrc ) || isRdfBlank ( be . blankSrc ) ) && refElementLinkId === be . blankSrcProp . value ) { if ( isRdfIri ( be . blankSrc ) ) { bindings . push ( { inst : be . blankSrc , } ) ; } else { bindings = bindings . concat ( decodeId ( be . blankSrc . value ) || [ { inst : be . blankSrc } ] ) ; } } else if ( be . blankTrg . value === refElementId && refElementLinkId === be . blankTrgProp . value ) { bindings . push ( be ) ; } } else { if ( be . inst . value === refElementId && ( isRdfIri ( be . blankTrg ) || isRdfBlank ( be . blankTrg ) ) && refElementLinkId === be . blankTrgProp . value ) { if ( isRdfIri ( be . blankTrg ) ) { bindings . push ( { inst : be . blankTrg , } ) ; } else { bindings = bindings . concat ( decodeId ( be . blankTrg . value ) || [ { inst : be . blankTrg } ] ) ; } } else if ( be . blankSrc . value === refElementId && refElementLinkId === be . blankSrcProp . value ) { bindings . push ( be ) ; } } } } return bindings ; } function getAllRelatedElements ( id ) { const blankElements = decodeId ( id ) ; let bindings = [ ] ; if ( blankElements ) { for ( const be of blankElements ) { if ( be . inst . value === id || id === be . blankSrc . value || id === be . blankTrg . value ) { bindings . push ( be ) ; if ( isRdfIri ( be . blankSrc ) ) { bindings . push ( { inst : be . blankSrc } ) ; } else if ( isRdfBlank ( be . blankSrc ) ) { bindings = bindings . concat ( decodeId ( be . blankSrc . value ) || [ { inst : be . blankSrc } ] ) ; } if ( isRdfIri ( be . blankTrg ) ) { bindings . push ( { inst : be . blankTrg } ) ; } else if ( isRdfBlank ( be . blankTrg ) ) { bindings = bindings . concat ( decodeId ( be . blankTrg . value ) || [ { inst : be . blankTrg } ] ) ; } } } } return bindings ; } function getElementBindings ( ids ) { let blankElements = [ ] ; for ( const id of ids ) { const blankBindings = decodeId ( id ) ; if ( blankBindings ) { blankElements = blankElements . concat ( decodeId ( id ) ) ; } } return blankElements . filter ( ( be ) => { return ids . indexOf ( be . inst . value ) !== - 1 ; } ) ; } function getLinkBinding ( ids ) { let blankElements = [ ] ; for ( const id of ids ) { const blankBindings = decodeId ( id ) ; if ( blankBindings ) { blankElements = blankElements . concat ( decodeId ( id ) ) ; } } const bindings = [ ] ; for ( const be of blankElements ) { if ( ids . indexOf ( be . inst . value ) !== - 1 ) { if ( ( isRdfIri ( be . blankSrc ) || isRdfBlank ( be . blankSrc ) ) && isRdfIri ( be . blankSrcProp ) && ids . indexOf ( be . blankSrc . value ) !== - 1 ) { bindings . push ( { source : be . blankSrc , type : be . blankSrcProp , target : be . inst , } ) ; } if ( ( isRdfIri ( be . blankTrg ) || isRdfBlank ( be . blankTrg ) ) && isRdfIri ( be . blankTrgProp ) && ids . indexOf ( be . blankTrg . value ) !== - 1 ) { bindings . push ( { source : be . inst , type : be . blankTrgProp , target : be . blankTrg , } ) ; } } } return bindings ; } function getLinkCountBinding ( id ) { const blankElements = decodeId ( id ) ; const bindings = [ ] ; const dictionary = { } ; for ( const be of blankElements ) { if ( id === be . inst . value ) { if ( ( isRdfIri ( be . blankTrg ) || isRdfBlank ( be . blankTrg ) ) && isRdfIri ( be . blankTrgProp ) ) { if ( ( isRdfIri ( be . blankSrc ) || isRdfBlank ( be . blankSrc ) ) && isRdfIri ( be . blankSrcProp ) ) { if ( ! dictionary [ be . blankSrcProp . value ] ) { dictionary [ be . blankSrcProp . value ] = { link : be . blankSrcProp , inCount : { type : 'literal' , value : '1' , 'xml:lang' : '' , } , outCount : { type : 'literal' , value : '0' , 'xml:lang' : '' , } , } ; } } if ( ! dictionary [ be . blankTrgProp . value ] ) { dictionary [ be . blankTrgProp . value ] = { link : be . blankTrgProp , inCount : { type : 'literal' , value : '0' , 'xml:lang' : '' , } , outCount : { type : 'literal' , value : '1' , 'xml:lang' : '' , } , } ; } else { dictionary [ be . blankTrgProp . value ] . outCount . value = ( + dictionary [ be . blankTrgProp . value ] . outCount . value + 1 ) . toString ( ) ; } } } } return Object . keys ( dictionary ) . map ( ( k ) => dictionary [ k ] ) ; }