From e5d25bb686abc131fcdcc290fdea113949e27580 Mon Sep 17 00:00:00 2001
From: Andrew n marshall
Date: Tue, 15 Nov 2016 15:51:51 -0800
Subject: [PATCH] String reference in JSON string messages (#741)
* Adds message references to message string interpolation, in the form of %{BKY_STRING}.
* Re-adding CONTROLS_IFELSE block using the new syntax, referencing to CONTROL_IF equivalents.
---
blocks/logic.js | 42 +++++++++++++++++++++
core/utils.js | 69 +++++++++++++++++++++++++++++++++-
generators/dart/logic.js | 2 +
generators/javascript/logic.js | 2 +
generators/lua/logic.js | 2 +
generators/php/logic.js | 2 +
generators/python/logic.js | 2 +
tests/jsunit/utils_test.js | 50 ++++++++++++++++++++++++
tests/playground.html | 2 +-
9 files changed, 170 insertions(+), 3 deletions(-)
diff --git a/blocks/logic.js b/blocks/logic.js
index 57af601a9..3a74e2872 100644
--- a/blocks/logic.js
+++ b/blocks/logic.js
@@ -265,6 +265,48 @@ Blockly.Blocks['controls_if_else'] = {
}
};
+Blockly.Blocks['controls_ifelse'] = {
+ /**
+ * If/else block that does not use a mutator.
+ */
+ init: function() {
+ this.jsonInit({
+ "message0": "%{BKY_CONTROLS_IF_MSG_IF} %1",
+ "args0": [
+ {
+ "type": "input_value",
+ "name": "IF0",
+ "check": "Boolean",
+ "align": "RIGHT"
+ }
+ ],
+ "message1": "%{BKY_CONTROLS_IF_MSG_THEN} %1",
+ "args1": [
+ {
+ "type": "input_statement",
+ "name": "DO0",
+ "check": "Boolean",
+ "align": "RIGHT"
+ }
+ ],
+ "message2": "%{BKY_CONTROLS_IF_MSG_ELSE} %1",
+ "args2": [
+ {
+ "type": "input_statement",
+ "name": "ELSE",
+ "check": "Boolean",
+ "align": "RIGHT"
+ }
+ ],
+ "previousStatement": null,
+ "nextStatement": null,
+ "colour": Blockly.Blocks.logic.HUE,
+ "tooltip": Blockly.Msg.CONTROLS_IF_TOOLTIP_2,
+ "helpUrl": Blockly.Msg.CONTROLS_IF_HELPURL
+ });
+ }
+};
+
Blockly.Blocks['logic_compare'] = {
/**
* Block for comparison operator.
diff --git a/core/utils.js b/core/utils.js
index 949eed2bd..ca2c9c641 100644
--- a/core/utils.js
+++ b/core/utils.js
@@ -323,6 +323,7 @@ Blockly.utils.tokenizeInterpolation = function(message) {
// 0 - Base case.
// 1 - % found.
// 2 - Digit found.
+ // 3 - Message ref found
var state = 0;
var buffer = [];
var number = null;
@@ -330,6 +331,11 @@ Blockly.utils.tokenizeInterpolation = function(message) {
var c = chars[i];
if (state == 0) {
if (c == '%') {
+ var text = buffer.join('');
+ if (text) {
+ tokens.push(text);
+ }
+ buffer.length = 0;
state = 1; // Start escape.
} else {
buffer.push(c); // Regular char.
@@ -346,8 +352,10 @@ Blockly.utils.tokenizeInterpolation = function(message) {
tokens.push(text);
}
buffer.length = 0;
+ } else if (c == '{') {
+ state = 3;
} else {
- buffer.push('%', c); // Not an escape: %a
+ buffer.push('%', c); // Not recognized. Return as literal.
state = 0;
}
} else if (state == 2) {
@@ -358,13 +366,70 @@ Blockly.utils.tokenizeInterpolation = function(message) {
i--; // Parse this char again.
state = 0;
}
+ } else if (state == 3) { // String table reference
+ if (c == '') {
+ // Premature end before closing '}'
+ buffer.splice(0, 0, '%{'); // Re-insert leading delimiter
+ i--; // Parse this char again.
+ state = 0; // and parse as string literal.
+ } else if (c != '}') {
+ buffer.push(c);
+ } else {
+ var rawKey = buffer.join('');
+ if (/[a-zA-Z][a-zA-Z0-9_]*/.test(rawKey)) { // Strict matching
+ // Found a valid string key. Attempt case insensitive match.
+ var keyUpper = rawKey.toUpperCase();
+
+ // BKY_ is the prefix used to namespace the strings used in Blockly
+ // core files and the predefined blocks in ../blocks/. These strings
+ // are defined in ../msgs/ files.
+ var bklyKey = goog.string.startsWith(keyUpper, 'BKY_') ?
+ keyUpper.substring(4) : null;
+ if (bklyKey && bklyKey in Blockly.Msg) {
+ var rawValue = Blockly.Msg[bklyKey];
+ var subTokens = Blockly.utils.tokenizeInterpolation(rawValue);
+ tokens = tokens.concat(subTokens);
+ } else {
+ // No entry found in the string table. Pass reference as string.
+ tokens.push('%{' + rawKey + '}');
+ }
+ buffer.length = 0; // Clear the array
+ state = 0;
+ } else {
+ tokens.push('%{' + rawKey + '}');
+ buffer.length = 0;
+ state = 0; // and parse as string literal.
+ }
+ }
}
}
var text = buffer.join('');
if (text) {
tokens.push(text);
}
- return tokens;
+
+ // Merge adjacent text tokens into a single string.
+ var mergedTokens = [];
+ buffer.length = 0;
+ for (var i = 0; i < tokens.length; ++i) {
+ if (typeof tokens[i] == 'string') {
+ buffer.push(tokens[i]);
+ } else {
+ text = buffer.join('');
+ if (text) {
+ mergedTokens.push(text);
+ }
+ buffer.length = 0;
+ mergedTokens.push(tokens[i]);
+ }
+ }
+ text = buffer.join('');
+ if (text) {
+ mergedTokens.push(text);
+ }
+ buffer.length = 0;
+
+ return mergedTokens;
};
/**
diff --git a/generators/dart/logic.js b/generators/dart/logic.js
index 0861edc74..620b55da8 100644
--- a/generators/dart/logic.js
+++ b/generators/dart/logic.js
@@ -50,6 +50,8 @@ Blockly.Dart['controls_if'] = function(block) {
return code + '\n';
};
+Blockly.Dart['controls_ifelse'] = Blockly.Dart['controls_if'];
+
Blockly.Dart['logic_compare'] = function(block) {
// Comparison operator.
var OPERATORS = {
diff --git a/generators/javascript/logic.js b/generators/javascript/logic.js
index 46b8974f7..1c4d4b620 100644
--- a/generators/javascript/logic.js
+++ b/generators/javascript/logic.js
@@ -50,6 +50,8 @@ Blockly.JavaScript['controls_if'] = function(block) {
return code + '\n';
};
+Blockly.JavaScript['controls_ifelse'] = Blockly.JavaScript['controls_if'];
+
Blockly.JavaScript['logic_compare'] = function(block) {
// Comparison operator.
var OPERATORS = {
diff --git a/generators/lua/logic.js b/generators/lua/logic.js
index 74eba62ed..033c52e33 100644
--- a/generators/lua/logic.js
+++ b/generators/lua/logic.js
@@ -50,6 +50,8 @@ Blockly.Lua['controls_if'] = function(block) {
return code + 'end\n';
};
+Blockly.Lua['controls_ifelse'] = Blockly.Lua['controls_if'];
+
Blockly.Lua['logic_compare'] = function(block) {
// Comparison operator.
var OPERATORS = {
diff --git a/generators/php/logic.js b/generators/php/logic.js
index f51767034..475d2f703 100644
--- a/generators/php/logic.js
+++ b/generators/php/logic.js
@@ -50,6 +50,8 @@ Blockly.PHP['controls_if'] = function(block) {
return code + '\n';
};
+Blockly.PHP['controls_ifelse'] = Blockly.PHP['controls_if'];
+
Blockly.PHP['logic_compare'] = function(block) {
// Comparison operator.
var OPERATORS = {
diff --git a/generators/python/logic.js b/generators/python/logic.js
index a1e79eafd..981ab3696 100644
--- a/generators/python/logic.js
+++ b/generators/python/logic.js
@@ -51,6 +51,8 @@ Blockly.Python['controls_if'] = function(block) {
return code;
};
+Blockly.Python['controls_ifelse'] = Blockly.Python['controls_if'];
+
Blockly.Python['logic_compare'] = function(block) {
// Comparison operator.
var OPERATORS = {
diff --git a/tests/jsunit/utils_test.js b/tests/jsunit/utils_test.js
index 3d6086522..a96cec57b 100644
--- a/tests/jsunit/utils_test.js
+++ b/tests/jsunit/utils_test.js
@@ -115,16 +115,66 @@ function test_commonWordSuffix() {
function test_tokenizeInterpolation() {
var tokens = Blockly.utils.tokenizeInterpolation('');
assertArrayEquals('Null interpolation', [], tokens);
+
tokens = Blockly.utils.tokenizeInterpolation('Hello');
assertArrayEquals('No interpolation', ['Hello'], tokens);
+
tokens = Blockly.utils.tokenizeInterpolation('Hello%World');
assertArrayEquals('Unescaped %.', ['Hello%World'], tokens);
+
tokens = Blockly.utils.tokenizeInterpolation('Hello%%World');
assertArrayEquals('Escaped %.', ['Hello%World'], tokens);
+
tokens = Blockly.utils.tokenizeInterpolation('Hello %1 World');
assertArrayEquals('Interpolation.', ['Hello ', 1, ' World'], tokens);
+
tokens = Blockly.utils.tokenizeInterpolation('%123Hello%456World%789');
assertArrayEquals('Interpolations.', [123, 'Hello', 456, 'World', 789], tokens);
+
tokens = Blockly.utils.tokenizeInterpolation('%%%x%%0%00%01%');
assertArrayEquals('Torture interpolations.', ['%%x%0', 0, 1, '%'], tokens);
+
+ Blockly.Msg = Blockly.Msg || {};
+
+ Blockly.Msg.STRING_REF = 'test string';
+ tokens = Blockly.utils.tokenizeInterpolation('%{bky_string_ref}');
+ assertArrayEquals('String table reference, lowercase', ['test string'], tokens);
+ tokens = Blockly.utils.tokenizeInterpolation('%{BKY_STRING_REF}');
+ assertArrayEquals('String table reference, uppercase', ['test string'], tokens);
+
+ Blockly.Msg.WITH_PARAM = 'before %1 after';
+ tokens = Blockly.utils.tokenizeInterpolation('%{bky_with_param}');
+ assertArrayEquals('String table reference, with parameter', ['before ', 1, ' after'], tokens);
+
+ Blockly.Msg.RECURSE = 'before %{bky_string_ref} after';
+ tokens = Blockly.utils.tokenizeInterpolation('%{bky_recurse}');
+ assertArrayEquals('String table reference, with subreference', ['before test string after'], tokens);
+
+ // Error cases...
+ tokens = Blockly.utils.tokenizeInterpolation('%{bky_undefined}');
+ assertArrayEquals('Undefined string table reference', ['%{bky_undefined}'], tokens);
+
+ Blockly.Msg['1'] = 'Will not match';
+ tokens = Blockly.utils.tokenizeInterpolation('before %{1} after');
+ assertArrayEquals('Invalid initial digit in string table reference', ['before %{1} after'], tokens);
+
+ Blockly.Msg['TWO WORDS'] = 'Will not match';
+ tokens = Blockly.utils.tokenizeInterpolation('before %{two words} after');
+ assertArrayEquals('Invalid character in string table reference: space', ['before %{two words} after'], tokens);
+
+ Blockly.Msg['TWO-WORDS'] = 'Will not match';
+ tokens = Blockly.utils.tokenizeInterpolation('before %{two-words} after');
+ assertArrayEquals('Invalid character in string table reference: dash', ['before %{two-words} after'], tokens);
+
+ Blockly.Msg['TWO.WORDS'] = 'Will not match';
+ tokens = Blockly.utils.tokenizeInterpolation('before %{two.words} after');
+ assertArrayEquals('Invalid character in string table reference: period', ['before %{two.words} after'], tokens);
+
+ Blockly.Msg['AB&C'] = 'Will not match';
+ tokens = Blockly.utils.tokenizeInterpolation('before %{ab&c} after');
+ assertArrayEquals('Invalid character in string table reference: &', ['before %{ab&c} after'], tokens);
+
+ Blockly.Msg['UNCLOSED'] = 'Will not match';
+ tokens = Blockly.utils.tokenizeInterpolation('before %{unclosed');
+ assertArrayEquals('String table reference, with parameter', ['before %{unclosed'], tokens);
}
diff --git a/tests/playground.html b/tests/playground.html
index 6270af20c..8a7435915 100644
--- a/tests/playground.html
+++ b/tests/playground.html
@@ -379,7 +379,7 @@ h1 {
-
+