diff --git a/nietzsche-beta-app/package.json b/nietzsche-beta-app/package.json index 68ddfa1..3485611 100644 --- a/nietzsche-beta-app/package.json +++ b/nietzsche-beta-app/package.json @@ -1,60 +1,60 @@ { "name": "nietzsche-app-beta", - "version": "0.7.1.3", + "version": "0.7.2.3", "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e", "compodoc": "./node_modules/.bin/compodoc -p tsconfig.app.json", "sparqljs": "./node_modules/sparqljs/sparql.js" }, "private": true, "dependencies": { "@angular/animations": "~8.2.14", "@angular/cdk": "~8.2.3", "@angular/common": "~8.2.14", "@angular/compiler": "~8.2.14", "@angular/core": "~8.2.14", "@angular/forms": "~8.2.14", "@angular/material": "^8.2.3", "@angular/platform-browser": "~8.2.14", "@angular/platform-browser-dynamic": "~8.2.14", "@angular/router": "~8.2.14", "@ctrl/ngx-codemirror": "^5.0.0", "@types/rdf-js": "^2.0.11", "codemirror": "^5.62.0", "lodash": "^4.17.20", "n3": "^1.10.0", "ngx-csv": "^0.3.2", "ngx-mat-standoff-markup": "^0.7.3", "rdfjs": "^0.0.1", "rxjs": "~6.4.0", "sparqljs": "^3.0.1", "tslib": "^1.10.0", "zone.js": "~0.9.1" }, "devDependencies": { "@angular-devkit/build-angular": "^0.803.25", "@angular/cli": "~8.3.24", "@angular/compiler-cli": "~8.2.14", "@angular/language-service": "~8.2.14", "@types/jasmine": "~3.3.8", "@types/jasminewd2": "~2.0.3", "@types/node": "~8.9.4", "codelyzer": "^5.0.0", "jasmine-core": "~3.4.0", "jasmine-spec-reporter": "~4.2.1", "karma": "~4.1.0", "karma-chrome-launcher": "~2.2.0", "karma-coverage-istanbul-reporter": "~2.0.1", "karma-jasmine": "~2.0.1", "karma-jasmine-html-reporter": "^1.4.0", "protractor": "~5.4.0", "ts-node": "~7.0.0", "tslint": "~5.15.0", "typescript": "~3.5.3" } } diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/cache.service.spec.ts b/nietzsche-beta-app/src/app/tln-edition/common/cache.service.spec.ts similarity index 100% rename from nietzsche-beta-app/src/app/tln-edition/tln-quant/cache.service.spec.ts rename to nietzsche-beta-app/src/app/tln-edition/common/cache.service.spec.ts diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/cache.service.ts b/nietzsche-beta-app/src/app/tln-edition/common/cache.service.ts similarity index 100% rename from nietzsche-beta-app/src/app/tln-edition/tln-quant/cache.service.ts rename to nietzsche-beta-app/src/app/tln-edition/common/cache.service.ts diff --git a/nietzsche-beta-app/src/app/tln-edition/common/paginator-result-status.ts b/nietzsche-beta-app/src/app/tln-edition/common/paginator-result-status.ts index bb041cf..2d38645 100644 --- a/nietzsche-beta-app/src/app/tln-edition/common/paginator-result-status.ts +++ b/nietzsche-beta-app/src/app/tln-edition/common/paginator-result-status.ts @@ -1,38 +1,38 @@ import {PageEvent} from '@angular/material/paginator'; import { FoundPage} from '../datatypes/search'; import { PageIndexUpdater } from '../models'; export interface ResultRange { start: number; end: number; } export class PaginatorResultStatus { resultLength: number; resultRange: ResultRange; resultIndex: number = 0; pageIndexUpdater: PageIndexUpdater; pageSizeOptions: number[] = [5, 10, 25, 100]; constructor(resultLength: number, pageIndexUpdater?: PageIndexUpdater){ this.resultLength = resultLength; this.resultRange = { start: 0, end: this.resultLength-1 }; this.pageIndexUpdater = pageIndexUpdater; } updateResultRange(index: number){ this.resultRange = null; this.resultIndex = index; let newStart = index*this.resultLength let newEnd = newStart+this.resultLength; this.resultRange = { start: newStart, end: newEnd }; - console.log(this.resultRange); + //console.log(this.resultRange); } showResults(event: PageEvent){ this.resultLength = event.pageSize; this.updateResultRange(event.pageIndex); if (this.pageIndexUpdater != null){ this.pageIndexUpdater.change.emit(event.pageIndex) } } } diff --git a/nietzsche-beta-app/src/app/tln-edition/datatypes/quant.ts b/nietzsche-beta-app/src/app/tln-edition/datatypes/quant.ts index aebb29a..b933cc5 100644 --- a/nietzsche-beta-app/src/app/tln-edition/datatypes/quant.ts +++ b/nietzsche-beta-app/src/app/tln-edition/datatypes/quant.ts @@ -1,251 +1,202 @@ import { Parser, Generator } from 'sparqljs'; import { BasicResultBindingElement, FusekiResults} from './basic_datatype'; import { ManuscriptPages } from './manuscript'; +import { QueryJson } from './query_json'; import { TextQuality } from '../models'; -export class QueryJson { - public static createFilterObject(filters: Object[] ): Object{ - let filterObject = { type: "filter", expression: null }; - if (filters.length > 1){ - filterObject.expression = { type: "operation", operator: "||", args: [] } - let filter = filters.pop() - this.pushFilter2Args(filters, filterObject.expression.args, filter); - } else { - filterObject.expression = filters[0]; - } - return filterObject; - } - public static createOperation(id: string, variable_name: string): Object { - return { type: "operation", operator: "=", args: [ - { termType: "Variable", value: variable_name}, - { termType: "NamedNode", value: id } - ]}; - } - private static pushFilter2Args(filters: Object[], args: Object[], latestFilter: Object){ - if (filters.length > 1){ - let filter = filters.pop(); - let disjunction = { type: "operation", operator: "||", args: [] } - disjunction.args.push(filter); - disjunction.args.push(latestFilter); - this.pushFilter2Args(filters, args, disjunction); - } else { - args.push(latestFilter); - args.push(filters.pop()); - } - } - public static hasSyntaxError(query: string): boolean { - const parser = new Parser(); - try { - const parsedQuery = parser.parse(query); - } catch(e){ - return true; - } - return false; - } - public static getSyntaxError(query: string): string { - const parser = new Parser(); - try { - const parsedQuery = parser.parse(query); - } catch(e){ - return String(e); - } - return ''; - } - -} export class NumericResultRow extends BasicResultBindingElement { static readonly hasText = "http://www.nie.org/ontology/nietzsche#hasText"; static readonly hasCleanText = "http://www.nie.org/ontology/nietzsche#hasCleanText"; static readonly hasEditedText = "http://www.nie.org/ontology/nietzsche#hasEditedText"; static readonly hasCleanEditedText = "http://www.nie.org/ontology/nietzsche#hasCleanEditedText"; static readonly manuscript_variable = "manuscript"; static readonly text_variable = "id"; static readonly raw_text_variable = "raw_text"; static readonly edited_text_variable = "edited_text"; static readonly word_variable = "word"; static readonly bindObject = { type: "bind", variable: { termType: "Variable", value: NumericResultRow.text_variable }, expression: { type: "operation", operator: "if", args: [ { type: "operation", operator: "bound", args: [ { termType: "Variable", value: NumericResultRow.edited_text_variable } ] }, { termType: "Variable", value: NumericResultRow.edited_text_variable }, { termType: "Variable", value: NumericResultRow.raw_text_variable }, ]} }; static readonly orderObject = { expression: { termType: "Variable", value: NumericResultRow.text_variable }, descending: false }; static readonly punctuationPattern = /[.,!;:\-_–()“„]/g static readonly query: string = ` PREFIX tln: PREFIX rdf: SELECT ?id ?word ?numText ?total WHERE { ?manuscript a tln:ArchivalManuscriptUnity; tln:hasManuscriptType "Mappe"; tln:hasPages/rdf:rest*/rdf:first ?page. ?page a tln:Page; tln:hasWords/rdf:rest*/rdf:first ?word. }`; wordIds: string[] = []; numText: number; numProperties: number = 1; numPropertiesPercent: number = 1; numPropertiesIncludeMulti: number = 1; numTextPercent: number = 1; numPropertyTextPercent: number = 1; total: number; constructor (data: any, id?: string, service?: any) { super(data, id, service); this.numText = this.getData4Key('numText'); this.total = this.getData4Key('total'); this.wordIds.push(this.getData4Key('word')); this.updatePercentages(); } public updateResult(item: NumericResultRow) { this.wordIds = this.wordIds.concat(item.wordIds); this.numPropertiesIncludeMulti = this.wordIds.length this.numProperties = (new Set(this.wordIds)).size; this.updatePercentages(); } private updatePercentages(){ this.numPropertiesPercent = Math.round((this.numProperties/this.total)*10000)/100; this.numTextPercent = Math.round((this.numText/this.total)*10000)/100; this.numPropertyTextPercent = Math.round((this.numProperties/this.numText)*10000)/100; } public static convertData(this: T, data: FusekiResults, id?: string, service?: any): Array> { if (!this.contentConforms2Type(data)){ return []; } let elements = []; let content = this.getContent(data); for (var i = 0; i < content.length; i++){ let element = new NumericResultRow(content[i], service); if (elements.length > 0 && elements[elements.length-1].id == element.id){ elements[elements.length-1].updateResult(element); } else { elements.push(element) } } //console.log(elements) return elements; } private static createGroup(whereItems: Object[], aggregate_variable: string, group?: string): Object { if (group != undefined && group != null) { return { type: "group", patterns: [ { queryType: "SELECT", variables: [ { expression: { type: "aggregate", aggregation: "count", distinct: false, expression: { termType: "Variable", value: group }}, variable: { termType: "Variable", value: aggregate_variable } }, { termType: "Variable", value: group } ], where: whereItems, type: "query", group: [ { expression: { termType: "Variable", value: group }} ] } ] }; } else { return { type: "group", patterns: [ { queryType: "SELECT", variables: [ { expression: { type: "aggregate", aggregation: "count", distinct: false, expression: { termType: "Variable", value: this.word_variable }}, variable: { termType: "Variable", value: aggregate_variable } } ], where: [{ type: "bgp", triples: whereItems }], type: "query" } ] }; } } private static hasTextObject(textQuality: TextQuality): Object { const hasText = (textQuality.clean) ? NumericResultRow.hasCleanText : NumericResultRow.hasText; const objectVariable = (textQuality.preferEditedText) ? NumericResultRow.raw_text_variable : NumericResultRow.text_variable; return { subject: { termType: "Variable", value: NumericResultRow.word_variable }, predicate: { termType: "NamedNode", value: hasText }, object: { termType: "Variable", value: objectVariable } }; } private static optionalEditedTextObject(textQuality: TextQuality): Object { const hasEditedText = (textQuality.clean) ? NumericResultRow.hasCleanEditedText : NumericResultRow.hasEditedText; return { type: "optional", patterns: [ { type: "bgp", triples: [ { subject: { termType: "Variable", value: NumericResultRow.word_variable }, predicate: { termType: "NamedNode", value: hasEditedText }, object: { termType: "Variable", value: NumericResultRow.edited_text_variable } } ] } ] }; } private static insertTextConditions(parsedQuery: Object, textQuality: TextQuality){ parsedQuery['where'][0].triples.push(this.hasTextObject(textQuality)); if(textQuality.preferEditedText){ parsedQuery['where'].push(this.optionalEditedTextObject(textQuality)); parsedQuery['where'].push(this.bindObject); } } public static getSelectableQuery(selectableProperties: SelectableWordProperty[], scopus: ManuscriptPages[], textQuality: TextQuality, text?: string, ignoreCase?: boolean, orderDesc?: boolean): string { let parser = new Parser(); let sparqlGenerator = new Generator({}); let parsedQuery = parser.parse(this.query) let basicWhereTriples = parsedQuery.where[0].triples.slice(); this.insertTextConditions(parsedQuery, textQuality); const whereBeforeProperties = JSON.parse(JSON.stringify(parsedQuery.where));//deep cloning selectableProperties.forEach(selectableProperty =>{ parsedQuery.where[0].triples.push({ subject: { termType: "Variable", value: this.word_variable }, predicate: { termType: "NamedNode", value: selectableProperty.id }, object: { termType: "Variable", value: selectableProperty.id.substring(selectableProperty.id.indexOf('#')+1) } }) }); if (text != undefined && text != null && text != '') { let regexFilter = { type: "filter", expression: { type: "operation", operator: "regex", args: [ { termType: "Variable", value: this.text_variable }, { termType: "Literal", value: text } ] } } if (ignoreCase != undefined && ignoreCase){ regexFilter.expression.args.push({ termType: "Literal", value: "i" }); } parsedQuery.where.push(regexFilter); } let totalGroup = this.createGroup(basicWhereTriples, "total"); let numGroup = this.createGroup(whereBeforeProperties, "numText", this.text_variable); if (scopus.length > 0){ - let filters = scopus.map(manuscript =>QueryJson.createOperation(manuscript.id, this.manuscript_variable)); + let filters = scopus.map(manuscript =>QueryJson.createEqualsOperation(manuscript.id, this.manuscript_variable)); let filterObject = QueryJson.createFilterObject(filters); parsedQuery.where.push(filterObject); totalGroup['patterns'][0].where.push(filterObject); numGroup['patterns'][0].where.push(filterObject); } parsedQuery.where.push(totalGroup); parsedQuery.where.push(numGroup); parsedQuery['order'] = [ this.orderObject ] return sparqlGenerator.stringify(parsedQuery); } } export class SelectableWordProperty extends BasicResultBindingElement{ static readonly query: string = ` PREFIX tln: PREFIX rdfs: PREFIX skos: SELECT DISTINCT ?id ?label ?propName WHERE { ?id rdfs:subPropertyOf tln:selectableWordProperty; skos:prefLabel ?label. #BIND(STRAFTER(STR(?id), STR(tln:)) as ?propName) }`; id: string; label: string; //propName: string; constructor (data: any, id?: string, service?: any) { super(data, id, service); this.id = this.getData4Key('id'); this.label = this.getData4Key('label'); //this.propName = this.getData4Key('propName'); } } diff --git a/nietzsche-beta-app/src/app/tln-edition/datatypes/query_json.ts b/nietzsche-beta-app/src/app/tln-edition/datatypes/query_json.ts new file mode 100644 index 0000000..a455f8b --- /dev/null +++ b/nietzsche-beta-app/src/app/tln-edition/datatypes/query_json.ts @@ -0,0 +1,129 @@ +import { Parser, Generator } from 'sparqljs'; + + +export interface ParsedQuery { + where: any[]; +} +export interface Arg { + termType: string; + value: string; +} +export class Literal implements Arg { + termType: string = "Literal" + value: string; + constructor(value: string){ + this.value = value; + } +} +export class NamedNode implements Arg { + termType: string = "NamedNode" + value: string; + constructor(value: string){ + this.value = value; + } +} +export class Variable implements Arg { + termType: string = "Variable" + value: string; + constructor(value: string){ + this.value = value; + } +} +export interface Triple { + subject: Arg; + predicate: Arg; + object: Arg; +} +export interface WhereBgp { + type: string; + triples: Triple[]; +} +export class QueryObject { + type: string; + constructor(type: string){ + this.type = type; + } +} +export class Operation extends QueryObject { + args: Arg[]; + operator: string; + constructor(operator: string, args: Arg[] ){ + super("operation"); + this.operator = operator; + this.args = args; + } +} +export class BGP extends QueryObject implements WhereBgp { + triples: Triple[]; + constructor(triples: Triple[] ){ + super("bgp"); + this.triples = triples; + } +} + +export class QueryJson { + public static toString(parsedQuery: ParsedQuery): string { + const sparqlGenerator = new Generator({}); + return sparqlGenerator.stringify(parsedQuery); + } + public static parseQuery(query: string): ParsedQuery { + const parser = new Parser(); + return parser.parse(query); + } + public static createFilterObject(filters: Object[] ): Object{ + let filterObject = { type: "filter", expression: null }; + if (filters.length > 1){ + filterObject.expression = { type: "operation", operator: "||", args: [] } + let filter = filters.pop() + this.pushFilter2Args(filters, filterObject.expression.args, filter); + } else { + filterObject.expression = filters[0]; + } + return filterObject; + } + public static createEqualsOperation(id: string, variable_name: string): Operation { + return new Operation("=", [ new Variable(variable_name), new NamedNode(id) ]); + } + public static createRegexOperation(variable_name: string, regex: string, flag?: string): Operation { + let args: Arg[] = [ new Variable(variable_name), new Literal(regex) ] + if (flag != undefined && flag != null && flag != '') { + args.push(new Literal(flag)) + } + return new Operation("regex", args); + } + private static pushFilter2Args(filters: Object[], args: Object[], latestFilter: Object){ + if (filters.length > 1){ + let filter = filters.pop(); + let disjunction = { type: "operation", operator: "||", args: [] } + disjunction.args.push(filter); + disjunction.args.push(latestFilter); + this.pushFilter2Args(filters, args, disjunction); + } else { + args.push(latestFilter); + args.push(filters.pop()); + } + } + public static hasSyntaxError(query: string): boolean { + const parser = new Parser(); + try { + const parsedQuery = parser.parse(query); + } catch(e){ + return true; + } + return false; + } + public static getSyntaxError(query: string): string { + const parser = new Parser(); + try { + const parsedQuery = parser.parse(query); + } catch(e){ + return String(e); + } + return ''; + } + public static splitWhereBGP(bgp: WhereBgp, key: string): BGP[] { + let triplesWithKey = bgp.triples.filter(triple =>triple.subject.value == key) + let triplesWithOutKey = bgp.triples.filter(triple =>triple.subject.value != key) + return [ new BGP(triplesWithKey), new BGP(triplesWithOutKey) ] + } +} diff --git a/nietzsche-beta-app/src/app/tln-edition/datatypes/search.ts b/nietzsche-beta-app/src/app/tln-edition/datatypes/search.ts index 82280e7..005f443 100644 --- a/nietzsche-beta-app/src/app/tln-edition/datatypes/search.ts +++ b/nietzsche-beta-app/src/app/tln-edition/datatypes/search.ts @@ -1,132 +1,138 @@ import { BasicResultBindingElement, FusekiResults} from './basic_datatype'; import { TLN_SEARCH_ROUTE } from '../constants'; +import { QueryJson } from './query_json'; +import { ManuscriptPages } from './manuscript'; import { TlnWord } from './word'; export class TlnExtWord extends TlnWord { startLine?: string; endLine?: string; constructor(data: any, id?: string, service?: any){ super(data, id, service) this.startLine = this.getData4Key('startLine'); this.endLine = this.getData4Key('endLine'); } } export class PageResult { words: TlnExtWord[] = []; constructor(words: TlnExtWord[]){ this.words = words; } public getWords(): string[] { return this.words.map(word =>word.id); } public getStartLine(): string { let sortedWords = this.words.sort((word0, word1) =>word0.line_number - word1.line_number) if (sortedWords.length == 0){ return ''; } return (sortedWords[0].startLine != undefined && sortedWords[0].startLine != null) ? sortedWords[0].startLine : sortedWords[0].line; } public getEndLine(): string { let sortedWords = this.words.sort((word0, word1) =>word0.line_number - word1.line_number) if (sortedWords.length == 0){ return ''; } return (sortedWords[sortedWords.length-1].endLine != undefined && sortedWords[sortedWords.length-1].endLine != null) ? sortedWords[sortedWords.length-1].endLine : sortedWords[sortedWords.length-1].line; } } - export class FoundPage extends BasicResultBindingElement{ - static readonly storagePrefix = TLN_SEARCH_ROUTE + '_' + FoundPage.name static readonly query: string = ` PREFIX tln: PREFIX rdf: SELECT DISTINCT ?id ?manuscript ?title ?number ?word ?text ?line ?line_number ?startLine ?endLine WHERE { - ?id a tln:Page; - tln:hasNumber ?number; - tln:hasPseudoText ?fulltext. - FILTER regex(?fulltext, "#find#", "s"). + ?id tln:hasPseudoText ?fulltext. + FILTER(?fulltext != "none"). ?manuscript a tln:ArchivalManuscriptUnity; tln:hasManuscriptType "Mappe"; tln:hasPages/rdf:rest*/rdf:first ?id; tln:hasTitle ?title. - ?id tln:hasWords/rdf:rest*/rdf:first ?word. + FILTER(?manuscript != "none"). + ?id a tln:Page; + tln:hasNumber ?number; + tln:hasWords/rdf:rest*/rdf:first ?word. ?word tln:hasOutputText ?text; tln:wordBelongsToLine ?line. ?line tln:lineHasNumber ?line_number. OPTIONAL{ ?previouseNode rdf:rest/rdf:first ?line; rdf:first ?startLine.} OPTIONAL{ ?myNode rdf:first ?line; rdf:rest/rdf:first ?endLine.} - #FILTER(). } order by ?id ?line_number`; title: string number: string; manuscript: string results: PageResult[] = []; constructor (data: any, id?: string, service?: any) { super(data, id, service); this.title = this.getData4Key('title'); this.number = this.getData4Key('number'); this.manuscript = this.getData4Key('manuscript'); } - public removeIncompleteResults(searchTerms: string[]) { - this.results = this.results.filter(result =>searchTerms.every(searchText =>result.words.filter(word =>word.text.match('^[^\w\s]*' + searchText + '.*')).length > 0)) + public removeIncompleteResults(searchTerms: string[], ignoreCaseFlag: string) { + this.results = this.results.filter(result =>searchTerms.every( + searchText =>result.words.filter(word =>word.text.match(new RegExp('^[^\w\s]*' + searchText + '.*', ignoreCaseFlag))).length > 0)) } /** * This method returns the parametrized SPARQL query of this FoundPage * * If "key" is omitted {@link /classes/BasicResultBindingElement.html#default_key|default_key} will be used. * * @param find the search text - * @param key will be ignored. + * @param korpus (optional) restrict search on selected ManuscriptPages. **/ - public static getQuery(find?: string, key?: string): string { - if(find == undefined || find == null){ - return this.query; - } + public static getSeachQuery(find: string, ignoreCase: boolean, korpus: ManuscriptPages[], manuscript_variable?: string): string { let words = find.split(' ') let find_regex = words.join('.*') + '.*' - let filter = 'FILTER regex(?text, "^[^\\\\w]?(' + words.join('.*|') + '.*)")'; - let query = this.query.replace('#find#', find_regex).replace('#FILTER()', filter); - //console.log(query); - return query; + let or_find_regex = '^[^\\w]?(' + words.join('.*|') + '.*)'; + let parsedQuery = QueryJson.parseQuery(this.query) + let ignoreCaseFlag = (ignoreCase) ? 'i' : ''; + let join_filters = [ QueryJson.createRegexOperation('fulltext', find_regex, 's' + ignoreCaseFlag) ] + parsedQuery.where[1] = QueryJson.createFilterObject(join_filters); + if (korpus.length > 0 && manuscript_variable != undefined && manuscript_variable != null){ + let equal_filters = korpus.map(manuscript =>QueryJson.createEqualsOperation(manuscript.id, manuscript_variable)); + parsedQuery.where[3] = QueryJson.createFilterObject(equal_filters); + } + let or_filters = [ QueryJson.createRegexOperation('text', or_find_regex, ignoreCaseFlag) ] + parsedQuery.where.push(QueryJson.createFilterObject(or_filters)); + return QueryJson.toString(parsedQuery); } public static convertData(this: T, data: FusekiResults, id?: string, service?: any): Array> { let elements = []; let pages = []; let searchTerms = service.getSearchTerms(); let content = this.getContent(data); let currentResult: PageResult = null; let currentPage: FoundPage = null; for (var i = 0; i < content.length; i++){ let page = new FoundPage(content[i], id, service); if(content[i]['word'] != undefined && content[i]['word'] != null) { let words = TlnExtWord.convertData({ head: { vars: []}, results: { bindings: [ content[i] ] } }, content[i]['word']['value']); if (pages.length == 0 || pages.map(page =>page.id).indexOf(page.id) == -1){ currentResult = new PageResult(words); currentPage = page; currentPage.results.push(currentResult) pages.push(currentPage) } else { if (currentResult.words.indexOf(words[0]) == -1){ if (currentResult.words.length > 0 && (Math.abs(Math.min(...currentResult.words.map(word =>word.line_number)) - words[0].line_number) > 4 || Math.abs(Math.max(...currentResult.words.map(word =>word.line_number)) - words[0].line_number) > 4)){ currentResult = new PageResult(words); currentPage.results.push(currentResult); } else { currentResult.words.push(words[0]); } } } } } - pages.forEach(page =>page.removeIncompleteResults(searchTerms)); + pages.forEach(page =>page.removeIncompleteResults(searchTerms, service.getIgnoreCaseFlag())); return pages.filter(page =>page.results.length > 0); } } diff --git a/nietzsche-beta-app/src/app/tln-edition/datatypes/word_presentation.ts b/nietzsche-beta-app/src/app/tln-edition/datatypes/word_presentation.ts index 5357225..36b8f86 100644 --- a/nietzsche-beta-app/src/app/tln-edition/datatypes/word_presentation.ts +++ b/nietzsche-beta-app/src/app/tln-edition/datatypes/word_presentation.ts @@ -1,82 +1,82 @@ import { Parser, Generator } from 'sparqljs'; import { BasicResultBindingElement, FusekiResults} from './basic_datatype'; import { ManuscriptStub } from './manuscript'; import { PageStub } from './page'; import { WordStub } from './word'; -import { QueryJson } from './quant'; +import { QueryJson } from './query_json'; export class PresentationWord extends WordStub { line_number: number; constructor (data: any, id?: string, service?: any) { super(data, id, service); this.line_number = this.getData4Key('line_number'); } } export class PageWords extends PageStub { words: WordStub[] = []; constructor (data: any, id?: string, service?: any) { super(data, id, service); } } export class ManuscriptPageWords extends ManuscriptStub { static readonly query_key: string = 'word'; static readonly default_key: string = 'word'; static readonly query: string = ` PREFIX tln: PREFIX rdf: SELECT ?id ?word ?title ?type ?page ?number ?text ?line_number WHERE { ?word tln:hasText ?text; tln:wordBelongsToLine/tln:lineHasNumber ?line_number. ?page a tln:Page; tln:hasWords/rdf:rest*/rdf:first ?word; tln:hasNumber ?number. ?id a tln:ArchivalManuscriptUnity; tln:hasPages/rdf:rest*/rdf:first ?page; tln:hasManuscriptType ?type; tln:hasTitle ?title. } ORDER BY ?id ?page`; pages: PageWords[] = []; constructor (data: any, id?: string, service?: any) { super(data, id, service); } public static convertData(this: T, data: FusekiResults, id?: string, service?: any): Array> { let manuscripts = []; let content = this.getContent(data); for (var i = 0; i < content.length; i++){ let manuscript = new ManuscriptPageWords(content[i], id, service); let page = PageWords.convertData({ head: { vars: []}, results: { bindings: [ content[i] ] } }, content[i]['page']['value'])[0] let word = PresentationWord.convertData({ head: { vars: []}, results: { bindings: [ content[i] ] } }, content[i]['word']['value'])[0] if (manuscripts.length > 0 && manuscript.id == manuscripts[manuscripts.length-1].id){ if (manuscripts[manuscripts.length-1].pages.length > 0 && page.id == manuscripts[manuscripts.length-1].pages[manuscripts[manuscripts.length-1].pages.length-1].id){ manuscripts[manuscripts.length-1].pages[manuscripts[manuscripts.length-1].pages.length-1].words.push(word); } else { if (manuscripts[manuscripts.length-1].pages.length > 0){ console.log(page) } page.words.push(word) manuscripts[manuscripts.length-1].pages.push(page) } } else { page.words.push(word) manuscript.pages.push(page); manuscripts.push(manuscript); } } console.log(manuscripts); return manuscripts; } public static getParameterizedQuery(wordIds: string[]): string { let parser = new Parser(); let sparqlGenerator = new Generator({}); let parsedQuery = parser.parse(this.query) - let filters = wordIds.map(id =>QueryJson.createOperation(id, this.query_key)); + let filters = wordIds.map(id =>QueryJson.createEqualsOperation(id, this.query_key)); let filterObject = QueryJson.createFilterObject(filters); parsedQuery.where.push(filterObject); return sparqlGenerator.stringify(parsedQuery); } } diff --git a/nietzsche-beta-app/src/app/tln-edition/models.ts b/nietzsche-beta-app/src/app/tln-edition/models.ts index 074d2a5..4e59516 100644 --- a/nietzsche-beta-app/src/app/tln-edition/models.ts +++ b/nietzsche-beta-app/src/app/tln-edition/models.ts @@ -1,124 +1,124 @@ import { Observable } from 'rxjs'; import {HttpErrorResponse } from '@angular/common/http'; import {Sort} from '@angular/material/sort'; import {EventEmitter} from '@angular/core'; import { StandoffMarkup } from 'ngx-mat-standoff-markup'; import { Line, Page, Word } from '../page-view/models'; import { Handler } from './data_handler'; import { Manuscript4Selection} from './datatypes/manuscript'; import { SelectableWordProperty} from './datatypes/quant'; export { externalAssignClass, externalAssignStyle, Configuration, Continuation, Copyright, Identifier, Image, Line, LineStub, Reference, Manuscript, Page, Point, Position, PositionalObject, TextField, TextByForeignHand, Word, USE_EXTERNAL_TOOLTIP } from '../page-view/models'; export interface AccordionItemStatus { expanded: boolean; disabled: boolean; } export interface AccordionStatus { [name: string]: AccordionItemStatus; } export interface EditorComment { id: string; editorClass: string; comment?: string; correction?: string; text?: string; textWithMarkup?: string; word?: Word; tLine?: Line; markups?: StandoffMarkup[]; } export interface ManuscriptUnity { numberOfPages: number; title: string; } export interface NavigationPage extends Page { title: string; index?: number; } export interface TextVersion { id: string; title: string; manuscript?: string; textUnities: TextUnity[]; } export interface TextUnity { id: string; number: string; startLine?: number; endLine?: number; belongsToPage?: string; hasFaksimileImage?: boolean; } /** * This interface specifies a query service that returns * the response from a HttpClient.post as an Observable. * */ export interface TlnQueryServiceInterface { error_emitter: EventEmitter; /** * @param query: The query to run. * @returns response: the response as an Observable. * */ getData(query: string): Observable; resetData(key: string): void; reset_data: EventEmitter; baseUrl: string; } export interface TextGeneticOrder { id: string; textVersions: TextVersion[]; } export interface TextQuality { clean: boolean; preferEditedText: boolean; } export interface DataProcessor { processData(): void; } export interface QueryProperties { ignoreCase: boolean; resultIndex?: number; - restrictKorpusOnContext: boolean; selectedManuscripts: Manuscript4Selection[]; + dataKey?: string; } export interface QuantQuery extends QueryProperties { altQuery?: string; filterValue?: string; selectedWordProperties: SelectableWordProperty[]; textQuality: TextQuality; currentTable: number; resultIndices: number[]; + restrictKorpusOnContext: boolean; sortArray: Sort[]; text?: string; - dataKey?: string; } export interface StorageHandler { getStorageKey(handler: Handler): string; getStorageDuration(handler: Handler): number; } export interface PageIndexUpdater { change: EventEmitter; } export interface LineContinuationReference { refPageNumber?: string; refTitle?: string; refLine?: number; isToRef: boolean; } diff --git a/nietzsche-beta-app/src/app/tln-edition/quant_data_handler.ts b/nietzsche-beta-app/src/app/tln-edition/quant_data_handler.ts index 6e8deb7..2f22ee4 100644 --- a/nietzsche-beta-app/src/app/tln-edition/quant_data_handler.ts +++ b/nietzsche-beta-app/src/app/tln-edition/quant_data_handler.ts @@ -1,36 +1,53 @@ import { OnInit, EventEmitter} from '@angular/core'; import { first, takeUntil } from 'rxjs/operators'; import { BasicResultBindingElement, AskResult} from './datatypes/basic_datatype'; import { DataHandler, Handler } from './data_handler'; /** * This class retrieves quantitative data from a query service and instantiates it using * corresponding handlers. **/ export class QuantitativeDataHandler extends DataHandler { /** * @param component the component that uses this data handler **/ constructor(protected component: OnInit){ super(component) } /** * Retrieve and instantiate data * @param key data handler key * @param query iri that should be passed to query **/ - public getData(key: string, query: string, raw_data_key?: string) { + public getData4Query(key: string, query: string, raw_data_key?: string) { this.start_processing.emit(true); let handler = this[key]['handler']; - this.queryService.getData(query).pipe(takeUntil(this.stop_processing) || first()).subscribe(results => { + this.queryService.getData(query).pipe(takeUntil(this.stop_processing)).subscribe(results => { this.component[key] = handler.convertData(results); if (raw_data_key != undefined && raw_data_key != null){ this.component[raw_data_key] = results; } this.processing_finished.emit(true); if (this[key]['process_data'] != undefined && this[key]['process_data'] != null){ this[key]['process_data'].processData(); } }); } + /** + * Retrieve and instantiate data + * @param key data handler key + * @param query iri that should be passed to query + * @param searchTermsString string containing the search terms + **/ + public getSearchData(key: string, query: string, searchTermsString: string) { + this.start_processing.emit(true); + let handler = this[key]['handler']; + this.queryService.getData(query).pipe(takeUntil(this.stop_processing)).subscribe(results => { + this.component[key] = handler.convertData(results, null, this[key]['service']); + this.processing_finished.emit(true); + if (this[key]['process_data'] != undefined && this[key]['process_data'] != null){ + this[key]['process_data'].processData(); + } + }); + } } diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-edition.module.ts b/nietzsche-beta-app/src/app/tln-edition/tln-edition.module.ts index bafe9a1..74259a4 100644 --- a/nietzsche-beta-app/src/app/tln-edition/tln-edition.module.ts +++ b/nietzsche-beta-app/src/app/tln-edition/tln-edition.module.ts @@ -1,111 +1,113 @@ import { BrowserModule } from '@angular/platform-browser'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { HttpClientModule } from "@angular/common/http"; import { NgModule, ModuleWithProviders } from '@angular/core'; import { MatBadgeModule, MatSnackBarModule, MatSlideToggleModule, MatTreeModule, MatSliderModule,MatProgressSpinnerModule,MatBottomSheetModule,MatButtonModule,MatCheckboxModule,MatDialogModule,MatExpansionModule,MatFormFieldModule,MatInputModule,MatListModule,MatPaginatorModule,MatRadioModule,MatSelectModule,MatSidenavModule,MatSortModule,MatTableModule,MatToolbarModule,MatButtonToggleModule,MatCardModule,MatIconModule,MatMenuModule,MatTabsModule,MatTooltipModule } from '@angular/material'; import { CodemirrorModule } from '@ctrl/ngx-codemirror'; import { NgxMatStandoffMarkupModule} from "ngx-mat-standoff-markup"; +import { CacheService } from './common/cache.service'; import { PageViewService } from '../page-view/page-view.service'; import { PageViewModule } from '../page-view/page-view.module'; import { OntologyViewerModule } from '../ontology-viewer/ontology-viewer.module'; import { TlnPageViewComponent } from './tln-page-view.component'; import { TlnQueryService, TlnCacheQueryService } from './tln-query.service'; import { ToolTipComponent } from './tooltip/tool-tip.component'; import { TlnViewerNavigation } from './tln-viewer-navigation/tln-viewer-navigation.component'; import { TlnInformationComponent } from './tln-information/tln-information.component'; import { TlnCrossrefComponent } from './tln-crossref/tln-crossref.component'; import { VersionViewComponent } from './tln-crossref/version-view/version-view.component'; import { PageVersionViewComponent } from './tln-crossref/page-version-view/page-version-view.component'; import { NavigationComponent } from './tln-crossref/navigation/navigation.component'; import { GeneticOrderFilterPipe} from './tln-crossref/page-version-view/filter.pipe'; import { TlnPageVersionViewComponent } from './tln-crossref/page-version-view/tln-page-version-view.component'; import { TlnManuscriptViewComponent } from './tln-manuscript-view/tln-manuscript-view.component'; import { TlnFulltextComponent } from './tln-fulltext/tln-fulltext.component'; import { FulltextNavigationComponent } from './tln-fulltext/navigation/navigation.component'; import { ResultPipePipe } from './tln-fulltext/result-pipe.pipe'; import { PageResultFilterPipe} from './common/page-result-filter.pipe'; import { SearchComponent } from './tln-navigation-elements/search.component'; import { ToggleNavigationComponent } from './tln-navigation-elements/toggle-navigation.component'; import { ZoomComponent } from './tln-navigation-elements/zoom.component'; import { ZoomPipe } from './tln-navigation-elements/zoom.pipe'; import { OpenInViewerComponent } from './tln-navigation-elements/open-in-viewer.component'; import { DebugPipe } from './debug.pipe'; import { TlnHeightDirective } from './tln-height.directive'; import { TlnQuantComponent } from './tln-quant/tln-quant.component'; import { TlnTableComponent } from './tln-quant/tln-table.component'; import { ResultPipe } from './tln-quant/result.pipe'; import { WordPresentationComponent } from './tln-quant/word-presentation.component'; import { ToIdsPipe } from './tln-quant/2-ids.pipe'; import { SortByLinePipe } from './tln-quant/sort-by-line.pipe'; import { QueryErrorComponent } from './tln-quant/query-error.component'; import { ExportComponent } from './tln-quant/export.component'; import { FusekiTableComponent } from './tln-quant/fuseki-table.component'; import { TlnOntologyViewerComponent } from './tln-quant/tln-ontology-viewer.component'; import { EditorCommentComponent } from './tooltip/editor-comment.component'; import { MergeEditorCommentPipe } from './tooltip/merge-editor-comment.pipe'; import { OverwrittenComponent } from './tooltip/overwritten.component'; import { FootnoteComponent } from './tln-information/footnote.component'; import { ImprintComponent } from './tln-information/imprint.component'; @NgModule({ declarations: [FulltextNavigationComponent, TlnPageViewComponent, ToolTipComponent, TlnViewerNavigation, TlnInformationComponent, TlnCrossrefComponent, VersionViewComponent, PageVersionViewComponent, NavigationComponent, GeneticOrderFilterPipe, TlnPageVersionViewComponent, TlnManuscriptViewComponent, TlnFulltextComponent, PageResultFilterPipe, ResultPipePipe, SearchComponent, ToggleNavigationComponent, ZoomComponent, ZoomPipe, OpenInViewerComponent, DebugPipe, TlnHeightDirective, TlnQuantComponent, TlnTableComponent, ResultPipe, WordPresentationComponent, ToIdsPipe, SortByLinePipe, QueryErrorComponent, ExportComponent, FusekiTableComponent, TlnOntologyViewerComponent, EditorCommentComponent, MergeEditorCommentPipe, OverwrittenComponent, FootnoteComponent, ImprintComponent], imports: [ CodemirrorModule, MatBadgeModule, MatSnackBarModule, MatSlideToggleModule, MatSliderModule, MatBottomSheetModule, MatButtonModule, MatButtonToggleModule, MatCardModule, MatCheckboxModule, MatDialogModule, MatExpansionModule, MatFormFieldModule, MatIconModule, MatInputModule, MatListModule, MatMenuModule, MatProgressSpinnerModule, MatPaginatorModule, MatRadioModule, MatSelectModule, MatSidenavModule, MatSortModule, MatTableModule, MatTabsModule, MatTreeModule, MatToolbarModule, MatTooltipModule, BrowserModule, CommonModule, FormsModule, NgxMatStandoffMarkupModule, PageViewModule, OntologyViewerModule ], exports: [ DebugPipe, FulltextNavigationComponent, NavigationComponent, ToggleNavigationComponent, ToolTipComponent, TlnCrossrefComponent, TlnHeightDirective, TlnManuscriptViewComponent, TlnPageViewComponent, TlnViewerNavigation, TlnQuantComponent ], providers: [ PageViewService, + CacheService //TlnQueryService ], entryComponents: [TlnInformationComponent, QueryErrorComponent] }) export class TlnEditionModule { } diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-fulltext/tln-fulltext.component.css b/nietzsche-beta-app/src/app/tln-edition/tln-fulltext/tln-fulltext.component.css index a625edb..db06b69 100644 --- a/nietzsche-beta-app/src/app/tln-edition/tln-fulltext/tln-fulltext.component.css +++ b/nietzsche-beta-app/src/app/tln-edition/tln-fulltext/tln-fulltext.component.css @@ -1,55 +1,61 @@ #page { width: 98%; position: relative; top: 0px; left: 0px; height: 100%; overflow: visible; } .icon-button { width: 15px; margin-left: 3px; } .page-navi { float: left; margin-top: -3px; margin-bottom: 1px; height: 38px; background-color: lightblue; z-index: 2; } .default-mouse { cursor: default; } .small-grey { color: lightgrey; font-size: 80%; } .text { position: absolute; top: 10px; left: 500px; } .search { position: relative; top: 60px; width: 500px; height: 100%; text-align: center; margin-right: 20px; } .extra-space { margin-top: 45px; } .title { text-align: left; margin-left: 20px; } .form { width: 95%; } .full-width { min-width: 150px; max-width: 500px; width: 80%; } +.korpus { + text-align: left; +} +.belowOptions { + margin-top: 50px; +} diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-fulltext/tln-fulltext.component.html b/nietzsche-beta-app/src/app/tln-edition/tln-fulltext/tln-fulltext.component.html index b319824..bd953d1 100644 --- a/nietzsche-beta-app/src/app/tln-edition/tln-fulltext/tln-fulltext.component.html +++ b/nietzsche-beta-app/src/app/tln-edition/tln-fulltext/tln-fulltext.component.html @@ -1,56 +1,75 @@
diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-fulltext/tln-fulltext.component.ts b/nietzsche-beta-app/src/app/tln-edition/tln-fulltext/tln-fulltext.component.ts index a358459..aca6701 100644 --- a/nietzsche-beta-app/src/app/tln-edition/tln-fulltext/tln-fulltext.component.ts +++ b/nietzsche-beta-app/src/app/tln-edition/tln-fulltext/tln-fulltext.component.ts @@ -1,116 +1,150 @@ -import { Component, Input, OnInit, ViewChild } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, ViewChild } from '@angular/core'; import { Router, ActivatedRoute, Params } from '@angular/router'; import {PageEvent} from '@angular/material/paginator'; import { TLN_CROSSREF_ROUTE, TLN_SEARCH_ROUTE, TLN_MANUSCRIPT_ROUTE, TLN_VIEWER_ROUTE, TLN_CONTEXT_VIEW_PARAM, TLN_FULLSCREEN_PARAM, TLN_FIND_PARAM, TLN_PAGE_PARAM, TLN_MANUSCRIPT_PARAM, TLN_SEARCH_QUERY_PARAM, TLN_RESULT_INDEX_PARAM,TLN_SELECTED_LINES_PARAM, TLN_TEXT_GENETIC_ORDER_PARAM, TLN_VIEW_OPTION_PARAM, TLN_ZOOM_PARAM, VIEW_OPTIONS, ONTOLOTY_PREFIX } from '../constants'; +import { ResultRange, PaginatorResultStatus } from '../common/paginator-result-status'; +import { CacheService } from '../common/cache.service'; import { Handler, DataHandler } from '../data_handler'; +import { QuantitativeDataHandler } from '../quant_data_handler'; import { FoundPage} from '../datatypes/search'; +import { Manuscript4Selection} from '../datatypes/manuscript'; import { Mapping } from '../route-reader'; import { RouteUpdater } from '../route-updater'; import { TlnCacheQueryService } from '../services'; -import { ResultRange, PaginatorResultStatus } from '../common/paginator-result-status'; -import { QueryProperties, StorageHandler, TlnQueryServiceInterface} from '../models'; +import {DataProcessor, PageIndexUpdater, QueryProperties, StorageHandler, TlnQueryServiceInterface} from '../models'; //TODO: german language support for paginator, see: https://github.com/ngx-translate/core @Component({ selector: 'tln-fulltext', templateUrl: './tln-fulltext.component.html', styleUrls: ['./tln-fulltext.component.css'] }) -export class TlnFulltextComponent extends RouteUpdater implements StorageHandler, OnInit { +export class TlnFulltextComponent extends RouteUpdater implements DataProcessor, OnInit { /** * OPTIONAL pass a queryService with method * {@link /interfaces/TlnQueryServiceInterface.html#getData|getData} * to TlnPageViewComponent. **/ @Input() queryService: TlnQueryServiceInterface; tlnQueryService: TlnQueryServiceInterface; //protected currentRoute: string = TLN_SEARCH_ROUTE; + change = new EventEmitter(); current_page_iri: string; current_manuscript_unity: string; - dataHandler: DataHandler = new DataHandler(this); + expandOptions: boolean = false; + dataHandler: QuantitativeDataHandler = new QuantitativeDataHandler(this); fullscreen: boolean; max_width: number = -1; max_height: number = -1; + manuscriptPages: Manuscript4Selection[] = []; + readonly manuscript_variable = "manuscript"; searchTerm: string; resultIndex: number = 0; paginatorResultStatus: PaginatorResultStatus = new PaginatorResultStatus(5); resultsReceived: boolean = false; selectedViewOption: string = VIEW_OPTIONS.TRANSKRIPTION startSearch: boolean = false; protected mapping: Mapping = { current_page_iri: { param: TLN_PAGE_PARAM, type: "string" }, - resultIndex: { param: TLN_RESULT_INDEX_PARAM, type: "number" }, + // resultIndex: { param: TLN_RESULT_INDEX_PARAM, type: "number" }, searchTerm: { param: TLN_FIND_PARAM, type: "string" }, queryProps: { param: TLN_SEARCH_QUERY_PARAM, type: "complex" }, current_manuscript_unity: { param: TLN_MANUSCRIPT_PARAM, type: "string" }, fullscreen: { param: TLN_FULLSCREEN_PARAM, type: "boolean" }, } pages: FoundPage[] = []; - queryProps: QueryProperties = { ignoreCase: false, selectedManuscripts: [], restrictKorpusOnContext: false } + queryProps: QueryProperties = { ignoreCase: false, selectedManuscripts: [], resultIndex: 0 } private readonly margin_width: number = 280; private readonly initialPreviewWidth : number = 300; previewWidth: number = this.initialPreviewWidth; - constructor(private localQueryService: TlnCacheQueryService, protected router: Router, protected activatedRoute: ActivatedRoute ) { + constructor(private cacheService: CacheService, private localQueryService: TlnCacheQueryService, protected router: Router, protected activatedRoute: ActivatedRoute ) { super(router, activatedRoute); } ngOnInit() { if (screen.availWidth - this.initialPreviewWidth - this.margin_width > 1000){ this.previewWidth = screen.availWidth - this.initialPreviewWidth - 1000; } this.tlnQueryService = (this.queryService != null) ? this.queryService : this.localQueryService; this.max_width = screen.availWidth - this.previewWidth - this.margin_width; this.max_height = screen.availHeight - 200; - this.dataHandler.addHandler('pages', { 'handler': FoundPage, 'storage_handler': this }); + this.dataHandler.addHandler('manuscriptPages', { 'handler': Manuscript4Selection}); + this.dataHandler.addHandler('pages', { 'handler': FoundPage, 'process_data': this }); this.dataHandler['pages']['service'] = this this.dataHandler.setQueryService(this.tlnQueryService); this.dataHandler.start_processing.subscribe( (started: boolean) =>{ this.resultsReceived = false; this.startSearch = true; }); this.dataHandler.processing_finished.subscribe( (finished: boolean) =>{ this.resultsReceived = true; this.startSearch = false; }); + this.dataHandler.getData('manuscriptPages'); super.ngOnInit(); + this.expandOptions = (this.queryProps.selectedManuscripts.length > 0 || this.queryProps.ignoreCase) } private clearFindText() { this.searchTerm = ''; this.pages = []; - this.resultIndex = 0; + this.queryProps.resultIndex = 0; super.updateParams(); } - private search(){ - this.resultIndex = 0; - this.paginatorResultStatus.updateResultRange(this.resultIndex); + private search(){ + this.queryProps.resultIndex = 0; + this.paginatorResultStatus.updateResultRange(this.queryProps.resultIndex); super.updateParams(); if (this.searchTerm != undefined && this.searchTerm != null && this.searchTerm != ''){ this.dataHandler.resetData('pages'); - this.dataHandler.getData('pages', this.searchTerm); + let query = this.dataHandler['pages'].handler.getSeachQuery(this.searchTerm, this.queryProps.ignoreCase, this.queryProps.selectedManuscripts, this.manuscript_variable) + this.dataHandler.getSearchData('pages', query, this.searchTerm); } - } - public getStorageDuration(handler: Handler): number { - return 28800000; - } - public getStorageKey(handler: Handler): string{ - return TLN_SEARCH_ROUTE + '_' + handler.handler.name + '_' + this.searchTerm.replace(' ', '_'); } protected readParams(params: Params){ let oldSearchTerm = this.searchTerm; - let oldResultIndex = this.resultIndex; super.readParams(params); if (this.searchTerm != undefined && this.searchTerm != null && this.searchTerm != '' && this.searchTerm != oldSearchTerm){ - this.resultIndex = 0; - this.dataHandler.getData('pages', this.searchTerm); + let results = (this.queryProps != null && this.queryProps.dataKey != undefined && this.queryProps.dataKey != null) + ? this.cacheService.getItem(this.queryProps.dataKey + this.searchTerm) : null; + if (results != null){ + this.pages = results; + } else { + this.dataHandler.resetData('pages'); + let query = this.dataHandler['pages'].handler.getSeachQuery(this.searchTerm, this.queryProps.ignoreCase, this.queryProps.selectedManuscripts, this.manuscript_variable) + this.dataHandler.getSearchData('pages', query, this.searchTerm); + } } - if(this.pages.length > 0 && this.resultIndex > 0 ){ - this.paginatorResultStatus.updateResultRange(this.resultIndex); + if(this.pages.length > 0 && this.queryProps.resultIndex > 0 ){ + this.paginatorResultStatus.updateResultRange(this.queryProps.resultIndex); } } getSearchTerms(): string[] { return this.searchTerm.split(' '); } + getIgnoreCaseFlag(): string { + return (this.queryProps.ignoreCase) ? 'i' : ''; + } + isSelected = (o1: any, o2: any): boolean => { + return o1.id == o2.id; + } + private updateResultIndex(event: PageEvent){ + this.paginatorResultStatus.showResults(event); + this.queryProps.resultIndex = event.pageIndex; + this.updateParams(); + } + public processData(){ + if (this.queryProps != null + && this.queryProps.dataKey != undefined + && this.queryProps.dataKey != null){ + this.cacheService.removeItem(this.queryProps.dataKey + this.searchTerm); + } + const dataKey = TLN_SEARCH_ROUTE + Date.now().toString(); + this.cacheService.setItem(dataKey + this.searchTerm, this.pages); + this.queryProps['dataKey'] = dataKey; + this.updateParams(); + } + } diff --git a/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-quant.component.ts b/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-quant.component.ts index 4b7fbcd..5edba7b 100644 --- a/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-quant.component.ts +++ b/nietzsche-beta-app/src/app/tln-edition/tln-quant/tln-quant.component.ts @@ -1,279 +1,280 @@ import { Component, OnInit, Input, ViewChild } from '@angular/core'; import { Router, ActivatedRoute, Params } from '@angular/router'; import { HttpErrorResponse} from '@angular/common/http'; import {Sort} from '@angular/material/sort'; import {MatBottomSheet, MatBottomSheetRef} from '@angular/material/bottom-sheet'; import {MatTabChangeEvent} from '@angular/material/tabs'; import {QueryErrorComponent} from './query-error.component'; import { CodemirrorComponent} from '@ctrl/ngx-codemirror'; import { DataHandler } from '../data_handler'; import { QuantitativeDataHandler } from '../quant_data_handler'; import { Manuscript4Selection} from '../datatypes/manuscript'; import { FusekiResults } from '../datatypes/basic_datatype'; -import { NumericResultRow, QueryJson, SelectableWordProperty} from '../datatypes/quant'; +import { NumericResultRow, SelectableWordProperty} from '../datatypes/quant'; +import { QueryJson} from '../datatypes/query_json'; import { AccordionStatus, DataProcessor, QuantQuery, TlnQueryServiceInterface, TextQuality} from '../models'; import { TlnCacheQueryService } from '../services'; import { TLN_QUANT_ROUTE, TLN_MANUSCRIPT_ROUTE, TLN_VIEWER_ROUTE, TLN_CONTEXT_VIEW_PARAM, TLN_FULLSCREEN_PARAM, TLN_FIND_PARAM, TLN_PAGE_PARAM, TLN_MANUSCRIPT_PARAM, TLN_QUANT_QUERY_PARAM, TLN_QUERY_PARAM, TLN_RESULT_INDEX_PARAM,TLN_SELECTED_LINES_PARAM, TLN_TEXT_GENETIC_ORDER_PARAM, TLN_VIEW_OPTION_PARAM, TLN_ZOOM_PARAM, VIEW_OPTIONS, ONTOLOTY_PREFIX } from '../constants'; import { Mapping } from '../route-reader'; import { RouteUpdater } from '../route-updater'; -import { CacheService } from './cache.service'; +import { CacheService } from '../common/cache.service'; import { CompareMapping, SelectFromArray } from '../common/select-array'; @Component({ selector: 'tln-quant', templateUrl: './tln-quant.component.html', providers: [ TlnCacheQueryService], styleUrls: ['./tln-quant.component.css'] }) export class TlnQuantComponent extends RouteUpdater implements DataProcessor, OnInit { /** * OPTIONAL pass a queryService with method * {@link /interfaces/TlnQueryServiceInterface.html#getData|getData} * to TlnPageViewComponent. **/ @Input() queryService: TlnQueryServiceInterface; @Input() restrictKorpusOnContext: boolean = false; @ViewChild(CodemirrorComponent,{static:false}) codemirror: CodemirrorComponent; tlnQueryService: TlnQueryServiceInterface; dataHandler: DataHandler = new DataHandler(this); fusekiResults: FusekiResults; current_manuscript_iri: string; select_manuscript_iri: string; displayedColumns: string[] = [ 'id', 'numProperties', 'numPropertyTextPercent', 'numPropertiesPercent', 'numPropertiesIncludeMulti', 'numText', 'numTextPercent' ]; errorLine: number = -1; quantDataHandler: QuantitativeDataHandler = new QuantitativeDataHandler(this); isLoadingResults: boolean = false; manuscriptPages: Manuscript4Selection[] = []; numResultRows: NumericResultRow[] = []; resultIndex: number = 0; quantAccordionStatus: AccordionStatus = { wordProperties: { expanded: true, disabled: false }, scopus: { expanded: false, disabled: false }, query: { expanded: false, disabled: false } }; query: string = ''; queryInput: string = ''; queryHasSyntaxError: boolean = false; curlQuery: string = ''; shareQuery: boolean = false; wordProperties: SelectableWordProperty[] = []; quantQuery: QuantQuery = { currentTable: 0, resultIndices: [ 0, 0 ], sortArray: [ { active: null, direction: '' }, { active: null, direction: '' }, { active: null, direction: '' }], ignoreCase: false, text: '', selectedManuscripts: [], selectedWordProperties: [], textQuality: { clean: true, preferEditedText: true }, restrictKorpusOnContext: false, filterValue: '' } protected mapping: Mapping = { current_manuscript_iri: { param: TLN_MANUSCRIPT_PARAM, type: "string" }, quantQuery: { param: TLN_QUANT_QUERY_PARAM, type: "complex" } } constructor(private cacheService: CacheService, private localQueryService: TlnCacheQueryService, protected router: Router, protected activatedRoute: ActivatedRoute, private _dialog: MatBottomSheet) { super(router, activatedRoute); } ngOnInit() { this.tlnQueryService = (this.queryService != null) ? this.queryService : this.localQueryService; this.dataHandler.addHandler('wordProperties', { 'handler': SelectableWordProperty}); this.dataHandler.addHandler('manuscriptPages', { 'handler': Manuscript4Selection}); this.quantDataHandler.addHandler('numResultRows', { 'handler': NumericResultRow, 'process_data': this }); this.dataHandler.setQueryService(this.tlnQueryService); this.quantDataHandler.setQueryService(this.tlnQueryService); this.quantDataHandler.start_processing.subscribe( (started: boolean) =>{ this.isLoadingResults = true; }); this.quantDataHandler.processing_finished.subscribe( (started: boolean) =>{ this.isLoadingResults = false; }); this.dataHandler.getData('wordProperties'); this.dataHandler.getData('manuscriptPages'); this.quantQuery.restrictKorpusOnContext = this.restrictKorpusOnContext if (this.restrictKorpusOnContext){ this.dataHandler['manuscriptPages']['process_data'] = new SelectFromArray(this, { compareValueKey: 'select_manuscript_iri', sourceArrayKey: 'manuscriptPages', targetArrayKey: ['quantQuery', 'selectedManuscripts'], commonPropertyKey: 'id' }) } super.ngOnInit(); } isManuscriptSelected(manuscript: Manuscript4Selection): boolean { return this.restrictKorpusOnContext && this.current_manuscript_iri == manuscript.id } isSelected = (o1: any, o2: any): boolean => { return o1.id == o2.id; } normalLineNumberFormatter = (line: number): string =>{ return String(line); } private markError(error: string){ if (this.quantQuery != null && this.quantQuery.dataKey != undefined && this.quantQuery.dataKey != null){ this.cacheService.removeItem(this.quantQuery.dataKey) this.quantQuery['dataKey'] = null; this.updateParams(); } const matches = error.match(/(.*line\s)([0-9]+)(:.*)/s) if (matches != null && matches.length == 4){ this.errorLine = Number(matches[2]); this.codemirror.codeMirror.setOption('lineNumberFormatter' as any, (line:number): string =>{ if (line == this.errorLine){ return 'E>'; } return String(line) }); this.quantAccordionStatus.query.expanded = true; } } private send() { const error = QueryJson.getSyntaxError(this.query); //console.log('error', error); if (error != ''){ this._dialog.open(QueryErrorComponent, { data: {error: error }}); this.markError(error); } else { this.numResultRows = []; Object.keys(this.quantAccordionStatus).forEach(key =>{this.quantAccordionStatus[key].expanded = false}); if (this.quantQuery.currentTable == 2){ this.quantQuery.currentTable = (this.query != this.getQuery()) ? 1 : 0; this.updateParams(); } - this.quantDataHandler.getData('numResultRows', this.query, 'fusekiResults'); + this.quantDataHandler.getData4Query('numResultRows', this.query, 'fusekiResults'); } } private cancel(){ this.quantDataHandler.stop_processing.emit(true); this.isLoadingResults = false; } private copyToClipboard(curlQueryInput){ curlQueryInput.select(); curlQueryInput.setSelectionRange(0, 99999); /* For mobile devices */ /* Copy the text inside the text field */ document.execCommand("copy"); } protected readParams(params: Params){ const old_current_manuscript_iri = this.current_manuscript_iri super.readParams(params); if (this.current_manuscript_iri != null && old_current_manuscript_iri != this.current_manuscript_iri && this.quantQuery.restrictKorpusOnContext){ this.select_manuscript_iri = this.current_manuscript_iri; this.quantAccordionStatus.scopus.expanded = true; } if (this.quantQuery != null){ if (this.quantQuery.selectedManuscripts.length > 0){ this.select_manuscript_iri = null; } if (this.quantQuery.altQuery != null){ this.query = this.quantQuery.altQuery; this.curlQuery = 'curl ' + this.tlnQueryService.baseUrl + ' -X POST --data \'query=' + encodeURI(this.query) + '\'' this.quantAccordionStatus.wordProperties = { expanded: false, disabled: true }; this.quantAccordionStatus.query.expanded = true; } if (this.quantQuery.dataKey != undefined && this.quantQuery.dataKey != null){ const results = this.cacheService.getItem(this.quantQuery.dataKey); if (results != null){ this.fusekiResults = results; this.numResultRows = NumericResultRow.convertData(this.fusekiResults); if (this.quantQuery.sortArray[this.quantQuery.currentTable].active != null){ const sort = this.quantQuery.sortArray[this.quantQuery.currentTable] if (this.quantQuery.currentTable == 0){ this.numResultRows = this.numResultRows.sort((a, b) => { return this.compare(a[sort.active], b[sort.active], sort.direction === 'asc') }); } else { this.fusekiResults.results.bindings = this.fusekiResults.results.bindings.sort((a, b) => { return this.compare((a[sort.active]['value']), (b[sort.active]['value']), sort.direction === 'asc') }); } } if (this.quantQuery.filterValue != undefined && this.quantQuery.filterValue != null && this.quantQuery.filterValue != ''){ this.numResultRows = this.numResultRows.filter(result =>result.id.startsWith(this.quantQuery.filterValue)); } } } } if (this.query == ''){ this.updateQuery(); } } private compare(a: number | string, b: number | string, isAsc: boolean): number { return (a < b ? -1 : 1) * (isAsc ? 1: -1); } private toggleEnableSelectionList(updateQuery: boolean) { if (this.errorLine > -1){ this.errorLine = -1; this.codemirror.codeMirror.setOption('lineNumberFormatter' as any, (line:number): string =>{return String(line)}) } if (updateQuery) { this.updateQuery(); this.queryHasSyntaxError = false; this.quantAccordionStatus.wordProperties.disabled = false; } else { const queryChanged = this.query != this.getQuery(); this.queryHasSyntaxError = QueryJson.hasSyntaxError(this.query); this.quantAccordionStatus.wordProperties.disabled = queryChanged; this.updateQuery4Ext(queryChanged); if (this.quantAccordionStatus.wordProperties.disabled) { this.quantAccordionStatus.wordProperties.expanded = false; } } } private getQuery(): string { let selectedManuscripts = (this.quantQuery.selectedManuscripts.length == this.manuscriptPages.length) ? [] : this.quantQuery.selectedManuscripts; return this.quantDataHandler['numResultRows'].handler.getSelectableQuery( this.quantQuery.selectedWordProperties, selectedManuscripts, this.quantQuery.textQuality, this.quantQuery.text, this.quantQuery.ignoreCase); } public processData(){ if (this.quantQuery != null && this.quantQuery.dataKey != undefined && this.quantQuery.dataKey != null){ this.cacheService.removeItem(this.quantQuery.dataKey); } const dataKey = TLN_QUANT_ROUTE + Date.now().toString(); this.cacheService.setItem(dataKey, this.fusekiResults); this.quantQuery['dataKey'] = dataKey; this.updateParams(); } private tabChanged(tabChange: MatTabChangeEvent){ this.quantQuery.currentTable = tabChange.index; this.quantQuery.resultIndex = 0; this.updateParams(); } private updateTextQuality(clean: boolean, preferEditedText?: boolean){ this.quantQuery.textQuality = { clean: clean, preferEditedText: preferEditedText }; this.updateQuery(); } private updatePageIndex(pageIndex: number){ if (typeof pageIndex == 'number'){ this.quantQuery.resultIndices[this.quantQuery.currentTable] = pageIndex; this.updateParams(); } } private updateQuantQuery(quantQuery: QuantQuery){ console.log(quantQuery); this.quantQuery = quantQuery; this.updateParams(); } private updateQuery(source?: string) { this.query = this.getQuery(); this.updateQuery4Ext(false); } private updateQuery4Ext(setAltQuery: boolean) { this.curlQuery = 'curl ' + this.tlnQueryService.baseUrl + ' -X POST --data \'query=' + encodeURI(this.query) + '\'' if (setAltQuery) { this.quantQuery.altQuery = this.query } else { this.quantQuery.altQuery = null; } this.updateParams(); } }