Commit 32754ad0 authored by delanoe's avatar delanoe

[FEAT] merge and Agent Based simulation presentation at AFS.

parents e9ce6b85 344f989f
from collections import defaultdict from collections import defaultdict
from math import sqrt
from gargantext_web.db import session, NodeNgram, NodeNgramNgram, bulk_insert
class Translations:
class BaseClass:
def __add__(self, other):
if hasattr(self, '__radd__'):
return self.__radd__(other)
else:
return NotImplemented
def __sub__(self, other):
if hasattr(self, '__rsub__'):
return self.__rsub__(other)
else:
return NotImplemented
def __mul__(self, other):
if hasattr(self, '__rmul__'):
return self.__rmul__(other)
else:
return NotImplemented
def __div__(self, other):
if hasattr(self, '__rdiv__'):
return self.__rdiv__(other)
else:
return NotImplemented
def __and__(self, other):
if hasattr(self, '__rand__'):
return self.__rand__(other)
else:
return NotImplemented
def __or__(self, other):
if hasattr(self, '__ror__'):
return self.__ror__(other)
else:
return NotImplemented
def __repr__(self):
items = self.items
if isinstance(items, defaultdict):
if len(items) and isinstance(next(iter(items.values())), defaultdict):
items = {
key: dict(value)
for key, value in items.items()
}
else:
items = dict(items)
return '<%s %s>' % (
self.__class__.__name__,
repr(items),
)
__str__ = __repr__
class Translations(BaseClass):
def __init__(self, other=None): def __init__(self, other=None):
if other is None: if other is None:
self.items = defaultdict(int) self.items = defaultdict(int)
self.groups = defaultdict(set) self.groups = defaultdict(set)
elif isinstance(other, int):
query = (session
.query(NodeNgramNgram.ngramy_id, NodeNgramNgram.ngramx_id)
.filter(NodeNgramNgram.node_id == other)
)
self.items = defaultdict(int, query)
self.groups = defaultdict(set)
for key, value in self.items.items():
self.groups[value].add(key)
elif isinstance(other, Translations): elif isinstance(other, Translations):
self.items = other.items.copy() self.items = other.items.copy()
self.groups = other.groups.copy() self.groups = other.groups.copy()
...@@ -18,39 +86,69 @@ class Translations: ...@@ -18,39 +86,69 @@ class Translations:
else: else:
raise TypeError raise TypeError
def __add__(self, other): def __rmul__(self, other):
result = self.__class__(self) result = NotImplemented
result.items.update(other) if isinstance(other, UnweightedList):
for key, value in other.groups: result = UnweightedList()
result.groups[key] += value result.items = set(
return result self.items.get(key, key)
for key in other.items
def __sub__(self, other): )
result = self.__class__(self) elif isinstance(other, WeightedList):
if isinstance(other, Translations): result = WeightedList()
for key, value in other.items.items(): for key, value in other.items.items():
result.items.pop(key, None) result.items[
result.groups[value].remove(key) self.items.get(key, key)
if len(result.groups[value]) == 0: ] += value
result.groups.pop(value) elif isinstance(other, Translations):
result = Translations()
items = self.items
items.update(other.items)
for key, value in items.items():
if value in items:
value = items[value]
if key != value:
result.items[key] = value
result.groups[value].add(key)
return result return result
def __iter__(self): def __iter__(self):
for key, value in self.items.items(): for key, value in self.items.items():
yield key, value yield key, value
def save(self, node_id):
# delete previous data
session.query(NodeNgramNgram).filter(NodeNgramNgram.node_id == node_id).delete()
session.commit()
# insert new data
bulk_insert(
NodeNgramNgram,
('node_id', 'ngramy_id', 'ngramx_id', 'score'),
((node_id, key, value, 1.0) for key, value in self.items.items())
)
class WeightedMatrix:
class WeightedMatrix(BaseClass):
def __init__(self, other=None): def __init__(self, other=None):
if other is None: if other is None:
self.items = defaultdict(lambda: defaultdict(float)) self.items = defaultdict(lambda: defaultdict(float))
elif isinstance(other, int):
query = (session
.query(NodeNgramNgram.ngramx_id, NodeNgramNgram.ngramy_id, NodeNgramNgram.score)
.filter(NodeNgramNgram.node_id == other)
)
self.items = defaultdict(lambda: defaultdict(float))
for key1, key2, value in self.items.items():
self.items[key1][key2] = value
elif isinstance(other, WeightedMatrix): elif isinstance(other, WeightedMatrix):
self.items = other.items.copy() self.items = defaultdict(lambda: defaultdict(float))
for key1, key2, value in other:
self.items[key1][key2] = value
elif hasattr(other, '__iter__'): elif hasattr(other, '__iter__'):
self.items = defaultdict(lambda: defaultdict(float)) self.items = defaultdict(lambda: defaultdict(float))
for row in other: for row in other:
self.items[other[0]][other[1]] = [other[2]] self.items[row[0]][row[1]] = row[2]
else: else:
raise TypeError raise TypeError
...@@ -59,36 +157,103 @@ class WeightedMatrix: ...@@ -59,36 +157,103 @@ class WeightedMatrix:
for key2, value in key2_value.items(): for key2, value in key2_value.items():
yield key1, key2, value yield key1, key2, value
def __sub__(self, other): def save(self, node_id):
"""Remove elements of the other list from the current one # delete previous data
Can only be substracted to another list of coocurrences. session.query(NodeNgramNgram).filter(NodeNgramNgram.node_id == node_id).delete()
""" session.commit()
pass # insert new data
bulk_insert(
NodeNgramNgram,
('node_id', 'ngramx_id', 'ngramy_id', 'score'),
((node_id, key1, key2, value) for key1, key2, value in self)
)
def __mul__(self, other): def __radd__(self, other):
if isinstance(other, Translations): result = NotImplemented
result = WeightedMatrix() if isinstance(other, WeightedMatrix):
for key1, key2_value in self.items.items(): result = WeightedMatrix()
for key2, value in self.items: for key1, key2, value in self:
result.items[ value = value + other.items[key1][key2]
other.items.get(key, key) if value != 0.0:
] = value result.items[key1][key2] = value
else: return result
raise TypeError
return result def __rsub__(self, other):
result = NotImplemented
if isinstance(other, (UnweightedList, WeightedList)):
result = WeightedMatrix()
for key1, key2, value in self:
if key1 in other.items or key2 in other.items:
continue
result.items[key1][key2] = value
elif isinstance(other, WeightedMatrix):
result = WeightedMatrix()
for key1, key2, value in self:
value = value - other.items[key1][key2]
if value != 0.0:
result.items[key1][key2] = value
return result
def __rand__(self, other):
result = NotImplemented
if isinstance(other, (UnweightedList, WeightedList)):
result = WeightedMatrix()
for key1, key2, value in self:
if key1 not in other.items or key2 not in other.items:
continue
result.items[key1][key2] = value
return result
def __rmul__(self, other):
result = NotImplemented
if isinstance(other, Translations):
result = WeightedMatrix()
for key1, key2_value in self.items.items():
key1 = other.items.get(key1, key1)
for key2, value in key2_value.items():
result.items[key1][
other.items.get(key2, key2)
] += value
elif isinstance(other, UnweightedList):
result = self.__rand__(other)
# elif isinstance(other, WeightedMatrix):
# result = WeightedMatrix()
elif isinstance(other, WeightedList):
result = WeightedMatrix()
for key1, key2, value in self:
if key1 not in other.items or key2 not in other.items:
continue
result.items[key1][key2] = value * sqrt(other.items[key1] * other.items[key2])
return result
def __rdiv__(self, other):
result = NotImplemented
if isinstance(other, WeightedList):
result = WeightedMatrix()
for key1, key2, value in self:
if key1 not in other.items or key2 not in other.items:
continue
result.items[key1][key2] = value / sqrt(other.items[key1] * other.items[key2])
return result
class UnweightedList: class UnweightedList(BaseClass):
def __init__(self, other=None): def __init__(self, other=None):
if other is None: if other is None:
self.items = set() self.items = set()
elif isinstance(other, int):
query = (session
.query(NodeNgram.ngram_id)
.filter(NodeNgram.node_id == other)
)
self.items = {row[0] for row in query}
elif isinstance(other, WeightedList): elif isinstance(other, WeightedList):
self.items = set(other.items.keys()) self.items = set(other.items.keys())
elif isinstance(other, UnweightedList): elif isinstance(other, UnweightedList):
self.items = other.items.copy() self.items = other.items.copy()
elif hasattr(other, '__iter__'): elif hasattr(other, '__iter__'):
items = (item for item in other) items = tuple(item for item in other)
if len(items) == 0: if len(items) == 0:
self.items = set() self.items = set()
else: else:
...@@ -99,44 +264,86 @@ class UnweightedList: ...@@ -99,44 +264,86 @@ class UnweightedList:
else: else:
raise TypeError raise TypeError
def __add__(self, other): def __radd__(self, other):
result = self.__class__(self) result = NotImplemented
if isinstance(other, UnweightedList): if isinstance(other, UnweightedList):
result.items |= other.items result = UnweightedList(other)
result.items |= self.items
elif isinstance(other, WeightedList): elif isinstance(other, WeightedList):
result.items |= set(other.items.keys()) result = WeightedList(other)
else: for key in self.items:
raise TypeError result.items[key] += 1.0
return result return result
__or__ = __add__ def __rsub__(self, other):
result = NotImplemented
def __sub__(self, other):
result = self.__class__(self)
if isinstance(other, UnweightedList): if isinstance(other, UnweightedList):
result = UnweightedList(self)
result.items -= other.items result.items -= other.items
elif isinstance(other, WeightedList): elif isinstance(other, WeightedList):
result = UnweightedList(self)
result.items -= set(other.items.keys()) result.items -= set(other.items.keys())
else:
raise TypeError
return result return result
def __and__(self, other): def __ror__(self, other):
result = self.__class__(self) result = NotImplemented
if isinstance(other, UnweightedList):
result = UnweightedList(self)
result.items |= other.items
elif isinstance(other, WeightedList):
result = UnweightedList(self)
result.items |= set(other.items.keys())
return result
def __rand__(self, other):
result = NotImplemented
if isinstance(other, UnweightedList): if isinstance(other, UnweightedList):
result = UnweightedList(self)
result.items &= other.items result.items &= other.items
elif isinstance(other, WeightedList): elif isinstance(other, WeightedList):
result.items &= set(other.items.keys()) result = UnweightedList(self)
else: result.items &= set(other.items)
raise TypeError return result
def __rmul__(self, other):
result = NotImplemented
if isinstance(other, Translations):
result = UnweightedList()
result.items = set(
other.items.get(key, key)
for key in self.items
)
elif isinstance(other, UnweightedList):
result = WeightedList(self)
result.items = {key: 1.0 for key in self.items & other.items}
elif isinstance(other, WeightedList):
result = WeightedList()
result.items = {key: value for key, value in other.items.items() if key in self.items}
return result return result
def save(self, node_id):
# delete previous data
session.query(NodeNgram).filter(NodeNgram.node_id == node_id).delete()
session.commit()
# insert new data
bulk_insert(
NodeNgram,
('node_id', 'ngram_id', 'weight'),
((node_id, key, 1.0) for key in self.items)
)
class WeightedList:
class WeightedList(BaseClass):
def __init__(self, other=None): def __init__(self, other=None):
if other is None: if other is None:
self.items = defaultdict(float) self.items = defaultdict(float)
elif isinstance(other, int):
query = (session
.query(NodeNgram.ngram_id, NodeNgram.weight)
.filter(NodeNgram.node_id == other)
)
self.items = defaultdict(float, query)
elif isinstance(other, WeightedList): elif isinstance(other, WeightedList):
self.items = other.items.copy() self.items = other.items.copy()
elif isinstance(other, UnweightedList): elif isinstance(other, UnweightedList):
...@@ -144,7 +351,7 @@ class WeightedList: ...@@ -144,7 +351,7 @@ class WeightedList:
for key in other.items: for key in other.items:
self.items[key] = 1.0 self.items[key] = 1.0
elif hasattr(other, '__iter__'): elif hasattr(other, '__iter__'):
self.items = defaultdict(float, items) self.items = defaultdict(float, other)
else: else:
raise TypeError raise TypeError
...@@ -152,60 +359,135 @@ class WeightedList: ...@@ -152,60 +359,135 @@ class WeightedList:
for key, value in self.items.items(): for key, value in self.items.items():
yield key, value yield key, value
def __add__(self, other): def __radd__(self, other):
"""Add elements from the other list to the current one result = NotImplemented
""" if isinstance(other, WeightedList):
result = self.__class__(self) result = WeightedList(self)
if isinstance(other, UnweightedList): for key, value in other.items.items():
for key, value in other.items:
result.items[key] += 1.0
elif isinstance(other, WeightedList):
for key, value in other.items:
result.items[key] += value result.items[key] += value
else: elif isinstance(other, UnweightedList):
raise TypeError result = WeightedList(self)
for key in other.items:
result.items[key] += 1.0
return result return result
def __sub__(self, other): def __rsub__(self, other):
"""Remove elements of the other list from the current one """Remove elements of the other list from the current one
""" """
result = self.__class__(self) result = NotImplemented
if isinstance(other, UnweightedList): if isinstance(other, UnweightedList):
for key in other.items: result = WeightedList()
result.items.pop(key, None) result.items = {key: value for key, value in self.items.items() if key not in other.items}
else: elif isinstance(other, WeightedList):
raise TypeError result = WeightedList(self)
for key, value in other.items.items():
if key in result.items and result.items[key] == value:
result.items.pop(key)
else:
result.items[key] -= value
return result return result
def __and__(self, other): def __ror__(self, other):
result = NotImplemented
if isinstance(other, UnweightedList): if isinstance(other, UnweightedList):
result = defaultdict(float) result = UnweightedList(self)
for key, value in self.items.items(): result.items |= other.items
if item in other.items: elif isinstance(other, WeightedList):
result[key] = value result = UnweightedList(self)
else: result.items |= set(other.items.keys())
raise TypeError
return result return result
def __mul__(self, other): def __rmul__(self, other):
if isinstance(other, Translations): result = NotImplemented
if isinstance(other, WeightedList):
result = WeightedList()
result.items = {
key: value * other.items[key]
for key, value
in self.items.items()
if key in other.items
}
if isinstance(other, UnweightedList):
result = WeightedList() result = WeightedList()
for key, value in self.items: result.items = {
key: value
for key, value
in self.items.items()
if key in other.items
}
elif isinstance(other, Translations):
result = WeightedList()
for key, value in self.items.items():
result.items[ result.items[
other.items.get(key, key) other.items.get(key, key)
] += value ] += value
else:
raise TypeError
return result return result
def __rand__(self, other):
result = NotImplemented
if isinstance(other, UnweightedList):
result = UnweightedList(self)
result.items &= other.items
elif isinstance(other, WeightedList):
result = UnweightedList(self)
result.items &= set(other.items.keys())
return result
def save(self, node_id):
# delete previous data
session.query(NodeNgram).filter(NodeNgram.node_id == node_id).delete()
session.commit()
# insert new data
bulk_insert(
NodeNgram,
('node_id', 'ngram_id', 'weight'),
((node_id, key, value) for key, value in self.items.items())
)
def test():
from collections import OrderedDict
# define operands
operands = OrderedDict()
operands['wm'] = WeightedMatrix(((1, 2, .5), (1, 3, .75), (2, 3, .6), (3, 3, 1), ))
operands['ul'] = UnweightedList((1, 2, 3, 4, 5))
# operands['ul2'] = UnweightedList((1, 2, 3, 6))
# operands['ul2'].save(5)
# operands['ul3'] = UnweightedList(5)
operands['wl'] = WeightedList({1:.7, 2:.8, 7: 1.1})
# operands['wl1'].save(5)
# operands['wl2'] = WeightedList(5)
# operands['t1'] = Translations({1:2, 4:5})
operands['t'] = Translations({3:2, 4:5})
# operands['t2'].save(5)
# operands['t3'] = Translations(5)
# define operators
operators = OrderedDict()
operators['+'] = '__add__'
operators['-'] = '__sub__'
operators['*'] = '__mul__'
operators['|'] = '__or__'
operators['&'] = '__and__'
# show operands
for operand_name, operand in operands.items():
print('%4s = %s' % (operand_name, operand))
# show operations results
for operator_name, operator in operators.items():
print()
for operand1_name, operand1 in operands.items():
for operand2_name, operand2 in operands.items():
if hasattr(operand1, operator):
result = getattr(operand1, operator)(operand2)
else:
result = '?'
print('%4s %s %-4s = %s' % (
operand1_name,
operator_name,
operand2_name,
'?' if result == NotImplemented else result,
))
if __name__ == '__main__':
test()
# if __name__ == '__main__':
# l = Coocurrences()
# l = List()
# for i in l:
# print(i)
# t1 = Translations()
# t2 = Translations()
# t2.items = {1: 2}
# for i in t1 + t2:
# print(i)
...@@ -9,7 +9,8 @@ ...@@ -9,7 +9,8 @@
"angular-loader": "~1.2.x", "angular-loader": "~1.2.x",
"angular-resource": "~1.2.x", "angular-resource": "~1.2.x",
"bootstrap": "~3.x", "bootstrap": "~3.x",
"angular-cookies": "1.2" "angular-cookies": "1.2",
"bootstrap-select": "silviomoreto/bootstrap-select#~1.7.3"
}, },
"resolutions": { "resolutions": {
"angular": "~1.2.x" "angular": "~1.2.x"
......
(function () {
'use strict';
var annotationsAppActiveLists = angular.module('annotationsAppActiveLists', []);
annotationsAppActiveLists.controller('ActiveListsController',
['$scope', '$rootScope', '$timeout',
function ($scope, $rootScope, $timeout) {
$scope.activeListsChange = function() {
var selected = $('.selectpicker option:selected').val();
var newActive = {};
$('.selectpicker option:selected').each(function(item, value) {
var id = value.id.split("---", 2)[1];
newActive[id] = value.value;
});
$rootScope.activeLists = newActive;
};
$rootScope.$watchCollection('activeLists', function (newValue, oldValue) {
if (newValue === undefined) return;
$timeout(function() {
$('.selectpicker').selectpicker('refresh');
});
});
$rootScope.$watchCollection('lists', function (newValue, oldValue) {
if (newValue === undefined) return;
// reformat lists to allListsSelect
var allListsSelect = [];
angular.forEach($rootScope.lists, function(value, key) {
this.push({
'id': key,
'label': value
});
// initialize activeLists with the MiamList by default
if (value == 'MiamList') {
$rootScope.activeLists = {};
$rootScope.activeLists[key] = value;
}
}, allListsSelect);
$rootScope.allListsSelect = allListsSelect;
$timeout(function() {
$('.selectpicker').selectpicker();
$('.selectpicker').selectpicker('val', ['MiamList']);
});
});
}]);
})(window);
/* app css stylesheet */ /* app css stylesheet */
/*
* Class names corresponding to server-side list names
* To display another list name, add a new class under this
*/
.MiamList {
color: black;
background-color: rgba(60, 118, 61, 0.5);
cursor: pointer;
}
.StopList {
color: black;
background-color: rgba(169, 68, 66, 0.2);
cursor: pointer;
}
.delete-keyword, .occurrences { .delete-keyword, .occurrences {
vertical-align: super; vertical-align: super;
font-size: 70%; font-size: 70%;
...@@ -27,47 +45,32 @@ ...@@ -27,47 +45,32 @@
border-bottom: none; border-bottom: none;
} }
.miamword { .main-panel, .text-panel, .words-panel {
color: black; margin: 10px 0;
background-color: rgba(60, 118, 61, 0.5);
cursor: pointer;
}
.stopword {
color: black;
background-color: rgba(169, 68, 66, 0.2);
cursor: pointer;
} }
.global-stopword { #annotationsApp {
color: black; min-width: 780px;
background-color: rgba(169, 68, 66, 0.05);
cursor: pointer;
} }
.main-panel, .text-panel, .words-panel { .words-panel {
height: 800px; min-width: 220px;
margin: 10px 0px;
} }
.text-panel { .text-panel {
overflow-y: auto; overflow-y: auto;
min-width: 400px;
} }
.words-list { .words-list {
margin-bottom: 5px; margin-bottom: 5px;
height: 250px;
} }
.keyword-container { .keyword-text {
/*display: inline-block;*/
}
.keyword {
word-break: break-all; word-break: break-all;
} }
.list-group-item { .keyword-group-item {
display: inline-block; display: inline-block;
float: left; float: left;
padding: 5px; padding: 5px;
...@@ -145,3 +148,7 @@ ...@@ -145,3 +148,7 @@
border-right: solid thin #CCC; border-right: solid thin #CCC;
margin-right: 5px; margin-right: 5px;
} }
.float-right {
float: right;
}
(function () { (function () {
'use strict'; 'use strict';
/*
* Django STATIC_URL given to angular to load async resources
*/
var S = window.STATIC_URL; var S = window.STATIC_URL;
window.annotationsApp = angular.module('annotationsApp', ['annotationsAppHttp']); window.annotationsApp = angular.module('annotationsApp', ['annotationsAppHttp',
'annotationsAppNgramList', 'annotationsAppHighlight', 'annotationsAppDocument',
'annotationsAppActiveLists', 'annotationsAppUtils']);
/*
* Angular Templates must not conflict with Django's
*/
window.annotationsApp.config(function($interpolateProvider) { window.annotationsApp.config(function($interpolateProvider) {
$interpolateProvider.startSymbol('{[{'); $interpolateProvider.startSymbol('{[{');
$interpolateProvider.endSymbol('}]}'); $interpolateProvider.endSymbol('}]}');
}); });
window.annotationsApp.directive('keywordTemplate', function () { /*
return { * Main function
templateUrl: function ($element, $attributes) { * GET the document node and all its ngrams
return S + 'annotations/keyword_tpl.html'; */
}
};
});
window.annotationsApp.controller('ExtraAnnotationController',
['$scope', '$rootScope', '$element', 'NgramHttpService',
function ($scope, $rootScope, $element, NgramHttpService) {
// TODO use the tooltip ?
$scope.onDeleteClick = function () {
NgramHttpService.delete({
'listId': $scope.keyword.list_id,
'ngramId': $scope.keyword.uuid
}).$promise.then(function(data) {
$.each($rootScope.annotations, function(index, element) {
if (element.list_id == $scope.keyword.list_id && element.uuid == $scope.keyword.uuid) {
$rootScope.annotations.splice(index, 1);
return false;
}
});
});
}
}]);
window.annotationsApp.controller('AnnotationController',
['$scope', '$rootScope', '$element',
function ($scope, $rootScope, $element) {
// FIXME maybe use angular.copy of the annotation
var keyword = _.find(
$rootScope.annotations,
function(annotation) { return annotation.uuid.toString() === $element[0].getAttribute('uuid').toString(); }
);
// attach the annotation scope dynamically
if (keyword) {
$scope.keyword = keyword;
}
$scope.onClick = function(e) {
$rootScope.$emit("positionAnnotationMenu", e.pageX, e.pageY);
$rootScope.$emit("toggleAnnotationMenu", $scope.keyword);
e.stopPropagation();
};
}]);
window.annotationsApp.directive('selectionTemplate', function () {
return {
templateUrl: function ($element, $attributes) {
return S + 'annotations/selection_tpl.html';
}
};
});
window.annotationsApp.controller('AnnotationMenuController',
['$scope', '$rootScope', '$element', '$timeout', 'NgramHttpService',
function ($scope, $rootScope, $element, $timeout, NgramHttpService) {
/*
* Universal text selection
*/
function getSelected() {
if (window.getSelection) {
return window.getSelection();
}
else if (document.getSelection) {
return document.getSelection();
}
else {
var selection = document.selection && document.selection.createRange();
if (selection.text) {
return selection.text;
}
return false;
}
return false;
}
var selection = getSelected();
function toggleSelectionHighlight(text) {
if (text.trim() !== "") {
$(".text-panel").addClass("selection");
} else {
$(".text-panel").removeClass("selection");
}
}
function toggleMenu(context, annotation) {
$timeout(function() {
$scope.$apply(function() {
if (angular.isObject(annotation)) {
$scope.level = angular.copy(annotation.level || 'global');
$scope.category = $rootScope.lists[annotation.list_id].toLowerCase();
$scope.listId = angular.copy(annotation.list_id);
// used in onClick
$scope.selection_text = angular.copy(annotation);
if ($scope.category == "miamlist") {
$scope.local_miamlist = false;
$scope.global_stoplist = true;
$scope.local_stoplist = true;
} else if ($scope.category == "stoplist") {
if ($scope.level == "local") {
$scope.local_stoplist = false;
$scope.global_stoplist = true;
}
if ($scope.level == "global") {
$scope.global_stoplist = false;
$scope.local_stoplist = true;
}
$scope.local_miamlist = true;
}
// show menu
$element.fadeIn(100);
}
else if (annotation.trim() !== "") {
$scope.selection_text = angular.copy(annotation);
$scope.level = "New Ngram from selection";
$scope.category = null;
$scope.local_miamlist = true;
$scope.local_stoplist = true;
$scope.global_stoplist = true;
// show menu
$element.fadeIn(100);
} else {
// close menu
$element.fadeOut(100);
}
});
});
}
var elt = $(".text-panel")[0];
var pos = $(".text-panel").position();
function positionElement(context, x, y) {
// todo try bootstrap popover component
$element.css('left', x + 10);
$element.css('top', y + 10);
}
function positionMenu(e) {
positionElement(null, e.pageX, e.pageY);
}
// TODO is mousedown necessary ?
$(".text-panel").mousedown(function(){
$(".text-panel").mousemove(positionMenu);
});
$(".text-panel").mouseup(function(){
$(".text-panel").unbind("mousemove", positionMenu);
toggleSelectionHighlight(selection.toString().trim());
toggleMenu(null, selection.toString().trim());
});
$(".text-panel").delegate(':not("#selection")', "click", function(e) {
if ($(e.target).hasClass("keyword-inline")) return;
positionMenu(e);
toggleSelectionHighlight(selection.toString().trim());
toggleMenu(null, selection.toString().trim());
});
$rootScope.$on("positionAnnotationMenu", positionElement);
$rootScope.$on("toggleAnnotationMenu", toggleMenu);
$scope.onClick = function($event, action, listId, level) {
if (angular.isObject($scope.selection_text)) {
// delete from the current list
NgramHttpService[action]({
'listId': listId,
'ngramId': $scope.selection_text.uuid
}).$promise.then(function(data) {
$.each($rootScope.annotations, function(index, element) {
if (element.list_id == listId && element.uuid == $scope.selection_text.uuid) {
$rootScope.annotations.splice(index, 1);
return false;
}
});
});
} else if ($scope.selection_text.trim() !== "") {
// new annotation from selection
NgramHttpService.post(
{
'listId': listId
},
{'annotation' : {'text': $scope.selection_text.trim()}}
).$promise.then(function(data) {
$rootScope.annotations.push(data);
});
}
// hide selection highlighted text and the menu
$(".text-panel").removeClass("selection");
$element.fadeOut(100);
};
}
]);
window.annotationsApp.controller('IntraTextController',
['$scope', '$rootScope', '$compile', 'NgramHttpService',
function ($scope, $rootScope, $compile, NgramHttpService) {
$scope.extra_stoplist = [];
$scope.extra_miamlist = [];
$scope.currentStopPage = 0;
$scope.currentMiamPage = 0;
$scope.pageSize = 15;
var counter = 0;
/*
* Replace the text by and html template
*/
function replaceTextByTemplate(text, annotation, template, pattern, lists) {
return text.replace(pattern, function(matched) {
var tpl = angular.element(template);
tpl.append(matched);
tpl.attr('title', annotation.tooltip_content);
tpl.attr('uuid', annotation.uuid);
if ('MiamList' == lists[annotation.list_id]) tpl.addClass("miamword");
if ('StopList' == lists[annotation.list_id]) tpl.addClass("stopword");
//if (annotation.category == 'stoplist' && annotation.level == 'global') tpl.addClass("global-stopword");
return tpl.get(0).outerHTML;
});
}
function compileText(annotations, fullText, abstractText, $rootScope) {
counter = 0;
var templateBegin = "<span ng-controller='AnnotationController' ng-click='onClick($event)' class='keyword-inline'>";
var templateBeginRegexp = "<span ng-controller='AnnotationController' ng-click='onClick\(\$event\)' class='keyword-inline'>";
var templateEnd = "</span>";
var template = templateBegin + templateEnd;
var startPattern = "\\b((?:"+templateBeginRegexp+")*";
var middlePattern = "(?:<\/span>)*\\s(?:"+templateBeginRegexp+")*";
var endPattern = "(?:<\/span>)*)\\b";
/*
* Sorts annotations on the number of words
*/
function lengthSort(listitems, valuekey) {
listitems.sort(function(a, b) {
var compA = a[valuekey].split(" ").length;
var compB = b[valuekey].split(" ").length;
return (compA > compB) ? -1 : (compA <= compB) ? 1 : 0;
});
return listitems;
}
var sortedSizeAnnotations = lengthSort(annotations, "text");
var extra_stoplist = [],
extra_miamlist = [];
_.each(sortedSizeAnnotations, function (annotation) {
// TODO better split to manage two-words with minus sign
annotation.category = $rootScope.lists[annotation.list_id].toLowerCase();
var words = annotation.text.split(" ");
var pattern = new RegExp(startPattern + words.join(middlePattern) + endPattern, 'gmi');
var textRegexp = new RegExp("\\b"+annotation.text+"\\b", 'igm');
if (pattern.test(fullText) === true) {
fullText = replaceTextByTemplate(fullText, annotation, template, pattern, $rootScope.lists);
// TODO remove debug
counter++;
} else if (pattern.test(abstractText) === true) {
abstractText = replaceTextByTemplate(abstractText, annotation, template, pattern, $rootScope.lists);
counter++;
} else if (!textRegexp.test($rootScope.full_text) && !textRegexp.test($rootScope.abstract_text)) {
if (annotation.category == "stoplist") {
// Deactivated stoplist for the moment
// if ($.inArray(annotation.uuid, $scope.extra_stoplist.map(function (item) {
// return item.uuid;
// })) == -1) {
// extra_stoplist = lengthSort(extra_stoplist.concat(annotation), "text");
// }
} else if (annotation.category == "miamlist") {
if ($.inArray(annotation.uuid, $scope.extra_miamlist.map(function (item) {
return item.uuid;
})) == -1) {
extra_miamlist = lengthSort(extra_miamlist.concat(annotation), "text");
}
}
}
});
$scope.extra_stoplist = extra_stoplist;
$scope.extra_miamlist = extra_miamlist;
return {
'fullTextHtml': fullText,
'abstractTextHtml': abstractText
};
}
$rootScope.$watchCollection('annotations', function (newValue, oldValue) {
if ($rootScope.annotations === undefined) return;
if (angular.equals(newValue, oldValue)) return;
$rootScope.miamListId = _.invert($rootScope.lists)['MiamList'];
$rootScope.stopListId = _.invert($rootScope.lists)['StopList'];
$scope.extra_stoplist = [];
$scope.extra_miamlist = [];
var result = compileText(
$rootScope.annotations,
angular.copy($rootScope.full_text),
angular.copy($rootScope.abstract_text),
$rootScope
);
$.each($rootScope.annotations, function(index, element) {
if (element.list_id == $rootScope.stopListId) {
$scope.extra_stoplist.push(element);
} else if (element.list_id == $rootScope.miamListId) {
$scope.extra_miamlist.push(element);
}
});
angular.element('#full-text').html(result.fullTextHtml);
angular.element('#abstract-text').html(result.abstractTextHtml);
angular.element('.text-container').find('[ng-controller=AnnotationController]').each(function(idx, elt) {
angular.element(elt).replaceWith($compile(elt)($rootScope.$new(true)));
});
});
function submitNewAnnotation($event, inputEltId, listId) {
if ($event.keyCode !== undefined && $event.keyCode != 13) return;
var value = $(inputEltId).val().trim();
if (value === "") return;
NgramHttpService.post(
{
'listId': listId,
'ngramId': 'new'
},
{'annotation' : {'text': value}},
function(data) {
// on success
if (data) {
$rootScope.annotations.push(data);
}
});
$(inputEltId).val("");
}
$scope.onMiamlistSubmit = function ($event) {
submitNewAnnotation($event, "#miamlist-input", _.invert($rootScope.lists)['MiamList']);
};
// TODO refactor
$scope.onStoplistSubmit = function ($event) {
submitNewAnnotation($event, "#stoplist-input", _.invert($rootScope.lists)['MiamList']);
};
$scope.numStopPages = function () {
if ($scope.extra_stoplist === undefined) return 0;
return Math.ceil($scope.extra_stoplist.length / $scope.pageSize);
};
$scope.numMiamPages = function () {
if ($scope.extra_miamlist === undefined) return 0;
return Math.ceil($scope.extra_miamlist.length / $scope.pageSize);
};
$scope.nextMiamPage = function() {
$scope.currentMiamPage = $scope.currentMiamPage + 1;
};
$scope.previousMiamPage = function() {
$scope.currentMiamPage = $scope.currentMiamPage - 1;
};
$scope.nextStopPage = function() {
$scope.currentStopPage = $scope.currentStopPage + 1;
};
$scope.previousStopPage = function() {
$scope.currentStopPage = $scope.currentStopPage - 1;
};
}
]);
window.annotationsApp.filter('startFrom', function () {
return function (input, start) {
if (input === undefined) return;
start = +start; //parse to int
return input.slice(start);
};
});
window.annotationsApp.controller('DocController',
['$scope', '$rootScope', 'NgramListHttpService', 'DocumentHttpService',
function ($scope, $rootScope, NgramListHttpService, DocumentHttpService) {
$rootScope.documentResource = DocumentHttpService.get(
{'docId': $rootScope.docId},
function(data, responseHeaders) {
$scope.title = data.title;
$scope.authors = data.authors;
$scope.journal = data.journal;
$scope.publication_date = data.publication_date;
// TODO this data have to be deleted
//$scope.current_page_number = data.current_page_number;
//$scope.last_page_number = data.last_page_number;
// put in rootScope because used by many components
$rootScope.docId = data.id;
$rootScope.full_text = data.full_text;
$rootScope.abstract_text = data.abstract_text;
// GET the annotations
// TODO
$rootScope.annotationsResource = NgramListHttpService.get(
{'corpusId': $rootScope.corpusId, 'docId': $rootScope.docId}
).$promise.then(function(data) {
$rootScope.annotations = data[$rootScope.corpusId.toString()][$rootScope.docId.toString()];
$rootScope.lists = data[$rootScope.corpusId.toString()]['lists'];
});
});
// TODO setup pagination client-side
$scope.onPreviousClick = function () {
DocumentHttpService.get($scope.docId - 1);
};
$scope.onNextClick = function () {
DocumentHttpService.get($scope.docId + 1);
};
}]);
window.annotationsApp.run(function ($rootScope) { window.annotationsApp.run(function ($rootScope) {
/* GET the document node and all the annotations in the list associated */
var path = window.location.pathname.match(/\/project\/(.*)\/corpus\/(.*)\/document\/(.*)\//); var path = window.location.pathname.match(/\/project\/(.*)\/corpus\/(.*)\/document\/(.*)\//);
$rootScope.projectId = path[1]; $rootScope.projectId = path[1];
$rootScope.corpusId = path[2]; $rootScope.corpusId = path[2];
......
(function () {
'use strict';
var annotationsAppDocument = angular.module('annotationsAppDocument', ['annotationsAppHttp']);
annotationsAppDocument.controller('DocController',
['$scope', '$rootScope', '$timeout', 'NgramListHttpService', 'DocumentHttpService',
function ($scope, $rootScope, $timeout, NgramListHttpService, DocumentHttpService) {
$rootScope.documentResource = DocumentHttpService.get(
{'docId': $rootScope.docId},
function(data, responseHeaders) {
$scope.authors = data.authors;
$scope.journal = data.journal;
$scope.publication_date = data.publication_date;
//$scope.current_page_number = data.current_page_number;
//$scope.last_page_number = data.last_page_number;
$rootScope.title = data.title;
$rootScope.docId = data.id;
$rootScope.full_text = data.full_text;
$rootScope.abstract_text = data.abstract_text;
// GET the annotationss
NgramListHttpService.get(
{
'corpusId': $rootScope.corpusId,
'docId': $rootScope.docId
},
function(data) {
$rootScope.annotations = data[$rootScope.corpusId.toString()][$rootScope.docId.toString()];
$rootScope.lists = data[$rootScope.corpusId.toString()].lists;
},
function(data) {
console.error("unable to get the list of ngrams");
}
);
});
// TODO setup article pagination
$scope.onPreviousClick = function () {
DocumentHttpService.get($scope.docId - 1);
};
$scope.onNextClick = function () {
DocumentHttpService.get($scope.docId + 1);
};
}]);
annotationsAppDocument.controller('DocFavoriteController',
['$scope', '$rootScope', 'DocumentHttpService',
function ($scope, $rootScope, DocumentHttpService) {
$scope.onStarClick = function($event) {
console.log("TODO");
};
$scope.isFavorite = false;
}]);
})(window);
(function () {
'use strict';
var annotationsAppHighlight = angular.module('annotationsAppHighlight', ['annotationsAppHttp']);
/*
* Controls the mouse selection on the text
*/
annotationsAppHighlight.controller('TextSelectionController',
['$scope', '$rootScope', '$element',
function ($scope, $rootScope, $element) {
// FIXME maybe use angular.copy of the annotation
var keyword = _.find(
$rootScope.annotations,
function(annotation) { return annotation.uuid.toString() === $element[0].getAttribute('uuid').toString(); }
);
// attach the annotation scope dynamically
if (keyword) {
$scope.keyword = keyword;
}
$scope.onClick = function(e) {
$rootScope.$emit("positionAnnotationMenu", e.pageX, e.pageY);
$rootScope.$emit("toggleAnnotationMenu", $scope.keyword);
e.stopPropagation();
};
}]);
/*
* Controls the menu over the current mouse selection
*/
annotationsAppHighlight.controller('TextSelectionMenuController',
['$scope', '$rootScope', '$element', '$timeout', 'NgramHttpService', 'NgramListHttpService',
function ($scope, $rootScope, $element, $timeout, NgramHttpService, NgramListHttpService) {
/*
* Universal text selection
*/
function getSelected() {
if (window.getSelection) {
return window.getSelection();
}
else if (document.getSelection) {
return document.getSelection();
}
else {
var selection = document.selection && document.selection.createRange();
if (selection.text) {
return selection.text;
}
return false;
}
return false;
}
// we only need one singleton at a time
var selection = getSelected();
/*
* When mouse selection is started, we highlight it
*/
function toggleSelectionHighlight(text) {
if (text.trim() !== "" && !$element.hasClass('menu-is-opened')) {
$(".text-panel").addClass("selection");
} else {
$(".text-panel").removeClass("selection");
}
}
/*
* Dynamically construct the selection menu scope
*/
function toggleMenu(context, annotation) {
$timeout(function() {
$scope.$apply(function() {
var miamlist_id = _.invert($rootScope.lists).MiamList;
var stoplist_id = _.invert($rootScope.lists).StopList;
// variable used in onClick
$scope.selection_text = angular.copy(annotation);
if (angular.isObject(annotation) && !$element.hasClass('menu-is-opened')) {
// existing ngram
// Delete from the current list
$scope.menuItems = [
{
'action': 'delete',
'listId': annotation.list_id,
'verb': 'Delete from',
'listName': $rootScope.lists[annotation.list_id]
}
];
if ($rootScope.lists[annotation.list_id] == "MiamList") {
// Add to the alternative list
$scope.menuItems.push({
'action': 'post',
'listId': stoplist_id,
'verb': 'Move to',
'listName': $rootScope.lists[stoplist_id]
});
} else if ($rootScope.lists[annotation.list_id] == "StopList") {
// Add to the alternative list
$scope.menuItems.push({
'action': 'post',
'listId': miamlist_id,
'verb': 'Move to',
'listName': $rootScope.lists[miamlist_id]
});
}
// show the menu
$element.fadeIn(100);
$element.addClass('menu-is-opened');
} else if (annotation.trim() !== "" && !$element.hasClass('menu-is-opened')) {
// new ngram
$scope.menuItems = [
{
'action': 'post',
'listId': miamlist_id,
'verb': 'Add to',
'listName': $rootScope.lists[miamlist_id]
}
];
// show the menu
$element.fadeIn(100);
$element.addClass('menu-is-opened');
} else {
$scope.menuItems = [];
// close the menu
$element.fadeOut(100);
$element.removeClass('menu-is-opened');
}
});
});
}
var pos = $(".text-panel").position();
function positionElement(context, x, y) {
// todo try bootstrap popover component
$element.css('left', x + 10);
$element.css('top', y + 10);
}
function positionMenu(e) {
positionElement(null, e.pageX, e.pageY);
}
/*
* Dynamically position the menu
*/
$(".text-container").mousedown(function(){
$(".text-container").mousemove(positionMenu);
});
/*
* Finish positioning the menu then display the menu
*/
$(".text-container").mouseup(function(){
$(".text-container").unbind("mousemove", positionMenu);
toggleSelectionHighlight(selection.toString().trim());
toggleMenu(null, selection.toString().trim());
});
/*
* Toggle the menu when clicking on an existing ngram keyword
*/
$(".text-container").delegate(':not("#selection")', "click", function(e) {
// if ($(e.target).hasClass("keyword-inline")) return;
positionMenu(e);
toggleSelectionHighlight(selection.toString().trim());
toggleMenu(null, selection.toString().trim());
});
$rootScope.$on("positionAnnotationMenu", positionElement);
$rootScope.$on("toggleAnnotationMenu", toggleMenu);
/*
* Menu click action
*/
$scope.onMenuClick = function($event, action, listId) {
if (angular.isObject($scope.selection_text)) {
// action on an existing Ngram
NgramHttpService[action]({
'listId': listId,
'ngramId': $scope.selection_text.uuid
}, function(data) {
// Refresh the annotationss
NgramListHttpService.get(
{
'corpusId': $rootScope.corpusId,
'docId': $rootScope.docId
},
function(data) {
$rootScope.annotations = data[$rootScope.corpusId.toString()][$rootScope.docId.toString()];
$rootScope.refreshDisplay();
},
function(data) {
console.error("unable to get the list of ngrams");
}
);
}, function(data) {
console.error("unable to edit the Ngram " + $scope.selection_text.text);
}
);
} else if ($scope.selection_text.trim() !== "") {
// new annotation from selection
NgramHttpService.post(
{
'listId': listId,
'ngramId': 'create'
},
{
'text': $scope.selection_text.trim()
}, function(data) {
// Refresh the annotationss
NgramListHttpService.get(
{
'corpusId': $rootScope.corpusId,
'docId': $rootScope.docId
},
function(data) {
$rootScope.annotations = data[$rootScope.corpusId.toString()][$rootScope.docId.toString()];
$rootScope.refreshDisplay();
},
function(data) {
console.error("unable to get the list of ngrams");
}
);
}, function(data) {
console.error("unable to edit the Ngram " + $scope.selection_text);
}
);
}
// hide the highlighted text the the menu
$(".text-panel").removeClass("selection");
$element.fadeOut(100);
};
}
]);
/*
* Text highlighting controller
*/
annotationsAppHighlight.controller('NGramHighlightController',
['$scope', '$rootScope', '$compile',
function ($scope, $rootScope, $compile) {
/*
* Replace the text by an html template for ngram keywords
*/
function replaceTextByTemplate(text, ngram, template, pattern) {
return text.replace(pattern, function(matched) {
var tpl = angular.element(template);
tpl.append(matched);
tpl.attr('title', ngram.tooltip_content);
tpl.attr('uuid', ngram.uuid);
/*
* Add CSS class depending on the list the ngram is into
* FIXME Lists names and css classes are fixed, can do better
*/
tpl.addClass(ngram.listName);
return tpl.get(0).outerHTML;
});
}
/*
* Sorts annotations on the number of words
* Required for overlapping ngrams
*/
function lengthSort(listitems, valuekey) {
listitems.sort(function(a, b) {
var compA = a[valuekey].split(" ").length;
var compB = b[valuekey].split(" ").length;
return (compA > compB) ? -1 : (compA <= compB) ? 1 : 0;
});
return listitems;
}
/*
* Match and replace Ngram into the text
*/
function compileNgramsHtml(annotations, textMapping, $rootScope) {
if ($rootScope.activeLists === undefined) return;
if (_.keys($rootScope.activeLists).length === 0) return;
var templateBegin = "<span ng-controller='TextSelectionController' ng-click='onClick($event)' class='keyword-inline'>";
var templateBeginRegexp = "<span ng-controller='TextSelectionController' ng-click='onClick\(\$event\)' class='keyword-inline'>";
var templateEnd = "</span>";
var template = templateBegin + templateEnd;
var startPattern = "\\b((?:"+templateBeginRegexp+")*";
var middlePattern = "(?:<\/span>)*\\s(?:"+templateBeginRegexp+")*";
var endPattern = "(?:<\/span>)*)\\b";
var sortedSizeAnnotations = lengthSort(annotations, "text"),
extraNgramList = angular.copy($rootScope.extraNgramList);
// reinitialize an empty list
extraNgramList = angular.forEach(extraNgramList, function(name, id) {
extraNgramList[id] = [];
});
angular.forEach(sortedSizeAnnotations, function (annotation) {
// exclude ngrams that are into inactive lists
if ($rootScope.activeLists[annotation.list_id] === undefined) return;
// used to setup css class
annotation.listName = $rootScope.lists[annotation.list_id];
// regexps
var words = annotation.text.split(" ");
var pattern = new RegExp(startPattern + words.join(middlePattern) + endPattern, 'gmi');
var textRegexp = new RegExp("\\b"+annotation.text+"\\b", 'igm');
var isDisplayedIntraText = false;
// highlight text as html
angular.forEach(textMapping, function(text, eltId) {
if (pattern.test(text) === true) {
textMapping[eltId] = replaceTextByTemplate(text, annotation, template, pattern);
isDisplayedIntraText = true;
}
});
if (!isDisplayedIntraText) {
// add extra-text ngrams that are not already displayed
if ($.inArray(annotation.uuid, extraNgramList[annotation.list_id].map(function (item) {
return item.uuid;
})) == -1) {
// push the ngram and sort
extraNgramList[annotation.list_id] = extraNgramList[annotation.list_id].concat(annotation);
}
}
});
// update extraNgramList
$rootScope.extraNgramList = angular.forEach(extraNgramList, function(name, id) {
extraNgramList[id] = lengthSort(extraNgramList[id], 'text');
});
// return the object of element ID with the corresponding HTML
return textMapping;
}
$rootScope.refreshDisplay = function() {
if ($rootScope.annotations === undefined) return;
if ($rootScope.activeLists === undefined) return;
if (_.keys($rootScope.activeLists).length === 0) return;
// initialize extraNgramList
var extraNgramList = {};
$rootScope.extraNgramList = angular.forEach($rootScope.activeLists, function(name, id) {
this[id] = [];
}, extraNgramList);
$rootScope.extraNgramList = extraNgramList;
/*
* Transform text into HTML with higlighted ngrams
*/
var result = compileNgramsHtml(
$rootScope.annotations,
{
'#full-text': angular.copy($rootScope.full_text),
'#abstract-text': angular.copy($rootScope.abstract_text),
'#title': angular.copy($rootScope.title)
},
$rootScope
);
// inject highlighted HTML
angular.forEach(result, function(html, eltId) {
angular.element(eltId).html(html);
});
// inject one Angular controller on every highlighted text element
angular.element('.text-container').find('[ng-controller=TextSelectionController]').each(function(idx, elt) {
angular.element(elt).replaceWith($compile(elt)($rootScope.$new(true)));
});
}
/*
* Listen changes on the ngram data
*/
$rootScope.$watchCollection('activeLists', function (newValue, oldValue) {
$rootScope.refreshDisplay();
});
}
]);
})(window);
...@@ -57,11 +57,11 @@ ...@@ -57,11 +57,11 @@
{ {
post: { post: {
method: 'POST', method: 'POST',
params: {'listId': '@listId', 'ngramId': ''} params: {'listId': '@listId', 'ngramId': '@ngramId'}
}, },
delete: { delete: {
method: 'DELETE', method: 'DELETE',
params: {'listId': '@listId', 'ngramId': '@id'} params: {'listId': '@listId', 'ngramId': '@ngramId'}
} }
} }
); );
......
<span ng-if="keyword.category == 'miamlist'" ng-click='onDeleteClick()' class="delete-keyword" data-keyword-id="{[{keyword.uuid}]}" data-keyword-text="{[{keyword.text}]}" data-keyword-category="miamlist">×</span> <span ng-click='onDeleteClick()' class="delete-keyword">×</span>
<span ng-if="keyword.category == 'miamlist'" data-toggle="tooltip" class="keyword miamword">{[{keyword.text}]}</span> <span data-toggle="tooltip" class="keyword-text {[{keyword.listName}]}">{[{keyword.text}]}</span>
<span ng-if="keyword.category == 'stoplist'" ng-click='onDeleteClick()' class="delete-keyword" data-keyword-id="{[{keyword.uuid}]}" data-keyword-text="{[{keyword.text}]}" data-keyword-category="stoplist">×</span>
<span ng-if="keyword.category == 'stoplist'" data-toggle="tooltip" class="keyword stopword">{[{keyword.text}]}</span>
<span class="occurrences" data-keyword-id="{[{keyword.uuid}]}">{[{keyword.occurrences}]}</span> <span class="occurrences" data-keyword-id="{[{keyword.uuid}]}">{[{keyword.occurrences}]}</span>
...@@ -19,6 +19,7 @@ var S = window.STATIC_URL; ...@@ -19,6 +19,7 @@ var S = window.STATIC_URL;
$script([ $script([
S + 'bower_components/angular/angular.min.js', S + 'bower_components/angular/angular.min.js',
S + 'bower_components/bootstrap/dist/js/bootstrap.min.js', S + 'bower_components/bootstrap/dist/js/bootstrap.min.js',
S + 'bower_components/bootstrap-select/dist/js/bootstrap-select.min.js',
S + 'bower_components/angular-loader/angular-loader.min.js', S + 'bower_components/angular-loader/angular-loader.min.js',
S + 'bower_components/underscore/underscore-1.5.2.js', S + 'bower_components/underscore/underscore-1.5.2.js',
//'bower_components/angular-route/angular-route.js', //'bower_components/angular-route/angular-route.js',
...@@ -26,7 +27,10 @@ $script([ ...@@ -26,7 +27,10 @@ $script([
$script([ $script([
S + 'bower_components/angular-cookies/angular-cookies.min.js', S + 'bower_components/angular-cookies/angular-cookies.min.js',
S + 'bower_components/angular-resource/angular-resource.min.js'], function() { S + 'bower_components/angular-resource/angular-resource.min.js'], function() {
$script([S + 'annotations/http.js', S + 'annotations/app.js'], function() { $script([S + 'annotations/http.js', S + 'annotations/highlight.js',
S + 'annotations/document.js', S + 'annotations/ngramlist.js',
S + 'annotations/activelists.js', S + 'annotations/ngramlist.js',
S + 'annotations/utils.js', S + 'annotations/app.js'], function() {
// when all is done, execute bootstrap angular application (replace ng-app directive) // when all is done, execute bootstrap angular application (replace ng-app directive)
angular.bootstrap(document.getElementById("annotationsApp"), ['annotationsApp']); angular.bootstrap(document.getElementById("annotationsApp"), ['annotationsApp']);
}); });
......
(function () {
'use strict';
var annotationsAppNgramList = angular.module('annotationsAppNgramList', ['annotationsAppHttp']);
/*
* Controls one Ngram displayed in the flat lists (called "extra-text")
*/
annotationsAppNgramList.controller('NgramController',
['$scope', '$rootScope', 'NgramHttpService', 'NgramListHttpService',
function ($scope, $rootScope, NgramHttpService, NgramListHttpService) {
/*
* Click on the 'delete' cross button
*/
$scope.onDeleteClick = function () {
NgramHttpService.delete({
'listId': $scope.keyword.list_id,
'ngramId': $scope.keyword.uuid
}, function(data) {
// Refresh the annotationss
NgramListHttpService.get(
{
'corpusId': $rootScope.corpusId,
'docId': $rootScope.docId
},
function(data) {
$rootScope.annotations = data[$rootScope.corpusId.toString()][$rootScope.docId.toString()];
$rootScope.refreshDisplay();
},
function(data) {
console.error("unable to refresh the list of ngrams");
}
);
}, function(data) {
console.error("unable to remove the Ngram " + $scope.keyword.text);
});
};
}]);
/*
* Controller for the list panel displaying extra-text ngram
*/
annotationsAppNgramList.controller('NgramListPaginationController',
['$scope', '$rootScope', function ($scope, $rootScope) {
$rootScope.$watchCollection('extraNgramList', function (newValue, oldValue) {
$scope.currentListPage = 0;
$scope.pageSize = 15;
$scope.nextListPage = function() {
$scope.currentListPage = $scope.currentListPage + 1;
};
$scope.previousListPage = function() {
$scope.currentListPage = $scope.currentListPage - 1;
};
$scope.totalListPages = function(listId) {
if ($rootScope.extraNgramList[listId] === undefined) return 0;
return Math.ceil($rootScope.extraNgramList[listId].length / $scope.pageSize);
};
});
}]);
/*
* Template of the ngram element displayed in the flat lists
*/
annotationsAppNgramList.directive('keywordTemplate', function () {
return {
templateUrl: function ($element, $attributes) {
return S + 'annotations/keyword_tpl.html';
}
};
});
/*
* new NGram from the user input
*/
annotationsAppNgramList.controller('NgramInputController',
['$scope', '$rootScope', '$element', 'NgramHttpService', 'NgramListHttpService',
function ($scope, $rootScope, $element, NgramHttpService, NgramListHttpService) {
/*
* Add a new NGram from the user input in the extra-text list
*/
$scope.onListSubmit = function ($event, listId) {
var inputEltId = "#"+ listId +"-input";
if ($event.keyCode !== undefined && $event.keyCode != 13) return;
var value = angular.element(inputEltId).val().trim();
if (value === "") return;
NgramHttpService.post(
{
'listId': listId,
'ngramId': 'create'
},
{
'text': value
},
function(data) {
// on success
if (data) {
angular.element(inputEltId).val("");
// Refresh the annotationss
NgramListHttpService.get(
{
'corpusId': $rootScope.corpusId,
'docId': $rootScope.docId
},
function(data) {
$rootScope.annotations = data[$rootScope.corpusId.toString()][$rootScope.docId.toString()];
$rootScope.refreshDisplay();
},
function(data) {
console.error("unable to get the list of ngrams");
}
);
}
}, function(data) {
// on error
angular.element(inputEltId).parent().addClass("has-error");
console.error("error adding Ngram "+ value);
}
);
};
}]);
})(window);
<ul class="noselection">
<li class="miamword" ng-if="local_miamlist === true" ng-click="onClick($event, 'post', miamListId, 'local')">add to miam-list</li>
<li class="miamword" ng-if="local_miamlist === false" ng-click="onClick($event, 'delete', miamListId, 'local')">remove from miam-list</li>
<li class="stopword" ng-if="local_stoplist === true" ng-click="onClick($event, 'post', stopListId, 'local')">add to stop-list</li>
<li class="stopword" ng-if="local_stoplist === false" ng-click="onClick($event, 'delete', stopListId, 'local')">remove from stop-list</li>
<!--<li class="stopword" ng-if="global_stoplist === true" ng-click="onClick($event, 'post', 'stoplist', 'global')">add to global stop-list</li>
<li class="stopword" ng-if="global_stoplist === false" ng-click="onClick($event, 'delete', 'stoplist', 'global')">remove from global stop-list</li>-->
</ul>
(function () {
'use strict';
var annotationsAppUtils = angular.module('annotationsAppUtils', []);
/*
* Filter used in lists pagination (extra-text panel)
*/
annotationsAppUtils.filter('startFrom', function () {
return function (input, start) {
if (input === undefined) return;
start = +start; //parse to int
return input.slice(start);
};
});
})(window);
/*!
* Bootstrap-select v1.7.3 (http://silviomoreto.github.io/bootstrap-select)
*
* Copyright 2013-2015 bootstrap-select
* Licensed under MIT (https://github.com/silviomoreto/bootstrap-select/blob/master/LICENSE)
*/.bootstrap-select{width:220px \0}.bootstrap-select>.dropdown-toggle{width:100%;padding-right:25px}.error .bootstrap-select .dropdown-toggle,.has-error .bootstrap-select .dropdown-toggle{border-color:#b94a48}.bootstrap-select.fit-width{width:auto!important}.bootstrap-select:not([class*=col-]):not([class*=form-control]):not(.input-group-btn){width:220px}.bootstrap-select .dropdown-toggle:focus{outline:thin dotted #333!important;outline:5px auto -webkit-focus-ring-color!important;outline-offset:-2px}.bootstrap-select.form-control{margin-bottom:0;padding:0;border:none}.bootstrap-select.form-control:not([class*=col-]){width:100%}.bootstrap-select.form-control.input-group-btn{z-index:auto}.bootstrap-select.btn-group:not(.input-group-btn),.bootstrap-select.btn-group[class*=col-]{float:none;display:inline-block;margin-left:0}.bootstrap-select.btn-group.dropdown-menu-right,.bootstrap-select.btn-group[class*=col-].dropdown-menu-right,.row .bootstrap-select.btn-group[class*=col-].dropdown-menu-right{float:right}.form-group .bootstrap-select.btn-group,.form-horizontal .bootstrap-select.btn-group,.form-inline .bootstrap-select.btn-group{margin-bottom:0}.form-group-lg .bootstrap-select.btn-group.form-control,.form-group-sm .bootstrap-select.btn-group.form-control{padding:0}.form-inline .bootstrap-select.btn-group .form-control{width:100%}.bootstrap-select.btn-group.disabled,.bootstrap-select.btn-group>.disabled{cursor:not-allowed}.bootstrap-select.btn-group.disabled:focus,.bootstrap-select.btn-group>.disabled:focus{outline:0!important}.bootstrap-select.btn-group .dropdown-toggle .filter-option{display:inline-block;overflow:hidden;width:100%;text-align:left}.bootstrap-select.btn-group .dropdown-toggle .caret{position:absolute;top:50%;right:12px;margin-top:-2px;vertical-align:middle}.bootstrap-select.btn-group[class*=col-] .dropdown-toggle{width:100%}.bootstrap-select.btn-group .dropdown-menu{min-width:100%;z-index:1035;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select.btn-group .dropdown-menu.inner{position:static;float:none;border:0;padding:0;margin:0;border-radius:0;-webkit-box-shadow:none;box-shadow:none}.bootstrap-select.btn-group .dropdown-menu li{position:relative}.bootstrap-select.btn-group .dropdown-menu li.active small{color:#fff}.bootstrap-select.btn-group .dropdown-menu li.disabled a{cursor:not-allowed}.bootstrap-select.btn-group .dropdown-menu li a{cursor:pointer}.bootstrap-select.btn-group .dropdown-menu li a.opt{position:relative;padding-left:2.25em}.bootstrap-select.btn-group .dropdown-menu li a span.check-mark{display:none}.bootstrap-select.btn-group .dropdown-menu li a span.text{display:inline-block}.bootstrap-select.btn-group .dropdown-menu li small{padding-left:.5em}.bootstrap-select.btn-group .dropdown-menu .notify{position:absolute;bottom:5px;width:96%;margin:0 2%;min-height:26px;padding:3px 5px;background:#f5f5f5;border:1px solid #e3e3e3;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05);pointer-events:none;opacity:.9;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select.btn-group .no-results{padding:3px;background:#f5f5f5;margin:0 5px;white-space:nowrap}.bootstrap-select.btn-group.fit-width .dropdown-toggle .filter-option{position:static}.bootstrap-select.btn-group.fit-width .dropdown-toggle .caret{position:static;top:auto;margin-top:-1px}.bootstrap-select.btn-group.show-tick .dropdown-menu li.selected a span.check-mark{position:absolute;display:inline-block;right:15px;margin-top:5px}.bootstrap-select.btn-group.show-tick .dropdown-menu li a span.text{margin-right:34px}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle{z-index:1036}.bootstrap-select.show-menu-arrow .dropdown-toggle:before{content:'';border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid rgba(204,204,204,.2);position:absolute;bottom:-4px;left:9px;display:none}.bootstrap-select.show-menu-arrow .dropdown-toggle:after{content:'';border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;bottom:-4px;left:10px;display:none}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:before{bottom:auto;top:-3px;border-top:7px solid rgba(204,204,204,.2);border-bottom:0}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:after{bottom:auto;top:-3px;border-top:6px solid #fff;border-bottom:0}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:before{right:12px;left:auto}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:after{right:13px;left:auto}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle:after,.bootstrap-select.show-menu-arrow.open>.dropdown-toggle:before{display:block}.bs-actionsbox,.bs-donebutton,.bs-searchbox{padding:4px 8px}.bs-actionsbox{float:left;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-actionsbox .btn-group button{width:50%}.bs-donebutton{float:left;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-donebutton .btn-group button{width:100%}.bs-searchbox+.bs-actionsbox{padding:0 8px 4px}.bs-searchbox .form-control{margin-bottom:0;width:100%}select.bs-select-hidden,select.selectpicker{display:none!important}select.mobile-device{position:absolute!important;top:0;left:0;display:block!important;width:100%;height:100%!important;opacity:0}
\ No newline at end of file
/*!
* Bootstrap-select v1.7.3 (http://silviomoreto.github.io/bootstrap-select)
*
* Copyright 2013-2015 bootstrap-select
* Licensed under MIT (https://github.com/silviomoreto/bootstrap-select/blob/master/LICENSE)
*/
!function(a,b){"function"==typeof define&&define.amd?define(["jquery"],function(a){return b(a)}):"object"==typeof exports?module.exports=b(require("jquery")):b(jQuery)}(this,function(){!function(a){"use strict";function b(b){var c=[{re:/[\xC0-\xC6]/g,ch:"A"},{re:/[\xE0-\xE6]/g,ch:"a"},{re:/[\xC8-\xCB]/g,ch:"E"},{re:/[\xE8-\xEB]/g,ch:"e"},{re:/[\xCC-\xCF]/g,ch:"I"},{re:/[\xEC-\xEF]/g,ch:"i"},{re:/[\xD2-\xD6]/g,ch:"O"},{re:/[\xF2-\xF6]/g,ch:"o"},{re:/[\xD9-\xDC]/g,ch:"U"},{re:/[\xF9-\xFC]/g,ch:"u"},{re:/[\xC7-\xE7]/g,ch:"c"},{re:/[\xD1]/g,ch:"N"},{re:/[\xF1]/g,ch:"n"}];return a.each(c,function(){b=b.replace(this.re,this.ch)}),b}function c(a){var b={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","`":"&#x60;"},c="(?:"+Object.keys(b).join("|")+")",d=new RegExp(c),e=new RegExp(c,"g"),f=null==a?"":""+a;return d.test(f)?f.replace(e,function(a){return b[a]}):f}function d(b,c){var d=arguments,f=b,g=c;[].shift.apply(d);var h,i=this.each(function(){var b=a(this);if(b.is("select")){var c=b.data("selectpicker"),i="object"==typeof f&&f;if(c){if(i)for(var j in i)i.hasOwnProperty(j)&&(c.options[j]=i[j])}else{var k=a.extend({},e.DEFAULTS,a.fn.selectpicker.defaults||{},b.data(),i);b.data("selectpicker",c=new e(this,k,g))}"string"==typeof f&&(h=c[f]instanceof Function?c[f].apply(c,d):c.options[f])}});return"undefined"!=typeof h?h:i}String.prototype.includes||!function(){var a={}.toString,b=function(){try{var a={},b=Object.defineProperty,c=b(a,a,a)&&b}catch(d){}return c}(),c="".indexOf,d=function(b){if(null==this)throw TypeError();var d=String(this);if(b&&"[object RegExp]"==a.call(b))throw TypeError();var e=d.length,f=String(b),g=f.length,h=arguments.length>1?arguments[1]:void 0,i=h?Number(h):0;i!=i&&(i=0);var j=Math.min(Math.max(i,0),e);return g+j>e?!1:-1!=c.call(d,f,i)};b?b(String.prototype,"includes",{value:d,configurable:!0,writable:!0}):String.prototype.includes=d}(),String.prototype.startsWith||!function(){var a=function(){try{var a={},b=Object.defineProperty,c=b(a,a,a)&&b}catch(d){}return c}(),b={}.toString,c=function(a){if(null==this)throw TypeError();var c=String(this);if(a&&"[object RegExp]"==b.call(a))throw TypeError();var d=c.length,e=String(a),f=e.length,g=arguments.length>1?arguments[1]:void 0,h=g?Number(g):0;h!=h&&(h=0);var i=Math.min(Math.max(h,0),d);if(f+i>d)return!1;for(var j=-1;++j<f;)if(c.charCodeAt(i+j)!=e.charCodeAt(j))return!1;return!0};a?a(String.prototype,"startsWith",{value:c,configurable:!0,writable:!0}):String.prototype.startsWith=c}(),Object.keys||(Object.keys=function(a,b,c){c=[];for(b in a)c.hasOwnProperty.call(a,b)&&c.push(b);return c}),a.fn.triggerNative=function(a){var b,c=this[0];c.dispatchEvent?("function"==typeof Event?b=new Event(a,{bubbles:!0}):(b=document.createEvent("Event"),b.initEvent(a,!0,!1)),c.dispatchEvent(b)):(c.fireEvent&&(b=document.createEventObject(),b.eventType=a,c.fireEvent("on"+a,b)),this.trigger(a))},a.expr[":"].icontains=function(b,c,d){var e=a(b),f=(e.data("tokens")||e.text()).toUpperCase();return f.includes(d[3].toUpperCase())},a.expr[":"].ibegins=function(b,c,d){var e=a(b),f=(e.data("tokens")||e.text()).toUpperCase();return f.startsWith(d[3].toUpperCase())},a.expr[":"].aicontains=function(b,c,d){var e=a(b),f=(e.data("tokens")||e.data("normalizedText")||e.text()).toUpperCase();return f.includes(d[3].toUpperCase())},a.expr[":"].aibegins=function(b,c,d){var e=a(b),f=(e.data("tokens")||e.data("normalizedText")||e.text()).toUpperCase();return f.startsWith(d[3].toUpperCase())};var e=function(b,c,d){d&&(d.stopPropagation(),d.preventDefault()),this.$element=a(b),this.$newElement=null,this.$button=null,this.$menu=null,this.$lis=null,this.options=c,null===this.options.title&&(this.options.title=this.$element.attr("title")),this.val=e.prototype.val,this.render=e.prototype.render,this.refresh=e.prototype.refresh,this.setStyle=e.prototype.setStyle,this.selectAll=e.prototype.selectAll,this.deselectAll=e.prototype.deselectAll,this.destroy=e.prototype.remove,this.remove=e.prototype.remove,this.show=e.prototype.show,this.hide=e.prototype.hide,this.init()};e.VERSION="1.7.2",e.DEFAULTS={noneSelectedText:"Nothing selected",noneResultsText:"No results matched {0}",countSelectedText:function(a,b){return 1==a?"{0} item selected":"{0} items selected"},maxOptionsText:function(a,b){return[1==a?"Limit reached ({n} item max)":"Limit reached ({n} items max)",1==b?"Group limit reached ({n} item max)":"Group limit reached ({n} items max)"]},selectAllText:"Select All",deselectAllText:"Deselect All",doneButton:!1,doneButtonText:"Close",multipleSeparator:", ",styleBase:"btn",style:"btn-default",size:"auto",title:null,selectedTextFormat:"values",width:!1,container:!1,hideDisabled:!1,showSubtext:!1,showIcon:!0,showContent:!0,dropupAuto:!0,header:!1,liveSearch:!1,liveSearchPlaceholder:null,liveSearchNormalize:!1,liveSearchStyle:"contains",actionsBox:!1,iconBase:"glyphicon",tickIcon:"glyphicon-ok",maxOptions:!1,mobile:!1,selectOnTab:!1,dropdownAlignRight:!1},e.prototype={constructor:e,init:function(){var b=this,c=this.$element.attr("id");this.$element.addClass("bs-select-hidden"),this.liObj={},this.multiple=this.$element.prop("multiple"),this.autofocus=this.$element.prop("autofocus"),this.$newElement=this.createView(),this.$element.after(this.$newElement),this.$button=this.$newElement.children("button"),this.$menu=this.$newElement.children(".dropdown-menu"),this.$menuInner=this.$menu.children(".inner"),this.$searchbox=this.$menu.find("input"),this.options.dropdownAlignRight&&this.$menu.addClass("dropdown-menu-right"),"undefined"!=typeof c&&(this.$button.attr("data-id",c),a('label[for="'+c+'"]').click(function(a){a.preventDefault(),b.$button.focus()})),this.checkDisabled(),this.clickListener(),this.options.liveSearch&&this.liveSearchListener(),this.render(),this.setStyle(),this.setWidth(),this.options.container&&this.selectPosition(),this.$menu.data("this",this),this.$newElement.data("this",this),this.options.mobile&&this.mobile(),this.$newElement.on("hide.bs.dropdown",function(a){b.$element.trigger("hide.bs.select",a)}),this.$newElement.on("hidden.bs.dropdown",function(a){b.$element.trigger("hidden.bs.select",a)}),this.$newElement.on("show.bs.dropdown",function(a){b.$element.trigger("show.bs.select",a)}),this.$newElement.on("shown.bs.dropdown",function(a){b.$element.trigger("shown.bs.select",a)}),setTimeout(function(){b.$element.trigger("loaded.bs.select")})},createDropdown:function(){var b=this.multiple?" show-tick":"",d=this.$element.parent().hasClass("input-group")?" input-group-btn":"",e=this.autofocus?" autofocus":"",f=this.options.header?'<div class="popover-title"><button type="button" class="close" aria-hidden="true">&times;</button>'+this.options.header+"</div>":"",g=this.options.liveSearch?'<div class="bs-searchbox"><input type="text" class="form-control" autocomplete="off"'+(null===this.options.liveSearchPlaceholder?"":' placeholder="'+c(this.options.liveSearchPlaceholder)+'"')+"></div>":"",h=this.multiple&&this.options.actionsBox?'<div class="bs-actionsbox"><div class="btn-group btn-group-sm btn-block"><button type="button" class="actions-btn bs-select-all btn btn-default">'+this.options.selectAllText+'</button><button type="button" class="actions-btn bs-deselect-all btn btn-default">'+this.options.deselectAllText+"</button></div></div>":"",i=this.multiple&&this.options.doneButton?'<div class="bs-donebutton"><div class="btn-group btn-block"><button type="button" class="btn btn-sm btn-default">'+this.options.doneButtonText+"</button></div></div>":"",j='<div class="btn-group bootstrap-select'+b+d+'"><button type="button" class="'+this.options.styleBase+' dropdown-toggle" data-toggle="dropdown"'+e+'><span class="filter-option pull-left"></span>&nbsp;<span class="caret"></span></button><div class="dropdown-menu open">'+f+g+h+'<ul class="dropdown-menu inner" role="menu"></ul>'+i+"</div></div>";return a(j)},createView:function(){var a=this.createDropdown(),b=this.createLi();return a.find("ul")[0].innerHTML=b,a},reloadLi:function(){this.destroyLi();var a=this.createLi();this.$menuInner[0].innerHTML=a},destroyLi:function(){this.$menu.find("li").remove()},createLi:function(){var d=this,e=[],f=0,g=document.createElement("option"),h=-1,i=function(a,b,c,d){return"<li"+("undefined"!=typeof c&""!==c?' class="'+c+'"':"")+("undefined"!=typeof b&null!==b?' data-original-index="'+b+'"':"")+("undefined"!=typeof d&null!==d?'data-optgroup="'+d+'"':"")+">"+a+"</li>"},j=function(a,e,f,g){return'<a tabindex="0"'+("undefined"!=typeof e?' class="'+e+'"':"")+("undefined"!=typeof f?' style="'+f+'"':"")+(d.options.liveSearchNormalize?' data-normalized-text="'+b(c(a))+'"':"")+("undefined"!=typeof g||null!==g?' data-tokens="'+g+'"':"")+">"+a+'<span class="'+d.options.iconBase+" "+d.options.tickIcon+' check-mark"></span></a>'};if(this.options.title&&!this.multiple&&(h--,!this.$element.find(".bs-title-option").length)){var k=this.$element[0];g.className="bs-title-option",g.appendChild(document.createTextNode(this.options.title)),g.value="",k.insertBefore(g,k.firstChild),void 0===a(k.options[k.selectedIndex]).attr("selected")&&(g.selected=!0)}return this.$element.find("option").each(function(b){var c=a(this);if(h++,!c.hasClass("bs-title-option")){var g=this.className||"",k=this.style.cssText,l=c.data("content")?c.data("content"):c.html(),m=c.data("tokens")?c.data("tokens"):null,n="undefined"!=typeof c.data("subtext")?'<small class="text-muted">'+c.data("subtext")+"</small>":"",o="undefined"!=typeof c.data("icon")?'<span class="'+d.options.iconBase+" "+c.data("icon")+'"></span> ':"",p=this.disabled||"OPTGROUP"===this.parentElement.tagName&&this.parentElement.disabled;if(""!==o&&p&&(o="<span>"+o+"</span>"),d.options.hideDisabled&&p)return void h--;if(c.data("content")||(l=o+'<span class="text">'+l+n+"</span>"),"OPTGROUP"===this.parentElement.tagName&&c.data("divider")!==!0){var q=" "+this.parentElement.className||"";if(0===c.index()){f+=1;var r=this.parentElement.label,s="undefined"!=typeof c.parent().data("subtext")?'<small class="text-muted">'+c.parent().data("subtext")+"</small>":"",t=c.parent().data("icon")?'<span class="'+d.options.iconBase+" "+c.parent().data("icon")+'"></span> ':"";r=t+'<span class="text">'+r+s+"</span>",0!==b&&e.length>0&&(h++,e.push(i("",null,"divider",f+"div"))),h++,e.push(i(r,null,"dropdown-header"+q,f))}e.push(i(j(l,"opt "+g+q,k,m),b,"",f))}else c.data("divider")===!0?e.push(i("",b,"divider")):c.data("hidden")===!0?e.push(i(j(l,g,k,m),b,"hidden is-hidden")):(this.previousElementSibling&&"OPTGROUP"===this.previousElementSibling.tagName&&(h++,e.push(i("",null,"divider",f+"div"))),e.push(i(j(l,g,k,m),b)));d.liObj[b]=h}}),this.multiple||0!==this.$element.find("option:selected").length||this.options.title||this.$element.find("option").eq(0).prop("selected",!0).attr("selected","selected"),e.join("")},findLis:function(){return null==this.$lis&&(this.$lis=this.$menu.find("li")),this.$lis},render:function(b){var c,d=this;b!==!1&&this.$element.find("option").each(function(a){var b=d.findLis().eq(d.liObj[a]);d.setDisabled(a,this.disabled||"OPTGROUP"===this.parentElement.tagName&&this.parentElement.disabled,b),d.setSelected(a,this.selected,b)}),this.tabIndex();var e=this.$element.find("option").map(function(){if(this.selected){if(d.options.hideDisabled&&(this.disabled||"OPTGROUP"===this.parentElement.tagName&&this.parentElement.disabled))return!1;var b,c=a(this),e=c.data("icon")&&d.options.showIcon?'<i class="'+d.options.iconBase+" "+c.data("icon")+'"></i> ':"";return b=d.options.showSubtext&&c.data("subtext")&&!d.multiple?' <small class="text-muted">'+c.data("subtext")+"</small>":"","undefined"!=typeof c.attr("title")?c.attr("title"):c.data("content")&&d.options.showContent?c.data("content"):e+c.html()+b}}).toArray(),f=this.multiple?e.join(this.options.multipleSeparator):e[0];if(this.multiple&&this.options.selectedTextFormat.indexOf("count")>-1){var g=this.options.selectedTextFormat.split(">");if(g.length>1&&e.length>g[1]||1==g.length&&e.length>=2){c=this.options.hideDisabled?", [disabled]":"";var h=this.$element.find("option").not('[data-divider="true"], [data-hidden="true"]'+c).length,i="function"==typeof this.options.countSelectedText?this.options.countSelectedText(e.length,h):this.options.countSelectedText;f=i.replace("{0}",e.length.toString()).replace("{1}",h.toString())}}void 0==this.options.title&&(this.options.title=this.$element.attr("title")),"static"==this.options.selectedTextFormat&&(f=this.options.title),f||(f="undefined"!=typeof this.options.title?this.options.title:this.options.noneSelectedText),this.$button.attr("title",a.trim(f.replace(/<[^>]*>?/g,""))),this.$button.children(".filter-option").html(f),this.$element.trigger("rendered.bs.select")},setStyle:function(a,b){this.$element.attr("class")&&this.$newElement.addClass(this.$element.attr("class").replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi,""));var c=a?a:this.options.style;"add"==b?this.$button.addClass(c):"remove"==b?this.$button.removeClass(c):(this.$button.removeClass(this.options.style),this.$button.addClass(c))},liHeight:function(b){if(b||this.options.size!==!1&&!this.sizeInfo){var c=document.createElement("div"),d=document.createElement("div"),e=document.createElement("ul"),f=document.createElement("li"),g=document.createElement("li"),h=document.createElement("a"),i=document.createElement("span"),j=this.options.header?this.$menu.find(".popover-title")[0].cloneNode(!0):null,k=this.options.liveSearch?document.createElement("div"):null,l=this.options.actionsBox&&this.multiple?this.$menu.find(".bs-actionsbox")[0].cloneNode(!0):null,m=this.options.doneButton&&this.multiple?this.$menu.find(".bs-donebutton")[0].cloneNode(!0):null;if(i.className="text",c.className=this.$menu[0].parentNode.className+" open",d.className="dropdown-menu open",e.className="dropdown-menu inner",f.className="divider",i.appendChild(document.createTextNode("Inner text")),h.appendChild(i),g.appendChild(h),e.appendChild(g),e.appendChild(f),j&&d.appendChild(j),k){var n=document.createElement("span");k.className="bs-searchbox",n.className="form-control",k.appendChild(n),d.appendChild(k)}l&&d.appendChild(l),d.appendChild(e),m&&d.appendChild(m),c.appendChild(d),document.body.appendChild(c);var o=h.offsetHeight,p=j?j.offsetHeight:0,q=k?k.offsetHeight:0,r=l?l.offsetHeight:0,s=m?m.offsetHeight:0,t=a(f).outerHeight(!0),u="function"==typeof getComputedStyle?getComputedStyle(d):!1,v=u?null:a(d),w=parseInt(u?u.paddingTop:v.css("paddingTop"))+parseInt(u?u.paddingBottom:v.css("paddingBottom"))+parseInt(u?u.borderTopWidth:v.css("borderTopWidth"))+parseInt(u?u.borderBottomWidth:v.css("borderBottomWidth")),x=w+parseInt(u?u.marginTop:v.css("marginTop"))+parseInt(u?u.marginBottom:v.css("marginBottom"))+2;document.body.removeChild(c),this.sizeInfo={liHeight:o,headerHeight:p,searchHeight:q,actionsHeight:r,doneButtonHeight:s,dividerHeight:t,menuPadding:w,menuExtras:x}}},setSize:function(){if(this.findLis(),this.liHeight(),this.options.header&&this.$menu.css("padding-top",0),this.options.size!==!1){var b,c,d,e,f=this,g=this.$menu,h=this.$menuInner,i=a(window),j=this.$newElement[0].offsetHeight,k=this.sizeInfo.liHeight,l=this.sizeInfo.headerHeight,m=this.sizeInfo.searchHeight,n=this.sizeInfo.actionsHeight,o=this.sizeInfo.doneButtonHeight,p=this.sizeInfo.dividerHeight,q=this.sizeInfo.menuPadding,r=this.sizeInfo.menuExtras,s=this.options.hideDisabled?".disabled":"",t=function(){d=f.$newElement.offset().top-i.scrollTop(),e=i.height()-d-j};if(t(),"auto"===this.options.size){var u=function(){var i,j=function(b,c){return function(d){return c?d.classList?d.classList.contains(b):a(d).hasClass(b):!(d.classList?d.classList.contains(b):a(d).hasClass(b))}},p=f.$menuInner[0].getElementsByTagName("li"),s=Array.prototype.filter?Array.prototype.filter.call(p,j("hidden",!1)):f.$lis.not(".hidden"),u=Array.prototype.filter?Array.prototype.filter.call(s,j("dropdown-header",!0)):s.filter(".dropdown-header");t(),b=e-r,f.options.container?(g.data("height")||g.data("height",g.height()),c=g.data("height")):c=g.height(),f.options.dropupAuto&&f.$newElement.toggleClass("dropup",d>e&&c>b-r),f.$newElement.hasClass("dropup")&&(b=d-r),i=s.length+u.length>3?3*k+r-2:0,g.css({"max-height":b+"px",overflow:"hidden","min-height":i+l+m+n+o+"px"}),h.css({"max-height":b-l-m-n-o-q+"px","overflow-y":"auto","min-height":Math.max(i-q,0)+"px"})};u(),this.$searchbox.off("input.getSize propertychange.getSize").on("input.getSize propertychange.getSize",u),i.off("resize.getSize scroll.getSize").on("resize.getSize scroll.getSize",u)}else if(this.options.size&&"auto"!=this.options.size&&this.$lis.not(s).length>this.options.size){var v=this.$lis.not(".divider").not(s).children().slice(0,this.options.size).last().parent().index(),w=this.$lis.slice(0,v+1).filter(".divider").length;b=k*this.options.size+w*p+q,f.options.container?(g.data("height")||g.data("height",g.height()),c=g.data("height")):c=g.height(),f.options.dropupAuto&&this.$newElement.toggleClass("dropup",d>e&&c>b-r),g.css({"max-height":b+l+m+n+o+"px",overflow:"hidden","min-height":""}),h.css({"max-height":b-q+"px","overflow-y":"auto","min-height":""})}}},setWidth:function(){if("auto"===this.options.width){this.$menu.css("min-width","0");var a=this.$menu.parent().clone().appendTo("body"),b=this.options.container?this.$newElement.clone().appendTo("body"):a,c=a.children(".dropdown-menu").outerWidth(),d=b.css("width","auto").children("button").outerWidth();a.remove(),b.remove(),this.$newElement.css("width",Math.max(c,d)+"px")}else"fit"===this.options.width?(this.$menu.css("min-width",""),this.$newElement.css("width","").addClass("fit-width")):this.options.width?(this.$menu.css("min-width",""),this.$newElement.css("width",this.options.width)):(this.$menu.css("min-width",""),this.$newElement.css("width",""));this.$newElement.hasClass("fit-width")&&"fit"!==this.options.width&&this.$newElement.removeClass("fit-width")},selectPosition:function(){var b,c,d=this,e="<div />",f=a(e),g=function(a){f.addClass(a.attr("class").replace(/form-control|fit-width/gi,"")).toggleClass("dropup",a.hasClass("dropup")),b=a.offset(),c=a.hasClass("dropup")?0:a[0].offsetHeight,f.css({top:b.top+c,left:b.left,width:a[0].offsetWidth,position:"absolute"})};this.$newElement.on("click",function(){d.isDisabled()||(g(a(this)),f.appendTo(d.options.container),f.toggleClass("open",!a(this).hasClass("open")),f.append(d.$menu))}),a(window).on("resize scroll",function(){g(d.$newElement)}),this.$element.on("hide.bs.select",function(){d.$menu.data("height",d.$menu.height()),f.detach()})},setSelected:function(a,b,c){if(!c)var c=this.findLis().eq(this.liObj[a]);c.toggleClass("selected",b)},setDisabled:function(a,b,c){if(!c)var c=this.findLis().eq(this.liObj[a]);b?c.addClass("disabled").children("a").attr("href","#").attr("tabindex",-1):c.removeClass("disabled").children("a").removeAttr("href").attr("tabindex",0)},isDisabled:function(){return this.$element[0].disabled},checkDisabled:function(){var a=this;this.isDisabled()?(this.$newElement.addClass("disabled"),this.$button.addClass("disabled").attr("tabindex",-1)):(this.$button.hasClass("disabled")&&(this.$newElement.removeClass("disabled"),this.$button.removeClass("disabled")),-1!=this.$button.attr("tabindex")||this.$element.data("tabindex")||this.$button.removeAttr("tabindex")),this.$button.click(function(){return!a.isDisabled()})},tabIndex:function(){this.$element.is("[tabindex]")&&(this.$element.data("tabindex",this.$element.attr("tabindex")),this.$button.attr("tabindex",this.$element.data("tabindex")))},clickListener:function(){var b=this,c=a(document);this.$newElement.on("touchstart.dropdown",".dropdown-menu",function(a){a.stopPropagation()}),c.data("spaceSelect",!1),this.$button.on("keyup",function(a){/(32)/.test(a.keyCode.toString(10))&&c.data("spaceSelect")&&(a.preventDefault(),c.data("spaceSelect",!1))}),this.$newElement.on("click",function(){b.setSize(),b.$element.on("shown.bs.select",function(){if(b.options.liveSearch||b.multiple){if(!b.multiple){var a=b.liObj[b.$element[0].selectedIndex];if("number"!=typeof a||b.options.size===!1)return;var c=b.$lis.eq(a)[0].offsetTop-b.$menuInner[0].offsetTop;c=c-b.$menuInner[0].offsetHeight/2+b.sizeInfo.liHeight/2,b.$menuInner[0].scrollTop=c}}else b.$menu.find(".selected a").focus()})}),this.$menu.on("click","li a",function(c){var d=a(this),e=d.parent().data("originalIndex"),f=b.$element.val(),g=b.$element.prop("selectedIndex");if(b.multiple&&c.stopPropagation(),c.preventDefault(),!b.isDisabled()&&!d.parent().hasClass("disabled")){var h=b.$element.find("option"),i=h.eq(e),j=i.prop("selected"),k=i.parent("optgroup"),l=b.options.maxOptions,m=k.data("maxOptions")||!1;if(b.multiple){if(i.prop("selected",!j),b.setSelected(e,!j),d.blur(),l!==!1||m!==!1){var n=l<h.filter(":selected").length,o=m<k.find("option:selected").length;if(l&&n||m&&o)if(l&&1==l)h.prop("selected",!1),i.prop("selected",!0),b.$menu.find(".selected").removeClass("selected"),b.setSelected(e,!0);else if(m&&1==m){k.find("option:selected").prop("selected",!1),i.prop("selected",!0);var p=d.parent().data("optgroup");b.$menu.find('[data-optgroup="'+p+'"]').removeClass("selected"),b.setSelected(e,!0)}else{var q="function"==typeof b.options.maxOptionsText?b.options.maxOptionsText(l,m):b.options.maxOptionsText,r=q[0].replace("{n}",l),s=q[1].replace("{n}",m),t=a('<div class="notify"></div>');q[2]&&(r=r.replace("{var}",q[2][l>1?0:1]),s=s.replace("{var}",q[2][m>1?0:1])),i.prop("selected",!1),b.$menu.append(t),l&&n&&(t.append(a("<div>"+r+"</div>")),b.$element.trigger("maxReached.bs.select")),m&&o&&(t.append(a("<div>"+s+"</div>")),b.$element.trigger("maxReachedGrp.bs.select")),setTimeout(function(){b.setSelected(e,!1)},10),t.delay(750).fadeOut(300,function(){a(this).remove()})}}}else h.prop("selected",!1),i.prop("selected",!0),b.$menu.find(".selected").removeClass("selected"),b.setSelected(e,!0);b.multiple?b.options.liveSearch&&b.$searchbox.focus():b.$button.focus(),(f!=b.$element.val()&&b.multiple||g!=b.$element.prop("selectedIndex")&&!b.multiple)&&(b.$element.triggerNative("change"),b.$element.trigger("changed.bs.select",[e,i.prop("selected"),j]))}}),this.$menu.on("click","li.disabled a, .popover-title, .popover-title :not(.close)",function(c){c.currentTarget==this&&(c.preventDefault(),c.stopPropagation(),b.options.liveSearch&&!a(c.target).hasClass("close")?b.$searchbox.focus():b.$button.focus())}),this.$menu.on("click","li.divider, li.dropdown-header",function(a){a.preventDefault(),a.stopPropagation(),b.options.liveSearch?b.$searchbox.focus():b.$button.focus()}),this.$menu.on("click",".popover-title .close",function(){b.$button.click()}),this.$searchbox.on("click",function(a){a.stopPropagation()}),this.$menu.on("click",".actions-btn",function(c){b.options.liveSearch?b.$searchbox.focus():b.$button.focus(),c.preventDefault(),c.stopPropagation(),a(this).hasClass("bs-select-all")?b.selectAll():b.deselectAll(),b.$element.triggerNative("change")}),this.$element.change(function(){b.render(!1)})},liveSearchListener:function(){var d=this,e=a('<li class="no-results"></li>');this.$newElement.on("click.dropdown.data-api touchstart.dropdown.data-api",function(){d.$menuInner.find(".active").removeClass("active"),d.$searchbox.val()&&(d.$searchbox.val(""),d.$lis.not(".is-hidden").removeClass("hidden"),e.parent().length&&e.remove()),d.multiple||d.$menuInner.find(".selected").addClass("active"),setTimeout(function(){d.$searchbox.focus()},10)}),this.$searchbox.on("click.dropdown.data-api focus.dropdown.data-api touchend.dropdown.data-api",function(a){a.stopPropagation()}),this.$searchbox.on("input propertychange",function(){if(d.$searchbox.val()){var f=d.$lis.not(".is-hidden").removeClass("hidden").children("a");f=d.options.liveSearchNormalize?f.not(":a"+d._searchStyle()+'("'+b(d.$searchbox.val())+'")'):f.not(":"+d._searchStyle()+'("'+d.$searchbox.val()+'")'),f.parent().addClass("hidden"),d.$lis.filter(".dropdown-header").each(function(){var b=a(this),c=b.data("optgroup");0===d.$lis.filter("[data-optgroup="+c+"]").not(b).not(".hidden").length&&(b.addClass("hidden"),d.$lis.filter("[data-optgroup="+c+"div]").addClass("hidden"))});var g=d.$lis.not(".hidden");g.each(function(b){var c=a(this);c.hasClass("divider")&&(c.index()===g.eq(0).index()||c.index()===g.last().index()||g.eq(b+1).hasClass("divider"))&&c.addClass("hidden")}),d.$lis.not(".hidden, .no-results").length?e.parent().length&&e.remove():(e.parent().length&&e.remove(),e.html(d.options.noneResultsText.replace("{0}",'"'+c(d.$searchbox.val())+'"')).show(),d.$menuInner.append(e))}else d.$lis.not(".is-hidden").removeClass("hidden"),e.parent().length&&e.remove();d.$lis.filter(".active").removeClass("active"),d.$searchbox.val()&&d.$lis.not(".hidden, .divider, .dropdown-header").eq(0).addClass("active").children("a").focus(),a(this).focus()})},_searchStyle:function(){var a="icontains";switch(this.options.liveSearchStyle){case"begins":case"startsWith":a="ibegins";break;case"contains":}return a},val:function(a){return"undefined"!=typeof a?(this.$element.val(a),this.render(),this.$element):this.$element.val()},selectAll:function(){this.findLis(),this.$element.find("option:enabled").not("[data-divider], [data-hidden]").prop("selected",!0),this.$lis.not(".divider, .dropdown-header, .disabled, .hidden").addClass("selected"),this.render(!1)},deselectAll:function(){this.findLis(),this.$element.find("option:enabled").not("[data-divider], [data-hidden]").prop("selected",!1),this.$lis.not(".divider, .dropdown-header, .disabled, .hidden").removeClass("selected"),this.render(!1)},keydown:function(c){var d,e,f,g,h,i,j,k,l,m=a(this),n=m.is("input")?m.parent().parent():m.parent(),o=n.data("this"),p=":not(.disabled, .hidden, .dropdown-header, .divider)",q={32:" ",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",59:";",65:"a",66:"b",67:"c",68:"d",69:"e",70:"f",71:"g",72:"h",73:"i",74:"j",75:"k",76:"l",77:"m",78:"n",79:"o",80:"p",81:"q",82:"r",83:"s",84:"t",85:"u",86:"v",87:"w",88:"x",89:"y",90:"z",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9"};if(o.options.liveSearch&&(n=m.parent().parent()),o.options.container&&(n=o.$menu),d=a("[role=menu] li a",n),l=o.$menu.parent().hasClass("open"),!l&&(c.keyCode>=48&&c.keyCode<=57||c.keyCode>=65&&c.keyCode<=90)&&(o.options.container?o.$newElement.trigger("click"):(o.setSize(),o.$menu.parent().addClass("open"),l=!0),o.$searchbox.focus()),o.options.liveSearch&&(/(^9$|27)/.test(c.keyCode.toString(10))&&l&&0===o.$menu.find(".active").length&&(c.preventDefault(),o.$menu.parent().removeClass("open"),o.options.container&&o.$newElement.removeClass("open"),o.$button.focus()),d=a("[role=menu] li:not(.disabled, .hidden, .dropdown-header, .divider)",n),m.val()||/(38|40)/.test(c.keyCode.toString(10))||0===d.filter(".active").length&&(d=o.$newElement.find("li"),d=o.options.liveSearchNormalize?d.filter(":a"+o._searchStyle()+"("+b(q[c.keyCode])+")"):d.filter(":"+o._searchStyle()+"("+q[c.keyCode]+")"))),d.length){if(/(38|40)/.test(c.keyCode.toString(10)))e=d.index(d.filter(":focus")),g=d.parent(p).first().data("originalIndex"),h=d.parent(p).last().data("originalIndex"),f=d.eq(e).parent().nextAll(p).eq(0).data("originalIndex"),i=d.eq(e).parent().prevAll(p).eq(0).data("originalIndex"),j=d.eq(f).parent().prevAll(p).eq(0).data("originalIndex"),o.options.liveSearch&&(d.each(function(b){a(this).hasClass("disabled")||a(this).data("index",b)}),e=d.index(d.filter(".active")),g=d.first().data("index"),h=d.last().data("index"),f=d.eq(e).nextAll().eq(0).data("index"),i=d.eq(e).prevAll().eq(0).data("index"),j=d.eq(f).prevAll().eq(0).data("index")),k=m.data("prevIndex"),38==c.keyCode?(o.options.liveSearch&&(e-=1),e!=j&&e>i&&(e=i),g>e&&(e=g),e==k&&(e=h)):40==c.keyCode&&(o.options.liveSearch&&(e+=1),-1==e&&(e=0),e!=j&&f>e&&(e=f),e>h&&(e=h),e==k&&(e=g)),m.data("prevIndex",e),o.options.liveSearch?(c.preventDefault(),m.hasClass("dropdown-toggle")||(d.removeClass("active").eq(e).addClass("active").children("a").focus(),m.focus())):d.eq(e).focus();else if(!m.is("input")){var r,s,t=[];d.each(function(){a(this).parent().hasClass("disabled")||a.trim(a(this).text().toLowerCase()).substring(0,1)==q[c.keyCode]&&t.push(a(this).parent().index())}),r=a(document).data("keycount"),r++,a(document).data("keycount",r),s=a.trim(a(":focus").text().toLowerCase()).substring(0,1),s!=q[c.keyCode]?(r=1,a(document).data("keycount",r)):r>=t.length&&(a(document).data("keycount",0),r>t.length&&(r=1)),d.eq(t[r-1]).focus()}if((/(13|32)/.test(c.keyCode.toString(10))||/(^9$)/.test(c.keyCode.toString(10))&&o.options.selectOnTab)&&l){if(/(32)/.test(c.keyCode.toString(10))||c.preventDefault(),o.options.liveSearch)/(32)/.test(c.keyCode.toString(10))||(o.$menu.find(".active a").click(),m.focus());else{var u=a(":focus");u.click(),u.focus(),c.preventDefault(),a(document).data("spaceSelect",!0)}a(document).data("keycount",0)}(/(^9$|27)/.test(c.keyCode.toString(10))&&l&&(o.multiple||o.options.liveSearch)||/(27)/.test(c.keyCode.toString(10))&&!l)&&(o.$menu.parent().removeClass("open"),o.options.container&&o.$newElement.removeClass("open"),o.$button.focus())}},mobile:function(){this.$element.addClass("mobile-device").appendTo(this.$newElement),this.options.container&&this.$menu.hide()},refresh:function(){this.$lis=null,this.liObj={},this.reloadLi(),this.render(),this.checkDisabled(),this.liHeight(!0),this.setStyle(),this.setWidth(),this.$lis&&this.$searchbox.trigger("propertychange"),this.$element.trigger("refreshed.bs.select")},hide:function(){this.$newElement.hide()},show:function(){this.$newElement.show()},remove:function(){this.$newElement.remove(),this.$element.remove()}};var f=a.fn.selectpicker;a.fn.selectpicker=d,a.fn.selectpicker.Constructor=e,a.fn.selectpicker.noConflict=function(){return a.fn.selectpicker=f,this},a(document).data("keycount",0).on("keydown",'.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="menu"], .bs-searchbox input',e.prototype.keydown).on("focusin.modal",'.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="menu"], .bs-searchbox input',function(a){a.stopPropagation()}),a(window).on("load.bs.select.data-api",function(){a(".selectpicker").each(function(){var b=a(this);d.call(b,b.data())})})}(jQuery)});
//# sourceMappingURL=bootstrap-select.js.map
\ No newline at end of file
...@@ -9,98 +9,104 @@ ...@@ -9,98 +9,104 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Gargantext corpus annotations editor</title> <title>Gargantext article editor</title>
<meta name="description" content=""> <meta name="description" content="Gargantext">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="{% static 'bower_components/bootstrap/dist/css/bootstrap.min.css' %}"> <link rel="stylesheet" href="{% static 'bower_components/bootstrap/dist/css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'bower_components/bootstrap-select/dist/css/bootstrap-select.min.css' %}">
<link rel="stylesheet" href="{% static 'bower_components/angular/angular-csp.css' %}"> <link rel="stylesheet" href="{% static 'bower_components/angular/angular-csp.css' %}">
<link rel="stylesheet" href="{% static 'annotations/app.css' %}"> <link rel="stylesheet" href="{% static 'annotations/app.css' %}">
<script src="{% static 'bower_components/jquery/dist/jquery.min.js' %}"></script> <script src="{% static 'bower_components/jquery/dist/jquery.min.js' %}"></script>
</head> </head>
<body> <body>
<!-- TODO integrate this later into the corpus.html django template --> <!-- TODO integrate this later into the any other django template -->
<div id="annotationsApp"> <div id="annotationsApp" ng-cloak>
<div class="container-fluid"> <div class="container-fluid">
<div class="row-fluid main-panel" ng-controller="IntraTextController"> <div class="row-fluid main-panel" ng-controller="NGramHighlightController">
<div class="col-md-4 col-xs-4 tabbable words-panel"> <div class="col-md-4 col-xs-4 tabbable words-panel">
<ul class="nav nav-pills nav-justified"> <ul class="nav nav-pills nav-justified">
<li class="active"><a href="#tab1" data-toggle="tab">Miamwords</a></li> <li ng-repeat="(listId, listName) in activeLists" ng-class="{active: $first == true}">
<li><a href="#tab2" data-toggle="tab">Stopwords</a></li> <a href="#tab-{[{listId}]}" data-toggle="tab">{[{listName}]}</a>
</li>
</ul> </ul>
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane active" id="tab1"> <div ng-controller="NgramListPaginationController" ng-repeat="(listId, listName) in activeLists" ng-class="{active: $first == true}" class="tab-pane" id="tab-{[{listId}]}">
<div ng-if="extra_miamlist.length == 0" class="alert alert-info" role="alert">No extra text miam-word yet</div> <div ng-if="extraNgramList[listId].length == 0" class="alert alert-info" role="alert">
<ul class="list-group words-list"> Input any keyword you want to link to this article and the list named '{[{listName}]}'
<li ng-repeat="keyword in extra_miamlist | startFrom:currentMiamPage*pageSize | limitTo:pageSize" class="list-group-item">
<div ng-controller="ExtraAnnotationController" keyword-template class="keyword-container"></div>
</li>
</ul>
<nav ng-class="{invisible: numMiamPages() - 1 == 0}" class="clearfix">
<ul class="pagination pagination-s pull-right words-pagination">
<li ng-class="{disabled: currentMiamPage == 0}"><a ng-click="previousMiamPage()" class="glyphicon glyphicon-backward"></a></li>
<li ng-class="{disabled: currentMiamPage >= numMiamPages()-1}"><a ng-click="nextMiamPage()" class="glyphicon glyphicon-forward"></a></li>
</ul>
</nav>
<div class="form-group">
<input type="text" class="form-control" id="miamlist-input" ng-keypress="onMiamlistSubmit($event)">
<button type="submit" class="btn btn-default btn-primary" ng-click="onMiamlistSubmit($event)">Add</button>
</div> </div>
</div>
<div class="tab-pane" id="tab2"> <ul class="list-group words-list clearfix">
<div ng-if="extra_stoplist.length == 0" class="alert alert-info" role="alert">No extra text stop-word yet</div> <li ng-repeat="keyword in extraNgramList[listId] | startFrom:currentListPage * pageSize | limitTo:pageSize" class="keyword-group-item">
<ul class="list-group words-list"> <div ng-controller="NgramController" keyword-template class="keyword-container"></div>
<li ng-repeat="keyword in extra_stoplist | startFrom:currentStopPage*pageSize | limitTo:pageSize" class="list-group-item">
<div ng-controller="ExtraAnnotationController" keyword-template class="keyword-container"></div>
</li> </li>
</ul> </ul>
<nav ng-class="{invisible: numStopPages() - 1 == 0}" class="clearfix">
<nav ng-class="{invisible: totalListPages(listId) - 1 == 0}" class="clearfix">
<ul class="pagination pagination-s pull-right words-pagination"> <ul class="pagination pagination-s pull-right words-pagination">
<li ng-class="{disabled: currentStopPage == 0}"><a ng-click="previousMiamPage()" class="glyphicon glyphicon-backward"></a></li> <li ng-class="{'disabled': currentListPage == 0}"><a ng-click="previousListPage()" class="glyphicon glyphicon-backward"></a></li>
<li ng-class="{disabled: currentStopPage >= numStopPages()-1}"><a ng-click="nextStopPage()" class="glyphicon glyphicon-forward"></a></li> <li ng-class="{'disabled': currentListPage >= totalListPages(listId) - 1}"><a ng-click="nextListPage()" class="glyphicon glyphicon-forward"></a></li>
</ul> </ul>
</nav> </nav>
<div class="form-group">
<input type="text" class="form-control" id="stoplist-input" ng-keypress="onStoplistSubmit($event)"> <div class="form-group" ng-controller="NgramInputController">
<button type="submit" class="btn btn-default btn-primary" ng-click="onStoplistSubmit($event)">Add</button> <input autosave="search" maxlength="240" placeholder="Add any text" type="text" class="form-control" id="{[{listId}]}-input" ng-keypress="onListSubmit($event, listId)">
<button type="submit" class="form-control btn btn-default btn-primary" ng-click="onListSubmit($event, listId)">Add to {[{listName}]}</button>
</div> </div>
</div> </div>
</div> </div>
<div>
<h5>Select lists</h5>
<select class="selectpicker" multiple ng-change="activeListsChange()" ng-model="lists" ng-controller="ActiveListsController">
<option ng-repeat="item in allListsSelect" id="list---{[{item.id}]}" ng-disabled="{[{ item.label == 'MiamList' }]}">{[{item.label}]}</option>
</select>
</div>
</div> </div>
<div class="col-md-8 col-xs-8 text-panel" ng-controller="DocController" id="document"> <div class="col-md-8 col-xs-8 text-panel" ng-controller="DocController" id="document">
<div class="row-fluid clearfix"> <div class="row-fluid clearfix">
<div class="col-md-7 col-xs-7"> <div class="col-md-10 col-xs-10">
<h3>{[{title}]}</h3> <h3 class="text-container" id="title">{[{title}]}</h3>
</div> </div>
<div class="col-md-5 col-xs-5"> <div class="col-md-2 col-xs-2 clearfix">
<nav> <button ng-controller="DocFavoriteController" type="button" class="btn btn-default float-right" ng-click="onStarClick($event)">
<span class="glyphicon" ng-class="{'glyphicon-star-empty': isFavorite == false, 'glyphicon-star': isFavorite == true}"></span>
</button>
<!--<nav>
<ul class="pager"> <ul class="pager">
<li ng-if="current_page_number > 1"><a ng-click="onPreviousClick()" href="#">Previous</a></li> <li ng-if="current_page_number > 1"><a ng-click="onPreviousClick()" href="#">Previous</a></li>
<li ng-if="current_page_number < last_page_number"><a ng-click="onNextClick()" href="#">Next</a></li> <li ng-if="current_page_number < last_page_number"><a ng-click="onNextClick()" href="#">Next</a></li>
</ul> </ul>
</nav> </nav>-->
</div> </div>
</div> </div>
<div class="row-fluid cleafix"> <div class="row-fluid">
<ul class="breadcrumb"> <ul class="list-group clearfix">
<li>{[{authors}]}</li> <li class="list-group-item small"><span class="badge">journal</span>{[{journal}]}</li>
<li>{[{journal}]}</li> <li class="list-group-item small"><span class="badge">authors</span>{[{authors}]}</li>
<li class="active pull-right">{[{publication_date}]}</li> <li class="list-group-item small"><span class="badge">date</span>{[{publication_date}]}</li>
</ul> </ul>
</div> </div>
<h4 ng-if="abstract_text != null">Abstract</h4> <div ng-if="abstract_text != null">
<span class="badge">abstract</span>
</div>
<p id="abstract-text" class="text-container"> <p id="abstract-text" class="text-container">
<div ng-if="abstract_text == null" class="alert alert-info" role="alert">No abstract text</div> <div ng-if="abstract_text == null" class="alert alert-info small" role="alert">Empty abstract text</div>
</p> </p>
<h4 ng-if="full_text != null">Full Article</h4> <div ng-if="full_text != null">
<span class="badge">full article</span>
</div>
<p id="full-text" class="text-container"> <p id="full-text" class="text-container">
<div ng-if="full_text == null" class="alert alert-info" role="alert">No full text</div> <div ng-if="full_text == null" class="alert alert-info small" role="alert">Empty full text</div>
</p> </p>
</div> </div>
</div> <!-- end of the main row --> </div> <!-- end of the main row -->
</div> </div>
<!-- this menu is over the text --> <!-- this menu is over the text on mouse selection -->
<div ng-controller="AnnotationMenuController" id="selection" class="selection-menu" selection-template></div> <div ng-controller="TextSelectionMenuController" id="selection" class="selection-menu">
<ul class="noselection">
<li ng-repeat="item in menuItems" class="{[{item.listName}]}" ng-click="onMenuClick($event, item.action, item.listId)">{[{item.verb}]} {[{item.listName}]}</li>
</ul>
</div>
</div> </div>
<!--[if lt IE 7]> <!--[if lt IE 7]>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p> <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
......
...@@ -5,6 +5,7 @@ from annotations import views ...@@ -5,6 +5,7 @@ from annotations import views
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^document/(?P<doc_id>[0-9]+)$', views.Document.as_view()), # document view url(r'^document/(?P<doc_id>[0-9]+)$', views.Document.as_view()), # document view
url(r'^corpus/(?P<corpus_id>[0-9]+)/document/(?P<doc_id>[0-9]+)$', views.NgramList.as_view()), # the list associated with an ngram url(r'^corpus/(?P<corpus_id>[0-9]+)/document/(?P<doc_id>[0-9]+)$', views.NgramList.as_view()), # the list associated with an ngram
url(r'^lists/(?P<list_id>[0-9]+)/ngrams(?:/(?P<ngram_id>[0-9]+))?$', views.NgramEdit.as_view()), # url(r'^lists/(?P<list_id>[0-9]+)/ngrams/(?P<ngram_id>[0-9]+)$', views.NgramEdit.as_view()), #
url(r'^lists/(?P<list_id>[0-9]+)/multiple?$', views.deleteMultiple ), # url(r'^lists/(?P<list_id>[0-9]+)/ngrams/create$', views.NgramCreate.as_view()), #
url(r'^lists/(?P<list_id>[0-9]+)/multiple?$', views.deleteMultiple ), # FIXME What's this ?
) )
...@@ -17,7 +17,6 @@ from node.models import Node ...@@ -17,7 +17,6 @@ from node.models import Node
from gargantext_web.db import * from gargantext_web.db import *
from ngram.lists import listIds, listNgramIds, ngramList from ngram.lists import listIds, listNgramIds, ngramList
from gargantext_web.api import JsonHttpResponse from gargantext_web.api import JsonHttpResponse
import json
@login_required @login_required
...@@ -40,16 +39,13 @@ class NgramList(APIView): ...@@ -40,16 +39,13 @@ class NgramList(APIView):
"""Get All for a doc id""" """Get All for a doc id"""
corpus_id = int(corpus_id) corpus_id = int(corpus_id)
doc_id = int(doc_id) doc_id = int(doc_id)
lists = dict() lists = {}
for list_type in ['MiamList', 'StopList']: for list_type in ['MiamList', 'StopList']:
list_id = list()
list_id = listIds(user_id=request.user.id, corpus_id=int(corpus_id), typeList=list_type) list_id = listIds(user_id=request.user.id, corpus_id=int(corpus_id), typeList=list_type)
lists["%s" % list_id[0][0]] = list_type lists["%s" % list_id[0][0]] = list_type
# ngrams of list_id of corpus_id: # ngrams for the corpus_id (ignoring doc_id for the moment):
doc_ngram_list = listNgramIds(corpus_id=corpus_id, doc_id=doc_id, user_id=request.user.id) doc_ngram_list = listNgramIds(corpus_id=corpus_id, doc_id=None, user_id=request.user.id)
#doc_ngram_list = [(1, 'miam', 2, 1931), (2, 'stop', 2, 1932), (3, 'Potassium channels', 4, 1931)]
data = { '%s' % corpus_id : { data = { '%s' % corpus_id : {
'%s' % doc_id : [ '%s' % doc_id : [
{ {
...@@ -66,50 +62,83 @@ class NgramList(APIView): ...@@ -66,50 +62,83 @@ class NgramList(APIView):
class NgramEdit(APIView): class NgramEdit(APIView):
""" """
Actions on one Ngram in one list Actions on one existing Ngram in one list
""" """
renderer_classes = (JSONRenderer,) renderer_classes = (JSONRenderer,)
authentication_classes = (SessionAuthentication, BasicAuthentication) authentication_classes = (SessionAuthentication, BasicAuthentication)
def post(self, request, list_id, ngram_id): def post(self, request, list_id, ngram_id):
""" """
Add a ngram in a list Edit an existing NGram in a given list
"""
list_id = int(list_id)
ngram_id = int(ngram_id)
# TODO remove the node_ngram from another conflicting list
# FIXME session.query(Node_Ngram).filter(Node_Ngram.ngram_id==ngram_id).delete()
# add the ngram to the list
node_ngram = Node_Ngram(node_id=list_id, ngram_id=ngram_id, weight=1.0)
session.add(node_ngram)
session.commit()
# return the response
return Response({
'uuid': ngram_id,
'list_id': list_id,
})
def delete(self, request, list_id, ngram_id):
"""
Delete a ngram from a list
"""
session.query(Node_Ngram).filter(Node_Ngram.node_id==list_id).filter(Node_Ngram.ngram_id==ngram_id).delete()
session.commit()
return Response(None, 204)
class NgramCreate(APIView):
"""
Create a new Ngram in one list
"""
renderer_classes = (JSONRenderer,)
authentication_classes = (SessionAuthentication, BasicAuthentication)
def post(self, request, list_id):
"""
create NGram in a given list
""" """
# TODO - if Ngram is in miam-list, and adding it to stop-list,
# then remove it from the previous list
list_id = int(list_id) list_id = int(list_id)
# format the ngram's text # format the ngram's text
ngram_text = request.data.get('annotation', {}).get('text', None) ngram_text = request.data.get('text', None)
if ngram_text is None:
raise APIException("Could not create a new Ngram without one \
text key in the json body")
ngram_text = ngram_text.strip().lower() ngram_text = ngram_text.strip().lower()
ngram_text = ' '.join(ngram_text.split()) ngram_text = ' '.join(ngram_text.split())
# retrieve the ngram's id # check if the ngram exists with the same terms
ngram = session.query(Ngram).filter(Ngram.terms == ngram_text).first() ngram = session.query(Ngram).filter(Ngram.terms == ngram_text).first()
if ngram is None: if ngram is None:
ngram = Ngram(n=len(ngram_text.split()), terms=ngram_text) ngram = Ngram(n=len(ngram_text.split()), terms=ngram_text)
session.add(ngram) else:
session.commit() # make sure the n value is correct
ngram.n = len(ngram_text.split())
session.add(ngram)
session.commit()
ngram_id = ngram.id ngram_id = ngram.id
# add the ngram to the list if not already done # create the new node_ngram relation
node_ngram = session.query(Node_Ngram).filter(Node_Ngram.node_id==list_id).filter(Node_Ngram.ngram_id==ngram_id).first() # TODO check existing Node_Ngram ?
if node_ngram is None: node_ngram = Node_Ngram(node_id=list_id, ngram_id=ngram_id, weight=1.0)
node_ngram = Node_Ngram(node_id=list_id, ngram_id=ngram_id, weight=1.0) session.add(node_ngram)
session.add(node_ngram) session.commit()
session.commit()
ngram_occurrences = node_ngram.weight
# return the response # return the response
return Response({ return Response({
'uuid': ngram_id, 'uuid': ngram_id,
'text': ngram_text, 'text': ngram_text,
'occurrences': ngram_occurrences,
'list_id': list_id, 'list_id': list_id,
}) })
def delete(self, request, list_id, ngram_id):
"""
Delete a ngram from a list
"""
session.query(Node_Ngram).filter(Node_Ngram.node_id==list_id).filter(Node_Ngram.ngram_id==ngram_id).delete()
return Response(None, 204)
def deleteMultiple(request, list_id): def deleteMultiple(request, list_id):
results = ["hola","mundo"] results = ["hola","mundo"]
......
...@@ -3,21 +3,33 @@ from django.core.exceptions import PermissionDenied, SuspiciousOperation ...@@ -3,21 +3,33 @@ from django.core.exceptions import PermissionDenied, SuspiciousOperation
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from sqlalchemy import text, distinct, or_ from sqlalchemy import text, distinct, or_,not_
from sqlalchemy.sql import func from sqlalchemy.sql import func
from sqlalchemy.orm import aliased from sqlalchemy.orm import aliased
import datetime
import copy
from gargantext_web.views import move_to_trash from gargantext_web.views import move_to_trash
from gargantext_web.db import * from gargantext_web.db import *
from gargantext_web.validation import validate, ValidationException
from node import models from node import models
def DebugHttpResponse(data): def DebugHttpResponse(data):
return HttpResponse('<html><body style="background:#000;color:#FFF"><pre>%s</pre></body></html>' % (str(data), )) return HttpResponse('<html><body style="background:#000;color:#FFF"><pre>%s</pre></body></html>' % (str(data), ))
import json import json
class JSONEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime.datetime):
return obj.isoformat()[:19] + 'Z'
else:
return super(self.__class__, self).default(obj)
json_encoder = JSONEncoder(indent=4)
def JsonHttpResponse(data, status=200): def JsonHttpResponse(data, status=200):
return HttpResponse( return HttpResponse(
content = json.dumps(data, indent=4), content = json_encoder.encode(data),
content_type = 'application/json; charset=utf-8', content_type = 'application/json; charset=utf-8',
status = status status = status
) )
...@@ -54,16 +66,25 @@ class APIException(_APIException): ...@@ -54,16 +66,25 @@ class APIException(_APIException):
self.detail = message self.detail = message
_operators = { _operators_dict = {
"=": lambda field, value: (field == value), "=": lambda field, value: (field == value),
"!=": lambda field, value: (field != value), "!=": lambda field, value: (field != value),
"<": lambda field, value: (field < value), "<": lambda field, value: (field < value),
">": lambda field, value: (field > value), ">": lambda field, value: (field > value),
"<=": lambda field, value: (field <= value), "<=": lambda field, value: (field <= value),
">=": lambda field, value: (field >= value), ">=": lambda field, value: (field >= value),
"in": lambda field, value: (or_(*tuple(field == x for x in value))), "in": lambda field, value: (or_(*tuple(field == x for x in value))),
"contains": lambda field, value: (field.contains(value)), "contains": lambda field, value: (field.contains(value)),
"startswith": lambda field, value: (field.startswith(value)), "doesnotcontain": lambda field, value: (not_(field.contains(value))),
"startswith": lambda field, value: (field.startswith(value)),
}
_hyperdata_list = [
hyperdata
for hyperdata in session.query(Hyperdata).order_by(Hyperdata.name)
]
_hyperdata_dict = {
hyperdata.name: hyperdata
for hyperdata in _hyperdata_list
} }
from rest_framework.decorators import api_view from rest_framework.decorators import api_view
...@@ -75,6 +96,7 @@ def Root(request, format=None): ...@@ -75,6 +96,7 @@ def Root(request, format=None):
'snippets': reverse('snippet-list', request=request, format=format) 'snippets': reverse('snippet-list', request=request, format=format)
}) })
class NodesChildrenNgrams(APIView): class NodesChildrenNgrams(APIView):
def get(self, request, node_id): def get(self, request, node_id):
...@@ -96,6 +118,8 @@ class NodesChildrenNgrams(APIView): ...@@ -96,6 +118,8 @@ class NodesChildrenNgrams(APIView):
ngrams_query = ngrams_query.filter(Ngram.terms.startswith(request.GET['startwith'])) ngrams_query = ngrams_query.filter(Ngram.terms.startswith(request.GET['startwith']))
if 'contain' in request.GET: if 'contain' in request.GET:
ngrams_query = ngrams_query.filter(Ngram.terms.contains(request.GET['contain'])) ngrams_query = ngrams_query.filter(Ngram.terms.contains(request.GET['contain']))
#if 'doesnotcontain' in request.GET:
# ngrams_query = ngrams_query.filter(not_(Ngram.terms.contains(request.GET['doesnotcontain'])))
# pagination # pagination
offset = int(request.GET.get('offset', 0)) offset = int(request.GET.get('offset', 0))
limit = int(request.GET.get('limit', 20)) limit = int(request.GET.get('limit', 20))
...@@ -117,6 +141,7 @@ class NodesChildrenNgrams(APIView): ...@@ -117,6 +141,7 @@ class NodesChildrenNgrams(APIView):
], ],
}) })
class NodesChildrenDuplicates(APIView): class NodesChildrenDuplicates(APIView):
def _fetch_duplicates(self, request, node_id, extra_columns=None, min_count=1): def _fetch_duplicates(self, request, node_id, extra_columns=None, min_count=1):
...@@ -205,6 +230,7 @@ class NodesChildrenDuplicates(APIView): ...@@ -205,6 +230,7 @@ class NodesChildrenDuplicates(APIView):
'deleted': count 'deleted': count
}) })
class NodesChildrenMetatadata(APIView): class NodesChildrenMetatadata(APIView):
def get(self, request, node_id): def get(self, request, node_id):
...@@ -264,49 +290,112 @@ class NodesChildrenMetatadata(APIView): ...@@ -264,49 +290,112 @@ class NodesChildrenMetatadata(APIView):
'data': collection, 'data': collection,
}) })
class NodesChildrenQueries(APIView): class NodesChildrenQueries(APIView):
def _parse_filter(self, filter): def _sql(self, input, node_id):
fields = dict()
# validate filter keys tables = set('nodes')
filter_keys = {'field', 'operator', 'value'} hyperdata_aliases = dict()
if set(filter) != filter_keys: # retrieve all unique fields names
raise APIException('Every filter should have exactly %d keys: "%s"'% (len(filter_keys), '", "'.join(filter_keys)), 400) fields_names = input['retrieve']['fields'].copy()
field, operator, value = filter['field'], filter['operator'], filter['value'] fields_names += [filter['field'] for filter in input['filters']]
fields_names += input['sort']
# validate operator fields_names = set(fields_names)
if operator not in _operators: # relate fields to their respective ORM counterparts
raise APIException('Invalid operator: "%s"'% (operator, ), 400) for field_name in fields_names:
field_name_parts = field_name.split('.')
# validate value, depending on the operator field = None
if operator == 'in': if len(field_name_parts) == 1 :
if not isinstance(value, list): field = getattr(Node, field_name)
raise APIException('Parameter "value" should be an array when using operator "%s"'% (operator, ), 400) elif field_name_parts[1] == 'count':
for v in value: if field_name_parts[0] == 'nodes':
if not isinstance(v, (int, float, str)): field = func.count(Node.id)
raise APIException('Parameter "value" should be an array of numbers or strings when using operator "%s"'% (operator, ), 400) elif field_name_parts[0] == 'ngrams':
else: field = func.count(Ngram.id)
if not isinstance(value, (int, float, str)): tables.add('ngrams')
raise APIException('Parameter "value" should be a number or string when using operator "%s"'% (operator, ), 400) elif field_name_parts[0] == 'ngrams':
field = getattr(Ngram, field_name_parts[1])
# parse field tables.add('ngrams')
field_objects = { elif field_name_parts[0] == 'hyperdata':
'hyperdata': None, hyperdata = _hyperdata_dict[field_name_parts[1]]
'ngrams': ['terms', 'n'], if hyperdata not in hyperdata_aliases:
} hyperdata_aliases[hyperdata] = aliased(Node_Hyperdata)
field = field.split('.') hyperdata_alias = hyperdata_aliases[hyperdata]
if len(field) < 2 or field[0] not in field_objects: field = getattr(hyperdata_alias, 'value_%s' % hyperdata.type)
raise APIException('Parameter "field" should be a in the form "object.key", where "object" takes one of the following values: "%s". "%s" was found instead' % ('", "'.join(field_objects), '.'.join(field)), 400) if len(field_name_parts) == 3 :
if field_objects[field[0]] is not None and field[1] not in field_objects[field[0]]: field = func.date_trunc(field_name_parts[2], field)
raise APIException('Invalid key for "%s" in parameter "field", should be one of the following values: "%s". "%s" was found instead' % (field[0], '", "'.join(field_objects[field[0]]), field[1]), 400) fields[field_name] = field
# build query: selected fields
# return value query = (session
return field, _operators[operator], value .query(*(fields[field_name] for field_name in input['retrieve']['fields']))
)
def _count_documents(self, query): # build query: selected tables
return { query = query.select_from(Node)
'fields': [] if 'ngrams' in tables:
} query = (query
.join(Node_Ngram, Node_Ngram.node_id == Node.id)
.join(Ngram, Ngram.id == Node_Ngram.ngram_id)
)
for hyperdata, hyperdata_alias in hyperdata_aliases.items():
query = (query
.join(hyperdata_alias, hyperdata_alias.node_id == Node.id)
.filter(hyperdata_alias.hyperdata_id == hyperdata.id)
)
# build query: filtering
query = (query
.filter(Node.parent_id == node_id)
)
for filter in input['filters']:
query = (query
.filter(_operators_dict[filter['operator']](
fields[filter['field']],
filter['value']
))
)
# build query: aggregations
if input['retrieve']['aggregate']:
for field_name in input['retrieve']['fields']:
if not field_name.endswith('.count'):
query = query.group_by(fields[field_name])
# build query: sorting
for field_name in input['sort']:
last = field_name[-1:]
if last in ('+', '-'):
field_name = field_name[:-1]
if last == '-':
query = query.order_by(fields[field_name].desc())
else:
query = query.order_by(fields[field_name])
# build and return result
output = copy.deepcopy(input)
output['pagination']['total'] = query.count()
output['results'] = list(
query[input['pagination']['offset']:input['pagination']['offset']+input['pagination']['limit']]
if input['pagination']['limit']
else query[input['pagination']['offset']:]
)
return output
def _haskell(self, input, node_id):
output = copy.deepcopy(input)
output['pagination']['total'] = 0
output['results'] = [
["1998-04-24T00:00:00Z",50],
["1999-04-24T00:00:00Z",10],
["2000-09-11T00:00:00Z",100],
["2001-09-11T00:00:00Z",120],
["2002-09-11T00:00:00Z",10],
["2003-09-11T00:00:00Z",10],
["2004-09-11T00:00:00Z",100],
["2005-09-11T00:00:00Z",10],
["2006-09-11T00:00:00Z",10],
["2007-09-11T00:00:00Z",10],
["2008-09-11T00:00:00Z",100],
["2009-09-11T00:00:00Z",10],
["2010-09-11T00:00:00Z",10],
]
return output
def post(self, request, node_id): def post(self, request, node_id):
""" Query the children of the given node. """ Query the children of the given node.
...@@ -348,199 +437,53 @@ class NodesChildrenQueries(APIView): ...@@ -348,199 +437,53 @@ class NodesChildrenQueries(APIView):
} }
""" """
hyperdata_aliases = {} # authorized field names
sql_fields = set({
# validate query 'id', 'name',
query_fields = {'pagination', 'retrieve', 'sort', 'filters'} 'nodes.count', 'ngrams.count',
for key in request.DATA: 'ngrams.terms', 'ngrams.n',
if key not in query_fields: })
raise APIException('Unrecognized field "%s" in query object. Accepted fields are: "%s"' % (key, '", "'.join(query_fields)), 400) for hyperdata in _hyperdata_list:
sql_fields.add('hyperdata.' + hyperdata.name)
# selecting info if hyperdata.type == 'datetime':
if 'retrieve' not in request.DATA: for part in ['year', 'month', 'day', 'hour', 'minute']:
raise APIException('The query should have a "retrieve" parameter.', 400) sql_fields.add('hyperdata.' + hyperdata.name + '.' + part)
retrieve = request.DATA['retrieve']
retrieve_types = {'fields', 'aggregates'} # authorized field names: Haskell
if 'type' not in retrieve: haskell_fields = set({
raise APIException('In the query\'s "retrieve" parameter, a "type" should be specified. Possible values are: "%s".' % ('", "'.join(retrieve_types), ), 400) 'haskell.test', "hyperdata.publication_date.day",
if 'list' not in retrieve or not isinstance(retrieve['list'], list): })
raise APIException('In the query\'s "retrieve" parameter, a "list" should be provided as an array', 400)
if retrieve['type'] not in retrieve_types:
raise APIException('Unrecognized "type": "%s" in the query\'s "retrieve" parameter. Possible values are: "%s".' % (retrieve['type'], '", "'.join(retrieve_types), ), 400)
if retrieve['type'] == 'fields':
fields_names = ['id'] + retrieve['list'] if 'id' not in retrieve['list'] else retrieve['list']
elif retrieve['type'] == 'aggregates':
fields_names = list(retrieve['list'])
fields_list = []
for field_name in fields_names:
split_field_name = field_name.split('.')
if split_field_name[0] == 'hyperdata':
hyperdata = session.query(Hyperdata).filter(Hyperdata.name == split_field_name[1]).first()
if hyperdata is None:
hyperdata_query = session.query(Hyperdata.name).order_by(Hyperdata.name)
hyperdata_names = [hyperdata.name for hyperdata in hyperdata_query.all()]
raise APIException('Invalid key for "%s" in parameter "field", should be one of the following values: "%s". "%s" was found instead' % (field[0], '", "'.join(hyperdata_names), field[1]), 400)
# check or create Node_Hyperdata alias; join if necessary
if hyperdata.id in hyperdata_aliases:
hyperdata_alias = hyperdata_aliases[hyperdata.id]
else:
hyperdata_alias = hyperdata_aliases[hyperdata.id] = aliased(Node_Hyperdata)
field = getattr(hyperdata_alias, 'value_' + hyperdata.type)
# operation on field
if len(split_field_name) > 2:
# datetime truncation
if hyperdata.type == 'datetime':
datepart = split_field_name[2]
accepted_dateparts = ['year', 'month', 'day', 'hour', 'minute']
if datepart not in accepted_dateparts:
raise APIException('Invalid date truncation for "%s": "%s". Accepted values are: "%s".' % (split_field_name[1], split_field_name[2], '", "'.join(accepted_dateparts), ), 400)
# field = extract(datepart, field)
field = func.date_trunc(datepart, field)
# field = func.date_trunc(text('"%s"'% (datepart,)), field)
else:
authorized_field_names = {'id', 'name', }
authorized_aggregates = {
'nodes.count': func.count(Node.id),
'ngrams.count': func.count(Ngram.id),
}
if retrieve['type'] == 'aggregates' and field_name in authorized_aggregates:
field = authorized_aggregates[field_name]
elif field_name in authorized_field_names:
field = getattr(Node, field_name)
else:
raise APIException('Unrecognized "field": "%s" in the query\'s "retrieve" parameter. Possible values are: "%s".' % (field_name, '", "'.join(authorized_field_names), ))
fields_list.append(
field.label(
field_name if '.' in field_name else 'node.' + field_name
)
)
# starting the query!
document_type_id = cache.NodeType['Document'].id ##session.query(NodeType.id).filter(NodeType.name == 'Document').scalar()
query = (session
.query(*fields_list)
.select_from(Node)
.filter(Node.type_id == document_type_id)
.filter(Node.parent_id == node_id)
)
# join ngrams if necessary
if 'ngrams.count' in fields_names:
query = (query
.join(Node_Ngram, Node_Ngram.node_id == Node.id)
.join(Ngram, Ngram.id == Node_Ngram.ngram_id)
)
# join hyperdata aliases
for hyperdata_id, hyperdata_alias in hyperdata_aliases.items():
query = (query
.join(hyperdata_alias, hyperdata_alias.node_id == Node.id)
.filter(hyperdata_alias.hyperdata_id == hyperdata_id)
)
# filtering
for filter in request.DATA.get('filters', []):
# parameters extraction & validation
field, operator, value = self._parse_filter(filter)
#
if field[0] == 'hyperdata':
# which hyperdata?
hyperdata = session.query(Hyperdata).filter(Hyperdata.name == field[1]).first()
if hyperdata is None:
hyperdata_query = session.query(Hyperdata.name).order_by(Hyperdata.name)
hyperdata_names = [hyperdata.name for hyperdata in hyperdata_query.all()]
raise APIException('Invalid key for "%s" in parameter "field", should be one of the following values: "%s". "%s" was found instead' % (field[0], '", "'.join(hyperdata_names), field[1]), 400)
# check or create Node_Hyperdata alias; join if necessary
if hyperdata.id in hyperdata_aliases:
hyperdata_alias = hyperdata_aliases[hyperdata.id]
else:
hyperdata_alias = hyperdata_aliases[hyperdata.id] = aliased(Node_Hyperdata)
query = (query
.join(hyperdata_alias, hyperdata_alias.node_id == Node.id)
.filter(hyperdata_alias.hyperdata_id == hyperdata.id)
)
# adjust date
if hyperdata.type == 'datetime':
value = value + '2000-01-01T00:00:00Z'[len(value):]
# filter query
query = query.filter(operator(
getattr(hyperdata_alias, 'value_' + hyperdata.type),
value
))
elif field[0] == 'ngrams':
query = query.filter(
Node.id.in_(session
.query(Node_Ngram.node_id)
.join(Ngram, Ngram.id == Node_Ngram.ngram_id)
.filter(operator(
getattr(Ngram, field[1]),
map(lambda x: x.replace('-', ' '), value)
))
)
)
# TODO: date_trunc (psql) -> index also # authorized field names: all of them
authorized_fields = sql_fields | haskell_fields
# groupping # input validation
for field_name in fields_names: input = validate(request.DATA, {'type': dict, 'items': {
if field_name not in authorized_aggregates: 'pagination': {'type': dict, 'items': {
# query = query.group_by(text(field_name)) 'limit': {'type': int, 'default': 0},
query = query.group_by('"%s"' % ( 'offset': {'type': int, 'default': 0},
field_name if '.' in field_name else 'node.' + field_name }, 'default': {'limit': 0, 'offset': 0}},
, )) 'filters': {'type': list, 'items': {'type': dict, 'items': {
'field': {'type': str, 'required': True, 'range': authorized_fields},
# sorting 'operator': {'type': str, 'required': True, 'range': list(_operators_dict.keys())},
sort_fields_names = request.DATA.get('sort', ['id']) 'value': {'required': True},
if not isinstance(sort_fields_names, list): }}, 'default': list()},
raise APIException('The query\'s "sort" parameter should be an array', 400) 'retrieve': {'type': dict, 'required': True, 'items': {
sort_fields_list = [] 'aggregate': {'type': bool, 'default': False},
for sort_field_name in sort_fields_names: 'fields': {'type': list, 'items': {'type': str, 'range': authorized_fields}, 'range': (1, )},
try: }},
desc = sort_field_name[0] == '-' 'sort': {'type': list, 'items': {'type': str}, 'default': list()},
if sort_field_name[0] in {'-', '+'}: }})
sort_field_name = sort_field_name[1:]
field = fields_list[fields_names.index(sort_field_name)] # return result, depending on the queried fields
if desc: if set(input['retrieve']['fields']) <= sql_fields:
field = field.desc() method = self._sql
sort_fields_list.append(field) elif set(input['retrieve']['fields']) <= haskell_fields:
except: method = self._haskell
raise APIException('Unrecognized field "%s" in the query\'s "sort" parameter. Accepted values are: "%s"' % (sort_field_name, '", "'.join(fields_names)), 400) else:
query = query.order_by(*sort_fields_list) raise ValidationException('queried fields are mixing incompatible types of fields')
return JsonHttpResponse(method(input, node_id), 201)
# pagination
pagination = request.DATA.get('pagination', {})
for key, value in pagination.items():
if key not in {'limit', 'offset'}:
raise APIException('Unrecognized parameter in "pagination": "%s"' % (key, ), 400)
if not isinstance(value, int):
raise APIException('In "pagination", "%s" should be an integer.' % (key, ), 400)
if 'offset' not in pagination:
pagination['offset'] = 0
if 'limit' not in pagination:
pagination['limit'] = 0
# respond to client!
# return DebugHttpResponse(str(query))
# return DebugHttpResponse(literalquery(query))
results = [
list(row)
# dict(zip(fields_names, row))
for row in (
query[pagination["offset"]:pagination["offset"]+pagination["limit"]]
if pagination['limit']
else query[pagination["offset"]:]
)
]
pagination["total"] = query.count()
return Response({
"pagination": pagination,
"retrieve": fields_names,
"sorted": sort_fields_names,
"results": results,
}, 201)
class NodesList(APIView): class NodesList(APIView):
authentication_classes = (SessionAuthentication, BasicAuthentication) authentication_classes = (SessionAuthentication, BasicAuthentication)
...@@ -598,6 +541,7 @@ class Nodes(APIView): ...@@ -598,6 +541,7 @@ class Nodes(APIView):
except Exception as error: except Exception as error:
msgres ="error deleting : " + node_id + str(error) msgres ="error deleting : " + node_id + str(error)
class CorpusController: class CorpusController:
@classmethod @classmethod
......
...@@ -48,7 +48,6 @@ urlpatterns = patterns('', ...@@ -48,7 +48,6 @@ urlpatterns = patterns('',
# Corpus management # Corpus management
# Document view (main) # Document view (main)
url(r'^project/(\d+)/corpus/(\d+)/$', views.corpus), url(r'^project/(\d+)/corpus/(\d+)/$', views.corpus),
url(r'^project/(\d+)/corpus/(\d+)/documents/?$', views.corpus),
# Journals view # Journals view
url(r'^project/(\d+)/corpus/(\d+)/journals/journals.json$', corpus_views.test_journals), url(r'^project/(\d+)/corpus/(\d+)/journals/journals.json$', corpus_views.test_journals),
...@@ -60,6 +59,7 @@ urlpatterns = patterns('', ...@@ -60,6 +59,7 @@ urlpatterns = patterns('',
# Update corpus # Update corpus
url(r'^project/(\d+)/corpus/(\d+)/(\w+)/update$', views.update_nodes), url(r'^project/(\d+)/corpus/(\d+)/(\w+)/update$', views.update_nodes),
url(r'^project/(\d+)/corpus/(\d+)/update$', views.update_nodes),
############################################################################ ############################################################################
# annotations App # annotations App
......
from rest_framework.exceptions import APIException
from datetime import datetime
__all__ = ['validate']
_types_names = {
bool: 'boolean',
int: 'integer',
float: 'float',
str: 'string',
dict: 'object',
list: 'array',
datetime: 'datetime',
}
class ValidationException(APIException):
status_code = 400
default_detail = 'Bad request!'
def validate(value, expected, path='input'):
# Is the expected type respected?
if 'type' in expected:
expected_type = expected['type']
if not isinstance(value, expected_type):
if expected_type in (bool, int, float, str, datetime, ):
try:
if expected_type == bool:
value = value not in {0, 0.0, '', '0', 'false'}
elif expected_type == datetime:
value = value + '2000-01-01T00:00:00Z'[len(value):]
value = datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')
else:
value = expected_type(value)
except ValueError:
raise ValidationException('%s should be a JSON %s, but could not be parsed as such' % (path, _types_names[expected_type], ))
else:
raise ValidationException('%s should be a JSON %s' % (path, _types_names[expected_type], ))
else:
expected_type = type(value)
# Is the value in the expected range?
if 'range' in expected:
expected_range = expected['range']
if isinstance(expected_range, tuple):
if expected_type in (int, float):
tested_value = value
tested_name = 'value'
elif expected_type in (str, list):
tested_value = len(value)
tested_name = 'length'
if tested_value < expected_range[0]:
raise ValidationException('%s should have a minimum %s of %d' % (path, tested_name, expected_range[0], ))
if len(expected_range) > 1 and tested_value > expected_range[1]:
raise ValidationException('%s should have a maximum %s of %d' % (path, tested_name, expected_range[1], ))
elif isinstance(expected_range, (list, set, )) and value not in expected_range:
expected_values = expected_range if isinstance(expected_range, list) else expected_range
expected_values = [str(value) for value in expected_values if isinstance(value, expected_type)]
if len(expected_values) < 16:
expected_values_str = '", "'.join(expected_values)
expected_values_str = '"' + expected_values_str + '"'
else:
expected_values_str = '", "'.join(expected_values[:16])
expected_values_str = '"' + expected_values_str + '"...'
raise ValidationException('%s should take one of the following values: %s' % (path, expected_values_str, ))
# Do we have to translate through a dictionary?
if 'translate' in expected:
translate = expected['translate']
if callable(translate):
value = translate(value)
if value is None and expected.get('required', False):
raise ValidationException('%s has been given an invalid value' % (path, ))
return value
try:
value = expected['translate'][value]
except KeyError:
if expected.get('translate_fallback_keep', False):
return value
if expected.get('required', False):
raise ValidationException('%s has been given an invalid value' % (path, ))
else:
return expected.get('default', value)
# Are we handling an iterable?
if expected_type in (list, dict):
if 'items' in expected:
expected_items = expected['items']
if expected_type == list:
for i, element in enumerate(value):
value[i] = validate(element, expected_items, '%s[%d]' % (path, i, ))
elif expected_type == dict:
if expected_items:
for key in value:
if key not in expected_items:
raise ValidationException('%s should not have a "%s" key.' % (path, key, ))
for expected_key, expected_value in expected_items.items():
if expected_key in value:
value[expected_key] = validate(value[expected_key], expected_value, '%s["%s"]' % (path, expected_key, ))
elif 'required' in expected_value and expected_value['required']:
raise ValidationException('%s should have a "%s" key.' % (path, expected_key, ))
elif 'default' in expected_value:
value[expected_key] = expected_value['default']
# Let's return the proper value!
return value
...@@ -252,7 +252,7 @@ def projects(request): ...@@ -252,7 +252,7 @@ def projects(request):
}) })
def update_nodes(request, project_id, corpus_id, view): def update_nodes(request, project_id, corpus_id, view=None):
''' '''
update function: update function:
- remove previous computations (temporary lists and coocurrences) - remove previous computations (temporary lists and coocurrences)
...@@ -265,7 +265,8 @@ def update_nodes(request, project_id, corpus_id, view): ...@@ -265,7 +265,8 @@ def update_nodes(request, project_id, corpus_id, view):
try: try:
offset = int(project_id) offset = int(project_id)
offset = int(corpus_id) offset = int(corpus_id)
offset = str(view) if view is not None:
offset = str(view)
except ValueError: except ValueError:
raise Http404() raise Http404()
...@@ -307,7 +308,10 @@ def update_nodes(request, project_id, corpus_id, view): ...@@ -307,7 +308,10 @@ def update_nodes(request, project_id, corpus_id, view):
#return redirect(request.path.replace('update', '')) #return redirect(request.path.replace('update', ''))
return redirect('/project/%s/corpus/%s/%s' % (project_id, corpus_id, view)) if view is None:
return redirect('/project/%s/corpus/%s/' % (project_id, corpus_id))
else:
return redirect('/project/%s/corpus/%s/%s' % (project_id, corpus_id, view))
# #
# return render_to_response( # return render_to_response(
# request.path, # request.path,
...@@ -385,7 +389,7 @@ def newpaginatorJSON(request , corpus_id): ...@@ -385,7 +389,7 @@ def newpaginatorJSON(request , corpus_id):
for doc in documents: for doc in documents:
if "publication_date" in doc.hyperdata: if "publication_date" in doc.hyperdata:
try: try:
realdate = doc.hyperdata["publication_date"].split(" ")[0] # in database is = (year-month-day = 2015-01-06 00:00:00 = 06 jan 2015 00 hrs) realdate = doc.hyperdata["publication_date"].replace('T',' ').split(" ")[0] # in database is = (year-month-day = 2015-01-06 00:00:00 = 06 jan 2015 00 hrs)
realdate = datetime.datetime.strptime(str(realdate), '%Y-%m-%d').date() # finalform = (yearmonthday = 20150106 = 06 jan 2015) realdate = datetime.datetime.strptime(str(realdate), '%Y-%m-%d').date() # finalform = (yearmonthday = 20150106 = 06 jan 2015)
# doc.date = realdate # doc.date = realdate
resdict = {} resdict = {}
......
...@@ -8,7 +8,7 @@ from admin.utils import PrintException ...@@ -8,7 +8,7 @@ from admin.utils import PrintException
##from node import models ##from node import models
# #
## SQLA models ## SQLA models
from gargantext_web.db import * from gargantext_web.db import session
################################################################################ ################################################################################
## If you need to reset all data ## If you need to reset all data
...@@ -33,17 +33,15 @@ hyperdata = { ...@@ -33,17 +33,15 @@ hyperdata = {
} }
for name_, type_ in hyperdata.items(): for name_, type_ in hyperdata.items():
data = (session.query(Hyperdata).filter( data_ = (session.query(Hyperdata).filter(
Hyperdata.name == str(name_), Hyperdata.name == str(name_),
Hyperdata.type == str(type_) Hyperdata.type == str(type_)
).first() ).first()
) )
if data_ is None:
if data is None:
print('Hyper Data' + name_ + 'does not existe, creating it') print('Hyper Data' + name_ + 'does not existe, creating it')
hyperdata = Hyperdata(name=name_, type=type_) hyperdata = Hyperdata(name=name_, type=type_)
session.add(hyperdata) session.add(hyperdata)
session.commit() session.commit()
......
...@@ -103,6 +103,7 @@ def listNgramIds(list_id=None, typeList=None, ...@@ -103,6 +103,7 @@ def listNgramIds(list_id=None, typeList=None,
.group_by(Ngram.id, ListNgram.node_id) .group_by(Ngram.id, ListNgram.node_id)
) )
# FIXME this is only used to filter on 1 document
if doc_id is not None: if doc_id is not None:
Doc = aliased(Node) Doc = aliased(Node)
DocNgram = aliased(NodeNgram) DocNgram = aliased(NodeNgram)
......
from .NgramsExtractor import NgramsExtractor from .NgramsExtractor import NgramsExtractor
from ..Taggers import NltkTagger from ..Taggers import NltkTagger, MeltTagger
class EnglishNgramsExtractor(NgramsExtractor): class EnglishNgramsExtractor(NgramsExtractor):
def start(self): def start(self):
self.tagger = NltkTagger() # self.tagger = NltkTagger()
self.tagger = MeltTagger(language='en')
\ No newline at end of file
from .NgramsExtractor import NgramsExtractor from .NgramsExtractor import NgramsExtractor
from ..Taggers import TreeTagger from ..Taggers import TreeTagger, MeltTagger
class FrenchNgramsExtractor(NgramsExtractor): class FrenchNgramsExtractor(NgramsExtractor):
def start(self): def start(self):
self.tagger = TreeTagger() #self.tagger = TreeTagger()
self.tagger = MeltTagger(language='fr')
...@@ -14,24 +14,24 @@ class NgramsExtractor: ...@@ -14,24 +14,24 @@ class NgramsExtractor:
self._label = "NP" self._label = "NP"
self._rule = self._label + ": " + rule self._rule = self._label + ": " + rule
self._grammar = nltk.RegexpParser(self._rule) self._grammar = nltk.RegexpParser(self._rule)
def __del__(self): def __del__(self):
self.stop() self.stop()
def start(self): def start(self):
self.tagger = TurboTagger() self.tagger = TurboTagger()
def stop(self): def stop(self):
pass pass
"""Extracts a list of ngrams. """Extracts a list of ngrams.
Returns a list of the ngrams found in the given text. Returns a list of the ngrams found in the given text.
""" """
def extract_ngrams(self, contents): def extract_ngrams(self, contents):
tagged_ngrams = self.tagger.tag_text(contents) tagged_tokens = list(self.tagger.tag_text(contents))
if len(tagged_ngrams): if len(tagged_tokens):
grammar_parsed = self._grammar.parse(tagged_ngrams) grammar_parsed = self._grammar.parse(tagged_tokens)
for subtree in grammar_parsed.subtrees(): for subtree in grammar_parsed.subtrees():
if subtree.label() == self._label: if subtree.label() == self._label:
yield subtree.leaves() yield subtree.leaves()
from .FrenchNgramsExtractor import FrenchNgramsExtractor from .FrenchNgramsExtractor import FrenchNgramsExtractor
from .TurboNgramsExtractor import TurboNgramsExtractor as EnglishNgramsExtractor #from .TurboNgramsExtractor import TurboNgramsExtractor as EnglishNgramsExtractor
# from .EnglishNgramsExtractor import EnglishNgramsExtractor from .EnglishNgramsExtractor import EnglishNgramsExtractor
from .NgramsExtractor import NgramsExtractor from .NgramsExtractor import NgramsExtractor
...@@ -12,7 +12,8 @@ import os ...@@ -12,7 +12,8 @@ import os
class identity_dict(dict): class identity_dict(dict):
def __missing__(self, key): def __missing__(self, key):
return key return key
_tag_replacements = identity_dict({ _tag_replacements = dict()
_tag_replacements['fr'] = identity_dict({
'DET': 'DT', 'DET': 'DT',
'NC': 'NN', 'NC': 'NN',
'NPP': 'NNP', 'NPP': 'NNP',
...@@ -46,11 +47,18 @@ _tag_replacements = identity_dict({ ...@@ -46,11 +47,18 @@ _tag_replacements = identity_dict({
# 'PREF': '', # 'PREF': '',
# 'ADJWH': '', # 'ADJWH': '',
}) })
_tag_replacements['en'] = identity_dict()
class MeltTagger(Tagger): class MeltTagger(Tagger):
def start(self, language='fr', melt_data_path='lib/melttagger'): def __init__(self, *args, **kwargs):
self.language = kwargs.pop('language', 'fr')
self._tag_replacements = _tag_replacements[self.language]
super(self.__class__, self).__init__(*args, **kwargs)
def start(self, melt_data_path='lib/melttagger'):
language = self.language
basepath = os.path.dirname(os.path.realpath(__file__)) basepath = os.path.dirname(os.path.realpath(__file__))
path = os.path.join(basepath, melt_data_path) path = os.path.join(basepath, melt_data_path)
self._pos_tagger = POSTagger() self._pos_tagger = POSTagger()
...@@ -94,12 +102,12 @@ class MeltTagger(Tagger): ...@@ -94,12 +102,12 @@ class MeltTagger(Tagger):
if len(token.string): if len(token.string):
yield (token.string, token.label, ) yield (token.string, token.label, )
def tag_text(self, text, lemmatize=True): def tag_text(self, text, lemmatize=False):
tagged_tokens = self._tag(text) tagged_tokens = self._tag(text)
# without lemmatization # without lemmatization
if not lemmatize: if not lemmatize:
for form, tag in tagged_tokens: for form, tag in tagged_tokens:
yield (form, _tag_replacements[tag]) yield (form, self._tag_replacements[tag])
return return
# with lemmatization # with lemmatization
command_input = ' '.join( command_input = ' '.join(
...@@ -110,4 +118,4 @@ class MeltTagger(Tagger): ...@@ -110,4 +118,4 @@ class MeltTagger(Tagger):
for token in lemmatized.split(): for token in lemmatized.split():
if len(token): if len(token):
values = token.split('/') values = token.split('/')
yield (values[0], _tag_replacements[values[1]], values[2].replace('*', '')) yield (values[0], self._tag_replacements[values[1]], values[2].replace('*', ''))
// Pre-defined constants // Pre-defined constants
var operators = { var operators = {
'text': [ 'text': [
{'label': 'contains', 'key': 'contains'} {'label': 'contains', 'key': 'contains'},
{'label': 'does not contain', 'key': 'doesnotcontain'},
], ],
'string': [ 'string': [
{'label': 'starts with', 'key': 'startswith'}, {'label': 'starts with', 'key': 'startswith'},
{'label': 'contains', 'key': 'contains'}, {'label': 'contains', 'key': 'contains'},
{'label': 'does not contain', 'key': 'doesnotcontain'},
{'label': 'ends with', 'key': 'endswith'}, {'label': 'ends with', 'key': 'endswith'},
{'label': 'is', 'key': '='}, {'label': 'is', 'key': '='},
{'label': 'is before', 'key': '<'}, {'label': 'is before', 'key': '<'},
...@@ -259,11 +261,21 @@ gargantext.controller("DatasetController", function($scope, $http) { ...@@ -259,11 +261,21 @@ gargantext.controller("DatasetController", function($scope, $http) {
$scope.corpora = []; $scope.corpora = [];
$http.get('/api/nodes?type=Project', {cache: true}).success(function(response){ $http.get('/api/nodes?type=Project', {cache: true}).success(function(response){
$scope.projects = response.data; $scope.projects = response.data;
// Initially set to what is indicated in the URL
if (/^\/project\/\d+\/corpus\/\d+/.test(location.pathname)) {
$scope.projectId = parseInt(location.pathname.split('/')[2]);
$scope.updateCorpora();
}
}); });
// update corpora according to the select parent project // update corpora according to the select parent project
$scope.updateCorpora = function() { $scope.updateCorpora = function() {
$http.get('/api/nodes?type=Corpus&parent=' + $scope.projectId, {cache: true}).success(function(response){ $http.get('/api/nodes?type=Corpus&parent=' + $scope.projectId, {cache: true}).success(function(response){
$scope.corpora = response.data; $scope.corpora = response.data;
// Initially set to what is indicated in the URL
if (/^\/project\/\d+\/corpus\/\d+/.test(location.pathname)) {
$scope.corpusId = parseInt(location.pathname.split('/')[4]);
$scope.updateEntities();
}
}); });
}; };
// update entities depending on the selected corpus // update entities depending on the selected corpus
...@@ -522,8 +534,8 @@ gargantext.controller("GraphController", function($scope, $http, $element) { ...@@ -522,8 +534,8 @@ gargantext.controller("GraphController", function($scope, $http, $element) {
filters: query.filters, filters: query.filters,
sort: ['hyperdata.publication_date.day'], sort: ['hyperdata.publication_date.day'],
retrieve: { retrieve: {
type: 'aggregates', aggregate: true,
list: ['hyperdata.publication_date.day', query.mesured] fields: ['hyperdata.publication_date.day', query.mesured]
} }
}; };
// request to the server // request to the server
...@@ -588,4 +600,4 @@ setTimeout(function(){ ...@@ -588,4 +600,4 @@ setTimeout(function(){
// // $('button.refresh').first().click(); // // $('button.refresh').first().click();
}, 500); }, 500);
}, 250); }, 250);
*/ */
\ No newline at end of file
...@@ -229,7 +229,7 @@ ...@@ -229,7 +229,7 @@
<hr/> <hr/>
<div class="corpus"> <div class="corpus">
<button ng-click="removeDataset($index)" title="remove this dataset">X</button> <button ng-click="removeDataset($index)" title="remove this dataset">X</button>
<select ng-model="mesured" style="background-color:{{ getColor($index, datasets.length) }}" ng-options="value as key for (key, value) in {'Documents count': 'nodes.count', 'Ngrams count': 'ngrams.count', 'Simulation par Agents': 'agents.count'}" ng-change="updateQuery()"></select> <select ng-model="mesured" style="background-color:{{ getColor($index, datasets.length) }}" ng-options="value as key for (key, value) in {'Documents count': 'nodes.count', 'Ngrams count': 'ngrams.count', 'Simulation par Agents': 'haskell.test'}" ng-change="updateQuery()"></select>
in the project in the project
<select ng-model="projectId" ng-change="updateCorpora()" ng-options="project.id as project.name for project in projects"></select>, <select ng-model="projectId" ng-change="updateCorpora()" ng-options="project.id as project.name for project in projects"></select>,
corpus corpus
......
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
<div class="col-md-4"> <div class="col-md-4">
<br> <br>
<a class="btn btn-default btn-lg" role="button" href="/project/{{project.id}}/corpus/{{corpus.id}}/{{view}}/update">Update</a> <a class="btn btn-default btn-lg" role="button" href="/project/{{project.id}}/corpus/{{corpus.id}}{% if view %}/{{view}}{% endif %}/update">Update</a>
<a class="btn btn-default btn-lg" role="button" href="/project/{{project.id}}/corpus/{{ corpus.id }}/corpus.csv">Download</a> <a class="btn btn-default btn-lg" role="button" href="/project/{{project.id}}/corpus/{{ corpus.id }}/corpus.csv">Download</a>
<a type="button" class="btn btn-default btn-lg" data-container="body" data-toggle="popover" data-placement="bottom" <a type="button" class="btn btn-default btn-lg" data-container="body" data-toggle="popover" data-placement="bottom"
......
from parsing.Taggers import MeltTagger from parsing.Taggers import MeltTagger
# from parsing.Taggers.melttagger.tagger import POSTagger, Token, DAGParser, DAGReader texts = {
'en':
"""Air raids on Japan by the Allies in World War II caused extensive destruction and casualties; the most commonly cited estimates are 333,000 killed and 473,000 wounded.
# # references: During the first years of the Pacific War, these attacks were limited to the Doolittle Raid in April 1942 and small-scale raids on military positions in the Kuril Islands starting in mid-1943. Strategic bombing raids began in June 1944 and were greatly expanded in November. The raids initially attempted to target industrial facilities, but from March 1945 onwards were generally directed against urban areas. Aircraft flying from aircraft carriers and the Ryukyu Islands also frequently struck targets in Japan during 1945 in preparation for an Allied invasion planned for October. In early August, the cities of Hiroshima and Nagasaki were struck and mostly destroyed by atomic bombs. Japan's military and civil defenses were not capable of protecting the country, and the Allied forces generally suffered few losses. The bombing campaign was one of the main factors in the Japanese government's decision to surrender in mid-August 1945. Nevertheless, there has been a long-running debate over the attacks on Japanese cities, and the decision to use atomic weapons has been particularly controversial.
# # - http://cs.nyu.edu/grishman/jet/guide/PennPOS.html """,
# # - http://www.lattice.cnrs.fr/sites/itellier/SEM.html 'fr':
# class identity_dict(dict): """Le vieil hôtel de ville, construit de 1608 à 1610 est le plus ancien bâtiment de la ville de Wiesbaden. Il se dresse sur la place centrale de la vieille ville, la Place du Palais, qui abrite aujourd'hui le Parlement de l'État de Hesse, l'église et l'hôtel de ville.
# def __missing__(self, key):
# return key
# _tag_replacements = identity_dict({
# 'DET': 'DT',
# 'NC': 'NN',
# 'NPP': 'NNP',
# 'ADJ': 'JJ',
# 'PONCT': '.',
# 'ADVWH': 'WRB',
# 'ADV': 'RB',
# 'DETWH': 'WDT',
# 'PROWH': 'WP',
# 'ET': 'FW',
# 'VINF': 'VB',
# 'I': 'UH',
# 'CS': 'IN',
# # 'CLS': '',
# # 'CLR': '',
# # 'CLO': '',
# # 'PRO': '',
# # 'PROREL': '',
# # 'P': '',
# # 'P+D': '',
# # 'P+PRO': '',
# # 'V': '',
# # 'VPR': '',
# # 'VPP': '',
# # 'VS': '',
# # 'VIMP': '',
# # 'PREF': '',
# # 'ADJWH': '',
# })
# import subprocess
# class MeltTagger:
# def __init__(self, language='fr', melt_data_path='./parsing/Taggers/melttagger'):
# path = '%s/%s' % (melt_data_path, language)
# self.pos_tagger = POSTagger()
# self.pos_tagger.load_tag_dictionary('%s/tag_dict.json' % path)
# self.pos_tagger.load_lexicon('%s/lexicon.json' % path)
# self.pos_tagger.load_model('%s' % path)
# self._preprocessing_commands = (
# # ('/usr/local/bin/clean_noisy_characters.sh', ),
# # ('/usr/local/bin/MElt_normalizer.pl', '-nc', '-c', '-d', '/usr/local/share/melt/normalization/%s' % language, '-l', language, ),
# ('/usr/local/share/melt/segmenteur.pl', '-a', '-ca', '-af=/usr/local/share/melt/pctabr', '-p', 'r'),
# )
# self._lemmatization_commands = (
# ('/usr/local/bin/MElt_postprocess.pl', '-npp', '-l', language),
# ('MElt_lemmatizer.pl', '-m', '/usr/local/share/melt/%s' % language),
# )
# def pipe(self, text, commands, encoding='utf8'):
# text = text.encode(encoding)
# # print(text.decode(encoding))
# for command in commands:
# # print(command)
# process = subprocess.Popen(
# command,
# bufsize=0,
# stdin=subprocess.PIPE,
# stdout=subprocess.PIPE,
# stderr=subprocess.PIPE,
# )
# text, err = process.communicate(text)
# # print()
# # print(text.decode(encoding))
# if len(err):
# print(err.decode(encoding))
# return text.decode(encoding)
# def tag(self, text, encoding='utf8', lemmatize=True):
# preprocessed = self.pipe(text, self._preprocessing_commands)
# if lemmatize:
# result = ''
# for sentence in preprocessed.split('\n'):
# words = sentence.split(' ')
# tokens = [Token(word) for word in words]
# tagged_tokens = self.pos_tagger.tag_token_sequence(tokens)
# # result += ' '.join(token.__str__() for token in tagged_tokens)
# for token in tagged_tokens:
# if len(token.string):
# result += '%s/%s ' % (token.string, token.label, )
# result += '\n'
# lemmatized = self.pipe(result, self._lemmatization_commands)
# for sentence in lemmatized.split('\n'):
# for token in sentence.split(' '):
# if len(token):
# yield tuple(token.split('/'))
# else:
# for sentence in preprocessed.split('\n'):
# words = sentence.split(' ')
# tokens = [Token(word) for word in words]
# tagged_tokens = self.pos_tagger.tag_token_sequence(tokens)
# for token in tagged_tokens:
# if len(token.string):
# yield (token.string, _tag_replacements[token.label], )
if __name__ == '__main__':
from time import time
t0 = time()
tagger = MeltTagger()
print(time() - t0)
print()
text = """Le vieil hôtel de ville, construit de 1608 à 1610 est le plus ancien bâtiment de la ville de Wiesbaden. Il se dresse sur la place centrale de la vieille ville, la Place du Palais, qui abrite aujourd'hui le Parlement de l'État de Hesse, l'église et l'hôtel de ville.
Il a été construit dans le style Renaissance. On a ajouté, en 1828, un étage de style romantique historié. Sur les bas-reliefs des cinq fenêtres de l'étage, en bois, étaient représentées les vertus de la force, la justice, la charité, de prudence et de modération, alors que la pierre a remplacé par des copies. Le pièces de chêne d'origine peut être visitées aujourd'hui au Musée de Wiesbaden. Aujourd'hui, le bâtiment sert de bureau de la ville de Wiesbaden. Il a été construit dans le style Renaissance. On a ajouté, en 1828, un étage de style romantique historié. Sur les bas-reliefs des cinq fenêtres de l'étage, en bois, étaient représentées les vertus de la force, la justice, la charité, de prudence et de modération, alors que la pierre a remplacé par des copies. Le pièces de chêne d'origine peut être visitées aujourd'hui au Musée de Wiesbaden. Aujourd'hui, le bâtiment sert de bureau de la ville de Wiesbaden.
Devant le porche, entre l'hôtel de Ville et l'Ancien hôtel de ville, se trouve la colonne centrale de Nassau, un lion couronné avec bouclier. Devant le porche, entre l'hôtel de Ville et l'Ancien hôtel de ville, se trouve la colonne centrale de Nassau, un lion couronné avec bouclier.
Il s'agit de construire progressivement, à partir des données initiales, un sous-graphe dans lequel sont classés les différents sommets par ordre croissant de leur distance minimale au sommet de départ. La distance correspond à la somme des poids des arêtes empruntées. Il s'agit de construire progressivement, à partir des données initiales, un sous-graphe dans lequel sont classés les différents sommets par ordre croissant de leur distance minimale au sommet de départ. La distance correspond à la somme des poids des arêtes empruntées.
...@@ -129,7 +16,18 @@ if __name__ == '__main__': ...@@ -129,7 +16,18 @@ if __name__ == '__main__':
Le plus proche des sommets adjacents est alors ajouté au sous-graphe. Le plus proche des sommets adjacents est alors ajouté au sous-graphe.
La seconde étape consiste à mettre à jour les distances des sommets adjacents à ce dernier. Encore une fois, on recherche alors le sommet doté de la distance la plus faible. Comme tous les sommets n'avaient plus une valeur infinie, il est donc possible que le sommet choisi ne soit pas un des derniers mis à jour. La seconde étape consiste à mettre à jour les distances des sommets adjacents à ce dernier. Encore une fois, on recherche alors le sommet doté de la distance la plus faible. Comme tous les sommets n'avaient plus une valeur infinie, il est donc possible que le sommet choisi ne soit pas un des derniers mis à jour.
On l'ajoute au sous-graphe, puis on continue ainsi à partir du dernier sommet ajouté, jusqu'à épuisement des sommets ou jusqu'à sélection du sommet d'arrivée. On l'ajoute au sous-graphe, puis on continue ainsi à partir du dernier sommet ajouté, jusqu'à épuisement des sommets ou jusqu'à sélection du sommet d'arrivée.
""" """,
}
language = 'en'
text = texts[language]
if __name__ == '__main__':
from time import time
t0 = time()
tagger = MeltTagger(language=language)
print(time() - t0)
print()
i = 0 i = 0
t0 = time() t0 = time()
for x in tagger.tag_text(text, lemmatize=True): for x in tagger.tag_text(text, lemmatize=True):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment