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
54 changes: 52 additions & 2 deletions src/ng/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,8 @@ Lexer.prototype = {
isIdent: function(ch) {
return ('a' <= ch && ch <= 'z' ||
'A' <= ch && ch <= 'Z' ||
'_' === ch || ch === '$');
'_' === ch || ch === '$' ||
this.options.additionalIdentChars(ch));
},

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


Expand Down Expand Up @@ -1225,6 +1227,54 @@ function $ParseProvider() {
};


/**
* @ngdoc method
* @name ng.$parseProvider#additionalIdentChars
* @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 it doesn’t have to be created on every function call.
*
* @param {function=} fn The function that will decide whether the given character is allowed or not.
*
* @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.additionalIdentChars(function(ch) {
* return romanianCharacters.indexOf(ch) > -1;
* });
* });
* </pre>
*/
this.additionalIdentChars = function(fn) {
if (isFunction(fn)) {
$parseOptions.additionalIdentChars = fn;
return this;
} else {
return $parseOptions.additionalIdentChars;
}
};


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

Expand Down
49 changes: 47 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, additionalIdentChars: 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 additionalIdentChars', function() {
beforeEach(function () {
lex = function () {
var mathCharacters = 'πΣε';

var lexer = new Lexer({csp: false, unwrapPromises: false, additionalIdentChars: 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.additionalIdentChars(function(ch) {
return mathCharacters.indexOf(ch) > -1;
});

}]));


Expand Down Expand Up @@ -328,6 +361,18 @@ describe('parser', function() {
expect(scope.$eval("'a' + 'b c'")).toEqual("ab c");
});

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!');
});

it('should parse filters', function() {
$filterProvider.register('substring', valueFn(function(input, start, end) {
return input.substring(start, end);
Expand Down