Support for integer values in Kalamar
Change-Id: I13994db8006fbfc213621ec46095a53600f22a0d
diff --git a/dev/js/spec/corpusByMatchSpec.js b/dev/js/spec/corpusByMatchSpec.js
index cdf941f..21e6560 100644
--- a/dev/js/spec/corpusByMatchSpec.js
+++ b/dev/js/spec/corpusByMatchSpec.js
@@ -100,9 +100,11 @@
cbm.add("pubDate", "2018-11-20", "type:date");
expect(cbm.toQuery()).toEqual('author = "Peter" & pubDate in 2018-11-20');
+ cbm.add("KED.nToks", 200, "type:integer");
+
cbm.toVcBuilder();
- expect(KorAP.vc.toQuery()).toEqual('(title = "Hello World!" | foo = "bar") & author = "Peter" & pubDate in 2018-11-20');
+ expect(KorAP.vc.toQuery()).toEqual('(title = "Hello World!" | foo = "bar") & author = "Peter" & pubDate in 2018-11-20 & KED.nToks = 200');
})
});
});
diff --git a/dev/js/spec/vcSpec.js b/dev/js/spec/vcSpec.js
index bfdf9fa..656a827 100644
--- a/dev/js/spec/vcSpec.js
+++ b/dev/js/spec/vcSpec.js
@@ -12,6 +12,7 @@
'vc/operators',
'vc/rewrite',
'vc/stringval',
+ 'vc/intval',
'vc/fragment'
], function (vcClass,
docClass,
@@ -23,6 +24,7 @@
operatorsClass,
rewriteClass,
stringValClass,
+ intValClass,
fragmentClass) {
KorAP._vcKeyMenu = undefined;
@@ -132,6 +134,14 @@
"@type" : "koral:doc"
});
+ var integerFactory = buildFactory(docClass, {
+ "key" : "KED.nToks",
+ "type" : "type:integer",
+ "match" : "match:eq",
+ "value" : "200",
+ "@type" : "koral:doc"
+ });
+
// Create example factories
var regexFactory = buildFactory(docClass, {
"key" : "title",
@@ -281,6 +291,15 @@
expect(doc.matchop()).toEqual('eq');
});
+ it('should deserialize JSON-LD integer', function () {
+ doc = integerFactory.create({});
+
+ expect(doc.matchop()).toEqual('eq');
+ expect(doc.key()).toEqual("KED.nToks");
+ expect(doc.type()).toEqual("integer");
+ expect(doc.value()).toEqual("200");
+ });
+
it('should be serializale to JSON', function () {
// Empty doc
@@ -381,6 +400,33 @@
value : "2014"
});
expect(doc.toQuery()).toEqual('pubDate in 2014');
+
+ doc = integerFactory.create();
+ expect(doc.toQuery()).toEqual('KED.nToks = 200');
+
+ doc = integerFactory.create({
+ value : "100"
+ });
+ expect(doc.toQuery()).toEqual('KED.nToks = 100');
+
+ doc = integerFactory.create({
+ value : "100",
+ match : "match:geq"
+ });
+ expect(doc.toQuery()).toEqual('KED.nToks >= 100');
+
+ doc = integerFactory.create({
+ value : "100",
+ match : "match:leq"
+ });
+ expect(doc.toQuery()).toEqual('KED.nToks <= 100');
+
+ // Check for numeric values
+ doc = integerFactory.create({
+ value : 100,
+ });
+ expect(doc.toQuery()).toEqual('KED.nToks = 100');
+
});
});
@@ -571,7 +617,15 @@
"match": 'match:ne',
"value": '[a]?bar',
"type": 'type:regex'
+ },
+ {
+ "@type": 'koral:doc',
+ "key": 'KED.nToks',
+ "match": 'match:leq',
+ "value": '300',
+ "type": 'type:integer'
}
+
]
}
]
@@ -579,7 +633,8 @@
expect(docGroup.toQuery()).toEqual(
'author = "Max Birkendale" | ' +
'(pubDate since 2014-05-12 & ' +
- 'pubDate until 2014-12-05 & foo != /[a]?bar/)'
+ 'pubDate until 2014-12-05 & foo != /[a]?bar/ & ' +
+ 'KED.nToks <= 300)'
);
@@ -590,7 +645,8 @@
expect(docGroup.incomplete()).toBeFalsy();
expect(docGroup.toQuery()).toEqual(
'(pubDate since 2014-05-12 & ' +
- 'pubDate until 2014-12-05 & foo != /[a]?bar/)'
+ 'pubDate until 2014-12-05 & foo != /[a]?bar/ & ' +
+ 'KED.nToks <= 300)'
);
});
});
@@ -2820,6 +2876,7 @@
var sv = stringValClass.create('der');
expect(sv.element().nodeName).toBe('DIV');
expect(sv.element().firstChild.nodeName).toBe('INPUT');
+ expect(sv.element().firstChild.getAttribute('type')).toBeNull();
expect(sv.element().firstChild.value).toBe('der');
});
@@ -2862,6 +2919,50 @@
});
+
+ describe('KorAP.VC.intValue', function () {
+ it('should be initializable', function () {
+ var iv = intValClass.create();
+ expect(iv.value()).toBe(0);
+
+ iv = intValClass.create('400');
+ expect(iv.value()).toBe(400);
+
+ iv = intValClass.create(400);
+ expect(iv.value()).toBe(400);
+
+ iv = intValClass.create('Baum');
+ expect(iv.value()).toBe(0);
+ });
+
+ it('should be modifiable', function () {
+ var iv = intValClass.create();
+ expect(iv.value()).toBe(0);
+
+ expect(iv.value('33')).toBe(33);
+ expect(iv.value()).toBe(33);
+ });
+
+ it('should have an element', function () {
+ var iv = intValClass.create(22);
+ expect(iv.element().nodeName).toBe('DIV');
+ expect(iv.element().firstChild.nodeName).toBe('INPUT');
+ expect(iv.element().firstChild.getAttribute('type')).toBe('number');
+ expect(iv.element().firstChild.value).toBe('22');
+ });
+
+ it('should be storable', function () {
+ var iv = intValClass.create();
+ var count = 1;
+ iv.store = function (value) {
+ expect(value).toBe(80);
+ };
+ iv.value('80');
+ iv.element().lastChild.click();
+ });
+ });
+
+
describe('KorAP.VC.Menu', function () {
var vc;
@@ -2889,7 +2990,8 @@
vc = vcClass.create([
['d', 'text'],
['e', 'string'],
- ['f', 'date']
+ ['f', 'date'],
+ ['g', 'integer']
]).fromJson();
expect(vc.builder().firstChild.classList.contains('unspecified')).toBeTruthy();
expect(vc.builder().firstChild.firstChild.tagName).toEqual('SPAN');
@@ -2903,6 +3005,7 @@
expect(list.getElementsByTagName("LI")[1].innerText).toEqual('d');
expect(list.getElementsByTagName("LI")[2].innerText).toEqual('e');
expect(list.getElementsByTagName("LI")[3].innerText).toEqual('f');
+ expect(list.getElementsByTagName("LI")[4].innerText).toEqual('g');
// blur
document.body.click();
});
@@ -3022,6 +3125,33 @@
document.body.click();
});
+ it('should be clickable on operation for integer', function () {
+
+ vc.builder().firstChild.firstChild.click();// Choose "g"
+ vc.builder().firstChild.firstChild.getElementsByTagName("LI")[4].click()
+ // Click on "g" (or unspecified)
+ vc.builder().firstChild.firstChild.click();
+ // Rechoose "g"
+ vc.builder().firstChild.firstChild.getElementsByTagName("LI")[4].click();
+ // Click on matchop
+ vc.builder().firstChild.children[1].click();
+ // Choose "geq"
+ vc.builder().firstChild.children[1].getElementsByTagName('li')[2].click();
+ expect(vc.builder().firstChild.children[1].innerText).toEqual("geq");
+
+ // Click on "e"
+ vc.builder().firstChild.firstChild.click();
+ // Choose "f"
+ vc.builder().firstChild.firstChild.getElementsByTagName("LI")[3].click();
+
+ // The matchoperator should still be "geq" as this is valid for dates as well (now)
+ var fc = vc.builder().firstChild;
+ expect(fc.firstChild.tagName).toEqual('SPAN');
+ expect(fc.firstChild.innerText).toEqual('f');
+ expect(fc.children[1].innerText).toEqual('geq');
+ // blur
+ document.body.click();
+ });
// Check json deserialization
it('should be initializable', function () {
diff --git a/dev/js/src/match/corpusByMatch.js b/dev/js/src/match/corpusByMatch.js
index cc2ae2f..4b8ec73 100644
--- a/dev/js/src/match/corpusByMatch.js
+++ b/dev/js/src/match/corpusByMatch.js
@@ -99,7 +99,7 @@
if (target.tagName === 'DD') {
type = target.getAttribute("data-type");
key = target.previousElementSibling.innerText;
- value = target.innerText;
+ value = (type == "type:integer" ? Number(target.innerText) : target.innerText);
}
// Meta information is in a list
diff --git a/dev/js/src/vc.js b/dev/js/src/vc.js
index 2c068ee..9d91a62 100644
--- a/dev/js/src/vc.js
+++ b/dev/js/src/vc.js
@@ -74,6 +74,7 @@
KorAP._validUnspecMatchRE = new RegExp(
"^(?:eq|ne|contains(?:not)?|excludes)$");
KorAP._validStringMatchRE = new RegExp("^(?:eq|ne)$");
+ KorAP._validIntegerMatchRE = new RegExp("^(?:[gl]?eq|ne)$");
KorAP._validTextMatchRE = KorAP._validUnspecMatchRE;
KorAP._validTextOnlyMatchRE = new RegExp(
"^(?:contains(?:not)?|excludes)$");
@@ -111,6 +112,12 @@
'regex' : menuClass.create([
[ 'eq', null ],
[ 'ne', null ]
+ ]),
+ 'integer' : menuClass.create([
+ [ 'eq', null ],
+ [ 'ne', null ],
+ [ 'geq', null ],
+ [ 'leq', null ]
])
};
diff --git a/dev/js/src/vc/doc.js b/dev/js/src/vc/doc.js
index 37b0e0b..d504642 100644
--- a/dev/js/src/vc/doc.js
+++ b/dev/js/src/vc/doc.js
@@ -7,9 +7,10 @@
'vc/jsonld',
'vc/rewritelist',
'vc/stringval',
+ 'vc/intval',
'vc/docgroupref',
'util'
-], function (jsonldClass, rewriteListClass, stringValClass, docGroupRefClass) {
+], function (jsonldClass, rewriteListClass, stringValClass, intValClass, docGroupRefClass) {
/*
* TODO:
@@ -219,8 +220,14 @@
return;
};
- if (json["value"] === undefined ||
- typeof json["value"] != 'string') {
+ if (json["value"] === undefined
+ ||
+ (typeof json["value"] != 'string'
+
+ && !(json["type"] != undefined &&
+ json["type"] == "type:integer" &&
+ typeof json["value"] == 'number')
+ )) {
KorAP.log(805, "Value is invalid");
return;
};
@@ -305,6 +312,24 @@
t.value(json["value"]);
}
+ // Key is integer
+ else if (json["type"] == "type:integer") {
+ t.type("integer");
+
+ // Check match type
+ if (!KorAP._validIntegerMatchRE.test(t.matchop())) {
+ KorAP.log(802, errstr802);
+
+ // Rewrite method
+ t.matchop('eq');
+ rewrite = 'modification';
+ };
+
+ // Set string value
+ t.value(json["value"]);
+ }
+
+
// Key is a date
else if (json["type"] === "type:date") {
t.type("date");
@@ -450,6 +475,8 @@
||
(t._type === 'text' && KorAP._validTextMatchRE.test(m))
||
+ (t._type === 'integer' && KorAP._validIntegerMatchRE.test(m))
+ ||
(t._type === 'date' && KorAP._validDateMatchRE.test(m))
) {
t._matchop = m;
@@ -557,32 +584,49 @@
dp.input().focus();
}
-
+
else {
- const regex = this.type() === 'regex' ? true : false;
- const str = stringValClass.create(this.value(), regex);
- const strElem = str.element();
- str.store = function (value, regex) {
- that.value(value);
- if (regex === true)
- that.type('regex');
- else
- that.type('string');
+ let vcVal;
+
+ if (this.type() === 'integer') {
+ vcVal = intValClass.create(this.value());
+
+ vcVal.store = function (value) {
+ that.value(value);
+ that.type('integer');
+ that._el.removeChild(
+ this._el
+ );
+ that.update();
+ };
+ }
+
+ else {
+ const regex = this.type() === 'regex' ? true : false;
+ vcVal = stringValClass.create(this.value(), regex);
+
+ vcVal.store = function (value, regex) {
+ that.value(value);
+ if (regex === true)
+ that.type('regex');
+ else
+ that.type('string');
- that._el.removeChild(
- this._el
- );
- that.update();
+ that._el.removeChild(
+ this._el
+ );
+ that.update();
+ };
};
// Insert element
this._el.insertBefore(
- strElem,
+ vcVal.element(),
this._valueE
);
- str.focus();
+ vcVal.focus();
};
},
@@ -666,10 +710,10 @@
string += '!~';
break;
case "geq":
- string += 'since';
+ string += (this.type() == 'date') ? 'since' : '>=';
break;
case "leq":
- string += 'until';
+ string += (this.type() == 'date') ? 'until' : '<=';
break;
default:
string += (this.type() == 'date') ? 'in' : '=';
@@ -681,6 +725,7 @@
// Add value
switch (this.type()) {
case "date":
+ case "integer":
return string + this.value();
case "regex":
return string + '/' + this.value().escapeRegex() + '/';
diff --git a/dev/js/src/vc/fragment.js b/dev/js/src/vc/fragment.js
index 72bd80e..e5b03a0 100644
--- a/dev/js/src/vc/fragment.js
+++ b/dev/js/src/vc/fragment.js
@@ -115,7 +115,7 @@
doc.key(item[0]);
doc.matchop("eq");
doc.value(item[1]);
- doc.type(item[2] === "date" ? "date" : "string");
+ doc.type(item[2] === "date" ? "date" : (item[2] === "integer" ? "integer" : "string"));
return doc;
}
);
diff --git a/dev/js/src/vc/intval.js b/dev/js/src/vc/intval.js
new file mode 100644
index 0000000..55a5829
--- /dev/null
+++ b/dev/js/src/vc/intval.js
@@ -0,0 +1,144 @@
+/**
+ * Add integer values to the virtual corpus
+ */
+"use strict";
+
+define(['util'], function () {
+
+ return {
+ /**
+ * Create new integer value helper.
+ */
+ create : function () {
+ const a = arguments;
+ let value = 0;
+
+ // Set value
+ if (a.length >= 1) {
+ if (a[0] !== undefined)
+ value = a[0];
+ };
+
+ return Object.create(this)._init(value);
+ },
+
+
+ // Initialize the integer value
+ _init : function (value) {
+ this.value(value);
+ return this;
+ },
+
+ /**
+ * Get or set the integer value.
+ */
+ value : function (val) {
+ if (arguments.length === 1) {
+
+ if (typeof val != "number")
+ val = parseInt(val);
+
+ if (isNaN(val))
+ val = 0;
+
+ this._value = val;
+ this._update();
+ };
+ return this._value;
+ },
+
+
+ // Update dom element
+ _update : function () {
+ if (this._el === undefined)
+ return;
+
+ this._value = this._input.value;
+ },
+
+
+ /**
+ * Store the integer value.
+ * This method should be overwritten.
+ * The method receives the value.
+ */
+ store : function (v) {},
+
+
+ /**
+ * Put focus on element
+ */
+ focus : function () {
+ this._el.children[0].focus();
+ },
+
+
+ /**
+ * Get the associated dom element.
+ */
+ element : function () {
+ if (this._el !== undefined)
+ return this._el;
+
+ // Create element
+ const e = this._el = document.createElement('div');
+ e.setAttribute('tabindex', 0);
+ e.style.outline = 0;
+
+ const cl = e.classList;
+ cl.add('value');
+
+ // Add input field
+ this._input = e.addE('input');
+ this._input.setAttribute("type", "number");
+
+ if (this.value() !== undefined) {
+ this._input.value = this.value();
+ };
+
+ // If the focus is not on the text field anymore,
+ // delegate focus to
+ this._input.addEventListener(
+ 'blur',
+ function (ev) {
+ const t = this;
+ if (!t._inField) {
+ t.value(t._input.value);
+ t.store(t.value());
+ };
+ ev.halt();
+ }.bind(this)
+ );
+
+ // Workaround to check the click is in the field
+ e.addEventListener(
+ 'mousedown',
+ function () {
+ this._inField = true;
+ }.bind(this)
+ );
+
+ e.addEventListener(
+ 'mouseup',
+ function () {
+ this._inField = false;
+ this._input.focus();
+ }.bind(this)
+ );
+
+ this._input.addEventListener(
+ 'keypress',
+ function (ev) {
+ const t = this;
+ if (ev.keyCode == 13) {
+ t.value(t._input.value);
+ t.store(t.value());
+ return false;
+ };
+ }.bind(this)
+ );
+
+ return e;
+ }
+ };
+});