|
|
|
@ -1,35 +1,404 @@ |
|
|
|
|
using System; |
|
|
|
|
using System.Collections.Generic; |
|
|
|
|
using System.IO; |
|
|
|
|
using System.Text; |
|
|
|
|
using Google.ProtocolBuffers.Descriptors; |
|
|
|
|
using System.Collections; |
|
|
|
|
|
|
|
|
|
namespace Google.ProtocolBuffers { |
|
|
|
|
public class TextFormat { |
|
|
|
|
/// <summary> |
|
|
|
|
/// Provides ASCII text formatting support for messages. |
|
|
|
|
/// TODO(jonskeet): Parsing support. |
|
|
|
|
/// </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); |
|
|
|
|
Print(message, generator); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Outputs a textual representation of <paramref name="fields" /> to <paramref name="output"/>. |
|
|
|
|
/// </summary> |
|
|
|
|
/// <param name="fields"></param> |
|
|
|
|
/// <param name="output"></param> |
|
|
|
|
public static void Print(UnknownFieldSet fields, TextWriter output) { |
|
|
|
|
TextGenerator generator = new TextGenerator(output); |
|
|
|
|
PrintUnknownFields(fields, generator); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public static string PrintToString(IMessage message) { |
|
|
|
|
throw new NotImplementedException(); |
|
|
|
|
StringWriter text = new StringWriter(); |
|
|
|
|
Print(message, 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) { |
|
|
|
|
MessageDescriptor descriptor = message.DescriptorForType; |
|
|
|
|
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) { |
|
|
|
|
case FieldType.Int32: |
|
|
|
|
case FieldType.Int64: |
|
|
|
|
case FieldType.SInt32: |
|
|
|
|
case FieldType.SInt64: |
|
|
|
|
case FieldType.SFixed32: |
|
|
|
|
case FieldType.SFixed64: |
|
|
|
|
case FieldType.Float: |
|
|
|
|
case FieldType.Double: |
|
|
|
|
case FieldType.UInt32: |
|
|
|
|
case FieldType.UInt64: |
|
|
|
|
case FieldType.Fixed32: |
|
|
|
|
case FieldType.Fixed64: |
|
|
|
|
// Good old ToString() does what we want for these types. (Including the |
|
|
|
|
// unsigned ones, unlike with Java.) |
|
|
|
|
generator.Print(value.ToString()); |
|
|
|
|
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: { |
|
|
|
|
generator.Print(((EnumValueDescriptor) value).Name); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
case FieldType.Message: |
|
|
|
|
case FieldType.Group: |
|
|
|
|
Print((IMessage) value, generator); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
internal static string PrintToString(UnknownFieldSet unknownFieldSet) { |
|
|
|
|
throw new NotImplementedException(); |
|
|
|
|
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(entry.Key.ToString()); |
|
|
|
|
generator.Print(": "); |
|
|
|
|
generator.Print(value.ToString()); |
|
|
|
|
generator.Print("\n"); |
|
|
|
|
} |
|
|
|
|
foreach (uint value in field.Fixed32List) { |
|
|
|
|
generator.Print(entry.Key.ToString()); |
|
|
|
|
generator.Print(": "); |
|
|
|
|
// FIXME(jonskeet): Get format of this right; in Java it's %08x. Find out what this means |
|
|
|
|
// Also check we're okay in terms of signed/unsigned. |
|
|
|
|
generator.Print(string.Format("0x{0:x}", value)); |
|
|
|
|
generator.Print("\n"); |
|
|
|
|
} |
|
|
|
|
foreach (ulong value in field.Fixed64List) { |
|
|
|
|
generator.Print(entry.Key.ToString()); |
|
|
|
|
generator.Print(": "); |
|
|
|
|
// FIXME(jonskeet): Get format of this right; in Java it's %016x. Find out what this means |
|
|
|
|
// Also check we're okay in terms of signed/unsigned. |
|
|
|
|
generator.Print(string.Format("0x{0:x}", 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"); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
internal static object ParseUInt64(string p) { |
|
|
|
|
throw new NotImplementedException(); |
|
|
|
|
|
|
|
|
|
internal static ulong ParseUInt64(string text) { |
|
|
|
|
return (ulong) ParseInteger(text, true, false); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
internal static object ParseInt64(string p) { |
|
|
|
|
throw new NotImplementedException(); |
|
|
|
|
internal static long ParseInt64(string text) { |
|
|
|
|
return ParseInteger(text, true, false); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
internal static object ParseUInt32(string p) { |
|
|
|
|
throw new NotImplementedException(); |
|
|
|
|
internal static uint ParseUInt32(string text) { |
|
|
|
|
return (uint) ParseInteger(text, true, false); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
internal static object ParseInt32(string p) { |
|
|
|
|
throw new NotImplementedException(); |
|
|
|
|
internal static int ParseInt32(string text) { |
|
|
|
|
return (int) ParseInteger(text, true, false); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
internal static object UnescapeBytes(string p) { |
|
|
|
|
throw new NotImplementedException(); |
|
|
|
|
/// <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; |
|
|
|
|
text = text.Substring(1); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ulong result = Convert.ToUInt64(text, radix); |
|
|
|
|
|
|
|
|
|
if (negative) { |
|
|
|
|
ulong max = isLong ? 0x8000000UL : 0x8000L; |
|
|
|
|
if (result > max) { |
|
|
|
|
throw new FormatException("Number of out range: " + original); |
|
|
|
|
} |
|
|
|
|
return -((long) result); |
|
|
|
|
} else { |
|
|
|
|
ulong max = isSigned |
|
|
|
|
? (isLong ? (ulong) long.MaxValue : int.MaxValue) |
|
|
|
|
: (isLong ? ulong.MaxValue : uint.MaxValue); |
|
|
|
|
if (result > max) { |
|
|
|
|
throw new FormatException("Number of out range: " + 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> |
|
|
|
|
/// 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> |
|
|
|
|
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> |
|
|
|
|
private 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) { |
|
|
|
|
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> |
|
|
|
|
internal 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); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|