diff --git a/app/__init__.py b/app/__init__.py index d9311c9a..145f2b3e 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -73,4 +73,7 @@ def create_app(config: Config = Config) -> Flask: from .settings import bp as settings_blueprint app.register_blueprint(settings_blueprint, url_prefix='/settings') + from .test import bp as test_blueprint + app.register_blueprint(test_blueprint, url_prefix='/test') + return app diff --git a/app/static/js/CorpusAnalysis/QueryBuilder.js b/app/static/js/CorpusAnalysis/QueryBuilder.js new file mode 100644 index 00000000..0fe8d50c --- /dev/null +++ b/app/static/js/CorpusAnalysis/QueryBuilder.js @@ -0,0 +1,609 @@ +class ConcordanceQueryBuilder { + + constructor() { + + + this.positionalAttrList = { + 'emptyToken': {prettyText: 'empty token', cqlOpening: '[', tokenValue:'', cqlClosing: ']'}, + 'word': {prettyText: 'word', cqlOpening: '[word=', tokenValue: '', cqlClosing: ']'}, + 'lemma': {prettyText: 'lemma', cqlOpening: '[lemma=', tokenValue:'', cqlClosing: ']'}, + 'pos': {prettyText: 'pos', cqlOpening: '[pos=', tokenValue:'', cqlClosing: ']'}, + 'simplePos': {prettyText: 'simple_pos', cqlOpening: '[simple_pos=', tokenValue:'', cqlClosing: ']'} + } + + this.elements = { + + counter: 0, + yourQueryContent: [], + queryContent:[], + + //#region QueryBuilder Elements + concordanceQueryBuilder: document.querySelector('#concordance-query-builder'), + concordanceQueryBuilderButton: document.querySelector('#concordance-query-builder-button'), + positionalAttr: document.querySelector('#token-attr'), + structuralAttr: document.querySelector('#structural-attr'), + buttonPreparer: document.querySelector('#button-preparer'), + yourQuery: document.querySelector('#your-query'), + insertQueryButton: document.querySelector('#insert-query-button'), + tokenQuery: document.querySelector('#token-query'), + tokenBuilderContent: document.querySelector('#token-builder-content'), + buildTokenButton: document.querySelector('#build-token-button'), + + + //#endregion QueryBuilder Elements + + //#region Strucutral Attributes + + sentence:document.querySelector('#sentence'), + entity: document.querySelector('#entity'), + textAnnotation: document.querySelector('#text-annotation'), + + entityBuilder: document.querySelector('#entity-builder'), + englishEntType: document.querySelector('#english-ent-type'), + emptyEntity: document.querySelector('#empty-entity'), + + textAnnotationBuilder: document.querySelector('#text-annotation-builder'), + textAnnotationOptions: document.querySelector('#text-annotation-options'), + textAnnotationInput: document.querySelector('#text-annotation-input'), + textAnnotationSubmit: document.querySelector('#text-annotation-submit'), + //#endregion Structural Attributes + + //#region Token Attributes + tokenCounter: 0, + + lemma: document.querySelector('#lemma'), + emptyToken: document.querySelector('#empty-token'), + word: document.querySelector('#word'), + lemma: document.querySelector('#lemma'), + pos: document.querySelector('#pos'), + simplePosButton: document.querySelector('#simple-pos-button'), + incidenceModifiers: document.querySelector('[data-target="incidence-modifiers"]'), + or: document.querySelector('#or'), + and: document.querySelector('#and'), + + + //#region Word and Lemma Elements + wordBuilder: document.querySelector('#word-builder'), + lemmaBuilder: document.querySelector('#lemma-builder'), + inputOptions: document.querySelector('#input-options'), + wordInput: document.querySelector('#word-input'), + wordSubmit: document.querySelector('#word-submit'), + lemmaInput: document.querySelector('#lemma-input'), + lemmaSubmit: document.querySelector('#lemma-submit'), + ignoreCaseCheckbox : document.querySelector('#ignore-case-checkbox'), + ignoreCase: document.querySelector('input[type="checkbox"]'), + wildcardChar: document.querySelector('#wildcard-char'), + optionGroup: document.querySelector('#option-group'), + incidenceModifiersTB: document.querySelector('[data-target="incidence-modifiers-text-builder"]'), + //#endregion Word and Lemma Elements + + //#region posBuilder Elements + posBuilder: document.querySelector('#pos-builder'), + englishPos: document.querySelector('#english-pos'), + germanPos: document.querySelector('#german-pos'), + //#endregion posBuilder Elements + + //#region simple_posBuilder Elements + simplePosBuilder: document.querySelector('#simplepos-builder'), + simplePos: document.querySelector('#simple-pos'), + //#endregion simple_posBuilder Elements + + //#region incidence modifiers + oneOrMore: document.querySelector('#one-or-more'), + zeroOrMore: document.querySelector('#zero-or-more'), + zeroOrOne: document.querySelector('#zero-or-one'), + exactlyN: document.querySelector('#exactly-n'), + betweenNM: document.querySelector('#between-n-m'), + + oneOrMoreTB: document.querySelector('#one-or-more-tb'), + zeroOrMoreTB: document.querySelector('#zero-or-more-tb'), + zeroOrOneTB: document.querySelector('#zero-or-one-tb'), + exactlyNTB: document.querySelector('#exactly-n-tb'), + betweenNMTB: document.querySelector('#between-n-m-tb') + //#endregion incidence modifiers + + //#endregion Token Attributes + } + + this.elements.concordanceQueryBuilderButton.addEventListener('click', () => {this.clearAll();}); + + //#region Structural Attribute Event Listeners + this.elements.sentence.addEventListener('click', () => {this.addSentence();}); + this.elements.entity.addEventListener('click', () => {this.addEntity();}); + this.elements.textAnnotation.addEventListener('click', () => {this.addTextAnnotation();}); + + this.elements.englishEntType.addEventListener('change', () => {this.englishEntTypeHandler();}); + this.elements.emptyEntity.addEventListener('click', () => {this.emptyEntityButton();}); + + this.elements.textAnnotationSubmit.addEventListener('click', () => {this.textAnnotationSubmitHandler();}); + + //#endregion + + //#region Token Attribute Event Listeners + this.elements.buildTokenButton.addEventListener('click', () => {this.addToken();}); + this.elements.emptyToken.addEventListener('click', () => {this.emptyTokenHandler();}); + this.elements.word.addEventListener('click', () => {this.wordBuilder();}); + this.elements.lemma.addEventListener('click', () => {this.lemmaBuilder();}); + this.elements.pos.addEventListener('click', () => {this.posBuilder();}); + this.elements.simplePosButton.addEventListener('click', () => {this.simplePosBuilder();}); + this.elements.or.addEventListener('click', () => {this.orHandler();}); + this.elements.and.addEventListener('click', () => {this.andHandler();}); + + this.elements.ignoreCase.addEventListener('change', () => {this.inputOptionHandler(this.elements.ignoreCase);}); + this.elements.wildcardChar.addEventListener('click', () => {this.inputOptionHandler(this.elements.wildcardChar);}); + this.elements.optionGroup.addEventListener('click', () => {this.inputOptionHandler(this.elements.optionGroup);}); + this.elements.wordSubmit.addEventListener('click', () => {this.textSubmit();}); + this.elements.lemmaSubmit.addEventListener('click', () => {this.textSubmit();}); + + this.elements.englishPos.addEventListener('change', () => {this.englishPosHandler();}); + this.elements.germanPos.addEventListener('change', () => {this.germanPosHandler();}); + this.elements.simplePos.addEventListener('change', () => {this.simplePosHandler();}); + + this.elements.oneOrMore.addEventListener('click', () => {this.incidenceModifiersHandler(this.elements.oneOrMore);}); + this.elements.zeroOrMore.addEventListener('click', () => {this.incidenceModifiersHandler(this.elements.zeroOrMore);}); + this.elements.zeroOrOne.addEventListener('click', () => {this.incidenceModifiersHandler(this.elements.zeroOrOne);}); + this.elements.exactlyN.addEventListener('click', () => {this.incidenceModifiersHandler(this.elements.exactlyN);}); + this.elements.betweenNM.addEventListener('click', () => {this.incidenceModifiersHandler(this.elements.betweenNM);}); + + this.elements.oneOrMoreTB.addEventListener('click', () => {this.incidenceModifiersHandlerTB(this.elements.oneOrMoreTB);}); + this.elements.zeroOrMoreTB.addEventListener('click', () => {this.incidenceModifiersHandlerTB(this.elements.zeroOrMoreTB);}); + this.elements.zeroOrOneTB.addEventListener('click', () => {this.incidenceModifiersHandlerTB(this.elements.zeroOrOneTB);}); + this.elements.exactlyNTB.addEventListener('click', () => {this.incidenceModifiersHandlerTB(this.elements.exactlyNTB);}); + this.elements.betweenNMTB.addEventListener('click', () => {this.incidenceModifiersHandlerTB(this.elements.betweenNMTB);}); + + + //#endregion Token Attribute Event Listeners + + + } + + //#region Structural Attribute Builder Functions + addSentence() { + this.hideEverything(); + if(this.elements.sentence.text === "End Sentence") { + this.buttonfactory("end-sentence", "Sentence End"); + this.elements.sentence.innerHTML = "Sentence"; + } else { + this.buttonfactory("start-sentence", "Sentence Start"); + this.elements.insertQueryButton.classList.remove('disabled'); + this.elements.counter += 1; + this.elements.queryContent.push('sentence'); + this.elements.sentence.innerHTML = "End Sentence"; + } + } + + addEntity() { + if(this.elements.entity.text === "End Entity"){ + this.buttonfactory("end-entity", "Entity End"); + this.elements.entity.innerHTML = "Entity"; + + } else { + this.hideEverything(); + this.elements.entityBuilder.classList.remove('hide'); + } + } + + englishEntTypeHandler() { + + this.buttonfactory("start-entity", "Entity Type=" + this.elements.englishEntType.value); + this.elements.insertQueryButton.classList.remove('disabled'); + this.elements.counter+=1; + this.elements.entity.innerHTML = "End Entity"; + this.hideEverything(); + + } + + emptyEntityButton() { + this.buttonfactory("start-entity", "Entity Start"); + this.elements.counter+=1; + this.elements.entity.innerHTML = "End Entity"; + this.hideEverything(); + } + + addTextAnnotation() { + this.hideEverything(); + this.elements.textAnnotationBuilder.classList.remove('hide'); + + } + + textAnnotationSubmitHandler() { + this.buttonfactory("tA" + this.elements.textAnnotationOptions.value, this.elements.textAnnotationOptions.value + "= " + this.elements.textAnnotationInput.value) + this.elements.counter+=1; + this.hideEverything(); + this.elements.textAnnotationInput.value = ""; + } + + + //#endregion Structural Attribute Builder Functions + + //#region Token Attribute Builder Functions + emptyTokenHandler() { + this.hideEverything(); + this.elements.incidenceModifiers.classList.remove("hide"); + this.tokenButtonfactory("emptyToken", "empty token"); + this.buttonDisabler("empty"); + this.elements.buildTokenButton.classList.remove('disabled'); + this.elements.tokenCounter+=1; + } + + wordBuilder() { + this.elements.incidenceModifiers.classList.add("hide"); + this.hideEverything(); + this.elements.wordBuilder.classList.remove('hide'); + this.elements.inputOptions.classList.remove('hide'); + this.elements.ignoreCaseCheckbox.classList.remove('hide'); + } + + lemmaBuilder() { + this.elements.incidenceModifiers.classList.add("hide"); + this.hideEverything(); + this.elements.lemmaBuilder.classList.remove('hide'); + this.elements.inputOptions.classList.remove('hide'); + this.elements.ignoreCaseCheckbox.classList.remove('hide'); + } + + inputOptionHandler(elem) { + let input; + + if (this.elements.wordBuilder.classList.contains("hide") === false){ + input = this.elements.wordInput; + }else{ + input = this.elements.lemmaInput; + } + + if (elem === this.elements.optionGroup) { + input.value += "( option1 | option2 )"; + let firstIndex = input.value.indexOf("option1"); + let lastIndex = firstIndex + "option1".length; + input.focus(); + input.setSelectionRange(firstIndex, lastIndex); + }else if (elem === this.elements.wildcardChar){ + input.value += "."; + } + } + + textSubmit() { + this.buttonDisabler(); + this.elements.incidenceModifiers.classList.remove('disabled'); + let c; + + if (this.elements.ignoreCase.checked){ + c = "%c"; + }else{ + c = ""; + } + + if (this.elements.wordBuilder.classList.contains("hide") === false){ + this.tokenButtonfactory("word", "word=" + this.elements.wordInput.value + c); + this.elements.buildTokenButton.classList.remove('disabled'); + this.elements.tokenCounter+=1; + this.elements.wordInput.value = ""; + }else if (this.elements.lemmaBuilder.classList.contains("hide") === false){ + this.tokenButtonfactory("lemma", "lemma=" + this.elements.lemmaInput.value + c); + this.elements.buildTokenButton.classList.remove('disabled'); + this.elements.tokenCounter+=1; + this.elements.lemmaInput.value = ""; + } + } + + posBuilder() { + this.hideEverything(); + this.elements.incidenceModifiers.classList.remove("hide"); + this.elements.positionalAttr.appendChild(this.elements.incidenceModifiers); + this.elements.posBuilder.classList.remove('hide'); + } + + englishPosHandler() { + this.buttonDisabler(); + this.tokenButtonfactory("pos", "pos=" + this.elements.englishPos.value); + this.elements.buildTokenButton.classList.remove('disabled'); + this.elements.tokenCounter+=1; + } + + germanPosHandler() { + this.buttonDisabler(); + this.tokenButtonfactory("pos", "pos=" + this.elements.germanPos.value); + this.elements.buildTokenButton.classList.remove('disabled'); + this.elements.tokenCounter+=1; + } + + simplePosBuilder() { + this.hideEverything(); + this.elements.incidenceModifiers.classList.remove("hide"); + this.elements.positionalAttr.appendChild(this.elements.incidenceModifiers); + this.elements.simplePosBuilder.classList.remove('hide'); + } + + simplePosHandler() { + this.buttonDisabler(); + this.tokenButtonfactory("simplePos", "simple_pos=" + this.elements.simplePos.value); + this.elements.buildTokenButton.classList.remove('disabled'); + this.elements.tokenCounter+=1; + } + + incidenceModifiersHandler(input) { + if (input.id === "exactly-n"){ + + } else if (input.id === "between-n-m"){ + + } else { + this.tokenButtonfactory("incidenceModifier", input.text); + } + this.elements.incidenceModifiers.classList.add('disabled'); + this.elements.tokenCounter+=1; + } + + incidenceModifiersHandlerTB(elem) { + let input; + + if (this.elements.wordBuilder.classList.contains("hide") === false){ + input = this.elements.wordInput; + }else{ + input = this.elements.lemmaInput; + } + + if (elem.id === "exactly-n-tb"){ + input.value += elem.dataset.token; + let index = input.value.lastIndexOf("{n}"); + let instance = M.Dropdown.getInstance(this.elements.incidenceModifiersTB); + instance.close(); + input.focus(); + input.setSelectionRange(index+1, index+2); + }else if (elem.id === "between-n-m-tb") { + input.value += input.dataset.token; + let index = input.value.lastIndexOf("{n,m}"); + let instance = M.Dropdown.getInstance(this.elements.incidenceModifiersTB); + instance.close(); + input.focus(); + input.setSelectionRange(index+1, index+2); + }else { + input.value += " " + elem.dataset.token; + } + } + + orHandler() { + this.elements.positionalAttr.appendChild(this.elements.incidenceModifiers); + this.buttonDisabler("condition"); + this.hideEverything(); + this.tokenButtonfactory("or", "or"); + this.elements.buildTokenButton.classList.remove('disabled'); + this.elements.tokenCounter+=1; + } + + andHandler() { + this.elements.positionalAttr.appendChild(this.elements.incidenceModifiers); + this.buttonDisabler("condition"); + this.hideEverything(); + this.tokenButtonfactory("and", "and"); + this.elements.buildTokenButton.classList.remove('disabled'); + this.elements.tokenCounter+=1; + } + + hideEverything(){ + + this.elements.wordBuilder.classList.add('hide'); + this.elements.lemmaBuilder.classList.add('hide'); + this.elements.ignoreCaseCheckbox.classList.add('hide'); + this.elements.inputOptions.classList.add('hide'); + this.elements.posBuilder.classList.add('hide'); + this.elements.simplePosBuilder.classList.add('hide'); + this.elements.entityBuilder.classList.add('hide'); + this.elements.textAnnotationBuilder.classList.add('hide'); + } + + buttonDisabler(attr = "token") { + let tokenButtonList = this.elements.positionalAttr.querySelectorAll('a'); + if (attr === "start"){ + tokenButtonList.forEach(element => { + if (element.id === "or"){ + element.classList.add('disabled'); + } else if (element.id === "and"){ + element.classList.add('disabled'); + }else if (element.dataset.target == "incidence-modifiers") { + element.classList.add('disabled'); + } else if (element.id === "empty-token"){ + element.classList.remove('disabled'); + } else { + element.classList.remove('disabled'); + } + }); + }else if (attr === "condition"){ + tokenButtonList.forEach(element => { + if (element.id === "or"){ + element.classList.add('disabled'); + } else if (element.id === "and"){ + element.classList.add('disabled'); + }else if (element.dataset.target == "incidence-modifiers") { + element.classList.add('disabled'); + } else if (element.id === "empty-token"){ + element.classList.add('disabled'); + } else { + element.classList.remove('disabled'); + } + }); + }else if (attr === "empty") { + tokenButtonList.forEach(element => { + if (element.id == "or"){ + element.classList.add('disabled'); + } else if (element.id == "and"){ + element.classList.add('disabled'); + }else if (element.dataset.target == "incidence-modifiers") { + element.classList.remove('disabled'); + } else { + element.classList.add('disabled'); + } + }); + }else{ + tokenButtonList.forEach(element => { + if (element.id == "or"){ + element.classList.remove('disabled'); + } else if (element.id == "and"){ + element.classList.remove('disabled'); + }else if (element.dataset.target == "incidence-modifiers") { + element.classList.remove('disabled'); + } else { + element.classList.add('disabled'); + } + }); + } + + } + + tokenButtonfactory(dataType, prettyText) { + this.elements.buttonPreparer.innerHTML += "" + prettyText + ""; + let tokenDummyButton = this.elements.buttonPreparer.querySelector(":first-child"); + tokenDummyButton.addEventListener('click', () => {this.deleteTokenAttr(tokenDummyButton);}); + this.elements.tokenQuery.appendChild(tokenDummyButton); + } + + deleteTokenAttr(attr){ + let nodesList = attr.parentElement.childNodes; + let indexOfAttr; + + for (let i = 0; i < nodesList.length; i++) { + if(nodesList[i] === attr){ + indexOfAttr = i; + } + } + + if(indexOfAttr>0){ + if (attr.dataset.type === "or" || attr.dataset.type === "and") { + this.elements.tokenQuery.removeChild(nodesList[indexOfAttr+1]); + this.elements.tokenCounter-=1; + + }else { + if (nodesList[indexOfAttr-1].dataset.type === "or" || nodesList[indexOfAttr-1].dataset.type === "and"){ + this.elements.tokenQuery.removeChild(nodesList[indexOfAttr-1]) + this.elements.tokenCounter-=1; + } + } + } + + this.elements.tokenQuery.removeChild(attr); + this.elements.tokenCounter-=1; + + if(this.elements.tokenCounter === 0){ + this.elements.buildTokenButton.classList.add('disabled'); + this.buttonDisabler("start"); + this.hideEverything(); + } + } + + addToken() { + let tokenQueryContent = ""; + this.elements.tokenQuery.childNodes.forEach(element => { + tokenQueryContent += " " + element.text + " " + }); + this.buttonfactory("token", tokenQueryContent); + tokenQueryContent = ""; + this.elements.tokenQuery.innerHTML = ""; + this.elements.tokenCounter = 0; + this.elements.buildTokenButton.classList.add('disabled'); + this.hideEverything(); + this.buttonDisabler("start") + } + + //#endregion Token Attribute Builder Functions + + //#region General Functions + + buttonfactory(dataType, prettyText) { + this.elements.buttonPreparer.innerHTML += "" + prettyText + ""; + let dummyButton = this.elements.buttonPreparer.querySelector(":first-child"); + dummyButton.addEventListener('click', () => {this.deleteAttr(dummyButton);}); + this.elements.yourQuery.appendChild(dummyButton); + } + + deleteAttr(attr) { + + let siblingList = []; + let nodesList = attr.parentElement.childNodes; + let indexOfAttr; + let connectedElement; + + // Why nodesList.indexOf(attr) doesn't work?! + for (let i = 0; i < nodesList.length; i++) { + if(nodesList[i] === attr){ + indexOfAttr = i; + } + } + switch (attr.dataset.type) { + case "start-sentence": + for (let i = indexOfAttr; i < nodesList.length; i++) { + if (nodesList[i].dataset.type === "end-sentence"){ + siblingList.push(nodesList[i]); + } + } + connectedElement = siblingList[0]; + break; + case "end-sentence": + for (let i = 0; i < indexOfAttr; i++) { + if (nodesList[i].dataset.type === "start-sentence"){ + siblingList.push(nodesList[i]); + } + } + connectedElement = siblingList[siblingList.length -1]; + break; + case "start-entity": + for (let i = indexOfAttr; i < nodesList.length; i++) { + if (nodesList[i].dataset.type === "end-entity"){ + siblingList.push(nodesList[i]); + } + } + connectedElement = siblingList[0]; + break; + case "end-entity": + for (let i = 0; i < indexOfAttr; i++) { + if (nodesList[i].dataset.type === "start-entity"){ + siblingList.push(nodesList[i]); + } + } + connectedElement = siblingList[siblingList.length -1]; + break; + default: + connectedElement = ""; + break; + } + + if (connectedElement !== ""){ + this.elements.yourQuery.removeChild(connectedElement); + } + this.elements.yourQuery.removeChild(attr); + + this.elements.counter -= 1; + if(this.elements.counter === 0){ + this.elements.insertQueryButton.classList.add('disabled'); + + } + } + + insertQuery() { + + + // this.elements.tokenQuery.childNodes.forEach(element => { + // this.elements.tokenQueryContent.push([element.dataset.type, element.text]); + // }); + // for (let key in this.positionalAttrList) { + // if (this.positionalAttrList.hasOwnProperty(key)){ + // for (let i = 0; i < this.elements.tokenQueryContent.length; i++) { + // if(key === this.elements.tokenQueryContent[i][0]){ + // console.log(this.positionalAttrList[key]['prettyText']); + // } + // } + // } + // } + } + + clearAll() { + this.elements.tokenQuery.innerHTML = ""; + this.elements.tokenCounter = 0; + this.elements.counter = 0; + this.elements.buildTokenButton.classList.add('disabled'); + this.elements.insertQueryButton.classList.add('disabled'); + this.hideEverything(); + this.buttonDisabler("start"); + this.elements.yourQuery.innerHTML = ""; + } + //#endregion General Functions +} + diff --git a/app/templates/_scripts.html.j2 b/app/templates/_scripts.html.j2 index 89a672dc..54095e00 100644 --- a/app/templates/_scripts.html.j2 +++ b/app/templates/_scripts.html.j2 @@ -9,6 +9,7 @@ 'js/CorpusAnalysis/CorpusAnalysisApp.js', 'js/CorpusAnalysis/CorpusAnalysisConcordance.js', 'js/CorpusAnalysis/CorpusAnalysisReader.js', + 'js/CorpusAnalysis/QueryBuilder.js', 'js/JobStatusNotifier.js', 'js/RessourceDisplays/RessourceDisplay.js', 'js/RessourceDisplays/CorpusDisplay.js', diff --git a/app/templates/test/analyse_corpus.concordance.html.j2 b/app/templates/test/analyse_corpus.concordance.html.j2 new file mode 100644 index 00000000..31320f70 --- /dev/null +++ b/app/templates/test/analyse_corpus.concordance.html.j2 @@ -0,0 +1,117 @@ +
+ | Source | +Left context | +KWIC | +Right Context | ++ |
---|
Query your corpus with the CQP query language utilizing a KWIC view.
+Inspect your corpus in detail with a full text view, including annotations.
+