TextFormat.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  1. #region Copyright notice and license
  2. // Protocol Buffers - Google's data interchange format
  3. // Copyright 2008 Google Inc. All rights reserved.
  4. // http://github.com/jskeet/dotnet-protobufs/
  5. // Original C++/Java/Python code:
  6. // http://code.google.com/p/protobuf/
  7. //
  8. // Redistribution and use in source and binary forms, with or without
  9. // modification, are permitted provided that the following conditions are
  10. // met:
  11. //
  12. // * Redistributions of source code must retain the above copyright
  13. // notice, this list of conditions and the following disclaimer.
  14. // * Redistributions in binary form must reproduce the above
  15. // copyright notice, this list of conditions and the following disclaimer
  16. // in the documentation and/or other materials provided with the
  17. // distribution.
  18. // * Neither the name of Google Inc. nor the names of its
  19. // contributors may be used to endorse or promote products derived from
  20. // this software without specific prior written permission.
  21. //
  22. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  23. // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  24. // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  25. // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  26. // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  27. // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  28. // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  29. // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  30. // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  31. // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  32. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  33. #endregion
  34. using System;
  35. using System.Collections;
  36. using System.Collections.Generic;
  37. using System.Globalization;
  38. using System.IO;
  39. using System.Text;
  40. using Google.ProtocolBuffers.Descriptors;
  41. namespace Google.ProtocolBuffers
  42. {
  43. /// <summary>
  44. /// Provides ASCII text formatting support for messages.
  45. /// TODO(jonskeet): Support for alternative line endings.
  46. /// (Easy to print, via TextGenerator. Not sure about parsing.)
  47. /// </summary>
  48. public static class TextFormat
  49. {
  50. /// <summary>
  51. /// Outputs a textual representation of the Protocol Message supplied into
  52. /// the parameter output.
  53. /// </summary>
  54. public static void Print(IMessage message, TextWriter output)
  55. {
  56. TextGenerator generator = new TextGenerator(output, "\n");
  57. Print(message, generator);
  58. }
  59. /// <summary>
  60. /// Outputs a textual representation of <paramref name="fields" /> to <paramref name="output"/>.
  61. /// </summary>
  62. public static void Print(UnknownFieldSet fields, TextWriter output)
  63. {
  64. TextGenerator generator = new TextGenerator(output, "\n");
  65. PrintUnknownFields(fields, generator);
  66. }
  67. public static string PrintToString(IMessage message)
  68. {
  69. StringWriter text = new StringWriter();
  70. Print(message, text);
  71. return text.ToString();
  72. }
  73. public static string PrintToString(UnknownFieldSet fields)
  74. {
  75. StringWriter text = new StringWriter();
  76. Print(fields, text);
  77. return text.ToString();
  78. }
  79. private static void Print(IMessage message, TextGenerator generator)
  80. {
  81. foreach (KeyValuePair<FieldDescriptor, object> entry in message.AllFields)
  82. {
  83. PrintField(entry.Key, entry.Value, generator);
  84. }
  85. PrintUnknownFields(message.UnknownFields, generator);
  86. }
  87. internal static void PrintField(FieldDescriptor field, object value, TextGenerator generator)
  88. {
  89. if (field.IsRepeated)
  90. {
  91. // Repeated field. Print each element.
  92. foreach (object element in (IEnumerable) value)
  93. {
  94. PrintSingleField(field, element, generator);
  95. }
  96. }
  97. else
  98. {
  99. PrintSingleField(field, value, generator);
  100. }
  101. }
  102. private static void PrintSingleField(FieldDescriptor field, Object value, TextGenerator generator)
  103. {
  104. if (field.IsExtension)
  105. {
  106. generator.Print("[");
  107. // We special-case MessageSet elements for compatibility with proto1.
  108. if (field.ContainingType.Options.MessageSetWireFormat
  109. && field.FieldType == FieldType.Message
  110. && field.IsOptional
  111. // object equality (TODO(jonskeet): Work out what this comment means!)
  112. && field.ExtensionScope == field.MessageType)
  113. {
  114. generator.Print(field.MessageType.FullName);
  115. }
  116. else
  117. {
  118. generator.Print(field.FullName);
  119. }
  120. generator.Print("]");
  121. }
  122. else
  123. {
  124. if (field.FieldType == FieldType.Group)
  125. {
  126. // Groups must be serialized with their original capitalization.
  127. generator.Print(field.MessageType.Name);
  128. }
  129. else
  130. {
  131. generator.Print(field.Name);
  132. }
  133. }
  134. if (field.MappedType == MappedType.Message)
  135. {
  136. generator.Print(" {\n");
  137. generator.Indent();
  138. }
  139. else
  140. {
  141. generator.Print(": ");
  142. }
  143. PrintFieldValue(field, value, generator);
  144. if (field.MappedType == MappedType.Message)
  145. {
  146. generator.Outdent();
  147. generator.Print("}");
  148. }
  149. generator.Print("\n");
  150. }
  151. private static void PrintFieldValue(FieldDescriptor field, object value, TextGenerator generator)
  152. {
  153. switch (field.FieldType)
  154. {
  155. // The Float and Double types must specify the "r" format to preserve their precision, otherwise,
  156. // the double to/from string will trim the precision to 6 places. As with other numeric formats
  157. // below, always use the invariant culture so it's predictable.
  158. case FieldType.Float:
  159. generator.Print(((float)value).ToString("r", FrameworkPortability.InvariantCulture));
  160. break;
  161. case FieldType.Double:
  162. generator.Print(((double)value).ToString("r", FrameworkPortability.InvariantCulture));
  163. break;
  164. case FieldType.Int32:
  165. case FieldType.Int64:
  166. case FieldType.SInt32:
  167. case FieldType.SInt64:
  168. case FieldType.SFixed32:
  169. case FieldType.SFixed64:
  170. case FieldType.UInt32:
  171. case FieldType.UInt64:
  172. case FieldType.Fixed32:
  173. case FieldType.Fixed64:
  174. // The simple Object.ToString converts using the current culture.
  175. // We want to always use the invariant culture so it's predictable.
  176. generator.Print(((IConvertible)value).ToString(FrameworkPortability.InvariantCulture));
  177. break;
  178. case FieldType.Bool:
  179. // Explicitly use the Java true/false
  180. generator.Print((bool) value ? "true" : "false");
  181. break;
  182. case FieldType.String:
  183. generator.Print("\"");
  184. generator.Print(EscapeText((string) value));
  185. generator.Print("\"");
  186. break;
  187. case FieldType.Bytes:
  188. {
  189. generator.Print("\"");
  190. generator.Print(EscapeBytes((ByteString) value));
  191. generator.Print("\"");
  192. break;
  193. }
  194. case FieldType.Enum:
  195. {
  196. if (value is IEnumLite && !(value is EnumValueDescriptor))
  197. {
  198. throw new NotSupportedException("Lite enumerations are not supported.");
  199. }
  200. generator.Print(((EnumValueDescriptor) value).Name);
  201. break;
  202. }
  203. case FieldType.Message:
  204. case FieldType.Group:
  205. if (value is IMessageLite && !(value is IMessage))
  206. {
  207. throw new NotSupportedException("Lite messages are not supported.");
  208. }
  209. Print((IMessage) value, generator);
  210. break;
  211. }
  212. }
  213. private static void PrintUnknownFields(UnknownFieldSet unknownFields, TextGenerator generator)
  214. {
  215. foreach (KeyValuePair<int, UnknownField> entry in unknownFields.FieldDictionary)
  216. {
  217. String prefix = entry.Key.ToString() + ": ";
  218. UnknownField field = entry.Value;
  219. foreach (ulong value in field.VarintList)
  220. {
  221. generator.Print(prefix);
  222. generator.Print(value.ToString());
  223. generator.Print("\n");
  224. }
  225. foreach (uint value in field.Fixed32List)
  226. {
  227. generator.Print(prefix);
  228. generator.Print(string.Format("0x{0:x8}", value));
  229. generator.Print("\n");
  230. }
  231. foreach (ulong value in field.Fixed64List)
  232. {
  233. generator.Print(prefix);
  234. generator.Print(string.Format("0x{0:x16}", value));
  235. generator.Print("\n");
  236. }
  237. foreach (ByteString value in field.LengthDelimitedList)
  238. {
  239. generator.Print(entry.Key.ToString());
  240. generator.Print(": \"");
  241. generator.Print(EscapeBytes(value));
  242. generator.Print("\"\n");
  243. }
  244. foreach (UnknownFieldSet value in field.GroupList)
  245. {
  246. generator.Print(entry.Key.ToString());
  247. generator.Print(" {\n");
  248. generator.Indent();
  249. PrintUnknownFields(value, generator);
  250. generator.Outdent();
  251. generator.Print("}\n");
  252. }
  253. }
  254. }
  255. [CLSCompliant(false)]
  256. public static ulong ParseUInt64(string text)
  257. {
  258. return (ulong) ParseInteger(text, false, true);
  259. }
  260. public static long ParseInt64(string text)
  261. {
  262. return ParseInteger(text, true, true);
  263. }
  264. [CLSCompliant(false)]
  265. public static uint ParseUInt32(string text)
  266. {
  267. return (uint) ParseInteger(text, false, false);
  268. }
  269. public static int ParseInt32(string text)
  270. {
  271. return (int) ParseInteger(text, true, false);
  272. }
  273. public static float ParseFloat(string text)
  274. {
  275. switch (text)
  276. {
  277. case "-inf":
  278. case "-infinity":
  279. case "-inff":
  280. case "-infinityf":
  281. return float.NegativeInfinity;
  282. case "inf":
  283. case "infinity":
  284. case "inff":
  285. case "infinityf":
  286. return float.PositiveInfinity;
  287. case "nan":
  288. case "nanf":
  289. return float.NaN;
  290. default:
  291. return float.Parse(text, FrameworkPortability.InvariantCulture);
  292. }
  293. }
  294. public static double ParseDouble(string text)
  295. {
  296. switch (text)
  297. {
  298. case "-inf":
  299. case "-infinity":
  300. return double.NegativeInfinity;
  301. case "inf":
  302. case "infinity":
  303. return double.PositiveInfinity;
  304. case "nan":
  305. return double.NaN;
  306. default:
  307. return double.Parse(text, FrameworkPortability.InvariantCulture);
  308. }
  309. }
  310. /// <summary>
  311. /// Parses an integer in hex (leading 0x), decimal (no prefix) or octal (leading 0).
  312. /// Only a negative sign is permitted, and it must come before the radix indicator.
  313. /// </summary>
  314. private static long ParseInteger(string text, bool isSigned, bool isLong)
  315. {
  316. string original = text;
  317. bool negative = false;
  318. if (text.StartsWith("-"))
  319. {
  320. if (!isSigned)
  321. {
  322. throw new FormatException("Number must be positive: " + original);
  323. }
  324. negative = true;
  325. text = text.Substring(1);
  326. }
  327. int radix = 10;
  328. if (text.StartsWith("0x"))
  329. {
  330. radix = 16;
  331. text = text.Substring(2);
  332. }
  333. else if (text.StartsWith("0"))
  334. {
  335. radix = 8;
  336. }
  337. ulong result;
  338. try
  339. {
  340. // Workaround for https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=278448
  341. // We should be able to use Convert.ToUInt64 for all cases.
  342. result = radix == 10 ? ulong.Parse(text) : Convert.ToUInt64(text, radix);
  343. }
  344. catch (OverflowException)
  345. {
  346. // Convert OverflowException to FormatException so there's a single exception type this method can throw.
  347. string numberDescription = string.Format("{0}-bit {1}signed integer", isLong ? 64 : 32,
  348. isSigned ? "" : "un");
  349. throw new FormatException("Number out of range for " + numberDescription + ": " + original);
  350. }
  351. if (negative)
  352. {
  353. ulong max = isLong ? 0x8000000000000000UL : 0x80000000L;
  354. if (result > max)
  355. {
  356. string numberDescription = string.Format("{0}-bit signed integer", isLong ? 64 : 32);
  357. throw new FormatException("Number out of range for " + numberDescription + ": " + original);
  358. }
  359. return -((long) result);
  360. }
  361. else
  362. {
  363. ulong max = isSigned
  364. ? (isLong ? (ulong) long.MaxValue : int.MaxValue)
  365. : (isLong ? ulong.MaxValue : uint.MaxValue);
  366. if (result > max)
  367. {
  368. string numberDescription = string.Format("{0}-bit {1}signed integer", isLong ? 64 : 32,
  369. isSigned ? "" : "un");
  370. throw new FormatException("Number out of range for " + numberDescription + ": " + original);
  371. }
  372. return (long) result;
  373. }
  374. }
  375. /// <summary>
  376. /// Tests a character to see if it's an octal digit.
  377. /// </summary>
  378. private static bool IsOctal(char c)
  379. {
  380. return '0' <= c && c <= '7';
  381. }
  382. /// <summary>
  383. /// Tests a character to see if it's a hex digit.
  384. /// </summary>
  385. private static bool IsHex(char c)
  386. {
  387. return ('0' <= c && c <= '9') ||
  388. ('a' <= c && c <= 'f') ||
  389. ('A' <= c && c <= 'F');
  390. }
  391. /// <summary>
  392. /// Interprets a character as a digit (in any base up to 36) and returns the
  393. /// numeric value.
  394. /// </summary>
  395. private static int ParseDigit(char c)
  396. {
  397. if ('0' <= c && c <= '9')
  398. {
  399. return c - '0';
  400. }
  401. else if ('a' <= c && c <= 'z')
  402. {
  403. return c - 'a' + 10;
  404. }
  405. else
  406. {
  407. return c - 'A' + 10;
  408. }
  409. }
  410. /// <summary>
  411. /// Unescapes a text string as escaped using <see cref="EscapeText(string)" />.
  412. /// Two-digit hex escapes (starting with "\x" are also recognised.
  413. /// </summary>
  414. public static string UnescapeText(string input)
  415. {
  416. return UnescapeBytes(input).ToStringUtf8();
  417. }
  418. /// <summary>
  419. /// Like <see cref="EscapeBytes" /> but escapes a text string.
  420. /// The string is first encoded as UTF-8, then each byte escaped individually.
  421. /// The returned value is guaranteed to be entirely ASCII.
  422. /// </summary>
  423. public static string EscapeText(string input)
  424. {
  425. return EscapeBytes(ByteString.CopyFromUtf8(input));
  426. }
  427. /// <summary>
  428. /// Escapes bytes in the format used in protocol buffer text format, which
  429. /// is the same as the format used for C string literals. All bytes
  430. /// that are not printable 7-bit ASCII characters are escaped, as well as
  431. /// backslash, single-quote, and double-quote characters. Characters for
  432. /// which no defined short-hand escape sequence is defined will be escaped
  433. /// using 3-digit octal sequences.
  434. /// The returned value is guaranteed to be entirely ASCII.
  435. /// </summary>
  436. public static String EscapeBytes(ByteString input)
  437. {
  438. StringBuilder builder = new StringBuilder(input.Length);
  439. foreach (byte b in input)
  440. {
  441. switch (b)
  442. {
  443. // C# does not use \a or \v
  444. case 0x07:
  445. builder.Append("\\a");
  446. break;
  447. case (byte) '\b':
  448. builder.Append("\\b");
  449. break;
  450. case (byte) '\f':
  451. builder.Append("\\f");
  452. break;
  453. case (byte) '\n':
  454. builder.Append("\\n");
  455. break;
  456. case (byte) '\r':
  457. builder.Append("\\r");
  458. break;
  459. case (byte) '\t':
  460. builder.Append("\\t");
  461. break;
  462. case 0x0b:
  463. builder.Append("\\v");
  464. break;
  465. case (byte) '\\':
  466. builder.Append("\\\\");
  467. break;
  468. case (byte) '\'':
  469. builder.Append("\\\'");
  470. break;
  471. case (byte) '"':
  472. builder.Append("\\\"");
  473. break;
  474. default:
  475. if (b >= 0x20 && b < 128)
  476. {
  477. builder.Append((char) b);
  478. }
  479. else
  480. {
  481. builder.Append('\\');
  482. builder.Append((char) ('0' + ((b >> 6) & 3)));
  483. builder.Append((char) ('0' + ((b >> 3) & 7)));
  484. builder.Append((char) ('0' + (b & 7)));
  485. }
  486. break;
  487. }
  488. }
  489. return builder.ToString();
  490. }
  491. /// <summary>
  492. /// Performs string unescaping from C style (octal, hex, form feeds, tab etc) into a byte string.
  493. /// </summary>
  494. public static ByteString UnescapeBytes(string input)
  495. {
  496. byte[] result = new byte[input.Length];
  497. int pos = 0;
  498. for (int i = 0; i < input.Length; i++)
  499. {
  500. char c = input[i];
  501. if (c > 127 || c < 32)
  502. {
  503. throw new FormatException("Escaped string must only contain ASCII");
  504. }
  505. if (c != '\\')
  506. {
  507. result[pos++] = (byte) c;
  508. continue;
  509. }
  510. if (i + 1 >= input.Length)
  511. {
  512. throw new FormatException("Invalid escape sequence: '\\' at end of string.");
  513. }
  514. i++;
  515. c = input[i];
  516. if (c >= '0' && c <= '7')
  517. {
  518. // Octal escape.
  519. int code = ParseDigit(c);
  520. if (i + 1 < input.Length && IsOctal(input[i + 1]))
  521. {
  522. i++;
  523. code = code*8 + ParseDigit(input[i]);
  524. }
  525. if (i + 1 < input.Length && IsOctal(input[i + 1]))
  526. {
  527. i++;
  528. code = code*8 + ParseDigit(input[i]);
  529. }
  530. result[pos++] = (byte) code;
  531. }
  532. else
  533. {
  534. switch (c)
  535. {
  536. case 'a':
  537. result[pos++] = 0x07;
  538. break;
  539. case 'b':
  540. result[pos++] = (byte) '\b';
  541. break;
  542. case 'f':
  543. result[pos++] = (byte) '\f';
  544. break;
  545. case 'n':
  546. result[pos++] = (byte) '\n';
  547. break;
  548. case 'r':
  549. result[pos++] = (byte) '\r';
  550. break;
  551. case 't':
  552. result[pos++] = (byte) '\t';
  553. break;
  554. case 'v':
  555. result[pos++] = 0x0b;
  556. break;
  557. case '\\':
  558. result[pos++] = (byte) '\\';
  559. break;
  560. case '\'':
  561. result[pos++] = (byte) '\'';
  562. break;
  563. case '"':
  564. result[pos++] = (byte) '\"';
  565. break;
  566. case 'x':
  567. // hex escape
  568. int code;
  569. if (i + 1 < input.Length && IsHex(input[i + 1]))
  570. {
  571. i++;
  572. code = ParseDigit(input[i]);
  573. }
  574. else
  575. {
  576. throw new FormatException("Invalid escape sequence: '\\x' with no digits");
  577. }
  578. if (i + 1 < input.Length && IsHex(input[i + 1]))
  579. {
  580. ++i;
  581. code = code*16 + ParseDigit(input[i]);
  582. }
  583. result[pos++] = (byte) code;
  584. break;
  585. default:
  586. throw new FormatException("Invalid escape sequence: '\\" + c + "'");
  587. }
  588. }
  589. }
  590. return ByteString.CopyFrom(result, 0, pos);
  591. }
  592. public static void Merge(string text, IBuilder builder)
  593. {
  594. Merge(text, ExtensionRegistry.Empty, builder);
  595. }
  596. public static void Merge(TextReader reader, IBuilder builder)
  597. {
  598. Merge(reader, ExtensionRegistry.Empty, builder);
  599. }
  600. public static void Merge(TextReader reader, ExtensionRegistry registry, IBuilder builder)
  601. {
  602. Merge(reader.ReadToEnd(), registry, builder);
  603. }
  604. public static void Merge(string text, ExtensionRegistry registry, IBuilder builder)
  605. {
  606. TextTokenizer tokenizer = new TextTokenizer(text);
  607. while (!tokenizer.AtEnd)
  608. {
  609. MergeField(tokenizer, registry, builder);
  610. }
  611. }
  612. /// <summary>
  613. /// Parses a single field from the specified tokenizer and merges it into
  614. /// the builder.
  615. /// </summary>
  616. private static void MergeField(TextTokenizer tokenizer, ExtensionRegistry extensionRegistry,
  617. IBuilder builder)
  618. {
  619. FieldDescriptor field;
  620. MessageDescriptor type = builder.DescriptorForType;
  621. ExtensionInfo extension = null;
  622. if (tokenizer.TryConsume("["))
  623. {
  624. // An extension.
  625. StringBuilder name = new StringBuilder(tokenizer.ConsumeIdentifier());
  626. while (tokenizer.TryConsume("."))
  627. {
  628. name.Append(".");
  629. name.Append(tokenizer.ConsumeIdentifier());
  630. }
  631. extension = extensionRegistry.FindByName(type, name.ToString());
  632. if (extension == null)
  633. {
  634. throw tokenizer.CreateFormatExceptionPreviousToken("Extension \"" + name +
  635. "\" not found in the ExtensionRegistry.");
  636. }
  637. else if (extension.Descriptor.ContainingType != type)
  638. {
  639. throw tokenizer.CreateFormatExceptionPreviousToken("Extension \"" + name +
  640. "\" does not extend message type \"" +
  641. type.FullName + "\".");
  642. }
  643. tokenizer.Consume("]");
  644. field = extension.Descriptor;
  645. }
  646. else
  647. {
  648. String name = tokenizer.ConsumeIdentifier();
  649. field = type.FindDescriptor<FieldDescriptor>(name);
  650. // Group names are expected to be capitalized as they appear in the
  651. // .proto file, which actually matches their type names, not their field
  652. // names.
  653. if (field == null)
  654. {
  655. // Explicitly specify the invariant culture so that this code does not break when
  656. // executing in Turkey.
  657. String lowerName = name.ToLower(FrameworkPortability.InvariantCulture);
  658. field = type.FindDescriptor<FieldDescriptor>(lowerName);
  659. // If the case-insensitive match worked but the field is NOT a group,
  660. // TODO(jonskeet): What? Java comment ends here!
  661. if (field != null && field.FieldType != FieldType.Group)
  662. {
  663. field = null;
  664. }
  665. }
  666. // Again, special-case group names as described above.
  667. if (field != null && field.FieldType == FieldType.Group && field.MessageType.Name != name)
  668. {
  669. field = null;
  670. }
  671. if (field == null)
  672. {
  673. throw tokenizer.CreateFormatExceptionPreviousToken(
  674. "Message type \"" + type.FullName + "\" has no field named \"" + name + "\".");
  675. }
  676. }
  677. object value = null;
  678. if (field.MappedType == MappedType.Message)
  679. {
  680. tokenizer.TryConsume(":"); // optional
  681. String endToken;
  682. if (tokenizer.TryConsume("<"))
  683. {
  684. endToken = ">";
  685. }
  686. else
  687. {
  688. tokenizer.Consume("{");
  689. endToken = "}";
  690. }
  691. IBuilder subBuilder;
  692. if (extension == null)
  693. {
  694. subBuilder = builder.CreateBuilderForField(field);
  695. }
  696. else
  697. {
  698. subBuilder = extension.DefaultInstance.WeakCreateBuilderForType() as IBuilder;
  699. if (subBuilder == null)
  700. {
  701. throw new NotSupportedException("Lite messages are not supported.");
  702. }
  703. }
  704. while (!tokenizer.TryConsume(endToken))
  705. {
  706. if (tokenizer.AtEnd)
  707. {
  708. throw tokenizer.CreateFormatException("Expected \"" + endToken + "\".");
  709. }
  710. MergeField(tokenizer, extensionRegistry, subBuilder);
  711. }
  712. value = subBuilder.WeakBuild();
  713. }
  714. else
  715. {
  716. tokenizer.Consume(":");
  717. switch (field.FieldType)
  718. {
  719. case FieldType.Int32:
  720. case FieldType.SInt32:
  721. case FieldType.SFixed32:
  722. value = tokenizer.ConsumeInt32();
  723. break;
  724. case FieldType.Int64:
  725. case FieldType.SInt64:
  726. case FieldType.SFixed64:
  727. value = tokenizer.ConsumeInt64();
  728. break;
  729. case FieldType.UInt32:
  730. case FieldType.Fixed32:
  731. value = tokenizer.ConsumeUInt32();
  732. break;
  733. case FieldType.UInt64:
  734. case FieldType.Fixed64:
  735. value = tokenizer.ConsumeUInt64();
  736. break;
  737. case FieldType.Float:
  738. value = tokenizer.ConsumeFloat();
  739. break;
  740. case FieldType.Double:
  741. value = tokenizer.ConsumeDouble();
  742. break;
  743. case FieldType.Bool:
  744. value = tokenizer.ConsumeBoolean();
  745. break;
  746. case FieldType.String:
  747. value = tokenizer.ConsumeString();
  748. break;
  749. case FieldType.Bytes:
  750. value = tokenizer.ConsumeByteString();
  751. break;
  752. case FieldType.Enum:
  753. {
  754. EnumDescriptor enumType = field.EnumType;
  755. if (tokenizer.LookingAtInteger())
  756. {
  757. int number = tokenizer.ConsumeInt32();
  758. value = enumType.FindValueByNumber(number);
  759. if (value == null)
  760. {
  761. throw tokenizer.CreateFormatExceptionPreviousToken(
  762. "Enum type \"" + enumType.FullName +
  763. "\" has no value with number " + number + ".");
  764. }
  765. }
  766. else
  767. {
  768. String id = tokenizer.ConsumeIdentifier();
  769. value = enumType.FindValueByName(id);
  770. if (value == null)
  771. {
  772. throw tokenizer.CreateFormatExceptionPreviousToken(
  773. "Enum type \"" + enumType.FullName +
  774. "\" has no value named \"" + id + "\".");
  775. }
  776. }
  777. break;
  778. }
  779. case FieldType.Message:
  780. case FieldType.Group:
  781. throw new InvalidOperationException("Can't get here.");
  782. }
  783. }
  784. if (field.IsRepeated)
  785. {
  786. builder.WeakAddRepeatedField(field, value);
  787. }
  788. else
  789. {
  790. builder.SetField(field, value);
  791. }
  792. }
  793. }
  794. }