diff --git a/app/static/css/queryBuilder.css b/app/static/css/queryBuilder.css index f48a4a64e9a74acb8d2a21849a71fd00b4645634..31c131135d972a0db6663b79c11a4f309afaf23b 100644 --- a/app/static/css/queryBuilder.css +++ b/app/static/css/queryBuilder.css @@ -1,9 +1,6 @@ -#corpus-analysis-concordance-query-builder-input-field-container { - border-bottom: #9E9E9E 1px solid; - height: 60px; -} - #corpus-analysis-concordance-query-builder-input-field { + border-bottom: #9E9E9E 1px solid; + min-height: 38px; margin-top: 23px; } diff --git a/app/static/js/CorpusAnalysis/QueryBuilder.js b/app/static/js/CorpusAnalysis/QueryBuilder.js index 8d86c06b7e711d0d92cd0cd5501b408de321842a..c51bc4a58cf5b453b8ee8441a302318e1cd8b42b 100644 --- a/app/static/js/CorpusAnalysis/QueryBuilder.js +++ b/app/static/js/CorpusAnalysis/QueryBuilder.js @@ -23,7 +23,7 @@ class ConcordanceQueryBuilder { if (modalId === 'corpus-analysis-concordance-exactly-n-token-modal' || modalId === 'corpus-analysis-concordance-between-nm-token-modal') { button.addEventListener('click', () => this.generalFunctions.tokenNMSubmitHandler(modalId)); } else if (modalId === 'corpus-analysis-concordance-exactly-n-character-modal' || modalId === 'corpus-analysis-concordance-between-nm-character-modal') { - button.addEventListener('click', () => this.generalFunctions.characterNMSubmitHandler(modalId)); + button.addEventListener('click', () => this.tokenAttributeBuilderFunctions.characterNMSubmitHandler(modalId)); } }); diff --git a/app/static/js/CorpusAnalysis/QueryBuilder/GeneralFunctionsQueryBuilder.js b/app/static/js/CorpusAnalysis/QueryBuilder/GeneralFunctionsQueryBuilder.js index bfe4fa7b0c200a1cdaf1d92e9626ab88286bec33..d6e0930362e3ffc00312612d3eb782a2450b60d7 100644 --- a/app/static/js/CorpusAnalysis/QueryBuilder/GeneralFunctionsQueryBuilder.js +++ b/app/static/js/CorpusAnalysis/QueryBuilder/GeneralFunctionsQueryBuilder.js @@ -9,6 +9,13 @@ class GeneralFunctionsQueryBuilder { }); } + resetQueryInputField() { + this.elements.queryInputField.innerHTML = ''; + this.addPlaceholder(); + this.updateChipList(); + this.queryPreviewBuilder(); + } + updateChipList() { this.elements.queryChipElements = this.elements.queryInputField.querySelectorAll('.query-component'); } @@ -210,7 +217,7 @@ class GeneralFunctionsQueryBuilder { let input_n = modal.querySelector('.n-m-input[data-value-type="n"]').value; let input_m = modal.querySelector('.n-m-input[data-value-type="m"]') || undefined; input_m = input_m !== undefined ? input_m.value : ''; - let input = `{${input_n},${input_m}}`; + let input = `{${input_n}${input_m !== '' ? ',' : ''}${input_m}}`; let pretty_input = `between ${input_n} and ${input_m} (${input})`; if (input_m === '') { pretty_input = `exactly ${input_n} (${input})`; @@ -220,7 +227,137 @@ class GeneralFunctionsQueryBuilder { instance.close(); this.tokenIncidenceModifierHandler(input, pretty_input); + } + + switchToExpertModeParser() { + let expertModeInputField = document.querySelector('#corpus-analysis-concordance-form-query'); + expertModeInputField.value = ''; + let queryBuilderInputFieldValue = Utils.unescape(document.querySelector('#corpus-analysis-concordance-query-preview').innerHTML.trim()); + if (queryBuilderInputFieldValue !== "") { + expertModeInputField.value = queryBuilderInputFieldValue; + } + } + switchToQueryBuilderParser() { + this.resetQueryInputField(); + let expertModeInputFieldValue = document.querySelector('#corpus-analysis-concordance-form-query').value; + let chipElements = this.parseTextToChip(expertModeInputFieldValue); + for (let chipElement of chipElements) { + this.queryChipFactory(chipElement['type'], chipElement['pretty'], chipElement['query']); + } } + + parseTextToChip(query) { + const parsingElementDict = { + '<s>': { + pretty: 'Sentence Start', + type: 'start-sentence' + }, + '<\/s>': { + pretty: 'Sentence End', + type: 'end-sentence' + }, + '<ent>': { + pretty: 'Entity Start', + type: 'start-empty-entity' + }, + '<ent_type="([A-Z]+)">': { + pretty: '', + type: 'start-entity' + }, + '<\\\/ent(_type)?>': { + pretty: 'Entity End', + type: 'end-entity' + }, + ':: ?match\\.text_[A-Za-z]+="[^"]+"': { + pretty: '', + type: 'text-annotation' + }, + '\\[(word|lemma|pos|simple_pos)=(("[^"]+")|(\\u0027[^\\u0027]+\\u0027)) ?(%c)? ?((\\&|\\|) ?(word|lemma|pos|simple_pos)=(("[^"]+")|(\\u0027[^\\u0027]+\\u0027)) ?(%c)? ?)*\\]': { + pretty: '', + type: 'token' + }, + '\\[\\]': { + pretty: 'Empty Token', + type: 'token' + }, + '(?<!\\[) ?\\+ ?(?![^\\]]\\])': { + pretty: ' one or more (+)', + type: 'token-incidence-modifier' + }, + '(?<!\\[) ?\\* ?(?![^\\]]\\])': { + pretty: 'zero or more (*)', + type: 'token-incidence-modifier' + }, + '(?<!\\[) ?\\? ?(?![^\\]]\\])': { + pretty: 'zero or one (?)', + type: 'token-incidence-modifier' + }, + '(?<!\\[) ?\\{[0-9]+} ?(?![^\\]]\\])': { + pretty: '', + type: 'token-incidence-modifier' + }, + '(?<!\\[) ?\\{[0-9]+(,[0-9]+)?} ?(?![^\\]]\\])': { + pretty: '', + type: 'token-incidence-modifier' + } + + } + + let chipElements = []; + + const regex = new RegExp(`<s>|<\/s>|<ent>|<ent_type="([A-Z]+)">|<\\\/ent(_type)?>|\\[(word|lemma|pos|simple_pos)=(("[^"]+")|(\\u0027[^\\u0027]+\\u0027)) ?(%c)? ?((\\&|\\|) ?(word|lemma|pos|simple_pos)=(("[^"]+")|(\\u0027[^\\u0027]+\\u0027)) ?(%c)? ?)*\\]|:: ?match\\.text_[A-Za-z]+="[^"]+"|(?<!\\[) ?(\\+|\\?|\\*|{[0-9]+(,[0-9]+)?}) ?(?![^\\]]\\])`, 'gi'); + let match; + + while ((match = regex.exec(query)) !== null) { + // This is necessary to avoid infinite loops with zero-width matches + if (match.index === regex.lastIndex) { + regex.lastIndex++; + } + let stringElement = match[0]; + for (let [pattern, chipElement] of Object.entries(parsingElementDict)) { + const parsingRegex = new RegExp(pattern, 'gi'); + if (parsingRegex.exec(stringElement)) { + let prettyText; + switch (pattern) { + case '<ent_type="([A-Z]+)">': + prettyText = `Entity Type=${stringElement.replace(/<ent_type="|">/g, '')}`; + break; + case ':: ?match\\.text_[A-Za-z]+="[^"]+"': + prettyText = stringElement.replace(/:: ?match\.text_|"|"/g, ''); + break; + case '\\[(word|lemma|pos|simple_pos)=(("[^"]+")|(\\u0027[^\\u0027]+\\u0027)) ?(%c)? ?((\\&|\\|) ?(word|lemma|pos|simple_pos)=(("[^"]+")|(\\u0027[^\\u0027]+\\u0027)) ?(%c)? ?)*\\]': + let doubleQuotes = /(word|lemma|pos|simple_pos)="[^"]+"/gi; + let singleQuotes = /(word|lemma|pos|simple_pos)='[^']+'/gi; + if (doubleQuotes.exec(stringElement)) { + prettyText = stringElement.replace(/^\[|\]$|"/g, ''); + } else if (singleQuotes.exec(stringElement)) { + prettyText = stringElement.replace(/^\[|\]$|'/g, ''); + } + prettyText = prettyText.replace(/\&/g, ' and ').replace(/\|/g, ' or '); + break; + case '(?<!\\[) ?\\{[0-9]+} ?(?![^\\]]\\])': + prettyText = `exactly ${stringElement.replace(/{|}/g, '')} (${stringElement})`; + break; + case '(?<!\\[) ?\\{[0-9]+(,[0-9]+)?} ?(?![^\\]]\\])': + prettyText = `between${stringElement.replace(/{|}/g, ' ').replace(',', ' and ')}(${stringElement})`; + break; + default: + prettyText = chipElement.pretty; + break; + } + chipElements.push({ + type: chipElement.type, + pretty: prettyText, + query: stringElement + }); + break; + } + } + } + + return chipElements; + } + } diff --git a/app/static/js/CorpusAnalysis/QueryBuilder/TokenAttributeBuilderFunctionsQueryBuilder.js b/app/static/js/CorpusAnalysis/QueryBuilder/TokenAttributeBuilderFunctionsQueryBuilder.js index 7caa8707b48c8b637bdbfd1daf10f99f7d63f104..1ac6835aab8383ca98705b4fcb97fd6795917785 100644 --- a/app/static/js/CorpusAnalysis/QueryBuilder/TokenAttributeBuilderFunctionsQueryBuilder.js +++ b/app/static/js/CorpusAnalysis/QueryBuilder/TokenAttributeBuilderFunctionsQueryBuilder.js @@ -141,7 +141,7 @@ class TokenAttributeBuilderFunctionsQueryBuilder extends GeneralFunctionsQueryBu this.disableTokenSubmit(); } else { tokenQueryPrettyText += `simple_pos=${this.elements.simplePosSelection.value}`; - tokenQueryCQLText += `simple_pos='${this.elements.simplePosSelection.value}'`; + tokenQueryCQLText += `simple_pos="${this.elements.simplePosSelection.value}"`; this.elements.simplePosSelection.value = ''; } break; diff --git a/app/templates/corpora/_analysis/concordance.html.j2 b/app/templates/corpora/_analysis/concordance.html.j2 index 96c7ef8dce15ccd3d55cc8e6624f446e5f1e11d6..23e1c5b61b24dcfe828ee7ad458deac56ba87220 100644 --- a/app/templates/corpora/_analysis/concordance.html.j2 +++ b/app/templates/corpora/_analysis/concordance.html.j2 @@ -129,6 +129,7 @@ {% macro scripts() %} <script> const corpusAnalysisConcordance = new CorpusAnalysisConcordance(corpusAnalysisApp); + const concordanceQueryBuilder = new ConcordanceQueryBuilder(); let queryBuilderDisplay = document.getElementById("corpus-analysis-concordance-query-builder-display"); let expertModeDisplay = document.getElementById("corpus-analysis-concordance-expert-mode-display"); @@ -138,11 +139,12 @@ if (this.checked) { queryBuilderDisplay.classList.add("hide"); expertModeDisplay.classList.remove("hide"); + concordanceQueryBuilder.generalFunctions.switchToExpertModeParser(); } else { queryBuilderDisplay.classList.remove("hide"); expertModeDisplay.classList.add("hide"); + concordanceQueryBuilder.generalFunctions.switchToQueryBuilderParser(); } }); </script> -{{ query_builder.scripts(id_prefix) }} {% endmacro %} diff --git a/app/templates/corpora/_analysis/query_builder/_expert_mode.html.j2 b/app/templates/corpora/_analysis/query_builder/_expert_mode.html.j2 index f0a1133e96825d56badd24fb302bc344ab273d31..7ebf4643df3d91c6c2d7cd9d190acf6b455f65e6 100644 --- a/app/templates/corpora/_analysis/query_builder/_expert_mode.html.j2 +++ b/app/templates/corpora/_analysis/query_builder/_expert_mode.html.j2 @@ -3,7 +3,7 @@ <form id="corpus-analysis-concordance-expert-mode-form"> <div class="input-field col s12 m9"> <i class="material-icons prefix">search</i> - <input class="validate corpus-analysis-action" id="corpus-analysis-concordance-form-query" name="query" type="text" required pattern=".*\S+.*" placeholder="Type in your query or use the Query Builder on the right"></input> + <input class="validate corpus-analysis-action" id="corpus-analysis-concordance-form-query" name="query" type="text" required pattern=".*\S+.*" placeholder="Type in your query via CQL"></input> <span class="error-color-text helper-text hide" id="corpus-analysis-concordance-error"></span> <a class="modal-trigger" data-manual-modal-chapter="manual-modal-cqp-query-language" href="#manual-modal" style="margin-left: 40px;"><i class="material-icons" style="font-size: inherit;">help</i> Corpus Query Language tutorial</a> <span> | </span> diff --git a/app/templates/corpora/_analysis/query_builder/_query_builder.html.j2 b/app/templates/corpora/_analysis/query_builder/_query_builder.html.j2 index d6343e95ed595d88655df5cf36bab4ac264afa64..c4f63943b65220847dc82795b0dbeb09c924f95d 100644 --- a/app/templates/corpora/_analysis/query_builder/_query_builder.html.j2 +++ b/app/templates/corpora/_analysis/query_builder/_query_builder.html.j2 @@ -434,8 +434,8 @@ </div> {% endmacro %} -{% macro scripts(id_prefix) %} +{# {% macro scripts(id_prefix) %} <script> const concordanceQueryBuilder = new ConcordanceQueryBuilder(); </script> -{% endmacro %} +{% endmacro %} #}