mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2024-12-26 11:24:18 +00:00
Compare commits
2 Commits
e8fe67d290
...
1f40002249
Author | SHA1 | Date | |
---|---|---|---|
|
1f40002249 | ||
|
1ff9c8bfe3 |
@ -1,52 +0,0 @@
|
|||||||
class ConcordanceQueryBuilder {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
|
|
||||||
this.elements = new ElementReferencesQueryBuilder();
|
|
||||||
this.generalFunctions = new GeneralFunctionsQueryBuilder(this.elements);
|
|
||||||
this.tokenAttributeBuilderFunctions = new TokenAttributeBuilderFunctionsQueryBuilder(this.elements);
|
|
||||||
this.structuralAttributeBuilderFunctions = new StructuralAttributeBuilderFunctionsQueryBuilder(this.elements);
|
|
||||||
|
|
||||||
// Eventlisteners for the incidence modifiers. There are two different types of incidence modifiers: token and character incidence modifiers.
|
|
||||||
document.querySelectorAll('.incidence-modifier-selection').forEach(button => {
|
|
||||||
let dropdownId = button.parentNode.parentNode.id;
|
|
||||||
if (dropdownId === 'corpus-analysis-concordance-token-incidence-modifiers-dropdown') {
|
|
||||||
button.addEventListener('click', () => this.generalFunctions.tokenIncidenceModifierHandler(button.dataset.token, button.innerHTML));
|
|
||||||
} else if (dropdownId === 'corpus-analysis-concordance-character-incidence-modifiers-dropdown') {
|
|
||||||
button.addEventListener('click', () => this.tokenAttributeBuilderFunctions.characterIncidenceModifierHandler(button));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Eventlisteners for the submit of n- and m-values of the incidence modifier modal for "exactly n" or "between n and m".
|
|
||||||
document.querySelectorAll('.n-m-submit-button').forEach(button => {
|
|
||||||
let modalId = button.dataset.modalId;
|
|
||||||
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.tokenAttributeBuilderFunctions.characterNMSubmitHandler(modalId));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.querySelector('#corpus-analysis-concordance-text-annotation-submit').addEventListener('click', () => this.structuralAttributeBuilderFunctions.textAnnotationSubmitHandler());
|
|
||||||
|
|
||||||
this.elements.positionalAttrModal = M.Modal.init(
|
|
||||||
document.querySelector('#corpus-analysis-concordance-positional-attr-modal'),
|
|
||||||
{
|
|
||||||
onOpenStart: () => {
|
|
||||||
this.tokenAttributeBuilderFunctions.preparePositionalAttrModal();
|
|
||||||
},
|
|
||||||
onCloseStart: () => {
|
|
||||||
this.tokenAttributeBuilderFunctions.resetPositionalAttrModal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
this.elements.structuralAttrModal = M.Modal.init(
|
|
||||||
document.querySelector('#corpus-analysis-concordance-structural-attr-modal'),
|
|
||||||
{
|
|
||||||
onCloseStart: () => {
|
|
||||||
this.structuralAttributeBuilderFunctions.resetStructuralAttrModal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,505 +0,0 @@
|
|||||||
class GeneralFunctionsQueryBuilder {
|
|
||||||
constructor(elements) {
|
|
||||||
this.elements = elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleClass(elements, className, action){
|
|
||||||
elements.forEach(element => {
|
|
||||||
document.querySelector(`[data-toggle-area="${element}"]`).classList[action](className);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
resetQueryInputField() {
|
|
||||||
this.elements.queryInputField.innerHTML = '';
|
|
||||||
this.addPlaceholder();
|
|
||||||
this.updateChipList();
|
|
||||||
this.queryPreviewBuilder();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateChipList() {
|
|
||||||
this.elements.queryChipElements = this.elements.queryInputField.querySelectorAll('.query-component');
|
|
||||||
}
|
|
||||||
|
|
||||||
removePlaceholder() {
|
|
||||||
let placeholder = this.elements.queryInputField.querySelector('#corpus-analysis-concordance-query-builder-input-field-placeholder');
|
|
||||||
if (placeholder && this.elements.queryInputField !== undefined) {
|
|
||||||
this.elements.queryInputField.innerHTML = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addPlaceholder() {
|
|
||||||
let placeholder = Utils.HTMLToElement('<span id="corpus-analysis-concordance-query-builder-input-field-placeholder">Click on a button to add a query component</span>');
|
|
||||||
this.elements.queryInputField.appendChild(placeholder);
|
|
||||||
}
|
|
||||||
|
|
||||||
resetMaterializeSelection(selectionElements, value = "default") {
|
|
||||||
selectionElements.forEach(selectionElement => {
|
|
||||||
if (selectionElement.querySelector(`option[value=${value}]`) !== null) {
|
|
||||||
selectionElement.querySelector(`option[value=${value}]`).selected = true;
|
|
||||||
}
|
|
||||||
let instance = M.FormSelect.getInstance(selectionElement);
|
|
||||||
instance.destroy();
|
|
||||||
M.FormSelect.init(selectionElement);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
submitQueryChipElement(dataType = undefined, prettyQueryText = undefined, queryText = undefined, index = null, isClosingTag = false, isEditable = false) {
|
|
||||||
if (this.elements.editingModusOn) {
|
|
||||||
let editedQueryChipElement = this.elements.queryChipElements[this.elements.editedQueryChipElementIndex];
|
|
||||||
editedQueryChipElement.dataset.type = dataType;
|
|
||||||
editedQueryChipElement.dataset.query = queryText;
|
|
||||||
editedQueryChipElement.firstChild.textContent = prettyQueryText;
|
|
||||||
this.updateChipList();
|
|
||||||
this.queryPreviewBuilder();
|
|
||||||
} else {
|
|
||||||
this.queryChipFactory(dataType, prettyQueryText, queryText, index, isClosingTag, isEditable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
queryChipFactory(dataType, prettyQueryText, queryText, index = null, isClosingTag = false, isEditable = false) {
|
|
||||||
// Creates a new query chip element, adds Eventlisteners for selection, deletion and drag and drop and appends it to the query input field.
|
|
||||||
queryText = Utils.escape(queryText);
|
|
||||||
prettyQueryText = Utils.escape(prettyQueryText);
|
|
||||||
let queryChipElement = Utils.HTMLToElement(
|
|
||||||
`
|
|
||||||
<span class="chip query-component" data-type="${dataType}" data-query="${queryText}" draggable="true" data-closing-tag="${isClosingTag}">
|
|
||||||
${prettyQueryText}${isEditable ? '<i class="material-icons chip-action-button" data-chip-action="edit" style="padding-left:5px; font-size:18px; cursor:pointer;">edit</i>': ''}
|
|
||||||
${isClosingTag ? '<i class="material-icons chip-action-button" data-chip-action="lock" style="padding-top:5px; font-size:20px; cursor:pointer;">lock_open</i>' : '<i class="material-icons close chip-action-button" data-chip-action="delete">close</i>'}
|
|
||||||
</span>
|
|
||||||
`
|
|
||||||
);
|
|
||||||
this.actionListeners(queryChipElement);
|
|
||||||
queryChipElement.addEventListener('dragstart', this.handleDragStart.bind(this, queryChipElement));
|
|
||||||
queryChipElement.addEventListener('dragend', this.handleDragEnd);
|
|
||||||
|
|
||||||
// Ensures that metadata is always at the end of the query and if an index is given, inserts the query chip at the given index and if there is a closing tag, inserts the query chip before the closing tag.
|
|
||||||
this.removePlaceholder();
|
|
||||||
let lastChild = this.elements.queryInputField.lastChild;
|
|
||||||
let isLastChildTextAnnotation = lastChild && lastChild.dataset.type === 'text-annotation';
|
|
||||||
if (!index) {
|
|
||||||
let closingTagElement = this.elements.queryInputField.querySelector('[data-closing-tag="true"]');
|
|
||||||
if (closingTagElement) {
|
|
||||||
index = Array.from(this.elements.queryInputField.children).indexOf(closingTagElement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (index || isLastChildTextAnnotation) {
|
|
||||||
let insertingElement = isLastChildTextAnnotation ? lastChild : this.elements.queryChipElements[index];
|
|
||||||
this.elements.queryInputField.insertBefore(queryChipElement, insertingElement);
|
|
||||||
} else {
|
|
||||||
this.elements.queryInputField.appendChild(queryChipElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateChipList();
|
|
||||||
this.queryPreviewBuilder();
|
|
||||||
}
|
|
||||||
|
|
||||||
actionListeners(queryChipElement) {
|
|
||||||
let notQuantifiableDataTypes = ['start-sentence', 'end-sentence', 'start-entity', 'start-empty-entity', 'end-entity', 'token-incidence-modifier'];
|
|
||||||
queryChipElement.addEventListener('click', (event) => {
|
|
||||||
if (event.target.classList.contains('chip')) {
|
|
||||||
if (!notQuantifiableDataTypes.includes(queryChipElement.dataset.type)) {
|
|
||||||
this.selectChipElement(queryChipElement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let chipActionButtons = queryChipElement.querySelectorAll('.chip-action-button');
|
|
||||||
chipActionButtons.forEach(button => {
|
|
||||||
button.addEventListener('click', (event) => {
|
|
||||||
if (event.target.dataset.chipAction === 'delete') {
|
|
||||||
this.deleteChipElement(queryChipElement);
|
|
||||||
} else if (event.target.dataset.chipAction === 'edit') {
|
|
||||||
this.editChipElement(queryChipElement);
|
|
||||||
} else if (event.target.dataset.chipAction === 'lock') {
|
|
||||||
this.lockClosingChipElement(queryChipElement);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
editChipElement(queryChipElement) {
|
|
||||||
//TODO: Split this function into smaller functionss
|
|
||||||
this.elements.editingModusOn = true;
|
|
||||||
this.elements.editedQueryChipElementIndex = Array.from(this.elements.queryInputField.children).indexOf(queryChipElement);
|
|
||||||
switch (queryChipElement.dataset.type) {
|
|
||||||
case 'start-entity':
|
|
||||||
this.elements.structuralAttrModal.open();
|
|
||||||
this.toggleClass(['entity-builder'], 'hide', 'remove');
|
|
||||||
this.toggleEditingAreaStructureAttrModal('add');
|
|
||||||
let entType = queryChipElement.dataset.query.replace(/<ent_type="|">/g, '');
|
|
||||||
let isEnglishEntType = this.elements.englishEntTypeSelection.querySelector(`option[value=${entType}]`) !== null;
|
|
||||||
let selection = isEnglishEntType ? this.elements.englishEntTypeSelection : this.elements.germanEntTypeSelection;
|
|
||||||
this.resetMaterializeSelection([selection], entType);
|
|
||||||
break;
|
|
||||||
case 'text-annotation':
|
|
||||||
this.elements.structuralAttrModal.open();
|
|
||||||
this.toggleClass(['text-annotation-builder'], 'hide', 'remove');
|
|
||||||
this.toggleEditingAreaStructureAttrModal('add');
|
|
||||||
let [textAnnotationSelection, textAnnotationContent] = queryChipElement.dataset.query
|
|
||||||
.replace(/:: ?match\.text_|"|"/g, '')
|
|
||||||
.split('=');
|
|
||||||
this.resetMaterializeSelection([this.elements.textAnnotationSelection], textAnnotationSelection);
|
|
||||||
this.elements.textAnnotationInput.value = textAnnotationContent;
|
|
||||||
break;
|
|
||||||
case 'token':
|
|
||||||
//This regex searches for word or lemma or pos or simple_pos="any string within single or double quotes" followed by one or no ignore case markers, followed by one or no condition characters.
|
|
||||||
let regex = new RegExp('(word|lemma|pos|simple_pos)=(("[^"]+")|(\\\\u0027[^\\\\u0027]+\\\\u0027)) ?(%c)? ?(\\&|\\|)?', 'gm');
|
|
||||||
let m;
|
|
||||||
let queryElementsContent = [];
|
|
||||||
while ((m = regex.exec(queryChipElement.dataset.query)) !== null) {
|
|
||||||
// This is necessary to avoid infinite loops with zero-width matches
|
|
||||||
if (m.index === regex.lastIndex) {
|
|
||||||
regex.lastIndex++;
|
|
||||||
}
|
|
||||||
let tokenAttr = m[1];
|
|
||||||
// Passes english-pos by default so that the template is added. In editTokenChipElement it is then checked whether it is english-pos or german-pos.
|
|
||||||
if (tokenAttr === 'pos') {
|
|
||||||
tokenAttr = 'english-pos';
|
|
||||||
}
|
|
||||||
let tokenValue = m[2].replace(/"|'/g, '');
|
|
||||||
let ignoreCase = false;
|
|
||||||
let condition = undefined;
|
|
||||||
m.forEach((match) => {
|
|
||||||
if (match === "%c") {
|
|
||||||
ignoreCase = true;
|
|
||||||
} else if (match === "&") {
|
|
||||||
condition = "and";
|
|
||||||
} else if (match === "|") {
|
|
||||||
condition = "or";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
queryElementsContent.push({tokenAttr: tokenAttr, tokenValue: tokenValue, ignoreCase: ignoreCase, condition: condition});
|
|
||||||
}
|
|
||||||
this.editTokenChipElement(queryElementsContent);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
editTokenChipElement(queryElementsContent) {
|
|
||||||
this.elements.positionalAttrModal.open();
|
|
||||||
queryElementsContent.forEach((queryElement) => {
|
|
||||||
this.resetMaterializeSelection([this.elements.positionalAttrSelection], queryElement.tokenAttr);
|
|
||||||
this.preparePositionalAttrModal();
|
|
||||||
switch (queryElement.tokenAttr) {
|
|
||||||
case 'word':
|
|
||||||
case 'lemma':
|
|
||||||
this.elements.tokenBuilderContent.querySelector('input').value = queryElement.tokenValue;
|
|
||||||
break;
|
|
||||||
case 'english-pos':
|
|
||||||
// English-pos is selected by default. Then it is checked whether the passed token value occurs in the english-pos selection. If not, the selection is reseted and changed to german-pos.
|
|
||||||
let selection = this.elements.tokenBuilderContent.querySelector('select');
|
|
||||||
queryElement.tokenAttr = selection.querySelector(`option[value=${queryElement.tokenValue}]`) ? 'english-pos' : 'german-pos';
|
|
||||||
this.resetMaterializeSelection([this.elements.positionalAttrSelection], queryElement.tokenAttr);
|
|
||||||
this.preparePositionalAttrModal();
|
|
||||||
this.resetMaterializeSelection([this.elements.tokenBuilderContent.querySelector('select')], queryElement.tokenValue);
|
|
||||||
break;
|
|
||||||
case 'simple_pos':
|
|
||||||
this.resetMaterializeSelection([this.elements.tokenBuilderContent.querySelector('select')], queryElement.tokenValue);
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (queryElement.ignoreCase) {
|
|
||||||
this.elements.ignoreCaseCheckbox.checked = true;
|
|
||||||
}
|
|
||||||
if (queryElement.condition !== undefined) {
|
|
||||||
this.conditionHandler(queryElement.condition, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
lockClosingChipElement(queryChipElement) {
|
|
||||||
queryChipElement.dataset.closingTag = 'false';
|
|
||||||
let lockIcon = queryChipElement.querySelector('[data-chip-action="lock"]');
|
|
||||||
lockIcon.textContent = 'lock';
|
|
||||||
//TODO: Write unlock-Function?
|
|
||||||
lockIcon.dataset.chipAction = 'unlock';
|
|
||||||
|
|
||||||
// let chipIndex = Array.from(this.elements.queryInputField.children).indexOf(queryChipElement);
|
|
||||||
// this.submitQueryChipElement(queryChipElement.dataset.type, queryChipElement.firstChild.textContent, queryChipElement.dataset.query, chipIndex+1);
|
|
||||||
// this.deleteChipElement(queryChipElement);
|
|
||||||
// this.updateChipList();
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteChipElement(attr) {
|
|
||||||
let elementIndex = Array.from(this.elements.queryInputField.children).indexOf(attr);
|
|
||||||
switch (attr.dataset.type) {
|
|
||||||
case 'start-sentence':
|
|
||||||
this.deletingClosingTagHandler(elementIndex, 'end-sentence');
|
|
||||||
break;
|
|
||||||
case 'start-entity':
|
|
||||||
this.deletingClosingTagHandler(elementIndex, 'end-entity');
|
|
||||||
break;
|
|
||||||
case 'token':
|
|
||||||
console.log(Array.from(this.elements.queryInputField.children)[elementIndex+1]);
|
|
||||||
let nextElement = Array.from(this.elements.queryInputField.children)[elementIndex+1];
|
|
||||||
if (nextElement.dataset.type === 'token-incidence-modifier') {
|
|
||||||
this.deleteChipElement(nextElement);
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
this.elements.queryInputField.removeChild(attr);
|
|
||||||
if (this.elements.queryInputField.children.length === 0) {
|
|
||||||
this.addPlaceholder();
|
|
||||||
}
|
|
||||||
this.updateChipList();
|
|
||||||
this.queryPreviewBuilder();
|
|
||||||
}
|
|
||||||
|
|
||||||
deletingClosingTagHandler(elementIndex, closingTagType) {
|
|
||||||
let closingTags = this.elements.queryInputField.querySelectorAll(`[data-type="${closingTagType}"]`);
|
|
||||||
for (let i = 0; i < closingTags.length; i++) {
|
|
||||||
let closingTag = closingTags[i];
|
|
||||||
|
|
||||||
if (Array.from(this.elements.queryInputField.children).indexOf(closingTag) > elementIndex) {
|
|
||||||
this.deleteChipElement(closingTag);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDragStart(queryChipElement, event) {
|
|
||||||
// is called when a query chip is dragged. It creates a dropzone (in form of a chip) for the dragged chip and adds it to the query input field.
|
|
||||||
let queryChips = this.elements.queryInputField.querySelectorAll('.query-component');
|
|
||||||
setTimeout(() => {
|
|
||||||
let targetChipElement = Utils.HTMLToElement('<span class="chip drop-target">Drop here</span>');
|
|
||||||
for (let element of queryChips) {
|
|
||||||
if (element === queryChipElement.nextSibling) {continue;}
|
|
||||||
let targetChipClone = targetChipElement.cloneNode(true);
|
|
||||||
if (element === queryChipElement && queryChips[queryChips.length - 1] !== element) {
|
|
||||||
queryChips[queryChips.length - 1].insertAdjacentElement('afterend', targetChipClone);
|
|
||||||
} else {
|
|
||||||
element.insertAdjacentElement('beforebegin', targetChipClone);
|
|
||||||
}
|
|
||||||
this.addDragDropListeners(targetChipClone, queryChipElement);
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDragEnd(event) {
|
|
||||||
document.querySelectorAll('.drop-target').forEach(target => target.remove());
|
|
||||||
}
|
|
||||||
|
|
||||||
addDragDropListeners(targetChipClone, queryChipElement) {
|
|
||||||
targetChipClone.addEventListener('dragover', (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
});
|
|
||||||
targetChipClone.addEventListener('dragenter', (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
event.target.style.borderStyle = 'solid dotted';
|
|
||||||
});
|
|
||||||
targetChipClone.addEventListener('dragleave', (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
event.target.style.borderStyle = 'hidden';
|
|
||||||
});
|
|
||||||
targetChipClone.addEventListener('drop', (event) => {
|
|
||||||
let dropzone = event.target;
|
|
||||||
dropzone.parentElement.replaceChild(queryChipElement, dropzone);
|
|
||||||
this.updateChipList();
|
|
||||||
this.queryPreviewBuilder();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
queryPreviewBuilder() {
|
|
||||||
// Builds the query preview in the form of pure CQL and displays it in the query preview field.
|
|
||||||
let queryPreview = document.querySelector('#corpus-analysis-concordance-query-preview');
|
|
||||||
let queryInputFieldContent = [];
|
|
||||||
this.elements.queryChipElements.forEach(element => {
|
|
||||||
let queryElement = element.dataset.query;
|
|
||||||
if (queryElement !== undefined) {
|
|
||||||
queryElement = Utils.escape(queryElement);
|
|
||||||
}
|
|
||||||
queryInputFieldContent.push(queryElement);
|
|
||||||
});
|
|
||||||
|
|
||||||
let queryString = queryInputFieldContent.join(' ');
|
|
||||||
let replacements = {
|
|
||||||
' +': '+',
|
|
||||||
' *': '*',
|
|
||||||
' ?': '?',
|
|
||||||
' {': '{'
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let key in replacements) {
|
|
||||||
queryString = queryString.replace(key, replacements[key]);
|
|
||||||
}
|
|
||||||
queryString += ';';
|
|
||||||
|
|
||||||
queryPreview.innerHTML = queryString;
|
|
||||||
queryPreview.parentNode.classList.toggle('hide', queryString === ';');
|
|
||||||
}
|
|
||||||
|
|
||||||
selectChipElement(attr) {
|
|
||||||
document.querySelectorAll('.chip.teal').forEach(element => {
|
|
||||||
if (element !== attr) {
|
|
||||||
element.classList.remove('teal', 'lighten-2');
|
|
||||||
this.toggleClass(['token-incidence-modifiers'], 'disabled', 'add');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.toggleClass(['token-incidence-modifiers'], 'disabled', 'toggle');
|
|
||||||
attr.classList.toggle('teal');
|
|
||||||
attr.classList.toggle('lighten-5');
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenIncidenceModifierHandler(incidenceModifier, incidenceModifierPretty) {
|
|
||||||
// Adds a token incidence modifier to the query input field.
|
|
||||||
let selectedChip = this.elements.queryInputField.querySelector('.chip.teal');
|
|
||||||
let selectedChipIndex = Array.from(this.elements.queryInputField.children).indexOf(selectedChip);
|
|
||||||
this.submitQueryChipElement('token-incidence-modifier', incidenceModifierPretty, incidenceModifier, selectedChipIndex+1);
|
|
||||||
this.selectChipElement(selectedChip);
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenNMSubmitHandler(modalId) {
|
|
||||||
// Adds a token incidence modifier (exactly n or between n and m) to the query input field.
|
|
||||||
let modal = document.querySelector(`#${modalId}`);
|
|
||||||
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 !== '' ? ',' : ''}${input_m}}`;
|
|
||||||
let pretty_input = `between ${input_n} and ${input_m} (${input})`;
|
|
||||||
if (input_m === '') {
|
|
||||||
pretty_input = `exactly ${input_n} (${input})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
let instance = M.Modal.getInstance(modal);
|
|
||||||
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 !== "" && 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.submitQueryChipElement(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 = [];
|
|
||||||
let regexPattern = Object.keys(parsingElementDict).map(pattern => `(${pattern})`).join('|');
|
|
||||||
const regex = new RegExp(regexPattern, '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)) {
|
|
||||||
// Creating the pretty text for the chip element
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
|||||||
class StructuralAttributeBuilderFunctionsQueryBuilder extends GeneralFunctionsQueryBuilder {
|
|
||||||
constructor(elements) {
|
|
||||||
super(elements);
|
|
||||||
this.elements = elements;
|
|
||||||
|
|
||||||
document.querySelectorAll('[data-structural-attr-modal-action-button]').forEach(button => {
|
|
||||||
button.addEventListener('click', () => {
|
|
||||||
this.actionButtonInStrucAttrModalHandler(button.dataset.structuralAttrModalActionButton);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
document.querySelector('.ent-type-selection-action[data-ent-type="any"]').addEventListener('click', () => {
|
|
||||||
this.submitQueryChipElement('start-empty-entity', 'Entity Start', '<ent>');
|
|
||||||
this.submitQueryChipElement('end-entity', 'Entity End', '</ent>', null, true);
|
|
||||||
this.elements.structuralAttrModal.close();
|
|
||||||
});
|
|
||||||
document.querySelector('.ent-type-selection-action[data-ent-type="english"]').addEventListener('change', (event) => {
|
|
||||||
this.submitQueryChipElement('start-entity', `Entity Type=${event.target.value}`, `<ent_type="${event.target.value}">`, null, false, true);
|
|
||||||
if (!this.elements.editingModusOn) {
|
|
||||||
this.submitQueryChipElement('end-entity', 'Entity End', '</ent_type>', null, true);
|
|
||||||
}
|
|
||||||
this.elements.structuralAttrModal.close();
|
|
||||||
});
|
|
||||||
document.querySelector('.ent-type-selection-action[data-ent-type="german"]').addEventListener('change', (event) => {
|
|
||||||
this.submitQueryChipElement('start-entity', `Entity Type=${event.target.value}`, `<ent_type="${event.target.value}">`, null, false, true);
|
|
||||||
if (!this.elements.editingModusOn) {
|
|
||||||
this.submitQueryChipElement('end-entity', 'Entity End', '</ent_type>', null, true);
|
|
||||||
}
|
|
||||||
this.elements.structuralAttrModal.close();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
resetStructuralAttrModal() {
|
|
||||||
this.resetMaterializeSelection([this.elements.englishEntTypeSelection, this.elements.germanEntTypeSelection]);
|
|
||||||
this.resetMaterializeSelection([this.elements.textAnnotationSelection], 'address');
|
|
||||||
this.elements.textAnnotationInput.value = '';
|
|
||||||
|
|
||||||
this.toggleClass(['entity-builder', 'text-annotation-builder'], 'hide', 'add');
|
|
||||||
this.toggleEditingAreaStructureAttrModal('remove');
|
|
||||||
this.elements.editingModusOn = false;
|
|
||||||
this.elements.editedQueryChipElementIndex = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleEditingAreaStructureAttrModal(action) {
|
|
||||||
// If the user edits a query chip element, the corresponding editing area is displayed and the other areas are hidden or disabled.
|
|
||||||
this.toggleClass(['sentence-button', 'entity-button', 'text-annotation-button', 'any-type-entity-button'], 'disabled', action);
|
|
||||||
}
|
|
||||||
|
|
||||||
actionButtonInStrucAttrModalHandler(action) {
|
|
||||||
switch (action) {
|
|
||||||
case 'sentence':
|
|
||||||
this.submitQueryChipElement('start-sentence', 'Sentence Start', '<s>');
|
|
||||||
this.submitQueryChipElement('end-sentence', 'Sentence End', '</s>', null, true);
|
|
||||||
this.elements.structuralAttrModal.close();
|
|
||||||
break;
|
|
||||||
case 'entity':
|
|
||||||
this.toggleClass(['entity-builder'], 'hide', 'toggle');
|
|
||||||
this.toggleClass(['text-annotation-builder'], 'hide', 'add');
|
|
||||||
break;
|
|
||||||
case 'meta-data':
|
|
||||||
this.toggleClass(['text-annotation-builder'], 'hide', 'toggle');
|
|
||||||
this.toggleClass(['entity-builder'], 'hide', 'add');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
textAnnotationSubmitHandler() {
|
|
||||||
let noValueMetadataMessage = document.querySelector('#corpus-analysis-concordance-no-value-metadata-message');
|
|
||||||
let textAnnotationSubmit = document.querySelector('#corpus-analysis-concordance-text-annotation-submit');
|
|
||||||
let textAnnotationInput = document.querySelector('#corpus-analysis-concordance-text-annotation-input');
|
|
||||||
let textAnnotationOptions = document.querySelector('#corpus-analysis-concordance-text-annotation-options');
|
|
||||||
|
|
||||||
if (textAnnotationInput.value === '') {
|
|
||||||
textAnnotationSubmit.classList.add('red');
|
|
||||||
noValueMetadataMessage.classList.remove('hide');
|
|
||||||
setTimeout(() => {
|
|
||||||
textAnnotationSubmit.classList.remove('red');
|
|
||||||
}, 500);
|
|
||||||
setTimeout(() => {
|
|
||||||
noValueMetadataMessage.classList.add('hide');
|
|
||||||
}, 3000);
|
|
||||||
} else {
|
|
||||||
let queryText = `:: match.text_${textAnnotationOptions.value}="${textAnnotationInput.value}"`;
|
|
||||||
this.submitQueryChipElement('text-annotation', `${textAnnotationOptions.value}=${textAnnotationInput.value}`, queryText, null, false, true);
|
|
||||||
this.elements.structuralAttrModal.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,264 +0,0 @@
|
|||||||
class TokenAttributeBuilderFunctionsQueryBuilder extends GeneralFunctionsQueryBuilder {
|
|
||||||
constructor(elements) {
|
|
||||||
super(elements);
|
|
||||||
this.elements = elements;
|
|
||||||
|
|
||||||
this.elements.positionalAttrSelection.addEventListener('change', () => {
|
|
||||||
this.preparePositionalAttrModal();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Options for positional attribute selection
|
|
||||||
document.querySelectorAll('.positional-attr-options-action-button[data-options-action]').forEach(button => {
|
|
||||||
button.addEventListener('click', () => {this.actionButtonInOptionSectionHandler(button.dataset.optionsAction);});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.elements.tokenSubmitButton.addEventListener('click', () => {this.addTokenToQuery();});
|
|
||||||
}
|
|
||||||
|
|
||||||
resetPositionalAttrModal() {
|
|
||||||
let originalSelectionList =
|
|
||||||
`
|
|
||||||
<option value="word" selected>word</option>
|
|
||||||
<option value="lemma" >lemma</option>
|
|
||||||
<option value="english-pos">english pos</option>
|
|
||||||
<option value="german-pos">german pos</option>
|
|
||||||
<option value="simple_pos">simple_pos</option>
|
|
||||||
<option value="empty-token">empty token</option>
|
|
||||||
`;
|
|
||||||
this.elements.positionalAttrSelection.innerHTML = originalSelectionList;
|
|
||||||
this.elements.tokenQuery.innerHTML = '';
|
|
||||||
this.elements.tokenBuilderContent.innerHTML = '';
|
|
||||||
this.toggleClass(['input-field-options'], 'hide', 'remove');
|
|
||||||
this.toggleClass(['incidence-modifiers', 'or', 'and'], 'disabled', 'add');
|
|
||||||
this.resetMaterializeSelection([this.elements.positionalAttrSelection], "word");
|
|
||||||
this.elements.ignoreCaseCheckbox.checked = false;
|
|
||||||
this.elements.editingModusOn = false;
|
|
||||||
this.elements.editedQueryChipElementIndex = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
preparePositionalAttrModal() {
|
|
||||||
let selection = this.elements.positionalAttrSelection.value;
|
|
||||||
if (selection !== 'empty-token') {
|
|
||||||
let selectionTemplate = document.querySelector(`.token-builder-section[data-token-builder-section="${selection}"]`);
|
|
||||||
let selectionTemplateClone = selectionTemplate.content.cloneNode(true);
|
|
||||||
|
|
||||||
this.elements.tokenBuilderContent.innerHTML = '';
|
|
||||||
this.elements.tokenBuilderContent.appendChild(selectionTemplateClone);
|
|
||||||
if (this.elements.tokenBuilderContent.querySelector('select') !== null) {
|
|
||||||
let selectElement = this.elements.tokenBuilderContent.querySelector('select');
|
|
||||||
M.FormSelect.init(selectElement);
|
|
||||||
selectElement.addEventListener('change', () => {this.optionToggleHandler();});
|
|
||||||
} else {
|
|
||||||
this.elements.tokenBuilderContent.querySelector('input').addEventListener('input', () => {this.optionToggleHandler();});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.optionToggleHandler();
|
|
||||||
|
|
||||||
if (selection === 'word' || selection === 'lemma') {
|
|
||||||
this.toggleClass(['input-field-options'], 'hide', 'remove');
|
|
||||||
} else if (selection === 'empty-token'){
|
|
||||||
this.addTokenToQuery();
|
|
||||||
} else {
|
|
||||||
this.toggleClass(['input-field-options'], 'hide', 'add');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenInputCheck(elem) {
|
|
||||||
return elem.querySelector('select') !== null ? elem.querySelector('select') : elem.querySelector('input');
|
|
||||||
}
|
|
||||||
|
|
||||||
optionToggleHandler() {
|
|
||||||
let input = this.tokenInputCheck(this.elements.tokenBuilderContent);
|
|
||||||
if (input.value === '' && this.elements.editingModusOn === false) {
|
|
||||||
this.toggleClass(['incidence-modifiers', 'or', 'and'], 'disabled', 'add');
|
|
||||||
} else if (this.elements.positionalAttrSelection.querySelectorAll('option').length === 1) {
|
|
||||||
this.toggleClass(['and'], 'disabled', 'add');
|
|
||||||
} else {
|
|
||||||
this.toggleClass(['incidence-modifiers', 'or', 'and'], 'disabled', 'remove');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
disableTokenSubmit() {
|
|
||||||
this.elements.tokenSubmitButton.classList.add('red');
|
|
||||||
this.elements.noValueMessage.classList.remove('hide');
|
|
||||||
setTimeout(() => {
|
|
||||||
this.elements.tokenSubmitButton.classList.remove('red');
|
|
||||||
}, 500);
|
|
||||||
setTimeout(() => {
|
|
||||||
this.elements.noValueMessage.classList.add('hide');
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
addTokenToQuery() {
|
|
||||||
let tokenQueryPrettyText = '';
|
|
||||||
let tokenQueryCQLText = '';
|
|
||||||
let input;
|
|
||||||
let kindOfToken = this.kindOfTokenCheck(this.elements.positionalAttrSelection.value);
|
|
||||||
|
|
||||||
// Takes all rows of the token query (if there is a query concatenation).
|
|
||||||
// Adds their contents to tokenQueryPrettyText and tokenQueryCQLText, which will later be expanded with the current input field.
|
|
||||||
let tokenQueryRows = this.elements.tokenQuery.querySelectorAll('.row');
|
|
||||||
tokenQueryRows.forEach(row => {
|
|
||||||
let ignoreCaseCheckbox = row.querySelector('input[type="checkbox"]');
|
|
||||||
let c = ignoreCaseCheckbox !== null && ignoreCaseCheckbox.checked ? ' %c' : '';
|
|
||||||
let tokenQueryRowInput = this.tokenInputCheck(row.querySelector('.token-query-template-content'));
|
|
||||||
let tokenQueryKindOfToken = this.kindOfTokenCheck(tokenQueryRowInput.closest('.input-field').dataset.kindOfToken);
|
|
||||||
let tokenConditionPrettyText = row.querySelector('[data-condition-pretty-text]').dataset.conditionPrettyText;
|
|
||||||
let tokenConditionCQLText = row.querySelector('[data-condition-cql-text]').dataset.conditionCqlText;
|
|
||||||
tokenQueryPrettyText += `${tokenQueryKindOfToken}=${tokenQueryRowInput.value}${c} ${tokenConditionPrettyText} `;
|
|
||||||
tokenQueryCQLText += `${tokenQueryKindOfToken}="${tokenQueryRowInput.value}"${c} ${tokenConditionCQLText}`;
|
|
||||||
});
|
|
||||||
if (kindOfToken === 'empty-token') {
|
|
||||||
tokenQueryPrettyText += 'empty token';
|
|
||||||
} else {
|
|
||||||
let c = this.elements.ignoreCaseCheckbox.checked ? ' %c' : '';
|
|
||||||
input = this.tokenInputCheck(this.elements.tokenBuilderContent);
|
|
||||||
tokenQueryPrettyText += `${kindOfToken}=${input.value}${c}`;
|
|
||||||
tokenQueryCQLText += `${kindOfToken}="${input.value}"${c}`;
|
|
||||||
}
|
|
||||||
// isTokenQueryInvalid looks if a valid value is passed. If the input fields/dropdowns are empty (isTokenQueryInvalid === true), no token is added.
|
|
||||||
if (this.elements.positionalAttrSelection.value !== 'empty-token' && input.value === '') {
|
|
||||||
this.disableTokenSubmit();
|
|
||||||
} else {
|
|
||||||
tokenQueryCQLText = `[${tokenQueryCQLText}]`;
|
|
||||||
this.submitQueryChipElement('token', tokenQueryPrettyText, tokenQueryCQLText, null, false, kindOfToken === 'empty-token' ? false : true);
|
|
||||||
this.elements.positionalAttrModal.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kindOfTokenCheck(kindOfToken) {
|
|
||||||
return kindOfToken === 'english-pos' || kindOfToken === 'german-pos' ? 'pos' : kindOfToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
actionButtonInOptionSectionHandler(elem) {
|
|
||||||
let input = this.tokenInputCheck(this.elements.tokenBuilderContent);
|
|
||||||
switch (elem) {
|
|
||||||
case 'option-group':
|
|
||||||
input.value += '(option1|option2)';
|
|
||||||
let firstIndex = input.value.indexOf('option1');
|
|
||||||
let lastIndex = firstIndex + 'option1'.length;
|
|
||||||
input.focus();
|
|
||||||
input.setSelectionRange(firstIndex, lastIndex);
|
|
||||||
break;
|
|
||||||
case 'wildcard-char':
|
|
||||||
input.value += '.';
|
|
||||||
break;
|
|
||||||
case 'and':
|
|
||||||
this.conditionHandler('and');
|
|
||||||
break;
|
|
||||||
case 'or':
|
|
||||||
this.conditionHandler('or');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
this.optionToggleHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
characterIncidenceModifierHandler(elem) {
|
|
||||||
let input = this.tokenInputCheck(this.elements.tokenBuilderContent);
|
|
||||||
input.value += elem.dataset.token;
|
|
||||||
}
|
|
||||||
|
|
||||||
characterNMSubmitHandler(modalId) {
|
|
||||||
let modal = document.querySelector(`#${modalId}`);
|
|
||||||
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 instance = M.Modal.getInstance(modal);
|
|
||||||
instance.close();
|
|
||||||
let tokenInput = this.tokenInputCheck(this.elements.tokenBuilderContent);
|
|
||||||
tokenInput.value += '{' + input + '}';
|
|
||||||
}
|
|
||||||
|
|
||||||
conditionHandler(conditionText, editMode = false) {
|
|
||||||
let tokenQueryTemplateClone = this.elements.tokenQueryTemplate.content.cloneNode(true);
|
|
||||||
tokenQueryTemplateClone.querySelector('.token-query-template-content').appendChild(this.elements.tokenBuilderContent.firstElementChild);
|
|
||||||
let notSelectedButton = tokenQueryTemplateClone.querySelector(`[data-condition-pretty-text]:not([data-condition-pretty-text="${conditionText}"])`);
|
|
||||||
let deleteButton = tokenQueryTemplateClone.querySelector(`[data-token-query-content-action="delete"]`);
|
|
||||||
deleteButton.addEventListener('click', (event) => {
|
|
||||||
this.deleteTokenQueryRow(event.target);
|
|
||||||
});
|
|
||||||
notSelectedButton.parentNode.removeChild(notSelectedButton);
|
|
||||||
this.elements.tokenQuery.appendChild(tokenQueryTemplateClone);
|
|
||||||
|
|
||||||
// Deleting the options which do not make sense in the context of the condition like "word" AND "word". Also sets selection default.
|
|
||||||
let selectionDefault = "word";
|
|
||||||
let optionDeleteList = ['empty-token'];
|
|
||||||
if (conditionText === 'and') {
|
|
||||||
switch (this.elements.positionalAttrSelection.value) {
|
|
||||||
case 'english-pos' || 'german-pos':
|
|
||||||
optionDeleteList.push('english-pos', 'german-pos');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
optionDeleteList.push(this.elements.positionalAttrSelection.value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let originalSelectionList =
|
|
||||||
`
|
|
||||||
<option value="word" selected>word</option>
|
|
||||||
<option value="lemma" >lemma</option>
|
|
||||||
<option value="english-pos">english pos</option>
|
|
||||||
<option value="german-pos">german pos</option>
|
|
||||||
<option value="simple_pos">simple_pos</option>
|
|
||||||
`;
|
|
||||||
this.elements.positionalAttrSelection.innerHTML = originalSelectionList;
|
|
||||||
M.FormSelect.init(this.elements.positionalAttrSelection);
|
|
||||||
}
|
|
||||||
let lastTokenQueryRow = this.elements.tokenQuery.lastElementChild;
|
|
||||||
if(lastTokenQueryRow.querySelector('[data-kind-of-token="word"]') || lastTokenQueryRow.querySelector('[data-kind-of-token="lemma"]')) {
|
|
||||||
this.appendIgnoreCaseCheckbox(lastTokenQueryRow.querySelector('.token-query-template-content'), this.elements.ignoreCaseCheckbox.checked);
|
|
||||||
}
|
|
||||||
this.elements.ignoreCaseCheckbox.checked = false;
|
|
||||||
this.setTokenSelection(selectionDefault, optionDeleteList);
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteTokenQueryRow(deleteButton) {
|
|
||||||
let deletedRow = deleteButton.closest('.row');
|
|
||||||
let condition = deletedRow.querySelector('[data-condition-pretty-text]').dataset.conditionPrettyText;
|
|
||||||
if (condition === 'and') {
|
|
||||||
let kindOfToken = deletedRow.querySelector('[data-kind-of-token]').dataset.kindOfToken;
|
|
||||||
switch (kindOfToken) {
|
|
||||||
case 'english-pos' || 'german-pos':
|
|
||||||
this.createOptionElementForPosAttrSelection('english-pos');
|
|
||||||
this.createOptionElementForPosAttrSelection('german-pos');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
this.createOptionElementForPosAttrSelection(kindOfToken);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
M.FormSelect.init(this.elements.positionalAttrSelection);
|
|
||||||
}
|
|
||||||
deletedRow.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
createOptionElementForPosAttrSelection(kindOfToken) {
|
|
||||||
let option = document.createElement('option');
|
|
||||||
option.value = kindOfToken;
|
|
||||||
option.text = kindOfToken;
|
|
||||||
this.elements.positionalAttrSelection.appendChild(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
appendIgnoreCaseCheckbox(parentElement, checked = false) {
|
|
||||||
let ignoreCaseCheckboxClone = document.querySelector('#ignore-case-checkbox-template').content.cloneNode(true);
|
|
||||||
parentElement.appendChild(ignoreCaseCheckboxClone);
|
|
||||||
M.Tooltip.init(parentElement.querySelectorAll('.tooltipped'));
|
|
||||||
if (checked) {
|
|
||||||
parentElement.querySelector('input[type="checkbox"]').checked = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setTokenSelection(selection, optionDeleteList) {
|
|
||||||
optionDeleteList.forEach(option => {
|
|
||||||
if (this.elements.positionalAttrSelection.querySelector(`option[value=${option}]`) !== null) {
|
|
||||||
this.elements.positionalAttrSelection.querySelector(`option[value=${option}]`).remove();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.resetMaterializeSelection([this.elements.positionalAttrSelection], selection);
|
|
||||||
this.preparePositionalAttrModal();
|
|
||||||
}
|
|
||||||
}
|
|
937
app/static/js/CorpusAnalysis/query-builder/index.js
Normal file
937
app/static/js/CorpusAnalysis/query-builder/index.js
Normal file
@ -0,0 +1,937 @@
|
|||||||
|
class ConcordanceQueryBuilder {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.elements = new ElementReferencesQueryBuilder();
|
||||||
|
|
||||||
|
//#region QB Constructor
|
||||||
|
// Eventlisteners for the incidence modifiers. There are two different types of incidence modifiers: token and character incidence modifiers.
|
||||||
|
document.querySelectorAll('.incidence-modifier-selection').forEach(button => {
|
||||||
|
let dropdownId = button.parentNode.parentNode.id;
|
||||||
|
if (dropdownId === 'corpus-analysis-concordance-token-incidence-modifiers-dropdown') {
|
||||||
|
button.addEventListener('click', () => this.tokenIncidenceModifierHandler(button.dataset.token, button.innerHTML));
|
||||||
|
} else if (dropdownId === 'corpus-analysis-concordance-character-incidence-modifiers-dropdown') {
|
||||||
|
button.addEventListener('click', () => this.characterIncidenceModifierHandler(button));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Eventlisteners for the submit of n- and m-values of the incidence modifier modal for "exactly n" or "between n and m".
|
||||||
|
document.querySelectorAll('.n-m-submit-button').forEach(button => {
|
||||||
|
let modalId = button.dataset.modalId;
|
||||||
|
if (modalId === 'corpus-analysis-concordance-exactly-n-token-modal' || modalId === 'corpus-analysis-concordance-between-nm-token-modal') {
|
||||||
|
button.addEventListener('click', () => this.tokenNMSubmitHandler(modalId));
|
||||||
|
} else if (modalId === 'corpus-analysis-concordance-exactly-n-character-modal' || modalId === 'corpus-analysis-concordance-between-nm-character-modal') {
|
||||||
|
button.addEventListener('click', () => this.characterNMSubmitHandler(modalId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelector('#corpus-analysis-concordance-text-annotation-submit').addEventListener('click', () => this.textAnnotationSubmitHandler());
|
||||||
|
|
||||||
|
this.elements.positionalAttrModal = M.Modal.init(
|
||||||
|
document.querySelector('#corpus-analysis-concordance-positional-attr-modal'),
|
||||||
|
{
|
||||||
|
onOpenStart: () => {
|
||||||
|
this.preparePositionalAttrModal();
|
||||||
|
},
|
||||||
|
onCloseStart: () => {
|
||||||
|
this.resetPositionalAttrModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.elements.structuralAttrModal = M.Modal.init(
|
||||||
|
document.querySelector('#corpus-analysis-concordance-structural-attr-modal'),
|
||||||
|
{
|
||||||
|
onCloseStart: () => {
|
||||||
|
this.resetStructuralAttrModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let queryBuilderDisplay = document.getElementById("corpus-analysis-concordance-query-builder-display");
|
||||||
|
let expertModeDisplay = document.getElementById("corpus-analysis-concordance-expert-mode-display");
|
||||||
|
let expertModeSwitch = document.getElementById("corpus-analysis-concordance-expert-mode-switch");
|
||||||
|
|
||||||
|
expertModeSwitch.addEventListener("change", () => {
|
||||||
|
const isChecked = expertModeSwitch.checked;
|
||||||
|
if (isChecked) {
|
||||||
|
queryBuilderDisplay.classList.add("hide");
|
||||||
|
expertModeDisplay.classList.remove("hide");
|
||||||
|
this.switchToExpertModeParser();
|
||||||
|
} else {
|
||||||
|
queryBuilderDisplay.classList.remove("hide");
|
||||||
|
expertModeDisplay.classList.add("hide");
|
||||||
|
this.switchToQueryBuilderParser();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//#endregion QB Constructor
|
||||||
|
|
||||||
|
//#region Structural Attribute Builder Constructor
|
||||||
|
document.querySelectorAll('[data-structural-attr-modal-action-button]').forEach(button => {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
this.actionButtonInStrucAttrModalHandler(button.dataset.structuralAttrModalActionButton);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
document.querySelector('.ent-type-selection-action[data-ent-type="any"]').addEventListener('click', () => {
|
||||||
|
this.submitQueryChipElement('start-empty-entity', 'Entity Start', '<ent>');
|
||||||
|
this.submitQueryChipElement('end-entity', 'Entity End', '</ent>', null, true);
|
||||||
|
this.elements.structuralAttrModal.close();
|
||||||
|
});
|
||||||
|
document.querySelector('.ent-type-selection-action[data-ent-type="english"]').addEventListener('change', (event) => {
|
||||||
|
this.submitQueryChipElement('start-entity', `Entity Type=${event.target.value}`, `<ent_type="${event.target.value}">`, null, false, true);
|
||||||
|
if (!this.elements.editingModusOn) {
|
||||||
|
this.submitQueryChipElement('end-entity', 'Entity End', '</ent_type>', null, true);
|
||||||
|
}
|
||||||
|
this.elements.structuralAttrModal.close();
|
||||||
|
});
|
||||||
|
document.querySelector('.ent-type-selection-action[data-ent-type="german"]').addEventListener('change', (event) => {
|
||||||
|
this.submitQueryChipElement('start-entity', `Entity Type=${event.target.value}`, `<ent_type="${event.target.value}">`, null, false, true);
|
||||||
|
if (!this.elements.editingModusOn) {
|
||||||
|
this.submitQueryChipElement('end-entity', 'Entity End', '</ent_type>', null, true);
|
||||||
|
}
|
||||||
|
this.elements.structuralAttrModal.close();
|
||||||
|
});
|
||||||
|
//#endregion Structural Attribute Builder Constructor
|
||||||
|
|
||||||
|
//#region Token Attribute Builder Constructor
|
||||||
|
this.elements.positionalAttrSelection.addEventListener('change', () => {
|
||||||
|
this.preparePositionalAttrModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Options for positional attribute selection
|
||||||
|
document.querySelectorAll('.positional-attr-options-action-button[data-options-action]').forEach(button => {
|
||||||
|
button.addEventListener('click', () => {this.actionButtonInOptionSectionHandler(button.dataset.optionsAction);});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.elements.tokenSubmitButton.addEventListener('click', () => {this.addTokenToQuery();});
|
||||||
|
//#endregion Token Attribute Builder Constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
//#region QB Functions
|
||||||
|
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 !== "" && queryBuilderInputFieldValue !== ";") {
|
||||||
|
expertModeInputField.value = queryBuilderInputFieldValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switchToQueryBuilderParser() {
|
||||||
|
this.resetQueryInputField();
|
||||||
|
let expertModeInputFieldValue = document.querySelector('#corpus-analysis-concordance-form-query').value;
|
||||||
|
let chipElements = this.parseTextToChip(expertModeInputFieldValue);
|
||||||
|
let closingTagElements = ['end-sentence', 'end-entity'];
|
||||||
|
let editableElements = ['start-entity', 'text-annotation', 'token'];
|
||||||
|
for (let chipElement of chipElements) {
|
||||||
|
let isClosingTag = closingTagElements.includes(chipElement['type']);
|
||||||
|
let isEditable = editableElements.includes(chipElement['type']);
|
||||||
|
if (chipElement['query'] === '[]'){
|
||||||
|
isEditable = false;
|
||||||
|
}
|
||||||
|
this.submitQueryChipElement(chipElement['type'], chipElement['pretty'], chipElement['query'], null, isClosingTag, isEditable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = [];
|
||||||
|
let regexPattern = Object.keys(parsingElementDict).map(pattern => `(${pattern})`).join('|');
|
||||||
|
const regex = new RegExp(regexPattern, '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)) {
|
||||||
|
// Creating the pretty text for the chip element
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
//#endregion QB Functions
|
||||||
|
|
||||||
|
//#region General Functions
|
||||||
|
toggleClass(elements, className, action){
|
||||||
|
elements.forEach(element => {
|
||||||
|
document.querySelector(`[data-toggle-area="${element}"]`).classList[action](className);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
resetQueryInputField() {
|
||||||
|
console.log("resetQueryInputField");
|
||||||
|
this.elements.queryInputField.innerHTML = '';
|
||||||
|
this.addPlaceholder();
|
||||||
|
this.updateChipList();
|
||||||
|
this.queryPreviewBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateChipList() {
|
||||||
|
this.elements.queryChipElements = this.elements.queryInputField.querySelectorAll('.query-component');
|
||||||
|
}
|
||||||
|
|
||||||
|
removePlaceholder() {
|
||||||
|
let placeholder = this.elements.queryInputField.querySelector('#corpus-analysis-concordance-query-builder-input-field-placeholder');
|
||||||
|
if (placeholder && this.elements.queryInputField !== undefined) {
|
||||||
|
this.elements.queryInputField.innerHTML = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addPlaceholder() {
|
||||||
|
let placeholder = Utils.HTMLToElement('<span id="corpus-analysis-concordance-query-builder-input-field-placeholder">Click on a button to add a query component</span>');
|
||||||
|
this.elements.queryInputField.appendChild(placeholder);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetMaterializeSelection(selectionElements, value = "default") {
|
||||||
|
selectionElements.forEach(selectionElement => {
|
||||||
|
if (selectionElement.querySelector(`option[value=${value}]`) !== null) {
|
||||||
|
selectionElement.querySelector(`option[value=${value}]`).selected = true;
|
||||||
|
}
|
||||||
|
let instance = M.FormSelect.getInstance(selectionElement);
|
||||||
|
instance.destroy();
|
||||||
|
M.FormSelect.init(selectionElement);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
submitQueryChipElement(dataType = undefined, prettyQueryText = undefined, queryText = undefined, index = null, isClosingTag = false, isEditable = false) {
|
||||||
|
if (this.elements.editingModusOn) {
|
||||||
|
let editedQueryChipElement = this.elements.queryChipElements[this.elements.editedQueryChipElementIndex];
|
||||||
|
editedQueryChipElement.dataset.type = dataType;
|
||||||
|
editedQueryChipElement.dataset.query = queryText;
|
||||||
|
editedQueryChipElement.firstChild.textContent = prettyQueryText;
|
||||||
|
this.updateChipList();
|
||||||
|
this.queryPreviewBuilder();
|
||||||
|
} else {
|
||||||
|
this.queryChipFactory(dataType, prettyQueryText, queryText, index, isClosingTag, isEditable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queryChipFactory(dataType, prettyQueryText, queryText, index = null, isClosingTag = false, isEditable = false) {
|
||||||
|
// Creates a new query chip element, adds Eventlisteners for selection, deletion and drag and drop and appends it to the query input field.
|
||||||
|
queryText = Utils.escape(queryText);
|
||||||
|
prettyQueryText = Utils.escape(prettyQueryText);
|
||||||
|
let queryChipElement = Utils.HTMLToElement(
|
||||||
|
`
|
||||||
|
<span class="chip query-component" data-type="${dataType}" data-query="${queryText}" draggable="true" data-closing-tag="${isClosingTag}">
|
||||||
|
${prettyQueryText}${isEditable ? '<i class="material-icons chip-action-button" data-chip-action="edit" style="padding-left:5px; font-size:18px; cursor:pointer;">edit</i>': ''}
|
||||||
|
${isClosingTag ? '<i class="material-icons chip-action-button" data-chip-action="lock" style="padding-top:5px; font-size:20px; cursor:pointer;">lock_open</i>' : '<i class="material-icons close chip-action-button" data-chip-action="delete">close</i>'}
|
||||||
|
</span>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
this.actionListeners(queryChipElement);
|
||||||
|
queryChipElement.addEventListener('dragstart', this.handleDragStart.bind(this, queryChipElement));
|
||||||
|
queryChipElement.addEventListener('dragend', this.handleDragEnd);
|
||||||
|
|
||||||
|
// Ensures that metadata is always at the end of the query and if an index is given, inserts the query chip at the given index and if there is a closing tag, inserts the query chip before the closing tag.
|
||||||
|
this.removePlaceholder();
|
||||||
|
let lastChild = this.elements.queryInputField.lastChild;
|
||||||
|
let isLastChildTextAnnotation = lastChild && lastChild.dataset.type === 'text-annotation';
|
||||||
|
if (!index) {
|
||||||
|
let closingTagElement = this.elements.queryInputField.querySelector('[data-closing-tag="true"]');
|
||||||
|
if (closingTagElement) {
|
||||||
|
index = Array.from(this.elements.queryInputField.children).indexOf(closingTagElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dataType !== 'text-annotation' && index) {
|
||||||
|
this.elements.queryInputField.insertBefore(queryChipElement, this.elements.queryChipElements[index]);
|
||||||
|
} else if (dataType !== 'text-annotation' && isLastChildTextAnnotation) {
|
||||||
|
this.elements.queryInputField.insertBefore(queryChipElement, lastChild);
|
||||||
|
} else {
|
||||||
|
this.elements.queryInputField.appendChild(queryChipElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateChipList();
|
||||||
|
this.queryPreviewBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
actionListeners(queryChipElement) {
|
||||||
|
let notQuantifiableDataTypes = ['start-sentence', 'end-sentence', 'start-entity', 'start-empty-entity', 'end-entity', 'token-incidence-modifier'];
|
||||||
|
queryChipElement.addEventListener('click', (event) => {
|
||||||
|
if (event.target.classList.contains('chip')) {
|
||||||
|
if (!notQuantifiableDataTypes.includes(queryChipElement.dataset.type)) {
|
||||||
|
this.selectChipElement(queryChipElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let chipActionButtons = queryChipElement.querySelectorAll('.chip-action-button');
|
||||||
|
// chipActionButtons.forEach(button => {
|
||||||
|
for (let button of chipActionButtons) {
|
||||||
|
button.addEventListener('click', (event) => {
|
||||||
|
if (event.target.dataset.chipAction === 'delete') {
|
||||||
|
this.deleteChipElement(queryChipElement);
|
||||||
|
} else if (event.target.dataset.chipAction === 'edit') {
|
||||||
|
this.editChipElement(queryChipElement);
|
||||||
|
} else if (event.target.dataset.chipAction === 'lock') {
|
||||||
|
this.lockClosingChipElement(queryChipElement);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//hier wird this.toggleEditingAreaStructuralAttrModal('add'); aufgerufen aus StructuralAttributeBuilderFunctionsQueryBuilder.js
|
||||||
|
editChipElement(queryChipElement) {
|
||||||
|
//TODO: Split this function into smaller functionss
|
||||||
|
this.elements.editingModusOn = true;
|
||||||
|
this.elements.editedQueryChipElementIndex = Array.from(this.elements.queryInputField.children).indexOf(queryChipElement);
|
||||||
|
switch (queryChipElement.dataset.type) {
|
||||||
|
case 'start-entity':
|
||||||
|
this.elements.structuralAttrModal.open();
|
||||||
|
this.toggleClass(['entity-builder'], 'hide', 'remove');
|
||||||
|
this.toggleEditingAreaStructuralAttrModal('add');
|
||||||
|
let entType = queryChipElement.dataset.query.replace(/<ent_type="|">/g, '');
|
||||||
|
let isEnglishEntType = this.elements.englishEntTypeSelection.querySelector(`option[value=${entType}]`) !== null;
|
||||||
|
let selection = isEnglishEntType ? this.elements.englishEntTypeSelection : this.elements.germanEntTypeSelection;
|
||||||
|
this.resetMaterializeSelection([selection], entType);
|
||||||
|
break;
|
||||||
|
case 'text-annotation':
|
||||||
|
this.elements.structuralAttrModal.open();
|
||||||
|
this.toggleClass(['text-annotation-builder'], 'hide', 'remove');
|
||||||
|
this.toggleEditingAreaStructuralAttrModal('add');
|
||||||
|
let [textAnnotationSelection, textAnnotationContent] = queryChipElement.dataset.query
|
||||||
|
.replace(/:: ?match\.text_|"|"/g, '')
|
||||||
|
.split('=');
|
||||||
|
this.resetMaterializeSelection([this.elements.textAnnotationSelection], textAnnotationSelection);
|
||||||
|
this.elements.textAnnotationInput.value = textAnnotationContent;
|
||||||
|
break;
|
||||||
|
case 'token':
|
||||||
|
//this regex searches for word or lemma or pos or simple_pos="any string within single or double quotes" followed by one or no ignore case markers, followed by one or no condition characters.
|
||||||
|
let regex = new RegExp('(word|lemma|pos|simple_pos)=(("[^"]+")|(\\\\u0027[^\\\\u0027]+\\\\u0027)) ?(%c)? ?(\\&|\\|)?', 'gm');
|
||||||
|
let m;
|
||||||
|
let queryElementsContent = [];
|
||||||
|
while ((m = regex.exec(queryChipElement.dataset.query)) !== null) {
|
||||||
|
// this is necessary to avoid infinite loops with zero-width matches
|
||||||
|
if (m.index === regex.lastIndex) {
|
||||||
|
regex.lastIndex++;
|
||||||
|
}
|
||||||
|
let tokenAttr = m[1];
|
||||||
|
// Passes english-pos by default so that the template is added. In editTokenChipElement it is then checked whether it is english-pos or german-pos.
|
||||||
|
if (tokenAttr === 'pos') {
|
||||||
|
tokenAttr = 'english-pos';
|
||||||
|
}
|
||||||
|
let tokenValue = m[2].replace(/"|'/g, '');
|
||||||
|
let ignoreCase = false;
|
||||||
|
let condition = undefined;
|
||||||
|
m.forEach((match) => {
|
||||||
|
if (match === "%c") {
|
||||||
|
ignoreCase = true;
|
||||||
|
} else if (match === "&") {
|
||||||
|
condition = "and";
|
||||||
|
} else if (match === "|") {
|
||||||
|
condition = "or";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
queryElementsContent.push({tokenAttr: tokenAttr, tokenValue: tokenValue, ignoreCase: ignoreCase, condition: condition});
|
||||||
|
}
|
||||||
|
this.editTokenChipElement(queryElementsContent);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//hier wird this.preparePositionalAttrModal(); und this.conditionHandler(); aufgerufen aus TokenAttributeBuilderFunctionsQueryBuilder.js
|
||||||
|
editTokenChipElement(queryElementsContent) {
|
||||||
|
this.elements.positionalAttrModal.open();
|
||||||
|
for (let queryElement of queryElementsContent) {
|
||||||
|
// queryElementsContent.forEach((queryElement) => {
|
||||||
|
this.resetMaterializeSelection([this.elements.positionalAttrSelection], queryElement.tokenAttr);
|
||||||
|
this.preparePositionalAttrModal();
|
||||||
|
switch (queryElement.tokenAttr) {
|
||||||
|
case 'word':
|
||||||
|
case 'lemma':
|
||||||
|
this.elements.tokenBuilderContent.querySelector('input').value = queryElement.tokenValue;
|
||||||
|
break;
|
||||||
|
case 'english-pos':
|
||||||
|
// English-pos is selected by default. Then it is checked whether the passed token value occurs in the english-pos selection. If not, the selection is reseted and changed to german-pos.
|
||||||
|
let selection = this.elements.tokenBuilderContent.querySelector('select');
|
||||||
|
queryElement.tokenAttr = selection.querySelector(`option[value=${queryElement.tokenValue}]`) ? 'english-pos' : 'german-pos';
|
||||||
|
this.resetMaterializeSelection([this.elements.positionalAttrSelection], queryElement.tokenAttr);
|
||||||
|
this.preparePositionalAttrModal();
|
||||||
|
this.resetMaterializeSelection([this.elements.tokenBuilderContent.querySelector('select')], queryElement.tokenValue);
|
||||||
|
break;
|
||||||
|
case 'simple_pos':
|
||||||
|
this.resetMaterializeSelection([this.elements.tokenBuilderContent.querySelector('select')], queryElement.tokenValue);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (queryElement.ignoreCase) {
|
||||||
|
this.elements.ignoreCaseCheckbox.checked = true;
|
||||||
|
}
|
||||||
|
if (queryElement.condition !== undefined) {
|
||||||
|
this.conditionHandler(queryElement.condition, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lockClosingChipElement(queryChipElement) {
|
||||||
|
queryChipElement.dataset.closingTag = 'false';
|
||||||
|
let lockIcon = queryChipElement.querySelector('[data-chip-action="lock"]');
|
||||||
|
lockIcon.textContent = 'lock';
|
||||||
|
//TODO: Write unlock-Function?
|
||||||
|
lockIcon.dataset.chipAction = 'unlock';
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteChipElement(attr) {
|
||||||
|
let elementIndex = Array.from(this.elements.queryInputField.children).indexOf(attr);
|
||||||
|
switch (attr.dataset.type) {
|
||||||
|
case 'start-sentence':
|
||||||
|
this.deletingClosingTagHandler(elementIndex, 'end-sentence');
|
||||||
|
break;
|
||||||
|
case 'start-entity':
|
||||||
|
this.deletingClosingTagHandler(elementIndex, 'end-entity');
|
||||||
|
break;
|
||||||
|
case 'token':
|
||||||
|
let nextElement = Array.from(this.elements.queryInputField.children)[elementIndex+1];
|
||||||
|
if (nextElement !== undefined && nextElement.dataset.type === 'token-incidence-modifier') {
|
||||||
|
this.deleteChipElement(nextElement);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.elements.queryInputField.removeChild(attr);
|
||||||
|
if (this.elements.queryInputField.children.length === 0) {
|
||||||
|
this.addPlaceholder();
|
||||||
|
}
|
||||||
|
this.updateChipList();
|
||||||
|
this.queryPreviewBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
deletingClosingTagHandler(elementIndex, closingTagType) {
|
||||||
|
let closingTags = this.elements.queryInputField.querySelectorAll(`[data-type="${closingTagType}"]`);
|
||||||
|
for (let i = 0; i < closingTags.length; i++) {
|
||||||
|
let closingTag = closingTags[i];
|
||||||
|
|
||||||
|
if (Array.from(this.elements.queryInputField.children).indexOf(closingTag) > elementIndex) {
|
||||||
|
this.deleteChipElement(closingTag);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDragStart(queryChipElement, event) {
|
||||||
|
// is called when a query chip is dragged. It creates a dropzone (in form of a chip) for the dragged chip and adds it to the query input field.
|
||||||
|
let queryChips = this.elements.queryInputField.querySelectorAll('.query-component');
|
||||||
|
if (queryChipElement.dataset.type === 'token-incidence-modifier') {
|
||||||
|
queryChips = this.elements.queryInputField.querySelectorAll('.query-component[data-type="token"]');
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
let targetChipElement = Utils.HTMLToElement('<span class="chip drop-target">Drop here</span>');
|
||||||
|
for (let element of queryChips) {
|
||||||
|
if (element === this.elements.queryInputField.querySelectorAll('.query-component')[0]) {
|
||||||
|
let secondTargetChipClone = targetChipElement.cloneNode(true);
|
||||||
|
element.insertAdjacentElement('beforebegin', secondTargetChipClone);
|
||||||
|
this.addDragDropListeners(secondTargetChipClone, queryChipElement);
|
||||||
|
}
|
||||||
|
if (element === queryChipElement || element.nextSibling === queryChipElement) {continue;}
|
||||||
|
|
||||||
|
let targetChipClone = targetChipElement.cloneNode(true);
|
||||||
|
element.insertAdjacentElement('afterend', targetChipClone);
|
||||||
|
|
||||||
|
this.addDragDropListeners(targetChipClone, queryChipElement);
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDragEnd(event) {
|
||||||
|
document.querySelectorAll('.drop-target').forEach(target => target.remove());
|
||||||
|
}
|
||||||
|
|
||||||
|
addDragDropListeners(targetChipClone, queryChipElement) {
|
||||||
|
targetChipClone.addEventListener('dragover', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
targetChipClone.addEventListener('dragenter', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.target.style.borderStyle = 'solid dotted';
|
||||||
|
});
|
||||||
|
targetChipClone.addEventListener('dragleave', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.target.style.borderStyle = 'hidden';
|
||||||
|
});
|
||||||
|
targetChipClone.addEventListener('drop', (event) => {
|
||||||
|
let dropzone = event.target;
|
||||||
|
dropzone.parentElement.replaceChild(queryChipElement, dropzone);
|
||||||
|
this.updateChipList();
|
||||||
|
this.queryPreviewBuilder();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
queryPreviewBuilder() {
|
||||||
|
// Builds the query preview in the form of pure CQL and displays it in the query preview field.
|
||||||
|
let queryPreview = document.querySelector('#corpus-analysis-concordance-query-preview');
|
||||||
|
let queryInputFieldContent = [];
|
||||||
|
this.elements.queryChipElements.forEach(element => {
|
||||||
|
let queryElement = element.dataset.query;
|
||||||
|
if (queryElement !== undefined) {
|
||||||
|
queryElement = Utils.escape(queryElement);
|
||||||
|
}
|
||||||
|
queryInputFieldContent.push(queryElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
let queryString = queryInputFieldContent.join(' ');
|
||||||
|
let replacements = {
|
||||||
|
' +': '+',
|
||||||
|
' *': '*',
|
||||||
|
' ?': '?',
|
||||||
|
' {': '{'
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let key in replacements) {
|
||||||
|
queryString = queryString.replace(key, replacements[key]);
|
||||||
|
}
|
||||||
|
queryString += ';';
|
||||||
|
|
||||||
|
queryPreview.innerHTML = queryString;
|
||||||
|
queryPreview.parentNode.classList.toggle('hide', queryString === ';');
|
||||||
|
}
|
||||||
|
|
||||||
|
selectChipElement(attr) {
|
||||||
|
document.querySelectorAll('.chip.teal').forEach(element => {
|
||||||
|
if (element !== attr) {
|
||||||
|
element.classList.remove('teal', 'lighten-2');
|
||||||
|
this.toggleClass(['token-incidence-modifiers'], 'disabled', 'add');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.toggleClass(['token-incidence-modifiers'], 'disabled', 'toggle');
|
||||||
|
attr.classList.toggle('teal');
|
||||||
|
attr.classList.toggle('lighten-5');
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenIncidenceModifierHandler(incidenceModifier, incidenceModifierPretty) {
|
||||||
|
// Adds a token incidence modifier to the query input field.
|
||||||
|
let selectedChip = this.elements.queryInputField.querySelector('.chip.teal');
|
||||||
|
let selectedChipIndex = Array.from(this.elements.queryInputField.children).indexOf(selectedChip);
|
||||||
|
this.submitQueryChipElement('token-incidence-modifier', incidenceModifierPretty, incidenceModifier, selectedChipIndex+1);
|
||||||
|
this.selectChipElement(selectedChip);
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenNMSubmitHandler(modalId) {
|
||||||
|
// Adds a token incidence modifier (exactly n or between n and m) to the query input field.
|
||||||
|
let modal = document.querySelector(`#${modalId}`);
|
||||||
|
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 !== '' ? ',' : ''}${input_m}}`;
|
||||||
|
let pretty_input = `between ${input_n} and ${input_m} (${input})`;
|
||||||
|
if (input_m === '') {
|
||||||
|
pretty_input = `exactly ${input_n} (${input})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let instance = M.Modal.getInstance(modal);
|
||||||
|
instance.close();
|
||||||
|
|
||||||
|
this.tokenIncidenceModifierHandler(input, pretty_input);
|
||||||
|
}
|
||||||
|
//#endregion General Functions
|
||||||
|
|
||||||
|
//#region Structural Attribute Builder Functions
|
||||||
|
// Hier wird resetMaterializeSelection() und toggleClass() aufgerufen, das in GeneralFunctionsQueryBuilder definiert ist.
|
||||||
|
resetStructuralAttrModal() {
|
||||||
|
this.resetMaterializeSelection([this.elements.englishEntTypeSelection, this.elements.germanEntTypeSelection]);
|
||||||
|
this.resetMaterializeSelection([this.elements.textAnnotationSelection], 'address');
|
||||||
|
this.elements.textAnnotationInput.value = '';
|
||||||
|
|
||||||
|
this.toggleClass(['entity-builder', 'text-annotation-builder'], 'hide', 'add');
|
||||||
|
this.toggleEditingAreaStructuralAttrModal('remove');
|
||||||
|
this.elements.editingModusOn = false;
|
||||||
|
this.elements.editedQueryChipElementIndex = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hier wird toggleClass() aufgerufen, das in GeneralFunctionsQueryBuilder definiert ist.
|
||||||
|
toggleEditingAreaStructuralAttrModal(action) {
|
||||||
|
// If the user edits a query chip element, the corresponding editing area is displayed and the other areas are hidden or disabled.
|
||||||
|
this.toggleClass(['sentence-button', 'entity-button', 'text-annotation-button', 'any-type-entity-button'], 'disabled', action);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hier wird toggleClass() und submitQueryChipElement() aufgerufen, das in GeneralFunctionsQueryBuilder definiert ist.
|
||||||
|
actionButtonInStrucAttrModalHandler(action) {
|
||||||
|
switch (action) {
|
||||||
|
case 'sentence':
|
||||||
|
this.submitQueryChipElement('start-sentence', 'Sentence Start', '<s>');
|
||||||
|
this.submitQueryChipElement('end-sentence', 'Sentence End', '</s>', null, true);
|
||||||
|
this.elements.structuralAttrModal.close();
|
||||||
|
break;
|
||||||
|
case 'entity':
|
||||||
|
this.toggleClass(['entity-builder'], 'hide', 'toggle');
|
||||||
|
this.toggleClass(['text-annotation-builder'], 'hide', 'add');
|
||||||
|
break;
|
||||||
|
case 'meta-data':
|
||||||
|
this.toggleClass(['text-annotation-builder'], 'hide', 'toggle');
|
||||||
|
this.toggleClass(['entity-builder'], 'hide', 'add');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hier wird submitQueryChipElement() aufgerufen, das in GeneralFunctionsQueryBuilder definiert ist.
|
||||||
|
textAnnotationSubmitHandler() {
|
||||||
|
let noValueMetadataMessage = document.querySelector('#corpus-analysis-concordance-no-value-metadata-message');
|
||||||
|
let textAnnotationSubmit = document.querySelector('#corpus-analysis-concordance-text-annotation-submit');
|
||||||
|
let textAnnotationInput = document.querySelector('#corpus-analysis-concordance-text-annotation-input');
|
||||||
|
let textAnnotationOptions = document.querySelector('#corpus-analysis-concordance-text-annotation-options');
|
||||||
|
|
||||||
|
if (textAnnotationInput.value === '') {
|
||||||
|
textAnnotationSubmit.classList.add('red');
|
||||||
|
noValueMetadataMessage.classList.remove('hide');
|
||||||
|
setTimeout(() => {
|
||||||
|
textAnnotationSubmit.classList.remove('red');
|
||||||
|
}, 500);
|
||||||
|
setTimeout(() => {
|
||||||
|
noValueMetadataMessage.classList.add('hide');
|
||||||
|
}, 3000);
|
||||||
|
} else {
|
||||||
|
let queryText = `:: match.text_${textAnnotationOptions.value}="${textAnnotationInput.value}"`;
|
||||||
|
this.submitQueryChipElement('text-annotation', `${textAnnotationOptions.value}=${textAnnotationInput.value}`, queryText, null, false, true);
|
||||||
|
this.elements.structuralAttrModal.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#endregion Structural Attribute Builder Functions
|
||||||
|
|
||||||
|
//#region Token Attribute Builder Functions
|
||||||
|
resetPositionalAttrModal() {
|
||||||
|
let originalSelectionList =
|
||||||
|
`
|
||||||
|
<option value="word" selected>word</option>
|
||||||
|
<option value="lemma" >lemma</option>
|
||||||
|
<option value="english-pos">english pos</option>
|
||||||
|
<option value="german-pos">german pos</option>
|
||||||
|
<option value="simple_pos">simple_pos</option>
|
||||||
|
<option value="empty-token">empty token</option>
|
||||||
|
`;
|
||||||
|
this.elements.positionalAttrSelection.innerHTML = originalSelectionList;
|
||||||
|
this.elements.tokenQuery.innerHTML = '';
|
||||||
|
this.elements.tokenBuilderContent.innerHTML = '';
|
||||||
|
this.toggleClass(['input-field-options'], 'hide', 'remove');
|
||||||
|
this.toggleClass(['incidence-modifiers', 'or', 'and'], 'disabled', 'add');
|
||||||
|
this.resetMaterializeSelection([this.elements.positionalAttrSelection], "word");
|
||||||
|
this.elements.ignoreCaseCheckbox.checked = false;
|
||||||
|
this.elements.editingModusOn = false;
|
||||||
|
this.elements.editedQueryChipElementIndex = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
preparePositionalAttrModal() {
|
||||||
|
let selection = this.elements.positionalAttrSelection.value;
|
||||||
|
if (selection !== 'empty-token') {
|
||||||
|
let selectionTemplate = document.querySelector(`.token-builder-section[data-token-builder-section="${selection}"]`);
|
||||||
|
let selectionTemplateClone = selectionTemplate.content.cloneNode(true);
|
||||||
|
|
||||||
|
this.elements.tokenBuilderContent.innerHTML = '';
|
||||||
|
this.elements.tokenBuilderContent.appendChild(selectionTemplateClone);
|
||||||
|
if (this.elements.tokenBuilderContent.querySelector('select') !== null) {
|
||||||
|
let selectElement = this.elements.tokenBuilderContent.querySelector('select');
|
||||||
|
M.FormSelect.init(selectElement);
|
||||||
|
selectElement.addEventListener('change', () => {this.optionToggleHandler();});
|
||||||
|
} else {
|
||||||
|
this.elements.tokenBuilderContent.querySelector('input').addEventListener('input', () => {this.optionToggleHandler();});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.optionToggleHandler();
|
||||||
|
|
||||||
|
if (selection === 'word' || selection === 'lemma') {
|
||||||
|
this.toggleClass(['input-field-options'], 'hide', 'remove');
|
||||||
|
} else if (selection === 'empty-token'){
|
||||||
|
this.addTokenToQuery();
|
||||||
|
} else {
|
||||||
|
this.toggleClass(['input-field-options'], 'hide', 'add');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenInputCheck(elem) {
|
||||||
|
return elem.querySelector('select') !== null ? elem.querySelector('select') : elem.querySelector('input');
|
||||||
|
}
|
||||||
|
|
||||||
|
optionToggleHandler() {
|
||||||
|
let input = this.tokenInputCheck(this.elements.tokenBuilderContent);
|
||||||
|
if (input.value === '' && this.elements.editingModusOn === false) {
|
||||||
|
this.toggleClass(['incidence-modifiers', 'or', 'and'], 'disabled', 'add');
|
||||||
|
} else if (this.elements.positionalAttrSelection.querySelectorAll('option').length === 1) {
|
||||||
|
this.toggleClass(['and'], 'disabled', 'add');
|
||||||
|
this.toggleClass(['or'], 'disabled', 'remove');
|
||||||
|
} else {
|
||||||
|
this.toggleClass(['incidence-modifiers', 'or', 'and'], 'disabled', 'remove');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disableTokenSubmit() {
|
||||||
|
this.elements.tokenSubmitButton.classList.add('red');
|
||||||
|
this.elements.noValueMessage.classList.remove('hide');
|
||||||
|
setTimeout(() => {
|
||||||
|
this.elements.tokenSubmitButton.classList.remove('red');
|
||||||
|
}, 500);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.elements.noValueMessage.classList.add('hide');
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
addTokenToQuery() {
|
||||||
|
let tokenQueryPrettyText = '';
|
||||||
|
let tokenQueryCQLText = '';
|
||||||
|
let input;
|
||||||
|
let kindOfToken = this.kindOfTokenCheck(this.elements.positionalAttrSelection.value);
|
||||||
|
|
||||||
|
// Takes all rows of the token query (if there is a query concatenation).
|
||||||
|
// Adds their contents to tokenQueryPrettyText and tokenQueryCQLText, which will later be expanded with the current input field.
|
||||||
|
let tokenQueryRows = this.elements.tokenQuery.querySelectorAll('.row');
|
||||||
|
tokenQueryRows.forEach(row => {
|
||||||
|
let ignoreCaseCheckbox = row.querySelector('input[type="checkbox"]');
|
||||||
|
let c = ignoreCaseCheckbox !== null && ignoreCaseCheckbox.checked ? ' %c' : '';
|
||||||
|
let tokenQueryRowInput = this.tokenInputCheck(row.querySelector('.token-query-template-content'));
|
||||||
|
let tokenQueryKindOfToken = this.kindOfTokenCheck(tokenQueryRowInput.closest('.input-field').dataset.kindOfToken);
|
||||||
|
let tokenConditionPrettyText = row.querySelector('[data-condition-pretty-text]').dataset.conditionPrettyText;
|
||||||
|
let tokenConditionCQLText = row.querySelector('[data-condition-cql-text]').dataset.conditionCqlText;
|
||||||
|
tokenQueryPrettyText += `${tokenQueryKindOfToken}=${tokenQueryRowInput.value}${c} ${tokenConditionPrettyText} `;
|
||||||
|
tokenQueryCQLText += `${tokenQueryKindOfToken}="${tokenQueryRowInput.value}"${c} ${tokenConditionCQLText}`;
|
||||||
|
});
|
||||||
|
if (kindOfToken === 'empty-token') {
|
||||||
|
tokenQueryPrettyText += 'empty token';
|
||||||
|
} else {
|
||||||
|
let c = this.elements.ignoreCaseCheckbox.checked ? ' %c' : '';
|
||||||
|
input = this.tokenInputCheck(this.elements.tokenBuilderContent);
|
||||||
|
tokenQueryPrettyText += `${kindOfToken}=${input.value}${c}`;
|
||||||
|
tokenQueryCQLText += `${kindOfToken}="${input.value}"${c}`;
|
||||||
|
}
|
||||||
|
// isTokenQueryInvalid looks if a valid value is passed. If the input fields/dropdowns are empty (isTokenQueryInvalid === true), no token is added.
|
||||||
|
if (this.elements.positionalAttrSelection.value !== 'empty-token' && input.value === '') {
|
||||||
|
this.disableTokenSubmit();
|
||||||
|
} else {
|
||||||
|
tokenQueryCQLText = `[${tokenQueryCQLText}]`;
|
||||||
|
this.submitQueryChipElement('token', tokenQueryPrettyText, tokenQueryCQLText, null, false, kindOfToken === 'empty-token' ? false : true);
|
||||||
|
this.elements.positionalAttrModal.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kindOfTokenCheck(kindOfToken) {
|
||||||
|
return kindOfToken === 'english-pos' || kindOfToken === 'german-pos' ? 'pos' : kindOfToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
actionButtonInOptionSectionHandler(elem) {
|
||||||
|
let input = this.tokenInputCheck(this.elements.tokenBuilderContent);
|
||||||
|
switch (elem) {
|
||||||
|
case 'option-group':
|
||||||
|
input.value += '(option1|option2)';
|
||||||
|
let firstIndex = input.value.indexOf('option1');
|
||||||
|
let lastIndex = firstIndex + 'option1'.length;
|
||||||
|
input.focus();
|
||||||
|
input.setSelectionRange(firstIndex, lastIndex);
|
||||||
|
break;
|
||||||
|
case 'wildcard-char':
|
||||||
|
input.value += '.';
|
||||||
|
break;
|
||||||
|
case 'and':
|
||||||
|
this.conditionHandler('and');
|
||||||
|
break;
|
||||||
|
case 'or':
|
||||||
|
this.conditionHandler('or');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.optionToggleHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
characterIncidenceModifierHandler(elem) {
|
||||||
|
let input = this.tokenInputCheck(this.elements.tokenBuilderContent);
|
||||||
|
input.value += elem.dataset.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
characterNMSubmitHandler(modalId) {
|
||||||
|
let modal = document.querySelector(`#${modalId}`);
|
||||||
|
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 instance = M.Modal.getInstance(modal);
|
||||||
|
instance.close();
|
||||||
|
let tokenInput = this.tokenInputCheck(this.elements.tokenBuilderContent);
|
||||||
|
tokenInput.value += '{' + input + '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
conditionHandler(conditionText, editMode = false) {
|
||||||
|
let tokenQueryTemplateClone = this.elements.tokenQueryTemplate.content.cloneNode(true);
|
||||||
|
tokenQueryTemplateClone.querySelector('.token-query-template-content').appendChild(this.elements.tokenBuilderContent.firstElementChild);
|
||||||
|
let notSelectedButton = tokenQueryTemplateClone.querySelector(`[data-condition-pretty-text]:not([data-condition-pretty-text="${conditionText}"])`);
|
||||||
|
let deleteButton = tokenQueryTemplateClone.querySelector(`[data-token-query-content-action="delete"]`);
|
||||||
|
deleteButton.addEventListener('click', (event) => {
|
||||||
|
this.deleteTokenQueryRow(event.target);
|
||||||
|
});
|
||||||
|
notSelectedButton.parentNode.removeChild(notSelectedButton);
|
||||||
|
this.elements.tokenQuery.appendChild(tokenQueryTemplateClone);
|
||||||
|
|
||||||
|
// Deleting the options which do not make sense in the context of the condition like "word" AND "word". Also sets selection default.
|
||||||
|
let selectionDefault = "word";
|
||||||
|
let optionDeleteList = ['empty-token'];
|
||||||
|
if (conditionText === 'and') {
|
||||||
|
switch (this.elements.positionalAttrSelection.value) {
|
||||||
|
case 'english-pos' || 'german-pos':
|
||||||
|
optionDeleteList.push('english-pos', 'german-pos');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
optionDeleteList.push(this.elements.positionalAttrSelection.value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let originalSelectionList =
|
||||||
|
`
|
||||||
|
<option value="word" selected>word</option>
|
||||||
|
<option value="lemma" >lemma</option>
|
||||||
|
<option value="english-pos">english pos</option>
|
||||||
|
<option value="german-pos">german pos</option>
|
||||||
|
<option value="simple_pos">simple_pos</option>
|
||||||
|
`;
|
||||||
|
this.elements.positionalAttrSelection.innerHTML = originalSelectionList;
|
||||||
|
M.FormSelect.init(this.elements.positionalAttrSelection);
|
||||||
|
}
|
||||||
|
let lastTokenQueryRow = this.elements.tokenQuery.lastElementChild;
|
||||||
|
if(lastTokenQueryRow.querySelector('[data-kind-of-token="word"]') || lastTokenQueryRow.querySelector('[data-kind-of-token="lemma"]')) {
|
||||||
|
this.appendIgnoreCaseCheckbox(lastTokenQueryRow.querySelector('.token-query-template-content'), this.elements.ignoreCaseCheckbox.checked);
|
||||||
|
}
|
||||||
|
this.elements.ignoreCaseCheckbox.checked = false;
|
||||||
|
this.setTokenSelection(selectionDefault, optionDeleteList);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteTokenQueryRow(deleteButton) {
|
||||||
|
let deletedRow = deleteButton.closest('.row');
|
||||||
|
let condition = deletedRow.querySelector('[data-condition-pretty-text]').dataset.conditionPrettyText;
|
||||||
|
if (condition === 'and') {
|
||||||
|
let kindOfToken = deletedRow.querySelector('[data-kind-of-token]').dataset.kindOfToken;
|
||||||
|
switch (kindOfToken) {
|
||||||
|
case 'english-pos' || 'german-pos':
|
||||||
|
this.createOptionElementForPosAttrSelection('english-pos');
|
||||||
|
this.createOptionElementForPosAttrSelection('german-pos');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.createOptionElementForPosAttrSelection(kindOfToken);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
M.FormSelect.init(this.elements.positionalAttrSelection);
|
||||||
|
}
|
||||||
|
deletedRow.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
createOptionElementForPosAttrSelection(kindOfToken) {
|
||||||
|
let option = document.createElement('option');
|
||||||
|
option.value = kindOfToken;
|
||||||
|
option.text = kindOfToken;
|
||||||
|
this.elements.positionalAttrSelection.appendChild(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
appendIgnoreCaseCheckbox(parentElement, checked = false) {
|
||||||
|
let ignoreCaseCheckboxClone = document.querySelector('#ignore-case-checkbox-template').content.cloneNode(true);
|
||||||
|
parentElement.appendChild(ignoreCaseCheckboxClone);
|
||||||
|
M.Tooltip.init(parentElement.querySelectorAll('.tooltipped'));
|
||||||
|
if (checked) {
|
||||||
|
parentElement.querySelector('input[type="checkbox"]').checked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setTokenSelection(selection, optionDeleteList) {
|
||||||
|
optionDeleteList.forEach(option => {
|
||||||
|
if (this.elements.positionalAttrSelection.querySelector(`option[value=${option}]`) !== null) {
|
||||||
|
this.elements.positionalAttrSelection.querySelector(`option[value=${option}]`).remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.resetMaterializeSelection([this.elements.positionalAttrSelection], selection);
|
||||||
|
this.preparePositionalAttrModal();
|
||||||
|
}
|
||||||
|
//#endregion Token Attribute Builder Functions
|
||||||
|
}
|
@ -103,13 +103,10 @@
|
|||||||
{%- assets
|
{%- assets
|
||||||
filters='rjsmin',
|
filters='rjsmin',
|
||||||
output='gen/CorpusAnalysis.%(version)s.js',
|
output='gen/CorpusAnalysis.%(version)s.js',
|
||||||
'js/CorpusAnalysis/QueryBuilder/ElementReferencesQueryBuilder.js',
|
'js/CorpusAnalysis/query-builder/index.js',
|
||||||
'js/CorpusAnalysis/QueryBuilder/GeneralFunctionsQueryBuilder.js',
|
'js/CorpusAnalysis/query-builder/element-references.js',
|
||||||
'js/CorpusAnalysis/QueryBuilder/StructuralAttributeBuilderFunctionsQueryBuilder.js',
|
|
||||||
'js/CorpusAnalysis/QueryBuilder/TokenAttributeBuilderFunctionsQueryBuilder.js',
|
|
||||||
'js/CorpusAnalysis/CorpusAnalysisApp.js',
|
'js/CorpusAnalysis/CorpusAnalysisApp.js',
|
||||||
'js/CorpusAnalysis/CorpusAnalysisConcordance.js',
|
'js/CorpusAnalysis/CorpusAnalysisConcordance.js',
|
||||||
'js/CorpusAnalysis/QueryBuilder.js',
|
|
||||||
'js/CorpusAnalysis/CorpusAnalysisReader.js',
|
'js/CorpusAnalysis/CorpusAnalysisReader.js',
|
||||||
'js/CorpusAnalysis/CorpusAnalysisStaticVisualization.js'
|
'js/CorpusAnalysis/CorpusAnalysisStaticVisualization.js'
|
||||||
%}
|
%}
|
||||||
|
@ -130,21 +130,5 @@
|
|||||||
<script>
|
<script>
|
||||||
const corpusAnalysisConcordance = new CorpusAnalysisConcordance(corpusAnalysisApp);
|
const corpusAnalysisConcordance = new CorpusAnalysisConcordance(corpusAnalysisApp);
|
||||||
const concordanceQueryBuilder = new ConcordanceQueryBuilder();
|
const concordanceQueryBuilder = new ConcordanceQueryBuilder();
|
||||||
|
|
||||||
let queryBuilderDisplay = document.getElementById("corpus-analysis-concordance-query-builder-display");
|
|
||||||
let expertModeDisplay = document.getElementById("corpus-analysis-concordance-expert-mode-display");
|
|
||||||
let expertModeSwitch = document.getElementById("corpus-analysis-concordance-expert-mode-switch");
|
|
||||||
|
|
||||||
expertModeSwitch.addEventListener("change", function() {
|
|
||||||
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>
|
</script>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
Loading…
Reference in New Issue
Block a user