Adding float-no-zero branch hosted build
This commit is contained in:
@@ -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" <address> 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" <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) {
|
||||
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 ­ 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,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><pre></code> or <code><code></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>';
|
||||
Reference in New Issue
Block a user