mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2024-12-25 10:54:18 +00:00
Editing nested token queries and bug fixes
This commit is contained in:
parent
f4d3415c11
commit
985e9b406f
@ -8,9 +8,6 @@ class ElementReferencesQueryBuilder {
|
|||||||
|
|
||||||
// Structural Attribute Builder Elements
|
// Structural Attribute Builder Elements
|
||||||
this.structuralAttrModal = M.Modal.getInstance(document.querySelector('#corpus-analysis-concordance-structural-attr-modal'));
|
this.structuralAttrModal = M.Modal.getInstance(document.querySelector('#corpus-analysis-concordance-structural-attr-modal'));
|
||||||
this.sentenceElement = document.querySelector('[data-structural-attr-modal-action-button="sentence"]');
|
|
||||||
this.entityElement = document.querySelector('[data-structural-attr-modal-action-button="entity"]');
|
|
||||||
this.textAnnotationElement = document.querySelector('[data-structural-attr-modal-action-button="text-annotation"]');
|
|
||||||
this.englishEntTypeSelection = document.querySelector('#corpus-analysis-concordance-english-ent-type-selection');
|
this.englishEntTypeSelection = document.querySelector('#corpus-analysis-concordance-english-ent-type-selection');
|
||||||
this.germanEntTypeSelection = document.querySelector('#corpus-analysis-concordance-german-ent-type-selection');
|
this.germanEntTypeSelection = document.querySelector('#corpus-analysis-concordance-german-ent-type-selection');
|
||||||
this.textAnnotationSelection = document.querySelector('#corpus-analysis-concordance-text-annotation-options');
|
this.textAnnotationSelection = document.querySelector('#corpus-analysis-concordance-text-annotation-options');
|
||||||
|
@ -56,7 +56,6 @@ class GeneralFunctionsQueryBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
queryChipFactory(dataType, prettyQueryText, queryText, index = null, isClosingTag = false, isEditable = false) {
|
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.
|
// 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);
|
queryText = Utils.escape(queryText);
|
||||||
@ -118,6 +117,7 @@ class GeneralFunctionsQueryBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
editChipElement(queryChipElement) {
|
editChipElement(queryChipElement) {
|
||||||
|
//TODO: Split this function into smaller functionss
|
||||||
this.elements.editingModusOn = true;
|
this.elements.editingModusOn = true;
|
||||||
this.elements.editedQueryChipElementIndex = Array.from(this.elements.queryInputField.children).indexOf(queryChipElement);
|
this.elements.editedQueryChipElementIndex = Array.from(this.elements.queryInputField.children).indexOf(queryChipElement);
|
||||||
switch (queryChipElement.dataset.type) {
|
switch (queryChipElement.dataset.type) {
|
||||||
@ -141,54 +141,105 @@ class GeneralFunctionsQueryBuilder {
|
|||||||
this.elements.textAnnotationInput.value = textAnnotationContent;
|
this.elements.textAnnotationInput.value = textAnnotationContent;
|
||||||
break;
|
break;
|
||||||
case 'token':
|
case 'token':
|
||||||
let [tokenAttr, tokenValue] = queryChipElement.dataset.query.replace(/\[|\]|"/g, '').split('=');
|
//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.
|
||||||
if (tokenAttr === 'pos') {
|
let regex = new RegExp('(word|lemma|pos|simple_pos)=(("[^"]+")|(\\\\u0027[^\\\\u0027]+\\\\u0027)) ?(%c)? ?(\\&|\\|)?', 'gm');
|
||||||
tokenAttr = this.elements.englishPosSelection.querySelector(`option[value=${tokenValue}]`) ? 'english-pos' : 'german-pos';
|
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(tokenAttr, tokenValue);
|
this.editTokenChipElement(queryElementsContent);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
editTokenChipElement(tokenAttr, tokenValue) {
|
editTokenChipElement(queryElementsContent) {
|
||||||
this.resetMaterializeSelection([this.elements.positionalAttrSelection], tokenAttr);
|
|
||||||
this.elements.positionalAttrModal.open();
|
this.elements.positionalAttrModal.open();
|
||||||
switch (tokenAttr) {
|
queryElementsContent.forEach((queryElement) => {
|
||||||
case 'word':
|
this.resetMaterializeSelection([this.elements.positionalAttrSelection], queryElement.tokenAttr);
|
||||||
this.elements.wordInput.value = tokenValue;
|
this.preparePositionalAttrModal();
|
||||||
break;
|
switch (queryElement.tokenAttr) {
|
||||||
case 'lemma':
|
case 'word':
|
||||||
this.elements.lemmaInput.value = tokenValue;
|
case 'lemma':
|
||||||
break;
|
this.elements.tokenBuilderContent.querySelector('input').value = queryElement.tokenValue;
|
||||||
case 'english-pos':
|
break;
|
||||||
this.resetMaterializeSelection([this.elements.englishPosSelection], tokenValue);
|
case 'english-pos':
|
||||||
break;
|
// 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.
|
||||||
case 'german-pos':
|
let selection = this.elements.tokenBuilderContent.querySelector('select');
|
||||||
this.resetMaterializeSelection([this.elements.germanPosSelection], tokenValue);
|
queryElement.tokenAttr = selection.querySelector(`option[value=${queryElement.tokenValue}]`) ? 'english-pos' : 'german-pos';
|
||||||
break;
|
this.resetMaterializeSelection([this.elements.positionalAttrSelection], queryElement.tokenAttr);
|
||||||
case 'simple-pos':
|
this.preparePositionalAttrModal();
|
||||||
this.resetMaterializeSelection([this.elements.simplePosSelection], tokenValue);
|
this.resetMaterializeSelection([this.elements.tokenBuilderContent.querySelector('select')], queryElement.tokenValue);
|
||||||
break;
|
break;
|
||||||
default:
|
case 'simple_pos':
|
||||||
break;
|
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) {
|
lockClosingChipElement(queryChipElement) {
|
||||||
let chipIndex = Array.from(this.elements.queryInputField.children).indexOf(queryChipElement);
|
queryChipElement.dataset.closingTag = 'false';
|
||||||
this.submitQueryChipElement(queryChipElement.dataset.type, queryChipElement.firstChild.textContent, queryChipElement.dataset.query, chipIndex+1);
|
let lockIcon = queryChipElement.querySelector('[data-chip-action="lock"]');
|
||||||
this.deleteChipElement(queryChipElement);
|
lockIcon.textContent = 'lock';
|
||||||
this.updateChipList();
|
//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) {
|
deleteChipElement(attr) {
|
||||||
if (attr.dataset.type === "start-sentence") {
|
let elementIndex = Array.from(this.elements.queryInputField.children).indexOf(attr);
|
||||||
this.elements.sentenceElement.innerHTML = 'Sentence';
|
switch (attr.dataset.type) {
|
||||||
} else if (attr.dataset.type === "start-entity" || attr.dataset.type === "start-empty-entity") {
|
case 'start-sentence':
|
||||||
this.elements.entityElement.innerHTML = 'Entity';
|
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);
|
this.elements.queryInputField.removeChild(attr);
|
||||||
if (this.elements.queryInputField.children.length === 0) {
|
if (this.elements.queryInputField.children.length === 0) {
|
||||||
@ -198,6 +249,18 @@ class GeneralFunctionsQueryBuilder {
|
|||||||
this.queryPreviewBuilder();
|
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) {
|
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.
|
// 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');
|
let queryChips = this.elements.queryInputField.querySelectorAll('.query-component');
|
||||||
@ -326,7 +389,6 @@ class GeneralFunctionsQueryBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
parseTextToChip(query) {
|
parseTextToChip(query) {
|
||||||
const parsingElementDict = {
|
const parsingElementDict = {
|
||||||
'<s>': {
|
'<s>': {
|
||||||
|
@ -69,7 +69,7 @@ class TokenAttributeBuilderFunctionsQueryBuilder extends GeneralFunctionsQueryBu
|
|||||||
|
|
||||||
optionToggleHandler() {
|
optionToggleHandler() {
|
||||||
let input = this.tokenInputCheck(this.elements.tokenBuilderContent);
|
let input = this.tokenInputCheck(this.elements.tokenBuilderContent);
|
||||||
if ((input.value === '' || input.value === 'default') && this.elements.editingModusOn === false) {
|
if (input.value === '' && this.elements.editingModusOn === false) {
|
||||||
this.toggleClass(['incidence-modifiers', 'or', 'and'], 'disabled', 'add');
|
this.toggleClass(['incidence-modifiers', 'or', 'and'], 'disabled', 'add');
|
||||||
} else if (this.elements.positionalAttrSelection.querySelectorAll('option').length === 1) {
|
} else if (this.elements.positionalAttrSelection.querySelectorAll('option').length === 1) {
|
||||||
this.toggleClass(['and'], 'disabled', 'add');
|
this.toggleClass(['and'], 'disabled', 'add');
|
||||||
@ -108,7 +108,6 @@ class TokenAttributeBuilderFunctionsQueryBuilder extends GeneralFunctionsQueryBu
|
|||||||
tokenQueryPrettyText += `${tokenQueryKindOfToken}=${tokenQueryRowInput.value}${c} ${tokenConditionPrettyText} `;
|
tokenQueryPrettyText += `${tokenQueryKindOfToken}=${tokenQueryRowInput.value}${c} ${tokenConditionPrettyText} `;
|
||||||
tokenQueryCQLText += `${tokenQueryKindOfToken}="${tokenQueryRowInput.value}"${c} ${tokenConditionCQLText}`;
|
tokenQueryCQLText += `${tokenQueryKindOfToken}="${tokenQueryRowInput.value}"${c} ${tokenConditionCQLText}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (kindOfToken === 'empty-token') {
|
if (kindOfToken === 'empty-token') {
|
||||||
tokenQueryPrettyText += 'empty token';
|
tokenQueryPrettyText += 'empty token';
|
||||||
} else {
|
} else {
|
||||||
@ -117,13 +116,12 @@ class TokenAttributeBuilderFunctionsQueryBuilder extends GeneralFunctionsQueryBu
|
|||||||
tokenQueryPrettyText += `${kindOfToken}=${input.value}${c}`;
|
tokenQueryPrettyText += `${kindOfToken}=${input.value}${c}`;
|
||||||
tokenQueryCQLText += `${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.
|
// isTokenQueryInvalid looks if a valid value is passed. If the input fields/dropdowns are empty (isTokenQueryInvalid === true), no token is added.
|
||||||
if ((input.value === '' || input.value === 'default') && this.elements.positionalAttrSelection.value !== 'empty-token') {
|
if (this.elements.positionalAttrSelection.value !== 'empty-token' && input.value === '') {
|
||||||
this.disableTokenSubmit();
|
this.disableTokenSubmit();
|
||||||
} else {
|
} else {
|
||||||
tokenQueryCQLText = `[${tokenQueryCQLText}]`;
|
tokenQueryCQLText = `[${tokenQueryCQLText}]`;
|
||||||
this.submitQueryChipElement('token', tokenQueryPrettyText, tokenQueryCQLText, null, false, true);
|
this.submitQueryChipElement('token', tokenQueryPrettyText, tokenQueryCQLText, null, false, kindOfToken === 'empty-token' ? false : true);
|
||||||
this.elements.positionalAttrModal.close();
|
this.elements.positionalAttrModal.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,7 +130,6 @@ class TokenAttributeBuilderFunctionsQueryBuilder extends GeneralFunctionsQueryBu
|
|||||||
return kindOfToken === 'english-pos' || kindOfToken === 'german-pos' ? 'pos' : kindOfToken;
|
return kindOfToken === 'english-pos' || kindOfToken === 'german-pos' ? 'pos' : kindOfToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
actionButtonInOptionSectionHandler(elem) {
|
actionButtonInOptionSectionHandler(elem) {
|
||||||
let input = this.tokenInputCheck(this.elements.tokenBuilderContent);
|
let input = this.tokenInputCheck(this.elements.tokenBuilderContent);
|
||||||
switch (elem) {
|
switch (elem) {
|
||||||
@ -176,7 +173,7 @@ class TokenAttributeBuilderFunctionsQueryBuilder extends GeneralFunctionsQueryBu
|
|||||||
tokenInput.value += '{' + input + '}';
|
tokenInput.value += '{' + input + '}';
|
||||||
}
|
}
|
||||||
|
|
||||||
conditionHandler(conditionText) {
|
conditionHandler(conditionText, editMode = false) {
|
||||||
let tokenQueryTemplateClone = this.elements.tokenQueryTemplate.content.cloneNode(true);
|
let tokenQueryTemplateClone = this.elements.tokenQueryTemplate.content.cloneNode(true);
|
||||||
tokenQueryTemplateClone.querySelector('.token-query-template-content').appendChild(this.elements.tokenBuilderContent.firstElementChild);
|
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 notSelectedButton = tokenQueryTemplateClone.querySelector(`[data-condition-pretty-text]:not([data-condition-pretty-text="${conditionText}"])`);
|
||||||
|
@ -173,8 +173,8 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="token-query-template-content"></div>
|
<div class="token-query-template-content"></div>
|
||||||
<div class="col s4" style="margin-top:15px;">
|
<div class="col s4" style="margin-top:15px;">
|
||||||
<a class="btn-small waves-effect waves-light disabled" data-condition-pretty-text="or" data-condition-cql-text=" | ">or</a>
|
<a class="btn-small waves-effect waves-light disabled" data-condition-pretty-text="or" data-condition-cql-text="| ">or</a>
|
||||||
<a class="btn-small waves-effect waves-light disabled" data-condition-pretty-text="and" data-condition-cql-text=" & ">and</a>
|
<a class="btn-small waves-effect waves-light disabled" data-condition-pretty-text="and" data-condition-cql-text="& ">and</a>
|
||||||
<a class="btn-floating waves-effect waves-light red" data-token-query-content-action="delete" style="margin-left:8px;"><i class="material-icons right">delete</i></a>
|
<a class="btn-floating waves-effect waves-light red" data-token-query-content-action="delete" style="margin-left:8px;"><i class="material-icons right">delete</i></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -208,7 +208,7 @@
|
|||||||
<template class="token-builder-section" data-token-builder-section="english-pos">
|
<template class="token-builder-section" data-token-builder-section="english-pos">
|
||||||
<div class= "input-field col s4" data-kind-of-token="english-pos">
|
<div class= "input-field col s4" data-kind-of-token="english-pos">
|
||||||
<select name="englishpos">
|
<select name="englishpos">
|
||||||
<option value="default" disabled selected>English pos tagset</option>
|
<option value="" disabled selected>English pos tagset</option>
|
||||||
<option value="ADD">email</option>
|
<option value="ADD">email</option>
|
||||||
<option value="AFX">affix</option>
|
<option value="AFX">affix</option>
|
||||||
<option value="CC">conjunction, coordinating</option>
|
<option value="CC">conjunction, coordinating</option>
|
||||||
@ -264,7 +264,7 @@
|
|||||||
<template class="token-builder-section" data-token-builder-section="german-pos">
|
<template class="token-builder-section" data-token-builder-section="german-pos">
|
||||||
<div class= "input-field col s4" data-kind-of-token="german-pos">
|
<div class= "input-field col s4" data-kind-of-token="german-pos">
|
||||||
<select name="germanpos">
|
<select name="germanpos">
|
||||||
<option value="default" disabled selected>German pos tagset</option>
|
<option value="" disabled selected>German pos tagset</option>
|
||||||
<option value="ADJA">adjective, attributive</option>
|
<option value="ADJA">adjective, attributive</option>
|
||||||
<option value="ADJD">adjective, adverbial or predicative</option>
|
<option value="ADJD">adjective, adverbial or predicative</option>
|
||||||
<option value="ADV">adverb</option>
|
<option value="ADV">adverb</option>
|
||||||
@ -327,7 +327,7 @@
|
|||||||
<template class="token-builder-section" data-token-builder-section="simple_pos">
|
<template class="token-builder-section" data-token-builder-section="simple_pos">
|
||||||
<div class= "input-field col s4" data-kind-of-token="simple_pos">
|
<div class= "input-field col s4" data-kind-of-token="simple_pos">
|
||||||
<select name="simplepos">
|
<select name="simplepos">
|
||||||
<option value="default" disabled selected>simple_pos tagset</option>
|
<option value="" disabled selected>simple_pos tagset</option>
|
||||||
<option value="ADJ">adjective</option>
|
<option value="ADJ">adjective</option>
|
||||||
<option value="ADP">adposition</option>
|
<option value="ADP">adposition</option>
|
||||||
<option value="ADV">adverb</option>
|
<option value="ADV">adverb</option>
|
||||||
@ -367,7 +367,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<p></p>
|
<p></p>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s8" >
|
<div class="col s12" >
|
||||||
<a class="btn-small waves-effect waves-light tooltipped positional-attr-options-action-button" data-options-action="wildcard-char" data-position="top" data-tooltip="Look for a variable character (also called wildcard character)">Wildcard character</a>
|
<a class="btn-small waves-effect waves-light tooltipped positional-attr-options-action-button" data-options-action="wildcard-char" data-position="top" data-tooltip="Look for a variable character (also called wildcard character)">Wildcard character</a>
|
||||||
<a class="btn-small waves-effect waves-light tooltipped positional-attr-options-action-button" data-options-action="option-group" data-position="top" data-tooltip="Find character sequences from a list of options">Option Group</a>
|
<a class="btn-small waves-effect waves-light tooltipped positional-attr-options-action-button" data-options-action="option-group" data-position="top" data-tooltip="Find character sequences from a list of options">Option Group</a>
|
||||||
<a class="dropdown-trigger btn-small waves-effect waves-light disabled" href="#" data-target="corpus-analysis-concordance-character-incidence-modifiers-dropdown" data-toggle-area="incidence-modifiers" data-position="top" data-tooltip="Incidence Modifiers are special characters or patterns, <br>which determine how often a character represented previously should occur.">incidence modifiers</a>
|
<a class="dropdown-trigger btn-small waves-effect waves-light disabled" href="#" data-target="corpus-analysis-concordance-character-incidence-modifiers-dropdown" data-toggle-area="incidence-modifiers" data-position="top" data-tooltip="Incidence Modifiers are special characters or patterns, <br>which determine how often a character represented previously should occur.">incidence modifiers</a>
|
||||||
|
Loading…
Reference in New Issue
Block a user