Path: blob/trunk/third_party/closure/goog/proto2/textformatserializer.js
2868 views
// Copyright 2011 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Protocol Buffer 2 Serializer which serializes messages16* into a user-friendly text format. Note that this code can run a bit17* slowly (especially for parsing) and should therefore not be used for18* time or space-critical applications.19*20* @see http://goo.gl/QDmDr21*/2223goog.provide('goog.proto2.TextFormatSerializer');2425goog.require('goog.array');26goog.require('goog.asserts');27goog.require('goog.json');28goog.require('goog.math');29goog.require('goog.object');30goog.require('goog.proto2.FieldDescriptor');31goog.require('goog.proto2.Message');32goog.require('goog.proto2.Serializer');33goog.require('goog.string');34353637/**38* TextFormatSerializer, a serializer which turns Messages into the human39* readable text format.40* @param {boolean=} opt_ignoreMissingFields If true, then fields that cannot be41* found on the proto when parsing the text format will be ignored.42* @param {boolean=} opt_useEnumValues If true, serialization code for enums43* will use enum integer values instead of human-readable symbolic names.44* @constructor45* @extends {goog.proto2.Serializer}46* @final47*/48goog.proto2.TextFormatSerializer = function(49opt_ignoreMissingFields, opt_useEnumValues) {50/**51* Whether to ignore fields not defined on the proto when parsing the text52* format.53* @type {boolean}54* @private55*/56this.ignoreMissingFields_ = !!opt_ignoreMissingFields;5758/**59* Whether to use integer enum values during enum serialization.60* If false, symbolic names will be used.61* @type {boolean}62* @private63*/64this.useEnumValues_ = !!opt_useEnumValues;65};66goog.inherits(goog.proto2.TextFormatSerializer, goog.proto2.Serializer);676869/**70* Deserializes a message from text format and places the data in the message.71* @param {goog.proto2.Message} message The message in which to72* place the information.73* @param {*} data The text format data.74* @return {?string} The parse error or null on success.75* @override76*/77goog.proto2.TextFormatSerializer.prototype.deserializeTo = function(78message, data) {79var textData = data.toString();80var parser = new goog.proto2.TextFormatSerializer.Parser();81if (!parser.parse(message, textData, this.ignoreMissingFields_)) {82return parser.getError();83}8485return null;86};878889/**90* Serializes a message to a string.91* @param {goog.proto2.Message} message The message to be serialized.92* @return {string} The serialized form of the message.93* @override94*/95goog.proto2.TextFormatSerializer.prototype.serialize = function(message) {96var printer = new goog.proto2.TextFormatSerializer.Printer_();97this.serializeMessage_(message, printer);98return printer.toString();99};100101102/**103* Serializes the message and prints the text form into the given printer.104* @param {goog.proto2.Message} message The message to serialize.105* @param {goog.proto2.TextFormatSerializer.Printer_} printer The printer to106* which the text format will be printed.107* @private108*/109goog.proto2.TextFormatSerializer.prototype.serializeMessage_ = function(110message, printer) {111var descriptor = message.getDescriptor();112var fields = descriptor.getFields();113114// Add the defined fields, recursively.115goog.array.forEach(fields, function(field) {116this.printField_(message, field, printer);117}, this);118119// Add the unknown fields, if any.120message.forEachUnknown(function(tag, value) {121this.serializeUnknown_(tag, value, goog.asserts.assert(printer));122}, this);123};124125126/**127* Serializes an unknown field. When parsed from the JsPb object format, this128* manifests as either a primitive type, an array, or a raw object with integer129* keys. There is no descriptor available to interpret the types of nested130* messages.131* @param {number} tag The tag for the field. Since it's unknown, this is a132* number rather than a string.133* @param {*} value The value of the field.134* @param {!goog.proto2.TextFormatSerializer.Printer_} printer The printer to135* which the text format will be serialized.136* @private137*/138goog.proto2.TextFormatSerializer.prototype.serializeUnknown_ = function(139tag, value, printer) {140if (!goog.isDefAndNotNull(value)) {141return;142}143144if (goog.isArray(value)) {145goog.array.forEach(value, function(val) {146this.serializeUnknown_(tag, val, printer);147}, this);148return;149}150151if (goog.isObject(value)) {152printer.append(tag);153printer.append(' {');154printer.appendLine();155printer.indent();156if (value instanceof goog.proto2.Message) {157// Note(user): This conditional is here to make the158// testSerializationOfUnknown unit test pass, but in practice we should159// never have a Message for an "unknown" field.160this.serializeMessage_(value, printer);161} else {162// For an unknown message, fields are keyed by positive integers. We163// don't have a 'length' property to use for enumeration, so go through164// all properties and ignore the ones that aren't legal keys.165for (var key in value) {166var keyAsNumber = goog.string.parseInt(key);167goog.asserts.assert(goog.math.isInt(keyAsNumber));168this.serializeUnknown_(keyAsNumber, value[key], printer);169}170}171printer.dedent();172printer.append('}');173printer.appendLine();174return;175}176177if (goog.isString(value)) {178value = goog.string.quote(value);179}180printer.append(tag);181printer.append(': ');182printer.append(value.toString());183printer.appendLine();184};185186187/**188* Prints the serialized value for the given field to the printer.189* @param {*} value The field's value.190* @param {goog.proto2.FieldDescriptor} field The field whose value is being191* printed.192* @param {goog.proto2.TextFormatSerializer.Printer_} printer The printer to193* which the value will be printed.194* @private195*/196goog.proto2.TextFormatSerializer.prototype.printFieldValue_ = function(197value, field, printer) {198switch (field.getFieldType()) {199case goog.proto2.FieldDescriptor.FieldType.DOUBLE:200case goog.proto2.FieldDescriptor.FieldType.FLOAT:201case goog.proto2.FieldDescriptor.FieldType.INT64:202case goog.proto2.FieldDescriptor.FieldType.UINT64:203case goog.proto2.FieldDescriptor.FieldType.INT32:204case goog.proto2.FieldDescriptor.FieldType.UINT32:205case goog.proto2.FieldDescriptor.FieldType.FIXED64:206case goog.proto2.FieldDescriptor.FieldType.FIXED32:207case goog.proto2.FieldDescriptor.FieldType.BOOL:208case goog.proto2.FieldDescriptor.FieldType.SFIXED32:209case goog.proto2.FieldDescriptor.FieldType.SFIXED64:210case goog.proto2.FieldDescriptor.FieldType.SINT32:211case goog.proto2.FieldDescriptor.FieldType.SINT64:212printer.append(value);213break;214215case goog.proto2.FieldDescriptor.FieldType.BYTES:216case goog.proto2.FieldDescriptor.FieldType.STRING:217value = goog.string.quote(value.toString());218printer.append(value);219break;220221case goog.proto2.FieldDescriptor.FieldType.ENUM:222if (!this.useEnumValues_) {223// Search the enum type for a matching key.224var found = false;225goog.object.forEach(field.getNativeType(), function(eValue, key) {226if (!found && eValue == value) {227printer.append(key);228found = true;229}230});231}232233if (!found || this.useEnumValues_) {234// Otherwise, just print the numeric value.235printer.append(value.toString());236}237break;238239case goog.proto2.FieldDescriptor.FieldType.GROUP:240case goog.proto2.FieldDescriptor.FieldType.MESSAGE:241this.serializeMessage_(242/** @type {goog.proto2.Message} */ (value), printer);243break;244}245};246247248/**249* Prints the serialized field to the printer.250* @param {goog.proto2.Message} message The parent message.251* @param {goog.proto2.FieldDescriptor} field The field to print.252* @param {goog.proto2.TextFormatSerializer.Printer_} printer The printer to253* which the field will be printed.254* @private255*/256goog.proto2.TextFormatSerializer.prototype.printField_ = function(257message, field, printer) {258// Skip fields not present.259if (!message.has(field)) {260return;261}262263var count = message.countOf(field);264for (var i = 0; i < count; ++i) {265// Field name.266printer.append(field.getName());267268// Field delimiter.269if (field.getFieldType() == goog.proto2.FieldDescriptor.FieldType.MESSAGE ||270field.getFieldType() == goog.proto2.FieldDescriptor.FieldType.GROUP) {271printer.append(' {');272printer.appendLine();273printer.indent();274} else {275printer.append(': ');276}277278// Write the field value.279this.printFieldValue_(message.get(field, i), field, printer);280281// Close the field.282if (field.getFieldType() == goog.proto2.FieldDescriptor.FieldType.MESSAGE ||283field.getFieldType() == goog.proto2.FieldDescriptor.FieldType.GROUP) {284printer.dedent();285printer.append('}');286printer.appendLine();287} else {288printer.appendLine();289}290}291};292293294////////////////////////////////////////////////////////////////////////////////295296297298/**299* Helper class used by the text format serializer for pretty-printing text.300* @constructor301* @private302*/303goog.proto2.TextFormatSerializer.Printer_ = function() {304/**305* The current indentation count.306* @type {number}307* @private308*/309this.indentation_ = 0;310311/**312* The buffer of string pieces.313* @type {Array<string>}314* @private315*/316this.buffer_ = [];317318/**319* Whether indentation is required before the next append of characters.320* @type {boolean}321* @private322*/323this.requiresIndentation_ = true;324};325326327/**328* @return {string} The contents of the printer.329* @override330*/331goog.proto2.TextFormatSerializer.Printer_.prototype.toString = function() {332return this.buffer_.join('');333};334335336/**337* Increases the indentation in the printer.338*/339goog.proto2.TextFormatSerializer.Printer_.prototype.indent = function() {340this.indentation_ += 2;341};342343344/**345* Decreases the indentation in the printer.346*/347goog.proto2.TextFormatSerializer.Printer_.prototype.dedent = function() {348this.indentation_ -= 2;349goog.asserts.assert(this.indentation_ >= 0);350};351352353/**354* Appends the given value to the printer.355* @param {*} value The value to append.356*/357goog.proto2.TextFormatSerializer.Printer_.prototype.append = function(value) {358if (this.requiresIndentation_) {359for (var i = 0; i < this.indentation_; ++i) {360this.buffer_.push(' ');361}362this.requiresIndentation_ = false;363}364365this.buffer_.push(value.toString());366};367368369/**370* Appends a newline to the printer.371*/372goog.proto2.TextFormatSerializer.Printer_.prototype.appendLine = function() {373this.buffer_.push('\n');374this.requiresIndentation_ = true;375};376377378////////////////////////////////////////////////////////////////////////////////379380381382/**383* Helper class for tokenizing the text format.384* @param {string} data The string data to tokenize.385* @param {boolean=} opt_ignoreWhitespace If true, whitespace tokens will not386* be reported by the tokenizer.387* @param {boolean=} opt_ignoreComments If true, comment tokens will not be388* reported by the tokenizer.389* @constructor390* @private391*/392goog.proto2.TextFormatSerializer.Tokenizer_ = function(393data, opt_ignoreWhitespace, opt_ignoreComments) {394395/**396* Whether to skip whitespace tokens on output.397* @private {boolean}398*/399this.ignoreWhitespace_ = !!opt_ignoreWhitespace;400401/**402* Whether to skip comment tokens on output.403* @private {boolean}404*/405this.ignoreComments_ = !!opt_ignoreComments;406407/**408* The data being tokenized.409* @private {string}410*/411this.data_ = data;412413/**414* The current index in the data.415* @private {number}416*/417this.index_ = 0;418419/**420* The data string starting at the current index.421* @private {string}422*/423this.currentData_ = data;424425/**426* The current token type.427* @private {goog.proto2.TextFormatSerializer.Tokenizer_.Token}428*/429this.current_ = {430type: goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes.END,431value: null432};433};434435436/**437* @typedef {{type: goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes,438* value: ?string}}439*/440goog.proto2.TextFormatSerializer.Tokenizer_.Token;441442443/**444* @return {goog.proto2.TextFormatSerializer.Tokenizer_.Token} The current445* token.446*/447goog.proto2.TextFormatSerializer.Tokenizer_.prototype.getCurrent = function() {448return this.current_;449};450451452/**453* An enumeration of all the token types.454* @enum {!RegExp}455*/456goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes = {457END: /---end---/,458// Leading "-" to identify "-infinity"."459IDENTIFIER: /^-?[a-zA-Z][a-zA-Z0-9_]*/,460NUMBER: /^(0x[0-9a-f]+)|(([-])?[0-9][0-9]*(\.?[0-9]+)?(e[+-]?[0-9]+|[f])?)/,461COMMENT: /^#.*/,462OPEN_BRACE: /^{/,463CLOSE_BRACE: /^}/,464OPEN_TAG: /^</,465CLOSE_TAG: /^>/,466OPEN_LIST: /^\[/,467CLOSE_LIST: /^\]/,468STRING: new RegExp('^"([^"\\\\]|\\\\.)*"'),469COLON: /^:/,470COMMA: /^,/,471SEMI: /^;/,472WHITESPACE: /^\s/473};474475476/**477* Advances to the next token.478* @return {boolean} True if a valid token was found, false if the end was479* reached or no valid token was found.480*/481goog.proto2.TextFormatSerializer.Tokenizer_.prototype.next = function() {482var types = goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes;483484// Skip any whitespace if requested.485while (this.nextInternal_()) {486var type = this.getCurrent().type;487if ((type != types.WHITESPACE && type != types.COMMENT) ||488(type == types.WHITESPACE && !this.ignoreWhitespace_) ||489(type == types.COMMENT && !this.ignoreComments_)) {490return true;491}492}493494// If we reach this point, set the current token to END.495this.current_ = {496type: goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes.END,497value: null498};499500return false;501};502503504/**505* Internal method for determining the next token.506* @return {boolean} True if a next token was found, false otherwise.507* @private508*/509goog.proto2.TextFormatSerializer.Tokenizer_.prototype.nextInternal_ =510function() {511if (this.index_ >= this.data_.length) {512return false;513}514515var data = this.currentData_;516var types = goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes;517var next = null;518519// Loop through each token type and try to match the beginning of the string520// with the token's regular expression.521goog.object.some(types, function(type, id) {522if (next || type == types.END) {523return false;524}525526// Note: This regular expression check is at, minimum, O(n).527var info = type.exec(data);528if (info && info.index == 0) {529next = {type: type, value: info[0]};530}531532return !!next;533});534535// Advance the index by the length of the token.536if (next) {537this.current_ =538/** @type {goog.proto2.TextFormatSerializer.Tokenizer_.Token} */ (next);539this.index_ += next.value.length;540this.currentData_ = this.currentData_.substring(next.value.length);541}542543return !!next;544};545546547////////////////////////////////////////////////////////////////////////////////548549550551/**552* Helper class for parsing the text format.553* @constructor554* @final555*/556goog.proto2.TextFormatSerializer.Parser = function() {557/**558* The error during parsing, if any.559* @type {?string}560* @private561*/562this.error_ = null;563564/**565* The current tokenizer.566* @type {goog.proto2.TextFormatSerializer.Tokenizer_}567* @private568*/569this.tokenizer_ = null;570571/**572* Whether to ignore missing fields in the proto when parsing.573* @type {boolean}574* @private575*/576this.ignoreMissingFields_ = false;577};578579580/**581* Parses the given data, filling the message as it goes.582* @param {goog.proto2.Message} message The message to fill.583* @param {string} data The text format data.584* @param {boolean=} opt_ignoreMissingFields If true, fields missing in the585* proto will be ignored.586* @return {boolean} True on success, false on failure. On failure, the587* getError method can be called to get the reason for failure.588*/589goog.proto2.TextFormatSerializer.Parser.prototype.parse = function(590message, data, opt_ignoreMissingFields) {591this.error_ = null;592this.ignoreMissingFields_ = !!opt_ignoreMissingFields;593this.tokenizer_ =594new goog.proto2.TextFormatSerializer.Tokenizer_(data, true, true);595this.tokenizer_.next();596return this.consumeMessage_(message, '');597};598599600/**601* @return {?string} The parse error, if any.602*/603goog.proto2.TextFormatSerializer.Parser.prototype.getError = function() {604return this.error_;605};606607608/**609* Reports a parse error.610* @param {string} msg The error message.611* @private612*/613goog.proto2.TextFormatSerializer.Parser.prototype.reportError_ = function(msg) {614this.error_ = msg;615};616617618/**619* Attempts to consume the given message.620* @param {goog.proto2.Message} message The message to consume and fill. If621* null, then the message contents will be consumed without ever being set622* to anything.623* @param {string} delimiter The delimiter expected at the end of the message.624* @return {boolean} True on success, false otherwise.625* @private626*/627goog.proto2.TextFormatSerializer.Parser.prototype.consumeMessage_ = function(628message, delimiter) {629var types = goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes;630while (!this.lookingAt_('>') && !this.lookingAt_('}') &&631!this.lookingAtType_(types.END)) {632if (!this.consumeField_(message)) {633return false;634}635}636637if (delimiter) {638if (!this.consume_(delimiter)) {639return false;640}641} else {642if (!this.lookingAtType_(types.END)) {643this.reportError_('Expected END token');644}645}646647return true;648};649650651/**652* Attempts to consume the value of the given field.653* @param {goog.proto2.Message} message The parent message.654* @param {goog.proto2.FieldDescriptor} field The field.655* @return {boolean} True on success, false otherwise.656* @private657*/658goog.proto2.TextFormatSerializer.Parser.prototype.consumeFieldValue_ = function(659message, field) {660var value = this.getFieldValue_(field);661if (goog.isNull(value)) {662return false;663}664665if (field.isRepeated()) {666message.add(field, value);667} else {668message.set(field, value);669}670671return true;672};673674675/**676* Attempts to convert a string to a number.677* @param {string} num in hexadecimal or float format.678* @return {number} The converted number or null on error.679* @private680*/681goog.proto2.TextFormatSerializer.Parser.getNumberFromString_ = function(num) {682683var returnValue = goog.string.contains(num, '.') ?684parseFloat(num) : // num is a float.685goog.string.parseInt(num); // num is an int.686687goog.asserts.assert(!isNaN(returnValue));688goog.asserts.assert(isFinite(returnValue));689690return returnValue;691};692693694/**695* Parse NaN, positive infinity, or negative infinity from a string.696* @param {string} identifier An identifier string to check.697* @return {?number} Infinity, negative infinity, NaN, or null if none698* of the constants could be parsed.699* @private700*/701goog.proto2.TextFormatSerializer.Parser.parseNumericalConstant_ = function(702identifier) {703if (/^-?inf(?:inity)?f?$/i.test(identifier)) {704return Infinity * (goog.string.startsWith(identifier, '-') ? -1 : 1);705}706707if (/^nanf?$/i.test(identifier)) {708return NaN;709}710711return null;712};713714715/**716* Attempts to parse the given field's value from the stream.717* @param {goog.proto2.FieldDescriptor} field The field.718* @return {*} The field's value or null if none.719* @private720*/721goog.proto2.TextFormatSerializer.Parser.prototype.getFieldValue_ = function(722field) {723var types = goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes;724switch (field.getFieldType()) {725case goog.proto2.FieldDescriptor.FieldType.DOUBLE:726case goog.proto2.FieldDescriptor.FieldType.FLOAT:727728var identifier = this.consumeIdentifier_();729if (identifier) {730var numericalIdentifier =731goog.proto2.TextFormatSerializer.Parser.parseNumericalConstant_(732identifier);733// Use isDefAndNotNull since !!NaN is false.734if (goog.isDefAndNotNull(numericalIdentifier)) {735return numericalIdentifier;736}737}738739case goog.proto2.FieldDescriptor.FieldType.INT32:740case goog.proto2.FieldDescriptor.FieldType.UINT32:741case goog.proto2.FieldDescriptor.FieldType.FIXED32:742case goog.proto2.FieldDescriptor.FieldType.SFIXED32:743case goog.proto2.FieldDescriptor.FieldType.SINT32:744var num = this.consumeNumber_();745if (!num) {746return null;747}748749return goog.proto2.TextFormatSerializer.Parser.getNumberFromString_(num);750751case goog.proto2.FieldDescriptor.FieldType.INT64:752case goog.proto2.FieldDescriptor.FieldType.UINT64:753case goog.proto2.FieldDescriptor.FieldType.FIXED64:754case goog.proto2.FieldDescriptor.FieldType.SFIXED64:755case goog.proto2.FieldDescriptor.FieldType.SINT64:756var num = this.consumeNumber_();757if (!num) {758return null;759}760761if (field.getNativeType() == Number) {762// 64-bit number stored as a number.763return goog.proto2.TextFormatSerializer.Parser.getNumberFromString_(764num);765}766767return num; // 64-bit numbers are by default stored as strings.768769case goog.proto2.FieldDescriptor.FieldType.BOOL:770var ident = this.consumeIdentifier_();771if (!ident) {772return null;773}774775switch (ident) {776case 'true':777return true;778case 'false':779return false;780default:781this.reportError_('Unknown type for bool: ' + ident);782return null;783}784785case goog.proto2.FieldDescriptor.FieldType.ENUM:786if (this.lookingAtType_(types.NUMBER)) {787var num = this.consumeNumber_();788if (!num) {789return null;790}791792return goog.proto2.TextFormatSerializer.Parser.getNumberFromString_(793num);794} else {795// Search the enum type for a matching key.796var name = this.consumeIdentifier_();797if (!name) {798return null;799}800801var enumValue = field.getNativeType()[name];802if (enumValue == null) {803this.reportError_('Unknown enum value: ' + name);804return null;805}806807return enumValue;808}809810case goog.proto2.FieldDescriptor.FieldType.BYTES:811case goog.proto2.FieldDescriptor.FieldType.STRING:812return this.consumeString_();813}814};815816817/**818* Attempts to consume a nested message.819* @param {goog.proto2.Message} message The parent message.820* @param {goog.proto2.FieldDescriptor} field The field.821* @return {boolean} True on success, false otherwise.822* @private823*/824goog.proto2.TextFormatSerializer.Parser.prototype.consumeNestedMessage_ =825function(message, field) {826var delimiter = '';827828// Messages support both < > and { } as delimiters for legacy reasons.829if (this.tryConsume_('<')) {830delimiter = '>';831} else {832if (!this.consume_('{')) {833return false;834}835delimiter = '}';836}837838var msg = field.getFieldMessageType().createMessageInstance();839var result = this.consumeMessage_(msg, delimiter);840if (!result) {841return false;842}843844// Add the message to the parent message.845if (field.isRepeated()) {846message.add(field, msg);847} else {848message.set(field, msg);849}850851return true;852};853854855/**856* Attempts to consume the value of an unknown field. This method uses857* heuristics to try to consume just the right tokens.858* @return {boolean} True on success, false otherwise.859* @private860*/861goog.proto2.TextFormatSerializer.Parser.prototype.consumeUnknownFieldValue_ =862function() {863// : is optional.864this.tryConsume_(':');865866// Handle form: [.. , ... , ..]867if (this.tryConsume_('[')) {868while (true) {869this.tokenizer_.next();870if (this.tryConsume_(']')) {871break;872}873if (!this.consume_(',')) {874return false;875}876}877878return true;879}880881// Handle nested messages/groups.882if (this.tryConsume_('<')) {883return this.consumeMessage_(null /* unknown */, '>');884} else if (this.tryConsume_('{')) {885return this.consumeMessage_(null /* unknown */, '}');886} else {887// Otherwise, consume a single token for the field value.888this.tokenizer_.next();889}890891return true;892};893894895/**896* Attempts to consume a field under a message.897* @param {goog.proto2.Message} message The parent message. If null, then the898* field value will be consumed without being assigned to anything.899* @return {boolean} True on success, false otherwise.900* @private901*/902goog.proto2.TextFormatSerializer.Parser.prototype.consumeField_ = function(903message) {904var fieldName = this.consumeIdentifier_();905if (!fieldName) {906this.reportError_('Missing field name');907return false;908}909910var field = null;911if (message) {912field = message.getDescriptor().findFieldByName(fieldName.toString());913}914915if (field == null) {916if (this.ignoreMissingFields_) {917return this.consumeUnknownFieldValue_();918} else {919this.reportError_('Unknown field: ' + fieldName);920return false;921}922}923924if (field.getFieldType() == goog.proto2.FieldDescriptor.FieldType.MESSAGE ||925field.getFieldType() == goog.proto2.FieldDescriptor.FieldType.GROUP) {926// : is optional here.927this.tryConsume_(':');928if (!this.consumeNestedMessage_(message, field)) {929return false;930}931} else {932// Long Format: "someField: 123"933// Short Format: "someField: [123, 456, 789]"934if (!this.consume_(':')) {935return false;936}937938if (field.isRepeated() && this.tryConsume_('[')) {939// Short repeated format, e.g. "foo: [1, 2, 3]"940while (true) {941if (!this.consumeFieldValue_(message, field)) {942return false;943}944if (this.tryConsume_(']')) {945break;946}947if (!this.consume_(',')) {948return false;949}950}951} else {952// Normal field format.953if (!this.consumeFieldValue_(message, field)) {954return false;955}956}957}958959// For historical reasons, fields may optionally be separated by commas or960// semicolons.961this.tryConsume_(',') || this.tryConsume_(';');962return true;963};964965966/**967* Attempts to consume a token with the given string value.968* @param {string} value The string value for the token.969* @return {boolean} True if the token matches and was consumed, false970* otherwise.971* @private972*/973goog.proto2.TextFormatSerializer.Parser.prototype.tryConsume_ = function(974value) {975if (this.lookingAt_(value)) {976this.tokenizer_.next();977return true;978}979return false;980};981982983/**984* Consumes a token of the given type.985* @param {goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes} type The type986* of the token to consume.987* @return {?string} The string value of the token or null on error.988* @private989*/990goog.proto2.TextFormatSerializer.Parser.prototype.consumeToken_ = function(991type) {992if (!this.lookingAtType_(type)) {993this.reportError_('Expected token type: ' + type);994return null;995}996997var value = this.tokenizer_.getCurrent().value;998this.tokenizer_.next();999return value;1000};100110021003/**1004* Consumes an IDENTIFIER token.1005* @return {?string} The string value or null on error.1006* @private1007*/1008goog.proto2.TextFormatSerializer.Parser.prototype.consumeIdentifier_ =1009function() {1010var types = goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes;1011return this.consumeToken_(types.IDENTIFIER);1012};101310141015/**1016* Consumes a NUMBER token.1017* @return {?string} The string value or null on error.1018* @private1019*/1020goog.proto2.TextFormatSerializer.Parser.prototype.consumeNumber_ = function() {1021var types = goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes;1022return this.consumeToken_(types.NUMBER);1023};102410251026/**1027* Consumes a STRING token. Strings may come in multiple adjacent tokens which1028* are automatically concatenated, like in C or Python.1029* @return {?string} The *deescaped* string value or null on error.1030* @private1031*/1032goog.proto2.TextFormatSerializer.Parser.prototype.consumeString_ = function() {1033var types = goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes;1034var value = this.consumeToken_(types.STRING);1035if (!value) {1036return null;1037}10381039var stringValue = goog.json.parse(value).toString();1040while (this.lookingAtType_(types.STRING)) {1041value = this.consumeToken_(types.STRING);1042stringValue += goog.json.parse(value).toString();1043}10441045return stringValue;1046};104710481049/**1050* Consumes a token with the given value. If not found, reports an error.1051* @param {string} value The string value expected for the token.1052* @return {boolean} True on success, false otherwise.1053* @private1054*/1055goog.proto2.TextFormatSerializer.Parser.prototype.consume_ = function(value) {1056if (!this.tryConsume_(value)) {1057this.reportError_('Expected token "' + value + '"');1058return false;1059}10601061return true;1062};106310641065/**1066* @param {string} value The value to check against.1067* @return {boolean} True if the current token has the given string value.1068* @private1069*/1070goog.proto2.TextFormatSerializer.Parser.prototype.lookingAt_ = function(value) {1071return this.tokenizer_.getCurrent().value == value;1072};107310741075/**1076* @param {goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes} type The1077* token type.1078* @return {boolean} True if the current token has the given type.1079* @private1080*/1081goog.proto2.TextFormatSerializer.Parser.prototype.lookingAtType_ = function(1082type) {1083return this.tokenizer_.getCurrent().type == type;1084};108510861087