Adding mapbox-gl branch
This commit is contained in:
@@ -0,0 +1,499 @@
|
||||
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Provides functions to parse and manipulate email addresses.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.format.EmailAddress');
|
||||
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Formats an email address string for display, and allows for extraction of
|
||||
* the individual components of the address.
|
||||
* @param {string=} opt_address The email address.
|
||||
* @param {string=} opt_name The name associated with the email address.
|
||||
* @constructor
|
||||
*/
|
||||
goog.format.EmailAddress = function(opt_address, opt_name) {
|
||||
/**
|
||||
* The name or personal string associated with the address.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.name_ = opt_name || '';
|
||||
|
||||
/**
|
||||
* The email address.
|
||||
* @type {string}
|
||||
* @protected
|
||||
*/
|
||||
this.address = opt_address || '';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Match string for opening tokens.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.format.EmailAddress.OPENERS_ = '"<([';
|
||||
|
||||
|
||||
/**
|
||||
* Match string for closing tokens.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.format.EmailAddress.CLOSERS_ = '">)]';
|
||||
|
||||
|
||||
/**
|
||||
* Match string for characters that require display names to be quoted and are
|
||||
* not address separators.
|
||||
* @type {string}
|
||||
* @const
|
||||
* @package
|
||||
*/
|
||||
goog.format.EmailAddress.SPECIAL_CHARS = '()<>@:\\\".[]';
|
||||
|
||||
|
||||
/**
|
||||
* Match string for address separators.
|
||||
* @type {string}
|
||||
* @const
|
||||
* @private
|
||||
*/
|
||||
goog.format.EmailAddress.ADDRESS_SEPARATORS_ = ',;';
|
||||
|
||||
|
||||
/**
|
||||
* Match string for characters that, when in a display name, require it to be
|
||||
* quoted.
|
||||
* @type {string}
|
||||
* @const
|
||||
* @private
|
||||
*/
|
||||
goog.format.EmailAddress.CHARS_REQUIRE_QUOTES_ =
|
||||
goog.format.EmailAddress.SPECIAL_CHARS +
|
||||
goog.format.EmailAddress.ADDRESS_SEPARATORS_;
|
||||
|
||||
|
||||
/**
|
||||
* A RegExp to match all double quotes. Used in cleanAddress().
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.format.EmailAddress.ALL_DOUBLE_QUOTES_ = /\"/g;
|
||||
|
||||
|
||||
/**
|
||||
* A RegExp to match escaped double quotes. Used in parse().
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.format.EmailAddress.ESCAPED_DOUBLE_QUOTES_ = /\\\"/g;
|
||||
|
||||
|
||||
/**
|
||||
* A RegExp to match all backslashes. Used in cleanAddress().
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.format.EmailAddress.ALL_BACKSLASHES_ = /\\/g;
|
||||
|
||||
|
||||
/**
|
||||
* A RegExp to match escaped backslashes. Used in parse().
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.format.EmailAddress.ESCAPED_BACKSLASHES_ = /\\\\/g;
|
||||
|
||||
|
||||
/**
|
||||
* A string representing the RegExp for the local part of an email address.
|
||||
* @private {string}
|
||||
*/
|
||||
goog.format.EmailAddress.LOCAL_PART_REGEXP_STR_ =
|
||||
'[+a-zA-Z0-9_.!#$%&\'*\\/=?^`{|}~-]+';
|
||||
|
||||
|
||||
/**
|
||||
* A string representing the RegExp for the domain part of an email address.
|
||||
* @private {string}
|
||||
*/
|
||||
goog.format.EmailAddress.DOMAIN_PART_REGEXP_STR_ =
|
||||
'([a-zA-Z0-9-]+\\.)+[a-zA-Z0-9]{2,63}';
|
||||
|
||||
|
||||
/**
|
||||
* A RegExp to match the local part of an email address.
|
||||
* @private {!RegExp}
|
||||
*/
|
||||
goog.format.EmailAddress.LOCAL_PART_ =
|
||||
new RegExp('^' + goog.format.EmailAddress.LOCAL_PART_REGEXP_STR_ + '$');
|
||||
|
||||
|
||||
/**
|
||||
* A RegExp to match the domain part of an email address.
|
||||
* @private {!RegExp}
|
||||
*/
|
||||
goog.format.EmailAddress.DOMAIN_PART_ =
|
||||
new RegExp('^' + goog.format.EmailAddress.DOMAIN_PART_REGEXP_STR_ + '$');
|
||||
|
||||
|
||||
/**
|
||||
* A RegExp to match an email address.
|
||||
* @private {!RegExp}
|
||||
*/
|
||||
goog.format.EmailAddress.EMAIL_ADDRESS_ =
|
||||
new RegExp('^' + goog.format.EmailAddress.LOCAL_PART_REGEXP_STR_ + '@' +
|
||||
goog.format.EmailAddress.DOMAIN_PART_REGEXP_STR_ + '$');
|
||||
|
||||
|
||||
/**
|
||||
* Get the name associated with the email address.
|
||||
* @return {string} The name or personal portion of the address.
|
||||
* @final
|
||||
*/
|
||||
goog.format.EmailAddress.prototype.getName = function() {
|
||||
return this.name_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the email address.
|
||||
* @return {string} The email address.
|
||||
* @final
|
||||
*/
|
||||
goog.format.EmailAddress.prototype.getAddress = function() {
|
||||
return this.address;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the name associated with the email address.
|
||||
* @param {string} name The name to associate.
|
||||
* @final
|
||||
*/
|
||||
goog.format.EmailAddress.prototype.setName = function(name) {
|
||||
this.name_ = name;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the email address.
|
||||
* @param {string} address The email address.
|
||||
* @final
|
||||
*/
|
||||
goog.format.EmailAddress.prototype.setAddress = function(address) {
|
||||
this.address = address;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return the address in a standard format:
|
||||
* - remove extra spaces.
|
||||
* - Surround name with quotes if it contains special characters.
|
||||
* @return {string} The cleaned address.
|
||||
* @override
|
||||
*/
|
||||
goog.format.EmailAddress.prototype.toString = function() {
|
||||
return this.toStringInternal(
|
||||
goog.format.EmailAddress.CHARS_REQUIRE_QUOTES_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check if a display name requires quoting.
|
||||
* @param {string} name The display name
|
||||
* @param {string} specialChars String that contains the characters that require
|
||||
* the display name to be quoted. This may change based in whereas we are
|
||||
* in EAI context or not.
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.format.EmailAddress.isQuoteNeeded_ = function(name, specialChars) {
|
||||
for (var i = 0; i < specialChars.length; i++) {
|
||||
var specialChar = specialChars[i];
|
||||
if (goog.string.contains(name, specialChar)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return the address in a standard format:
|
||||
* - remove extra spaces.
|
||||
* - Surround name with quotes if it contains special characters.
|
||||
* @param {string} specialChars String that contains the characters that require
|
||||
* the display name to be quoted.
|
||||
* @return {string} The cleaned address.
|
||||
* @protected
|
||||
*/
|
||||
goog.format.EmailAddress.prototype.toStringInternal = function(specialChars) {
|
||||
var name = this.getName();
|
||||
|
||||
// We intentionally remove double quotes in the name because escaping
|
||||
// them to \" looks ugly.
|
||||
name = name.replace(goog.format.EmailAddress.ALL_DOUBLE_QUOTES_, '');
|
||||
|
||||
// If the name has special characters, we need to quote it and escape \'s.
|
||||
if (goog.format.EmailAddress.isQuoteNeeded_(name, specialChars)) {
|
||||
name = '"' +
|
||||
name.replace(goog.format.EmailAddress.ALL_BACKSLASHES_, '\\\\') + '"';
|
||||
}
|
||||
|
||||
if (name == '') {
|
||||
return this.address;
|
||||
}
|
||||
if (this.address == '') {
|
||||
return name;
|
||||
}
|
||||
return name + ' <' + this.address + '>';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines is the current object is a valid email address.
|
||||
* @return {boolean} Whether the email address is valid.
|
||||
*/
|
||||
goog.format.EmailAddress.prototype.isValid = function() {
|
||||
return goog.format.EmailAddress.isValidAddrSpec(this.address);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the provided string is a valid email address. Supports both
|
||||
* simple email addresses (address specs) and addresses that contain display
|
||||
* names.
|
||||
* @param {string} str The email address to check.
|
||||
* @return {boolean} Whether the provided string is a valid address.
|
||||
*/
|
||||
goog.format.EmailAddress.isValidAddress = function(str) {
|
||||
return goog.format.EmailAddress.parse(str).isValid();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the provided string is a valid address spec (local@domain.com).
|
||||
* @param {string} str The email address to check.
|
||||
* @return {boolean} Whether the provided string is a valid address spec.
|
||||
*/
|
||||
goog.format.EmailAddress.isValidAddrSpec = function(str) {
|
||||
// This is a fairly naive implementation, but it covers 99% of use cases.
|
||||
// For more details, see http://en.wikipedia.org/wiki/Email_address#Syntax
|
||||
return goog.format.EmailAddress.EMAIL_ADDRESS_.test(str);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the provided string is a valid local part (part before the '@') of
|
||||
* an email address.
|
||||
* @param {string} str The local part to check.
|
||||
* @return {boolean} Whether the provided string is a valid local part.
|
||||
*/
|
||||
goog.format.EmailAddress.isValidLocalPartSpec = function(str) {
|
||||
return goog.format.EmailAddress.LOCAL_PART_.test(str);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the provided string is a valid domain part (part after the '@') of
|
||||
* an email address.
|
||||
* @param {string} str The domain part to check.
|
||||
* @return {boolean} Whether the provided string is a valid domain part.
|
||||
*/
|
||||
goog.format.EmailAddress.isValidDomainPartSpec = function(str) {
|
||||
return goog.format.EmailAddress.DOMAIN_PART_.test(str);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parses an email address of the form "name" <address> ("name" is
|
||||
* optional) into an email address.
|
||||
* @param {string} addr The address string.
|
||||
* @param {function(new: goog.format.EmailAddress, string=,string=)} ctor
|
||||
* EmailAddress constructor to instantiate the output address.
|
||||
* @return {!goog.format.EmailAddress} The parsed address.
|
||||
* @protected
|
||||
*/
|
||||
goog.format.EmailAddress.parseInternal = function(addr, ctor) {
|
||||
// TODO(ecattell): Strip bidi markers.
|
||||
var name = '';
|
||||
var address = '';
|
||||
for (var i = 0; i < addr.length;) {
|
||||
var token = goog.format.EmailAddress.getToken_(addr, i);
|
||||
if (token.charAt(0) == '<' && token.indexOf('>') != -1) {
|
||||
var end = token.indexOf('>');
|
||||
address = token.substring(1, end);
|
||||
} else if (address == '') {
|
||||
name += token;
|
||||
}
|
||||
i += token.length;
|
||||
}
|
||||
|
||||
// Check if it's a simple email address of the form "jlim@google.com".
|
||||
if (address == '' && name.indexOf('@') != -1) {
|
||||
address = name;
|
||||
name = '';
|
||||
}
|
||||
|
||||
name = goog.string.collapseWhitespace(name);
|
||||
name = goog.string.stripQuotes(name, '\'');
|
||||
name = goog.string.stripQuotes(name, '"');
|
||||
// Replace escaped quotes and slashes.
|
||||
name = name.replace(goog.format.EmailAddress.ESCAPED_DOUBLE_QUOTES_, '"');
|
||||
name = name.replace(goog.format.EmailAddress.ESCAPED_BACKSLASHES_, '\\');
|
||||
address = goog.string.collapseWhitespace(address);
|
||||
return new ctor(address, name);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parses an email address of the form "name" <address> into
|
||||
* an email address.
|
||||
* @param {string} addr The address string.
|
||||
* @return {!goog.format.EmailAddress} The parsed address.
|
||||
*/
|
||||
goog.format.EmailAddress.parse = function(addr) {
|
||||
return goog.format.EmailAddress.parseInternal(
|
||||
addr, goog.format.EmailAddress);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parse a string containing email addresses of the form
|
||||
* "name" <address> into an array of email addresses.
|
||||
* @param {string} str The address list.
|
||||
* @param {function(string)} parser The parser to employ.
|
||||
* @param {function(string):boolean} separatorChecker Accepts a character and
|
||||
* returns whether it should be considered an address separator.
|
||||
* @return {!Array<!goog.format.EmailAddress>} The parsed emails.
|
||||
* @protected
|
||||
*/
|
||||
goog.format.EmailAddress.parseListInternal = function(
|
||||
str, parser, separatorChecker) {
|
||||
var result = [];
|
||||
var email = '';
|
||||
var token;
|
||||
|
||||
// Remove non-UNIX-style newlines that would otherwise cause getToken_ to
|
||||
// choke. Remove multiple consecutive whitespace characters for the same
|
||||
// reason.
|
||||
str = goog.string.collapseWhitespace(str);
|
||||
|
||||
for (var i = 0; i < str.length; ) {
|
||||
token = goog.format.EmailAddress.getToken_(str, i);
|
||||
if (separatorChecker(token) ||
|
||||
(token == ' ' && parser(email).isValid())) {
|
||||
if (!goog.string.isEmptyOrWhitespace(email)) {
|
||||
result.push(parser(email));
|
||||
}
|
||||
email = '';
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
email += token;
|
||||
i += token.length;
|
||||
}
|
||||
|
||||
// Add the final token.
|
||||
if (!goog.string.isEmptyOrWhitespace(email)) {
|
||||
result.push(parser(email));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parses a string containing email addresses of the form
|
||||
* "name" <address> into an array of email addresses.
|
||||
* @param {string} str The address list.
|
||||
* @return {!Array<!goog.format.EmailAddress>} The parsed emails.
|
||||
*/
|
||||
goog.format.EmailAddress.parseList = function(str) {
|
||||
return goog.format.EmailAddress.parseListInternal(
|
||||
str, goog.format.EmailAddress.parse,
|
||||
goog.format.EmailAddress.isAddressSeparator);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the next token from a position in an address string.
|
||||
* @param {string} str the string.
|
||||
* @param {number} pos the position.
|
||||
* @return {string} the token.
|
||||
* @private
|
||||
*/
|
||||
goog.format.EmailAddress.getToken_ = function(str, pos) {
|
||||
var ch = str.charAt(pos);
|
||||
var p = goog.format.EmailAddress.OPENERS_.indexOf(ch);
|
||||
if (p == -1) {
|
||||
return ch;
|
||||
}
|
||||
if (goog.format.EmailAddress.isEscapedDlQuote_(str, pos)) {
|
||||
|
||||
// If an opener is an escaped quote we do not treat it as a real opener
|
||||
// and keep accumulating the token.
|
||||
return ch;
|
||||
}
|
||||
var closerChar = goog.format.EmailAddress.CLOSERS_.charAt(p);
|
||||
var endPos = str.indexOf(closerChar, pos + 1);
|
||||
|
||||
// If the closer is a quote we go forward skipping escaped quotes until we
|
||||
// hit the real closing one.
|
||||
while (endPos >= 0 &&
|
||||
goog.format.EmailAddress.isEscapedDlQuote_(str, endPos)) {
|
||||
endPos = str.indexOf(closerChar, endPos + 1);
|
||||
}
|
||||
var token = (endPos >= 0) ? str.substring(pos, endPos + 1) : ch;
|
||||
return token;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the character in the current position is an escaped double quote
|
||||
* ( \" ).
|
||||
* @param {string} str the string.
|
||||
* @param {number} pos the position.
|
||||
* @return {boolean} true if the char is escaped double quote.
|
||||
* @private
|
||||
*/
|
||||
goog.format.EmailAddress.isEscapedDlQuote_ = function(str, pos) {
|
||||
if (str.charAt(pos) != '"') {
|
||||
return false;
|
||||
}
|
||||
var slashCount = 0;
|
||||
for (var idx = pos - 1; idx >= 0 && str.charAt(idx) == '\\'; idx--) {
|
||||
slashCount++;
|
||||
}
|
||||
return ((slashCount % 2) != 0);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} ch The character to test.
|
||||
* @return {boolean} Whether the provided character is an address separator.
|
||||
*/
|
||||
goog.format.EmailAddress.isAddressSeparator = function(ch) {
|
||||
return goog.string.contains(goog.format.EmailAddress.ADDRESS_SEPARATORS_, ch);
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2010 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.format.EmailAddress
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.format.EmailAddressTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,229 @@
|
||||
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
goog.provide('goog.format.EmailAddressTest');
|
||||
goog.setTestOnly('goog.format.EmailAddressTest');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.format.EmailAddress');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
function testparseList() {
|
||||
assertParsedList('', [], 'Failed to parse empty stringy');
|
||||
assertParsedList(',,', [], 'Failed to parse string with commas only');
|
||||
|
||||
assertParsedList('<foo@gmail.com>', ['foo@gmail.com']);
|
||||
|
||||
assertParsedList('<foo@gmail.com>,', ['foo@gmail.com'],
|
||||
'Failed to parse 1 address with trailing comma');
|
||||
|
||||
assertParsedList('<foo@gmail.com>, ', ['foo@gmail.com'],
|
||||
'Failed to parse 1 address with trailing whitespace and comma');
|
||||
|
||||
assertParsedList(',<foo@gmail.com>', ['foo@gmail.com'],
|
||||
'Failed to parse 1 address with leading comma');
|
||||
|
||||
assertParsedList(' ,<foo@gmail.com>', ['foo@gmail.com'],
|
||||
'Failed to parse 1 address with leading whitespace and comma');
|
||||
|
||||
assertParsedList('<foo@gmail.com>, <bar@gmail.com>',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses');
|
||||
|
||||
assertParsedList('<foo@gmail.com>, <bar@gmail.com>,',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses and trailing comma');
|
||||
|
||||
assertParsedList('<foo@gmail.com>, <bar@gmail.com>, ',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses, trailing comma and whitespace');
|
||||
|
||||
assertParsedList(
|
||||
'John Doe <john@gmail.com>; Jane Doe <jane@gmail.com>, ' +
|
||||
'<jerry@gmail.com>',
|
||||
['john@gmail.com', 'jane@gmail.com', 'jerry@gmail.com'],
|
||||
'Failed to parse addresses with semicolon separator');
|
||||
}
|
||||
|
||||
function testparseListOpenersAndClosers() {
|
||||
assertParsedList(
|
||||
'aaa@gmail.com, "bbb@gmail.com", <ccc@gmail.com>, ' +
|
||||
'(ddd@gmail.com), [eee@gmail.com]',
|
||||
['aaa@gmail.com', '"bbb@gmail.com"', 'ccc@gmail.com',
|
||||
'(ddd@gmail.com)', '[eee@gmail.com]'],
|
||||
'Failed to handle all 5 opener/closer characters');
|
||||
}
|
||||
|
||||
function testparseListIdn() {
|
||||
var idnaddr = 'mailtest@\u4F8B\u3048.\u30C6\u30B9\u30C8';
|
||||
assertParsedList(idnaddr, [idnaddr]);
|
||||
}
|
||||
|
||||
function testparseListWithQuotedSpecialChars() {
|
||||
var res = assertParsedList(
|
||||
'a\\"b\\"c <d@e.f>,"g\\"h\\"i\\\\" <j@k.l>',
|
||||
['d@e.f', 'j@k.l']);
|
||||
assertEquals('Wrong name 0', 'a"b"c', res[0].getName());
|
||||
assertEquals('Wrong name 1', 'g"h"i\\', res[1].getName());
|
||||
}
|
||||
|
||||
function testparseListWithCommaInLocalPart() {
|
||||
var res = assertParsedList(
|
||||
'"Doe, John" <doe.john@gmail.com>, <someone@gmail.com>',
|
||||
['doe.john@gmail.com', 'someone@gmail.com']);
|
||||
|
||||
assertEquals('Doe, John', res[0].getName());
|
||||
assertEquals('', res[1].getName());
|
||||
}
|
||||
|
||||
function testparseListWithWhitespaceSeparatedEmails() {
|
||||
var res = assertParsedList(
|
||||
'a@b.com <c@d.com> e@f.com "G H" <g@h.com> i@j.com',
|
||||
['a@b.com', 'c@d.com', 'e@f.com', 'g@h.com', 'i@j.com']);
|
||||
assertEquals('G H', res[3].getName());
|
||||
}
|
||||
|
||||
function testparseListSystemNewlines() {
|
||||
// These Windows newlines can be inserted in IE8, or copied-and-pasted from
|
||||
// bad data on a Mac, as seen in bug 11081852.
|
||||
assertParsedList('a@b.com\r\nc@d.com', ['a@b.com', 'c@d.com'],
|
||||
'Failed to parse Windows newlines');
|
||||
assertParsedList('a@b.com\nc@d.com', ['a@b.com', 'c@d.com'],
|
||||
'Failed to parse *nix newlines');
|
||||
assertParsedList('a@b.com\n\rc@d.com', ['a@b.com', 'c@d.com'],
|
||||
'Failed to parse obsolete newlines');
|
||||
assertParsedList('a@b.com\rc@d.com', ['a@b.com', 'c@d.com'],
|
||||
'Failed to parse pre-OS X Mac newlines');
|
||||
}
|
||||
|
||||
function testToString() {
|
||||
var f = function(str) {
|
||||
return goog.format.EmailAddress.parse(str).toString();
|
||||
};
|
||||
|
||||
// No modification.
|
||||
assertEquals('JOHN Doe <john@gmail.com>',
|
||||
f('JOHN Doe <john@gmail.com>'));
|
||||
|
||||
// Extra spaces.
|
||||
assertEquals('JOHN Doe <john@gmail.com>',
|
||||
f(' JOHN Doe <john@gmail.com> '));
|
||||
|
||||
// No name.
|
||||
assertEquals('john@gmail.com', f('<john@gmail.com>'));
|
||||
assertEquals('john@gmail.com', f('john@gmail.com'));
|
||||
|
||||
// No address.
|
||||
assertEquals('JOHN Doe', f('JOHN Doe <>'));
|
||||
|
||||
// Special chars in the name.
|
||||
assertEquals('"JOHN, Doe" <john@gmail.com>',
|
||||
f('JOHN, Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN(Johnny) Doe" <john@gmail.com>',
|
||||
f('JOHN(Johnny) Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN[Johnny] Doe" <john@gmail.com>',
|
||||
f('JOHN[Johnny] Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN@work Doe" <john@gmail.com>',
|
||||
f('JOHN@work Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN:theking Doe" <john@gmail.com>',
|
||||
f('JOHN:theking Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN\\\\ Doe" <john@gmail.com>',
|
||||
f('JOHN\\ Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN.com Doe" <john@gmail.com>',
|
||||
f('JOHN.com Doe <john@gmail.com>'));
|
||||
|
||||
// Already quoted.
|
||||
assertEquals('"JOHN, Doe" <john@gmail.com>',
|
||||
f('"JOHN, Doe" <john@gmail.com>'));
|
||||
|
||||
// Needless quotes.
|
||||
assertEquals('JOHN Doe <john@gmail.com>',
|
||||
f('"JOHN Doe" <john@gmail.com>'));
|
||||
// Not quoted-string, but has double quotes.
|
||||
assertEquals('"JOHN, Doe" <john@gmail.com>',
|
||||
f('JOHN, "Doe" <john@gmail.com>'));
|
||||
|
||||
// No special characters other than quotes.
|
||||
assertEquals('JOHN Doe <john@gmail.com>',
|
||||
f('JOHN "Doe" <john@gmail.com>'));
|
||||
|
||||
// Escaped quotes are also removed.
|
||||
assertEquals('"JOHN, Doe" <john@gmail.com>',
|
||||
f('JOHN, \\"Doe\\" <john@gmail.com>'));
|
||||
}
|
||||
|
||||
function doIsValidTest(testFunc, valid, invalid) {
|
||||
goog.array.forEach(valid, function(str) {
|
||||
assertTrue('"' + str + '" should be valid.', testFunc(str));
|
||||
});
|
||||
goog.array.forEach(invalid, function(str) {
|
||||
assertFalse('"' + str + '" should be invalid.', testFunc(str));
|
||||
});
|
||||
}
|
||||
|
||||
function testIsValid() {
|
||||
var valid = [
|
||||
'e@b.eu', '<a.b+foo@c.com>', 'eric <e@b.com>', '"e" <e@b.com>',
|
||||
'a@FOO.MUSEUM', 'bla@b.co.ac.uk', 'bla@a.b.com', 'o\'hara@gm.com',
|
||||
'plus+is+allowed@gmail.com', '!/#$%&\'*+-=~|`{}?^_@expample.com',
|
||||
'confirm-bhk=modulo.org@yahoogroups.com'];
|
||||
var invalid = [
|
||||
'e', '', 'e @c.com', 'a@b', 'foo.com', 'foo@c..com', 'test@gma=il.com',
|
||||
'aaa@gmail', 'has some spaces@gmail.com', 'has@three@at@signs.com',
|
||||
'@no-local-part.com', 'み.ん-あ@みんあ.みんあ',
|
||||
'みんあ@test.com', 'test@test.みんあ', 'test@みんあ.com',
|
||||
'fullwidthfullstop@sld' + '\uff0e' + 'tld',
|
||||
'ideographicfullstop@sld' + '\u3002' + 'tld',
|
||||
'halfwidthideographicfullstop@sld' + '\uff61' + 'tld'];
|
||||
doIsValidTest(goog.format.EmailAddress.isValidAddress, valid, invalid);
|
||||
}
|
||||
|
||||
function testIsValidLocalPart() {
|
||||
var valid = [
|
||||
'e', 'a.b+foo', 'o\'hara', 'user+someone', '!/#$%&\'*+-=~|`{}?^_',
|
||||
'confirm-bhk=modulo.org'];
|
||||
var invalid = [
|
||||
'A@b@c', 'a"b(c)d,e:f;g<h>i[j\\k]l', 'just"not"right',
|
||||
'this is"not\\allowed', 'this\\ still\"not\\\\allowed', 'has some spaces'];
|
||||
doIsValidTest(goog.format.EmailAddress.isValidLocalPartSpec, valid, invalid);
|
||||
}
|
||||
|
||||
function testIsValidDomainPart() {
|
||||
var valid = [
|
||||
'example.com', 'dept.example.org', 'long.domain.with.lots.of.dots'];
|
||||
var invalid = ['', '@has.an.at.sign', '..has.leading.dots', 'gma=il.com',
|
||||
'DoesNotHaveADot', 'sld' + '\uff0e' + 'tld', 'sld' + '\u3002' + 'tld',
|
||||
'sld' + '\uff61' + 'tld'];
|
||||
doIsValidTest(goog.format.EmailAddress.isValidDomainPartSpec, valid, invalid);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Asserts that parsing the inputString produces a list of email addresses
|
||||
* containing the specified address strings, irrespective of their order.
|
||||
* @param {string} inputString A raw address list.
|
||||
* @param {Array<string>} expectedList The expected results.
|
||||
* @param {string=} opt_message An assertion message.
|
||||
* @return {string} the resulting email address objects.
|
||||
*/
|
||||
function assertParsedList(inputString, expectedList, opt_message) {
|
||||
var message = opt_message || 'Should parse address correctly';
|
||||
var result = goog.format.EmailAddress.parseList(inputString);
|
||||
assertEquals(
|
||||
'Should have correct # of addresses', expectedList.length, result.length);
|
||||
for (var i = 0; i < expectedList.length; ++i) {
|
||||
assertEquals(message, expectedList[i], result[i].getAddress());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,502 @@
|
||||
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Provides utility functions for formatting strings, numbers etc.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.format');
|
||||
|
||||
goog.require('goog.i18n.GraphemeBreak');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* Formats a number of bytes in human readable form.
|
||||
* 54, 450K, 1.3M, 5G etc.
|
||||
* @param {number} bytes The number of bytes to show.
|
||||
* @param {number=} opt_decimals The number of decimals to use. Defaults to 2.
|
||||
* @return {string} The human readable form of the byte size.
|
||||
*/
|
||||
goog.format.fileSize = function(bytes, opt_decimals) {
|
||||
return goog.format.numBytesToString(bytes, opt_decimals, false);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether string value containing scaling units (K, M, G, T, P, m,
|
||||
* u, n) can be converted to a number.
|
||||
*
|
||||
* Where there is a decimal, there must be a digit to the left of the
|
||||
* decimal point.
|
||||
*
|
||||
* Negative numbers are valid.
|
||||
*
|
||||
* Examples:
|
||||
* 0, 1, 1.0, 10.4K, 2.3M, -0.3P, 1.2m
|
||||
*
|
||||
* @param {string} val String value to check.
|
||||
* @return {boolean} True if string could be converted to a numeric value.
|
||||
*/
|
||||
goog.format.isConvertableScaledNumber = function(val) {
|
||||
return goog.format.SCALED_NUMERIC_RE_.test(val);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a string to numeric value, taking into account the units.
|
||||
* If string ends in 'B', use binary conversion.
|
||||
* @param {string} stringValue String to be converted to numeric value.
|
||||
* @return {number} Numeric value for string.
|
||||
*/
|
||||
goog.format.stringToNumericValue = function(stringValue) {
|
||||
if (goog.string.endsWith(stringValue, 'B')) {
|
||||
return goog.format.stringToNumericValue_(
|
||||
stringValue, goog.format.NUMERIC_SCALES_BINARY_);
|
||||
}
|
||||
return goog.format.stringToNumericValue_(
|
||||
stringValue, goog.format.NUMERIC_SCALES_SI_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a string to number of bytes, taking into account the units.
|
||||
* Binary conversion.
|
||||
* @param {string} stringValue String to be converted to numeric value.
|
||||
* @return {number} Numeric value for string.
|
||||
*/
|
||||
goog.format.stringToNumBytes = function(stringValue) {
|
||||
return goog.format.stringToNumericValue_(
|
||||
stringValue, goog.format.NUMERIC_SCALES_BINARY_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a numeric value to string representation. SI conversion.
|
||||
* @param {number} val Value to be converted.
|
||||
* @param {number=} opt_decimals The number of decimals to use. Defaults to 2.
|
||||
* @return {string} String representation of number.
|
||||
*/
|
||||
goog.format.numericValueToString = function(val, opt_decimals) {
|
||||
return goog.format.numericValueToString_(
|
||||
val, goog.format.NUMERIC_SCALES_SI_, opt_decimals);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts number of bytes to string representation. Binary conversion.
|
||||
* Default is to return the additional 'B' suffix, e.g. '10.5KB' to minimize
|
||||
* confusion with counts that are scaled by powers of 1000.
|
||||
* @param {number} val Value to be converted.
|
||||
* @param {number=} opt_decimals The number of decimals to use. Defaults to 2.
|
||||
* @param {boolean=} opt_suffix If true, include trailing 'B' in returned
|
||||
* string. Default is true.
|
||||
* @param {boolean=} opt_useSeparator If true, number and scale will be
|
||||
* separated by a no break space. Default is false.
|
||||
* @return {string} String representation of number of bytes.
|
||||
*/
|
||||
goog.format.numBytesToString = function(val, opt_decimals, opt_suffix,
|
||||
opt_useSeparator) {
|
||||
var suffix = '';
|
||||
if (!goog.isDef(opt_suffix) || opt_suffix) {
|
||||
suffix = 'B';
|
||||
}
|
||||
return goog.format.numericValueToString_(
|
||||
val, goog.format.NUMERIC_SCALES_BINARY_, opt_decimals, suffix,
|
||||
opt_useSeparator);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a string to numeric value, taking into account the units.
|
||||
* @param {string} stringValue String to be converted to numeric value.
|
||||
* @param {Object} conversion Dictionary of conversion scales.
|
||||
* @return {number} Numeric value for string. If it cannot be converted,
|
||||
* returns NaN.
|
||||
* @private
|
||||
*/
|
||||
goog.format.stringToNumericValue_ = function(stringValue, conversion) {
|
||||
var match = stringValue.match(goog.format.SCALED_NUMERIC_RE_);
|
||||
if (!match) {
|
||||
return NaN;
|
||||
}
|
||||
var val = match[1] * conversion[match[2]];
|
||||
return val;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a numeric value to string, using specified conversion
|
||||
* scales.
|
||||
* @param {number} val Value to be converted.
|
||||
* @param {Object} conversion Dictionary of scaling factors.
|
||||
* @param {number=} opt_decimals The number of decimals to use. Default is 2.
|
||||
* @param {string=} opt_suffix Optional suffix to append.
|
||||
* @param {boolean=} opt_useSeparator If true, number and scale will be
|
||||
* separated by a space. Default is false.
|
||||
* @return {string} The human readable form of the byte size.
|
||||
* @private
|
||||
*/
|
||||
goog.format.numericValueToString_ = function(val, conversion,
|
||||
opt_decimals, opt_suffix, opt_useSeparator) {
|
||||
var prefixes = goog.format.NUMERIC_SCALE_PREFIXES_;
|
||||
var orig_val = val;
|
||||
var symbol = '';
|
||||
var separator = '';
|
||||
var scale = 1;
|
||||
if (val < 0) {
|
||||
val = -val;
|
||||
}
|
||||
for (var i = 0; i < prefixes.length; i++) {
|
||||
var unit = prefixes[i];
|
||||
scale = conversion[unit];
|
||||
if (val >= scale || (scale <= 1 && val > 0.1 * scale)) {
|
||||
// Treat values less than 1 differently, allowing 0.5 to be "0.5" rather
|
||||
// than "500m"
|
||||
symbol = unit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!symbol) {
|
||||
scale = 1;
|
||||
} else {
|
||||
if (opt_suffix) {
|
||||
symbol += opt_suffix;
|
||||
}
|
||||
if (opt_useSeparator) {
|
||||
separator = ' ';
|
||||
}
|
||||
}
|
||||
var ex = Math.pow(10, goog.isDef(opt_decimals) ? opt_decimals : 2);
|
||||
return Math.round(orig_val / scale * ex) / ex + separator + symbol;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression for detecting scaling units, such as K, M, G, etc. for
|
||||
* converting a string representation to a numeric value.
|
||||
*
|
||||
* Also allow 'k' to be aliased to 'K'. These could be used for SI (powers
|
||||
* of 1000) or Binary (powers of 1024) conversions.
|
||||
*
|
||||
* Also allow final 'B' to be interpreted as byte-count, implicitly triggering
|
||||
* binary conversion (e.g., '10.2MB').
|
||||
*
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.format.SCALED_NUMERIC_RE_ = /^([-]?\d+\.?\d*)([K,M,G,T,P,k,m,u,n]?)[B]?$/;
|
||||
|
||||
|
||||
/**
|
||||
* Ordered list of scaling prefixes in decreasing order.
|
||||
* @private {Array<string>}
|
||||
*/
|
||||
goog.format.NUMERIC_SCALE_PREFIXES_ = [
|
||||
'P', 'T', 'G', 'M', 'K', '', 'm', 'u', 'n'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Scaling factors for conversion of numeric value to string. SI conversion.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.format.NUMERIC_SCALES_SI_ = {
|
||||
'': 1,
|
||||
'n': 1e-9,
|
||||
'u': 1e-6,
|
||||
'm': 1e-3,
|
||||
'k': 1e3,
|
||||
'K': 1e3,
|
||||
'M': 1e6,
|
||||
'G': 1e9,
|
||||
'T': 1e12,
|
||||
'P': 1e15
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Scaling factors for conversion of numeric value to string. Binary
|
||||
* conversion.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.format.NUMERIC_SCALES_BINARY_ = {
|
||||
'': 1,
|
||||
'n': Math.pow(1024, -3),
|
||||
'u': Math.pow(1024, -2),
|
||||
'm': 1.0 / 1024,
|
||||
'k': 1024,
|
||||
'K': 1024,
|
||||
'M': Math.pow(1024, 2),
|
||||
'G': Math.pow(1024, 3),
|
||||
'T': Math.pow(1024, 4),
|
||||
'P': Math.pow(1024, 5)
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* First Unicode code point that has the Mark property.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.format.FIRST_GRAPHEME_EXTEND_ = 0x300;
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if and only if given character should be treated as a breaking
|
||||
* space. All ASCII control characters, the main Unicode range of spacing
|
||||
* characters (U+2000 to U+200B inclusive except for U+2007), and several other
|
||||
* Unicode space characters are treated as breaking spaces.
|
||||
* @param {number} charCode The character code under consideration.
|
||||
* @return {boolean} True if the character is a breaking space.
|
||||
* @private
|
||||
*/
|
||||
goog.format.isTreatedAsBreakingSpace_ = function(charCode) {
|
||||
return (charCode <= goog.format.WbrToken_.SPACE) ||
|
||||
(charCode >= 0x1000 &&
|
||||
((charCode >= 0x2000 && charCode <= 0x2006) ||
|
||||
(charCode >= 0x2008 && charCode <= 0x200B) ||
|
||||
charCode == 0x1680 ||
|
||||
charCode == 0x180E ||
|
||||
charCode == 0x2028 ||
|
||||
charCode == 0x2029 ||
|
||||
charCode == 0x205f ||
|
||||
charCode == 0x3000));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if and only if given character is an invisible formatting
|
||||
* character.
|
||||
* @param {number} charCode The character code under consideration.
|
||||
* @return {boolean} True if the character is an invisible formatting character.
|
||||
* @private
|
||||
*/
|
||||
goog.format.isInvisibleFormattingCharacter_ = function(charCode) {
|
||||
// See: http://unicode.org/charts/PDF/U2000.pdf
|
||||
return (charCode >= 0x200C && charCode <= 0x200F) ||
|
||||
(charCode >= 0x202A && charCode <= 0x202E);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Inserts word breaks into an HTML string at a given interval. The counter is
|
||||
* reset if a space or a character which behaves like a space is encountered,
|
||||
* but it isn't incremented if an invisible formatting character is encountered.
|
||||
* WBRs aren't inserted into HTML tags or entities. Entities count towards the
|
||||
* character count, HTML tags do not.
|
||||
*
|
||||
* With common strings aliased, objects allocations are constant based on the
|
||||
* length of the string: N + 3. This guarantee does not hold if the string
|
||||
* contains an element >= U+0300 and hasGraphemeBreak is non-trivial.
|
||||
*
|
||||
* @param {string} str HTML to insert word breaks into.
|
||||
* @param {function(number, number, boolean): boolean} hasGraphemeBreak A
|
||||
* function determining if there is a grapheme break between two characters,
|
||||
* in the same signature as goog.i18n.GraphemeBreak.hasGraphemeBreak.
|
||||
* @param {number=} opt_maxlen Maximum length after which to ensure
|
||||
* there is a break. Default is 10 characters.
|
||||
* @return {string} The string including word breaks.
|
||||
* @private
|
||||
*/
|
||||
goog.format.insertWordBreaksGeneric_ = function(str, hasGraphemeBreak,
|
||||
opt_maxlen) {
|
||||
var maxlen = opt_maxlen || 10;
|
||||
if (maxlen > str.length) return str;
|
||||
|
||||
var rv = [];
|
||||
var n = 0; // The length of the current token
|
||||
|
||||
// This will contain the ampersand or less-than character if one of the
|
||||
// two has been seen; otherwise, the value is zero.
|
||||
var nestingCharCode = 0;
|
||||
|
||||
// First character position from input string that has not been outputted.
|
||||
var lastDumpPosition = 0;
|
||||
|
||||
var charCode = 0;
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
// Using charCodeAt versus charAt avoids allocating new string objects.
|
||||
var lastCharCode = charCode;
|
||||
charCode = str.charCodeAt(i);
|
||||
|
||||
// Don't add a WBR before characters that might be grapheme extending.
|
||||
var isPotentiallyGraphemeExtending =
|
||||
charCode >= goog.format.FIRST_GRAPHEME_EXTEND_ &&
|
||||
!hasGraphemeBreak(lastCharCode, charCode, true);
|
||||
|
||||
// Don't add a WBR at the end of a word. For the purposes of determining
|
||||
// work breaks, all ASCII control characters and some commonly encountered
|
||||
// Unicode spacing characters are treated as breaking spaces.
|
||||
if (n >= maxlen &&
|
||||
!goog.format.isTreatedAsBreakingSpace_(charCode) &&
|
||||
!isPotentiallyGraphemeExtending) {
|
||||
// Flush everything seen so far, and append a word break.
|
||||
rv.push(str.substring(lastDumpPosition, i), goog.format.WORD_BREAK_HTML);
|
||||
lastDumpPosition = i;
|
||||
n = 0;
|
||||
}
|
||||
|
||||
if (!nestingCharCode) {
|
||||
// Not currently within an HTML tag or entity
|
||||
|
||||
if (charCode == goog.format.WbrToken_.LT ||
|
||||
charCode == goog.format.WbrToken_.AMP) {
|
||||
|
||||
// Entering an HTML Entity '&' or open tag '<'
|
||||
nestingCharCode = charCode;
|
||||
} else if (goog.format.isTreatedAsBreakingSpace_(charCode)) {
|
||||
|
||||
// A space or control character -- reset the token length
|
||||
n = 0;
|
||||
} else if (!goog.format.isInvisibleFormattingCharacter_(charCode)) {
|
||||
|
||||
// A normal flow character - increment. For grapheme extending
|
||||
// characters, this is not *technically* a new character. However,
|
||||
// since the grapheme break detector might be overly conservative,
|
||||
// we have to continue incrementing, or else we won't even be able
|
||||
// to add breaks when we get to things like punctuation. For the
|
||||
// case where we have a full grapheme break detector, it is okay if
|
||||
// we occasionally break slightly early.
|
||||
n++;
|
||||
}
|
||||
} else if (charCode == goog.format.WbrToken_.GT &&
|
||||
nestingCharCode == goog.format.WbrToken_.LT) {
|
||||
|
||||
// Leaving an HTML tag, treat the tag as zero-length
|
||||
nestingCharCode = 0;
|
||||
} else if (charCode == goog.format.WbrToken_.SEMI_COLON &&
|
||||
nestingCharCode == goog.format.WbrToken_.AMP) {
|
||||
|
||||
// Leaving an HTML entity, treat it as length one
|
||||
nestingCharCode = 0;
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
// Take care of anything we haven't flushed so far.
|
||||
rv.push(str.substr(lastDumpPosition));
|
||||
|
||||
return rv.join('');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Inserts word breaks into an HTML string at a given interval.
|
||||
*
|
||||
* This method is as aggressive as possible, using a full table of Unicode
|
||||
* characters where it is legal to insert word breaks; however, this table
|
||||
* comes at a 2.5k pre-gzip (~1k post-gzip) size cost. Consider using
|
||||
* insertWordBreaksBasic to minimize the size impact.
|
||||
*
|
||||
* @param {string} str HTML to insert word breaks into.
|
||||
* @param {number=} opt_maxlen Maximum length after which to ensure there is a
|
||||
* break. Default is 10 characters.
|
||||
* @return {string} The string including word breaks.
|
||||
*/
|
||||
goog.format.insertWordBreaks = function(str, opt_maxlen) {
|
||||
return goog.format.insertWordBreaksGeneric_(str,
|
||||
goog.i18n.GraphemeBreak.hasGraphemeBreak, opt_maxlen);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines conservatively if a character has a Grapheme break.
|
||||
*
|
||||
* Conforms to a similar signature as goog.i18n.GraphemeBreak, but is overly
|
||||
* conservative, returning true only for characters in common scripts that
|
||||
* are simple to account for.
|
||||
*
|
||||
* @param {number} lastCharCode The previous character code. Ignored.
|
||||
* @param {number} charCode The character code under consideration. It must be
|
||||
* at least \u0300 as a precondition -- this case is covered by
|
||||
* insertWordBreaksGeneric_.
|
||||
* @param {boolean=} opt_extended Ignored, to conform with the interface.
|
||||
* @return {boolean} Whether it is one of the recognized subsets of characters
|
||||
* with a grapheme break.
|
||||
* @private
|
||||
*/
|
||||
goog.format.conservativelyHasGraphemeBreak_ = function(
|
||||
lastCharCode, charCode, opt_extended) {
|
||||
// Return false for everything except the most common Cyrillic characters.
|
||||
// Don't worry about Latin characters, because insertWordBreaksGeneric_
|
||||
// itself already handles those.
|
||||
// TODO(gboyer): Also account for Greek, Armenian, and Georgian if it is
|
||||
// simple to do so.
|
||||
return charCode >= 0x400 && charCode < 0x523;
|
||||
};
|
||||
|
||||
|
||||
// TODO(gboyer): Consider using a compile-time flag to switch implementations
|
||||
// rather than relying on the developers to toggle implementations.
|
||||
/**
|
||||
* Inserts word breaks into an HTML string at a given interval.
|
||||
*
|
||||
* This method is less aggressive than insertWordBreaks, only inserting
|
||||
* breaks next to punctuation and between Latin or Cyrillic characters.
|
||||
* However, this is good enough for the common case of URLs. It also
|
||||
* works for all Latin and Cyrillic languages, plus CJK has no need for word
|
||||
* breaks. When this method is used, goog.i18n.GraphemeBreak may be dead
|
||||
* code eliminated.
|
||||
*
|
||||
* @param {string} str HTML to insert word breaks into.
|
||||
* @param {number=} opt_maxlen Maximum length after which to ensure there is a
|
||||
* break. Default is 10 characters.
|
||||
* @return {string} The string including word breaks.
|
||||
*/
|
||||
goog.format.insertWordBreaksBasic = function(str, opt_maxlen) {
|
||||
return goog.format.insertWordBreaksGeneric_(str,
|
||||
goog.format.conservativelyHasGraphemeBreak_, opt_maxlen);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* True iff the current userAgent is IE8 or above.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.format.IS_IE8_OR_ABOVE_ = goog.userAgent.IE &&
|
||||
goog.userAgent.isVersionOrHigher(8);
|
||||
|
||||
|
||||
/**
|
||||
* Constant for the WBR replacement used by insertWordBreaks. Safari requires
|
||||
* <wbr></wbr>, Opera needs the ­ entity, though this will give a visible
|
||||
* hyphen at breaks. IE8 uses a zero width space.
|
||||
* Other browsers just use <wbr>.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.format.WORD_BREAK_HTML =
|
||||
goog.userAgent.WEBKIT ?
|
||||
'<wbr></wbr>' : goog.userAgent.OPERA ?
|
||||
'­' : goog.format.IS_IE8_OR_ABOVE_ ?
|
||||
'​' : '<wbr>';
|
||||
|
||||
|
||||
/**
|
||||
* Tokens used within insertWordBreaks.
|
||||
* @private
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.format.WbrToken_ = {
|
||||
LT: 60, // '<'.charCodeAt(0)
|
||||
GT: 62, // '>'.charCodeAt(0)
|
||||
AMP: 38, // '&'.charCodeAt(0)
|
||||
SEMI_COLON: 59, // ';'.charCodeAt(0)
|
||||
SPACE: 32 // ' '.charCodeAt(0)
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.format
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.formatTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,303 @@
|
||||
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
goog.provide('goog.formatTest');
|
||||
goog.setTestOnly('goog.formatTest');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.format');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.testing.PropertyReplacer');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var propertyReplacer = new goog.testing.PropertyReplacer();
|
||||
|
||||
function tearDown() {
|
||||
// set wordBreakHtml back to the original value (some tests edit this member).
|
||||
propertyReplacer.reset();
|
||||
}
|
||||
|
||||
function testFormatFileSize() {
|
||||
var fileSize = goog.format.fileSize;
|
||||
|
||||
assertEquals('45', fileSize(45));
|
||||
assertEquals('45', fileSize(45, 0));
|
||||
assertEquals('45', fileSize(45, 1));
|
||||
assertEquals('45', fileSize(45, 3));
|
||||
assertEquals('454', fileSize(454));
|
||||
assertEquals('600', fileSize(600));
|
||||
|
||||
assertEquals('1K', fileSize(1024));
|
||||
assertEquals('2K', fileSize(2 * 1024));
|
||||
assertEquals('5K', fileSize(5 * 1024));
|
||||
assertEquals('5.123K', fileSize(5.12345 * 1024, 3));
|
||||
assertEquals('5.68K', fileSize(5.678 * 1024, 2));
|
||||
|
||||
assertEquals('1M', fileSize(1024 * 1024));
|
||||
assertEquals('1.5M', fileSize(1.5 * 1024 * 1024));
|
||||
assertEquals('2M', fileSize(1.5 * 1024 * 1024, 0));
|
||||
assertEquals('1.5M', fileSize(1.51 * 1024 * 1024, 1));
|
||||
assertEquals('1.56M', fileSize(1.56 * 1024 * 1024, 2));
|
||||
|
||||
assertEquals('1G', fileSize(1024 * 1024 * 1024));
|
||||
assertEquals('6G', fileSize(6 * 1024 * 1024 * 1024));
|
||||
assertEquals('12.06T', fileSize(12345.6789 * 1024 * 1024 * 1024));
|
||||
}
|
||||
|
||||
function testIsConvertableScaledNumber() {
|
||||
var isConvertableScaledNumber = goog.format.isConvertableScaledNumber;
|
||||
|
||||
assertTrue(isConvertableScaledNumber('0'));
|
||||
assertTrue(isConvertableScaledNumber('45'));
|
||||
assertTrue(isConvertableScaledNumber('45K'));
|
||||
assertTrue(isConvertableScaledNumber('45MB'));
|
||||
assertTrue(isConvertableScaledNumber('45GB'));
|
||||
assertTrue(isConvertableScaledNumber('45T'));
|
||||
assertTrue(isConvertableScaledNumber('2.33P'));
|
||||
assertTrue(isConvertableScaledNumber('45m'));
|
||||
assertTrue(isConvertableScaledNumber('45u'));
|
||||
assertTrue(isConvertableScaledNumber('-5.0n'));
|
||||
|
||||
assertFalse(isConvertableScaledNumber('45x'));
|
||||
assertFalse(isConvertableScaledNumber('ux'));
|
||||
assertFalse(isConvertableScaledNumber('K'));
|
||||
}
|
||||
|
||||
function testNumericValueToString() {
|
||||
var numericValueToString = goog.format.numericValueToString;
|
||||
|
||||
assertEquals('0', numericValueToString(0.0));
|
||||
assertEquals('45', numericValueToString(45));
|
||||
assertEquals('454', numericValueToString(454));
|
||||
assertEquals('600', numericValueToString(600));
|
||||
|
||||
assertEquals('1.02K', numericValueToString(1024));
|
||||
assertEquals('2.05K', numericValueToString(2 * 1024));
|
||||
assertEquals('5.12K', numericValueToString(5 * 1024));
|
||||
assertEquals('5.246K', numericValueToString(5.12345 * 1024, 3));
|
||||
assertEquals('5.81K', numericValueToString(5.678 * 1024, 2));
|
||||
|
||||
assertEquals('1.05M', numericValueToString(1024 * 1024));
|
||||
assertEquals('1.57M', numericValueToString(1.5 * 1024 * 1024));
|
||||
assertEquals('2M', numericValueToString(1.5 * 1024 * 1024, 0));
|
||||
assertEquals('1.6M', numericValueToString(1.51 * 1024 * 1024, 1));
|
||||
assertEquals('1.64M', numericValueToString(1.56 * 1024 * 1024, 2));
|
||||
|
||||
assertEquals('1.07G', numericValueToString(1024 * 1024 * 1024));
|
||||
assertEquals('6.44G', numericValueToString(6 * 1024 * 1024 * 1024));
|
||||
assertEquals('13.26T', numericValueToString(12345.6789 * 1024 * 1024 * 1024));
|
||||
|
||||
assertEquals('23.4m', numericValueToString(0.0234));
|
||||
assertEquals('1.23u', numericValueToString(0.00000123));
|
||||
assertEquals('15.78n', numericValueToString(0.000000015784));
|
||||
assertEquals('0.58u', numericValueToString(0.0000005784));
|
||||
assertEquals('0.5', numericValueToString(0.5));
|
||||
|
||||
assertEquals('-45', numericValueToString(-45.3, 0));
|
||||
assertEquals('-45', numericValueToString(-45.5, 0));
|
||||
assertEquals('-46', numericValueToString(-45.51, 0));
|
||||
}
|
||||
|
||||
function testFormatNumBytes() {
|
||||
var numBytesToString = goog.format.numBytesToString;
|
||||
|
||||
assertEquals('45', numBytesToString(45));
|
||||
assertEquals('454', numBytesToString(454));
|
||||
|
||||
assertEquals('5KB', numBytesToString(5 * 1024));
|
||||
assertEquals('1MB', numBytesToString(1024 * 1024));
|
||||
assertEquals('6GB', numBytesToString(6 * 1024 * 1024 * 1024));
|
||||
assertEquals('12.06TB', numBytesToString(12345.6789 * 1024 * 1024 * 1024));
|
||||
|
||||
assertEquals('454', numBytesToString(454, 2, true, true));
|
||||
assertEquals('5 KB', numBytesToString(5 * 1024, 2, true, true));
|
||||
}
|
||||
|
||||
function testStringToNumeric() {
|
||||
var stringToNumericValue = goog.format.stringToNumericValue;
|
||||
var epsilon = Math.pow(10, -10);
|
||||
|
||||
assertNaN(stringToNumericValue('foo'));
|
||||
|
||||
assertEquals(45, stringToNumericValue('45'));
|
||||
assertEquals(-45, stringToNumericValue('-45'));
|
||||
assertEquals(-45, stringToNumericValue('-45'));
|
||||
assertEquals(454, stringToNumericValue('454'));
|
||||
|
||||
assertEquals(5 * 1024, stringToNumericValue('5KB'));
|
||||
assertEquals(1024 * 1024, stringToNumericValue('1MB'));
|
||||
assertEquals(6 * 1024 * 1024 * 1024, stringToNumericValue('6GB'));
|
||||
assertEquals(13260110230978.56, stringToNumericValue('12.06TB'));
|
||||
|
||||
assertEquals(5010, stringToNumericValue('5.01K'));
|
||||
assertEquals(5100000, stringToNumericValue('5.1M'));
|
||||
assertTrue(Math.abs(0.051 - stringToNumericValue('51.0m')) < epsilon);
|
||||
assertTrue(Math.abs(0.000051 - stringToNumericValue('51.0u')) < epsilon);
|
||||
}
|
||||
|
||||
function testStringToNumBytes() {
|
||||
var stringToNumBytes = goog.format.stringToNumBytes;
|
||||
|
||||
assertEquals(45, stringToNumBytes('45'));
|
||||
assertEquals(454, stringToNumBytes('454'));
|
||||
|
||||
assertEquals(5 * 1024, stringToNumBytes('5K'));
|
||||
assertEquals(1024 * 1024, stringToNumBytes('1M'));
|
||||
assertEquals(6 * 1024 * 1024 * 1024, stringToNumBytes('6G'));
|
||||
assertEquals(13260110230978.56, stringToNumBytes('12.06T'));
|
||||
}
|
||||
|
||||
function testInsertWordBreaks() {
|
||||
// HTML that gets inserted is browser dependent, ensure for the test it is
|
||||
// a constant - browser dependent HTML is for display purposes only.
|
||||
propertyReplacer.set(goog.format, 'WORD_BREAK_HTML', '<wbr>');
|
||||
|
||||
var insertWordBreaks = goog.format.insertWordBreaks;
|
||||
|
||||
assertEquals('abcdef', insertWordBreaks('abcdef', 10));
|
||||
assertEquals('ab<wbr>cd<wbr>ef', insertWordBreaks('abcdef', 2));
|
||||
assertEquals(
|
||||
'a<wbr>b<wbr>c<wbr>d<wbr>e<wbr>f', insertWordBreaks('abcdef', 1));
|
||||
|
||||
assertEquals(
|
||||
'a&b=<wbr>=fal<wbr>se', insertWordBreaks('a&b==false', 4));
|
||||
assertEquals('<&>»<wbr>«',
|
||||
insertWordBreaks('<&>»«', 4));
|
||||
|
||||
assertEquals('a<wbr>b<wbr>c d<wbr>e<wbr>f', insertWordBreaks('abc def', 1));
|
||||
assertEquals('ab<wbr>c de<wbr>f', insertWordBreaks('abc def', 2));
|
||||
assertEquals('abc def', insertWordBreaks('abc def', 3));
|
||||
assertEquals('abc def', insertWordBreaks('abc def', 4));
|
||||
|
||||
assertEquals('a<b>cd</b>e<wbr>f', insertWordBreaks('a<b>cd</b>ef', 4));
|
||||
assertEquals('Thi<wbr>s is a <a href="">lin<wbr>k</a>.',
|
||||
insertWordBreaks('This is a <a href="">link</a>.', 3));
|
||||
assertEquals('<abc a="&&&&&">a<wbr>b',
|
||||
insertWordBreaks('<abc a="&&&&&">ab', 1));
|
||||
|
||||
assertEquals('ab\u0300<wbr>cd', insertWordBreaks('ab\u0300cd', 2));
|
||||
assertEquals('ab\u036F<wbr>cd', insertWordBreaks('ab\u036Fcd', 2));
|
||||
assertEquals('ab<wbr>\u0370c<wbr>d', insertWordBreaks('ab\u0370cd', 2));
|
||||
assertEquals('ab<wbr>\uFE1Fc<wbr>d', insertWordBreaks('ab\uFE1Fcd', 2));
|
||||
assertEquals('ab\u0300<wbr>c\u0301<wbr>de<wbr>f',
|
||||
insertWordBreaks('ab\u0300c\u0301def', 2));
|
||||
}
|
||||
|
||||
function testInsertWordBreaksWithFormattingCharacters() {
|
||||
// HTML that gets inserted is browser dependent, ensure for the test it is
|
||||
// a constant - browser dependent HTML is for display purposes only.
|
||||
propertyReplacer.set(goog.format, 'WORD_BREAK_HTML', '<wbr>');
|
||||
var insertWordBreaks = goog.format.insertWordBreaks;
|
||||
|
||||
// A date in Arabic-Indic digits with Right-to-Left Marks (U+200F).
|
||||
// The date is "11<RLM>/01<RLM>/2012".
|
||||
var textWithRLMs = 'This is a date - ' +
|
||||
'\u0661\u0661\u200f/\u0660\u0661\u200f/\u0662\u0660\u0661\u0662';
|
||||
// A string of 10 Xs with invisible formatting characters in between.
|
||||
// These characters are in the ranges U+200C to U+200F and U+202A to
|
||||
// U+202E, inclusive. See: http://unicode.org/charts/PDF/U2000.pdf
|
||||
var stringWithInvisibleFormatting = 'X\u200cX\u200dX\u200eX\u200fX\u202a' +
|
||||
'X\u202bX\u202cX\u202dX\u202eX';
|
||||
// A string formed by concatenating copies of the previous string alternating
|
||||
// with characters which behave like breaking spaces. Besides the space
|
||||
// character itself, the other characters are in the range U+2000 to U+200B
|
||||
// inclusive, except for the exclusion of U+2007 and inclusion of U+2029.
|
||||
// See: http://unicode.org/charts/PDF/U2000.pdf
|
||||
var stringWithInvisibleFormattingAndSpacelikeCharacters =
|
||||
stringWithInvisibleFormatting + ' ' +
|
||||
stringWithInvisibleFormatting + '\u2000' +
|
||||
stringWithInvisibleFormatting + '\u2001' +
|
||||
stringWithInvisibleFormatting + '\u2002' +
|
||||
stringWithInvisibleFormatting + '\u2003' +
|
||||
stringWithInvisibleFormatting + '\u2005' +
|
||||
stringWithInvisibleFormatting + '\u2006' +
|
||||
stringWithInvisibleFormatting + '\u2008' +
|
||||
stringWithInvisibleFormatting + '\u2009' +
|
||||
stringWithInvisibleFormatting + '\u200A' +
|
||||
stringWithInvisibleFormatting + '\u200B' +
|
||||
stringWithInvisibleFormatting + '\u2029' +
|
||||
stringWithInvisibleFormatting;
|
||||
|
||||
// Test that the word break algorithm does not count RLMs towards word
|
||||
// length, and therefore does not insert word breaks into a typical date
|
||||
// written in Arabic-Indic digits with RTMs (b/5853915).
|
||||
assertEquals(textWithRLMs, insertWordBreaks(textWithRLMs, 10));
|
||||
|
||||
// Test that invisible formatting characters are not counted towards word
|
||||
// length, and that characters which are treated as breaking spaces behave as
|
||||
// breaking spaces.
|
||||
assertEquals(stringWithInvisibleFormattingAndSpacelikeCharacters,
|
||||
insertWordBreaks(stringWithInvisibleFormattingAndSpacelikeCharacters,
|
||||
10));
|
||||
}
|
||||
|
||||
function testInsertWordBreaksBasic() {
|
||||
// HTML that gets inserted is browser dependent, ensure for the test it is
|
||||
// a constant - browser dependent HTML is for display purposes only.
|
||||
propertyReplacer.set(goog.format, 'WORD_BREAK_HTML', '<wbr>');
|
||||
var insertWordBreaksBasic = goog.format.insertWordBreaksBasic;
|
||||
|
||||
assertEquals('abcdef', insertWordBreaksBasic('abcdef', 10));
|
||||
assertEquals('ab<wbr>cd<wbr>ef', insertWordBreaksBasic('abcdef', 2));
|
||||
assertEquals(
|
||||
'a<wbr>b<wbr>c<wbr>d<wbr>e<wbr>f', insertWordBreaksBasic('abcdef', 1));
|
||||
assertEquals('ab\u0300<wbr>c\u0301<wbr>de<wbr>f',
|
||||
insertWordBreaksBasic('ab\u0300c\u0301def', 2));
|
||||
|
||||
assertEquals(
|
||||
'Inserting word breaks into the word "Russia" should work fine.',
|
||||
'\u0420\u043E<wbr>\u0441\u0441<wbr>\u0438\u044F',
|
||||
insertWordBreaksBasic('\u0420\u043E\u0441\u0441\u0438\u044F', 2));
|
||||
|
||||
// The word 'Internet' in Hindi.
|
||||
var hindiInternet = '\u0907\u0902\u091F\u0930\u0928\u0947\u091F';
|
||||
assertEquals('The basic algorithm is not good enough to insert word ' +
|
||||
'breaks into Hindi.',
|
||||
hindiInternet, insertWordBreaksBasic(hindiInternet, 2));
|
||||
// The word 'Internet' in Hindi broken into slashes.
|
||||
assertEquals('Hindi can have word breaks inserted between slashes',
|
||||
hindiInternet + '<wbr>/' + hindiInternet + '<wbr>.' + hindiInternet,
|
||||
insertWordBreaksBasic(hindiInternet + '/' + hindiInternet + '.' +
|
||||
hindiInternet, 2));
|
||||
}
|
||||
|
||||
function testWordBreaksWorking() {
|
||||
var text = goog.string.repeat('test', 20);
|
||||
var textWbr = goog.string.repeat('test' + goog.format.WORD_BREAK_HTML, 20);
|
||||
|
||||
var overflowEl = goog.dom.createDom('div',
|
||||
{'style': 'width: 100px; overflow: hidden; margin 5px'});
|
||||
var wbrEl = goog.dom.createDom('div',
|
||||
{'style': 'width: 100px; overflow: hidden; margin-top: 15px'});
|
||||
goog.dom.appendChild(goog.global.document.body, overflowEl);
|
||||
goog.dom.appendChild(goog.global.document.body, wbrEl);
|
||||
|
||||
overflowEl.innerHTML = text;
|
||||
wbrEl.innerHTML = textWbr;
|
||||
|
||||
assertTrue('Text should overflow', overflowEl.scrollWidth > 100);
|
||||
assertTrue('Text should not overflow', wbrEl.scrollWidth <= 100);
|
||||
}
|
||||
|
||||
function testWordBreaksRemovedFromTextContent() {
|
||||
var expectedText = goog.string.repeat('test', 20);
|
||||
var textWbr = goog.string.repeat('test' + goog.format.WORD_BREAK_HTML, 20);
|
||||
|
||||
var wbrEl = goog.dom.createDom('div', null);
|
||||
wbrEl.innerHTML = textWbr;
|
||||
|
||||
assertEquals('text content should have wbr character removed', expectedText,
|
||||
goog.dom.getTextContent(wbrEl));
|
||||
|
||||
}
|
||||
@@ -0,0 +1,409 @@
|
||||
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Provides functions to parse and pretty-print HTML strings.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.format.HtmlPrettyPrinter');
|
||||
goog.provide('goog.format.HtmlPrettyPrinter.Buffer');
|
||||
|
||||
goog.require('goog.object');
|
||||
goog.require('goog.string.StringBuffer');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This class formats HTML to be more human-readable.
|
||||
* TODO(user): Add hierarchical indentation.
|
||||
* @param {number=} opt_timeOutMillis Max # milliseconds to spend on #format. If
|
||||
* this time is exceeded, return partially formatted. 0 or negative number
|
||||
* indicates no timeout.
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.format.HtmlPrettyPrinter = function(opt_timeOutMillis) {
|
||||
/**
|
||||
* Max # milliseconds to spend on #format.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.timeOutMillis_ = opt_timeOutMillis && opt_timeOutMillis > 0 ?
|
||||
opt_timeOutMillis : 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Singleton.
|
||||
* @type {goog.format.HtmlPrettyPrinter?}
|
||||
* @private
|
||||
*/
|
||||
goog.format.HtmlPrettyPrinter.instance_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Singleton lazy initializer.
|
||||
* @return {!goog.format.HtmlPrettyPrinter} Singleton.
|
||||
* @private
|
||||
*/
|
||||
goog.format.HtmlPrettyPrinter.getInstance_ = function() {
|
||||
if (!goog.format.HtmlPrettyPrinter.instance_) {
|
||||
goog.format.HtmlPrettyPrinter.instance_ =
|
||||
new goog.format.HtmlPrettyPrinter();
|
||||
}
|
||||
return goog.format.HtmlPrettyPrinter.instance_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Static utility function. See prototype #format.
|
||||
* @param {string} html The HTML text to pretty print.
|
||||
* @return {string} Formatted result.
|
||||
*/
|
||||
goog.format.HtmlPrettyPrinter.format = function(html) {
|
||||
return goog.format.HtmlPrettyPrinter.getInstance_().format(html);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* List of patterns used to tokenize HTML for pretty printing. Cache
|
||||
* subexpression for tag name.
|
||||
* comment|meta-tag|tag|text|other-less-than-characters
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.format.HtmlPrettyPrinter.TOKEN_REGEX_ =
|
||||
/(?:<!--.*?-->|<!.*?>|<(\/?)(\w+)[^>]*>|[^<]+|<)/g;
|
||||
|
||||
|
||||
/**
|
||||
* Tags whose contents we don't want pretty printed.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.format.HtmlPrettyPrinter.NON_PRETTY_PRINTED_TAGS_ = goog.object.createSet(
|
||||
'script',
|
||||
'style',
|
||||
'pre',
|
||||
'xmp');
|
||||
|
||||
|
||||
/**
|
||||
* 'Block' tags. We should add newlines before and after these tags during
|
||||
* pretty printing. Tags drawn mostly from HTML4 definitions for block and other
|
||||
* non-online tags, excepting the ones in
|
||||
* #goog.format.HtmlPrettyPrinter.NON_PRETTY_PRINTED_TAGS_.
|
||||
*
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.format.HtmlPrettyPrinter.BLOCK_TAGS_ = goog.object.createSet(
|
||||
'address',
|
||||
'applet',
|
||||
'area',
|
||||
'base',
|
||||
'basefont',
|
||||
'blockquote',
|
||||
'body',
|
||||
'caption',
|
||||
'center',
|
||||
'col',
|
||||
'colgroup',
|
||||
'dir',
|
||||
'div',
|
||||
'dl',
|
||||
'fieldset',
|
||||
'form',
|
||||
'frame',
|
||||
'frameset',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'head',
|
||||
'hr',
|
||||
'html',
|
||||
'iframe',
|
||||
'isindex',
|
||||
'legend',
|
||||
'link',
|
||||
'menu',
|
||||
'meta',
|
||||
'noframes',
|
||||
'noscript',
|
||||
'ol',
|
||||
'optgroup',
|
||||
'option',
|
||||
'p',
|
||||
'param',
|
||||
'table',
|
||||
'tbody',
|
||||
'td',
|
||||
'tfoot',
|
||||
'th',
|
||||
'thead',
|
||||
'title',
|
||||
'tr',
|
||||
'ul');
|
||||
|
||||
|
||||
/**
|
||||
* Non-block tags that break flow. We insert a line break after, but not before
|
||||
* these. Tags drawn from HTML4 definitions.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.format.HtmlPrettyPrinter.BREAKS_FLOW_TAGS_ = goog.object.createSet(
|
||||
'br',
|
||||
'dd',
|
||||
'dt',
|
||||
'br',
|
||||
'li',
|
||||
'noframes');
|
||||
|
||||
|
||||
/**
|
||||
* Empty tags. These are treated as both start and end tags.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.format.HtmlPrettyPrinter.EMPTY_TAGS_ = goog.object.createSet(
|
||||
'br',
|
||||
'hr',
|
||||
'isindex');
|
||||
|
||||
|
||||
/**
|
||||
* Breaks up HTML so it's easily readable by the user.
|
||||
* @param {string} html The HTML text to pretty print.
|
||||
* @return {string} Formatted result.
|
||||
* @throws {Error} Regex error, data loss, or endless loop detected.
|
||||
*/
|
||||
goog.format.HtmlPrettyPrinter.prototype.format = function(html) {
|
||||
// Trim leading whitespace, but preserve first indent; in other words, keep
|
||||
// any spaces immediately before the first non-whitespace character (that's
|
||||
// what $1 is), but remove all other leading whitespace. This adjustment
|
||||
// historically had been made in Docs. The motivation is that some
|
||||
// browsers prepend several line breaks in designMode.
|
||||
html = html.replace(/^\s*?( *\S)/, '$1');
|
||||
|
||||
// Trim trailing whitespace.
|
||||
html = html.replace(/\s+$/, '');
|
||||
|
||||
// Keep track of how much time we've used.
|
||||
var timeOutMillis = this.timeOutMillis_;
|
||||
var startMillis = timeOutMillis ? goog.now() : 0;
|
||||
|
||||
// Handles concatenation of the result and required line breaks.
|
||||
var buffer = new goog.format.HtmlPrettyPrinter.Buffer();
|
||||
|
||||
// Declare these for efficiency since we access them in a loop.
|
||||
var tokenRegex = goog.format.HtmlPrettyPrinter.TOKEN_REGEX_;
|
||||
var nonPpTags = goog.format.HtmlPrettyPrinter.NON_PRETTY_PRINTED_TAGS_;
|
||||
var blockTags = goog.format.HtmlPrettyPrinter.BLOCK_TAGS_;
|
||||
var breaksFlowTags = goog.format.HtmlPrettyPrinter.BREAKS_FLOW_TAGS_;
|
||||
var emptyTags = goog.format.HtmlPrettyPrinter.EMPTY_TAGS_;
|
||||
|
||||
// Used to verify we're making progress through our regex tokenization.
|
||||
var lastIndex = 0;
|
||||
|
||||
// Use this to track non-pretty-printed tags and childen.
|
||||
var nonPpTagStack = [];
|
||||
|
||||
// Loop through each matched token.
|
||||
var match;
|
||||
while (match = tokenRegex.exec(html)) {
|
||||
// Get token.
|
||||
var token = match[0];
|
||||
|
||||
// Is this token a tag? match.length == 3 for tags, 1 for all others.
|
||||
if (match.length == 3) {
|
||||
var tagName = match[2];
|
||||
if (tagName) {
|
||||
tagName = tagName.toLowerCase();
|
||||
}
|
||||
|
||||
// Non-pretty-printed tags?
|
||||
if (nonPpTags.hasOwnProperty(tagName)) {
|
||||
// End tag?
|
||||
if (match[1] == '/') {
|
||||
// Do we have a matching start tag?
|
||||
var stackSize = nonPpTagStack.length;
|
||||
var startTagName = stackSize ? nonPpTagStack[stackSize - 1] : null;
|
||||
if (startTagName == tagName) {
|
||||
// End of non-pretty-printed block. Line break after.
|
||||
nonPpTagStack.pop();
|
||||
buffer.pushToken(false, token, !nonPpTagStack.length);
|
||||
} else {
|
||||
// Malformed HTML. No line breaks.
|
||||
buffer.pushToken(false, token, false);
|
||||
}
|
||||
} else {
|
||||
// Start of non-pretty-printed block. Line break before.
|
||||
buffer.pushToken(!nonPpTagStack.length, token, false);
|
||||
nonPpTagStack.push(tagName);
|
||||
}
|
||||
} else if (nonPpTagStack.length) {
|
||||
// Inside non-pretty-printed block, no new line breaks.
|
||||
buffer.pushToken(false, token, false);
|
||||
} else if (blockTags.hasOwnProperty(tagName)) {
|
||||
// Put line break before start block and after end block tags.
|
||||
var isEmpty = emptyTags.hasOwnProperty(tagName);
|
||||
var isEndTag = match[1] == '/';
|
||||
buffer.pushToken(isEmpty || !isEndTag, token, isEmpty || isEndTag);
|
||||
} else if (breaksFlowTags.hasOwnProperty(tagName)) {
|
||||
var isEmpty = emptyTags.hasOwnProperty(tagName);
|
||||
var isEndTag = match[1] == '/';
|
||||
// Put line break after end flow-breaking tags.
|
||||
buffer.pushToken(false, token, isEndTag || isEmpty);
|
||||
} else {
|
||||
// All other tags, no line break.
|
||||
buffer.pushToken(false, token, false);
|
||||
}
|
||||
} else {
|
||||
// Non-tags, no line break.
|
||||
buffer.pushToken(false, token, false);
|
||||
}
|
||||
|
||||
// Double check that we're making progress.
|
||||
var newLastIndex = tokenRegex.lastIndex;
|
||||
if (!token || newLastIndex <= lastIndex) {
|
||||
throw Error('Regex failed to make progress through source html.');
|
||||
}
|
||||
lastIndex = newLastIndex;
|
||||
|
||||
// Out of time?
|
||||
if (timeOutMillis) {
|
||||
if (goog.now() - startMillis > timeOutMillis) {
|
||||
// Push unprocessed data as one big token and reset regex object.
|
||||
buffer.pushToken(false, html.substring(tokenRegex.lastIndex), false);
|
||||
tokenRegex.lastIndex = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we end in a line break.
|
||||
buffer.lineBreak();
|
||||
|
||||
// Construct result string.
|
||||
var result = String(buffer);
|
||||
|
||||
// Length should be original length plus # line breaks added.
|
||||
var expectedLength = html.length + buffer.breakCount;
|
||||
if (result.length != expectedLength) {
|
||||
throw Error('Lost data pretty printing html.');
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This class is a buffer to which we push our output. It tracks line breaks to
|
||||
* make sure we don't add unnecessary ones.
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.format.HtmlPrettyPrinter.Buffer = function() {
|
||||
/**
|
||||
* Tokens to be output in #toString.
|
||||
* @type {goog.string.StringBuffer}
|
||||
* @private
|
||||
*/
|
||||
this.out_ = new goog.string.StringBuffer();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tracks number of line breaks added.
|
||||
* @type {number}
|
||||
*/
|
||||
goog.format.HtmlPrettyPrinter.Buffer.prototype.breakCount = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Tracks if we are at the start of a new line.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.format.HtmlPrettyPrinter.Buffer.prototype.isBeginningOfNewLine_ = true;
|
||||
|
||||
|
||||
/**
|
||||
* Tracks if we need a new line before the next token.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.format.HtmlPrettyPrinter.Buffer.prototype.needsNewLine_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Adds token and necessary line breaks to output buffer.
|
||||
* @param {boolean} breakBefore If true, add line break before token if
|
||||
* necessary.
|
||||
* @param {string} token Token to push.
|
||||
* @param {boolean} breakAfter If true, add line break after token if
|
||||
* necessary.
|
||||
*/
|
||||
goog.format.HtmlPrettyPrinter.Buffer.prototype.pushToken = function(
|
||||
breakBefore, token, breakAfter) {
|
||||
// If this token needs a preceeding line break, and
|
||||
// we haven't already added a line break, and
|
||||
// this token does not start with a line break,
|
||||
// then add line break.
|
||||
// Due to FF3.0 bug with lists, we don't insert a /n
|
||||
// right before </ul>. See bug 1520665.
|
||||
if ((this.needsNewLine_ || breakBefore) &&
|
||||
!/^\r?\n/.test(token) &&
|
||||
!/\/ul/i.test(token)) {
|
||||
this.lineBreak();
|
||||
}
|
||||
|
||||
// Token.
|
||||
this.out_.append(token);
|
||||
|
||||
// Remember if this string ended with a line break so we know we don't have to
|
||||
// insert another one before the next token.
|
||||
this.isBeginningOfNewLine_ = /\r?\n$/.test(token);
|
||||
|
||||
// Remember if this token requires a line break after it. We don't insert it
|
||||
// here because we might not have to if the next token starts with a line
|
||||
// break.
|
||||
this.needsNewLine_ = breakAfter && !this.isBeginningOfNewLine_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Append line break if we need one.
|
||||
*/
|
||||
goog.format.HtmlPrettyPrinter.Buffer.prototype.lineBreak = function() {
|
||||
if (!this.isBeginningOfNewLine_) {
|
||||
this.out_.append('\n');
|
||||
++this.breakCount;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} String representation of tokens.
|
||||
* @override
|
||||
*/
|
||||
goog.format.HtmlPrettyPrinter.Buffer.prototype.toString = function() {
|
||||
return this.out_.toString();
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.format.HtmlPrettyPrinter
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.format.HtmlPrettyPrinterTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,205 @@
|
||||
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
goog.provide('goog.format.HtmlPrettyPrinterTest');
|
||||
goog.setTestOnly('goog.format.HtmlPrettyPrinterTest');
|
||||
|
||||
goog.require('goog.format.HtmlPrettyPrinter');
|
||||
goog.require('goog.testing.MockClock');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var COMPLEX_HTML = '<!DOCTYPE root-element [SYSTEM OR PUBLIC FPI] "uri" [' +
|
||||
'<!-- internal declarations -->]>' +
|
||||
'<html><head><title>My HTML</title><!-- my comment --></head>' +
|
||||
'<body><h1>My Header</h1>My text.<br><b>My bold text.</b><hr>' +
|
||||
'<pre>My\npreformatted <br> HTML.</pre>5 < 10</body>' +
|
||||
'</html>';
|
||||
var mockClock;
|
||||
var mockClockTicks;
|
||||
|
||||
function setUp() {
|
||||
mockClockTicks = 0;
|
||||
mockClock = new goog.testing.MockClock();
|
||||
mockClock.getCurrentTime = function() {
|
||||
return mockClockTicks++;
|
||||
};
|
||||
mockClock.install();
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
if (mockClock) {
|
||||
mockClock.uninstall();
|
||||
}
|
||||
}
|
||||
|
||||
function testSimpleHtml() {
|
||||
var actual = goog.format.HtmlPrettyPrinter.format('<br><b>bold</b>');
|
||||
assertEquals('<br>\n<b>bold</b>\n', actual);
|
||||
assertEquals(actual, goog.format.HtmlPrettyPrinter.format(actual));
|
||||
}
|
||||
|
||||
function testSimpleHtmlMixedCase() {
|
||||
var actual = goog.format.HtmlPrettyPrinter.format('<BR><b>bold</b>');
|
||||
assertEquals('<BR>\n<b>bold</b>\n', actual);
|
||||
assertEquals(actual, goog.format.HtmlPrettyPrinter.format(actual));
|
||||
}
|
||||
|
||||
function testComplexHtml() {
|
||||
var actual = goog.format.HtmlPrettyPrinter.format(COMPLEX_HTML);
|
||||
var expected = '<!DOCTYPE root-element [SYSTEM OR PUBLIC FPI] "uri" [' +
|
||||
'<!-- internal declarations -->]>\n' +
|
||||
'<html>\n' +
|
||||
'<head>\n' +
|
||||
'<title>My HTML</title>\n' +
|
||||
'<!-- my comment -->' +
|
||||
'</head>\n' +
|
||||
'<body>\n' +
|
||||
'<h1>My Header</h1>\n' +
|
||||
'My text.<br>\n' +
|
||||
'<b>My bold text.</b>\n' +
|
||||
'<hr>\n' +
|
||||
'<pre>My\npreformatted <br> HTML.</pre>\n' +
|
||||
'5 < 10' +
|
||||
'</body>\n' +
|
||||
'</html>\n';
|
||||
assertEquals(expected, actual);
|
||||
assertEquals(actual, goog.format.HtmlPrettyPrinter.format(actual));
|
||||
}
|
||||
|
||||
function testTimeout() {
|
||||
var pp = new goog.format.HtmlPrettyPrinter(3);
|
||||
var actual = pp.format(COMPLEX_HTML);
|
||||
var expected = '<!DOCTYPE root-element [SYSTEM OR PUBLIC FPI] "uri" [' +
|
||||
'<!-- internal declarations -->]>\n' +
|
||||
'<html>\n' +
|
||||
'<head><title>My HTML</title><!-- my comment --></head>' +
|
||||
'<body><h1>My Header</h1>My text.<br><b>My bold text.</b><hr>' +
|
||||
'<pre>My\npreformatted <br> HTML.</pre>5 < 10</body>' +
|
||||
'</html>\n';
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
function testKeepLeadingIndent() {
|
||||
var original = ' <b>Bold</b> <i>Ital</i> ';
|
||||
var expected = ' <b>Bold</b> <i>Ital</i>\n';
|
||||
assertEquals(expected, goog.format.HtmlPrettyPrinter.format(original));
|
||||
}
|
||||
|
||||
function testTrimLeadingLineBreaks() {
|
||||
var original = '\n \t\r\n \n <b>Bold</b> <i>Ital</i> ';
|
||||
var expected = ' <b>Bold</b> <i>Ital</i>\n';
|
||||
assertEquals(expected, goog.format.HtmlPrettyPrinter.format(original));
|
||||
}
|
||||
|
||||
function testExtraLines() {
|
||||
var original = '<br>\ntombrat';
|
||||
assertEquals(original + '\n', goog.format.HtmlPrettyPrinter.format(original));
|
||||
}
|
||||
|
||||
function testCrlf() {
|
||||
var original = '<br>\r\none\r\ntwo<br>';
|
||||
assertEquals(original + '\n', goog.format.HtmlPrettyPrinter.format(original));
|
||||
}
|
||||
|
||||
function testEndInLineBreak() {
|
||||
assertEquals('foo\n', goog.format.HtmlPrettyPrinter.format('foo'));
|
||||
assertEquals('foo\n', goog.format.HtmlPrettyPrinter.format('foo\n'));
|
||||
assertEquals('foo\n', goog.format.HtmlPrettyPrinter.format('foo\n\n'));
|
||||
assertEquals('foo<br>\n', goog.format.HtmlPrettyPrinter.format('foo<br>'));
|
||||
assertEquals('foo<br>\n', goog.format.HtmlPrettyPrinter.format('foo<br>\n'));
|
||||
}
|
||||
|
||||
function testTable() {
|
||||
var original = '<table>' +
|
||||
'<tr><td>one.one</td><td>one.two</td></tr>' +
|
||||
'<tr><td>two.one</td><td>two.two</td></tr>' +
|
||||
'</table>';
|
||||
var expected = '<table>\n' +
|
||||
'<tr>\n<td>one.one</td>\n<td>one.two</td>\n</tr>\n' +
|
||||
'<tr>\n<td>two.one</td>\n<td>two.two</td>\n</tr>\n' +
|
||||
'</table>\n';
|
||||
assertEquals(expected, goog.format.HtmlPrettyPrinter.format(original));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* We have a sanity check in HtmlPrettyPrinter to make sure the regex index
|
||||
* advances after every match. We should never hit this, but we include it on
|
||||
* the chance there is some corner case where the pattern would match but not
|
||||
* process a new token. It's not generally a good idea to break the
|
||||
* implementation to test behavior, but this is the easiest way to mimic a
|
||||
* bad internal state.
|
||||
*/
|
||||
function testRegexMakesProgress() {
|
||||
var original = goog.format.HtmlPrettyPrinter.TOKEN_REGEX_;
|
||||
|
||||
try {
|
||||
// This regex matches \B, an index between 2 word characters, so the regex
|
||||
// index does not advance when matching this.
|
||||
goog.format.HtmlPrettyPrinter.TOKEN_REGEX_ =
|
||||
/(?:\B|<!--.*?-->|<!.*?>|<(\/?)(\w+)[^>]*>|[^<]+|<)/g;
|
||||
|
||||
// It would work on this string.
|
||||
assertEquals('f o o\n', goog.format.HtmlPrettyPrinter.format('f o o'));
|
||||
|
||||
// But not this one.
|
||||
var ex = assertThrows('should have failed for invalid regex - endless loop',
|
||||
goog.partial(goog.format.HtmlPrettyPrinter.format, COMPLEX_HTML));
|
||||
assertEquals('Regex failed to make progress through source html.',
|
||||
ex.message);
|
||||
} finally {
|
||||
goog.format.HtmlPrettyPrinter.TOKEN_REGEX_ = original;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* FF3.0 doesn't like \n between </li> and </ul>. See bug 1520665.
|
||||
*/
|
||||
function testLists() {
|
||||
var original = '<ul><li>one</li><ul><li>two</li></UL><li>three</li></ul>';
|
||||
var expected =
|
||||
'<ul><li>one</li>\n<ul><li>two</li></UL>\n<li>three</li></ul>\n';
|
||||
assertEquals(expected, goog.format.HtmlPrettyPrinter.format(original));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* We have a sanity check in HtmlPrettyPrinter to make sure the regex fully
|
||||
* tokenizes the string. We should never hit this, but we include it on the
|
||||
* chance there is some corner case where the pattern would miss a section of
|
||||
* original string. It's not generally a good idea to break the
|
||||
* implementation to test behavior, but this is the easiest way to mimic a
|
||||
* bad internal state.
|
||||
*/
|
||||
function testAvoidDataLoss() {
|
||||
var original = goog.format.HtmlPrettyPrinter.TOKEN_REGEX_;
|
||||
|
||||
try {
|
||||
// This regex does not match stranded '<' characters, so does not fully
|
||||
// tokenize the string.
|
||||
goog.format.HtmlPrettyPrinter.TOKEN_REGEX_ =
|
||||
/(?:<!--.*?-->|<!.*?>|<(\/?)(\w+)[^>]*>|[^<]+)/g;
|
||||
|
||||
// It would work on this string.
|
||||
assertEquals('foo\n', goog.format.HtmlPrettyPrinter.format('foo'));
|
||||
|
||||
// But not this one.
|
||||
var ex = assertThrows('should have failed for invalid regex - data loss',
|
||||
goog.partial(goog.format.HtmlPrettyPrinter.format, COMPLEX_HTML));
|
||||
assertEquals('Lost data pretty printing html.', ex.message);
|
||||
} finally {
|
||||
goog.format.HtmlPrettyPrinter.TOKEN_REGEX_ = original;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
// Copyright 2014 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Provides functions to parse and manipulate internationalized
|
||||
* email addresses. This is useful in the context of Email Address
|
||||
* Internationalization (EAI) as defined by RFC6530.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.format.InternationalizedEmailAddress');
|
||||
|
||||
goog.require('goog.format.EmailAddress');
|
||||
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Formats an email address string for display, and allows for extraction of
|
||||
* the individual components of the address.
|
||||
* @param {string=} opt_address The email address.
|
||||
* @param {string=} opt_name The name associated with the email address.
|
||||
* @constructor
|
||||
* @extends {goog.format.EmailAddress}
|
||||
*/
|
||||
goog.format.InternationalizedEmailAddress = function(opt_address, opt_name) {
|
||||
goog.format.InternationalizedEmailAddress.base(
|
||||
this, 'constructor', opt_address, opt_name);
|
||||
};
|
||||
goog.inherits(
|
||||
goog.format.InternationalizedEmailAddress, goog.format.EmailAddress);
|
||||
|
||||
|
||||
/**
|
||||
* A string representing the RegExp for the local part of an EAI email address.
|
||||
* @private
|
||||
*/
|
||||
goog.format.InternationalizedEmailAddress.EAI_LOCAL_PART_REGEXP_STR_ =
|
||||
'((?!\\s)[+a-zA-Z0-9_.!#$%&\'*\\/=?^`{|}~\u0080-\uFFFFFF-])+';
|
||||
|
||||
|
||||
/**
|
||||
* A string representing the RegExp for a label in the domain part of an EAI
|
||||
* email address.
|
||||
* @private
|
||||
*/
|
||||
goog.format.InternationalizedEmailAddress.EAI_LABEL_CHAR_REGEXP_STR_ =
|
||||
'(?!\\s)[a-zA-Z0-9\u0080-\u3001\u3003-\uFF0D\uFF0F-\uFF60\uFF62-\uFFFFFF-]';
|
||||
|
||||
|
||||
/**
|
||||
* A string representing the RegExp for the domain part of an EAI email address.
|
||||
* @private
|
||||
*/
|
||||
goog.format.InternationalizedEmailAddress.EAI_DOMAIN_PART_REGEXP_STR_ =
|
||||
// A unicode character (ASCII or Unicode excluding periods)
|
||||
'(' + goog.format.InternationalizedEmailAddress.EAI_LABEL_CHAR_REGEXP_STR_ +
|
||||
// Such character 1+ times, followed by a Unicode period. All 1+ times.
|
||||
'+[\\.\\uFF0E\\u3002\\uFF61])+' +
|
||||
// And same thing but without a period in the end
|
||||
goog.format.InternationalizedEmailAddress.EAI_LABEL_CHAR_REGEXP_STR_ +
|
||||
'{2,63}';
|
||||
|
||||
|
||||
/**
|
||||
* Match string for address separators. This list is the result of the
|
||||
* discussion in b/16241003.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.format.InternationalizedEmailAddress.ADDRESS_SEPARATORS_ =
|
||||
',' + // U+002C ( , ) COMMA
|
||||
';' + // U+003B ( ; ) SEMICOLON
|
||||
'\u055D' + // ( ՝ ) ARMENIAN COMMA
|
||||
'\u060C' + // ( ، ) ARABIC COMMA
|
||||
'\u1363' + // ( ፣ ) ETHIOPIC COMMA
|
||||
'\u1802' + // ( ᠂ ) MONGOLIAN COMMA
|
||||
'\u1808' + // ( ᠈ ) MONGOLIAN MANCHU COMMA
|
||||
'\u2E41' + // ( ⹁ ) REVERSED COMMA
|
||||
'\u3001' + // ( 、 ) IDEOGRAPHIC COMMA
|
||||
'\uFF0C' + // ( , ) FULLWIDTH COMMA
|
||||
'\u061B' + // ( ؛ ) ARABIC SEMICOLON
|
||||
'\u1364' + // ( ፤ ) ETHIOPIC SEMICOLON
|
||||
'\uFF1B' + // ( ; ) FULLWIDTH SEMICOLON
|
||||
'\uFF64' + // ( 、 ) HALFWIDTH IDEOGRAPHIC COMMA
|
||||
'\u104A'; // ( ၊ ) MYANMAR SIGN LITTLE SECTION
|
||||
|
||||
|
||||
/**
|
||||
* Match string for characters that, when in a display name, require it to be
|
||||
* quoted.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.format.InternationalizedEmailAddress.CHARS_REQUIRE_QUOTES_ =
|
||||
goog.format.EmailAddress.SPECIAL_CHARS +
|
||||
goog.format.InternationalizedEmailAddress.ADDRESS_SEPARATORS_;
|
||||
|
||||
|
||||
/**
|
||||
* A RegExp to match the local part of an EAI email address.
|
||||
* @private {!RegExp}
|
||||
*/
|
||||
goog.format.InternationalizedEmailAddress.EAI_LOCAL_PART_ =
|
||||
new RegExp('^' +
|
||||
goog.format.InternationalizedEmailAddress.EAI_LOCAL_PART_REGEXP_STR_ +
|
||||
'$');
|
||||
|
||||
|
||||
/**
|
||||
* A RegExp to match the domain part of an EAI email address.
|
||||
* @private {!RegExp}
|
||||
*/
|
||||
goog.format.InternationalizedEmailAddress.EAI_DOMAIN_PART_ =
|
||||
new RegExp('^' +
|
||||
goog.format.InternationalizedEmailAddress.EAI_DOMAIN_PART_REGEXP_STR_ +
|
||||
'$');
|
||||
|
||||
|
||||
/**
|
||||
* A RegExp to match an EAI email address.
|
||||
* @private {!RegExp}
|
||||
*/
|
||||
goog.format.InternationalizedEmailAddress.EAI_EMAIL_ADDRESS_ =
|
||||
new RegExp('^' +
|
||||
goog.format.InternationalizedEmailAddress.EAI_LOCAL_PART_REGEXP_STR_ +
|
||||
'@' +
|
||||
goog.format.InternationalizedEmailAddress.EAI_DOMAIN_PART_REGEXP_STR_ +
|
||||
'$');
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the provided string is a valid local part (part before the '@') of
|
||||
* an EAI email address.
|
||||
* @param {string} str The local part to check.
|
||||
* @return {boolean} Whether the provided string is a valid local part.
|
||||
*/
|
||||
goog.format.InternationalizedEmailAddress.isValidLocalPartSpec = function(str) {
|
||||
if (!goog.isDefAndNotNull(str)) {
|
||||
return false;
|
||||
}
|
||||
return goog.format.InternationalizedEmailAddress.EAI_LOCAL_PART_.test(str);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the provided string is a valid domain part (part after the '@') of
|
||||
* an EAI email address.
|
||||
* @param {string} str The domain part to check.
|
||||
* @return {boolean} Whether the provided string is a valid domain part.
|
||||
*/
|
||||
goog.format.InternationalizedEmailAddress.isValidDomainPartSpec =
|
||||
function(str) {
|
||||
if (!goog.isDefAndNotNull(str)) {
|
||||
return false;
|
||||
}
|
||||
return goog.format.InternationalizedEmailAddress.EAI_DOMAIN_PART_.test(str);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.format.InternationalizedEmailAddress.prototype.isValid = function() {
|
||||
return goog.format.InternationalizedEmailAddress.isValidAddrSpec(
|
||||
this.address);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the provided string is a valid email address. Supports both
|
||||
* simple email addresses (address specs) and addresses that contain display
|
||||
* names.
|
||||
* @param {string} str The email address to check.
|
||||
* @return {boolean} Whether the provided string is a valid address.
|
||||
*/
|
||||
goog.format.InternationalizedEmailAddress.isValidAddress = function(str) {
|
||||
if (!goog.isDefAndNotNull(str)) {
|
||||
return false;
|
||||
}
|
||||
return goog.format.InternationalizedEmailAddress.parse(str).isValid();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the provided string is a valid address spec (local@domain.com).
|
||||
* @param {string} str The email address to check.
|
||||
* @return {boolean} Whether the provided string is a valid address spec.
|
||||
*/
|
||||
goog.format.InternationalizedEmailAddress.isValidAddrSpec = function(str) {
|
||||
if (!goog.isDefAndNotNull(str)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is a fairly naive implementation, but it covers 99% of use cases.
|
||||
// For more details, see http://en.wikipedia.org/wiki/Email_address#Syntax
|
||||
return goog.format.InternationalizedEmailAddress.EAI_EMAIL_ADDRESS_.test(str);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parses a string containing email addresses of the form
|
||||
* "name" <address> into an array of email addresses.
|
||||
* @param {string} str The address list.
|
||||
* @return {!Array<!goog.format.EmailAddress>} The parsed emails.
|
||||
*/
|
||||
goog.format.InternationalizedEmailAddress.parseList = function(str) {
|
||||
return goog.format.EmailAddress.parseListInternal(
|
||||
str, goog.format.InternationalizedEmailAddress.parse,
|
||||
goog.format.InternationalizedEmailAddress.isAddressSeparator);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parses an email address of the form "name" <address> into
|
||||
* an email address.
|
||||
* @param {string} addr The address string.
|
||||
* @return {!goog.format.EmailAddress} The parsed address.
|
||||
*/
|
||||
goog.format.InternationalizedEmailAddress.parse = function(addr) {
|
||||
return goog.format.EmailAddress.parseInternal(
|
||||
addr, goog.format.InternationalizedEmailAddress);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} ch The character to test.
|
||||
* @return {boolean} Whether the provided character is an address separator.
|
||||
*/
|
||||
goog.format.InternationalizedEmailAddress.isAddressSeparator = function(ch) {
|
||||
return goog.string.contains(
|
||||
goog.format.InternationalizedEmailAddress.ADDRESS_SEPARATORS_, ch);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return the address in a standard format:
|
||||
* - remove extra spaces.
|
||||
* - Surround name with quotes if it contains special characters.
|
||||
* @return {string} The cleaned address.
|
||||
* @override
|
||||
*/
|
||||
goog.format.InternationalizedEmailAddress.prototype.toString = function() {
|
||||
return this.toStringInternal(
|
||||
goog.format.InternationalizedEmailAddress.CHARS_REQUIRE_QUOTES_);
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2014 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.format.InternationalizedEmailAddress
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.format.InternationalizedEmailAddressTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,335 @@
|
||||
// Copyright 2014 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
goog.provide('goog.format.InternationalizedEmailAddressTest');
|
||||
goog.setTestOnly('goog.format.InternationalizedEmailAddressTest');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.format.InternationalizedEmailAddress');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
|
||||
/**
|
||||
* Asserts that the given validation function generates the expected outcome for
|
||||
* a set of expected valid and a second set of expected invalid addresses.
|
||||
* containing the specified address strings, irrespective of their order.
|
||||
* @param {function(string):boolean} testFunc Validation function to be tested.
|
||||
* @param {!Array<string>} valid List of addresses that should be valid.
|
||||
* @param {!Array<string>} invalid List of addresses that should be invalid.
|
||||
* @private
|
||||
*/
|
||||
function doIsValidTest(testFunc, valid, invalid) {
|
||||
goog.array.forEach(valid, function(str) {
|
||||
assertTrue('"' + str + '" should be valid.', testFunc(str));
|
||||
});
|
||||
goog.array.forEach(invalid, function(str) {
|
||||
assertFalse('"' + str + '" should be invalid.', testFunc(str));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Asserts that parsing the inputString produces a list of email addresses
|
||||
* containing the specified address strings, irrespective of their order.
|
||||
* @param {string} inputString A raw address list.
|
||||
* @param {!Array<string>} expectedList The expected results.
|
||||
* @param {string=} opt_message An assertion message.
|
||||
* @return {string} the resulting email address objects.
|
||||
*/
|
||||
function assertParsedList(inputString, expectedList, opt_message) {
|
||||
var message = opt_message || 'Should parse address correctly';
|
||||
var result = goog.format.InternationalizedEmailAddress.parseList(inputString);
|
||||
assertEquals(
|
||||
'Should have correct # of addresses', expectedList.length, result.length);
|
||||
for (var i = 0; i < expectedList.length; ++i) {
|
||||
assertEquals(message, expectedList[i], result[i].getAddress());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function testParseList() {
|
||||
// Test only the new cases added by EAI (other cases covered in parent
|
||||
// class test)
|
||||
assertParsedList('<me.みんあ@me.xn--l8jtg9b>', ['me.みんあ@me.xn--l8jtg9b']);
|
||||
}
|
||||
|
||||
function testIsEaiValid() {
|
||||
var valid = [
|
||||
'e@b.eu',
|
||||
'<a.b+foo@c.com>',
|
||||
'eric <e@b.com>',
|
||||
'"e" <e@b.com>',
|
||||
'a@FOO.MUSEUM',
|
||||
'bla@b.co.ac.uk',
|
||||
'bla@a.b.com',
|
||||
'o\'hara@gm.com',
|
||||
'plus+is+allowed@gmail.com',
|
||||
'!/#$%&\'*+-=~|`{}?^_@expample.com',
|
||||
'confirm-bhk=modulo.org@yahoogroups.com',
|
||||
'み.ん-あ@みんあ.みんあ',
|
||||
'みんあ@test.com',
|
||||
'test@test.みんあ',
|
||||
'test@みんあ.com',
|
||||
'me.みんあ@me.xn--l8jtg9b',
|
||||
'みんあ@me.xn--l8jtg9b',
|
||||
'fullwidthfullstop@sld' + '\uff0e' + 'tld',
|
||||
'ideographicfullstop@sld' + '\u3002' + 'tld',
|
||||
'halfwidthideographicfullstop@sld' + '\uff61' + 'tld'
|
||||
];
|
||||
var invalid = [
|
||||
null,
|
||||
undefined,
|
||||
'e',
|
||||
'',
|
||||
'e @c.com',
|
||||
'a@b',
|
||||
'foo.com',
|
||||
'foo@c..com',
|
||||
'test@gma=il.com',
|
||||
'aaa@gmail',
|
||||
'has some spaces@gmail.com',
|
||||
'has@three@at@signs.com',
|
||||
'@no-local-part.com'
|
||||
];
|
||||
doIsValidTest(
|
||||
goog.format.InternationalizedEmailAddress.isValidAddress, valid, invalid);
|
||||
}
|
||||
|
||||
function testIsValidLocalPart() {
|
||||
var valid = [
|
||||
'e',
|
||||
'a.b+foo',
|
||||
'o\'hara',
|
||||
'user+someone',
|
||||
'!/#$%&\'*+-=~|`{}?^_',
|
||||
'confirm-bhk=modulo.org',
|
||||
'me.みんあ',
|
||||
'みんあ'
|
||||
];
|
||||
var invalid = [
|
||||
null,
|
||||
undefined,
|
||||
'A@b@c',
|
||||
'a"b(c)d,e:f;g<h>i[j\\k]l',
|
||||
'just"not"right',
|
||||
'this is"not\\allowed',
|
||||
'this\\ still\"not\\\\allowed',
|
||||
'has some spaces'
|
||||
];
|
||||
doIsValidTest(goog.format.InternationalizedEmailAddress.isValidLocalPartSpec,
|
||||
valid, invalid);
|
||||
}
|
||||
|
||||
function testIsValidDomainPart() {
|
||||
var valid = [
|
||||
'example.com',
|
||||
'dept.example.org',
|
||||
'long.domain.with.lots.of.dots',
|
||||
'me.xn--l8jtg9b',
|
||||
'me.みんあ',
|
||||
'sld.looooooongtld',
|
||||
'sld' + '\uff0e' + 'tld',
|
||||
'sld' + '\u3002' + 'tld',
|
||||
'sld' + '\uff61' + 'tld'
|
||||
];
|
||||
var invalid = [
|
||||
null,
|
||||
undefined,
|
||||
'',
|
||||
'@has.an.at.sign',
|
||||
'..has.leading.dots',
|
||||
'gma=il.com',
|
||||
'DoesNotHaveADot',
|
||||
'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffffffffffgggggggggg'
|
||||
];
|
||||
doIsValidTest(goog.format.InternationalizedEmailAddress.isValidDomainPartSpec,
|
||||
valid, invalid);
|
||||
}
|
||||
|
||||
|
||||
function testparseListWithAdditionalSeparators() {
|
||||
assertParsedList('<foo@gmail.com>\u055D <bar@gmail.com>',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with U+055D');
|
||||
assertParsedList('<foo@gmail.com>\u055D <bar@gmail.com>\u055D',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with trailing U+055D');
|
||||
|
||||
assertParsedList('<foo@gmail.com>\u060C <bar@gmail.com>',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with U+060C');
|
||||
assertParsedList('<foo@gmail.com>\u060C <bar@gmail.com>\u060C',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with trailing U+060C');
|
||||
|
||||
assertParsedList('<foo@gmail.com>\u1363 <bar@gmail.com>',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with U+1363');
|
||||
assertParsedList('<foo@gmail.com>\u1363 <bar@gmail.com>\u1363',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with trailing U+1363');
|
||||
|
||||
assertParsedList('<foo@gmail.com>\u1802 <bar@gmail.com>',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with U+1802');
|
||||
assertParsedList('<foo@gmail.com>\u1802 <bar@gmail.com>\u1802',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with trailing U+1802');
|
||||
|
||||
assertParsedList('<foo@gmail.com>\u1808 <bar@gmail.com>',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with U+1808');
|
||||
assertParsedList('<foo@gmail.com>\u1808 <bar@gmail.com>\u1808',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with trailing U+1808');
|
||||
|
||||
assertParsedList('<foo@gmail.com>\u2E41 <bar@gmail.com>',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with U+2E41');
|
||||
assertParsedList('<foo@gmail.com>\u2E41 <bar@gmail.com>\u2E41',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with trailing U+2E41');
|
||||
|
||||
assertParsedList('<foo@gmail.com>\u3001 <bar@gmail.com>',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with U+3001');
|
||||
assertParsedList('<foo@gmail.com>\u3001 <bar@gmail.com>\u3001',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with trailing U+3001');
|
||||
|
||||
assertParsedList('<foo@gmail.com>\uFF0C <bar@gmail.com>',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with U+FF0C');
|
||||
assertParsedList('<foo@gmail.com>\uFF0C <bar@gmail.com>\uFF0C',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with trailing U+FF0C');
|
||||
|
||||
assertParsedList('<foo@gmail.com>\u0613 <bar@gmail.com>',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with U+0613');
|
||||
assertParsedList('<foo@gmail.com>\u0613 <bar@gmail.com>\u0613',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with trailing U+0613');
|
||||
|
||||
assertParsedList('<foo@gmail.com>\u1364 <bar@gmail.com>',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with U+1364');
|
||||
assertParsedList('<foo@gmail.com>\u1364 <bar@gmail.com>\u1364',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with trailing U+1364');
|
||||
|
||||
assertParsedList('<foo@gmail.com>\uFF1B <bar@gmail.com>',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with U+FF1B');
|
||||
assertParsedList('<foo@gmail.com>\uFF1B <bar@gmail.com>\uFF1B',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with trailing U+FF1B');
|
||||
|
||||
assertParsedList('<foo@gmail.com>\uFF64 <bar@gmail.com>',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with U+FF64');
|
||||
assertParsedList('<foo@gmail.com>\uFF64 <bar@gmail.com>\uFF64',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with trailing U+FF64');
|
||||
|
||||
assertParsedList('<foo@gmail.com>\u104A <bar@gmail.com>',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with U+104A');
|
||||
assertParsedList('<foo@gmail.com>\u104A <bar@gmail.com>\u104A',
|
||||
['foo@gmail.com', 'bar@gmail.com'],
|
||||
'Failed to parse 2 email addresses with trailing U+104A');
|
||||
}
|
||||
|
||||
function testToString() {
|
||||
var f = function(str) {
|
||||
return goog.format.InternationalizedEmailAddress.parse(str).toString();
|
||||
};
|
||||
|
||||
// No modification.
|
||||
assertEquals('JOHN Doe <john@gmail.com>',
|
||||
f('JOHN Doe <john@gmail.com>'));
|
||||
|
||||
// Extra spaces.
|
||||
assertEquals('JOHN Doe <john@gmail.com>',
|
||||
f(' JOHN Doe <john@gmail.com> '));
|
||||
|
||||
// No name.
|
||||
assertEquals('john@gmail.com', f('<john@gmail.com>'));
|
||||
assertEquals('john@gmail.com', f('john@gmail.com'));
|
||||
|
||||
// No address.
|
||||
assertEquals('JOHN Doe', f('JOHN Doe <>'));
|
||||
|
||||
// Already quoted.
|
||||
assertEquals('"JOHN, Doe" <john@gmail.com>',
|
||||
f('"JOHN, Doe" <john@gmail.com>'));
|
||||
|
||||
// Needless quotes.
|
||||
assertEquals('JOHN Doe <john@gmail.com>',
|
||||
f('"JOHN Doe" <john@gmail.com>'));
|
||||
// Not quoted-string, but has double quotes.
|
||||
assertEquals('"JOHN, Doe" <john@gmail.com>',
|
||||
f('JOHN, "Doe" <john@gmail.com>'));
|
||||
|
||||
// No special characters other than quotes.
|
||||
assertEquals('JOHN Doe <john@gmail.com>',
|
||||
f('JOHN "Doe" <john@gmail.com>'));
|
||||
|
||||
// Escaped quotes are also removed.
|
||||
assertEquals('"JOHN, Doe" <john@gmail.com>',
|
||||
f('JOHN, \\"Doe\\" <john@gmail.com>'));
|
||||
|
||||
// Characters that require quoting for the display name.
|
||||
assertEquals('"JOHN, Doe" <john@gmail.com>',
|
||||
f('JOHN, Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN; Doe" <john@gmail.com>',
|
||||
f('JOHN; Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN\u055D Doe" <john@gmail.com>',
|
||||
f('JOHN\u055D Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN\u060C Doe" <john@gmail.com>',
|
||||
f('JOHN\u060C Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN\u1363 Doe" <john@gmail.com>',
|
||||
f('JOHN\u1363 Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN\u1802 Doe" <john@gmail.com>',
|
||||
f('JOHN\u1802 Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN\u1808 Doe" <john@gmail.com>',
|
||||
f('JOHN\u1808 Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN\u2E41 Doe" <john@gmail.com>',
|
||||
f('JOHN\u2E41 Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN\u3001 Doe" <john@gmail.com>',
|
||||
f('JOHN\u3001 Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN\uFF0C Doe" <john@gmail.com>',
|
||||
f('JOHN\uFF0C Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN\u061B Doe" <john@gmail.com>',
|
||||
f('JOHN\u061B Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN\u1364 Doe" <john@gmail.com>',
|
||||
f('JOHN\u1364 Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN\uFF1B Doe" <john@gmail.com>',
|
||||
f('JOHN\uFF1B Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN\uFF64 Doe" <john@gmail.com>',
|
||||
f('JOHN\uFF64 Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN(Johnny) Doe" <john@gmail.com>',
|
||||
f('JOHN(Johnny) Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN[Johnny] Doe" <john@gmail.com>',
|
||||
f('JOHN[Johnny] Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN@work Doe" <john@gmail.com>',
|
||||
f('JOHN@work Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN:theking Doe" <john@gmail.com>',
|
||||
f('JOHN:theking Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN\\\\ Doe" <john@gmail.com>',
|
||||
f('JOHN\\ Doe <john@gmail.com>'));
|
||||
assertEquals('"JOHN.com Doe" <john@gmail.com>',
|
||||
f('JOHN.com Doe <john@gmail.com>'));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,414 @@
|
||||
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Creates a string of a JSON object, properly indented for
|
||||
* display.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.format.JsonPrettyPrinter');
|
||||
goog.provide('goog.format.JsonPrettyPrinter.HtmlDelimiters');
|
||||
goog.provide('goog.format.JsonPrettyPrinter.TextDelimiters');
|
||||
|
||||
goog.require('goog.json');
|
||||
goog.require('goog.json.Serializer');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.string.StringBuffer');
|
||||
goog.require('goog.string.format');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Formats a JSON object as a string, properly indented for display. Supports
|
||||
* displaying the string as text or html. Users can also specify their own
|
||||
* set of delimiters for different environments. For example, the JSON object:
|
||||
*
|
||||
* <code>{"a": 1, "b": {"c": null, "d": true, "e": [1, 2]}}</code>
|
||||
*
|
||||
* Will be displayed like this:
|
||||
*
|
||||
* <code>{
|
||||
* "a": 1,
|
||||
* "b": {
|
||||
* "c": null,
|
||||
* "d": true,
|
||||
* "e": [
|
||||
* 1,
|
||||
* 2
|
||||
* ]
|
||||
* }
|
||||
* }</code>
|
||||
* @param {goog.format.JsonPrettyPrinter.TextDelimiters} delimiters Container
|
||||
* for the various strings to use to delimit objects, arrays, newlines, and
|
||||
* other pieces of the output.
|
||||
* @constructor
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter = function(delimiters) {
|
||||
|
||||
/**
|
||||
* The set of characters to use as delimiters.
|
||||
* @type {goog.format.JsonPrettyPrinter.TextDelimiters}
|
||||
* @private
|
||||
*/
|
||||
this.delimiters_ = delimiters ||
|
||||
new goog.format.JsonPrettyPrinter.TextDelimiters();
|
||||
|
||||
/**
|
||||
* Used to serialize property names and values.
|
||||
* @type {goog.json.Serializer}
|
||||
* @private
|
||||
*/
|
||||
this.jsonSerializer_ = new goog.json.Serializer();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Formats a JSON object as a string, properly indented for display.
|
||||
* @param {*} json The object to pretty print. It could be a JSON object, a
|
||||
* string representing a JSON object, or any other type.
|
||||
* @return {string} Returns a string of the JSON object, properly indented for
|
||||
* display.
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter.prototype.format = function(json) {
|
||||
// If input is undefined, null, or empty, return an empty string.
|
||||
if (!goog.isDefAndNotNull(json)) {
|
||||
return '';
|
||||
}
|
||||
if (goog.isString(json)) {
|
||||
if (goog.string.isEmptyOrWhitespace(json)) {
|
||||
return '';
|
||||
}
|
||||
// Try to coerce a string into a JSON object.
|
||||
json = goog.json.parse(json);
|
||||
}
|
||||
var outputBuffer = new goog.string.StringBuffer();
|
||||
this.printObject_(json, outputBuffer, 0);
|
||||
return outputBuffer.toString();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Formats a property value based on the type of the propery.
|
||||
* @param {*} val The object to format.
|
||||
* @param {goog.string.StringBuffer} outputBuffer The buffer to write the
|
||||
* response to.
|
||||
* @param {number} indent The number of spaces to indent each line of the
|
||||
* output.
|
||||
* @private
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter.prototype.printObject_ = function(val,
|
||||
outputBuffer, indent) {
|
||||
var typeOf = goog.typeOf(val);
|
||||
switch (typeOf) {
|
||||
case 'null':
|
||||
case 'boolean':
|
||||
case 'number':
|
||||
case 'string':
|
||||
// "null", "boolean", "number" and "string" properties are printed
|
||||
// directly to the output.
|
||||
this.printValue_(
|
||||
/** @type {null|string|boolean|number} */ (val),
|
||||
typeOf, outputBuffer);
|
||||
break;
|
||||
case 'array':
|
||||
// Example of how an array looks when formatted
|
||||
// (using the default delimiters):
|
||||
// [
|
||||
// 1,
|
||||
// 2,
|
||||
// 3
|
||||
// ]
|
||||
outputBuffer.append(this.delimiters_.arrayStart);
|
||||
var i = 0;
|
||||
// Iterate through the array and format each element.
|
||||
for (i = 0; i < val.length; i++) {
|
||||
if (i > 0) {
|
||||
// There are multiple elements, add a comma to separate them.
|
||||
outputBuffer.append(this.delimiters_.propertySeparator);
|
||||
}
|
||||
outputBuffer.append(this.delimiters_.lineBreak);
|
||||
this.printSpaces_(indent + this.delimiters_.indent, outputBuffer);
|
||||
this.printObject_(val[i], outputBuffer,
|
||||
indent + this.delimiters_.indent);
|
||||
}
|
||||
// If there are no properties in this object, don't put a line break
|
||||
// between the beginning "[" and ending "]", so the output of an empty
|
||||
// array looks like <code>[]</code>.
|
||||
if (i > 0) {
|
||||
outputBuffer.append(this.delimiters_.lineBreak);
|
||||
this.printSpaces_(indent, outputBuffer);
|
||||
}
|
||||
outputBuffer.append(this.delimiters_.arrayEnd);
|
||||
break;
|
||||
case 'object':
|
||||
// Example of how an object looks when formatted
|
||||
// (using the default delimiters):
|
||||
// {
|
||||
// "a": 1,
|
||||
// "b": 2,
|
||||
// "c": "3"
|
||||
// }
|
||||
outputBuffer.append(this.delimiters_.objectStart);
|
||||
var propertyCount = 0;
|
||||
// Iterate through the object and display each property.
|
||||
for (var name in val) {
|
||||
if (!val.hasOwnProperty(name)) {
|
||||
continue;
|
||||
}
|
||||
if (propertyCount > 0) {
|
||||
// There are multiple properties, add a comma to separate them.
|
||||
outputBuffer.append(this.delimiters_.propertySeparator);
|
||||
}
|
||||
outputBuffer.append(this.delimiters_.lineBreak);
|
||||
this.printSpaces_(indent + this.delimiters_.indent, outputBuffer);
|
||||
this.printName_(name, outputBuffer);
|
||||
outputBuffer.append(this.delimiters_.nameValueSeparator,
|
||||
this.delimiters_.space);
|
||||
this.printObject_(val[name], outputBuffer,
|
||||
indent + this.delimiters_.indent);
|
||||
propertyCount++;
|
||||
}
|
||||
// If there are no properties in this object, don't put a line break
|
||||
// between the beginning "{" and ending "}", so the output of an empty
|
||||
// object looks like <code>{}</code>.
|
||||
if (propertyCount > 0) {
|
||||
outputBuffer.append(this.delimiters_.lineBreak);
|
||||
this.printSpaces_(indent, outputBuffer);
|
||||
}
|
||||
outputBuffer.append(this.delimiters_.objectEnd);
|
||||
break;
|
||||
// Other types, such as "function", aren't expected in JSON, and their
|
||||
// behavior is undefined. In these cases, just print an empty string to the
|
||||
// output buffer. This allows the pretty printer to continue while still
|
||||
// outputing well-formed JSON.
|
||||
default:
|
||||
this.printValue_('', 'unknown', outputBuffer);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Prints a property name to the output.
|
||||
* @param {string} name The property name.
|
||||
* @param {goog.string.StringBuffer} outputBuffer The buffer to write the
|
||||
* response to.
|
||||
* @private
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter.prototype.printName_ = function(name,
|
||||
outputBuffer) {
|
||||
outputBuffer.append(this.delimiters_.preName,
|
||||
this.jsonSerializer_.serialize(name), this.delimiters_.postName);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Prints a property name to the output.
|
||||
* @param {string|boolean|number|null} val The property value.
|
||||
* @param {string} typeOf The type of the value. Used to customize
|
||||
* value-specific css in the display. This allows clients to distinguish
|
||||
* between different types in css. For example, the client may define two
|
||||
* classes: "goog-jsonprettyprinter-propertyvalue-string" and
|
||||
* "goog-jsonprettyprinter-propertyvalue-number" to assign a different color
|
||||
* to string and number values.
|
||||
* @param {goog.string.StringBuffer} outputBuffer The buffer to write the
|
||||
* response to.
|
||||
* @private
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter.prototype.printValue_ = function(val,
|
||||
typeOf, outputBuffer) {
|
||||
outputBuffer.append(goog.string.format(this.delimiters_.preValue, typeOf),
|
||||
this.jsonSerializer_.serialize(val),
|
||||
goog.string.format(this.delimiters_.postValue, typeOf));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Print a number of space characters to the output.
|
||||
* @param {number} indent The number of spaces to indent the line.
|
||||
* @param {goog.string.StringBuffer} outputBuffer The buffer to write the
|
||||
* response to.
|
||||
* @private
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter.prototype.printSpaces_ = function(indent,
|
||||
outputBuffer) {
|
||||
outputBuffer.append(goog.string.repeat(this.delimiters_.space, indent));
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A container for the delimiting characters used to display the JSON string
|
||||
* to a text display. Each delimiter is a publicly accessible property of
|
||||
* the object, which makes it easy to tweak delimiters to specific environments.
|
||||
* @constructor
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter.TextDelimiters = function() {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Represents a space character in the output. Used to indent properties a
|
||||
* certain number of spaces, and to separate property names from property
|
||||
* values.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter.TextDelimiters.prototype.space = ' ';
|
||||
|
||||
|
||||
/**
|
||||
* Represents a newline character in the output. Used to begin a new line.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter.TextDelimiters.prototype.lineBreak = '\n';
|
||||
|
||||
|
||||
/**
|
||||
* Represents the start of an object in the output.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter.TextDelimiters.prototype.objectStart = '{';
|
||||
|
||||
|
||||
/**
|
||||
* Represents the end of an object in the output.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter.TextDelimiters.prototype.objectEnd = '}';
|
||||
|
||||
|
||||
/**
|
||||
* Represents the start of an array in the output.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter.TextDelimiters.prototype.arrayStart = '[';
|
||||
|
||||
|
||||
/**
|
||||
* Represents the end of an array in the output.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter.TextDelimiters.prototype.arrayEnd = ']';
|
||||
|
||||
|
||||
/**
|
||||
* Represents the string used to separate properties in the output.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter.TextDelimiters.prototype.propertySeparator = ',';
|
||||
|
||||
|
||||
/**
|
||||
* Represents the string used to separate property names from property values in
|
||||
* the output.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter.TextDelimiters.prototype.nameValueSeparator = ':';
|
||||
|
||||
|
||||
/**
|
||||
* A string that's placed before a property name in the output. Useful for
|
||||
* wrapping a property name in an html tag.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter.TextDelimiters.prototype.preName = '';
|
||||
|
||||
|
||||
/**
|
||||
* A string that's placed after a property name in the output. Useful for
|
||||
* wrapping a property name in an html tag.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter.TextDelimiters.prototype.postName = '';
|
||||
|
||||
|
||||
/**
|
||||
* A string that's placed before a property value in the output. Useful for
|
||||
* wrapping a property value in an html tag.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter.TextDelimiters.prototype.preValue = '';
|
||||
|
||||
|
||||
/**
|
||||
* A string that's placed after a property value in the output. Useful for
|
||||
* wrapping a property value in an html tag.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter.TextDelimiters.prototype.postValue = '';
|
||||
|
||||
|
||||
/**
|
||||
* Represents the number of spaces to indent each sub-property of the JSON.
|
||||
* @type {number}
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter.TextDelimiters.prototype.indent = 2;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A container for the delimiting characters used to display the JSON string
|
||||
* to an HTML <code><pre></code> or <code><code></code> element.
|
||||
* @constructor
|
||||
* @extends {goog.format.JsonPrettyPrinter.TextDelimiters}
|
||||
* @final
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter.HtmlDelimiters = function() {
|
||||
goog.format.JsonPrettyPrinter.TextDelimiters.call(this);
|
||||
};
|
||||
goog.inherits(goog.format.JsonPrettyPrinter.HtmlDelimiters,
|
||||
goog.format.JsonPrettyPrinter.TextDelimiters);
|
||||
|
||||
|
||||
/**
|
||||
* A <code>span</code> tag thats placed before a property name. Used to style
|
||||
* property names with CSS.
|
||||
* @type {string}
|
||||
* @override
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter.HtmlDelimiters.prototype.preName =
|
||||
'<span class="' +
|
||||
goog.getCssName('goog-jsonprettyprinter-propertyname') +
|
||||
'">';
|
||||
|
||||
|
||||
/**
|
||||
* A closing <code>span</code> tag that's placed after a property name.
|
||||
* @type {string}
|
||||
* @override
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter.HtmlDelimiters.prototype.postName = '</span>';
|
||||
|
||||
|
||||
/**
|
||||
* A <code>span</code> tag thats placed before a property value. Used to style
|
||||
* property value with CSS. The span tag's class is in the format
|
||||
* goog-jsonprettyprinter-propertyvalue-{TYPE}, where {TYPE} is the JavaScript
|
||||
* type of the object (the {TYPE} parameter is obtained from goog.typeOf). This
|
||||
* can be used to style different value types.
|
||||
* @type {string}
|
||||
* @override
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter.HtmlDelimiters.prototype.preValue =
|
||||
'<span class="' +
|
||||
goog.getCssName('goog-jsonprettyprinter-propertyvalue') +
|
||||
'-%s">';
|
||||
|
||||
|
||||
/**
|
||||
* A closing <code>span</code> tag that's placed after a property value.
|
||||
* @type {string}
|
||||
* @override
|
||||
*/
|
||||
goog.format.JsonPrettyPrinter.HtmlDelimiters.prototype.postValue = '</span>';
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2010 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.format.JsonPrettyPrinter
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.format.JsonPrettyPrinterTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
goog.provide('goog.format.JsonPrettyPrinterTest');
|
||||
goog.setTestOnly('goog.format.JsonPrettyPrinterTest');
|
||||
|
||||
goog.require('goog.format.JsonPrettyPrinter');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var formatter;
|
||||
|
||||
|
||||
function setUp() {
|
||||
formatter = new goog.format.JsonPrettyPrinter();
|
||||
}
|
||||
|
||||
|
||||
function testUndefined() {
|
||||
assertEquals('', formatter.format());
|
||||
}
|
||||
|
||||
|
||||
function testNull() {
|
||||
assertEquals('', formatter.format(null));
|
||||
}
|
||||
|
||||
|
||||
function testBoolean() {
|
||||
assertEquals('true', formatter.format(true));
|
||||
}
|
||||
|
||||
|
||||
function testNumber() {
|
||||
assertEquals('1', formatter.format(1));
|
||||
}
|
||||
|
||||
|
||||
function testEmptyString() {
|
||||
assertEquals('', formatter.format(''));
|
||||
}
|
||||
|
||||
|
||||
function testWhitespaceString() {
|
||||
assertEquals('', formatter.format(' '));
|
||||
}
|
||||
|
||||
|
||||
function testString() {
|
||||
assertEquals('{}', formatter.format('{}'));
|
||||
}
|
||||
|
||||
|
||||
function testEmptyArray() {
|
||||
assertEquals('[]', formatter.format([]));
|
||||
}
|
||||
|
||||
|
||||
function testArrayOneElement() {
|
||||
assertEquals('[\n 1\n]', formatter.format([1]));
|
||||
}
|
||||
|
||||
|
||||
function testArrayMultipleElements() {
|
||||
assertEquals('[\n 1,\n 2,\n 3\n]', formatter.format([1, 2, 3]));
|
||||
}
|
||||
|
||||
|
||||
function testFunction() {
|
||||
assertEquals('{\n "a": "1",\n "b": ""\n}',
|
||||
formatter.format({'a': '1', 'b': function() { return null; }}));
|
||||
}
|
||||
|
||||
|
||||
function testObject() {
|
||||
assertEquals('{}', formatter.format({}));
|
||||
}
|
||||
|
||||
|
||||
function testObjectMultipleProperties() {
|
||||
assertEquals('{\n "a": null,\n "b": true,\n "c": 1,\n "d": "d",\n "e":' +
|
||||
' [\n 1,\n 2,\n 3\n ],\n "f": {\n "g": 1,\n "h": "h"\n' +
|
||||
' }\n}',
|
||||
formatter.format({'a': null, 'b': true, 'c': 1, 'd': 'd', 'e': [1, 2, 3],
|
||||
'f': {'g': 1, 'h': 'h'}}));
|
||||
}
|
||||
|
||||
|
||||
function testHtmlDelimiters() {
|
||||
var htmlFormatter = new goog.format.JsonPrettyPrinter(
|
||||
new goog.format.JsonPrettyPrinter.HtmlDelimiters());
|
||||
assertEquals('{\n <span class="goog-jsonprettyprinter-propertyname">"a"</s' +
|
||||
'pan>: <span class="goog-jsonprettyprinter-propertyvalue-number">1</spa' +
|
||||
'n>,\n <span class="goog-jsonprettyprinter-propertyname">"b"</span>: <' +
|
||||
'span class="goog-jsonprettyprinter-propertyvalue-string">"2"</span>,\n' +
|
||||
' <span class="goog-jsonprettyprinter-propertyname">"c"</span>: <span ' +
|
||||
'class="goog-jsonprettyprinter-propertyvalue-unknown">""</span>\n}',
|
||||
htmlFormatter.format({'a': 1, 'b': '2', 'c': function() {}}));
|
||||
}
|
||||
Reference in New Issue
Block a user