blob: 4e3786ba00e133fbe8e2eb0a502a7f8767817866 [file] [log] [blame]
Nils Diewalde8518f82015-03-18 22:41:49 +00001/**
Nils Diewalda297f062015-04-02 00:23:46 +00002 * Get information on matches,
3 * generate annotation tables and trees.
Nils Diewalde8518f82015-03-18 22:41:49 +00004 *
5 * @author Nils Diewald
6 */
Nils Diewalda297f062015-04-02 00:23:46 +00007// require menu.js, dagre
Nils Diewalde8518f82015-03-18 22:41:49 +00008/*
Nils Diewald6e43ffd2015-03-25 18:55:39 +00009 * - Highlight (at least mark as bold) the match
10 * - Scroll to match vertically per default
Nils Diewalde8518f82015-03-18 22:41:49 +000011 */
12var KorAP = KorAP || {};
13
14(function (KorAP) {
15 "use strict";
16
Nils Diewald6e43ffd2015-03-25 18:55:39 +000017 var svgXmlns = "http://www.w3.org/2000/svg";
18
Nils Diewald4f6521a2015-03-20 21:30:13 +000019 // Default log message
20 KorAP.log = KorAP.log || function (type, msg) {
21 console.log(type + ": " + msg);
22 };
23
Nils Diewald6e43ffd2015-03-25 18:55:39 +000024 // Localization values
25 var loc = (KorAP.Locale = KorAP.Locale || {} );
Nils Diewalda297f062015-04-02 00:23:46 +000026 loc.ADDTREE = loc.ADDTREE || 'Add tree view';
27 loc.SHOWINFO = loc.SHOWINFO || 'Show information';
28 loc.CLOSE = loc.CLOSE || 'Close';
Nils Diewald6e43ffd2015-03-25 18:55:39 +000029
Nils Diewalde8518f82015-03-18 22:41:49 +000030 KorAP._AvailableRE = new RegExp("^([^\/]+?)\/([^=]+?)(?:=(spans|rels|tokens))?$");
Nils Diewalda297f062015-04-02 00:23:46 +000031 KorAP._TermRE = new RegExp("^(?:([^\/]+?)\/)?([^:]+?):(.+?)$");
32 KorAP._matchTerms = ['corpusID', 'docID', 'textID', 'matchID', 'available'];
Nils Diewalde8518f82015-03-18 22:41:49 +000033
34 // API requests
35 KorAP.API = KorAP.API || {};
Nils Diewald6e43ffd2015-03-25 18:55:39 +000036
Nils Diewald8bc7e412015-03-19 22:08:27 +000037 // TODO: Make this async
Nils Diewald58141332015-04-07 16:18:45 +000038 KorAP.API.getMatchInfo = KorAP.API.getMatchInfo || function () {
39 KorAP.log(0, 'KorAP.API.getMatchInfo() not implemented')
40 return {};
41 };
Nils Diewalda297f062015-04-02 00:23:46 +000042
43 /**
44 * Match object
45 */
46 KorAP.Match = {
Nils Diewalde8518f82015-03-18 22:41:49 +000047
48 /**
49 * Create a new annotation object.
50 * Expects an array of available foundry/layer=type terms.
51 * Supported types are 'spans', 'tokens' and 'rels'.
52 */
Nils Diewalda297f062015-04-02 00:23:46 +000053 create : function (match) {
54 return Object.create(KorAP.Match)._init(match);
Nils Diewalde8518f82015-03-18 22:41:49 +000055 },
56
Nils Diewald6e43ffd2015-03-25 18:55:39 +000057 /**
Nils Diewalda297f062015-04-02 00:23:46 +000058 * Initialize match.
Nils Diewald6e43ffd2015-03-25 18:55:39 +000059 */
Nils Diewalda297f062015-04-02 00:23:46 +000060 _init : function (match) {
61 this._element = null;
Nils Diewald6e43ffd2015-03-25 18:55:39 +000062
Nils Diewalda297f062015-04-02 00:23:46 +000063 // No match defined
64 if (arguments.length < 1 ||
65 match === null ||
66 match === undefined) {
67 throw new Error('Missing parameters');
68 }
Nils Diewald6e43ffd2015-03-25 18:55:39 +000069
Nils Diewalda297f062015-04-02 00:23:46 +000070 // Match defined as a node
71 else if (match instanceof Node) {
72 this._element = match;
Nils Diewald6e43ffd2015-03-25 18:55:39 +000073
Nils Diewalda297f062015-04-02 00:23:46 +000074 // Circular reference !!
75 match["_match"] = this;
76
77 this.corpusID = match.getAttribute('data-corpus-id'),
78 this.docID = match.getAttribute('data-doc-id'),
79 this.textID = match.getAttribute('data-text-id'),
80 this.matchID = match.getAttribute('data-match-id')
81
82 // List of available annotations
83 this.available = match.getAttribute('data-available-info').split(' ');
84 }
85
86 // Match as an object
87 else {
88
89 // Iterate over allowed match terms
90 for (var i in KorAP._matchTerms) {
91 var term = KorAP._matchTerms[i];
92 if (match[term] !== undefined) {
93 this[term] = match[term];
94 }
95 else {
96 this[term] = undefined;
97 }
98 };
99 };
100
Nils Diewalde8518f82015-03-18 22:41:49 +0000101 this._available = {
102 tokens : [],
Nils Diewalda297f062015-04-02 00:23:46 +0000103 spans : [],
104 rels : []
Nils Diewalde8518f82015-03-18 22:41:49 +0000105 };
Nils Diewalda297f062015-04-02 00:23:46 +0000106
107 // Iterate over info layers
108 for (var i = 0; i < this.available.length; i++) {
109 var term = this.available[i];
110
Nils Diewalde8518f82015-03-18 22:41:49 +0000111 // Create info layer objects
112 try {
113 var layer = KorAP.InfoLayer.create(term);
114 this._available[layer.type].push(layer);
115 }
116 catch (e) {
117 continue;
118 };
119 };
Nils Diewalda297f062015-04-02 00:23:46 +0000120
Nils Diewalde8518f82015-03-18 22:41:49 +0000121 return this;
122 },
123
124
125 /**
126 * Return a list of parseable tree annotations.
127 */
128 getSpans : function () {
129 return this._available.spans;
130 },
131
132
133 /**
134 * Return a list of parseable token annotations.
135 */
136 getTokens : function () {
137 return this._available.tokens;
138 },
139
140
141 /**
142 * Return a list of parseable relation annotations.
143 */
144 getRels : function () {
145 return this._available.rels;
146 },
147
Nils Diewalda297f062015-04-02 00:23:46 +0000148 /**
149 * Open match
150 */
151 open : function () {
152
153 // Add actions unless it's already activated
154 var element = this._element;
155
156 // There is an element to open
157 if (this._element === undefined || this._element === null)
158 return false;
159
160 // The element is already opened
161 if (element.classList.contains('active'))
162 return false;
163
164 // Add active class to element
165 element.classList.add('active');
166
167 // Create action buttons
168 var ul = document.createElement('ul');
169 ul.classList.add('action', 'right');
170 element.appendChild(ul);
171
172 // Use localization
173 var loc = KorAP.Locale;
174
175 // Add close button
176 var close = document.createElement('li');
177 close.appendChild(document.createElement('span'))
178 .appendChild(document.createTextNode(loc.CLOSE));
179 close.classList.add('close');
180 close.setAttribute('title', loc.CLOSE);
181
182 // Add info button
183 var info = document.createElement('li');
184 info.appendChild(document.createElement('span'))
185 .appendChild(document.createTextNode(loc.SHOWINFO));
186 info.classList.add('info');
187 info.setAttribute('title', loc.SHOWINFO);
188
189 var that = this;
190
191 // Close match
192 close.addEventListener('click', function (e) {
193 e.halt();
194 that.close()
195 });
196
197 // Add information, unless it already exists
198 info.addEventListener('click', function (e) {
199 e.halt();
Nils Diewald5c5a7472015-04-02 22:13:38 +0000200 that.info().toggle();
Nils Diewalda297f062015-04-02 00:23:46 +0000201 });
202
203 ul.appendChild(close);
204 ul.appendChild(info);
205
206 return true;
207 },
208
Nils Diewald8bc7e412015-03-19 22:08:27 +0000209 /**
Nils Diewalda297f062015-04-02 00:23:46 +0000210 * Close info view
211 */
212 close : function () {
213 this._element.classList.remove('active');
214
215/*
216 if (this._info !== undefined) {
217 this._info.destroy();
218 };
219*/
220 },
221
222
Nils Diewalda297f062015-04-02 00:23:46 +0000223 /**
224 * Get and open associated match info.
225 */
226 info : function () {
227
228 // Create match info
229 if (this._info === undefined)
230 this._info = KorAP.MatchInfo.create(this);
231
232 // There is an element to append
233 if (this._element === undefined ||
234 this._element === null)
235 return this._info;
236
237 // Info is already activated
Nils Diewald5c5a7472015-04-02 22:13:38 +0000238 if (this._info._element !== undefined)
Nils Diewalda297f062015-04-02 00:23:46 +0000239 return this._info;
240
Nils Diewalda297f062015-04-02 00:23:46 +0000241 return this._info;
242 },
243
244
245 /**
246 * Get match element.
247 */
248 element : function () {
249
250 // May be null
251 return this._element;
252 }
253 };
254
255
256
257 /**
258 * Information about a match.
259 */
260 KorAP.MatchInfo = {
261
262 /**
263 * Create new object
264 */
265 create : function (match) {
266 return Object.create(KorAP.MatchInfo)._init(match);
267 },
268
269 /**
270 * Initialize object
271 */
272 _init : function (match) {
273 this._match = match;
Nils Diewald5c5a7472015-04-02 22:13:38 +0000274 this.opened = false;
Nils Diewalda297f062015-04-02 00:23:46 +0000275 return this;
276 },
277
Nils Diewalda297f062015-04-02 00:23:46 +0000278 /**
279 * Get match object
280 */
281 match : function () {
282 return this._match;
283 },
284
Nils Diewald5c5a7472015-04-02 22:13:38 +0000285 toggle : function () {
286 if (this.opened == true) {
287 this._match.element().children[0].removeChild(
288 this.element()
289 );
290 this.opened = false;
291 }
292 else {
293 // Append element to match
294 this._match.element().children[0].appendChild(
295 this.element()
296 );
297 this.opened = true;
298 };
299
300 return this.opened;
301 },
302
Nils Diewalda297f062015-04-02 00:23:46 +0000303
304 /**
305 * Retrieve and parse snippet for table representation
Nils Diewald8bc7e412015-03-19 22:08:27 +0000306 */
Nils Diewald58141332015-04-07 16:18:45 +0000307 getTable : function (tokens, cb) {
Nils Diewalde8518f82015-03-18 22:41:49 +0000308 var focus = [];
309
310 // Get all tokens
311 if (tokens === undefined) {
Nils Diewalda297f062015-04-02 00:23:46 +0000312 focus = this._match.getTokens();
Nils Diewalde8518f82015-03-18 22:41:49 +0000313 }
314
315 // Get only some tokens
316 else {
317
318 // Push newly to focus array
319 for (var i = 0; i < tokens.length; i++) {
320 var term = tokens[i];
321 try {
322 // Create info layer objects
323 var layer = KorAP.InfoLayer.create(term);
324 layer.type = "tokens";
325 focus.push(layer);
326 }
327 catch (e) {
328 continue;
329 };
330 };
331 };
332
333 // No tokens chosen
334 if (focus.length == 0)
Nils Diewald58141332015-04-07 16:18:45 +0000335 cb(null);
Nils Diewalde8518f82015-03-18 22:41:49 +0000336
337 // Get info (may be cached)
Nils Diewald4f6521a2015-03-20 21:30:13 +0000338 // TODO: Async
Nils Diewald58141332015-04-07 16:18:45 +0000339 KorAP.API.getMatchInfo(
Nils Diewalde8518f82015-03-18 22:41:49 +0000340 this._match,
Nils Diewald58141332015-04-07 16:18:45 +0000341 { 'spans' : false, 'layer' : focus },
342
343 // Callback for retrieval
344 function (matchResponse) {
345 // Get snippet from match info
346 if (matchResponse["snippet"] !== undefined) {
347 this._table = KorAP.MatchTable.create(matchResponse["snippet"]);
348 cb(this._table);
349 };
350 }.bind(this)
Nils Diewalde8518f82015-03-18 22:41:49 +0000351 );
352
Nils Diewald58141332015-04-07 16:18:45 +0000353/*
Nils Diewalda297f062015-04-02 00:23:46 +0000354 // Todo: Store the table as a hash of the focus
Nils Diewalde8518f82015-03-18 22:41:49 +0000355 return null;
Nils Diewald58141332015-04-07 16:18:45 +0000356*/
Nils Diewald4f6521a2015-03-20 21:30:13 +0000357 },
Nils Diewalde8518f82015-03-18 22:41:49 +0000358
Nils Diewalda297f062015-04-02 00:23:46 +0000359
360 /**
361 * Retrieve and parse snippet for tree representation
362 */
Nils Diewald58141332015-04-07 16:18:45 +0000363 getTree : function (foundry, layer, cb) {
Nils Diewald4f6521a2015-03-20 21:30:13 +0000364 var focus = [];
365
Nils Diewald58141332015-04-07 16:18:45 +0000366 // TODO: Support and cache multiple trees
367
368 KorAP.API.getMatchInfo(
Nils Diewald4f6521a2015-03-20 21:30:13 +0000369 this._match, {
370 'spans' : true,
371 'foundry' : foundry,
372 'layer' : layer
Nils Diewald58141332015-04-07 16:18:45 +0000373 },
374 function (matchResponse) {
375 // Get snippet from match info
376 if (matchResponse["snippet"] !== undefined) {
377 // Todo: This should be cached somehow
378 cb(KorAP.MatchTree.create(matchResponse["snippet"]));
379 }
380 else {
381 cb(null);
382 };
383 }.bind(this)
Nils Diewald4f6521a2015-03-20 21:30:13 +0000384 );
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000385 },
386
387 /**
Nils Diewalda297f062015-04-02 00:23:46 +0000388 * Destroy this match information view.
389 */
390 destroy : function () {
391
392 // Remove circular reference
393 if (this._treeMenu !== undefined)
394 delete this._treeMenu["info"];
395
396 this._treeMenu.destroy();
397 this._treeMenu = undefined;
398 this._match = undefined;
399
400 // Element destroy
401 },
402
403
404 /**
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000405 * Add a new tree view to the list
406 */
Nils Diewald58141332015-04-07 16:18:45 +0000407 addTree : function (foundry, layer, cb) {
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000408 var matchtree = document.createElement('div');
409 matchtree.classList.add('matchtree');
410
411 var h6 = matchtree.appendChild(document.createElement('h6'));
412 h6.appendChild(document.createElement('span'))
413 .appendChild(document.createTextNode(foundry));
414 h6.appendChild(document.createElement('span'))
415 .appendChild(document.createTextNode(layer));
416
417 var tree = matchtree.appendChild(
418 document.createElement('div')
419 );
Nils Diewald58141332015-04-07 16:18:45 +0000420
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000421 this._element.insertBefore(matchtree, this._element.lastChild);
422
423 var close = tree.appendChild(document.createElement('em'));
424 close.addEventListener(
425 'click', function (e) {
426 matchtree.parentNode.removeChild(matchtree);
427 e.halt();
428 }
429 );
430
Nils Diewald58141332015-04-07 16:18:45 +0000431 // Get tree data async
432 this.getTree(foundry, layer, function (treeObj) {
433 // Something went wrong - probably log!!!
434 if (treeObj === null) {
435 tree.appendChild(document.createTextNode('No data available.'));
436 }
437 else {
438 tree.appendChild(treeObj.element());
439 // Reposition the view to the center
440 // (This may in a future release be a reposition
441 // to move the root into the center or the actual
442 // match)
443 treeObj.center();
444 }
445
446 if (cb !== undefined)
447 cb(treeObj);
448 });
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000449 },
450
451 /**
452 * Create match information view.
453 */
454 element : function () {
Nils Diewalda297f062015-04-02 00:23:46 +0000455
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000456 if (this._element !== undefined)
457 return this._element;
458
459 // Create info table
460 var info = document.createElement('div');
461 info.classList.add('matchinfo');
462
463 // Append default table
464 var matchtable = document.createElement('div');
465 matchtable.classList.add('matchtable');
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000466 info.appendChild(matchtable);
467
Nils Diewald58141332015-04-07 16:18:45 +0000468 // Create the table asynchronous
469 this.getTable(undefined, function (table) {
470 if (table !== null) {
471 matchtable.appendChild(table.element());
472 };
473 });
474
Nils Diewalda297f062015-04-02 00:23:46 +0000475 // Get spans
476 var spanLayers = this._match.getSpans().sort(
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000477 function (a, b) {
478 if (a.foundry < b.foundry) {
479 return -1;
480 }
481 else if (a.foundry > b.foundry) {
482 return 1;
483 }
484 else if (a.layer < b.layer) {
485 return -1;
486 }
487 else if (a.layer > b.layer) {
488 return 1;
489 };
490 return 0;
491 });
492
493 var menuList = [];
494
495 // Show tree views
496 for (var i = 0; i < spanLayers.length; i++) {
497 var span = spanLayers[i];
498
499 // Add foundry/layer to menu list
500 menuList.push([
501 span.foundry + '/' + span.layer,
502 span.foundry,
503 span.layer
504 ]);
505 };
506
507 // Create tree menu
508 var treemenu = this.treeMenu(menuList);
509 var span = info.appendChild(document.createElement('p'));
Nils Diewalda297f062015-04-02 00:23:46 +0000510 span.classList.add('addtree');
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000511 span.appendChild(document.createTextNode(loc.ADDTREE));
512
513 var treeElement = treemenu.element();
514 span.appendChild(treeElement);
515
516 span.addEventListener('click', function (e) {
517 treemenu.show('');
518 treemenu.focus();
519 });
520
521 this._element = info;
522
523 return info;
Nils Diewalda297f062015-04-02 00:23:46 +0000524
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000525 },
526
Nils Diewalda297f062015-04-02 00:23:46 +0000527
528 /**
529 * Get tree menu.
530 * There is only one menu rendered
531 * - no matter how many trees exist
532 */
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000533 treeMenu : function (list) {
534 if (this._treeMenu !== undefined)
535 return this._treeMenu;
536
537 return this._treeMenu = KorAP.MatchTreeMenu.create(this, list);
Nils Diewald8bc7e412015-03-19 22:08:27 +0000538 }
Nils Diewalde8518f82015-03-18 22:41:49 +0000539 };
540
Nils Diewalda297f062015-04-02 00:23:46 +0000541
Nils Diewalde8518f82015-03-18 22:41:49 +0000542
543 /**
544 *
545 * Alternatively pass a string as <tt>base/s=span</tt>
546 *
547 * @param foundry
548 */
549 KorAP.InfoLayer = {
550 create : function (foundry, layer, type) {
551 return Object.create(KorAP.InfoLayer)._init(foundry, layer, type);
552 },
553 _init : function (foundry, layer, type) {
554 if (foundry === undefined)
555 throw new Error("Missing parameters");
556
557 if (layer === undefined) {
558 if (KorAP._AvailableRE.exec(foundry)) {
559 this.foundry = RegExp.$1;
560 this.layer = RegExp.$2;
561 this.type = RegExp.$3;
562 }
563 else {
564 throw new Error("Missing parameters");
565 };
566 }
567 else {
568 this.foundry = foundry;
569 this.layer = layer;
570 this.type = type;
571 };
572
573 if (this.type === undefined)
574 this.type = 'tokens';
575
576 return this;
577 }
578 };
579
580
Nils Diewald8bc7e412015-03-19 22:08:27 +0000581 KorAP.MatchTable = {
Nils Diewalde8518f82015-03-18 22:41:49 +0000582 create : function (snippet) {
Nils Diewald8bc7e412015-03-19 22:08:27 +0000583 return Object.create(KorAP.MatchTable)._init(snippet);
Nils Diewalde8518f82015-03-18 22:41:49 +0000584 },
585 _init : function (snippet) {
586 // Create html for traversal
587 var html = document.createElement("div");
588 html.innerHTML = snippet;
589
590 this._pos = 0;
591 this._token = [];
592 this._info = [];
Nils Diewald8bc7e412015-03-19 22:08:27 +0000593 this._foundry = {};
594 this._layer = {};
Nils Diewalde8518f82015-03-18 22:41:49 +0000595
596 // Parse the snippet
597 this._parse(html.childNodes);
598
Nils Diewalde8518f82015-03-18 22:41:49 +0000599 html.innerHTML = '';
600 return this;
601 },
602
603 length : function () {
604 return this._pos;
605 },
606
607 getToken : function (pos) {
608 if (pos === undefined)
609 return this._token;
610 return this._token[pos];
611 },
612
613 getValue : function (pos, foundry, layer) {
614 return this._info[pos][foundry + '/' + layer]
615 },
616
617 getLayerPerFoundry : function (foundry) {
618 return this._foundry[foundry]
619 },
620
621 getFoundryPerLayer : function (layer) {
622 return this._layer[layer];
623 },
624
625 // Parse the snippet
626 _parse : function (children) {
627
628 // Get all children
629 for (var i in children) {
630 var c = children[i];
631
632 // Create object on position unless it exists
633 if (this._info[this._pos] === undefined)
634 this._info[this._pos] = {};
635
636 // Store at position in foundry/layer as array
637 var found = this._info[this._pos];
638
639 // Element with title
640 if (c.nodeType === 1) {
641 if (c.getAttribute("title") &&
642 KorAP._TermRE.exec(c.getAttribute("title"))) {
643
644 // Fill position with info
645 var foundry, layer, value;
646 if (RegExp.$2) {
647 foundry = RegExp.$1;
648 layer = RegExp.$2;
649 }
650 else {
651 foundry = "base";
652 layer = RegExp.$1
653 };
654
655 value = RegExp.$3;
656
657 if (found[foundry + "/" + layer] === undefined)
658 found[foundry + "/" + layer] = [];
659
660 // Push value to foundry/layer at correct position
661 found[foundry + "/" + layer].push(RegExp.$3);
662
663 // Set foundry
Nils Diewald8bc7e412015-03-19 22:08:27 +0000664 if (this._foundry[foundry] === undefined)
Nils Diewalde8518f82015-03-18 22:41:49 +0000665 this._foundry[foundry] = {};
666 this._foundry[foundry][layer] = 1;
667
668 // Set layer
Nils Diewald8bc7e412015-03-19 22:08:27 +0000669 if (this._layer[layer] === undefined)
Nils Diewalde8518f82015-03-18 22:41:49 +0000670 this._layer[layer] = {};
671 this._layer[layer][foundry] = 1;
672 };
673
674 // depth search
675 if (c.hasChildNodes())
676 this._parse(c.childNodes);
677 }
678
Nils Diewald8bc7e412015-03-19 22:08:27 +0000679 // Leaf node
680 // store string on position and go to next string
Nils Diewalde8518f82015-03-18 22:41:49 +0000681 else if (c.nodeType === 3) {
682 if (c.nodeValue.match(/[a-z0-9]/i))
683 this._token[this._pos++] = c.nodeValue;
684 };
685 };
686
687 delete this._info[this._pos];
688 },
Nils Diewald4f6521a2015-03-20 21:30:13 +0000689
690
691 /**
692 * Get HTML table view of annotations.
693 */
Nils Diewalde8518f82015-03-18 22:41:49 +0000694 element : function () {
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000695 if (this._element !== undefined)
696 return this._element;
697
Nils Diewalde8518f82015-03-18 22:41:49 +0000698 // First the legend table
Nils Diewald8bc7e412015-03-19 22:08:27 +0000699 var d = document;
700 var table = d.createElement('table');
Nils Diewald8bc7e412015-03-19 22:08:27 +0000701
Nils Diewald4f6521a2015-03-20 21:30:13 +0000702 // Single row in head
703 var tr = table.appendChild(d.createElement('thead'))
704 .appendChild(d.createElement('tr'));
705
706 // Add cell to row
Nils Diewald8bc7e412015-03-19 22:08:27 +0000707 var addCell = function (type, name) {
708 var c = this.appendChild(d.createElement(type))
709 if (name === undefined)
710 return c;
711
712 if (name instanceof Array) {
713 for (var n = 0; n < name.length; n++) {
714 c.appendChild(d.createTextNode(name[n]));
715 if (n !== name.length - 1) {
716 c.appendChild(d.createElement('br'));
717 };
718 };
719 }
720 else {
721 c.appendChild(d.createTextNode(name));
722 };
723 };
724
725 tr.addCell = addCell;
726
727 // Add header information
728 tr.addCell('th', 'Foundry');
729 tr.addCell('th', 'Layer');
730
731 // Add tokens
732 for (var i in this._token) {
733 tr.addCell('th', this.getToken(i));
734 };
735
736 var tbody = table.appendChild(
737 d.createElement('tbody')
738 );
739
740 var foundryList = Object.keys(this._foundry).sort();
741
742 for (var f = 0; f < foundryList.length; f++) {
743 var foundry = foundryList[f];
744 var layerList =
745 Object.keys(this._foundry[foundry]).sort();
746
747 for (var l = 0; l < layerList.length; l++) {
748 var layer = layerList[l];
749 tr = tbody.appendChild(
750 d.createElement('tr')
751 );
752 tr.setAttribute('tabindex', 0);
753 tr.addCell = addCell;
754
755 tr.addCell('th', foundry);
756 tr.addCell('th', layer);
757
758 for (var v = 0; v < this.length(); v++) {
759 tr.addCell(
760 'td',
761 this.getValue(v, foundry, layer)
762 );
763 };
764 };
765 };
766
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000767 return this._element = table;
Nils Diewalde8518f82015-03-18 22:41:49 +0000768 }
769 };
770
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000771
Nils Diewald4f6521a2015-03-20 21:30:13 +0000772 /**
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000773 * Visualize span annotations as a tree using Dagre.
Nils Diewald4f6521a2015-03-20 21:30:13 +0000774 */
Nils Diewald4f6521a2015-03-20 21:30:13 +0000775 KorAP.MatchTree = {
776
777 create : function (snippet) {
778 return Object.create(KorAP.MatchTree)._init(snippet);
779 },
780
781 nodes : function () {
782 return this._next;
783 },
784
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000785 _addNode : function (id, obj) {
786 obj["width"] = 55;
787 obj["height"] = 20;
788 this._graph.setNode(id, obj)
789 },
790
791 _addEdge : function (src, target) {
792 this._graph.setEdge(src, target);
793 },
794
Nils Diewald4f6521a2015-03-20 21:30:13 +0000795 _init : function (snippet) {
796 this._next = new Number(0);
797
798 // Create html for traversal
799 var html = document.createElement("div");
800 html.innerHTML = snippet;
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000801 var g = new dagre.graphlib.Graph({
802 "directed" : true
803 });
804 g.setGraph({
805 "nodesep" : 35,
806 "ranksep" : 15,
807 "marginx" : 40,
808 "marginy" : 10
809 });
810 g.setDefaultEdgeLabel({});
811
812 this._graph = g;
Nils Diewald4f6521a2015-03-20 21:30:13 +0000813
814 // This is a new root
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000815 this._addNode(
Nils Diewald4f6521a2015-03-20 21:30:13 +0000816 this._next++,
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000817 { "class" : "root" }
Nils Diewald4f6521a2015-03-20 21:30:13 +0000818 );
819
820 // Parse nodes from root
821 this._parse(0, html.childNodes);
822
823 // Root node has only one child - remove
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000824 if (g.outEdges(0).length === 1)
825 g.removeNode(0);
Nils Diewald4f6521a2015-03-20 21:30:13 +0000826
827 html = undefined;
828 return this;
829 },
830
831 // Remove foundry and layer for labels
832 _clean : function (title) {
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000833 return title.replace(KorAP._TermRE, "$3");
Nils Diewald4f6521a2015-03-20 21:30:13 +0000834 },
835
836 // Parse the snippet
837 _parse : function (parent, children) {
838 for (var i in children) {
839 var c = children[i];
840
841 // Element node
842 if (c.nodeType == 1) {
843
844 // Get title from html
845 if (c.getAttribute("title")) {
846 var title = this._clean(c.getAttribute("title"));
847
848 // Add child node
849 var id = this._next++;
850
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000851 this._addNode(id, {
852 "class" : "middle",
Nils Diewald4f6521a2015-03-20 21:30:13 +0000853 "label" : title
854 });
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000855 this._addEdge(parent, id);
Nils Diewald4f6521a2015-03-20 21:30:13 +0000856
857 // Check for next level
858 if (c.hasChildNodes())
859 this._parse(id, c.childNodes);
860 }
861
862 // Step further
863 else if (c.hasChildNodes())
864 this._parse(parent, c.childNodes);
865 }
866
867 // Text node
868 else if (c.nodeType == 3)
869
870 if (c.nodeValue.match(/[-a-z0-9]/i)) {
871
872 // Add child node
873 var id = this._next++;
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000874 this._addNode(id, {
875 "class" : "leaf",
Nils Diewald4f6521a2015-03-20 21:30:13 +0000876 "label" : c.nodeValue
877 });
878
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000879 this._addEdge(parent, id);
Nils Diewald4f6521a2015-03-20 21:30:13 +0000880 };
881 };
882 return this;
883 },
884
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000885 /**
886 * Center the viewport of the canvas
887 */
888 center : function () {
889 if (this._element === undefined)
890 return;
891
892 var treeDiv = this._element.parentNode;
893
894 var cWidth = parseFloat(window.getComputedStyle(this._element).width);
895 var treeWidth = parseFloat(window.getComputedStyle(treeDiv).width);
896 // Reposition:
897 if (cWidth > treeWidth) {
898 var scrollValue = (cWidth - treeWidth) / 2;
899 treeDiv.scrollLeft = scrollValue;
900 };
901 },
902
903 // Get element
Nils Diewald4f6521a2015-03-20 21:30:13 +0000904 element : function () {
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000905 if (this._element !== undefined)
906 return this._element;
Nils Diewald4f6521a2015-03-20 21:30:13 +0000907
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000908 var g = this._graph;
Nils Diewald4f6521a2015-03-20 21:30:13 +0000909
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000910 dagre.layout(g);
911
912 var canvas = document.createElementNS(svgXmlns, 'svg');
913 this._element = canvas;
914
915 canvas.setAttribute('height', g.graph().height);
916 canvas.setAttribute('width', g.graph().width);
917
918 // Create edges
919 g.edges().forEach(
920 function (e) {
921 var src = g.node(e.v);
922 var target = g.node(e.w);
923 var p = document.createElementNS(svgXmlns, 'path');
924 p.setAttributeNS(null, "d", _line(src, target));
925 p.classList.add('edge');
926 canvas.appendChild(p);
927 });
928
929 // Create nodes
930 g.nodes().forEach(
931 function (v) {
932 v = g.node(v);
933 var group = document.createElementNS(svgXmlns, 'g');
934 group.classList.add(v.class);
935
936 // Add node box
937 var rect = group.appendChild(document.createElementNS(svgXmlns, 'rect'));
938 rect.setAttributeNS(null, 'x', v.x - v.width / 2);
939 rect.setAttributeNS(null, 'y', v.y - v.height / 2);
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000940 rect.setAttributeNS(null, 'rx', 5);
941 rect.setAttributeNS(null, 'ry', 5);
Nils Diewald58141332015-04-07 16:18:45 +0000942 rect.setAttributeNS(null, 'width', v.width);
943 rect.setAttributeNS(null, 'height', v.height);
944
945 if (v.class === 'root' && v.label === undefined) {
946 rect.setAttributeNS(null, 'width', v.height);
947 rect.setAttributeNS(null, 'x', v.x - v.height / 2);
948 rect.setAttributeNS(null, 'class', 'empty');
949 };
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000950
951 // Add label
Nils Diewald58141332015-04-07 16:18:45 +0000952 if (v.label !== undefined) {
953 var text = group.appendChild(document.createElementNS(svgXmlns, 'text'));
954 text.setAttributeNS(null, 'x', v.x - v.width / 2);
955 text.setAttributeNS(null, 'y', v.y - v.height / 2);
956 text.setAttributeNS(
957 null,
958 'transform',
959 'translate(' + v.width/2 + ',' + ((v.height / 2) + 5) + ')'
960 );
961
962 var tspan = document.createElementNS(svgXmlns, 'tspan');
963 tspan.appendChild(document.createTextNode(v.label));
964 text.appendChild(tspan);
965 };
966
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000967 canvas.appendChild(group);
968 }
969 );
970
Nils Diewald4f6521a2015-03-20 21:30:13 +0000971 return this._element;
972 }
973 };
974
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000975 /**
976 * Menu item for tree view choice.
977 */
978 KorAP.MatchTreeItem = {
979 create : function (params) {
980 return Object.create(KorAP.MenuItem)
981 .upgradeTo(KorAP.MatchTreeItem)._init(params);
982 },
983 content : function (content) {
984 if (arguments.length === 1) {
985 this._content = content;
986 };
987 return this._content;
988 },
989
990 // The foundry attribute
991 foundry : function () {
992 return this._foundry;
993 },
994
995 // The layer attribute
996 layer : function () {
997 return this._layer;
998 },
999
1000 // enter or click
1001 onclick : function (e) {
1002 var menu = this.menu();
1003 menu.hide();
1004 e.halt();
Nils Diewalda297f062015-04-02 00:23:46 +00001005 if (menu.info() !== undefined)
1006 menu.info().addTree(this._foundry, this._layer);
Nils Diewald6e43ffd2015-03-25 18:55:39 +00001007 },
1008
1009 _init : function (params) {
1010 if (params[0] === undefined)
1011 throw new Error("Missing parameters");
1012
1013 this._name = params[0];
1014 this._foundry = params[1];
1015 this._layer = params[2];
1016 this._content = document.createTextNode(this._name);
1017 this._lcField = ' ' + this.content().textContent.toLowerCase();
1018 return this;
1019 }
1020 };
1021
1022
1023 /**
1024 * Menu to choose from for tree views.
1025 */
1026 KorAP.MatchTreeMenu = {
1027 create : function (info, params) {
1028 var obj = Object.create(KorAP.Menu)
1029 .upgradeTo(KorAP.MatchTreeMenu)
1030 ._init(KorAP.MatchTreeItem, undefined, params);
1031 obj.limit(6);
Nils Diewalda297f062015-04-02 00:23:46 +00001032
Nils Diewald6e43ffd2015-03-25 18:55:39 +00001033 obj._info = info;
1034
1035 // This is only domspecific
1036 obj.element().addEventListener('blur', function (e) {
1037 this.menu.hide();
1038 });
1039
1040 return obj;
1041 },
1042 info :function () {
1043 return this._info;
1044 }
1045 };
1046
1047
1048 // Create path for node connections
1049 function _line (src, target) {
1050 var x1 = src.x,
1051 y1 = src.y,
1052 x2 = target.x,
1053 y2 = target.y - target.height / 2;
1054
1055 // c 0,0 -10,0
1056 return 'M ' + x1 + ',' + y1 + ' ' +
1057 'C ' + x1 + ',' + y1 + ' ' +
1058 x2 + ',' + (y2 - (y2 - y1) / 2) + ' ' +
1059 x2 + ',' + y2;
1060 };
1061
Nils Diewalde8518f82015-03-18 22:41:49 +00001062}(this.KorAP));