From 311d15f49331527d038a1859c6fc1f6a5585fdbf Mon Sep 17 00:00:00 2001 From: David Huynh Date: Tue, 9 Mar 2010 06:57:08 +0000 Subject: [PATCH] Re-organized column header popup menus and added a bunch of common facets and common cell edit transforms. Added native syntax for regex in GEL and modified replace, split, partition, and rpartition functions to support regex. Removed function replaceRegex. git-svn-id: http://google-refine.googlecode.com/svn/trunk@249 7d457c2a-affb-35e4-300a-418c747d4874 --- .../expr/functions/strings/Partition.java | 58 ++++-- .../expr/functions/strings/RPartition.java | 59 ++++-- .../expr/functions/strings/Replace.java | 15 +- .../expr/functions/strings/ReplaceRegexp.java | 37 ---- .../expr/functions/strings/Split.java | 15 +- .../gel/ControlFunctionRegistry.java | 2 - .../com/metaweb/gridworks/gel/Parser.java | 47 +++-- .../com/metaweb/gridworks/gel/Scanner.java | 54 ++++- .../dialogs/expression-preview-dialog.js | 5 +- .../views/data-table-column-header-ui.js | 184 +++++++++++++----- 10 files changed, 328 insertions(+), 148 deletions(-) delete mode 100644 src/main/java/com/metaweb/gridworks/expr/functions/strings/ReplaceRegexp.java diff --git a/src/main/java/com/metaweb/gridworks/expr/functions/strings/Partition.java b/src/main/java/com/metaweb/gridworks/expr/functions/strings/Partition.java index 0bf7996ac..d9cf93e47 100644 --- a/src/main/java/com/metaweb/gridworks/expr/functions/strings/Partition.java +++ b/src/main/java/com/metaweb/gridworks/expr/functions/strings/Partition.java @@ -1,6 +1,8 @@ package com.metaweb.gridworks.expr.functions.strings; import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.json.JSONException; import org.json.JSONWriter; @@ -10,22 +12,53 @@ import com.metaweb.gridworks.gel.Function; public class Partition implements Function { public Object call(Properties bindings, Object[] args) { - if (args.length == 2) { + if (args.length >= 2 && args.length <= 3) { Object o1 = args[0]; Object o2 = args[1]; - if (o1 != null && o2 != null && o1 instanceof String && o2 instanceof String) { + + boolean omitFragment = false; + if (args.length == 3) { + Object o3 = args[2]; + if (o3 instanceof Boolean) { + omitFragment = ((Boolean) o3).booleanValue(); + } + } + + if (o1 != null && o2 != null && o1 instanceof String) { String s = (String) o1; - String frag = (String) o2; - int index = s.indexOf(frag); - String[] output = new String[3]; - if (index > -1) { - output[0] = s.substring(0, index); - output[1] = frag; - output[2] = s.substring(index + frag.length(), s.length()); + + int from = -1; + int to = -1; + + if (o2 instanceof String) { + String frag = (String) o2; + + from = s.indexOf(frag); + to = from + frag.length(); + } else if (o2 instanceof Pattern) { + Pattern pattern = (Pattern) o2; + Matcher matcher = pattern.matcher(s); + if (matcher.find()) { + from = matcher.start(); + to = matcher.end(); + } + } + + String[] output = omitFragment ? new String[2] : new String[3]; + if (from > -1) { + output[0] = s.substring(0, from); + if (omitFragment) { + output[1] = s.substring(to); + } else { + output[1] = s.substring(from, to); + output[2] = s.substring(to); + } } else { output[0] = s; output[1] = ""; - output[2] = ""; + if (!omitFragment) { + output[2] = ""; + } } return output; } @@ -37,8 +70,9 @@ public class Partition implements Function { throws JSONException { writer.object(); - writer.key("description"); writer.value("Returns an array of strings [a,frag,b] where a is the string part before the first occurrence of frag in s and b is what's left."); - writer.key("params"); writer.value("string s, string frag"); + writer.key("description"); writer.value( + "Returns an array of strings [a,frag,b] where a is the string part before the first occurrence of frag in s and b is what's left. If omitFragment is true, frag is not returned."); + writer.key("params"); writer.value("string s, string or regex frag, optional boolean omitFragment"); writer.key("returns"); writer.value("array"); writer.endObject(); } diff --git a/src/main/java/com/metaweb/gridworks/expr/functions/strings/RPartition.java b/src/main/java/com/metaweb/gridworks/expr/functions/strings/RPartition.java index 15543f887..3dfadaeff 100644 --- a/src/main/java/com/metaweb/gridworks/expr/functions/strings/RPartition.java +++ b/src/main/java/com/metaweb/gridworks/expr/functions/strings/RPartition.java @@ -1,6 +1,8 @@ package com.metaweb.gridworks.expr.functions.strings; import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.json.JSONException; import org.json.JSONWriter; @@ -10,22 +12,54 @@ import com.metaweb.gridworks.gel.Function; public class RPartition implements Function { public Object call(Properties bindings, Object[] args) { - if (args.length == 2) { + if (args.length >= 2 && args.length <= 3) { Object o1 = args[0]; Object o2 = args[1]; - if (o1 != null && o2 != null && o1 instanceof String && o2 instanceof String) { + + boolean omitFragment = false; + if (args.length == 3) { + Object o3 = args[2]; + if (o3 instanceof Boolean) { + omitFragment = ((Boolean) o3).booleanValue(); + } + } + + if (o1 != null && o2 != null && o1 instanceof String) { String s = (String) o1; - String frag = (String) o2; - int index = s.lastIndexOf(frag); - String[] output = new String[3]; - if (index > -1) { - output[0] = s.substring(0, index); - output[1] = frag; - output[2] = s.substring(index + frag.length(), s.length()); + + int from = -1; + int to = -1; + + if (o2 instanceof String) { + String frag = (String) o2; + + from = s.lastIndexOf(frag); + to = from + frag.length(); + } else if (o2 instanceof Pattern) { + Pattern pattern = (Pattern) o2; + Matcher matcher = pattern.matcher(s); + + while (matcher.find()) { + from = matcher.start(); + to = matcher.end(); + } + } + + String[] output = omitFragment ? new String[2] : new String[3]; + if (from > -1) { + output[0] = s.substring(0, from); + if (omitFragment) { + output[1] = s.substring(to); + } else { + output[1] = s.substring(from, to); + output[2] = s.substring(to); + } } else { output[0] = s; output[1] = ""; - output[2] = ""; + if (!omitFragment) { + output[2] = ""; + } } return output; } @@ -37,8 +71,9 @@ public class RPartition implements Function { throws JSONException { writer.object(); - writer.key("description"); writer.value("Returns an array of strings [a,frag,b] where a is the string part before the last occurrence of frag in s and b is what's left."); - writer.key("params"); writer.value("string s, string frag"); + writer.key("description"); writer.value( + "Returns an array of strings [a,frag,b] where a is the string part before the last occurrence of frag in s and b is what's left. If omitFragment is true, frag is not returned."); + writer.key("params"); writer.value("string s, string or regex frag, optional boolean omitFragment"); writer.key("returns"); writer.value("array"); writer.endObject(); } diff --git a/src/main/java/com/metaweb/gridworks/expr/functions/strings/Replace.java b/src/main/java/com/metaweb/gridworks/expr/functions/strings/Replace.java index d3be5d38b..10ac2e2f1 100644 --- a/src/main/java/com/metaweb/gridworks/expr/functions/strings/Replace.java +++ b/src/main/java/com/metaweb/gridworks/expr/functions/strings/Replace.java @@ -1,6 +1,7 @@ package com.metaweb.gridworks.expr.functions.strings; import java.util.Properties; +import java.util.regex.Pattern; import org.json.JSONException; import org.json.JSONWriter; @@ -16,12 +17,18 @@ public class Replace implements Function { Object o1 = args[0]; Object o2 = args[1]; Object o3 = args[2]; - if (o1 != null && o2 != null && o3 != null && o2 instanceof String && o3 instanceof String) { + if (o1 != null && o2 != null && o3 != null && o3 instanceof String) { String str = (o1 instanceof String) ? (String) o1 : o1.toString(); - return str.replace((String) o2, (String) o3); + + if (o2 instanceof String) { + return str.replace((String) o2, (String) o3); + } else if (o2 instanceof Pattern) { + Pattern pattern = (Pattern) o2; + return pattern.matcher(str).replaceAll((String) o3); + } } } - return new EvalError(ControlFunctionRegistry.getFunctionName(this) + " expects 3 strings"); + return new EvalError(ControlFunctionRegistry.getFunctionName(this) + " expects 3 strings, or 1 string, 1 regex, and 1 string"); } @@ -30,7 +37,7 @@ public class Replace implements Function { writer.object(); writer.key("description"); writer.value("Returns the string obtained by replacing f with r in s"); - writer.key("params"); writer.value("string s, string f, string r"); + writer.key("params"); writer.value("string s, string or regex f, string r"); writer.key("returns"); writer.value("string"); writer.endObject(); } diff --git a/src/main/java/com/metaweb/gridworks/expr/functions/strings/ReplaceRegexp.java b/src/main/java/com/metaweb/gridworks/expr/functions/strings/ReplaceRegexp.java deleted file mode 100644 index e3a501c50..000000000 --- a/src/main/java/com/metaweb/gridworks/expr/functions/strings/ReplaceRegexp.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.metaweb.gridworks.expr.functions.strings; - -import java.util.Properties; - -import org.json.JSONException; -import org.json.JSONWriter; - -import com.metaweb.gridworks.expr.EvalError; -import com.metaweb.gridworks.gel.ControlFunctionRegistry; -import com.metaweb.gridworks.gel.Function; - -public class ReplaceRegexp implements Function { - - public Object call(Properties bindings, Object[] args) { - if (args.length == 3) { - Object o1 = args[0]; - Object o2 = args[1]; - Object o3 = args[2]; - if (o1 != null && o2 != null && o3 != null && o2 instanceof String && o3 instanceof String) { - String str = (o1 instanceof String) ? (String) o1 : o1.toString(); - return str.replaceAll((String) o2, (String) o3); - } - } - return new EvalError(ControlFunctionRegistry.getFunctionName(this) + " expects 3 strings"); - } - - - public void write(JSONWriter writer, Properties options) - throws JSONException { - - writer.object(); - writer.key("description"); writer.value("Returns the string obtained by replacing f with r in s"); - writer.key("params"); writer.value("string s, string f, string r"); - writer.key("returns"); writer.value("string"); - writer.endObject(); - } -} diff --git a/src/main/java/com/metaweb/gridworks/expr/functions/strings/Split.java b/src/main/java/com/metaweb/gridworks/expr/functions/strings/Split.java index 12731e65a..0f0275e37 100644 --- a/src/main/java/com/metaweb/gridworks/expr/functions/strings/Split.java +++ b/src/main/java/com/metaweb/gridworks/expr/functions/strings/Split.java @@ -1,6 +1,7 @@ package com.metaweb.gridworks.expr.functions.strings; import java.util.Properties; +import java.util.regex.Pattern; import org.json.JSONException; import org.json.JSONWriter; @@ -15,11 +16,17 @@ public class Split implements Function { if (args.length == 2) { Object v = args[0]; Object split = args[1]; - if (v != null && split != null && split instanceof String) { - return (v instanceof String ? (String) v : v.toString()).split((String) split); + if (v != null && split != null) { + String str = (v instanceof String ? (String) v : v.toString()); + if (split instanceof String) { + return str.split((String) split); + } else if (split instanceof Pattern) { + Pattern pattern = (Pattern) split; + return pattern.split(str); + } } } - return new EvalError(ControlFunctionRegistry.getFunctionName(this) + " expects 2 strings"); + return new EvalError(ControlFunctionRegistry.getFunctionName(this) + " expects 2 strings, or 1 string and 1 regex"); } public void write(JSONWriter writer, Properties options) @@ -27,7 +34,7 @@ public class Split implements Function { writer.object(); writer.key("description"); writer.value("Returns the array of strings obtained by splitting s with separator sep"); - writer.key("params"); writer.value("string s, string sep"); + writer.key("params"); writer.value("string s, string or regex sep"); writer.key("returns"); writer.value("array"); writer.endObject(); } diff --git a/src/main/java/com/metaweb/gridworks/gel/ControlFunctionRegistry.java b/src/main/java/com/metaweb/gridworks/gel/ControlFunctionRegistry.java index 65d57cdda..2242a26e6 100644 --- a/src/main/java/com/metaweb/gridworks/gel/ControlFunctionRegistry.java +++ b/src/main/java/com/metaweb/gridworks/gel/ControlFunctionRegistry.java @@ -43,7 +43,6 @@ import com.metaweb.gridworks.expr.functions.strings.RPartition; import com.metaweb.gridworks.expr.functions.strings.Reinterpret; import com.metaweb.gridworks.expr.functions.strings.Replace; import com.metaweb.gridworks.expr.functions.strings.ReplaceChars; -import com.metaweb.gridworks.expr.functions.strings.ReplaceRegexp; import com.metaweb.gridworks.expr.functions.strings.SHA1; import com.metaweb.gridworks.expr.functions.strings.Split; import com.metaweb.gridworks.expr.functions.strings.SplitByCharType; @@ -117,7 +116,6 @@ public class ControlFunctionRegistry { registerFunction("slice", new Slice()); registerFunction("substring", new Slice()); registerFunction("replace", new Replace()); - registerFunction("replaceRegexp", new ReplaceRegexp()); registerFunction("replaceChars", new ReplaceChars()); registerFunction("split", new Split()); registerFunction("splitByCharType", new SplitByCharType()); diff --git a/src/main/java/com/metaweb/gridworks/gel/Parser.java b/src/main/java/com/metaweb/gridworks/gel/Parser.java index 387c20160..aa6dccdd9 100644 --- a/src/main/java/com/metaweb/gridworks/gel/Parser.java +++ b/src/main/java/com/metaweb/gridworks/gel/Parser.java @@ -2,10 +2,12 @@ package com.metaweb.gridworks.gel; import java.util.LinkedList; import java.util.List; +import java.util.regex.Pattern; import com.metaweb.gridworks.expr.Evaluable; import com.metaweb.gridworks.expr.ParsingException; import com.metaweb.gridworks.gel.Scanner.NumberToken; +import com.metaweb.gridworks.gel.Scanner.RegexToken; import com.metaweb.gridworks.gel.Scanner.Token; import com.metaweb.gridworks.gel.Scanner.TokenType; import com.metaweb.gridworks.gel.ast.ControlCallExpr; @@ -26,7 +28,7 @@ public class Parser { public Parser(String s, int from, int to) throws ParsingException { _scanner = new Scanner(s, from, to); - _token = _scanner.next(); + _token = _scanner.next(true); _root = parseExpression(); } @@ -35,8 +37,8 @@ public class Parser { return _root; } - protected void next() { - _token = _scanner.next(); + protected void next(boolean regexPossible) { + _token = _scanner.next(regexPossible); } protected ParsingException makeException(String desc) { @@ -54,7 +56,7 @@ public class Parser { String op = _token.text; - next(); + next(true); Evaluable sub2 = parseSubExpression(); @@ -73,7 +75,7 @@ public class Parser { String op = _token.text; - next(); + next(true); Evaluable sub2 = parseSubExpression(); @@ -92,7 +94,7 @@ public class Parser { String op = _token.text; - next(); + next(true); Evaluable factor2 = parseFactor(); @@ -111,22 +113,27 @@ public class Parser { if (_token.type == TokenType.String) { eval = new LiteralExpr(_token.text); - next(); + next(false); + } else if (_token.type == TokenType.Regex) { + RegexToken t = (RegexToken) _token; + + eval = new LiteralExpr(Pattern.compile(_token.text, t.caseInsensitive ? Pattern.CASE_INSENSITIVE : 0)); + next(false); } else if (_token.type == TokenType.Number) { eval = new LiteralExpr(((NumberToken)_token).value); - next(); + next(false); } else if (_token.type == TokenType.Operator && _token.text.equals("-")) { // unary minus? - next(); + next(true); if (_token != null && _token.type == TokenType.Number) { eval = new LiteralExpr(-((NumberToken)_token).value); - next(); + next(false); } else { throw makeException("Bad negative number"); } } else if (_token.type == TokenType.Identifier) { String text = _token.text; - next(); + next(false); if (_token == null || _token.type != TokenType.Delimiter || !_token.text.equals("(")) { eval = new VariableExpr(text); @@ -137,7 +144,7 @@ public class Parser { throw makeException("Unknown function or control named " + text); } - next(); // swallow ( + next(true); // swallow ( List args = parseExpressionList(")"); @@ -153,12 +160,12 @@ public class Parser { } } } else if (_token.type == TokenType.Delimiter && _token.text.equals("(")) { - next(); + next(true); eval = parseExpression(); if (_token != null && _token.type == TokenType.Delimiter && _token.text.equals(")")) { - next(); + next(false); } else { throw makeException("Missing )"); } @@ -168,17 +175,17 @@ public class Parser { while (_token != null) { if (_token.type == TokenType.Operator && _token.text.equals(".")) { - next(); // swallow . + next(false); // swallow . if (_token == null || _token.type != TokenType.Identifier) { throw makeException("Missing function name"); } String identifier = _token.text; - next(); + next(false); if (_token != null && _token.type == TokenType.Delimiter && _token.text.equals("(")) { - next(); // swallow ( + next(true); // swallow ( Function f = ControlFunctionRegistry.getFunction(identifier); if (f == null) { @@ -193,7 +200,7 @@ public class Parser { eval = new FieldAccessorExpr(eval, identifier); } } else if (_token.type == TokenType.Delimiter && _token.text.equals("[")) { - next(); // swallow [ + next(true); // swallow [ List args = parseExpressionList("]"); args.add(0, eval); @@ -219,7 +226,7 @@ public class Parser { l.add(eval); if (_token != null && _token.type == TokenType.Delimiter && _token.text.equals(",")) { - next(); // swallow comma, loop back for more + next(true); // swallow comma, loop back for more } else { break; } @@ -227,7 +234,7 @@ public class Parser { } if (_token != null && _token.type == TokenType.Delimiter && _token.text.equals(closingDelimiter)) { - next(); // swallow closing delimiter + next(false); // swallow closing delimiter } else { throw makeException("Missing " + closingDelimiter); } diff --git a/src/main/java/com/metaweb/gridworks/gel/Scanner.java b/src/main/java/com/metaweb/gridworks/gel/Scanner.java index 714a3504d..3df2ba1bc 100644 --- a/src/main/java/com/metaweb/gridworks/gel/Scanner.java +++ b/src/main/java/com/metaweb/gridworks/gel/Scanner.java @@ -7,7 +7,8 @@ public class Scanner { Operator, Identifier, Number, - String + String, + Regex } static public class Token { @@ -42,6 +43,15 @@ public class Scanner { } } + static public class RegexToken extends Token { + final public boolean caseInsensitive; + + public RegexToken(int start, int end, String text, boolean caseInsensitive) { + super(start, end, TokenType.Regex, text); + this.caseInsensitive = caseInsensitive; + } + } + protected String _text; protected int _index; protected int _limit; @@ -60,7 +70,7 @@ public class Scanner { return _index; } - public Token next() { + public Token next(boolean regexPossible) { // skip whitespace while (_index < _limit && Character.isWhitespace(_text.charAt(_index))) { _index++; @@ -148,6 +158,46 @@ public class Scanner { TokenType.Identifier, _text.substring(start, _index) ); + } else if (c == '/' && regexPossible) { + /* + * Regex literal + */ + StringBuffer sb = new StringBuffer(); + + _index++; // skip opening delimiter + + while (_index < _limit) { + c = _text.charAt(_index); + if (c == '/') { + _index++; // skip closing delimiter + + boolean caseInsensitive = false; + if (_index < _limit && _text.charAt(_index) == 'i') { + caseInsensitive = true; + _index++; + } + + return new RegexToken( + start, + _index, + sb.toString(), + caseInsensitive + ); + } else if (c == '\\') { + sb.append(c); + + _index++; // skip escaping marker + if (_index < _limit) { + sb.append(_text.charAt(_index)); + } + } else { + sb.append(c); + } + _index++; + } + + detail = "Regex not properly closed"; + // fall through } else if ("+-*/.".indexOf(c) >= 0) { // operator _index++; diff --git a/src/main/webapp/scripts/dialogs/expression-preview-dialog.js b/src/main/webapp/scripts/dialogs/expression-preview-dialog.js index 41d50ce4d..39fe3b542 100644 --- a/src/main/webapp/scripts/dialogs/expression-preview-dialog.js +++ b/src/main/webapp/scripts/dialogs/expression-preview-dialog.js @@ -322,7 +322,10 @@ ExpressionPreviewDialog.Widget.prototype._renderPreview = function(expression, d var renderValue = function(td, v) { if (v !== null && v !== undefined) { if ($.isArray(v)) { - td.text(JSON.stringify(v)); + var a = []; + $.each(v, function() { a.push(JSON.stringify(this)); }); + + td.text("[ " + a.join(", ") + " ]"); } else if ($.isPlainObject(v)) { $('').addClass("expression-preview-special-value").text("Error: " + v.message).appendTo(td); } else if (typeof v === "string" && v.length == 0) { diff --git a/src/main/webapp/scripts/views/data-table-column-header-ui.js b/src/main/webapp/scripts/views/data-table-column-header-ui.js index c8080b8ae..731fbf87d 100644 --- a/src/main/webapp/scripts/views/data-table-column-header-ui.js +++ b/src/main/webapp/scripts/views/data-table-column-header-ui.js @@ -54,55 +54,6 @@ DataTableColumnHeaderUI.prototype._render = function() { DataTableColumnHeaderUI.prototype._createMenuForColumnHeader = function(elmt) { self = this; MenuSystem.createAndShowStandardMenu([ - { - label: "Edit Cells", - submenu: [ - { - label: "To Titlecase", - click: function() { self._doTextTransform("toTitlecase(value)", "store-blank", false, ""); } - }, - { - label: "To Uppercase", - click: function() { self._doTextTransform("toUppercase(value)", "store-blank", false, ""); } - }, - { - label: "To Lowercase", - click: function() { self._doTextTransform("toLowercase(value)", "store-blank", false, ""); } - }, - { - label: "Custom Transform ...", - click: function() { self._doTextTransformPrompt(); } - }, - {}, - { - label: "Split Multi-Valued Cells ...", - click: function() { self._doSplitMultiValueCells(); } - }, - { - label: "Join Multi-Valued Cells ...", - click: function() { self._doJoinMultiValueCells(); } - }, - {}, - { - label: "Cluster & Edit ...", - click: function() { new FacetBasedEditDialog(self._column.name, "value"); } - } - ] - }, - { - label: "Edit Column", - submenu: [ - { - label: "Add Column Based on This Column ...", - click: function() { self._doAddColumn("value"); } - }, - { - label: "Remove This Column", - click: function() { self._doRemoveColumn(); } - }, - ] - }, - {}, { label: "Filter", tooltip: "Filter rows by this column's cell content or characteristics", @@ -124,6 +75,24 @@ DataTableColumnHeaderUI.prototype._createMenuForColumnHeader = function(elmt) { label: "Custom Text Facet ...", click: function() { self._doFilterByExpressionPrompt("value", "list"); } }, + { + label: "Common Text Facets", + submenu: [ + { + label: "Word Facet", + click: function() { + ui.browsingEngine.addFacet( + "list", + { + "name" : self._column.name + " value.split(' ')", + "columnName" : self._column.name, + "expression" : "value.split(' ')" + } + ); + } + } + ] + }, {}, { label: "Numeric Facet", @@ -134,11 +103,7 @@ DataTableColumnHeaderUI.prototype._createMenuForColumnHeader = function(elmt) { "name" : self._column.name, "columnName" : self._column.name, "expression" : "value", - "mode" : "range", - "min" : 0, - "max" : 1 - }, - { + "mode" : "range" } ); } @@ -147,6 +112,53 @@ DataTableColumnHeaderUI.prototype._createMenuForColumnHeader = function(elmt) { label: "Custom Numeric Facet ...", click: function() { self._doFilterByExpressionPrompt("value", "range"); } }, + { + label: "Common Numeric Facets", + submenu: [ + { + label: "Text Length Facet", + click: function() { + ui.browsingEngine.addFacet( + "range", + { + "name" : self._column.name + ": value.length()", + "columnName" : self._column.name, + "expression" : "value.length()", + "mode" : "range" + } + ); + } + }, + { + label: "Log of Text Length Facet", + click: function() { + ui.browsingEngine.addFacet( + "range", + { + "name" : self._column.name + ": value.length().log()", + "columnName" : self._column.name, + "expression" : "value.length().log()", + "mode" : "range" + } + ); + } + }, + { + label: "Unicode Char-code Facet", + click: function() { + ui.browsingEngine.addFacet( + "range", + { + "name" : self._column.name + ": value.unicode()", + "columnName" : self._column.name, + "expression" : "value.unicode()", + "mode" : "range" + } + ); + } + } + ] + }, {}, { label: "Text Search", @@ -205,6 +217,70 @@ DataTableColumnHeaderUI.prototype._createMenuForColumnHeader = function(elmt) { } ] }, + {}, + { + label: "Edit Cells", + submenu: [ + { + label: "Transform ...", + click: function() { self._doTextTransformPrompt(); } + }, + { + label: "Common Transforms", + submenu: [ + { + label: "Unescape HTML entities", + click: function() { self._doTextTransform("value.unescape('html')", "store-blank", true, 10); } + }, + { + label: "Collapse whitespace", + click: function() { self._doTextTransform("value.replaceRegexp('\\s+', ' ')", "store-blank", false, ""); } + }, + {}, + { + label: "To Titlecase", + click: function() { self._doTextTransform("toTitlecase(value)", "store-blank", false, ""); } + }, + { + label: "To Uppercase", + click: function() { self._doTextTransform("toUppercase(value)", "store-blank", false, ""); } + }, + { + label: "To Lowercase", + click: function() { self._doTextTransform("toLowercase(value)", "store-blank", false, ""); } + } + ] + }, + {}, + { + label: "Split Multi-Valued Cells ...", + click: function() { self._doSplitMultiValueCells(); } + }, + { + label: "Join Multi-Valued Cells ...", + click: function() { self._doJoinMultiValueCells(); } + }, + {}, + { + label: "Cluster & Edit ...", + click: function() { new FacetBasedEditDialog(self._column.name, "value"); } + } + ] + }, + { + label: "Edit Column", + submenu: [ + { + label: "Add Column Based on This Column ...", + click: function() { self._doAddColumn("value"); } + }, + { + label: "Remove This Column", + click: function() { self._doRemoveColumn(); } + }, + ] + }, + {}, { label: "View", tooltip: "Collapse/expand columns to make viewing the data more convenient",