#region Copyright notice and license | |
// Protocol Buffers - Google's data interchange format | |
// Copyright 2008 Google Inc. All rights reserved. | |
// http://github.com/jskeet/dotnet-protobufs/ | |
// Original C++/Java/Python code: | |
// http://code.google.com/p/protobuf/ | |
// | |
// Redistribution and use in source and binary forms, with or without | |
// modification, are permitted provided that the following conditions are | |
// met: | |
// | |
// * Redistributions of source code must retain the above copyright | |
// notice, this list of conditions and the following disclaimer. | |
// * Redistributions in binary form must reproduce the above | |
// copyright notice, this list of conditions and the following disclaimer | |
// in the documentation and/or other materials provided with the | |
// distribution. | |
// * Neither the name of Google Inc. nor the names of its | |
// contributors may be used to endorse or promote products derived from | |
// this software without specific prior written permission. | |
// | |
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
#endregion | |
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Globalization; | |
using System.IO; | |
using System.Text; | |
using Google.ProtocolBuffers.Descriptors; | |
namespace Google.ProtocolBuffers | |
{ | |
/// <summary> | |
/// Provides ASCII text formatting support for messages. | |
/// TODO(jonskeet): Support for alternative line endings. | |
/// (Easy to print, via TextGenerator. Not sure about parsing.) | |
/// </summary> | |
public static class TextFormat | |
{ | |
/// <summary> | |
/// Outputs a textual representation of the Protocol Message supplied into | |
/// the parameter output. | |
/// </summary> | |
public static void Print(IMessage message, TextWriter output) | |
{ | |
TextGenerator generator = new TextGenerator(output, "\n"); | |
Print(message, generator); | |
} | |
/// <summary> | |
/// Outputs a textual representation of the Protocol Message builder supplied into | |
/// the parameter output. | |
/// </summary> | |
public static void Print(IBuilder builder, TextWriter output) | |
{ | |
TextGenerator generator = new TextGenerator(output, "\n"); | |
Print(builder, generator); | |
} | |
/// <summary> | |
/// Outputs a textual representation of <paramref name="fields" /> to <paramref name="output"/>. | |
/// </summary> | |
public static void Print(UnknownFieldSet fields, TextWriter output) | |
{ | |
TextGenerator generator = new TextGenerator(output, "\n"); | |
PrintUnknownFields(fields, generator); | |
} | |
public static string PrintToString(IMessage message) | |
{ | |
StringWriter text = new StringWriter(); | |
Print(message, text); | |
return text.ToString(); | |
} | |
public static string PrintToString(IBuilder builder) | |
{ | |
StringWriter text = new StringWriter(); | |
Print(builder, text); | |
return text.ToString(); | |
} | |
public static string PrintToString(UnknownFieldSet fields) | |
{ | |
StringWriter text = new StringWriter(); | |
Print(fields, text); | |
return text.ToString(); | |
} | |
private static void Print(IMessage message, TextGenerator generator) | |
{ | |
foreach (KeyValuePair<FieldDescriptor, object> entry in message.AllFields) | |
{ | |
PrintField(entry.Key, entry.Value, generator); | |
} | |
PrintUnknownFields(message.UnknownFields, generator); | |
} | |
private static void Print(IBuilder message, TextGenerator generator) | |
{ | |
foreach (KeyValuePair<FieldDescriptor, object> entry in message.AllFields) | |
{ | |
PrintField(entry.Key, entry.Value, generator); | |
} | |
PrintUnknownFields(message.UnknownFields, generator); | |
} | |
internal static void PrintField(FieldDescriptor field, object value, TextGenerator generator) | |
{ | |
if (field.IsRepeated) | |
{ | |
// Repeated field. Print each element. | |
foreach (object element in (IEnumerable) value) | |
{ | |
PrintSingleField(field, element, generator); | |
} | |
} | |
else | |
{ | |
PrintSingleField(field, value, generator); | |
} | |
} | |
private static void PrintSingleField(FieldDescriptor field, Object value, TextGenerator generator) | |
{ | |
if (field.IsExtension) | |
{ | |
generator.Print("["); | |
// We special-case MessageSet elements for compatibility with proto1. | |
if (field.ContainingType.Options.MessageSetWireFormat | |
&& field.FieldType == FieldType.Message | |
&& field.IsOptional | |
// object equality (TODO(jonskeet): Work out what this comment means!) | |
&& field.ExtensionScope == field.MessageType) | |
{ | |
generator.Print(field.MessageType.FullName); | |
} | |
else | |
{ | |
generator.Print(field.FullName); | |
} | |
generator.Print("]"); | |
} | |
else | |
{ | |
if (field.FieldType == FieldType.Group) | |
{ | |
// Groups must be serialized with their original capitalization. | |
generator.Print(field.MessageType.Name); | |
} | |
else | |
{ | |
generator.Print(field.Name); | |
} | |
} | |
if (field.MappedType == MappedType.Message) | |
{ | |
generator.Print(" {\n"); | |
generator.Indent(); | |
} | |
else | |
{ | |
generator.Print(": "); | |
} | |
PrintFieldValue(field, value, generator); | |
if (field.MappedType == MappedType.Message) | |
{ | |
generator.Outdent(); | |
generator.Print("}"); | |
} | |
generator.Print("\n"); | |
} | |
private static void PrintFieldValue(FieldDescriptor field, object value, TextGenerator generator) | |
{ | |
switch (field.FieldType) | |
{ | |
// The Float and Double types must specify the "r" format to preserve their precision, otherwise, | |
// the double to/from string will trim the precision to 6 places. As with other numeric formats | |
// below, always use the invariant culture so it's predictable. | |
case FieldType.Float: | |
generator.Print(((float)value).ToString("r", FrameworkPortability.InvariantCulture)); | |
break; | |
case FieldType.Double: | |
generator.Print(((double)value).ToString("r", FrameworkPortability.InvariantCulture)); | |
break; | |
case FieldType.Int32: | |
case FieldType.Int64: | |
case FieldType.SInt32: | |
case FieldType.SInt64: | |
case FieldType.SFixed32: | |
case FieldType.SFixed64: | |
case FieldType.UInt32: | |
case FieldType.UInt64: | |
case FieldType.Fixed32: | |
case FieldType.Fixed64: | |
// The simple Object.ToString converts using the current culture. | |
// We want to always use the invariant culture so it's predictable. | |
generator.Print(((IConvertible)value).ToString(FrameworkPortability.InvariantCulture)); | |
break; | |
case FieldType.Bool: | |
// Explicitly use the Java true/false | |
generator.Print((bool) value ? "true" : "false"); | |
break; | |
case FieldType.String: | |
generator.Print("\""); | |
generator.Print(EscapeText((string) value)); | |
generator.Print("\""); | |
break; | |
case FieldType.Bytes: | |
{ | |
generator.Print("\""); | |
generator.Print(EscapeBytes((ByteString) value)); | |
generator.Print("\""); | |
break; | |
} | |
case FieldType.Enum: | |
{ | |
if (value is IEnumLite && !(value is EnumValueDescriptor)) | |
{ | |
throw new NotSupportedException("Lite enumerations are not supported."); | |
} | |
generator.Print(((EnumValueDescriptor) value).Name); | |
break; | |
} | |
case FieldType.Message: | |
case FieldType.Group: | |
if (value is IMessageLite && !(value is IMessage)) | |
{ | |
throw new NotSupportedException("Lite messages are not supported."); | |
} | |
Print((IMessage) value, generator); | |
break; | |
} | |
} | |
private static void PrintUnknownFields(UnknownFieldSet unknownFields, TextGenerator generator) | |
{ | |
foreach (KeyValuePair<int, UnknownField> entry in unknownFields.FieldDictionary) | |
{ | |
String prefix = entry.Key.ToString() + ": "; | |
UnknownField field = entry.Value; | |
foreach (ulong value in field.VarintList) | |
{ | |
generator.Print(prefix); | |
generator.Print(value.ToString()); | |
generator.Print("\n"); | |
} | |
foreach (uint value in field.Fixed32List) | |
{ | |
generator.Print(prefix); | |
generator.Print(string.Format("0x{0:x8}", value)); | |
generator.Print("\n"); | |
} | |
foreach (ulong value in field.Fixed64List) | |
{ | |
generator.Print(prefix); | |
generator.Print(string.Format("0x{0:x16}", value)); | |
generator.Print("\n"); | |
} | |
foreach (ByteString value in field.LengthDelimitedList) | |
{ | |
generator.Print(entry.Key.ToString()); | |
generator.Print(": \""); | |
generator.Print(EscapeBytes(value)); | |
generator.Print("\"\n"); | |
} | |
foreach (UnknownFieldSet value in field.GroupList) | |
{ | |
generator.Print(entry.Key.ToString()); | |
generator.Print(" {\n"); | |
generator.Indent(); | |
PrintUnknownFields(value, generator); | |
generator.Outdent(); | |
generator.Print("}\n"); | |
} | |
} | |
} | |
public static ulong ParseUInt64(string text) | |
{ | |
return (ulong) ParseInteger(text, false, true); | |
} | |
public static long ParseInt64(string text) | |
{ | |
return ParseInteger(text, true, true); | |
} | |
public static uint ParseUInt32(string text) | |
{ | |
return (uint) ParseInteger(text, false, false); | |
} | |
public static int ParseInt32(string text) | |
{ | |
return (int) ParseInteger(text, true, false); | |
} | |
public static float ParseFloat(string text) | |
{ | |
switch (text) | |
{ | |
case "-inf": | |
case "-infinity": | |
case "-inff": | |
case "-infinityf": | |
return float.NegativeInfinity; | |
case "inf": | |
case "infinity": | |
case "inff": | |
case "infinityf": | |
return float.PositiveInfinity; | |
case "nan": | |
case "nanf": | |
return float.NaN; | |
default: | |
return float.Parse(text, FrameworkPortability.InvariantCulture); | |
} | |
} | |
public static double ParseDouble(string text) | |
{ | |
switch (text) | |
{ | |
case "-inf": | |
case "-infinity": | |
return double.NegativeInfinity; | |
case "inf": | |
case "infinity": | |
return double.PositiveInfinity; | |
case "nan": | |
return double.NaN; | |
default: | |
return double.Parse(text, FrameworkPortability.InvariantCulture); | |
} | |
} | |
/// <summary> | |
/// Parses an integer in hex (leading 0x), decimal (no prefix) or octal (leading 0). | |
/// Only a negative sign is permitted, and it must come before the radix indicator. | |
/// </summary> | |
private static long ParseInteger(string text, bool isSigned, bool isLong) | |
{ | |
string original = text; | |
bool negative = false; | |
if (text.StartsWith("-")) | |
{ | |
if (!isSigned) | |
{ | |
throw new FormatException("Number must be positive: " + original); | |
} | |
negative = true; | |
text = text.Substring(1); | |
} | |
int radix = 10; | |
if (text.StartsWith("0x")) | |
{ | |
radix = 16; | |
text = text.Substring(2); | |
} | |
else if (text.StartsWith("0")) | |
{ | |
radix = 8; | |
} | |
ulong result; | |
try | |
{ | |
// Workaround for https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=278448 | |
// We should be able to use Convert.ToUInt64 for all cases. | |
result = radix == 10 ? ulong.Parse(text) : Convert.ToUInt64(text, radix); | |
} | |
catch (OverflowException) | |
{ | |
// Convert OverflowException to FormatException so there's a single exception type this method can throw. | |
string numberDescription = string.Format("{0}-bit {1}signed integer", isLong ? 64 : 32, | |
isSigned ? "" : "un"); | |
throw new FormatException("Number out of range for " + numberDescription + ": " + original); | |
} | |
if (negative) | |
{ | |
ulong max = isLong ? 0x8000000000000000UL : 0x80000000L; | |
if (result > max) | |
{ | |
string numberDescription = string.Format("{0}-bit signed integer", isLong ? 64 : 32); | |
throw new FormatException("Number out of range for " + numberDescription + ": " + original); | |
} | |
return -((long) result); | |
} | |
else | |
{ | |
ulong max = isSigned | |
? (isLong ? (ulong) long.MaxValue : int.MaxValue) | |
: (isLong ? ulong.MaxValue : uint.MaxValue); | |
if (result > max) | |
{ | |
string numberDescription = string.Format("{0}-bit {1}signed integer", isLong ? 64 : 32, | |
isSigned ? "" : "un"); | |
throw new FormatException("Number out of range for " + numberDescription + ": " + original); | |
} | |
return (long) result; | |
} | |
} | |
/// <summary> | |
/// Tests a character to see if it's an octal digit. | |
/// </summary> | |
private static bool IsOctal(char c) | |
{ | |
return '0' <= c && c <= '7'; | |
} | |
/// <summary> | |
/// Tests a character to see if it's a hex digit. | |
/// </summary> | |
private static bool IsHex(char c) | |
{ | |
return ('0' <= c && c <= '9') || | |
('a' <= c && c <= 'f') || | |
('A' <= c && c <= 'F'); | |
} | |
/// <summary> | |
/// Interprets a character as a digit (in any base up to 36) and returns the | |
/// numeric value. | |
/// </summary> | |
private static int ParseDigit(char c) | |
{ | |
if ('0' <= c && c <= '9') | |
{ | |
return c - '0'; | |
} | |
else if ('a' <= c && c <= 'z') | |
{ | |
return c - 'a' + 10; | |
} | |
else | |
{ | |
return c - 'A' + 10; | |
} | |
} | |
/// <summary> | |
/// Unescapes a text string as escaped using <see cref="EscapeText(string)" />. | |
/// Two-digit hex escapes (starting with "\x" are also recognised. | |
/// </summary> | |
public static string UnescapeText(string input) | |
{ | |
return UnescapeBytes(input).ToStringUtf8(); | |
} | |
/// <summary> | |
/// Like <see cref="EscapeBytes" /> but escapes a text string. | |
/// The string is first encoded as UTF-8, then each byte escaped individually. | |
/// The returned value is guaranteed to be entirely ASCII. | |
/// </summary> | |
public static string EscapeText(string input) | |
{ | |
return EscapeBytes(ByteString.CopyFromUtf8(input)); | |
} | |
/// <summary> | |
/// Escapes bytes in the format used in protocol buffer text format, which | |
/// is the same as the format used for C string literals. All bytes | |
/// that are not printable 7-bit ASCII characters are escaped, as well as | |
/// backslash, single-quote, and double-quote characters. Characters for | |
/// which no defined short-hand escape sequence is defined will be escaped | |
/// using 3-digit octal sequences. | |
/// The returned value is guaranteed to be entirely ASCII. | |
/// </summary> | |
public static String EscapeBytes(ByteString input) | |
{ | |
StringBuilder builder = new StringBuilder(input.Length); | |
foreach (byte b in input) | |
{ | |
switch (b) | |
{ | |
// C# does not use \a or \v | |
case 0x07: | |
builder.Append("\\a"); | |
break; | |
case (byte) '\b': | |
builder.Append("\\b"); | |
break; | |
case (byte) '\f': | |
builder.Append("\\f"); | |
break; | |
case (byte) '\n': | |
builder.Append("\\n"); | |
break; | |
case (byte) '\r': | |
builder.Append("\\r"); | |
break; | |
case (byte) '\t': | |
builder.Append("\\t"); | |
break; | |
case 0x0b: | |
builder.Append("\\v"); | |
break; | |
case (byte) '\\': | |
builder.Append("\\\\"); | |
break; | |
case (byte) '\'': | |
builder.Append("\\\'"); | |
break; | |
case (byte) '"': | |
builder.Append("\\\""); | |
break; | |
default: | |
if (b >= 0x20 && b < 128) | |
{ | |
builder.Append((char) b); | |
} | |
else | |
{ | |
builder.Append('\\'); | |
builder.Append((char) ('0' + ((b >> 6) & 3))); | |
builder.Append((char) ('0' + ((b >> 3) & 7))); | |
builder.Append((char) ('0' + (b & 7))); | |
} | |
break; | |
} | |
} | |
return builder.ToString(); | |
} | |
/// <summary> | |
/// Performs string unescaping from C style (octal, hex, form feeds, tab etc) into a byte string. | |
/// </summary> | |
public static ByteString UnescapeBytes(string input) | |
{ | |
byte[] result = new byte[input.Length]; | |
int pos = 0; | |
for (int i = 0; i < input.Length; i++) | |
{ | |
char c = input[i]; | |
if (c > 127 || c < 32) | |
{ | |
throw new FormatException("Escaped string must only contain ASCII"); | |
} | |
if (c != '\\') | |
{ | |
result[pos++] = (byte) c; | |
continue; | |
} | |
if (i + 1 >= input.Length) | |
{ | |
throw new FormatException("Invalid escape sequence: '\\' at end of string."); | |
} | |
i++; | |
c = input[i]; | |
if (c >= '0' && c <= '7') | |
{ | |
// Octal escape. | |
int code = ParseDigit(c); | |
if (i + 1 < input.Length && IsOctal(input[i + 1])) | |
{ | |
i++; | |
code = code*8 + ParseDigit(input[i]); | |
} | |
if (i + 1 < input.Length && IsOctal(input[i + 1])) | |
{ | |
i++; | |
code = code*8 + ParseDigit(input[i]); | |
} | |
result[pos++] = (byte) code; | |
} | |
else | |
{ | |
switch (c) | |
{ | |
case 'a': | |
result[pos++] = 0x07; | |
break; | |
case 'b': | |
result[pos++] = (byte) '\b'; | |
break; | |
case 'f': | |
result[pos++] = (byte) '\f'; | |
break; | |
case 'n': | |
result[pos++] = (byte) '\n'; | |
break; | |
case 'r': | |
result[pos++] = (byte) '\r'; | |
break; | |
case 't': | |
result[pos++] = (byte) '\t'; | |
break; | |
case 'v': | |
result[pos++] = 0x0b; | |
break; | |
case '\\': | |
result[pos++] = (byte) '\\'; | |
break; | |
case '\'': | |
result[pos++] = (byte) '\''; | |
break; | |
case '"': | |
result[pos++] = (byte) '\"'; | |
break; | |
case 'x': | |
// hex escape | |
int code; | |
if (i + 1 < input.Length && IsHex(input[i + 1])) | |
{ | |
i++; | |
code = ParseDigit(input[i]); | |
} | |
else | |
{ | |
throw new FormatException("Invalid escape sequence: '\\x' with no digits"); | |
} | |
if (i + 1 < input.Length && IsHex(input[i + 1])) | |
{ | |
++i; | |
code = code*16 + ParseDigit(input[i]); | |
} | |
result[pos++] = (byte) code; | |
break; | |
default: | |
throw new FormatException("Invalid escape sequence: '\\" + c + "'"); | |
} | |
} | |
} | |
return ByteString.CopyFrom(result, 0, pos); | |
} | |
public static void Merge(string text, IBuilder builder) | |
{ | |
Merge(text, ExtensionRegistry.Empty, builder); | |
} | |
public static void Merge(TextReader reader, IBuilder builder) | |
{ | |
Merge(reader, ExtensionRegistry.Empty, builder); | |
} | |
public static void Merge(TextReader reader, ExtensionRegistry registry, IBuilder builder) | |
{ | |
Merge(reader.ReadToEnd(), registry, builder); | |
} | |
public static void Merge(string text, ExtensionRegistry registry, IBuilder builder) | |
{ | |
TextTokenizer tokenizer = new TextTokenizer(text); | |
while (!tokenizer.AtEnd) | |
{ | |
MergeField(tokenizer, registry, builder); | |
} | |
} | |
/// <summary> | |
/// Parses a single field from the specified tokenizer and merges it into | |
/// the builder. | |
/// </summary> | |
private static void MergeField(TextTokenizer tokenizer, ExtensionRegistry extensionRegistry, | |
IBuilder builder) | |
{ | |
FieldDescriptor field; | |
MessageDescriptor type = builder.DescriptorForType; | |
ExtensionInfo extension = null; | |
if (tokenizer.TryConsume("[")) | |
{ | |
// An extension. | |
StringBuilder name = new StringBuilder(tokenizer.ConsumeIdentifier()); | |
while (tokenizer.TryConsume(".")) | |
{ | |
name.Append("."); | |
name.Append(tokenizer.ConsumeIdentifier()); | |
} | |
extension = extensionRegistry.FindByName(type, name.ToString()); | |
if (extension == null) | |
{ | |
throw tokenizer.CreateFormatExceptionPreviousToken("Extension \"" + name + | |
"\" not found in the ExtensionRegistry."); | |
} | |
else if (extension.Descriptor.ContainingType != type) | |
{ | |
throw tokenizer.CreateFormatExceptionPreviousToken("Extension \"" + name + | |
"\" does not extend message type \"" + | |
type.FullName + "\"."); | |
} | |
tokenizer.Consume("]"); | |
field = extension.Descriptor; | |
} | |
else | |
{ | |
String name = tokenizer.ConsumeIdentifier(); | |
field = type.FindDescriptor<FieldDescriptor>(name); | |
// Group names are expected to be capitalized as they appear in the | |
// .proto file, which actually matches their type names, not their field | |
// names. | |
if (field == null) | |
{ | |
// Explicitly specify the invariant culture so that this code does not break when | |
// executing in Turkey. | |
String lowerName = name.ToLowerInvariant(); | |
field = type.FindDescriptor<FieldDescriptor>(lowerName); | |
// If the case-insensitive match worked but the field is NOT a group, | |
// TODO(jonskeet): What? Java comment ends here! | |
if (field != null && field.FieldType != FieldType.Group) | |
{ | |
field = null; | |
} | |
} | |
// Again, special-case group names as described above. | |
if (field != null && field.FieldType == FieldType.Group && field.MessageType.Name != name) | |
{ | |
field = null; | |
} | |
if (field == null) | |
{ | |
throw tokenizer.CreateFormatExceptionPreviousToken( | |
"Message type \"" + type.FullName + "\" has no field named \"" + name + "\"."); | |
} | |
} | |
object value = null; | |
if (field.MappedType == MappedType.Message) | |
{ | |
tokenizer.TryConsume(":"); // optional | |
String endToken; | |
if (tokenizer.TryConsume("<")) | |
{ | |
endToken = ">"; | |
} | |
else | |
{ | |
tokenizer.Consume("{"); | |
endToken = "}"; | |
} | |
IBuilder subBuilder; | |
if (extension == null) | |
{ | |
subBuilder = builder.CreateBuilderForField(field); | |
} | |
else | |
{ | |
subBuilder = extension.DefaultInstance.WeakCreateBuilderForType() as IBuilder; | |
if (subBuilder == null) | |
{ | |
throw new NotSupportedException("Lite messages are not supported."); | |
} | |
} | |
while (!tokenizer.TryConsume(endToken)) | |
{ | |
if (tokenizer.AtEnd) | |
{ | |
throw tokenizer.CreateFormatException("Expected \"" + endToken + "\"."); | |
} | |
MergeField(tokenizer, extensionRegistry, subBuilder); | |
} | |
value = subBuilder.WeakBuild(); | |
} | |
else | |
{ | |
tokenizer.Consume(":"); | |
switch (field.FieldType) | |
{ | |
case FieldType.Int32: | |
case FieldType.SInt32: | |
case FieldType.SFixed32: | |
value = tokenizer.ConsumeInt32(); | |
break; | |
case FieldType.Int64: | |
case FieldType.SInt64: | |
case FieldType.SFixed64: | |
value = tokenizer.ConsumeInt64(); | |
break; | |
case FieldType.UInt32: | |
case FieldType.Fixed32: | |
value = tokenizer.ConsumeUInt32(); | |
break; | |
case FieldType.UInt64: | |
case FieldType.Fixed64: | |
value = tokenizer.ConsumeUInt64(); | |
break; | |
case FieldType.Float: | |
value = tokenizer.ConsumeFloat(); | |
break; | |
case FieldType.Double: | |
value = tokenizer.ConsumeDouble(); | |
break; | |
case FieldType.Bool: | |
value = tokenizer.ConsumeBoolean(); | |
break; | |
case FieldType.String: | |
value = tokenizer.ConsumeString(); | |
break; | |
case FieldType.Bytes: | |
value = tokenizer.ConsumeByteString(); | |
break; | |
case FieldType.Enum: | |
{ | |
EnumDescriptor enumType = field.EnumType; | |
if (tokenizer.LookingAtInteger()) | |
{ | |
int number = tokenizer.ConsumeInt32(); | |
value = enumType.FindValueByNumber(number); | |
if (value == null) | |
{ | |
throw tokenizer.CreateFormatExceptionPreviousToken( | |
"Enum type \"" + enumType.FullName + | |
"\" has no value with number " + number + "."); | |
} | |
} | |
else | |
{ | |
String id = tokenizer.ConsumeIdentifier(); | |
value = enumType.FindValueByName(id); | |
if (value == null) | |
{ | |
throw tokenizer.CreateFormatExceptionPreviousToken( | |
"Enum type \"" + enumType.FullName + | |
"\" has no value named \"" + id + "\"."); | |
} | |
} | |
break; | |
} | |
case FieldType.Message: | |
case FieldType.Group: | |
throw new InvalidOperationException("Can't get here."); | |
} | |
} | |
if (field.IsRepeated) | |
{ | |
builder.WeakAddRepeatedField(field, value); | |
} | |
else | |
{ | |
builder.SetField(field, value); | |
} | |
} | |
} | |
} |