// Copyright 2011 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 Protocol Buffer 2 Serializer which serializes messages * into a user-friendly text format. Note that this code can run a bit * slowly (especially for parsing) and should therefore not be used for * time or space-critical applications. * * @see http://goo.gl/QDmDr */ goog.provide('goog.proto2.TextFormatSerializer'); goog.require('goog.array'); goog.require('goog.asserts'); goog.require('goog.json'); goog.require('goog.math'); goog.require('goog.object'); goog.require('goog.proto2.FieldDescriptor'); goog.require('goog.proto2.Message'); goog.require('goog.proto2.Serializer'); goog.require('goog.string'); /** * TextFormatSerializer, a serializer which turns Messages into the human * readable text format. * @param {boolean=} opt_ignoreMissingFields If true, then fields that cannot be * found on the proto when parsing the text format will be ignored. * @param {boolean=} opt_useEnumValues If true, serialization code for enums * will use enum integer values instead of human-readable symbolic names. * @constructor * @extends {goog.proto2.Serializer} * @final */ goog.proto2.TextFormatSerializer = function( opt_ignoreMissingFields, opt_useEnumValues) { /** * Whether to ignore fields not defined on the proto when parsing the text * format. * @type {boolean} * @private */ this.ignoreMissingFields_ = !!opt_ignoreMissingFields; /** * Whether to use integer enum values during enum serialization. * If false, symbolic names will be used. * @type {boolean} * @private */ this.useEnumValues_ = !!opt_useEnumValues; }; goog.inherits(goog.proto2.TextFormatSerializer, goog.proto2.Serializer); /** * Deserializes a message from text format and places the data in the message. * @param {goog.proto2.Message} message The message in which to * place the information. * @param {*} data The text format data. * @return {?string} The parse error or null on success. * @override */ goog.proto2.TextFormatSerializer.prototype.deserializeTo = function( message, data) { var textData = data.toString(); var parser = new goog.proto2.TextFormatSerializer.Parser(); if (!parser.parse(message, textData, this.ignoreMissingFields_)) { return parser.getError(); } return null; }; /** * Serializes a message to a string. * @param {goog.proto2.Message} message The message to be serialized. * @return {string} The serialized form of the message. * @override */ goog.proto2.TextFormatSerializer.prototype.serialize = function(message) { var printer = new goog.proto2.TextFormatSerializer.Printer_(); this.serializeMessage_(message, printer); return printer.toString(); }; /** * Serializes the message and prints the text form into the given printer. * @param {goog.proto2.Message} message The message to serialize. * @param {goog.proto2.TextFormatSerializer.Printer_} printer The printer to * which the text format will be printed. * @private */ goog.proto2.TextFormatSerializer.prototype.serializeMessage_ = function( message, printer) { var descriptor = message.getDescriptor(); var fields = descriptor.getFields(); // Add the defined fields, recursively. goog.array.forEach(fields, function(field) { this.printField_(message, field, printer); }, this); // Add the unknown fields, if any. message.forEachUnknown(function(tag, value) { this.serializeUnknown_(tag, value, goog.asserts.assert(printer)); }, this); }; /** * Serializes an unknown field. When parsed from the JsPb object format, this * manifests as either a primitive type, an array, or a raw object with integer * keys. There is no descriptor available to interpret the types of nested * messages. * @param {number} tag The tag for the field. Since it's unknown, this is a * number rather than a string. * @param {*} value The value of the field. * @param {!goog.proto2.TextFormatSerializer.Printer_} printer The printer to * which the text format will be serialized. * @private */ goog.proto2.TextFormatSerializer.prototype.serializeUnknown_ = function( tag, value, printer) { if (!goog.isDefAndNotNull(value)) { return; } if (goog.isArray(value)) { goog.array.forEach(value, function(val) { this.serializeUnknown_(tag, val, printer); }, this); return; } if (goog.isObject(value)) { printer.append(tag); printer.append(' {'); printer.appendLine(); printer.indent(); if (value instanceof goog.proto2.Message) { // Note(user): This conditional is here to make the // testSerializationOfUnknown unit test pass, but in practice we should // never have a Message for an "unknown" field. this.serializeMessage_(value, printer); } else { // For an unknown message, fields are keyed by positive integers. We // don't have a 'length' property to use for enumeration, so go through // all properties and ignore the ones that aren't legal keys. for (var key in value) { var keyAsNumber = goog.string.parseInt(key); goog.asserts.assert(goog.math.isInt(keyAsNumber)); this.serializeUnknown_(keyAsNumber, value[key], printer); } } printer.dedent(); printer.append('}'); printer.appendLine(); return; } if (goog.isString(value)) { value = goog.string.quote(value); } printer.append(tag); printer.append(': '); printer.append(value.toString()); printer.appendLine(); }; /** * Prints the serialized value for the given field to the printer. * @param {*} value The field's value. * @param {goog.proto2.FieldDescriptor} field The field whose value is being * printed. * @param {goog.proto2.TextFormatSerializer.Printer_} printer The printer to * which the value will be printed. * @private */ goog.proto2.TextFormatSerializer.prototype.printFieldValue_ = function( value, field, printer) { switch (field.getFieldType()) { case goog.proto2.FieldDescriptor.FieldType.DOUBLE: case goog.proto2.FieldDescriptor.FieldType.FLOAT: case goog.proto2.FieldDescriptor.FieldType.INT64: case goog.proto2.FieldDescriptor.FieldType.UINT64: case goog.proto2.FieldDescriptor.FieldType.INT32: case goog.proto2.FieldDescriptor.FieldType.UINT32: case goog.proto2.FieldDescriptor.FieldType.FIXED64: case goog.proto2.FieldDescriptor.FieldType.FIXED32: case goog.proto2.FieldDescriptor.FieldType.BOOL: case goog.proto2.FieldDescriptor.FieldType.SFIXED32: case goog.proto2.FieldDescriptor.FieldType.SFIXED64: case goog.proto2.FieldDescriptor.FieldType.SINT32: case goog.proto2.FieldDescriptor.FieldType.SINT64: printer.append(value); break; case goog.proto2.FieldDescriptor.FieldType.BYTES: case goog.proto2.FieldDescriptor.FieldType.STRING: value = goog.string.quote(value.toString()); printer.append(value); break; case goog.proto2.FieldDescriptor.FieldType.ENUM: if (!this.useEnumValues_) { // Search the enum type for a matching key. var found = false; goog.object.forEach(field.getNativeType(), function(eValue, key) { if (!found && eValue == value) { printer.append(key); found = true; } }); } if (!found || this.useEnumValues_) { // Otherwise, just print the numeric value. printer.append(value.toString()); } break; case goog.proto2.FieldDescriptor.FieldType.GROUP: case goog.proto2.FieldDescriptor.FieldType.MESSAGE: this.serializeMessage_( /** @type {goog.proto2.Message} */ (value), printer); break; } }; /** * Prints the serialized field to the printer. * @param {goog.proto2.Message} message The parent message. * @param {goog.proto2.FieldDescriptor} field The field to print. * @param {goog.proto2.TextFormatSerializer.Printer_} printer The printer to * which the field will be printed. * @private */ goog.proto2.TextFormatSerializer.prototype.printField_ = function( message, field, printer) { // Skip fields not present. if (!message.has(field)) { return; } var count = message.countOf(field); for (var i = 0; i < count; ++i) { // Field name. printer.append(field.getName()); // Field delimiter. if (field.getFieldType() == goog.proto2.FieldDescriptor.FieldType.MESSAGE || field.getFieldType() == goog.proto2.FieldDescriptor.FieldType.GROUP) { printer.append(' {'); printer.appendLine(); printer.indent(); } else { printer.append(': '); } // Write the field value. this.printFieldValue_(message.get(field, i), field, printer); // Close the field. if (field.getFieldType() == goog.proto2.FieldDescriptor.FieldType.MESSAGE || field.getFieldType() == goog.proto2.FieldDescriptor.FieldType.GROUP) { printer.dedent(); printer.append('}'); printer.appendLine(); } else { printer.appendLine(); } } }; //////////////////////////////////////////////////////////////////////////////// /** * Helper class used by the text format serializer for pretty-printing text. * @constructor * @private */ goog.proto2.TextFormatSerializer.Printer_ = function() { /** * The current indentation count. * @type {number} * @private */ this.indentation_ = 0; /** * The buffer of string pieces. * @type {Array} * @private */ this.buffer_ = []; /** * Whether indentation is required before the next append of characters. * @type {boolean} * @private */ this.requiresIndentation_ = true; }; /** * @return {string} The contents of the printer. * @override */ goog.proto2.TextFormatSerializer.Printer_.prototype.toString = function() { return this.buffer_.join(''); }; /** * Increases the indentation in the printer. */ goog.proto2.TextFormatSerializer.Printer_.prototype.indent = function() { this.indentation_ += 2; }; /** * Decreases the indentation in the printer. */ goog.proto2.TextFormatSerializer.Printer_.prototype.dedent = function() { this.indentation_ -= 2; goog.asserts.assert(this.indentation_ >= 0); }; /** * Appends the given value to the printer. * @param {*} value The value to append. */ goog.proto2.TextFormatSerializer.Printer_.prototype.append = function(value) { if (this.requiresIndentation_) { for (var i = 0; i < this.indentation_; ++i) { this.buffer_.push(' '); } this.requiresIndentation_ = false; } this.buffer_.push(value.toString()); }; /** * Appends a newline to the printer. */ goog.proto2.TextFormatSerializer.Printer_.prototype.appendLine = function() { this.buffer_.push('\n'); this.requiresIndentation_ = true; }; //////////////////////////////////////////////////////////////////////////////// /** * Helper class for tokenizing the text format. * @param {string} data The string data to tokenize. * @param {boolean=} opt_ignoreWhitespace If true, whitespace tokens will not * be reported by the tokenizer. * @param {boolean=} opt_ignoreComments If true, comment tokens will not be * reported by the tokenizer. * @constructor * @private */ goog.proto2.TextFormatSerializer.Tokenizer_ = function( data, opt_ignoreWhitespace, opt_ignoreComments) { /** * Whether to skip whitespace tokens on output. * @private {boolean} */ this.ignoreWhitespace_ = !!opt_ignoreWhitespace; /** * Whether to skip comment tokens on output. * @private {boolean} */ this.ignoreComments_ = !!opt_ignoreComments; /** * The data being tokenized. * @private {string} */ this.data_ = data; /** * The current index in the data. * @private {number} */ this.index_ = 0; /** * The data string starting at the current index. * @private {string} */ this.currentData_ = data; /** * The current token type. * @private {goog.proto2.TextFormatSerializer.Tokenizer_.Token} */ this.current_ = { type: goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes.END, value: null }; }; /** * @typedef {{type: goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes, * value: ?string}} */ goog.proto2.TextFormatSerializer.Tokenizer_.Token; /** * @return {goog.proto2.TextFormatSerializer.Tokenizer_.Token} The current * token. */ goog.proto2.TextFormatSerializer.Tokenizer_.prototype.getCurrent = function() { return this.current_; }; /** * An enumeration of all the token types. * @enum {!RegExp} */ goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes = { END: /---end---/, // Leading "-" to identify "-infinity"." IDENTIFIER: /^-?[a-zA-Z][a-zA-Z0-9_]*/, NUMBER: /^(0x[0-9a-f]+)|(([-])?[0-9][0-9]*(\.?[0-9]+)?(e[+-]?[0-9]+|[f])?)/, COMMENT: /^#.*/, OPEN_BRACE: /^{/, CLOSE_BRACE: /^}/, OPEN_TAG: /^/, OPEN_LIST: /^\[/, CLOSE_LIST: /^\]/, STRING: new RegExp('^"([^"\\\\]|\\\\.)*"'), COLON: /^:/, COMMA: /^,/, SEMI: /^;/, WHITESPACE: /^\s/ }; /** * Advances to the next token. * @return {boolean} True if a valid token was found, false if the end was * reached or no valid token was found. */ goog.proto2.TextFormatSerializer.Tokenizer_.prototype.next = function() { var types = goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes; // Skip any whitespace if requested. while (this.nextInternal_()) { var type = this.getCurrent().type; if ((type != types.WHITESPACE && type != types.COMMENT) || (type == types.WHITESPACE && !this.ignoreWhitespace_) || (type == types.COMMENT && !this.ignoreComments_)) { return true; } } // If we reach this point, set the current token to END. this.current_ = { type: goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes.END, value: null }; return false; }; /** * Internal method for determining the next token. * @return {boolean} True if a next token was found, false otherwise. * @private */ goog.proto2.TextFormatSerializer.Tokenizer_.prototype.nextInternal_ = function() { if (this.index_ >= this.data_.length) { return false; } var data = this.currentData_; var types = goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes; var next = null; // Loop through each token type and try to match the beginning of the string // with the token's regular expression. goog.object.some(types, function(type, id) { if (next || type == types.END) { return false; } // Note: This regular expression check is at, minimum, O(n). var info = type.exec(data); if (info && info.index == 0) { next = {type: type, value: info[0]}; } return !!next; }); // Advance the index by the length of the token. if (next) { this.current_ = /** @type {goog.proto2.TextFormatSerializer.Tokenizer_.Token} */ (next); this.index_ += next.value.length; this.currentData_ = this.currentData_.substring(next.value.length); } return !!next; }; //////////////////////////////////////////////////////////////////////////////// /** * Helper class for parsing the text format. * @constructor * @final */ goog.proto2.TextFormatSerializer.Parser = function() { /** * The error during parsing, if any. * @type {?string} * @private */ this.error_ = null; /** * The current tokenizer. * @type {goog.proto2.TextFormatSerializer.Tokenizer_} * @private */ this.tokenizer_ = null; /** * Whether to ignore missing fields in the proto when parsing. * @type {boolean} * @private */ this.ignoreMissingFields_ = false; }; /** * Parses the given data, filling the message as it goes. * @param {goog.proto2.Message} message The message to fill. * @param {string} data The text format data. * @param {boolean=} opt_ignoreMissingFields If true, fields missing in the * proto will be ignored. * @return {boolean} True on success, false on failure. On failure, the * getError method can be called to get the reason for failure. */ goog.proto2.TextFormatSerializer.Parser.prototype.parse = function( message, data, opt_ignoreMissingFields) { this.error_ = null; this.ignoreMissingFields_ = !!opt_ignoreMissingFields; this.tokenizer_ = new goog.proto2.TextFormatSerializer.Tokenizer_(data, true, true); this.tokenizer_.next(); return this.consumeMessage_(message, ''); }; /** * @return {?string} The parse error, if any. */ goog.proto2.TextFormatSerializer.Parser.prototype.getError = function() { return this.error_; }; /** * Reports a parse error. * @param {string} msg The error message. * @private */ goog.proto2.TextFormatSerializer.Parser.prototype.reportError_ = function(msg) { this.error_ = msg; }; /** * Attempts to consume the given message. * @param {goog.proto2.Message} message The message to consume and fill. If * null, then the message contents will be consumed without ever being set * to anything. * @param {string} delimiter The delimiter expected at the end of the message. * @return {boolean} True on success, false otherwise. * @private */ goog.proto2.TextFormatSerializer.Parser.prototype.consumeMessage_ = function( message, delimiter) { var types = goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes; while (!this.lookingAt_('>') && !this.lookingAt_('}') && !this.lookingAtType_(types.END)) { if (!this.consumeField_(message)) { return false; } } if (delimiter) { if (!this.consume_(delimiter)) { return false; } } else { if (!this.lookingAtType_(types.END)) { this.reportError_('Expected END token'); } } return true; }; /** * Attempts to consume the value of the given field. * @param {goog.proto2.Message} message The parent message. * @param {goog.proto2.FieldDescriptor} field The field. * @return {boolean} True on success, false otherwise. * @private */ goog.proto2.TextFormatSerializer.Parser.prototype.consumeFieldValue_ = function( message, field) { var value = this.getFieldValue_(field); if (goog.isNull(value)) { return false; } if (field.isRepeated()) { message.add(field, value); } else { message.set(field, value); } return true; }; /** * Attempts to convert a string to a number. * @param {string} num in hexadecimal or float format. * @return {number} The converted number or null on error. * @private */ goog.proto2.TextFormatSerializer.Parser.getNumberFromString_ = function(num) { var returnValue = goog.string.contains(num, '.') ? parseFloat(num) : // num is a float. goog.string.parseInt(num); // num is an int. goog.asserts.assert(!isNaN(returnValue)); goog.asserts.assert(isFinite(returnValue)); return returnValue; }; /** * Parse NaN, positive infinity, or negative infinity from a string. * @param {string} identifier An identifier string to check. * @return {?number} Infinity, negative infinity, NaN, or null if none * of the constants could be parsed. * @private */ goog.proto2.TextFormatSerializer.Parser.parseNumericalConstant_ = function( identifier) { if (/^-?inf(?:inity)?f?$/i.test(identifier)) { return Infinity * (goog.string.startsWith(identifier, '-') ? -1 : 1); } if (/^nanf?$/i.test(identifier)) { return NaN; } return null; }; /** * Attempts to parse the given field's value from the stream. * @param {goog.proto2.FieldDescriptor} field The field. * @return {*} The field's value or null if none. * @private */ goog.proto2.TextFormatSerializer.Parser.prototype.getFieldValue_ = function( field) { var types = goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes; switch (field.getFieldType()) { case goog.proto2.FieldDescriptor.FieldType.DOUBLE: case goog.proto2.FieldDescriptor.FieldType.FLOAT: var identifier = this.consumeIdentifier_(); if (identifier) { var numericalIdentifier = goog.proto2.TextFormatSerializer.Parser.parseNumericalConstant_( identifier); // Use isDefAndNotNull since !!NaN is false. if (goog.isDefAndNotNull(numericalIdentifier)) { return numericalIdentifier; } } case goog.proto2.FieldDescriptor.FieldType.INT32: case goog.proto2.FieldDescriptor.FieldType.UINT32: case goog.proto2.FieldDescriptor.FieldType.FIXED32: case goog.proto2.FieldDescriptor.FieldType.SFIXED32: case goog.proto2.FieldDescriptor.FieldType.SINT32: var num = this.consumeNumber_(); if (!num) { return null; } return goog.proto2.TextFormatSerializer.Parser.getNumberFromString_(num); case goog.proto2.FieldDescriptor.FieldType.INT64: case goog.proto2.FieldDescriptor.FieldType.UINT64: case goog.proto2.FieldDescriptor.FieldType.FIXED64: case goog.proto2.FieldDescriptor.FieldType.SFIXED64: case goog.proto2.FieldDescriptor.FieldType.SINT64: var num = this.consumeNumber_(); if (!num) { return null; } if (field.getNativeType() == Number) { // 64-bit number stored as a number. return goog.proto2.TextFormatSerializer.Parser.getNumberFromString_( num); } return num; // 64-bit numbers are by default stored as strings. case goog.proto2.FieldDescriptor.FieldType.BOOL: var ident = this.consumeIdentifier_(); if (!ident) { return null; } switch (ident) { case 'true': return true; case 'false': return false; default: this.reportError_('Unknown type for bool: ' + ident); return null; } case goog.proto2.FieldDescriptor.FieldType.ENUM: if (this.lookingAtType_(types.NUMBER)) { var num = this.consumeNumber_(); if (!num) { return null; } return goog.proto2.TextFormatSerializer.Parser.getNumberFromString_( num); } else { // Search the enum type for a matching key. var name = this.consumeIdentifier_(); if (!name) { return null; } var enumValue = field.getNativeType()[name]; if (enumValue == null) { this.reportError_('Unknown enum value: ' + name); return null; } return enumValue; } case goog.proto2.FieldDescriptor.FieldType.BYTES: case goog.proto2.FieldDescriptor.FieldType.STRING: return this.consumeString_(); } }; /** * Attempts to consume a nested message. * @param {goog.proto2.Message} message The parent message. * @param {goog.proto2.FieldDescriptor} field The field. * @return {boolean} True on success, false otherwise. * @private */ goog.proto2.TextFormatSerializer.Parser.prototype.consumeNestedMessage_ = function(message, field) { var delimiter = ''; // Messages support both < > and { } as delimiters for legacy reasons. if (this.tryConsume_('<')) { delimiter = '>'; } else { if (!this.consume_('{')) { return false; } delimiter = '}'; } var msg = field.getFieldMessageType().createMessageInstance(); var result = this.consumeMessage_(msg, delimiter); if (!result) { return false; } // Add the message to the parent message. if (field.isRepeated()) { message.add(field, msg); } else { message.set(field, msg); } return true; }; /** * Attempts to consume the value of an unknown field. This method uses * heuristics to try to consume just the right tokens. * @return {boolean} True on success, false otherwise. * @private */ goog.proto2.TextFormatSerializer.Parser.prototype.consumeUnknownFieldValue_ = function() { // : is optional. this.tryConsume_(':'); // Handle form: [.. , ... , ..] if (this.tryConsume_('[')) { while (true) { this.tokenizer_.next(); if (this.tryConsume_(']')) { break; } if (!this.consume_(',')) { return false; } } return true; } // Handle nested messages/groups. if (this.tryConsume_('<')) { return this.consumeMessage_(null /* unknown */, '>'); } else if (this.tryConsume_('{')) { return this.consumeMessage_(null /* unknown */, '}'); } else { // Otherwise, consume a single token for the field value. this.tokenizer_.next(); } return true; }; /** * Attempts to consume a field under a message. * @param {goog.proto2.Message} message The parent message. If null, then the * field value will be consumed without being assigned to anything. * @return {boolean} True on success, false otherwise. * @private */ goog.proto2.TextFormatSerializer.Parser.prototype.consumeField_ = function( message) { var fieldName = this.consumeIdentifier_(); if (!fieldName) { this.reportError_('Missing field name'); return false; } var field = null; if (message) { field = message.getDescriptor().findFieldByName(fieldName.toString()); } if (field == null) { if (this.ignoreMissingFields_) { return this.consumeUnknownFieldValue_(); } else { this.reportError_('Unknown field: ' + fieldName); return false; } } if (field.getFieldType() == goog.proto2.FieldDescriptor.FieldType.MESSAGE || field.getFieldType() == goog.proto2.FieldDescriptor.FieldType.GROUP) { // : is optional here. this.tryConsume_(':'); if (!this.consumeNestedMessage_(message, field)) { return false; } } else { // Long Format: "someField: 123" // Short Format: "someField: [123, 456, 789]" if (!this.consume_(':')) { return false; } if (field.isRepeated() && this.tryConsume_('[')) { // Short repeated format, e.g. "foo: [1, 2, 3]" while (true) { if (!this.consumeFieldValue_(message, field)) { return false; } if (this.tryConsume_(']')) { break; } if (!this.consume_(',')) { return false; } } } else { // Normal field format. if (!this.consumeFieldValue_(message, field)) { return false; } } } // For historical reasons, fields may optionally be separated by commas or // semicolons. this.tryConsume_(',') || this.tryConsume_(';'); return true; }; /** * Attempts to consume a token with the given string value. * @param {string} value The string value for the token. * @return {boolean} True if the token matches and was consumed, false * otherwise. * @private */ goog.proto2.TextFormatSerializer.Parser.prototype.tryConsume_ = function( value) { if (this.lookingAt_(value)) { this.tokenizer_.next(); return true; } return false; }; /** * Consumes a token of the given type. * @param {goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes} type The type * of the token to consume. * @return {?string} The string value of the token or null on error. * @private */ goog.proto2.TextFormatSerializer.Parser.prototype.consumeToken_ = function( type) { if (!this.lookingAtType_(type)) { this.reportError_('Expected token type: ' + type); return null; } var value = this.tokenizer_.getCurrent().value; this.tokenizer_.next(); return value; }; /** * Consumes an IDENTIFIER token. * @return {?string} The string value or null on error. * @private */ goog.proto2.TextFormatSerializer.Parser.prototype.consumeIdentifier_ = function() { var types = goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes; return this.consumeToken_(types.IDENTIFIER); }; /** * Consumes a NUMBER token. * @return {?string} The string value or null on error. * @private */ goog.proto2.TextFormatSerializer.Parser.prototype.consumeNumber_ = function() { var types = goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes; return this.consumeToken_(types.NUMBER); }; /** * Consumes a STRING token. Strings may come in multiple adjacent tokens which * are automatically concatenated, like in C or Python. * @return {?string} The *deescaped* string value or null on error. * @private */ goog.proto2.TextFormatSerializer.Parser.prototype.consumeString_ = function() { var types = goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes; var value = this.consumeToken_(types.STRING); if (!value) { return null; } var stringValue = goog.json.parse(value).toString(); while (this.lookingAtType_(types.STRING)) { value = this.consumeToken_(types.STRING); stringValue += goog.json.parse(value).toString(); } return stringValue; }; /** * Consumes a token with the given value. If not found, reports an error. * @param {string} value The string value expected for the token. * @return {boolean} True on success, false otherwise. * @private */ goog.proto2.TextFormatSerializer.Parser.prototype.consume_ = function(value) { if (!this.tryConsume_(value)) { this.reportError_('Expected token "' + value + '"'); return false; } return true; }; /** * @param {string} value The value to check against. * @return {boolean} True if the current token has the given string value. * @private */ goog.proto2.TextFormatSerializer.Parser.prototype.lookingAt_ = function(value) { return this.tokenizer_.getCurrent().value == value; }; /** * @param {goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes} type The * token type. * @return {boolean} True if the current token has the given type. * @private */ goog.proto2.TextFormatSerializer.Parser.prototype.lookingAtType_ = function( type) { return this.tokenizer_.getCurrent().type == type; };