textformatserializer.js 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085
  1. // Copyright 2011 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Protocol Buffer 2 Serializer which serializes messages
  16. * into a user-friendly text format. Note that this code can run a bit
  17. * slowly (especially for parsing) and should therefore not be used for
  18. * time or space-critical applications.
  19. *
  20. * @see http://goo.gl/QDmDr
  21. */
  22. goog.provide('goog.proto2.TextFormatSerializer');
  23. goog.require('goog.array');
  24. goog.require('goog.asserts');
  25. goog.require('goog.json');
  26. goog.require('goog.math');
  27. goog.require('goog.object');
  28. goog.require('goog.proto2.FieldDescriptor');
  29. goog.require('goog.proto2.Message');
  30. goog.require('goog.proto2.Serializer');
  31. goog.require('goog.string');
  32. /**
  33. * TextFormatSerializer, a serializer which turns Messages into the human
  34. * readable text format.
  35. * @param {boolean=} opt_ignoreMissingFields If true, then fields that cannot be
  36. * found on the proto when parsing the text format will be ignored.
  37. * @param {boolean=} opt_useEnumValues If true, serialization code for enums
  38. * will use enum integer values instead of human-readable symbolic names.
  39. * @constructor
  40. * @extends {goog.proto2.Serializer}
  41. * @final
  42. */
  43. goog.proto2.TextFormatSerializer = function(
  44. opt_ignoreMissingFields, opt_useEnumValues) {
  45. /**
  46. * Whether to ignore fields not defined on the proto when parsing the text
  47. * format.
  48. * @type {boolean}
  49. * @private
  50. */
  51. this.ignoreMissingFields_ = !!opt_ignoreMissingFields;
  52. /**
  53. * Whether to use integer enum values during enum serialization.
  54. * If false, symbolic names will be used.
  55. * @type {boolean}
  56. * @private
  57. */
  58. this.useEnumValues_ = !!opt_useEnumValues;
  59. };
  60. goog.inherits(goog.proto2.TextFormatSerializer, goog.proto2.Serializer);
  61. /**
  62. * Deserializes a message from text format and places the data in the message.
  63. * @param {goog.proto2.Message} message The message in which to
  64. * place the information.
  65. * @param {*} data The text format data.
  66. * @return {?string} The parse error or null on success.
  67. * @override
  68. */
  69. goog.proto2.TextFormatSerializer.prototype.deserializeTo = function(
  70. message, data) {
  71. var textData = data.toString();
  72. var parser = new goog.proto2.TextFormatSerializer.Parser();
  73. if (!parser.parse(message, textData, this.ignoreMissingFields_)) {
  74. return parser.getError();
  75. }
  76. return null;
  77. };
  78. /**
  79. * Serializes a message to a string.
  80. * @param {goog.proto2.Message} message The message to be serialized.
  81. * @return {string} The serialized form of the message.
  82. * @override
  83. */
  84. goog.proto2.TextFormatSerializer.prototype.serialize = function(message) {
  85. var printer = new goog.proto2.TextFormatSerializer.Printer_();
  86. this.serializeMessage_(message, printer);
  87. return printer.toString();
  88. };
  89. /**
  90. * Serializes the message and prints the text form into the given printer.
  91. * @param {goog.proto2.Message} message The message to serialize.
  92. * @param {goog.proto2.TextFormatSerializer.Printer_} printer The printer to
  93. * which the text format will be printed.
  94. * @private
  95. */
  96. goog.proto2.TextFormatSerializer.prototype.serializeMessage_ = function(
  97. message, printer) {
  98. var descriptor = message.getDescriptor();
  99. var fields = descriptor.getFields();
  100. // Add the defined fields, recursively.
  101. goog.array.forEach(fields, function(field) {
  102. this.printField_(message, field, printer);
  103. }, this);
  104. // Add the unknown fields, if any.
  105. message.forEachUnknown(function(tag, value) {
  106. this.serializeUnknown_(tag, value, goog.asserts.assert(printer));
  107. }, this);
  108. };
  109. /**
  110. * Serializes an unknown field. When parsed from the JsPb object format, this
  111. * manifests as either a primitive type, an array, or a raw object with integer
  112. * keys. There is no descriptor available to interpret the types of nested
  113. * messages.
  114. * @param {number} tag The tag for the field. Since it's unknown, this is a
  115. * number rather than a string.
  116. * @param {*} value The value of the field.
  117. * @param {!goog.proto2.TextFormatSerializer.Printer_} printer The printer to
  118. * which the text format will be serialized.
  119. * @private
  120. */
  121. goog.proto2.TextFormatSerializer.prototype.serializeUnknown_ = function(
  122. tag, value, printer) {
  123. if (!goog.isDefAndNotNull(value)) {
  124. return;
  125. }
  126. if (goog.isArray(value)) {
  127. goog.array.forEach(value, function(val) {
  128. this.serializeUnknown_(tag, val, printer);
  129. }, this);
  130. return;
  131. }
  132. if (goog.isObject(value)) {
  133. printer.append(tag);
  134. printer.append(' {');
  135. printer.appendLine();
  136. printer.indent();
  137. if (value instanceof goog.proto2.Message) {
  138. // Note(user): This conditional is here to make the
  139. // testSerializationOfUnknown unit test pass, but in practice we should
  140. // never have a Message for an "unknown" field.
  141. this.serializeMessage_(value, printer);
  142. } else {
  143. // For an unknown message, fields are keyed by positive integers. We
  144. // don't have a 'length' property to use for enumeration, so go through
  145. // all properties and ignore the ones that aren't legal keys.
  146. for (var key in value) {
  147. var keyAsNumber = goog.string.parseInt(key);
  148. goog.asserts.assert(goog.math.isInt(keyAsNumber));
  149. this.serializeUnknown_(keyAsNumber, value[key], printer);
  150. }
  151. }
  152. printer.dedent();
  153. printer.append('}');
  154. printer.appendLine();
  155. return;
  156. }
  157. if (goog.isString(value)) {
  158. value = goog.string.quote(value);
  159. }
  160. printer.append(tag);
  161. printer.append(': ');
  162. printer.append(value.toString());
  163. printer.appendLine();
  164. };
  165. /**
  166. * Prints the serialized value for the given field to the printer.
  167. * @param {*} value The field's value.
  168. * @param {goog.proto2.FieldDescriptor} field The field whose value is being
  169. * printed.
  170. * @param {goog.proto2.TextFormatSerializer.Printer_} printer The printer to
  171. * which the value will be printed.
  172. * @private
  173. */
  174. goog.proto2.TextFormatSerializer.prototype.printFieldValue_ = function(
  175. value, field, printer) {
  176. switch (field.getFieldType()) {
  177. case goog.proto2.FieldDescriptor.FieldType.DOUBLE:
  178. case goog.proto2.FieldDescriptor.FieldType.FLOAT:
  179. case goog.proto2.FieldDescriptor.FieldType.INT64:
  180. case goog.proto2.FieldDescriptor.FieldType.UINT64:
  181. case goog.proto2.FieldDescriptor.FieldType.INT32:
  182. case goog.proto2.FieldDescriptor.FieldType.UINT32:
  183. case goog.proto2.FieldDescriptor.FieldType.FIXED64:
  184. case goog.proto2.FieldDescriptor.FieldType.FIXED32:
  185. case goog.proto2.FieldDescriptor.FieldType.BOOL:
  186. case goog.proto2.FieldDescriptor.FieldType.SFIXED32:
  187. case goog.proto2.FieldDescriptor.FieldType.SFIXED64:
  188. case goog.proto2.FieldDescriptor.FieldType.SINT32:
  189. case goog.proto2.FieldDescriptor.FieldType.SINT64:
  190. printer.append(value);
  191. break;
  192. case goog.proto2.FieldDescriptor.FieldType.BYTES:
  193. case goog.proto2.FieldDescriptor.FieldType.STRING:
  194. value = goog.string.quote(value.toString());
  195. printer.append(value);
  196. break;
  197. case goog.proto2.FieldDescriptor.FieldType.ENUM:
  198. if (!this.useEnumValues_) {
  199. // Search the enum type for a matching key.
  200. var found = false;
  201. goog.object.forEach(field.getNativeType(), function(eValue, key) {
  202. if (!found && eValue == value) {
  203. printer.append(key);
  204. found = true;
  205. }
  206. });
  207. }
  208. if (!found || this.useEnumValues_) {
  209. // Otherwise, just print the numeric value.
  210. printer.append(value.toString());
  211. }
  212. break;
  213. case goog.proto2.FieldDescriptor.FieldType.GROUP:
  214. case goog.proto2.FieldDescriptor.FieldType.MESSAGE:
  215. this.serializeMessage_(
  216. /** @type {goog.proto2.Message} */ (value), printer);
  217. break;
  218. }
  219. };
  220. /**
  221. * Prints the serialized field to the printer.
  222. * @param {goog.proto2.Message} message The parent message.
  223. * @param {goog.proto2.FieldDescriptor} field The field to print.
  224. * @param {goog.proto2.TextFormatSerializer.Printer_} printer The printer to
  225. * which the field will be printed.
  226. * @private
  227. */
  228. goog.proto2.TextFormatSerializer.prototype.printField_ = function(
  229. message, field, printer) {
  230. // Skip fields not present.
  231. if (!message.has(field)) {
  232. return;
  233. }
  234. var count = message.countOf(field);
  235. for (var i = 0; i < count; ++i) {
  236. // Field name.
  237. printer.append(field.getName());
  238. // Field delimiter.
  239. if (field.getFieldType() == goog.proto2.FieldDescriptor.FieldType.MESSAGE ||
  240. field.getFieldType() == goog.proto2.FieldDescriptor.FieldType.GROUP) {
  241. printer.append(' {');
  242. printer.appendLine();
  243. printer.indent();
  244. } else {
  245. printer.append(': ');
  246. }
  247. // Write the field value.
  248. this.printFieldValue_(message.get(field, i), field, printer);
  249. // Close the field.
  250. if (field.getFieldType() == goog.proto2.FieldDescriptor.FieldType.MESSAGE ||
  251. field.getFieldType() == goog.proto2.FieldDescriptor.FieldType.GROUP) {
  252. printer.dedent();
  253. printer.append('}');
  254. printer.appendLine();
  255. } else {
  256. printer.appendLine();
  257. }
  258. }
  259. };
  260. ////////////////////////////////////////////////////////////////////////////////
  261. /**
  262. * Helper class used by the text format serializer for pretty-printing text.
  263. * @constructor
  264. * @private
  265. */
  266. goog.proto2.TextFormatSerializer.Printer_ = function() {
  267. /**
  268. * The current indentation count.
  269. * @type {number}
  270. * @private
  271. */
  272. this.indentation_ = 0;
  273. /**
  274. * The buffer of string pieces.
  275. * @type {Array<string>}
  276. * @private
  277. */
  278. this.buffer_ = [];
  279. /**
  280. * Whether indentation is required before the next append of characters.
  281. * @type {boolean}
  282. * @private
  283. */
  284. this.requiresIndentation_ = true;
  285. };
  286. /**
  287. * @return {string} The contents of the printer.
  288. * @override
  289. */
  290. goog.proto2.TextFormatSerializer.Printer_.prototype.toString = function() {
  291. return this.buffer_.join('');
  292. };
  293. /**
  294. * Increases the indentation in the printer.
  295. */
  296. goog.proto2.TextFormatSerializer.Printer_.prototype.indent = function() {
  297. this.indentation_ += 2;
  298. };
  299. /**
  300. * Decreases the indentation in the printer.
  301. */
  302. goog.proto2.TextFormatSerializer.Printer_.prototype.dedent = function() {
  303. this.indentation_ -= 2;
  304. goog.asserts.assert(this.indentation_ >= 0);
  305. };
  306. /**
  307. * Appends the given value to the printer.
  308. * @param {*} value The value to append.
  309. */
  310. goog.proto2.TextFormatSerializer.Printer_.prototype.append = function(value) {
  311. if (this.requiresIndentation_) {
  312. for (var i = 0; i < this.indentation_; ++i) {
  313. this.buffer_.push(' ');
  314. }
  315. this.requiresIndentation_ = false;
  316. }
  317. this.buffer_.push(value.toString());
  318. };
  319. /**
  320. * Appends a newline to the printer.
  321. */
  322. goog.proto2.TextFormatSerializer.Printer_.prototype.appendLine = function() {
  323. this.buffer_.push('\n');
  324. this.requiresIndentation_ = true;
  325. };
  326. ////////////////////////////////////////////////////////////////////////////////
  327. /**
  328. * Helper class for tokenizing the text format.
  329. * @param {string} data The string data to tokenize.
  330. * @param {boolean=} opt_ignoreWhitespace If true, whitespace tokens will not
  331. * be reported by the tokenizer.
  332. * @param {boolean=} opt_ignoreComments If true, comment tokens will not be
  333. * reported by the tokenizer.
  334. * @constructor
  335. * @private
  336. */
  337. goog.proto2.TextFormatSerializer.Tokenizer_ = function(
  338. data, opt_ignoreWhitespace, opt_ignoreComments) {
  339. /**
  340. * Whether to skip whitespace tokens on output.
  341. * @private {boolean}
  342. */
  343. this.ignoreWhitespace_ = !!opt_ignoreWhitespace;
  344. /**
  345. * Whether to skip comment tokens on output.
  346. * @private {boolean}
  347. */
  348. this.ignoreComments_ = !!opt_ignoreComments;
  349. /**
  350. * The data being tokenized.
  351. * @private {string}
  352. */
  353. this.data_ = data;
  354. /**
  355. * The current index in the data.
  356. * @private {number}
  357. */
  358. this.index_ = 0;
  359. /**
  360. * The data string starting at the current index.
  361. * @private {string}
  362. */
  363. this.currentData_ = data;
  364. /**
  365. * The current token type.
  366. * @private {goog.proto2.TextFormatSerializer.Tokenizer_.Token}
  367. */
  368. this.current_ = {
  369. type: goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes.END,
  370. value: null
  371. };
  372. };
  373. /**
  374. * @typedef {{type: goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes,
  375. * value: ?string}}
  376. */
  377. goog.proto2.TextFormatSerializer.Tokenizer_.Token;
  378. /**
  379. * @return {goog.proto2.TextFormatSerializer.Tokenizer_.Token} The current
  380. * token.
  381. */
  382. goog.proto2.TextFormatSerializer.Tokenizer_.prototype.getCurrent = function() {
  383. return this.current_;
  384. };
  385. /**
  386. * An enumeration of all the token types.
  387. * @enum {!RegExp}
  388. */
  389. goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes = {
  390. END: /---end---/,
  391. // Leading "-" to identify "-infinity"."
  392. IDENTIFIER: /^-?[a-zA-Z][a-zA-Z0-9_]*/,
  393. NUMBER: /^(0x[0-9a-f]+)|(([-])?[0-9][0-9]*(\.?[0-9]+)?(e[+-]?[0-9]+|[f])?)/,
  394. COMMENT: /^#.*/,
  395. OPEN_BRACE: /^{/,
  396. CLOSE_BRACE: /^}/,
  397. OPEN_TAG: /^</,
  398. CLOSE_TAG: /^>/,
  399. OPEN_LIST: /^\[/,
  400. CLOSE_LIST: /^\]/,
  401. STRING: new RegExp('^"([^"\\\\]|\\\\.)*"'),
  402. COLON: /^:/,
  403. COMMA: /^,/,
  404. SEMI: /^;/,
  405. WHITESPACE: /^\s/
  406. };
  407. /**
  408. * Advances to the next token.
  409. * @return {boolean} True if a valid token was found, false if the end was
  410. * reached or no valid token was found.
  411. */
  412. goog.proto2.TextFormatSerializer.Tokenizer_.prototype.next = function() {
  413. var types = goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes;
  414. // Skip any whitespace if requested.
  415. while (this.nextInternal_()) {
  416. var type = this.getCurrent().type;
  417. if ((type != types.WHITESPACE && type != types.COMMENT) ||
  418. (type == types.WHITESPACE && !this.ignoreWhitespace_) ||
  419. (type == types.COMMENT && !this.ignoreComments_)) {
  420. return true;
  421. }
  422. }
  423. // If we reach this point, set the current token to END.
  424. this.current_ = {
  425. type: goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes.END,
  426. value: null
  427. };
  428. return false;
  429. };
  430. /**
  431. * Internal method for determining the next token.
  432. * @return {boolean} True if a next token was found, false otherwise.
  433. * @private
  434. */
  435. goog.proto2.TextFormatSerializer.Tokenizer_.prototype.nextInternal_ =
  436. function() {
  437. if (this.index_ >= this.data_.length) {
  438. return false;
  439. }
  440. var data = this.currentData_;
  441. var types = goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes;
  442. var next = null;
  443. // Loop through each token type and try to match the beginning of the string
  444. // with the token's regular expression.
  445. goog.object.some(types, function(type, id) {
  446. if (next || type == types.END) {
  447. return false;
  448. }
  449. // Note: This regular expression check is at, minimum, O(n).
  450. var info = type.exec(data);
  451. if (info && info.index == 0) {
  452. next = {type: type, value: info[0]};
  453. }
  454. return !!next;
  455. });
  456. // Advance the index by the length of the token.
  457. if (next) {
  458. this.current_ =
  459. /** @type {goog.proto2.TextFormatSerializer.Tokenizer_.Token} */ (next);
  460. this.index_ += next.value.length;
  461. this.currentData_ = this.currentData_.substring(next.value.length);
  462. }
  463. return !!next;
  464. };
  465. ////////////////////////////////////////////////////////////////////////////////
  466. /**
  467. * Helper class for parsing the text format.
  468. * @constructor
  469. * @final
  470. */
  471. goog.proto2.TextFormatSerializer.Parser = function() {
  472. /**
  473. * The error during parsing, if any.
  474. * @type {?string}
  475. * @private
  476. */
  477. this.error_ = null;
  478. /**
  479. * The current tokenizer.
  480. * @type {goog.proto2.TextFormatSerializer.Tokenizer_}
  481. * @private
  482. */
  483. this.tokenizer_ = null;
  484. /**
  485. * Whether to ignore missing fields in the proto when parsing.
  486. * @type {boolean}
  487. * @private
  488. */
  489. this.ignoreMissingFields_ = false;
  490. };
  491. /**
  492. * Parses the given data, filling the message as it goes.
  493. * @param {goog.proto2.Message} message The message to fill.
  494. * @param {string} data The text format data.
  495. * @param {boolean=} opt_ignoreMissingFields If true, fields missing in the
  496. * proto will be ignored.
  497. * @return {boolean} True on success, false on failure. On failure, the
  498. * getError method can be called to get the reason for failure.
  499. */
  500. goog.proto2.TextFormatSerializer.Parser.prototype.parse = function(
  501. message, data, opt_ignoreMissingFields) {
  502. this.error_ = null;
  503. this.ignoreMissingFields_ = !!opt_ignoreMissingFields;
  504. this.tokenizer_ =
  505. new goog.proto2.TextFormatSerializer.Tokenizer_(data, true, true);
  506. this.tokenizer_.next();
  507. return this.consumeMessage_(message, '');
  508. };
  509. /**
  510. * @return {?string} The parse error, if any.
  511. */
  512. goog.proto2.TextFormatSerializer.Parser.prototype.getError = function() {
  513. return this.error_;
  514. };
  515. /**
  516. * Reports a parse error.
  517. * @param {string} msg The error message.
  518. * @private
  519. */
  520. goog.proto2.TextFormatSerializer.Parser.prototype.reportError_ = function(msg) {
  521. this.error_ = msg;
  522. };
  523. /**
  524. * Attempts to consume the given message.
  525. * @param {goog.proto2.Message} message The message to consume and fill. If
  526. * null, then the message contents will be consumed without ever being set
  527. * to anything.
  528. * @param {string} delimiter The delimiter expected at the end of the message.
  529. * @return {boolean} True on success, false otherwise.
  530. * @private
  531. */
  532. goog.proto2.TextFormatSerializer.Parser.prototype.consumeMessage_ = function(
  533. message, delimiter) {
  534. var types = goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes;
  535. while (!this.lookingAt_('>') && !this.lookingAt_('}') &&
  536. !this.lookingAtType_(types.END)) {
  537. if (!this.consumeField_(message)) {
  538. return false;
  539. }
  540. }
  541. if (delimiter) {
  542. if (!this.consume_(delimiter)) {
  543. return false;
  544. }
  545. } else {
  546. if (!this.lookingAtType_(types.END)) {
  547. this.reportError_('Expected END token');
  548. }
  549. }
  550. return true;
  551. };
  552. /**
  553. * Attempts to consume the value of the given field.
  554. * @param {goog.proto2.Message} message The parent message.
  555. * @param {goog.proto2.FieldDescriptor} field The field.
  556. * @return {boolean} True on success, false otherwise.
  557. * @private
  558. */
  559. goog.proto2.TextFormatSerializer.Parser.prototype.consumeFieldValue_ = function(
  560. message, field) {
  561. var value = this.getFieldValue_(field);
  562. if (goog.isNull(value)) {
  563. return false;
  564. }
  565. if (field.isRepeated()) {
  566. message.add(field, value);
  567. } else {
  568. message.set(field, value);
  569. }
  570. return true;
  571. };
  572. /**
  573. * Attempts to convert a string to a number.
  574. * @param {string} num in hexadecimal or float format.
  575. * @return {number} The converted number or null on error.
  576. * @private
  577. */
  578. goog.proto2.TextFormatSerializer.Parser.getNumberFromString_ = function(num) {
  579. var returnValue = goog.string.contains(num, '.') ?
  580. parseFloat(num) : // num is a float.
  581. goog.string.parseInt(num); // num is an int.
  582. goog.asserts.assert(!isNaN(returnValue));
  583. goog.asserts.assert(isFinite(returnValue));
  584. return returnValue;
  585. };
  586. /**
  587. * Parse NaN, positive infinity, or negative infinity from a string.
  588. * @param {string} identifier An identifier string to check.
  589. * @return {?number} Infinity, negative infinity, NaN, or null if none
  590. * of the constants could be parsed.
  591. * @private
  592. */
  593. goog.proto2.TextFormatSerializer.Parser.parseNumericalConstant_ = function(
  594. identifier) {
  595. if (/^-?inf(?:inity)?f?$/i.test(identifier)) {
  596. return Infinity * (goog.string.startsWith(identifier, '-') ? -1 : 1);
  597. }
  598. if (/^nanf?$/i.test(identifier)) {
  599. return NaN;
  600. }
  601. return null;
  602. };
  603. /**
  604. * Attempts to parse the given field's value from the stream.
  605. * @param {goog.proto2.FieldDescriptor} field The field.
  606. * @return {*} The field's value or null if none.
  607. * @private
  608. */
  609. goog.proto2.TextFormatSerializer.Parser.prototype.getFieldValue_ = function(
  610. field) {
  611. var types = goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes;
  612. switch (field.getFieldType()) {
  613. case goog.proto2.FieldDescriptor.FieldType.DOUBLE:
  614. case goog.proto2.FieldDescriptor.FieldType.FLOAT:
  615. var identifier = this.consumeIdentifier_();
  616. if (identifier) {
  617. var numericalIdentifier =
  618. goog.proto2.TextFormatSerializer.Parser.parseNumericalConstant_(
  619. identifier);
  620. // Use isDefAndNotNull since !!NaN is false.
  621. if (goog.isDefAndNotNull(numericalIdentifier)) {
  622. return numericalIdentifier;
  623. }
  624. }
  625. case goog.proto2.FieldDescriptor.FieldType.INT32:
  626. case goog.proto2.FieldDescriptor.FieldType.UINT32:
  627. case goog.proto2.FieldDescriptor.FieldType.FIXED32:
  628. case goog.proto2.FieldDescriptor.FieldType.SFIXED32:
  629. case goog.proto2.FieldDescriptor.FieldType.SINT32:
  630. var num = this.consumeNumber_();
  631. if (!num) {
  632. return null;
  633. }
  634. return goog.proto2.TextFormatSerializer.Parser.getNumberFromString_(num);
  635. case goog.proto2.FieldDescriptor.FieldType.INT64:
  636. case goog.proto2.FieldDescriptor.FieldType.UINT64:
  637. case goog.proto2.FieldDescriptor.FieldType.FIXED64:
  638. case goog.proto2.FieldDescriptor.FieldType.SFIXED64:
  639. case goog.proto2.FieldDescriptor.FieldType.SINT64:
  640. var num = this.consumeNumber_();
  641. if (!num) {
  642. return null;
  643. }
  644. if (field.getNativeType() == Number) {
  645. // 64-bit number stored as a number.
  646. return goog.proto2.TextFormatSerializer.Parser.getNumberFromString_(
  647. num);
  648. }
  649. return num; // 64-bit numbers are by default stored as strings.
  650. case goog.proto2.FieldDescriptor.FieldType.BOOL:
  651. var ident = this.consumeIdentifier_();
  652. if (!ident) {
  653. return null;
  654. }
  655. switch (ident) {
  656. case 'true':
  657. return true;
  658. case 'false':
  659. return false;
  660. default:
  661. this.reportError_('Unknown type for bool: ' + ident);
  662. return null;
  663. }
  664. case goog.proto2.FieldDescriptor.FieldType.ENUM:
  665. if (this.lookingAtType_(types.NUMBER)) {
  666. var num = this.consumeNumber_();
  667. if (!num) {
  668. return null;
  669. }
  670. return goog.proto2.TextFormatSerializer.Parser.getNumberFromString_(
  671. num);
  672. } else {
  673. // Search the enum type for a matching key.
  674. var name = this.consumeIdentifier_();
  675. if (!name) {
  676. return null;
  677. }
  678. var enumValue = field.getNativeType()[name];
  679. if (enumValue == null) {
  680. this.reportError_('Unknown enum value: ' + name);
  681. return null;
  682. }
  683. return enumValue;
  684. }
  685. case goog.proto2.FieldDescriptor.FieldType.BYTES:
  686. case goog.proto2.FieldDescriptor.FieldType.STRING:
  687. return this.consumeString_();
  688. }
  689. };
  690. /**
  691. * Attempts to consume a nested message.
  692. * @param {goog.proto2.Message} message The parent message.
  693. * @param {goog.proto2.FieldDescriptor} field The field.
  694. * @return {boolean} True on success, false otherwise.
  695. * @private
  696. */
  697. goog.proto2.TextFormatSerializer.Parser.prototype.consumeNestedMessage_ =
  698. function(message, field) {
  699. var delimiter = '';
  700. // Messages support both < > and { } as delimiters for legacy reasons.
  701. if (this.tryConsume_('<')) {
  702. delimiter = '>';
  703. } else {
  704. if (!this.consume_('{')) {
  705. return false;
  706. }
  707. delimiter = '}';
  708. }
  709. var msg = field.getFieldMessageType().createMessageInstance();
  710. var result = this.consumeMessage_(msg, delimiter);
  711. if (!result) {
  712. return false;
  713. }
  714. // Add the message to the parent message.
  715. if (field.isRepeated()) {
  716. message.add(field, msg);
  717. } else {
  718. message.set(field, msg);
  719. }
  720. return true;
  721. };
  722. /**
  723. * Attempts to consume the value of an unknown field. This method uses
  724. * heuristics to try to consume just the right tokens.
  725. * @return {boolean} True on success, false otherwise.
  726. * @private
  727. */
  728. goog.proto2.TextFormatSerializer.Parser.prototype.consumeUnknownFieldValue_ =
  729. function() {
  730. // : is optional.
  731. this.tryConsume_(':');
  732. // Handle form: [.. , ... , ..]
  733. if (this.tryConsume_('[')) {
  734. while (true) {
  735. this.tokenizer_.next();
  736. if (this.tryConsume_(']')) {
  737. break;
  738. }
  739. if (!this.consume_(',')) {
  740. return false;
  741. }
  742. }
  743. return true;
  744. }
  745. // Handle nested messages/groups.
  746. if (this.tryConsume_('<')) {
  747. return this.consumeMessage_(null /* unknown */, '>');
  748. } else if (this.tryConsume_('{')) {
  749. return this.consumeMessage_(null /* unknown */, '}');
  750. } else {
  751. // Otherwise, consume a single token for the field value.
  752. this.tokenizer_.next();
  753. }
  754. return true;
  755. };
  756. /**
  757. * Attempts to consume a field under a message.
  758. * @param {goog.proto2.Message} message The parent message. If null, then the
  759. * field value will be consumed without being assigned to anything.
  760. * @return {boolean} True on success, false otherwise.
  761. * @private
  762. */
  763. goog.proto2.TextFormatSerializer.Parser.prototype.consumeField_ = function(
  764. message) {
  765. var fieldName = this.consumeIdentifier_();
  766. if (!fieldName) {
  767. this.reportError_('Missing field name');
  768. return false;
  769. }
  770. var field = null;
  771. if (message) {
  772. field = message.getDescriptor().findFieldByName(fieldName.toString());
  773. }
  774. if (field == null) {
  775. if (this.ignoreMissingFields_) {
  776. return this.consumeUnknownFieldValue_();
  777. } else {
  778. this.reportError_('Unknown field: ' + fieldName);
  779. return false;
  780. }
  781. }
  782. if (field.getFieldType() == goog.proto2.FieldDescriptor.FieldType.MESSAGE ||
  783. field.getFieldType() == goog.proto2.FieldDescriptor.FieldType.GROUP) {
  784. // : is optional here.
  785. this.tryConsume_(':');
  786. if (!this.consumeNestedMessage_(message, field)) {
  787. return false;
  788. }
  789. } else {
  790. // Long Format: "someField: 123"
  791. // Short Format: "someField: [123, 456, 789]"
  792. if (!this.consume_(':')) {
  793. return false;
  794. }
  795. if (field.isRepeated() && this.tryConsume_('[')) {
  796. // Short repeated format, e.g. "foo: [1, 2, 3]"
  797. while (true) {
  798. if (!this.consumeFieldValue_(message, field)) {
  799. return false;
  800. }
  801. if (this.tryConsume_(']')) {
  802. break;
  803. }
  804. if (!this.consume_(',')) {
  805. return false;
  806. }
  807. }
  808. } else {
  809. // Normal field format.
  810. if (!this.consumeFieldValue_(message, field)) {
  811. return false;
  812. }
  813. }
  814. }
  815. // For historical reasons, fields may optionally be separated by commas or
  816. // semicolons.
  817. this.tryConsume_(',') || this.tryConsume_(';');
  818. return true;
  819. };
  820. /**
  821. * Attempts to consume a token with the given string value.
  822. * @param {string} value The string value for the token.
  823. * @return {boolean} True if the token matches and was consumed, false
  824. * otherwise.
  825. * @private
  826. */
  827. goog.proto2.TextFormatSerializer.Parser.prototype.tryConsume_ = function(
  828. value) {
  829. if (this.lookingAt_(value)) {
  830. this.tokenizer_.next();
  831. return true;
  832. }
  833. return false;
  834. };
  835. /**
  836. * Consumes a token of the given type.
  837. * @param {goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes} type The type
  838. * of the token to consume.
  839. * @return {?string} The string value of the token or null on error.
  840. * @private
  841. */
  842. goog.proto2.TextFormatSerializer.Parser.prototype.consumeToken_ = function(
  843. type) {
  844. if (!this.lookingAtType_(type)) {
  845. this.reportError_('Expected token type: ' + type);
  846. return null;
  847. }
  848. var value = this.tokenizer_.getCurrent().value;
  849. this.tokenizer_.next();
  850. return value;
  851. };
  852. /**
  853. * Consumes an IDENTIFIER token.
  854. * @return {?string} The string value or null on error.
  855. * @private
  856. */
  857. goog.proto2.TextFormatSerializer.Parser.prototype.consumeIdentifier_ =
  858. function() {
  859. var types = goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes;
  860. return this.consumeToken_(types.IDENTIFIER);
  861. };
  862. /**
  863. * Consumes a NUMBER token.
  864. * @return {?string} The string value or null on error.
  865. * @private
  866. */
  867. goog.proto2.TextFormatSerializer.Parser.prototype.consumeNumber_ = function() {
  868. var types = goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes;
  869. return this.consumeToken_(types.NUMBER);
  870. };
  871. /**
  872. * Consumes a STRING token. Strings may come in multiple adjacent tokens which
  873. * are automatically concatenated, like in C or Python.
  874. * @return {?string} The *deescaped* string value or null on error.
  875. * @private
  876. */
  877. goog.proto2.TextFormatSerializer.Parser.prototype.consumeString_ = function() {
  878. var types = goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes;
  879. var value = this.consumeToken_(types.STRING);
  880. if (!value) {
  881. return null;
  882. }
  883. var stringValue = goog.json.parse(value).toString();
  884. while (this.lookingAtType_(types.STRING)) {
  885. value = this.consumeToken_(types.STRING);
  886. stringValue += goog.json.parse(value).toString();
  887. }
  888. return stringValue;
  889. };
  890. /**
  891. * Consumes a token with the given value. If not found, reports an error.
  892. * @param {string} value The string value expected for the token.
  893. * @return {boolean} True on success, false otherwise.
  894. * @private
  895. */
  896. goog.proto2.TextFormatSerializer.Parser.prototype.consume_ = function(value) {
  897. if (!this.tryConsume_(value)) {
  898. this.reportError_('Expected token "' + value + '"');
  899. return false;
  900. }
  901. return true;
  902. };
  903. /**
  904. * @param {string} value The value to check against.
  905. * @return {boolean} True if the current token has the given string value.
  906. * @private
  907. */
  908. goog.proto2.TextFormatSerializer.Parser.prototype.lookingAt_ = function(value) {
  909. return this.tokenizer_.getCurrent().value == value;
  910. };
  911. /**
  912. * @param {goog.proto2.TextFormatSerializer.Tokenizer_.TokenTypes} type The
  913. * token type.
  914. * @return {boolean} True if the current token has the given type.
  915. * @private
  916. */
  917. goog.proto2.TextFormatSerializer.Parser.prototype.lookingAtType_ = function(
  918. type) {
  919. return this.tokenizer_.getCurrent().type == type;
  920. };