blob: fd22a9731f9aa830626370fd2968ac596f44b276 [file] [log] [blame]
Nils Diewald86dad5b2015-01-28 15:09:07 +00001/**
Akroncd42a142019-07-12 18:55:37 +02002 * Create virtual corpora with a visual user interface. This resembles the
3 * corpus/collection type objects of a KoralQuery "collection"/"corpus" object.
hebastaa79d69d2018-07-24 12:13:02 +02004 *
Nils Diewald4c221252015-04-21 20:19:25 +00005 * KoralQuery v0.3 is expected.
hebastaa79d69d2018-07-24 12:13:02 +02006 *
Nils Diewald86dad5b2015-01-28 15:09:07 +00007 * @author Nils Diewald
8 */
Nils Diewald2fe12e12015-03-06 16:47:06 +00009/*
Nils Diewald1fcb2ad2015-04-20 19:19:18 +000010 * This replaces a previous version written by Mengfei Zhou
Nils Diewald2fe12e12015-03-06 16:47:06 +000011 */
Nils Diewald2fe12e12015-03-06 16:47:06 +000012
Nils Diewaldd0770492014-12-19 03:55:00 +000013/*
hebasta86759392018-07-25 15:44:37 +020014 TODO: Disable "and" or "or" in case it's followed
15 by an unspecified document
16 TODO: Add "and"-method to root to add further constraints
17 based on match-input (like clicking on a pubDate timestamp in a match)
18 TODO: Implement "persistence"-Option, injecting the current creation
19 date stamp
20 TODO: Implement vec-Type for document-id vectors like docID in [1,2,3,4 ...]
21
22 Error codes:
23 701: "JSON-LD group has no @type attribute"
24 704: "Operation needs operand list"
25 802: "Match type is not supported by value type"
26 804: "Unknown value type"
27 805: "Value is invalid"
28 806: "Value is not a valid date string"
29 807: "Value is not a valid regular expression"
30 810: "Unknown document group operation" (like 711)
31 811: "Document group expects operation" (like 703)
32 812: "Operand not supported in document group" (like 744)
33 813: "Collection type is not supported" (like 713)
34 814: "Unknown rewrite operation"
35 815: "Rewrite expects source"
36
37 Localization strings:
38 KorAP.Locale = {
39 EMPTY : '...',
40 AND : 'and',
41 OR : 'or',
42 DELETE : 'x' }
43
44 and various field names with the prefix 'VC_'
hebastaa79d69d2018-07-24 12:13:02 +020045 */
Nils Diewald86dad5b2015-01-28 15:09:07 +000046
Akronb19803c2018-08-16 16:39:42 +020047define([
48 'vc/unspecified',
49 'vc/doc',
50 'vc/docgroup',
51 'vc/docgroupref',
52 'vc/menu',
53 'vc/statistic',
54 'datepicker',
55 'buttongroup',
hebasta2535c762018-11-21 16:27:33 +010056 'panel/vcpanel',
Akronb19803c2018-08-16 16:39:42 +020057 'view/corpstatv',
Akronec6bb8e2018-08-29 13:07:56 +020058 'buttongroup',
Akronb19803c2018-08-16 16:39:42 +020059 'util'
60], function(
61 unspecDocClass,
62 docClass,
63 docGroupClass,
64 docGroupRefClass,
65 menuClass,
66 statClass,
67 dpClass,
68 buttonGrClass,
hebasta2535c762018-11-21 16:27:33 +010069 vcPanelClass,
Akronec6bb8e2018-08-29 13:07:56 +020070 corpStatVClass,
71 buttonGroupClass) {
Akronb19803c2018-08-16 16:39:42 +020072 "use strict";
Nils Diewald1fcb2ad2015-04-20 19:19:18 +000073
Akronb19803c2018-08-16 16:39:42 +020074 KorAP._validUnspecMatchRE = new RegExp(
75 "^(?:eq|ne|contains(?:not)?|excludes)$");
76 KorAP._validStringMatchRE = new RegExp("^(?:eq|ne)$");
77 KorAP._validTextMatchRE = KorAP._validUnspecMatchRE;
78 KorAP._validTextOnlyMatchRE = new RegExp(
79 "^(?:contains(?:not)?|excludes)$");
80 KorAP._overrideStyles = false;
81 // KorAP._validDateMatchRE is defined in datepicker.js!
Nils Diewaldd0770492014-12-19 03:55:00 +000082
Akronb19803c2018-08-16 16:39:42 +020083 const loc = KorAP.Locale;
hebastaa0282be2018-12-05 16:58:00 +010084 loc.SHOW_STAT = loc.SHOW_STAT || 'Statistics';
85 loc.VERB_SHOWSTAT = loc.VERB_SHOWSTAT || 'Corpus Statistics';
Akron8a670162018-08-28 10:09:13 +020086 loc.VC_allCorpora = loc.VC_allCorpora || 'all corpora';
87 loc.VC_oneCollection = loc.VC_oneCollection || 'a virtual corpus';
Akronec6bb8e2018-08-29 13:07:56 +020088 loc.MINIMIZE = loc.MINIMIZE || 'Minimize';
Nils Diewald3a2d8022014-12-16 02:45:41 +000089
Akronb19803c2018-08-16 16:39:42 +020090 KorAP._vcKeyMenu = undefined;
91 KorAP._vcDatePicker = dpClass.create();
Nils Diewald3a2d8022014-12-16 02:45:41 +000092
Akronb19803c2018-08-16 16:39:42 +020093 // Create match menus ....
94 KorAP._vcMatchopMenu = {
95 'string' : menuClass.create([
96 [ 'eq', null ],
97 [ 'ne', null ]
98 ]),
99 'text' : menuClass.create([
100 [ 'eq', null ], // Requires exact match
101 [ 'ne', null ],
102 [ 'contains', null ], // Requires token sequence match
103 [ 'containsnot', null ]
104 ]),
105 'date' : menuClass.create([
106 [ 'eq', null ],
107 [ 'ne', null ],
108 [ 'geq', null ],
109 [ 'leq', null ]
110 ]),
111 'regex' : menuClass.create([
112 [ 'eq', null ],
113 [ 'ne', null ]
114 ])
115 };
116
117 /**
Akroncd42a142019-07-12 18:55:37 +0200118 * Virtual corpus
Akronb19803c2018-08-16 16:39:42 +0200119 */
120 return {
121
122 /**
Akroncd42a142019-07-12 18:55:37 +0200123 * The JSON-LD type of the virtual corpus
Akronb19803c2018-08-16 16:39:42 +0200124 */
125 ldType : function() {
126 return null;
127 },
128
Akroncd42a142019-07-12 18:55:37 +0200129 // Initialize virtual corpus
Akronb19803c2018-08-16 16:39:42 +0200130 _init : function(keyList) {
131
132 // Inject localized css styles
133 if (!KorAP._overrideStyles) {
134 var sheet = KorAP.newStyleSheet();
135
136 // Add css rule for OR operations
137 sheet.insertRule('.vc .docGroup[data-operation=or] > .doc::before,'
138 + '.vc .docGroup[data-operation=or] > .docGroup::before '
139 + '{ content: "' + loc.OR + '" }', 0);
140
141 // Add css rule for AND operations
142 sheet.insertRule(
143 '.vc .docGroup[data-operation=and] > .doc::before,'
144 + '.vc .docGroup[data-operation=and] > .docGroup::before '
145 + '{ content: "' + loc.AND + '" }', 1);
146
147 KorAP._overrideStyles = true;
Nils Diewald359a72c2015-04-20 17:40:29 +0000148 };
149
Akron3ad46942018-08-22 16:47:14 +0200150 var l;
151 if (keyList) {
Akronadab5e52018-08-20 13:50:53 +0200152 l = keyList.slice();
Akron3ad46942018-08-22 16:47:14 +0200153 l.unshift(['referTo', 'ref']);
154 }
155 else {
156 l = [['referTo', 'ref']];
157 }
Akronadab5e52018-08-20 13:50:53 +0200158
Akronb19803c2018-08-16 16:39:42 +0200159 // Create key menu
Akron3ad46942018-08-22 16:47:14 +0200160 KorAP._vcKeyMenu = menuClass.create(l);
Akronb19803c2018-08-16 16:39:42 +0200161 KorAP._vcKeyMenu.limit(6);
Akron712733a2018-04-05 18:17:47 +0200162
Akronb19803c2018-08-16 16:39:42 +0200163 return this;
164 },
Nils Diewald359a72c2015-04-20 17:40:29 +0000165
Akronb19803c2018-08-16 16:39:42 +0200166 /**
Akroncd42a142019-07-12 18:55:37 +0200167 * Create a new virtual corpus
Akronb19803c2018-08-16 16:39:42 +0200168 */
169 create : function(keyList) {
170 var obj = Object.create(this)._init(keyList);
171 obj._root = unspecDocClass.create(obj);
172 return obj;
173 },
Nils Diewaldd599d542015-01-08 20:41:34 +0000174
Akron8a670162018-08-28 10:09:13 +0200175
Akronb19803c2018-08-16 16:39:42 +0200176 /**
Akroncd42a142019-07-12 18:55:37 +0200177 * Create and render a new virtual corpus based on a KoralQuery
178 * corpus document
Akronb19803c2018-08-16 16:39:42 +0200179 */
180 fromJson : function(json) {
Akron13af2f42019-07-25 15:06:21 +0200181
182 let obj;
183
Akronb19803c2018-08-16 16:39:42 +0200184 if (json !== undefined) {
185 // Parse root document
186 if (json['@type'] == 'koral:doc') {
Akron13af2f42019-07-25 15:06:21 +0200187 obj = docClass.create(this, json);
hebastaa79d69d2018-07-24 12:13:02 +0200188 }
Akronb19803c2018-08-16 16:39:42 +0200189 // parse root group
190 else if (json['@type'] == 'koral:docGroup') {
Akron13af2f42019-07-25 15:06:21 +0200191 obj = docGroupClass.create(this, json);
Akronb19803c2018-08-16 16:39:42 +0200192 }
193
194 // parse root reference
195 else if (json['@type'] == 'koral:docGroupRef') {
Akron13af2f42019-07-25 15:06:21 +0200196 obj = docGroupRefClass.create(this, json);
Akronb19803c2018-08-16 16:39:42 +0200197 }
198
199 // Unknown collection type
200 else {
201 KorAP.log(813, "Collection type is not supported");
202 return;
203 };
204 }
205
206 else {
207 // Add unspecified object
Akron13af2f42019-07-25 15:06:21 +0200208 obj = unspecDocClass.create(this);
Nils Diewald845282c2015-05-14 07:53:03 +0000209 };
Akronb19803c2018-08-16 16:39:42 +0200210
211 // Init element and update
Akron13af2f42019-07-25 15:06:21 +0200212 this.root(obj);
213
Akronb19803c2018-08-16 16:39:42 +0200214 return this;
215 },
Akrond2474aa2018-08-28 12:06:27 +0200216
217
218 // Check if the virtual corpus contains a rerite
219 wasRewritten : function (obj) {
220
221 var obj;
222 if (arguments.length === 1) {
223 obj = arguments[0];
224 }
225 else {
226 obj = this._root;
227 };
228
229 // Check for rewrite
230 if (obj.rewrites() && obj.rewrites().length() > 0) {
231 return true;
232 }
233
234 // Check recursively
235 else if (obj.ldType() === 'docGroup') {
236 for (var i in obj.operands()) {
237 if (this.wasRewritten(obj.getOperand(i))) {
238 return true;
239 }
240 };
241 };
242
243 return false;
244 },
Akron43c5cc62018-08-28 13:10:25 +0200245
Akron8a670162018-08-28 10:09:13 +0200246
Akronb19803c2018-08-16 16:39:42 +0200247 /**
hebasta3f4be922018-12-11 10:41:46 +0100248 * Clean the virtual document to unspecified doc.
Akronb19803c2018-08-16 16:39:42 +0200249 */
250 clean : function() {
251 if (this._root.ldType() !== "non") {
252 this._root.destroy();
253 this.root(unspecDocClass.create(this));
254 };
hebasta3f4be922018-12-11 10:41:46 +0100255 //update for graying corpus statistic by deleting the first line of the vc builder
256 this.update();
Akronb19803c2018-08-16 16:39:42 +0200257 return this;
258 },
259
260 /**
Akroncd42a142019-07-12 18:55:37 +0200261 * Get or set the root object of the virtual corpus
Akronb19803c2018-08-16 16:39:42 +0200262 */
263 root : function(obj) {
264 if (arguments.length === 1) {
Akronadab5e52018-08-20 13:50:53 +0200265 var e = this.builder();
Akronb19803c2018-08-16 16:39:42 +0200266
267 if (e.firstChild !== null) {
Akronadab5e52018-08-20 13:50:53 +0200268
269 // Object not yet set
Akronb19803c2018-08-16 16:39:42 +0200270 if (e.firstChild !== obj.element()) {
271 e.replaceChild(obj.element(), e.firstChild);
272 };
273 }
274
275 // Append root element
276 else {
277 e.appendChild(obj.element());
278 };
279
280 // Update parent child relations
281 this._root = obj;
282 obj.parent(this);
283
284 this.update();
285 };
286 return this._root;
287 },
288
Akronadab5e52018-08-20 13:50:53 +0200289
290 /**
291 * Get the wrapper element associated with the vc
292 */
293 builder : function () {
294
295 // Initialize if necessary
296 if (this._builder !== undefined)
297 return this._builder;
298
299 this.element();
300 return this._builder;
301 },
302
Akronb19803c2018-08-16 16:39:42 +0200303 /**
Akron68d28322018-08-27 15:02:42 +0200304 * Get the element associated with the virtual corpus
Akronb19803c2018-08-16 16:39:42 +0200305 */
306 element : function() {
Akronb19803c2018-08-16 16:39:42 +0200307 if (this._element !== undefined) {
308 return this._element;
309 };
310
311 this._element = document.createElement('div');
Akronec6bb8e2018-08-29 13:07:56 +0200312 this._element.classList.add('vc');
313
Akronb19803c2018-08-16 16:39:42 +0200314
Akronadab5e52018-08-20 13:50:53 +0200315 this._builder = this._element.addE('div');
316 this._builder.setAttribute('class', 'builder');
317
Akronec6bb8e2018-08-29 13:07:56 +0200318 var btn = buttonGroupClass.create(
319 ['action','button-view']
320 );
321 var that = this;
322 btn.add(loc.MINIMIZE, ['button-icon','minimize'], function () {
323 that.minimize();
324 });
325 this._element.appendChild(btn.element());
326
327
328
Akronb19803c2018-08-16 16:39:42 +0200329 // Initialize root
Akronadab5e52018-08-20 13:50:53 +0200330 this._builder.appendChild(this._root.element());
Akronb19803c2018-08-16 16:39:42 +0200331
332 // Add panel to display corpus statistic, ...
333 this.addVcInfPanel();
334
hebasta4dd77bc2019-02-07 12:57:57 +0100335 //Adds EventListener for corpus changes
336 this._element.addEventListener('vcChange', function (e) {
337 that.checkStatActive(e.detail);
338 }, false);
339
Akronb19803c2018-08-16 16:39:42 +0200340 return this._element;
341 },
342
Akronec6bb8e2018-08-29 13:07:56 +0200343
344 /**
345 * Check, if the VC is open
346 */
347 isOpen : function () {
348 if (!this._element)
349 return false;
350 return this._element.classList.contains('active');
351 },
352
353 /**
354 * Open the VC view
355 */
356 open : function () {
357 this.element().classList.add('active');
358 if (this.onOpen)
359 this.onOpen();
360 },
361
362
363 /**
364 * Minimize the VC view
365 */
366 minimize : function () {
367 this.element().classList.remove('active');
368 if (this.onMinimize)
369 this.onMinimize();
370 },
371
372
Akronb19803c2018-08-16 16:39:42 +0200373 /**
374 * Update the whole object based on the underlying data structure
hebastaa0282be2018-12-05 16:58:00 +0100375 */
Akronb19803c2018-08-16 16:39:42 +0200376 update : function() {
377 this._root.update();
hebasta4dd77bc2019-02-07 12:57:57 +0100378 if (KorAP.vc){
379 var vcchevent = new CustomEvent('vcChange', {'detail':this});
380 this.element().dispatchEvent(vcchevent);
381 };
Akronb19803c2018-08-16 16:39:42 +0200382 return this;
383 },
Akronb19803c2018-08-16 16:39:42 +0200384 /**
385 * Make the vc persistant by injecting the current timestamp as a
386 * creation date limit criterion.
387 * THIS IS CURRENTLY NOT USED
388 */
389 makePersistant : function() {
390 // this.root().wrapOnRoot('and');
391 var todayStr = KorAP._vcDatePicker.today();
392 var doc = docClass.create();
393 var root = this.root();
394
395 if (root.ldType() === 'docGroup' && root.operation === 'and') {
396 root.append(cond);
397 } else {
398 root.wrapOnRoot('and');
399 root.append(doc);
400 };
401
402 doc.key("creationDate");
403 doc.type("date");
404 doc.matchop("leq");
405 doc.value(todayStr);
406
407 /*
408 * { "@type" : "koral:doc", "key" : "creationDate", "type" :
409 * "type:date", "match" : "match:leq", "value" : todayStr }
410 * this.root().append(cond);
411 */
412 this.update();
413 },
414
Akron8a670162018-08-28 10:09:13 +0200415
416 // Get the reference name
417 getName : function () {
418 if (this._root.ldType() === 'non') {
419 return loc.VC_allCorpora;
420 }
421 else if (this._root.ldType() === 'docGroupRef') {
422 return this._root.ref();
423 }
424 else {
425 return loc.VC_oneCollection;
426 }
427 },
428
Akron4feec9d2018-11-20 17:00:50 +0100429 // Add "and" constraint to VC
430 addRequired : function (doc) {
431
432 let root = this.root();
433 let ldType = root.ldType();
434
435 let parent = root.parent();
436 if (ldType === 'non') {
437 parent.root(doc);
438 }
439
440 // root is doc
441 else if (
442 ldType === 'doc' ||
443 ldType === 'docGroupRef' ||
444 (ldType === 'docGroup' &&
445 root.operation() === 'or'
446 )) {
447 let group = require('vc/docgroup').create(
448 parent
449 );
450 group.operation("and");
451 group.append(root);
452 group.append(doc);
453 group.element(); // Init (seems to be necessary)
454 parent.root(group);
455 }
456
457 // root is a docGroup
458 // and is already an 'and'-Group
459 else if (ldType === 'docGroup') {
460 root.append(doc);
461 }
462
463 // Unknown
464 else {
465 console.log("Unknown root object");
466 };
467
468 // Init element and update
469 this.update();
470 },
Akron8a670162018-08-28 10:09:13 +0200471
Akronb19803c2018-08-16 16:39:42 +0200472 /**
473 * Get the generated json string
474 */
475 toJson : function() {
476 return this._root.toJson();
477 },
478
479 /**
480 * Get the generated query string
481 */
482 toQuery : function() {
483 return this._root.toQuery();
484 },
485
hebasta2535c762018-11-21 16:27:33 +0100486
487 /**
Akronb19803c2018-08-16 16:39:42 +0200488 * Add panel to display virtual corpus information
489 */
490 addVcInfPanel : function() {
Akronb19803c2018-08-16 16:39:42 +0200491 var dv = this._element.addE('div');
hebasta2535c762018-11-21 16:27:33 +0100492 //Create panel
493 this.panel = vcPanelClass.create(this);
494 dv.appendChild(this.panel.element());
hebastaa0282be2018-12-05 16:58:00 +0100495
hebasta2535c762018-11-21 16:27:33 +0100496 },
497
hebastaa0282be2018-12-05 16:58:00 +0100498 /**
hebasta48842cf2018-12-11 12:57:38 +0100499 * Checks if corpus statistic has to be disabled,
hebastaa0282be2018-12-05 16:58:00 +0100500 * and to be updated after clicking at the "reload-button"
501 */
hebasta48842cf2018-12-11 12:57:38 +0100502 checkStatActive : function(){
hebastaa0282be2018-12-05 16:58:00 +0100503 if(this.panel !== undefined && this.panel.statView !==undefined){
hebasta48842cf2018-12-11 12:57:38 +0100504 this.panel.statView.checkStatActive();
hebastaa0282be2018-12-05 16:58:00 +0100505 }
506 }
Akronb19803c2018-08-16 16:39:42 +0200507 };
508});