Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 53 additions & 2 deletions src/ng/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,8 @@ Lexer.prototype = {
isIdent: function(ch) {
return ('a' <= ch && ch <= 'z' ||
'A' <= ch && ch <= 'Z' ||
'_' === ch || ch === '$');
'_' === ch || ch === '$' ||
this.options.additionalIsIdent(ch));
},

isExpOperator: function(ch) {
Expand Down Expand Up @@ -1120,7 +1121,8 @@ function $ParseProvider() {
var $parseOptions = {
csp: false,
unwrapPromises: false,
logPromiseWarnings: true
logPromiseWarnings: true,
additionalIsIdent: noop
};


Expand Down Expand Up @@ -1207,6 +1209,55 @@ function $ParseProvider() {
};


/**
* @ngdoc method
* @name ng.$parseProvider#additionalIsIdent
* @methodOf ng.$parseProvider
* @description
*
* Allows extending of the set of character allowed in identifiers used in Angular expressions. The
* `fn` function will be passed a character as argument and is expected to return `true` or `false`,
* based on whether that character is allowed or not. It is useful when you want to have non-English
* Unicode letters in your identifiers.
*
* Since this function will be called with every characters outside of `/[a-zA-Z_$]/`, it’s a good
* idea to keep it simple and fast. When the character set you want to add is relativelly small, a
* string and `indexOf()` (as in the example below) is preferable to a regexp and `test()`. On the
* subject of performance, it is good to take advantage of how variable scope works in JS and
* define the character set — whether a large string or large regexp — *outside* of `fn` itself (as
* in the example below), so that we don’t have to create it on every function call.
*
* @param {function=} fn The function that the the caracter will be passed to, to decide whether it’s
* allowed or not. The default is set to `noop` function which returns falsy.
*
* @returns {function|self} Returns the current setting when used as getter and self if used as
* setter.
*
* @example
* Let’s say that you’re developing an app for some business that has a domain language
* that is just hard to translate to English, and you want to try to use natural terminology, in
* Romanian. This snippet will allow you to use Romanian charactes:
*
* <pre>
* app.config(function($parseProvider) {
* var romanianCharacters = 'şŞţŢîÎăĂâÂ';
*
* $parseProvider.additionalIsIdent(function(ch) {
* return romanianCharacters.indexOf(ch) > -1;
* });
* });
* </pre>
*/
this.additionalIsIdent = function(fn) {
if (isFunction(fn)) {
$parseOptions.additionalIsIdent = fn;
return this;
} else {
return $parseOptions.additionalIsIdent;
}
};


this.$get = ['$filter', '$sniffer', '$log', function($filter, $sniffer, $log) {
$parseOptions.csp = $sniffer.csp;

Expand Down
44 changes: 42 additions & 2 deletions test/ng/parseSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('parser', function() {

beforeEach(function () {
lex = function () {
var lexer = new Lexer({csp: false, unwrapPromises: false});
var lexer = new Lexer({csp: false, unwrapPromises: false, additionalIsIdent: noop});
return lexer.lex.apply(lexer, arguments);
};
});
Expand Down Expand Up @@ -190,12 +190,45 @@ describe('parser', function() {
lex("'\\u1''bla'");
}).toThrowMinErr("$parse", "lexerr", "Lexer Error: Invalid unicode escape [\\u1''b] at column 2 in expression ['\\u1''bla'].");
});

describe('with additionalIsIdent', function() {
beforeEach(function () {
lex = function () {
var mathCharacters = 'πΣε';

var lexer = new Lexer({csp: false, unwrapPromises: false, additionalIsIdent: function(ch) {
return mathCharacters.indexOf(ch) > -1;
}});

return lexer.lex.apply(lexer, arguments);
};
});

it('correctly tokenizes identifiers containing the specified special characters', function() {
var tokens = lex(" Σ == π + ε ");

expect(tokens.length).toEqual(5);
expect(tokens[0].text).toEqual('Σ');
expect(tokens[1].text).toEqual('==');
expect(tokens[2].text).toEqual('π');
expect(tokens[3].text).toEqual('+');
expect(tokens[4].text).toEqual('ε');
});

});
});

var $filterProvider, scope;

beforeEach(module(['$filterProvider', function (filterProvider) {
beforeEach(module(['$filterProvider', '$parseProvider', function (filterProvider, parseProvider) {
$filterProvider = filterProvider;

var mathCharacters = 'Σπε';

parseProvider.additionalIsIdent(function(ch) {
return mathCharacters.indexOf(ch) > -1;
});

}]));


Expand Down Expand Up @@ -335,6 +368,13 @@ describe('parser', function() {
expect(scope.$eval("x.y.z", scope)).not.toBeDefined();
});

it('correctly evaluates identifiers with non-English characters', function() {
scope.π = 3.14;
expect(scope.$eval("π", scope)).toEqual(scope.π);
expect(scope.$eval("Σ = π + π", scope)).toEqual(scope.π + scope.π);
expect(scope.Σ).toEqual(scope.π + scope.π);
});

it('should resolve deeply nested paths (important for CSP mode)', function() {
scope.a = {b: {c: {d: {e: {f: {g: {h: {i: {j: {k: {l: {m: {n: 'nooo!'}}}}}}}}}}}}};
expect(scope.$eval("a.b.c.d.e.f.g.h.i.j.k.l.m.n", scope)).toBe('nooo!');
Expand Down