// Takes Stanford CoreNLP JSON output (var data = ... in data.js) // and uses brat to render everything. //var serverAddress = 'http://localhost:9000'; var serverAddress = ''; // Load Brat libraries var bratLocation = 'https://nlp.stanford.edu/js/brat/'; head.js( // External libraries bratLocation + '/client/lib/jquery.svg.min.js', bratLocation + '/client/lib/jquery.svgdom.min.js', // brat helper modules bratLocation + '/client/src/configuration.js', bratLocation + '/client/src/util.js', bratLocation + '/client/src/annotation_log.js', bratLocation + '/client/lib/webfont.js', // brat modules bratLocation + '/client/src/dispatcher.js', bratLocation + '/client/src/url_monitor.js', bratLocation + '/client/src/visualizer.js', // parse viewer './corenlp-parseviewer.js' ); // Uses Dagre (https://github.com/cpettitt/dagre) for constinuency parse // visualization. It works better than the brat visualization. var useDagre = true; var currentQuery = 'The quick brown fox jumped over the lazy dog.'; var currentSentences = ''; var currentText = ''; // ---------------------------------------------------------------------------- // HELPERS // ---------------------------------------------------------------------------- /** * Add the startsWith function to the String class */ if (typeof String.prototype.startsWith !== 'function') { // see below for better implementation! String.prototype.startsWith = function (str){ return this.indexOf(str) === 0; }; } function isInt(value) { return !isNaN(value) && (function(x) { return (x | 0) === x; })(parseFloat(value)) } /** * A reverse map of PTB tokens to their original gloss */ var tokensMap = { '-LRB-': '(', '-RRB-': ')', '-LSB-': '[', '-RSB-': ']', '-LCB-': '{', '-RCB-': '}', '``': '"', '\'\'': '"', }; /** * A mapping from part of speech tag to the associated visualization color */ function posColor(posTag) { if (posTag.startsWith('N')) { return '#A4BCED'; } else if (posTag.startsWith('V') || posTag.startsWith('M')) { return '#ADF6A2'; } else if (posTag.startsWith('P')) { return '#CCDAF6'; } else if (posTag.startsWith('I')) { return '#FFE8BE'; } else if (posTag.startsWith('R') || posTag.startsWith('W')) { return '#FFFDA8'; } else if (posTag.startsWith('D') || posTag === 'CD') { return '#CCADF6'; } else if (posTag.startsWith('J')) { return '#FFFDA8'; } else if (posTag.startsWith('T')) { return '#FFE8BE'; } else if (posTag.startsWith('E') || posTag.startsWith('S')) { return '#E4CBF6'; } else if (posTag.startsWith('CC')) { return '#FFFFFF'; } else if (posTag === 'LS' || posTag === 'FW') { return '#FFFFFF'; } else { return '#E3E3E3'; } } /** * A mapping from named entity tag to the associated visualization color */ function nerColor(nerTag) { if (nerTag === 'PERSON') { return '#FFCCAA'; } else if (nerTag === 'ORGANIZATION') { return '#8FB2FF'; } else if (nerTag === 'MISC') { return '#F1F447'; } else if (nerTag === 'LOCATION' || nerTag === 'COUNTRY' || nerTag === 'STATE_OR_PROVINCE' || nerTag === 'CITY') { return '#95DFFF'; } else if (nerTag === 'DATE' || nerTag === 'TIME' || nerTag === 'DURATION' || nerTag === 'SET') { return '#9AFFE6'; } else if (nerTag === 'MONEY') { return '#FFFFFF'; } else if (nerTag === 'PERCENT') { return '#FFA22B'; } else { return '#E3E3E3'; } } var d3_category18 = { // Just like d3_category20 but no grays! name: 'd3_category_18', colors: [ '#aec7e8', '#ffbb78', '#98df8a', '#ff9896', '#c5b0d5', '#c49c94', '#f7b6d2', '#dbdb8d', '#9edae5', '#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#bcbd22', '#17becf', ] }; function generateRandomColor() { return "#" + Math.random().toString(16).slice(2, 8); } function generateNextColor(i, palette) { if (palette && i < palette.colors.length) { return palette.colors[i]; } else { return generateRandomColor(); } } function getTagColor(tag, colorIndex, colors) { var ci = colorIndex[tag]; if (ci == null) { ci = colors.length; colorIndex[tag] = ci; colors.push(generateNextColor(ci, d3_category18)); } return colors[ci]; } /** * A mapping from coref values to the associated visualization color */ var corefColorIndex = {}; var corefColors = []; function corefColor(corefTag) { if (corefTag === "MENTION") { return '#FFE000'; } else { return getTagColor(corefTag, corefColorIndex, corefColors); } } var speakerColorIndex = {}; var speakerColors = []; function speakerColor(tag) { return getTagColor(tag, speakerColorIndex, speakerColors); } /** * A mapping from sentiment value to the associated * visualization color */ function sentimentColor(sentiment) { if (sentiment === "VERY POSITIVE") { return '#00FF00'; } else if (sentiment === "POSITIVE") { return '#7FFF00'; } else if (sentiment === "NEUTRAL") { return '#FFFF00'; } else if (sentiment === "NEGATIVE") { return '#FF7F00'; } else if (sentiment === "VERY NEGATIVE") { return '#FF0000'; } else { return '#E3E3E3'; } } /** * Get a list of annotators, from the annotator option input. */ function annotators() { var annotators = "tokenize,ssplit"; if ($('#language').val() === 'de' | $('#language').val() === 'fr' | $('#language').val() === 'es') { annotators += ",mwt"; } $('#annotators').find('option:selected').each(function () { annotators += "," + $(this).val(); }); return annotators; } /** * Get the input date */ function date() { function f(n) { return n < 10 ? '0' + n : n; } var date = new Date(); var M = date.getMonth() + 1; var D = date.getDate(); var Y = date.getFullYear(); var h = date.getHours(); var m = date.getMinutes(); var s = date.getSeconds(); return "" + Y + "-" + f(M) + "-" + f(D) + "T" + f(h) + ':' + f(m) + ':' + f(s); } //----------------------------------------------------------------------------- // Constituency parser //----------------------------------------------------------------------------- function ConstituencyParseProcessor() { var parenthesize = function (input, list) { if (list === undefined) { return parenthesize(input, []); } else { var token = input.shift(); if (token === undefined) { return list.pop(); } else if (token === "(") { list.push(parenthesize(input, [])); return parenthesize(input, list); } else if (token === ")") { return list; } else { return parenthesize(input, list.concat(token)); } } }; var toTree = function (list) { if (list.length === 2 && typeof list[1] === 'string') { return {label: list[0], text: list[1], isTerminal: true}; } else if (list.length >= 2) { var label = list.shift(); var node = {label: label}; var rest = list.map(function (x) { var t = toTree(x); if (typeof t === 'object') { t.parent = node; } return t; }); node.children = rest; return node; } else { return list; } }; var indexTree = function (tree, tokens, index) { index = index || 0; if (tree.isTerminal) { tree.token = tokens[index]; tree.tokenIndex = index; tree.tokenStart = index; tree.tokenEnd = index + 1; return index + 1; } else if (tree.children) { tree.tokenStart = index; for (var i = 0; i < tree.children.length; i++) { var child = tree.children[i]; index = indexTree(child, tokens, index); } tree.tokenEnd = index; } return index; }; var tokenize = function (input) { return input.split('"') .map(function (x, i) { if (i % 2 === 0) { // not in string return x.replace(/\(/g, ' ( ') .replace(/\)/g, ' ) '); } else { // in string return x.replace(/ /g, "!whitespace!"); } }) .join('"') .trim() .split(/\s+/) .map(function (x) { return x.replace(/!whitespace!/g, " "); }); }; var convertParseStringToTree = function (input, tokens) { var p = parenthesize(tokenize(input)); if (Array.isArray(p)) { var tree = toTree(p); // Correlate tree with tokens indexTree(tree, tokens); return tree; } }; this.process = function(annotation) { for (var i = 0; i < annotation.sentences.length; i++) { var s = annotation.sentences[i]; if (s.parse) { s.parseTree = convertParseStringToTree(s.parse, s.tokens); } } } } // ---------------------------------------------------------------------------- // RENDER // ---------------------------------------------------------------------------- /** * Render a given JSON data structure */ function render(data, reverse) { // Tweak arguments if (typeof reverse !== 'boolean') { reverse = false; } // Error checks if (typeof data.sentences === 'undefined') { return; } /** * Register an entity type (a tag) for Brat */ var entityTypesSet = {}; var entityTypes = []; function addEntityType(name, type, coarseType) { if (typeof coarseType === "undefined") { coarseType = type; } // Don't add duplicates if (entityTypesSet[type]) return; entityTypesSet[type] = true; // Get the color of the entity type color = '#ffccaa'; if (name === 'POS') { color = posColor(type); } else if (name === 'NER') { color = nerColor(coarseType); } else if (name === 'NNER') { color = nerColor(coarseType); } else if (name === 'SPEAKER') { color = speakerColor(coarseType); } else if (name === 'COREF') { color = corefColor(coarseType); } else if (name === 'ENTITY') { color = posColor('NN'); } else if (name === 'RELATION') { color = posColor('VB'); } else if (name === 'LEMMA') { color = '#FFFFFF'; } else if (name === 'SENTIMENT') { color = sentimentColor(type); } else if (name === 'LINK') { color = '#FFFFFF'; } else if (name === 'KBP_ENTITY') { color = '#FFFFFF'; } // Register the type entityTypes.push({ type: type, labels : [type], bgColor: color, borderColor: 'darken' }); } /** * Register a relation type (an arc) for Brat */ var relationTypesSet = {}; var relationTypes = []; function addRelationType(type, symmetricEdge) { // Prevent adding duplicates if (relationTypesSet[type]) return; relationTypesSet[type] = true; // Default arguments if (typeof symmetricEdge === 'undefined') { symmetricEdge = false; } // Add the type relationTypes.push({ type: type, labels: [type], dashArray: (symmetricEdge ? '3,3' : undefined), arrowHead: (symmetricEdge ? 'none' : undefined), }); } // // Construct text of annotation // currentText = []; // GLOBAL currentSentences = data.sentences; // GLOBAL data.sentences.forEach(function(sentence) { for (var i = 0; i < sentence.tokens.length; ++i) { var token = sentence.tokens[i]; var word = token.word; if (!(typeof tokensMap[word] === "undefined")) { word = tokensMap[word]; } if (i > 0) { currentText.push(' '); } token.characterOffsetBegin = currentText.length; for (var j = 0; j < word.length; ++j) { currentText.push(word[j]); } token.characterOffsetEnd = currentText.length; } currentText.push('\n'); }); currentText = currentText.join(''); // // Shared variables // These are what we'll render in BRAT // // (pos) var posEntities = []; // (lemma) var lemmaEntities = []; // (ner) var nerEntities = []; var nerEntitiesNormalized = []; // (sentiment) var sentimentEntities = []; // (entitylinking) var linkEntities = []; // (dependencies) var depsRelations = []; var deps2Relations = []; // (openie) var openieEntities = []; var openieEntitiesSet = {}; var openieRelations = []; var openieRelationsSet = {}; // (kbp) var kbpEntities = []; var kbpEntitiesSet = []; var kbpRelations = []; var kbpRelationsSet = []; var cparseEntities = []; var cparseRelations = []; var speakerEntities = []; // // Loop over sentences. // This fills in the variables above. // for (var sentI = 0; sentI < data.sentences.length; ++sentI) { var sentence = data.sentences[sentI]; var index = sentence.index; var tokens = sentence.tokens; var deps = sentence['basicDependencies']; var deps2 = sentence['enhancedPlusPlusDependencies']; var parseTree = sentence['parseTree']; // Speakers if (tokens.length > 0 && typeof tokens[0].speaker !== 'undefined') { var speaker = tokens[0].speaker; var speakerId = 'S(' + speaker + ')'; addEntityType('SPEAKER', speakerId); var begin = parseInt(tokens[0].characterOffsetBegin); var end = parseInt(tokens[tokens.length-1].characterOffsetEnd); speakerEntities.push(['sent' + sentI, speakerId, [[begin, end]]]); } // POS tags /** * Generate a POS tagged token id */ function posID(i) { return 'POS_' + sentI + '_' + i; } if (tokens.length > 0 && typeof tokens[0].pos !== 'undefined') { for (var i = 0; i < tokens.length; i++) { var token = tokens[i]; var pos = token.pos; var begin = parseInt(token.characterOffsetBegin); var end = parseInt(token.characterOffsetEnd); addEntityType('POS', pos); posEntities.push([posID(i), pos, [[begin, end]]]); } } // Constituency parse // Carries the same assumption as NER if (parseTree && !useDagre) { var parseEntities = []; var parseRels = []; function processParseTree(tree, index) { tree.visitIndex = index; index++; if (tree.isTerminal) { parseEntities[tree.visitIndex] = posEntities[tree.tokenIndex]; return index; } else if (tree.children) { addEntityType('PARSENODE', tree.label); parseEntities[tree.visitIndex] = ['PARSENODE_' + sentI + '_' + tree.visitIndex, tree.label, [[tokens[tree.tokenStart].characterOffsetBegin, tokens[tree.tokenEnd-1].characterOffsetEnd]]]; var parentEnt = parseEntities[tree.visitIndex]; for (var i = 0; i < tree.children.length; i++) { var child = tree.children[i]; index = processParseTree(child, index); var childEnt = parseEntities[child.visitIndex]; addRelationType('pc'); parseRels.push(['PARSEEDGE_' + sentI + '_' + parseRels.length, 'pc', [['parent', parentEnt[0]], ['child', childEnt[0]]]]); } } return index; } processParseTree(parseTree, 0); cparseEntities = cparseEntities.concat(cparseEntities, parseEntities); cparseRelations = cparseRelations.concat(parseRels); } // Dependency parsing /** * Process a dependency tree from JSON to Brat relations */ function processDeps(name, deps) { var relations = []; // Format: [${ID}, ${TYPE}, [[${ARGNAME}, ${TARGET}], [${ARGNAME}, ${TARGET}]]] for (var i = 0; i < deps.length; i++) { var dep = deps[i]; var governor = dep.governor - 1; var dependent = dep.dependent - 1; if (governor == -1) continue; addRelationType(dep.dep); relations.push([name + '_' + sentI + '_' + i, dep.dep, [['governor', posID(governor)], ['dependent', posID(dependent)]]]); } return relations; } // Actually add the dependencies if (typeof deps !== 'undefined') { depsRelations = depsRelations.concat(processDeps('dep', deps)); } if (typeof deps2 !== 'undefined') { deps2Relations = deps2Relations.concat(processDeps('dep2', deps2)); } // Lemmas if (tokens.length > 0 && typeof tokens[0].lemma !== 'undefined') { for (var i = 0; i < tokens.length; i++) { var token = tokens[i]; var lemma = token.lemma; var begin = parseInt(token.characterOffsetBegin); var end = parseInt(token.characterOffsetEnd); addEntityType('LEMMA', lemma); lemmaEntities.push(['LEMMA_' + sentI + '_' + i, lemma, [[begin, end]]]); } } // NER tags // Assumption: contiguous occurrence of one non-O is a single entity if (tokens.some(function(token) { return token.ner; })) { for (var i = 0; i < tokens.length; i++) { var ner = tokens[i].ner || 'O'; var normalizedNER = tokens[i].normalizedNER; if (typeof normalizedNER === "undefined") { normalizedNER = ner; } if (ner == 'O') continue; var j = i; while (j < tokens.length - 1 && tokens[j+1].ner == ner) j++; addEntityType('NER', ner, ner); nerEntities.push(['NER_' + sentI + '_' + i, ner, [[tokens[i].characterOffsetBegin, tokens[j].characterOffsetEnd]]]); if (ner != normalizedNER) { addEntityType('NNER', normalizedNER, ner); nerEntities.push(['NNER_' + sentI + '_' + i, normalizedNER, [[tokens[i].characterOffsetBegin, tokens[j].characterOffsetEnd]]]); } i = j; } } // Sentiment if (typeof sentence.sentiment !== "undefined") { var sentiment = sentence.sentiment.toUpperCase().replace("VERY", "VERY "); addEntityType('SENTIMENT', sentiment); sentimentEntities.push(['SENTIMENT_' + sentI, sentiment, [[tokens[0].characterOffsetBegin, tokens[tokens.length - 1].characterOffsetEnd]]]); } // Entity Links // Carries the same assumption as NER if (tokens.length > 0) { for (var i = 0; i < tokens.length; i++) { var link = tokens[i].entitylink; if (link == 'O' || typeof link === 'undefined') continue; var j = i; while (j < tokens.length - 1 && tokens[j+1].entitylink == link) j++; addEntityType('LINK', link); linkEntities.push(['LINK_' + sentI + '_' + i, link, [[tokens[i].characterOffsetBegin, tokens[j].characterOffsetEnd]]]); i = j; } } // Open IE // Helper Functions function openieID(span) { return 'OPENIEENTITY' + '_' + sentI + '_' + span[0] + '_' + span[1]; } function addEntity(span, role) { // Don't add duplicate entities if (openieEntitiesSet[[sentI, span, role]]) return; openieEntitiesSet[[sentI, span, role]] = true; // Add the entity openieEntities.push([openieID(span), role, [[tokens[span[0]].characterOffsetBegin, tokens[span[1] - 1].characterOffsetEnd ]] ]); } function addRelation(gov, dep, role) { // Don't add duplicate relations if (openieRelationsSet[[sentI, gov, dep, role]]) return; openieRelationsSet[[sentI, gov, dep, role]] = true; // Add the relation openieRelations.push(['OPENIESUBJREL_' + sentI + '_' + gov[0] + '_' + gov[1] + '_' + dep[0] + '_' + dep[1], role, [['governor', openieID(gov)], ['dependent', openieID(dep)] ] ]); } // Render OpenIE if (typeof sentence.openie !== 'undefined') { // Register the entities + relations we'll need addEntityType('ENTITY', 'Entity'); addEntityType('RELATION', 'Relation'); addRelationType('subject'); addRelationType('object'); // Loop over triples for (var i = 0; i < sentence.openie.length; ++i) { var subjectSpan = sentence.openie[i].subjectSpan; var relationSpan = sentence.openie[i].relationSpan; var objectSpan = sentence.openie[i].objectSpan; if (parseInt(relationSpan[0]) < 0 || parseInt(relationSpan[1]) < 0) { continue; // This is a phantom relation } var begin = parseInt(token.characterOffsetBegin); // Add the entities addEntity(subjectSpan, 'Entity'); addEntity(relationSpan, 'Relation'); addEntity(objectSpan, 'Entity'); // Add the relations addRelation(relationSpan, subjectSpan, 'subject'); addRelation(relationSpan, objectSpan, 'object'); } } // End OpenIE block // // KBP // // Helper Functions function kbpEntity(span) { return 'KBPENTITY' + '_' + sentI + '_' + span[0] + '_' + span[1]; } function addKBPEntity(span, role) { // Don't add duplicate entities if (kbpEntitiesSet[[sentI, span, role]]) return; kbpEntitiesSet[[sentI, span, role]] = true; // Add the entity kbpEntities.push([kbpEntity(span), role, [[tokens[span[0]].characterOffsetBegin, tokens[span[1] - 1].characterOffsetEnd ]] ]); } function addKBPRelation(gov, dep, role) { // Don't add duplicate relations if (kbpRelationsSet[[sentI, gov, dep, role]]) return; kbpRelationsSet[[sentI, gov, dep, role]] = true; // Add the relation kbpRelations.push(['KBPRELATION_' + sentI + '_' + gov[0] + '_' + gov[1] + '_' + dep[0] + '_' + dep[1], role, [['governor', kbpEntity(gov)], ['dependent', kbpEntity(dep)] ] ]); } if (typeof sentence.kbp !== 'undefined') { // Register the entities + relations we'll need addRelationType('subject'); addRelationType('object'); // Loop over triples for (var i = 0; i < sentence.kbp.length; ++i) { var subjectSpan = sentence.kbp[i].subjectSpan; var subjectLink = 'Entity'; for (var k = subjectSpan[0]; k < subjectSpan[1]; ++k) { if (subjectLink == 'Entity' && typeof tokens[k] !== 'undefined' && tokens[k].entitylink != 'O' && typeof tokens[k].entitylink !== 'undefined') { subjectLink = tokens[k].entitylink } } addEntityType('KBP_ENTITY', subjectLink); var objectSpan = sentence.kbp[i].objectSpan; var objectLink = 'Entity'; for (var k = objectSpan[0]; k < objectSpan[1]; ++k) { if (objectLink == 'Entity' && typeof tokens[k] !== 'undefined' && tokens[k].entitylink != 'O' && typeof tokens[k].entitylink !== 'undefined') { objectLink = tokens[k].entitylink } } addEntityType('KBP_ENTITY', objectLink); var relation = sentence.kbp[i].relation; var begin = parseInt(token.characterOffsetBegin); // Add the entities addKBPEntity(subjectSpan, subjectLink); addKBPEntity(objectSpan, objectLink); // Add the relations addKBPRelation(subjectSpan, objectSpan, relation); } } // End KBP block } // End sentence loop // // Coreference // var corefEntities = []; var corefRelations = []; corefColors = []; corefColorIndex = {}; speakerColors = []; speakerColorIndex = {}; if (typeof data.corefs !== 'undefined') { addRelationType('coref', true); addEntityType('COREF', 'Mention'); var clusters = Object.keys(data.corefs); clusters.forEach( function (clusterId) { var chain = data.corefs[clusterId]; if (chain.length > 1) { var entityChainId = 'CorefEntity' + clusterId; addEntityType('COREF', entityChainId); for (var i = 0; i < chain.length; ++i) { var mention = chain[i]; var id = 'COREF' + mention.id; var tokens = data.sentences[mention.sentNum - 1].tokens; corefEntities.push([id, entityChainId, [[tokens[mention.startIndex - 1].characterOffsetBegin, tokens[mention.endIndex - 2].characterOffsetEnd ]] ]); // if (i > 0) { // var lastId = 'COREF' + chain[i - 1].id; // corefRelations.push(['COREF' + chain[i-1].id + '_' + chain[i].id, // 'coref', // [['governor', lastId], // ['dependent', id] ] ]); // } } } }); } // End coreference block // // Actually render the elements // /** * Helper function to render a given set of entities / relations * to a Div, if it exists. */ function embed(container, entities, relations, reverse) { var text = currentText; if (reverse) { var length = currentText.length; for (var i = 0; i < entities.length; ++i) { var offsets = entities[i][2][0]; var tmp = length - offsets[0]; offsets[0] = length - offsets[1]; offsets[1] = tmp; } text = text.split("").reverse().join(""); } if ($('#' + container).length > 0) { Util.embed(container, {entity_types: entityTypes, relation_types: relationTypes}, {text: text, entities: entities, relations: relations} ); } } // Render each annotation head.ready(function() { embed('pos', posEntities); embed('lemma', lemmaEntities); embed('ner', nerEntities); embed('entities', linkEntities); if (!useDagre) { embed('parse', cparseEntities, cparseRelations); } embed('deps', posEntities, depsRelations); embed('deps2', posEntities, deps2Relations); if (speakerEntities.length) { embed('speakers', speakerEntities); } embed('coref', corefEntities, corefRelations); embed('openie', openieEntities, openieRelations); embed('kbp', kbpEntities, kbpRelations); embed('sentiment', sentimentEntities); // Constituency parse // Uses d3 and dagre-d3 (not brat) if ($('#parse').length > 0 && useDagre) { var parseViewer = new ParseViewer({ selector: '#parse' }); parseViewer.showAnnotation(data); $('#parse').addClass('svg').css('display', 'block'); } }); } // End render function /** * Render a TokensRegex response */ function renderTokensregex(data) { /**COREF' * Register an entity type (a tag) for Brat */ var entityTypesSet = {}; var entityTypes = []; function addEntityType(type, color) { // Don't add duplicates if (entityTypesSet[type]) return; entityTypesSet[type] = true; // Set the color if (typeof color === 'undefined') { color = '#ADF6A2'; } // Register the type entityTypes.push({ type: type, labels : [type], bgColor: color, borderColor: 'darken' }); } var entities = []; for (var sentI = 0; sentI < data.sentences.length; ++sentI) { var tokens = currentSentences[sentI].tokens; for (var matchI = 0; matchI < data.sentences[sentI].length; ++matchI) { var match = data.sentences[sentI][matchI]; // Add groups for (groupName in match) { if (groupName.startsWith("$") || isInt(groupName)) { addEntityType(groupName, '#FFFDA8'); var begin = parseInt(tokens[match[groupName].begin].characterOffsetBegin); var end = parseInt(tokens[match[groupName].end - 1].characterOffsetEnd); entities.push(['TOK_' + sentI + '_' + matchI + '_' + groupName, groupName, [[begin, end]]]); } } // Add match addEntityType('match', '#ADF6A2'); var begin = parseInt(tokens[match.begin].characterOffsetBegin); var end = parseInt(tokens[match.end - 1].characterOffsetEnd); entities.push(['TOK_' + sentI + '_' + matchI + '_match', 'match', [[begin, end]]]); } } Util.embed('tokensregex', {entity_types: entityTypes, relation_types: []}, {text: currentText, entities: entities, relations: []} ); } // END renderTokensregex() /** * Render a Semgrex response */ function renderSemgrex(data) { /** * Register an entity type (a tag) for Brat */ var entityTypesSet = {}; var entityTypes = []; function addEntityType(type, color) { // Don't add duplicates if (entityTypesSet[type]) return; entityTypesSet[type] = true; // Set the color if (typeof color === 'undefined') { color = '#ADF6A2'; } // Register the type entityTypes.push({ type: type, labels : [type], bgColor: color, borderColor: 'darken' }); } relationTypes = [{ type: 'semgrex', labels: ['-'], dashArray: '3,3', arrowHead: 'none', }]; var entities = []; var relations = []; for (var sentI = 0; sentI < data.sentences.length; ++sentI) { var tokens = currentSentences[sentI].tokens; for (var matchI = 0; matchI < data.sentences[sentI].length; ++matchI) { var match = data.sentences[sentI][matchI]; // Add match addEntityType('match', '#ADF6A2'); var begin = parseInt(tokens[match.begin].characterOffsetBegin); var end = parseInt(tokens[match.end - 1].characterOffsetEnd); entities.push(['SEM_' + sentI + '_' + matchI + '_match', 'match', [[begin, end]]]); // Add groups for (groupName in match) { if (groupName.startsWith("$") || isInt(groupName)) { // (add node) group = match[groupName]; groupName = groupName.substring(1); addEntityType(groupName, '#FFFDA8'); var begin = parseInt(tokens[group.begin].characterOffsetBegin); var end = parseInt(tokens[group.end - 1].characterOffsetEnd); entities.push(['SEM_' + sentI + '_' + matchI + '_' + groupName, groupName, [[begin, end]]]); // (add relation) relations.push(['SEMGREX_' + sentI + '_' + matchI + '_' + groupName, 'semgrex', [['governor', 'SEM_' + sentI + '_' + matchI + '_match'], ['dependent', 'SEM_' + sentI + '_' + matchI + '_' + groupName] ] ]); } } } } Util.embed('semgrex', {entity_types: entityTypes, relation_types: relationTypes}, {text: currentText, entities: entities, relations: relations} ); } // END renderSemgrex /** * Render a Tregex response */ function renderTregex(data) { $('#tregex').empty(); $('#tregex').append('
' + JSON.stringify(data, null, 4) + '
'); } // END renderTregex // ---------------------------------------------------------------------------- // MAIN // ---------------------------------------------------------------------------- /** * MAIN() * * The entry point of the page */ $(document).ready(function() { // Some initial styling $('.chosen-select').chosen(); $('.chosen-container').css('width', '100%'); // Language-specific changes $('#language').on('change', function() { $('#text').attr('dir', ''); if ($('#language').val() === 'ar') { $('#text').attr('dir', 'rtl'); $('#text').attr('placeholder', 'على سبيل المثال، قفز الثعلب البني السريع فوق الكلب الكسول.'); } else if ($('#language').val() === 'en') { $('#text').attr('placeholder', 'e.g., The quick brown fox jumped over the lazy dog.'); } else if ($('#language').val() === 'zh') { $('#text').attr('placeholder', '例如,快速的棕色狐狸跳過了懶惰的狗。'); } else if ($('#language').val() === 'fr') { $('#text').attr('placeholder', 'Par exemple, le renard brun rapide a sauté sur le chien paresseux.'); } else if ($('#language').val() === 'de') { $('#text').attr('placeholder', 'Z. B. sprang der schnelle braune Fuchs über den faulen Hund.'); } else if ($('#language').val() === 'es') { $('#text').attr('placeholder', 'Por ejemplo, el rápido zorro marrón saltó sobre el perro perezoso.'); } else { $('#text').attr('placeholder', 'Unknown language for placeholder query: ' + $('#language').val()); } }); // Submit on shift-enter $('#text').keydown(function (event) { if (event.keyCode == 13) { if(event.shiftKey){ event.preventDefault(); // don't register the enter key when pressed return false; } } }); $('#text').keyup(function (event) { if (event.keyCode == 13) { if(event.shiftKey){ $('#submit').click(); // submit the form when the enter key is released event.stopPropagation(); return false; } } }); // Submit on clicking the 'submit' button $('#submit').click(function() { // Get the text to annotate currentQuery = $('#text').val(); if (currentQuery.trim() == '') { if ($('#language').val() === 'ar') { currentQuery = 'قفز الثعلب البني السريع فوق الكلب الكسول.'; } else if ($('#language').val() === 'en') { currentQuery = 'The quick brown fox jumped over the lazy dog.'; } else if ($('#language').val() === 'zh') { currentQuery = '快速的棕色狐狸跳过了懒惰的狗'; } else if ($('#language').val() === 'fr') { currentQuery = 'Le renard brun rapide a sauté sur le chien paresseux.'; } else if ($('#language').val() === 'de') { currentQuery = 'Sprang der schnelle braune Fuchs über den faulen Hund.'; } else if ($('#language').val() === 'es') { currentQuery = 'El rápido zorro marrón saltó sobre el perro perezoso.'; } else { currentQuery = 'Unknown language for default query: ' + $('#language').val(); } $('#text').val(currentQuery); } // Update the UI $('#submit').prop('disabled', true); $('#annotations').hide(); $('#patterns_row').hide(); $('#loading').show(); // Run query $.ajax({ type: 'POST', url: serverAddress + '?properties=' + encodeURIComponent( '{"annotators": "' + annotators() + '", "date": "' + date() + '"}') + '&pipelineLanguage=' + encodeURIComponent($('#language').val()), data: encodeURIComponent(currentQuery), //jQuery doesn't automatically URI encode strings dataType: 'json', contentType: "application/x-www-form-urlencoded;charset=UTF-8", success: function(data) { $('#submit').prop('disabled', false); if (typeof data === 'undefined' || data.sentences == undefined) { alert("Failed to reach server!"); } else { // Process constituency parse var constituencyParseProcessor = new ConstituencyParseProcessor(); constituencyParseProcessor.process(data); // Empty divs $('#annotations').empty(); // Re-render divs function createAnnotationDiv(id, annotator, selector, label) { // (make sure we requested that element) if (annotators().indexOf(annotator) < 0) { return; } // (make sure the data contains that element) ok = false; if (typeof data[selector] !== 'undefined') { ok = true; } else if (typeof data.sentences !== 'undefined' && data.sentences.length > 0) { if (typeof data.sentences[0][selector] !== 'undefined') { ok = true; } else if (typeof data.sentences[0].tokens != 'undefined' && data.sentences[0].tokens.length > 0) { // (make sure the annotator select is in at least one of the tokens of any sentence) ok = data.sentences.some(function(sentence) { return sentence.tokens.some(function(token) { return typeof token[selector] !== 'undefined'; }); }); } } // (render the element) if (ok) { $('#annotations').append('

' + label + ':

'); } } // (create the divs) // div id annotator field_in_data label createAnnotationDiv('pos', 'pos', 'pos', 'Part-of-Speech' ); createAnnotationDiv('lemma', 'lemma', 'lemma', 'Lemmas' ); createAnnotationDiv('ner', 'ner', 'ner', 'Named Entity Recognition'); createAnnotationDiv('parse', 'parse', 'parseTree', 'Constituency Parse' ); createAnnotationDiv('deps', 'depparse', 'basicDependencies', 'Basic Dependencies' ); createAnnotationDiv('deps2', 'depparse', 'enhancedPlusPlusDependencies', 'Enhanced++ Dependencies' ); createAnnotationDiv('openie', 'openie', 'openie', 'Open IE' ); createAnnotationDiv('speakers', 'coref', 'corefs', 'Speakers' ); createAnnotationDiv('coref', 'coref', 'corefs', 'Coreference' ); createAnnotationDiv('entities', 'entitylink', 'entitylink', 'Wikidict Entities' ); createAnnotationDiv('kbp', 'kbp', 'kbp', 'KBP Relations' ); createAnnotationDiv('sentiment','sentiment', 'sentiment', 'Sentiment' ); // Update UI $('#loading').hide(); $('.corenlp_error').remove(); // Clear error messages $('#annotations').show(); // Render var reverse = $('#language').val() === 'ar'; render(data, reverse); // Render patterns $('#annotations').append('

CoreNLP Tools:

'); // TODO(gabor) a strange place to add this header to $('#patterns_row').show(); } }, error: function(data) { DATA = data; var alertDiv = $('
').addClass('alert').addClass('alert-danger').addClass('alert-dismissible').addClass('corenlp_error').attr('role', 'alert') var button = $(''); var message = $('').text(data.responseText); button.appendTo(alertDiv); message.appendTo(alertDiv); $('#loading').hide(); alertDiv.appendTo($('#errors')); $('#submit').prop('disabled', false); } }); event.preventDefault(); event.stopPropagation(); return false; }); // Support passing parameters on page launch, via window.location.hash parameters. // Example: http://localhost:9000/#text=foo%20bar&annotators=pos,lemma,ner (function() { var rawParams = window.location.hash.slice(1).split("&"); var params = {}; rawParams.forEach(function(paramKV) { paramKV = paramKV.split("="); if (paramKV.length === 2) { var key = paramKV[0]; var value = paramKV[1]; params[key] = value; } }); if (params.text) { var text = decodeURIComponent(params.text); $('#text').val(text); } if (params.annotators) { var annotators = params.annotators.split(","); // De-select everything $('#annotators').find('option').each(function() { $(this).prop('selected', false); }); // Select the specified ones. annotators.forEach(function(a) { $('#annotators').find('option[value="'+a+'"]').prop('selected', true); }); // Refresh Chosen $('#annotators').trigger('chosen:updated'); } if (params.text || params.annotators) { // Finally, let's auto-submit. $('#submit').click(); } })(); $('#form_tokensregex').submit( function (e) { // Don't actually submit the form e.preventDefault(); // Get text if ($('#tokensregex_search').val().trim() == '') { $('#tokensregex_search').val('(?$foxtype [{pos:JJ}]+ ) fox'); } var pattern = $('#tokensregex_search').val(); // Remove existing annotation $('#tokensregex').remove(); // Make ajax call $.ajax({ type: 'POST', url: serverAddress + '/tokensregex?pattern=' + encodeURIComponent( pattern.replace("&", "\\&").replace('+', '\\+')) + '&properties=' + encodeURIComponent( '{"annotators": "' + annotators() + '", "date": "' + date() + '"}') + '&pipelineLanguage=' + encodeURIComponent($('#language').val()), data: encodeURIComponent(currentQuery), success: function(data) { $('.tokensregex_error').remove(); // Clear error messages $('
').appendTo($('#div_tokensregex')); renderTokensregex(data); }, error: function(data) { var alertDiv = $('
').addClass('alert').addClass('alert-danger').addClass('alert-dismissible').addClass('tokensregex_error').attr('role', 'alert') var button = $(''); var message = $('').text(data.responseText); button.appendTo(alertDiv); message.appendTo(alertDiv); alertDiv.appendTo($('#div_tokensregex')); } }); }); $('#form_semgrex').submit( function (e) { // Don't actually submit the form e.preventDefault(); // Get text if ($('#semgrex_search').val().trim() == '') { $('#semgrex_search').val('{pos:/VB.*/} >nsubj {}=subject >/nmod:.*/ {}=prep_phrase'); } var pattern = $('#semgrex_search').val(); // Remove existing annotation $('#semgrex').remove(); // Add missing required annotators var requiredAnnotators = annotators().split(','); if (requiredAnnotators.indexOf('depparse') < 0) { requiredAnnotators.push('depparse'); } // Make ajax call $.ajax({ type: 'POST', url: serverAddress + '/semgrex?pattern=' + encodeURIComponent( pattern.replace("&", "\\&").replace('+', '\\+')) + '&properties=' + encodeURIComponent( '{"annotators": "' + requiredAnnotators.join(',') + '", "date": "' + date() + '"}') + '&pipelineLanguage=' + encodeURIComponent($('#language').val()), data: encodeURIComponent(currentQuery), success: function(data) { $('.semgrex_error').remove(); // Clear error messages $('
').appendTo($('#div_semgrex')); renderSemgrex(data); }, error: function(data) { var alertDiv = $('
').addClass('alert').addClass('alert-danger').addClass('alert-dismissible').addClass('semgrex_error').attr('role', 'alert') var button = $(''); var message = $('').text(data.responseText); button.appendTo(alertDiv); message.appendTo(alertDiv); alertDiv.appendTo($('#div_semgrex')); } }); }); $('#form_tregex').submit( function (e) { // Don't actually submit the form e.preventDefault(); // Get text if ($('#tregex_search').val().trim() == '') { $('#tregex_search').val('NP < NN=animal'); } var pattern = $('#tregex_search').val(); // Remove existing annotation $('#tregex').remove(); // Add missing required annotators var requiredAnnotators = annotators().split(','); if (requiredAnnotators.indexOf('parse') < 0) { requiredAnnotators.push('parse'); } // Make ajax call $.ajax({ type: 'POST', url: serverAddress + '/tregex?pattern=' + encodeURIComponent( pattern.replace("&", "\\&").replace('+', '\\+')) + '&properties=' + encodeURIComponent( '{"annotators": "' + requiredAnnotators.join(',') + '", "date": "' + date() + '"}') + '&pipelineLanguage=' + encodeURIComponent($('#language').val()), data: encodeURIComponent(currentQuery), success: function(data) { $('.tregex_error').remove(); // Clear error messages $('
').appendTo($('#div_tregex')); renderTregex(data); }, error: function(data) { var alertDiv = $('
').addClass('alert').addClass('alert-danger').addClass('alert-dismissible').addClass('tregex_error').attr('role', 'alert') var button = $(''); var message = $('').text(data.responseText); button.appendTo(alertDiv); message.appendTo(alertDiv); alertDiv.appendTo($('#div_tregex')); } }); }); });