Fix %% escaping in interpolation.

This commit is contained in:
Neil Fraser
2015-07-10 16:08:27 -07:00
parent bcebb0776b
commit 916a5f56b0
4 changed files with 88 additions and 15 deletions

View File

@@ -1060,11 +1060,10 @@ Blockly.Block.prototype.toString=function(a){var b=[];if(this.collapsed_)b.push(
Blockly.Block.prototype.appendStatementInput=function(a){return this.appendInput_(Blockly.NEXT_STATEMENT,a)};Blockly.Block.prototype.appendDummyInput=function(a){return this.appendInput_(Blockly.DUMMY_INPUT,a||"")};
Blockly.Block.prototype.jsonInit=function(a){goog.asserts.assert(void 0==a.output||void 0==a.previousStatement,"Must not have both an output and a previousStatement.");this.setColour(a.colour);for(var b=0;void 0!==a["message"+b];)this.interpolate_(a["message"+b],a["args"+b]||[],a["lastDummyAlign"+b]),b++;void 0!==a.inputsInline&&this.setInputsInline(a.inputsInline);void 0!==a.output&&this.setOutput(!0,a.output);void 0!==a.previousStatement&&this.setPreviousStatement(!0,a.previousStatement);void 0!==
a.nextStatement&&this.setNextStatement(!0,a.nextStatement);void 0!==a.tooltip&&this.setTooltip(a.tooltip);void 0!==a.helpUrl&&this.setHelpUrl(a.helpUrl)};
Blockly.Block.prototype.interpolate_=function(a,b,c){var d=a.split(/(%\d+)/),e=[],f=0;a=[];for(var g=0;g<d.length;g++){var h=d[g];if(h.match(/^%\d+$/)){var k=parseInt(h.substring(1),10);goog.asserts.assert(0<k&&k<=b.length,'Message index "%s" out of range.',h);goog.asserts.assert(!e[k],'Message index "%s" duplicated.',h);e[k]=!0;f++;a.push(b[k-1])}else(h=h.replace(/%%/g,"%").trim())&&a.push(h)}goog.asserts.assert(f==b.length,"Message does not reference all %s arg(s).",b.length);!a.length||"string"!=
typeof a[a.length-1]&&0!=a[a.length-1].type.indexOf("field_")||(b={type:"input_dummy"},c&&(b.align=c),a.push(b));c={LEFT:Blockly.ALIGN_LEFT,RIGHT:Blockly.ALIGN_RIGHT,CENTRE:Blockly.ALIGN_CENTRE};d=[];for(g=0;g<a.length;g++)if(e=a[g],"string"==typeof e)d.push([e,void 0]);else{b=f=null;do switch(h=!1,e.type){case "input_value":b=this.appendValueInput(e.name);break;case "input_statement":b=this.appendStatementInput(e.name);break;case "input_dummy":b=this.appendDummyInput(e.name);break;case "field_label":f=
new Blockly.FieldLabel(e.text);break;case "field_input":f=new Blockly.FieldTextInput(e.text);break;case "field_angle":f=new Blockly.FieldAngle(e.angle);break;case "field_checkbox":f=new Blockly.FieldCheckbox(e.checked?"TRUE":"FALSE");break;case "field_colour":f=new Blockly.FieldColour(e.colour);break;case "field_variable":f=new Blockly.FieldVariable(e.variable);break;case "field_dropdown":f=new Blockly.FieldDropdown(e.options);break;case "field_image":f=new Blockly.FieldImage(e.src,e.width,e.height,
e.alt);break;case "field_date":if(Blockly.FieldDate){f=new Blockly.FieldDate(e.date);break}default:e.alt&&(e=e.alt,h=!0)}while(h);if(f)d.push([f,e.name]);else if(b){e.check&&b.setCheck(e.check);e.align&&b.setAlign(c[e.align]);for(e=0;e<d.length;e++)b.appendField(d[e][0],d[e][1]);d.length=0}}};
Blockly.Block.prototype.appendInput_=function(a,b){var c=null;if(a==Blockly.INPUT_VALUE||a==Blockly.NEXT_STATEMENT)c=new Blockly.Connection(this,a);c=new Blockly.Input(a,b,this,c);this.inputList.push(c);this.rendered&&(this.render(),this.bumpNeighbours_());return c};
Blockly.Block.prototype.interpolate_=function(a,b,c){var d=Blockly.tokenizeInterpolation(a),e=[],f=0;a=[];for(var g=0;g<d.length;g++){var h=d[g];"number"==typeof h?(goog.asserts.assert(0<h&&h<=b.length,'Message index "%s" out of range.',h),goog.asserts.assert(!e[h],'Message index "%s" duplicated.',h),e[h]=!0,f++,a.push(b[h-1])):(h=h.trim())&&a.push(h)}goog.asserts.assert(f==b.length,"Message does not reference all %s arg(s).",b.length);!a.length||"string"!=typeof a[a.length-1]&&0!=a[a.length-1].type.indexOf("field_")||
(b={type:"input_dummy"},c&&(b.align=c),a.push(b));c={LEFT:Blockly.ALIGN_LEFT,RIGHT:Blockly.ALIGN_RIGHT,CENTRE:Blockly.ALIGN_CENTRE};d=[];for(g=0;g<a.length;g++)if(e=a[g],"string"==typeof e)d.push([e,void 0]);else{b=f=null;do switch(h=!1,e.type){case "input_value":b=this.appendValueInput(e.name);break;case "input_statement":b=this.appendStatementInput(e.name);break;case "input_dummy":b=this.appendDummyInput(e.name);break;case "field_label":f=new Blockly.FieldLabel(e.text);break;case "field_input":f=
new Blockly.FieldTextInput(e.text);break;case "field_angle":f=new Blockly.FieldAngle(e.angle);break;case "field_checkbox":f=new Blockly.FieldCheckbox(e.checked?"TRUE":"FALSE");break;case "field_colour":f=new Blockly.FieldColour(e.colour);break;case "field_variable":f=new Blockly.FieldVariable(e.variable);break;case "field_dropdown":f=new Blockly.FieldDropdown(e.options);break;case "field_image":f=new Blockly.FieldImage(e.src,e.width,e.height,e.alt);break;case "field_date":if(Blockly.FieldDate){f=
new Blockly.FieldDate(e.date);break}default:e.alt&&(e=e.alt,h=!0)}while(h);if(f)d.push([f,e.name]);else if(b){e.check&&b.setCheck(e.check);e.align&&b.setAlign(c[e.align]);for(e=0;e<d.length;e++)b.appendField(d[e][0],d[e][1]);d.length=0}}};Blockly.Block.prototype.appendInput_=function(a,b){var c=null;if(a==Blockly.INPUT_VALUE||a==Blockly.NEXT_STATEMENT)c=new Blockly.Connection(this,a);c=new Blockly.Input(a,b,this,c);this.inputList.push(c);this.rendered&&(this.render(),this.bumpNeighbours_());return c};
Blockly.Block.prototype.moveInputBefore=function(a,b){if(a!=b){for(var c=-1,d=b?-1:this.inputList.length,e=0,f;f=this.inputList[e];e++)if(f.name==a){if(c=e,-1!=d)break}else if(b&&f.name==b&&(d=e,-1!=c))break;goog.asserts.assert(-1!=c,'Named input "%s" not found.',a);goog.asserts.assert(-1!=d,'Reference input "%s" not found.',b);this.moveNumberedInputBefore(c,d)}};
Blockly.Block.prototype.moveNumberedInputBefore=function(a,b){goog.asserts.assert(a!=b,"Can't move input to itself.");goog.asserts.assert(a<this.inputList.length,"Input index "+a+" out of bounds.");goog.asserts.assert(b<=this.inputList.length,"Reference input "+b+" out of bounds.");var c=this.inputList[a];this.inputList.splice(a,1);a<b&&b--;this.inputList.splice(b,0,c);this.rendered&&(this.render(),this.bumpNeighbours_())};
Blockly.Block.prototype.removeInput=function(a,b){for(var c=0,d;d=this.inputList[c];c++)if(d.name==a){d.connection&&d.connection.targetConnection&&d.connection.targetBlock().setParent(null);d.dispose();this.inputList.splice(c,1);this.rendered&&(this.render(),this.bumpNeighbours_());return}b||goog.asserts.fail('Input "%s" not found.',a)};Blockly.Block.prototype.getInput=function(a){for(var b=0,c;c=this.inputList[b];b++)if(c.name==a)return c;return null};
@@ -1355,6 +1354,7 @@ Blockly.createSvgElement=function(a,b,c){a=document.createElementNS(Blockly.SVG_
Blockly.mouseToSvg=function(a,b){var c=b.createSVGPoint();c.x=a.clientX;c.y=a.clientY;var d=b.getScreenCTM(),d=d.inverse();return c.matrixTransform(d)};Blockly.shortestStringLength=function(a){if(!a.length)return 0;for(var b=a[0].length,c=1;c<a.length;c++)b=Math.min(b,a[c].length);return b};
Blockly.commonWordPrefix=function(a,b){if(!a.length)return 0;if(1==a.length)return a[0].length;for(var c=0,d=b||Blockly.shortestStringLength(a),e=0;e<d;e++){for(var f=a[0][e],g=1;g<a.length;g++)if(f!=a[g][e])return c;" "==f&&(c=e+1)}for(g=1;g<a.length;g++)if((f=a[g][e])&&" "!=f)return c;return d};
Blockly.commonWordSuffix=function(a,b){if(!a.length)return 0;if(1==a.length)return a[0].length;for(var c=0,d=b||Blockly.shortestStringLength(a),e=0;e<d;e++){for(var f=a[0].substr(-e-1,1),g=1;g<a.length;g++)if(f!=a[g].substr(-e-1,1))return c;" "==f&&(c=e+1)}for(g=1;g<a.length;g++)if((f=a[g].charAt(a[g].length-e-1))&&" "!=f)return c;return d};Blockly.isNumber=function(a){return!!a.match(/^\s*-?\d+(\.\d+)?\s*$/)};
Blockly.tokenizeInterpolation=function(a){var b=[];a=a.split("");a.push("");for(var c=0,d=[],e=null,f=0;f<a.length;f++){var g=a[f];0==c?"%"==g?c=1:d.push(g):1==c?"%"==g?(d.push(g),c=0):"0"<=g&&"9">=g?(c=2,e=g,(g=d.join(""))&&b.push(g),d.length=0):(d.push("%",g),c=0):2==c&&("0"<=g&&"9">=g?e+=g:(b.push(parseInt(e,10)),f--,c=0))}(g=d.join(""))&&b.push(g);return b};
// Copyright 2011 Google Inc. Apache License 2.0
Blockly.SVG_NS="http://www.w3.org/2000/svg";Blockly.HTML_NS="http://www.w3.org/1999/xhtml";Blockly.HSV_SATURATION=.45;Blockly.HSV_VALUE=.65;Blockly.SPRITE={width:64,height:92,url:"sprites.png"};Blockly.makeColour=function(a){return goog.color.hsvToHex(a,Blockly.HSV_SATURATION,255*Blockly.HSV_VALUE)};Blockly.INPUT_VALUE=1;Blockly.OUTPUT_VALUE=2;Blockly.NEXT_STATEMENT=3;Blockly.PREVIOUS_STATEMENT=4;Blockly.DUMMY_INPUT=5;Blockly.ALIGN_LEFT=-1;Blockly.ALIGN_CENTRE=0;Blockly.ALIGN_RIGHT=1;
Blockly.OPPOSITE_TYPE=[];Blockly.OPPOSITE_TYPE[Blockly.INPUT_VALUE]=Blockly.OUTPUT_VALUE;Blockly.OPPOSITE_TYPE[Blockly.OUTPUT_VALUE]=Blockly.INPUT_VALUE;Blockly.OPPOSITE_TYPE[Blockly.NEXT_STATEMENT]=Blockly.PREVIOUS_STATEMENT;Blockly.OPPOSITE_TYPE[Blockly.PREVIOUS_STATEMENT]=Blockly.NEXT_STATEMENT;Blockly.selected=null;Blockly.highlightedConnection_=null;Blockly.localConnection_=null;Blockly.DRAG_RADIUS=5;Blockly.SNAP_RADIUS=20;Blockly.BUMP_DELAY=250;Blockly.COLLAPSE_CHARS=30;Blockly.LONGPRESS=750;

View File

@@ -948,25 +948,23 @@ Blockly.Block.prototype.jsonInit = function(json) {
* @private
*/
Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) {
// Parse the message and interpolate the arguments.
// Build a list of elements.
var tokens = message.split(/(%\d+)/);
var tokens = Blockly.tokenizeInterpolation(message);
// Interpolate the arguments. Build a list of elements.
var indexDup = [];
var indexCount = 0;
var elements = [];
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
if (token.match(/^%\d+$/)) {
var index = parseInt(token.substring(1), 10);
goog.asserts.assert(index > 0 && index <= args.length,
if (typeof token == 'number') {
goog.asserts.assert(token > 0 && token <= args.length,
'Message index "%s" out of range.', token);
goog.asserts.assert(!indexDup[index],
goog.asserts.assert(!indexDup[token],
'Message index "%s" duplicated.', token);
indexDup[index] = true;
indexDup[token] = true;
indexCount++;
elements.push(args[index - 1]);
elements.push(args[token - 1]);
} else {
token = token.replace(/%%/g, '%').trim();
token = token.trim();
if (token) {
elements.push(token);
}

View File

@@ -469,3 +469,61 @@ Blockly.commonWordSuffix = function(array, opt_shortest) {
Blockly.isNumber = function(str) {
return !!str.match(/^\s*-?\d+(\.\d+)?\s*$/);
};
/**
* Parse a string with any number of interpolation tokens (%1, %2, ...).
* '%' characters may be self-escaped (%%).
* @param {string} message Text containing interpolation tokens.
* @return {!Array.<string|number>} Array of strings and numbers.
*/
Blockly.tokenizeInterpolation = function(message) {
var tokens = [];
var chars = message.split('');
chars.push(''); // End marker.
// Parse the message with a finite state machine.
// 0 - Base case.
// 1 - % found.
// 2 - Digit found.
var state = 0;
var buffer = [];
var number = null;
for (var i = 0; i < chars.length; i++) {
var c = chars[i];
if (state == 0) {
if (c == '%') {
state = 1; // Start escape.
} else {
buffer.push(c); // Regular char.
}
} else if (state == 1) {
if (c == '%') {
buffer.push(c); // Escaped %: %%
state = 0;
} else if ('0' <= c && c <= '9') {
state = 2;
number = c;
var text = buffer.join('');
if (text) {
tokens.push(text);
}
buffer.length = 0;
} else {
buffer.push('%', c); // Not an escape: %a
state = 0;
}
} else if (state == 2) {
if ('0' <= c && c <= '9') {
number += c; // Multi-digit number.
} else {
tokens.push(parseInt(number, 10));
i--; // Parse this char again.
state = 0;
}
}
}
var text = buffer.join('');
if (text) {
tokens.push(text);
}
return tokens;
};

View File

@@ -187,3 +187,20 @@ function test_commonWordSuffix() {
len = Blockly.commonWordSuffix([]);
assertEquals('Empty list', 0, len);
}
function test_tokenizeInterpolation() {
var tokens = Blockly.tokenizeInterpolation('');
assertArrayEquals('Null interpolation', [], tokens);
tokens = Blockly.tokenizeInterpolation('Hello');
assertArrayEquals('No interpolation', ['Hello'], tokens);
tokens = Blockly.tokenizeInterpolation('Hello%World');
assertArrayEquals('Unescaped %.', ['Hello%World'], tokens);
tokens = Blockly.tokenizeInterpolation('Hello%%World');
assertArrayEquals('Escaped %.', ['Hello%World'], tokens);
tokens = Blockly.tokenizeInterpolation('Hello %1 World');
assertArrayEquals('Interpolation.', ['Hello ', 1, ' World'], tokens);
tokens = Blockly.tokenizeInterpolation('%123Hello%456World%789');
assertArrayEquals('Interpolations.', [123, 'Hello', 456, 'World', 789], tokens);
tokens = Blockly.tokenizeInterpolation('%%%x%%0%00%01%');
assertArrayEquals('Torture interpolations.', ['%%x%0', 0, 1, '%'], tokens);
}