Merge morphTable and morphTree to matchInfo module
diff --git a/public/js/runner/matchInfo.html b/public/js/runner/matchInfo.html
new file mode 100644
index 0000000..3d274fc
--- /dev/null
+++ b/public/js/runner/matchInfo.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Spec Runner for Morph Table View</title>
+ <link rel="shortcut icon" type="image/png" href="../lib/jasmine-2.1.1/jasmine_favicon.png">
+ <link rel="stylesheet" href="../lib/jasmine-2.1.1/jasmine.css">
+ <script src="../lib/jasmine-2.1.1/jasmine.js"></script>
+ <script src="../lib/jasmine-2.1.1/jasmine-html.js"></script>
+ <script src="../lib/jasmine-2.1.1/boot.js"></script>
+ <script src="../src/matchInfo.js"></script>
+ <script src="../spec/matchInfoSpec.js"></script>
+</head>
+<body>
+</body>
+</html>
diff --git a/public/js/spec/matchInfoSpec.js b/public/js/spec/matchInfoSpec.js
new file mode 100644
index 0000000..dc0d4a1
--- /dev/null
+++ b/public/js/spec/matchInfoSpec.js
@@ -0,0 +1,178 @@
+describe('KorAP.InfoLayer', function () {
+ it('should be initializable', function () {
+ expect(
+ function() { KorAP.InfoLayer.create() }
+ ).toThrow(new Error("Missing parameters"));
+
+ expect(
+ function() { KorAP.InfoLayer.create("base") }
+ ).toThrow(new Error("Missing parameters"));
+
+ var layer = KorAP.InfoLayer.create("base", "s");
+ expect(layer).toBeTruthy();
+ expect(layer.foundry).toEqual("base");
+ expect(layer.layer).toEqual("s");
+ expect(layer.type).toEqual("tokens");
+
+ layer = KorAP.InfoLayer.create("cnx", "syn", "spans");
+ expect(layer).toBeTruthy();
+ expect(layer.foundry).toEqual("cnx");
+ expect(layer.layer).toEqual("syn");
+ expect(layer.type).toEqual("spans");
+ });
+});
+
+describe('KorAP.Match', function () {
+ var match = {
+ 'corpusID' : 'WPD',
+ 'docID' : 'UUU',
+ 'textID' : '01912',
+ 'pos' : 'p121-122'
+ };
+
+ it('should be initializable', function () {
+ var mInfo = KorAP.Match.create(match);
+ expect(mInfo.corpusID).toEqual("WPD");
+ });
+});
+
+describe('KorAP.MatchInfo', function () {
+ var available = [
+ 'base/s=spans',
+ 'corenlp/c=spans',
+ 'corenlp/ne=tokens',
+ 'corenlp/p=tokens',
+ 'corenlp/s=spans',
+ 'glemm/l=tokens',
+ 'mate/l=tokens',
+ 'mate/m=tokens',
+ 'mate/p=tokens',
+ 'opennlp/p=tokens',
+ 'opennlp/s=spans',
+ 'tt/l=tokens',
+ 'tt/p=tokens',
+ 'tt/s=spans'
+ ];
+
+ var match = {
+ 'corpusID' : 'WPD',
+ 'docID' : 'UUU',
+ 'textID' : '01912',
+ 'pos' : 'p121-122'
+ };
+
+ var snippet = "<span title=\"cnx/l:meist\">" +
+ " <span title=\"cnx/p:ADV\">" +
+ " <span title=\"cnx/syn:@PREMOD\">" +
+ " <span title=\"mate/l:meist\">" +
+ " <span title=\"mate/p:ADV\">" +
+ " <span title=\"opennlp/p:ADV\">meist</span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ "</span>" +
+ "<span title=\"cnx/l:deutlich\">" +
+ " <span title=\"cnx/p:A\">" +
+ " <span title=\"cnx/syn:@PREMOD\">" +
+ " <span title=\"mate/l:deutlich\">" +
+ " <span title=\"mate/m:degree:pos\">" +
+ " <span title=\"mate/p:ADJD\">" +
+ " <span title=\"opennlp/p:ADJD\">deutlich</span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ "</span>" +
+ "<span title=\"cnx/l:fähig\">" +
+ " <span title=\"cnx/l:leistung\">" +
+ " <span title=\"cnx/p:A\">" +
+ " <span title=\"cnx/syn:@NH\">" +
+ " <span title=\"mate/l:leistungsfähig\">" +
+ " <span title=\"mate/m:degree:comp\">" +
+ " <span title=\"mate/p:ADJD\">" +
+ " <span title=\"opennlp/p:ADJD\">leistungsfähiger</span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ "</span>";
+
+
+ it('should be initializable', function () {
+ expect(function() {
+ KorAP.MatchInfo.create()
+ }).toThrow(new Error('Missing parameters'));
+
+ expect(function() {
+ KorAP.MatchInfo.create(available)
+ }).toThrow(new Error('Missing parameters'));
+
+ expect(KorAP.MatchInfo.create(match, available)).toBeTruthy();
+
+ // /corpus/WPD/UUU.01912/p121-122/matchInfo?spans=false&foundry=*
+ var info = KorAP.MatchInfo.create(match, available);
+
+ // Spans:
+ var spans = info.getSpans();
+ expect(spans[0].foundry).toEqual("base");
+ expect(spans[0].layer).toEqual("s");
+
+ expect(spans[1].foundry).toEqual("corenlp");
+ expect(spans[1].layer).toEqual("c");
+
+ expect(spans[2].foundry).toEqual("corenlp");
+ expect(spans[2].layer).toEqual("s");
+
+ expect(spans[spans.length-1].foundry).toEqual("tt");
+ expect(spans[spans.length-1].layer).toEqual("s");
+
+ // Tokens:
+ var tokens = info.getTokens();
+ expect(tokens[0].foundry).toEqual("corenlp");
+ expect(tokens[0].layer).toEqual("ne");
+
+ expect(tokens[1].foundry).toEqual("corenlp");
+ expect(tokens[1].layer).toEqual("p");
+
+ expect(tokens[tokens.length-1].foundry).toEqual("tt");
+ expect(tokens[tokens.length-1].layer).toEqual("p");
+ });
+
+
+ it('should parse into a table', function () {
+ var info = KorAP.MatchInfo.create(match, available);
+
+ expect(info.getTable('base/s')).not.toBeTruthy();
+
+ // Override getMatchInfo API call
+ KorAP.API.getMatchInfo = function() {
+ return { "snippet": snippet };
+ };
+
+ var table = info.getTable();
+ expect(table).toBeTruthy();
+
+ expect(table.length()).toBe(3);
+
+ expect(table.getToken(0)).toBe("meist");
+ expect(table.getToken(1)).toBe("deutlich");
+ expect(table.getToken(2)).toBe("leistungsfähiger");
+
+ expect(table.getValue(0, "cnx", "p")[0]).toBe("ADV");
+ expect(table.getValue(0, "cnx", "syn")[0]).toBe("@PREMOD");
+
+ expect(table.getValue(2, "cnx", "l")[0]).toBe("fähig");
+ expect(table.getValue(2, "cnx", "l")[1]).toBe("leistung");
+ });
+
+});
+
+// table = view.toTable();
+// table.sortBy('');
+// table.element();
+// tree = view.toTree();
+// tree.element();
diff --git a/public/js/spec/menuSpec.js b/public/js/spec/menuSpec.js
index a818574..08fea76 100644
--- a/public/js/spec/menuSpec.js
+++ b/public/js/spec/menuSpec.js
@@ -328,6 +328,18 @@
['Autor', 'author']
];
+ var demolonglist = [
+ ['Titel', 'title'],
+ ['Untertitel', 'subTitle'],
+ ['Veröffentlichungsdatum', 'pubDate'],
+ ['Länge', 'length'],
+ ['Autor', 'author'],
+ ['Genre', 'genre'],
+ ['corpusID', 'corpusID'],
+ ['docID', 'docID'],
+ ['textID', 'textID'],
+ ];
+
it('should be initializable', function () {
var list = [
["Constituency"],
@@ -689,7 +701,7 @@
// Change show
expect(menu.prefix("e").show()).toBe(true);
-
+ expect(menu._prefix.active()).toBe(false);
expect(menu.shownItem(0).name()).toEqual("Constituency");
expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Constitu<mark>e</mark>ncy</strong><span><mark>E</mark>xampl<mark>e</mark> 1</span>");
expect(menu.shownItem(0).active()).toBe(true);
@@ -935,6 +947,7 @@
expect(menu.shownItem(2)).toBe(undefined);
});
+
it('should be navigatable with a prefix (2)', function () {
var menu = KorAP.HintMenu.create("cnx/", demolist);
menu.limit(3);
@@ -943,7 +956,6 @@
menu.prefix('el');
expect(menu.show()).toBe(true);
-
expect(menu.prefix()).toEqual("el");
expect(menu._prefix.active()).toEqual(false);
expect(menu.shownItem(0).name()).toEqual("Titel");
@@ -979,6 +991,52 @@
expect(menu.shownItem(2)).toBe(undefined);
});
- xit('should be page downable');
+ it('should be navigatable with a prefix (3)', function () {
+ var menu = KorAP.HintMenu.create("cnx/", demolist);
+ menu.limit(3);
+ expect(menu.show()).toBe(true);
+ expect(menu.prefix()).toEqual("");
+ menu.prefix('el');
+ expect(menu.show()).toBe(true);
+
+ expect(menu.prefix()).toEqual("el");
+ expect(menu._prefix.active()).toEqual(false);
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Tit<mark>el</mark></strong>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Untertit<mark>el</mark></strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Backward
+ menu.prev();
+ expect(menu._prefix.active()).toEqual(true);
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Tit<mark>el</mark></strong>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Untertit<mark>el</mark></strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+
+ // Forward
+ menu.next();
+ expect(menu.prefix()).toEqual("el");
+ expect(menu._prefix.active()).toEqual(false);
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Tit<mark>el</mark></strong>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Untertit<mark>el</mark></strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ });
+
+ it('should be page downable', function () {
+
+ });
xit('should be page upable');
});
diff --git a/public/js/src/matchInfo.js b/public/js/src/matchInfo.js
new file mode 100644
index 0000000..515a78f
--- /dev/null
+++ b/public/js/src/matchInfo.js
@@ -0,0 +1,319 @@
+/**
+ * Make annotations visible.
+ *
+ * @author Nils Diewald
+ */
+/*
+ - Scroll with a static left legend.
+ - Highlight (at least mark as bold) the match
+ - Scroll to match vertically per default
+ */
+var KorAP = KorAP || {};
+
+(function (KorAP) {
+ "use strict";
+
+ KorAP._AvailableRE = new RegExp("^([^\/]+?)\/([^=]+?)(?:=(spans|rels|tokens))?$");
+ KorAP._TermRE = new RegExp("^([^\/]+?)(?:\/([^:]+?))?:(.+?)$");
+ KorAP._matchTerms = ["corpusID", "docID", "textID"];
+
+ // API requests
+ KorAP.API = KorAP.API || {};
+ KorAP.API.getMatchInfo = KorAP.API.getMatchInfo || function () { return {} };
+
+ KorAP.MatchInfo = {
+
+ /**
+ * Create a new annotation object.
+ * Expects an array of available foundry/layer=type terms.
+ * Supported types are 'spans', 'tokens' and 'rels'.
+ */
+ create : function (match, available) {
+ if (arguments.length < 2)
+ throw new Error("Missing parameters");
+
+ return Object.create(KorAP.MatchInfo)._init(match, available);
+ },
+
+ _init : function (match, available) {
+ this._match = KorAP.Match.create(match);
+ this._available = {
+ tokens : [],
+ spans : [],
+ rels : []
+ };
+ for (var i = 0; i < available.length; i++) {
+ var term = available[i];
+ // Create info layer objects
+ try {
+ var layer = KorAP.InfoLayer.create(term);
+ this._available[layer.type].push(layer);
+ }
+ catch (e) {
+ continue;
+ };
+ };
+ return this;
+ },
+
+
+ /**
+ * Return a list of parseable tree annotations.
+ */
+ getSpans : function () {
+ return this._available.spans;
+ },
+
+
+ /**
+ * Return a list of parseable token annotations.
+ */
+ getTokens : function () {
+ return this._available.tokens;
+ },
+
+
+ /**
+ * Return a list of parseable relation annotations.
+ */
+ getRels : function () {
+ return this._available.rels;
+ },
+
+
+ getTable : function (tokens) {
+ var focus = [];
+
+ // Get all tokens
+ if (tokens === undefined) {
+ focus = this.getTokens();
+ }
+
+ // Get only some tokens
+ else {
+
+ // Push newly to focus array
+ for (var i = 0; i < tokens.length; i++) {
+ var term = tokens[i];
+ try {
+ // Create info layer objects
+ var layer = KorAP.InfoLayer.create(term);
+ layer.type = "tokens";
+ focus.push(layer);
+ }
+ catch (e) {
+ continue;
+ };
+ };
+ };
+
+ // No tokens chosen
+ if (focus.length == 0)
+ return;
+
+ // Get info (may be cached)
+ var matchResponse = KorAP.API.getMatchInfo(
+ this._match,
+ { 'spans' : true, 'layer' : focus }
+ );
+
+ // Get snippet from match info
+ if (matchResponse["snippet"] !== undefined) {
+ this._table = KorAP.InfoTable.create(matchResponse["snippet"]);
+ return this._table;
+ };
+
+ return null;
+ }
+
+ /*
+ // Parse snippet for table visualization
+ getTree : function (foundry, layer) {
+ },
+ */
+ };
+
+ KorAP.Match = {
+ create : function (match) {
+ return Object.create(KorAP.Match)._init(match);
+ },
+ _init : function (match) {
+ for (var i in KorAP._matchTerms) {
+ var term = KorAP._matchTerms[i];
+ if (match[term] !== undefined) {
+ this[term] = match[term];
+ }
+ else {
+ this[term] = undefined;
+ }
+ };
+ return this;
+ },
+ };
+
+ /**
+ *
+ * Alternatively pass a string as <tt>base/s=span</tt>
+ *
+ * @param foundry
+ */
+ KorAP.InfoLayer = {
+ create : function (foundry, layer, type) {
+ return Object.create(KorAP.InfoLayer)._init(foundry, layer, type);
+ },
+ _init : function (foundry, layer, type) {
+ if (foundry === undefined)
+ throw new Error("Missing parameters");
+
+ if (layer === undefined) {
+ if (KorAP._AvailableRE.exec(foundry)) {
+ this.foundry = RegExp.$1;
+ this.layer = RegExp.$2;
+ this.type = RegExp.$3;
+ }
+ else {
+ throw new Error("Missing parameters");
+ };
+ }
+ else {
+ this.foundry = foundry;
+ this.layer = layer;
+ this.type = type;
+ };
+
+ if (this.type === undefined)
+ this.type = 'tokens';
+
+ return this;
+ }
+ };
+
+
+ KorAP.InfoTable = {
+ create : function (snippet) {
+ return Object.create(KorAP.InfoTable)._init(snippet);
+ },
+ _init : function (snippet) {
+ // Create html for traversal
+ var html = document.createElement("div");
+ html.innerHTML = snippet;
+
+ this._pos = 0;
+ this._token = [];
+ this._info = [];
+ this._foundry = [];
+ this._layer = [];
+
+ // Parse the snippet
+ this._parse(html.childNodes);
+
+ this._layer = undefined;
+ this._foundry = undefined;
+
+ html.innerHTML = '';
+ return this;
+ },
+
+ length : function () {
+ return this._pos;
+ },
+
+ getToken : function (pos) {
+ if (pos === undefined)
+ return this._token;
+ return this._token[pos];
+ },
+
+ getValue : function (pos, foundry, layer) {
+ return this._info[pos][foundry + '/' + layer]
+ },
+
+ getLayerPerFoundry : function (foundry) {
+ return this._foundry[foundry]
+ },
+
+ getFoundryPerLayer : function (layer) {
+ return this._layer[layer];
+ },
+
+ // Parse the snippet
+ _parse : function (children) {
+
+ // Get all children
+ for (var i in children) {
+ var c = children[i];
+
+ // Create object on position unless it exists
+ if (this._info[this._pos] === undefined)
+ this._info[this._pos] = {};
+
+ // Store at position in foundry/layer as array
+ var found = this._info[this._pos];
+
+ // Element with title
+ if (c.nodeType === 1) {
+ if (c.getAttribute("title") &&
+ KorAP._TermRE.exec(c.getAttribute("title"))) {
+
+ // Fill position with info
+ var foundry, layer, value;
+ if (RegExp.$2) {
+ foundry = RegExp.$1;
+ layer = RegExp.$2;
+ }
+ else {
+ foundry = "base";
+ layer = RegExp.$1
+ };
+
+ value = RegExp.$3;
+
+ if (found[foundry + "/" + layer] === undefined)
+ found[foundry + "/" + layer] = [];
+
+ // Push value to foundry/layer at correct position
+ found[foundry + "/" + layer].push(RegExp.$3);
+
+ // Set foundry
+ if (!this._foundry[foundry])
+ this._foundry[foundry] = {};
+ this._foundry[foundry][layer] = 1;
+
+ // Set layer
+ if (!this._layer[layer])
+ this._layer[layer] = {};
+ this._layer[layer][foundry] = 1;
+ };
+
+ // depth search
+ if (c.hasChildNodes())
+ this._parse(c.childNodes);
+ }
+
+ // Leaf node - store string on position and go to next string
+ else if (c.nodeType === 3) {
+ if (c.nodeValue.match(/[a-z0-9]/i))
+ this._token[this._pos++] = c.nodeValue;
+ };
+ };
+
+ delete this._info[this._pos];
+ },
+ element : function () {
+ var ce = document.createElement;
+ // First the legend table
+ /*
+ var table = ce('table');
+ var row = ce('tr');
+ table.appendChild(tr);
+ */
+ }
+ };
+
+
+ /*
+ KorAP.InfoFoundryLayer = {};
+ KorAP.InfoTree = {};
+ KorAP.InfoTable = {};
+ */
+}(this.KorAP));
diff --git a/public/js/src/menu.js b/public/js/src/menu.js
index 3d0017d..8ccf740 100644
--- a/public/js/src/menu.js
+++ b/public/js/src/menu.js
@@ -83,6 +83,9 @@
this.next();
break;
case 39: // 'Right'
+ if (this._prefix.active())
+ break;
+
var item = this.liveItem(this._position);
if (item["further"] !== undefined) {
item["further"].bind(item).apply();
@@ -223,6 +226,7 @@
* @param {string} Prefix for filtering the list
*/
show : function () {
+
// Initialize the list
if (!this._initList())
return false;
@@ -233,6 +237,7 @@
// Set the first element to active
// Todo: Or the last element chosen
this.liveItem(0).active(true);
+ this._prefix.active(false);
this._active = this._list[0];
this._position = 0;
@@ -466,8 +471,13 @@
var newItem;
// Set new live item
- var oldItem = this.liveItem(this._position++);
- oldItem.active(false);
+ if (!this._prefix.active()) {
+ var oldItem = this.liveItem(this._position);
+ oldItem.active(false);
+ };
+
+ this._position++;
+
newItem = this.liveItem(this._position);
// The next element is undefined - roll to top or to prefix
@@ -501,6 +511,42 @@
newItem.active(true);
},
+ /*
+ * Page down to the first item on the next page
+ */
+ /*
+ nextPage : function () {
+
+ // Prefix is active
+ if (this._prefix.active()) {
+ this._prefix.active(false);
+ }
+
+ // Last item is chosen
+ else if (this._position >= this.limit() + this._offset) {
+
+ this._position = this.limit() + this._offset - 1;
+ newItem = this.liveItem(this._position);
+ var oldItem = this.liveItem(this._position--);
+ oldItem.active(false);
+ }
+
+ // Last item of page is chosen
+ else if (0) {
+
+ // Jump to last item
+ else {
+ var oldItem = this.liveItem(this._position);
+ oldItem.active(false);
+
+ this._position = this.limit() + this._offset - 1;
+ newItem = this.liveItem(this._position);
+ };
+
+ newItem.active(true);
+ },
+ */
+
/*
* Make the previous item in the menu active
@@ -508,8 +554,10 @@
prev : function () {
// No active element set
- if (this._position == -1)
+ if (this._position === -1) {
return;
+ // TODO: Choose last item
+ };
var newItem;
@@ -534,7 +582,6 @@
this._position = this.liveLength() - 1;
if (prefix.isSet() && !prefix.active()) {
-
this._position++;
prefix.active(true);
return;
@@ -792,6 +839,10 @@
// Add prefix span
this._element = document.createElement('span');
this._element.classList.add('pref');
+ // Connect action
+ if (this.onclick !== undefined)
+ this._element["onclick"] = this.onclick.bind(this);
+
return this;
},
_update : function () {