Adding float-no-zero branch hosted build

This commit is contained in:
ahocevar
2014-03-07 10:55:12 +01:00
parent 84cad42f6d
commit bd9092199b
1664 changed files with 731463 additions and 0 deletions
@@ -0,0 +1,332 @@
// 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 componants 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}
* @private
*/
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_ = '">)]';
/**
* A RegExp to check special characters to be quoted. Used in cleanAddress().
* @type {RegExp}
* @private
*/
goog.format.EmailAddress.SPECIAL_CHARS_RE_ = /[()<>@,;:\\\".\[\]]/;
/**
* 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;
/**
* Get the name associated with the email address.
* @return {string} The name or personal portion of the address.
*/
goog.format.EmailAddress.prototype.getName = function() {
return this.name_;
};
/**
* Get the email address.
* @return {string} The email address.
*/
goog.format.EmailAddress.prototype.getAddress = function() {
return this.address_;
};
/**
* Set the name associated with the email address.
* @param {string} name The name to associate.
*/
goog.format.EmailAddress.prototype.setName = function(name) {
this.name_ = name;
};
/**
* Set the email address.
* @param {string} address The email address.
*/
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() {
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.
var quoteNeeded = goog.format.EmailAddress.SPECIAL_CHARS_RE_.test(name);
if (quoteNeeded) {
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
// TODO(mariakhomenko): we should also be handling i18n domain names as per
// http://en.wikipedia.org/wiki/Internationalized_domain_name
var filter =
/^[+a-zA-Z0-9_.!#$%&'*\/=?^`{|}~-]+@([a-zA-Z0-9-]+\.)+[a-zA-Z0-9]{2,6}$/;
return filter.test(str);
};
/**
* Parse an email address of the form "name" &lt;address&gt; into
* an email address.
* @param {string} addr The address string.
* @return {goog.format.EmailAddress} The parsed address.
*/
goog.format.EmailAddress.parse = function(addr) {
// 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 goog.format.EmailAddress(address, name);
};
/**
* Parse a string containing email addresses of the form
* "name" &lt;address&gt; 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) {
var result = [];
var email = '';
var token;
for (var i = 0; i < str.length; ) {
token = goog.format.EmailAddress.getToken_(str, i);
if (token == ',' || token == ';' ||
(token == ' ' && goog.format.EmailAddress.parse(email).isValid())) {
if (!goog.string.isEmpty(email)) {
result.push(goog.format.EmailAddress.parse(email));
}
email = '';
i++;
continue;
}
email += token;
i += token.length;
}
// Add the final token.
if (!goog.string.isEmpty(email)) {
result.push(goog.format.EmailAddress.parse(email));
}
return result;
};
/**
* 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);
};
@@ -0,0 +1,491 @@
// 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.
* @return {string} String representation of number of bytes.
*/
goog.format.numBytesToString = function(val, opt_decimals, opt_suffix) {
var suffix = '';
if (!goog.isDef(opt_suffix) || opt_suffix) {
suffix = 'B';
}
return goog.format.numericValueToString_(
val, goog.format.NUMERIC_SCALES_BINARY_, opt_decimals, suffix);
};
/**
* 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.
* @return {string} The human readable form of the byte size.
* @private
*/
goog.format.numericValueToString_ = function(val, conversion,
opt_decimals, opt_suffix) {
var prefixes = goog.format.NUMERIC_SCALE_PREFIXES_;
var orig_val = val;
var symbol = '';
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;
}
var ex = Math.pow(10, goog.isDef(opt_decimals) ? opt_decimals : 2);
return Math.round(orig_val / scale * ex) / ex + 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.
* @type {Array}
* @private
*/
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 &shy; 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 ?
'&shy;' : goog.format.IS_IE8_OR_ABOVE_ ?
'&#8203;' : '<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,407 @@
// 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
*/
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
*/
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,413 @@
// 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.isEmpty(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>&lt;pre&gt;</code> or <code>&lt;code&gt;</code> element.
* @constructor
* @extends {goog.format.JsonPrettyPrinter.TextDelimiters}
*/
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>';