| OLD | NEW |
| 1 # Protocol Buffers - Google's data interchange format | 1 # Protocol Buffers - Google's data interchange format |
| 2 # Copyright 2008 Google Inc. All rights reserved. | 2 # Copyright 2008 Google Inc. All rights reserved. |
| 3 # http://code.google.com/p/protobuf/ | 3 # https://developers.google.com/protocol-buffers/ |
| 4 # | 4 # |
| 5 # Redistribution and use in source and binary forms, with or without | 5 # Redistribution and use in source and binary forms, with or without |
| 6 # modification, are permitted provided that the following conditions are | 6 # modification, are permitted provided that the following conditions are |
| 7 # met: | 7 # met: |
| 8 # | 8 # |
| 9 # * Redistributions of source code must retain the above copyright | 9 # * Redistributions of source code must retain the above copyright |
| 10 # notice, this list of conditions and the following disclaimer. | 10 # notice, this list of conditions and the following disclaimer. |
| 11 # * Redistributions in binary form must reproduce the above | 11 # * Redistributions in binary form must reproduce the above |
| 12 # copyright notice, this list of conditions and the following disclaimer | 12 # copyright notice, this list of conditions and the following disclaimer |
| 13 # in the documentation and/or other materials provided with the | 13 # in the documentation and/or other materials provided with the |
| 14 # distribution. | 14 # distribution. |
| 15 # * Neither the name of Google Inc. nor the names of its | 15 # * Neither the name of Google Inc. nor the names of its |
| 16 # contributors may be used to endorse or promote products derived from | 16 # contributors may be used to endorse or promote products derived from |
| 17 # this software without specific prior written permission. | 17 # this software without specific prior written permission. |
| 18 # | 18 # |
| 19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 30 | 30 |
| 31 #PY25 compatible for GAE. |
| 32 # |
| 33 # Copyright 2007 Google Inc. All Rights Reserved. |
| 34 |
| 31 """Contains routines for printing protocol messages in text format.""" | 35 """Contains routines for printing protocol messages in text format.""" |
| 32 | 36 |
| 33 __author__ = 'kenton@google.com (Kenton Varda)' | 37 __author__ = 'kenton@google.com (Kenton Varda)' |
| 34 | 38 |
| 35 import cStringIO | 39 import cStringIO |
| 36 import re | 40 import re |
| 37 | 41 |
| 38 from collections import deque | |
| 39 from google.protobuf.internal import type_checkers | 42 from google.protobuf.internal import type_checkers |
| 40 from google.protobuf import descriptor | 43 from google.protobuf import descriptor |
| 44 from google.protobuf import text_encoding |
| 41 | 45 |
| 42 __all__ = [ 'MessageToString', 'PrintMessage', 'PrintField', | 46 __all__ = ['MessageToString', 'PrintMessage', 'PrintField', |
| 43 'PrintFieldValue', 'Merge' ] | 47 'PrintFieldValue', 'Merge'] |
| 44 | 48 |
| 45 | 49 |
| 46 _INTEGER_CHECKERS = (type_checkers.Uint32ValueChecker(), | 50 _INTEGER_CHECKERS = (type_checkers.Uint32ValueChecker(), |
| 47 type_checkers.Int32ValueChecker(), | 51 type_checkers.Int32ValueChecker(), |
| 48 type_checkers.Uint64ValueChecker(), | 52 type_checkers.Uint64ValueChecker(), |
| 49 type_checkers.Int64ValueChecker()) | 53 type_checkers.Int64ValueChecker()) |
| 50 _FLOAT_INFINITY = re.compile('-?inf(?:inity)?f?', re.IGNORECASE) | 54 _FLOAT_INFINITY = re.compile('-?inf(?:inity)?f?', re.IGNORECASE) |
| 51 _FLOAT_NAN = re.compile('nanf?', re.IGNORECASE) | 55 _FLOAT_NAN = re.compile('nanf?', re.IGNORECASE) |
| 56 _FLOAT_TYPES = frozenset([descriptor.FieldDescriptor.CPPTYPE_FLOAT, |
| 57 descriptor.FieldDescriptor.CPPTYPE_DOUBLE]) |
| 52 | 58 |
| 53 | 59 |
| 54 class ParseError(Exception): | 60 class Error(Exception): |
| 61 """Top-level module error for text_format.""" |
| 62 |
| 63 |
| 64 class ParseError(Error): |
| 55 """Thrown in case of ASCII parsing error.""" | 65 """Thrown in case of ASCII parsing error.""" |
| 56 | 66 |
| 57 | 67 |
| 58 def MessageToString(message, as_utf8=False, as_one_line=False): | 68 def MessageToString(message, as_utf8=False, as_one_line=False, |
| 69 pointy_brackets=False, use_index_order=False, |
| 70 float_format=None): |
| 71 """Convert protobuf message to text format. |
| 72 |
| 73 Floating point values can be formatted compactly with 15 digits of |
| 74 precision (which is the most that IEEE 754 "double" can guarantee) |
| 75 using float_format='.15g'. |
| 76 |
| 77 Args: |
| 78 message: The protocol buffers message. |
| 79 as_utf8: Produce text output in UTF8 format. |
| 80 as_one_line: Don't introduce newlines between fields. |
| 81 pointy_brackets: If True, use angle brackets instead of curly braces for |
| 82 nesting. |
| 83 use_index_order: If True, print fields of a proto message using the order |
| 84 defined in source code instead of the field number. By default, use the |
| 85 field number order. |
| 86 float_format: If set, use this to specify floating point number formatting |
| 87 (per the "Format Specification Mini-Language"); otherwise, str() is used. |
| 88 |
| 89 Returns: |
| 90 A string of the text formatted protocol buffer message. |
| 91 """ |
| 59 out = cStringIO.StringIO() | 92 out = cStringIO.StringIO() |
| 60 PrintMessage(message, out, as_utf8=as_utf8, as_one_line=as_one_line) | 93 PrintMessage(message, out, as_utf8=as_utf8, as_one_line=as_one_line, |
| 94 pointy_brackets=pointy_brackets, |
| 95 use_index_order=use_index_order, |
| 96 float_format=float_format) |
| 61 result = out.getvalue() | 97 result = out.getvalue() |
| 62 out.close() | 98 out.close() |
| 63 if as_one_line: | 99 if as_one_line: |
| 64 return result.rstrip() | 100 return result.rstrip() |
| 65 return result | 101 return result |
| 66 | 102 |
| 103 def _IsMapEntry(field): |
| 104 return (field.type == descriptor.FieldDescriptor.TYPE_MESSAGE and |
| 105 field.message_type.has_options and |
| 106 field.message_type.GetOptions().map_entry) |
| 67 | 107 |
| 68 def PrintMessage(message, out, indent=0, as_utf8=False, as_one_line=False): | 108 def PrintMessage(message, out, indent=0, as_utf8=False, as_one_line=False, |
| 69 for field, value in message.ListFields(): | 109 pointy_brackets=False, use_index_order=False, |
| 70 if field.label == descriptor.FieldDescriptor.LABEL_REPEATED: | 110 float_format=None): |
| 111 fields = message.ListFields() |
| 112 if use_index_order: |
| 113 fields.sort(key=lambda x: x[0].index) |
| 114 for field, value in fields: |
| 115 if _IsMapEntry(field): |
| 116 for key in value: |
| 117 # This is slow for maps with submessage entires because it copies the |
| 118 # entire tree. Unfortunately this would take significant refactoring |
| 119 # of this file to work around. |
| 120 # |
| 121 # TODO(haberman): refactor and optimize if this becomes an issue. |
| 122 entry_submsg = field.message_type._concrete_class( |
| 123 key=key, value=value[key]) |
| 124 PrintField(field, entry_submsg, out, indent, as_utf8, as_one_line, |
| 125 pointy_brackets=pointy_brackets, |
| 126 use_index_order=use_index_order, float_format=float_format) |
| 127 elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED: |
| 71 for element in value: | 128 for element in value: |
| 72 PrintField(field, element, out, indent, as_utf8, as_one_line) | 129 PrintField(field, element, out, indent, as_utf8, as_one_line, |
| 130 pointy_brackets=pointy_brackets, |
| 131 use_index_order=use_index_order, |
| 132 float_format=float_format) |
| 73 else: | 133 else: |
| 74 PrintField(field, value, out, indent, as_utf8, as_one_line) | 134 PrintField(field, value, out, indent, as_utf8, as_one_line, |
| 135 pointy_brackets=pointy_brackets, |
| 136 use_index_order=use_index_order, |
| 137 float_format=float_format) |
| 75 | 138 |
| 76 | 139 |
| 77 def PrintField(field, value, out, indent=0, as_utf8=False, as_one_line=False): | 140 def PrintField(field, value, out, indent=0, as_utf8=False, as_one_line=False, |
| 141 pointy_brackets=False, use_index_order=False, float_format=None): |
| 78 """Print a single field name/value pair. For repeated fields, the value | 142 """Print a single field name/value pair. For repeated fields, the value |
| 79 should be a single element.""" | 143 should be a single element.""" |
| 80 | 144 |
| 81 out.write(' ' * indent); | 145 out.write(' ' * indent) |
| 82 if field.is_extension: | 146 if field.is_extension: |
| 83 out.write('[') | 147 out.write('[') |
| 84 if (field.containing_type.GetOptions().message_set_wire_format and | 148 if (field.containing_type.GetOptions().message_set_wire_format and |
| 85 field.type == descriptor.FieldDescriptor.TYPE_MESSAGE and | 149 field.type == descriptor.FieldDescriptor.TYPE_MESSAGE and |
| 86 field.message_type == field.extension_scope and | 150 field.message_type == field.extension_scope and |
| 87 field.label == descriptor.FieldDescriptor.LABEL_OPTIONAL): | 151 field.label == descriptor.FieldDescriptor.LABEL_OPTIONAL): |
| 88 out.write(field.message_type.full_name) | 152 out.write(field.message_type.full_name) |
| 89 else: | 153 else: |
| 90 out.write(field.full_name) | 154 out.write(field.full_name) |
| 91 out.write(']') | 155 out.write(']') |
| 92 elif field.type == descriptor.FieldDescriptor.TYPE_GROUP: | 156 elif field.type == descriptor.FieldDescriptor.TYPE_GROUP: |
| 93 # For groups, use the capitalized name. | 157 # For groups, use the capitalized name. |
| 94 out.write(field.message_type.name) | 158 out.write(field.message_type.name) |
| 95 else: | 159 else: |
| 96 out.write(field.name) | 160 out.write(field.name) |
| 97 | 161 |
| 98 if field.cpp_type != descriptor.FieldDescriptor.CPPTYPE_MESSAGE: | 162 if field.cpp_type != descriptor.FieldDescriptor.CPPTYPE_MESSAGE: |
| 99 # The colon is optional in this case, but our cross-language golden files | 163 # The colon is optional in this case, but our cross-language golden files |
| 100 # don't include it. | 164 # don't include it. |
| 101 out.write(': ') | 165 out.write(': ') |
| 102 | 166 |
| 103 PrintFieldValue(field, value, out, indent, as_utf8, as_one_line) | 167 PrintFieldValue(field, value, out, indent, as_utf8, as_one_line, |
| 168 pointy_brackets=pointy_brackets, |
| 169 use_index_order=use_index_order, |
| 170 float_format=float_format) |
| 104 if as_one_line: | 171 if as_one_line: |
| 105 out.write(' ') | 172 out.write(' ') |
| 106 else: | 173 else: |
| 107 out.write('\n') | 174 out.write('\n') |
| 108 | 175 |
| 109 | 176 |
| 110 def PrintFieldValue(field, value, out, indent=0, | 177 def PrintFieldValue(field, value, out, indent=0, as_utf8=False, |
| 111 as_utf8=False, as_one_line=False): | 178 as_one_line=False, pointy_brackets=False, |
| 179 use_index_order=False, |
| 180 float_format=None): |
| 112 """Print a single field value (not including name). For repeated fields, | 181 """Print a single field value (not including name). For repeated fields, |
| 113 the value should be a single element.""" | 182 the value should be a single element.""" |
| 114 | 183 |
| 184 if pointy_brackets: |
| 185 openb = '<' |
| 186 closeb = '>' |
| 187 else: |
| 188 openb = '{' |
| 189 closeb = '}' |
| 190 |
| 115 if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: | 191 if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: |
| 116 if as_one_line: | 192 if as_one_line: |
| 117 out.write(' { ') | 193 out.write(' %s ' % openb) |
| 118 PrintMessage(value, out, indent, as_utf8, as_one_line) | 194 PrintMessage(value, out, indent, as_utf8, as_one_line, |
| 119 out.write('}') | 195 pointy_brackets=pointy_brackets, |
| 196 use_index_order=use_index_order, |
| 197 float_format=float_format) |
| 198 out.write(closeb) |
| 120 else: | 199 else: |
| 121 out.write(' {\n') | 200 out.write(' %s\n' % openb) |
| 122 PrintMessage(value, out, indent + 2, as_utf8, as_one_line) | 201 PrintMessage(value, out, indent + 2, as_utf8, as_one_line, |
| 123 out.write(' ' * indent + '}') | 202 pointy_brackets=pointy_brackets, |
| 203 use_index_order=use_index_order, |
| 204 float_format=float_format) |
| 205 out.write(' ' * indent + closeb) |
| 124 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM: | 206 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM: |
| 125 enum_value = field.enum_type.values_by_number.get(value, None) | 207 enum_value = field.enum_type.values_by_number.get(value, None) |
| 126 if enum_value is not None: | 208 if enum_value is not None: |
| 127 out.write(enum_value.name) | 209 out.write(enum_value.name) |
| 128 else: | 210 else: |
| 129 out.write(str(value)) | 211 out.write(str(value)) |
| 130 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_STRING: | 212 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_STRING: |
| 131 out.write('\"') | 213 out.write('\"') |
| 132 if type(value) is unicode: | 214 if isinstance(value, unicode): |
| 133 out.write(_CEscape(value.encode('utf-8'), as_utf8)) | 215 out_value = value.encode('utf-8') |
| 134 else: | 216 else: |
| 135 out.write(_CEscape(value, as_utf8)) | 217 out_value = value |
| 218 if field.type == descriptor.FieldDescriptor.TYPE_BYTES: |
| 219 # We need to escape non-UTF8 chars in TYPE_BYTES field. |
| 220 out_as_utf8 = False |
| 221 else: |
| 222 out_as_utf8 = as_utf8 |
| 223 out.write(text_encoding.CEscape(out_value, out_as_utf8)) |
| 136 out.write('\"') | 224 out.write('\"') |
| 137 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_BOOL: | 225 elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_BOOL: |
| 138 if value: | 226 if value: |
| 139 out.write("true") | 227 out.write('true') |
| 140 else: | 228 else: |
| 141 out.write("false") | 229 out.write('false') |
| 230 elif field.cpp_type in _FLOAT_TYPES and float_format is not None: |
| 231 out.write('{1:{0}}'.format(float_format, value)) |
| 142 else: | 232 else: |
| 143 out.write(str(value)) | 233 out.write(str(value)) |
| 144 | 234 |
| 145 | 235 |
| 146 def Merge(text, message): | 236 def Parse(text, message): |
| 147 """Merges an ASCII representation of a protocol message into a message. | 237 """Parses an ASCII representation of a protocol message into a message. |
| 148 | 238 |
| 149 Args: | 239 Args: |
| 150 text: Message ASCII representation. | 240 text: Message ASCII representation. |
| 151 message: A protocol buffer message to merge into. | 241 message: A protocol buffer message to merge into. |
| 152 | 242 |
| 243 Returns: |
| 244 The same message passed as argument. |
| 245 |
| 153 Raises: | 246 Raises: |
| 154 ParseError: On ASCII parsing problems. | 247 ParseError: On ASCII parsing problems. |
| 155 """ | 248 """ |
| 156 tokenizer = _Tokenizer(text) | 249 if not isinstance(text, str): text = text.decode('utf-8') |
| 157 while not tokenizer.AtEnd(): | 250 return ParseLines(text.split('\n'), message) |
| 158 _MergeField(tokenizer, message) | |
| 159 | 251 |
| 160 | 252 |
| 161 def _MergeField(tokenizer, message): | 253 def Merge(text, message): |
| 254 """Parses an ASCII representation of a protocol message into a message. |
| 255 |
| 256 Like Parse(), but allows repeated values for a non-repeated field, and uses |
| 257 the last one. |
| 258 |
| 259 Args: |
| 260 text: Message ASCII representation. |
| 261 message: A protocol buffer message to merge into. |
| 262 |
| 263 Returns: |
| 264 The same message passed as argument. |
| 265 |
| 266 Raises: |
| 267 ParseError: On ASCII parsing problems. |
| 268 """ |
| 269 return MergeLines(text.split('\n'), message) |
| 270 |
| 271 |
| 272 def ParseLines(lines, message): |
| 273 """Parses an ASCII representation of a protocol message into a message. |
| 274 |
| 275 Args: |
| 276 lines: An iterable of lines of a message's ASCII representation. |
| 277 message: A protocol buffer message to merge into. |
| 278 |
| 279 Returns: |
| 280 The same message passed as argument. |
| 281 |
| 282 Raises: |
| 283 ParseError: On ASCII parsing problems. |
| 284 """ |
| 285 _ParseOrMerge(lines, message, False) |
| 286 return message |
| 287 |
| 288 |
| 289 def MergeLines(lines, message): |
| 290 """Parses an ASCII representation of a protocol message into a message. |
| 291 |
| 292 Args: |
| 293 lines: An iterable of lines of a message's ASCII representation. |
| 294 message: A protocol buffer message to merge into. |
| 295 |
| 296 Returns: |
| 297 The same message passed as argument. |
| 298 |
| 299 Raises: |
| 300 ParseError: On ASCII parsing problems. |
| 301 """ |
| 302 _ParseOrMerge(lines, message, True) |
| 303 return message |
| 304 |
| 305 |
| 306 def _ParseOrMerge(lines, message, allow_multiple_scalars): |
| 307 """Converts an ASCII representation of a protocol message into a message. |
| 308 |
| 309 Args: |
| 310 lines: Lines of a message's ASCII representation. |
| 311 message: A protocol buffer message to merge into. |
| 312 allow_multiple_scalars: Determines if repeated values for a non-repeated |
| 313 field are permitted, e.g., the string "foo: 1 foo: 2" for a |
| 314 required/optional field named "foo". |
| 315 |
| 316 Raises: |
| 317 ParseError: On ASCII parsing problems. |
| 318 """ |
| 319 tokenizer = _Tokenizer(lines) |
| 320 while not tokenizer.AtEnd(): |
| 321 _MergeField(tokenizer, message, allow_multiple_scalars) |
| 322 |
| 323 |
| 324 def _MergeField(tokenizer, message, allow_multiple_scalars): |
| 162 """Merges a single protocol message field into a message. | 325 """Merges a single protocol message field into a message. |
| 163 | 326 |
| 164 Args: | 327 Args: |
| 165 tokenizer: A tokenizer to parse the field name and values. | 328 tokenizer: A tokenizer to parse the field name and values. |
| 166 message: A protocol message to record the data. | 329 message: A protocol message to record the data. |
| 330 allow_multiple_scalars: Determines if repeated values for a non-repeated |
| 331 field are permitted, e.g., the string "foo: 1 foo: 2" for a |
| 332 required/optional field named "foo". |
| 167 | 333 |
| 168 Raises: | 334 Raises: |
| 169 ParseError: In case of ASCII parsing problems. | 335 ParseError: In case of ASCII parsing problems. |
| 170 """ | 336 """ |
| 171 message_descriptor = message.DESCRIPTOR | 337 message_descriptor = message.DESCRIPTOR |
| 338 if (hasattr(message_descriptor, 'syntax') and |
| 339 message_descriptor.syntax == 'proto3'): |
| 340 # Proto3 doesn't represent presence so we can't test if multiple |
| 341 # scalars have occurred. We have to allow them. |
| 342 allow_multiple_scalars = True |
| 172 if tokenizer.TryConsume('['): | 343 if tokenizer.TryConsume('['): |
| 173 name = [tokenizer.ConsumeIdentifier()] | 344 name = [tokenizer.ConsumeIdentifier()] |
| 174 while tokenizer.TryConsume('.'): | 345 while tokenizer.TryConsume('.'): |
| 175 name.append(tokenizer.ConsumeIdentifier()) | 346 name.append(tokenizer.ConsumeIdentifier()) |
| 176 name = '.'.join(name) | 347 name = '.'.join(name) |
| 177 | 348 |
| 178 if not message_descriptor.is_extendable: | 349 if not message_descriptor.is_extendable: |
| 179 raise tokenizer.ParseErrorPreviousToken( | 350 raise tokenizer.ParseErrorPreviousToken( |
| 180 'Message type "%s" does not have extensions.' % | 351 'Message type "%s" does not have extensions.' % |
| 181 message_descriptor.full_name) | 352 message_descriptor.full_name) |
| 353 # pylint: disable=protected-access |
| 182 field = message.Extensions._FindExtensionByName(name) | 354 field = message.Extensions._FindExtensionByName(name) |
| 355 # pylint: enable=protected-access |
| 183 if not field: | 356 if not field: |
| 184 raise tokenizer.ParseErrorPreviousToken( | 357 raise tokenizer.ParseErrorPreviousToken( |
| 185 'Extension "%s" not registered.' % name) | 358 'Extension "%s" not registered.' % name) |
| 186 elif message_descriptor != field.containing_type: | 359 elif message_descriptor != field.containing_type: |
| 187 raise tokenizer.ParseErrorPreviousToken( | 360 raise tokenizer.ParseErrorPreviousToken( |
| 188 'Extension "%s" does not extend message type "%s".' % ( | 361 'Extension "%s" does not extend message type "%s".' % ( |
| 189 name, message_descriptor.full_name)) | 362 name, message_descriptor.full_name)) |
| 190 tokenizer.Consume(']') | 363 tokenizer.Consume(']') |
| 191 else: | 364 else: |
| 192 name = tokenizer.ConsumeIdentifier() | 365 name = tokenizer.ConsumeIdentifier() |
| (...skipping 10 matching lines...) Expand all Loading... |
| 203 if (field and field.type == descriptor.FieldDescriptor.TYPE_GROUP and | 376 if (field and field.type == descriptor.FieldDescriptor.TYPE_GROUP and |
| 204 field.message_type.name != name): | 377 field.message_type.name != name): |
| 205 field = None | 378 field = None |
| 206 | 379 |
| 207 if not field: | 380 if not field: |
| 208 raise tokenizer.ParseErrorPreviousToken( | 381 raise tokenizer.ParseErrorPreviousToken( |
| 209 'Message type "%s" has no field named "%s".' % ( | 382 'Message type "%s" has no field named "%s".' % ( |
| 210 message_descriptor.full_name, name)) | 383 message_descriptor.full_name, name)) |
| 211 | 384 |
| 212 if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: | 385 if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: |
| 386 is_map_entry = _IsMapEntry(field) |
| 213 tokenizer.TryConsume(':') | 387 tokenizer.TryConsume(':') |
| 214 | 388 |
| 215 if tokenizer.TryConsume('<'): | 389 if tokenizer.TryConsume('<'): |
| 216 end_token = '>' | 390 end_token = '>' |
| 217 else: | 391 else: |
| 218 tokenizer.Consume('{') | 392 tokenizer.Consume('{') |
| 219 end_token = '}' | 393 end_token = '}' |
| 220 | 394 |
| 221 if field.label == descriptor.FieldDescriptor.LABEL_REPEATED: | 395 if field.label == descriptor.FieldDescriptor.LABEL_REPEATED: |
| 222 if field.is_extension: | 396 if field.is_extension: |
| 223 sub_message = message.Extensions[field].add() | 397 sub_message = message.Extensions[field].add() |
| 398 elif is_map_entry: |
| 399 sub_message = field.message_type._concrete_class() |
| 224 else: | 400 else: |
| 225 sub_message = getattr(message, field.name).add() | 401 sub_message = getattr(message, field.name).add() |
| 226 else: | 402 else: |
| 227 if field.is_extension: | 403 if field.is_extension: |
| 228 sub_message = message.Extensions[field] | 404 sub_message = message.Extensions[field] |
| 229 else: | 405 else: |
| 230 sub_message = getattr(message, field.name) | 406 sub_message = getattr(message, field.name) |
| 231 sub_message.SetInParent() | 407 sub_message.SetInParent() |
| 232 | 408 |
| 233 while not tokenizer.TryConsume(end_token): | 409 while not tokenizer.TryConsume(end_token): |
| 234 if tokenizer.AtEnd(): | 410 if tokenizer.AtEnd(): |
| 235 raise tokenizer.ParseErrorPreviousToken('Expected "%s".' % (end_token)) | 411 raise tokenizer.ParseErrorPreviousToken('Expected "%s".' % (end_token)) |
| 236 _MergeField(tokenizer, sub_message) | 412 _MergeField(tokenizer, sub_message, allow_multiple_scalars) |
| 413 |
| 414 if is_map_entry: |
| 415 value_cpptype = field.message_type.fields_by_name['value'].cpp_type |
| 416 if value_cpptype == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: |
| 417 value = getattr(message, field.name)[sub_message.key] |
| 418 value.MergeFrom(sub_message.value) |
| 419 else: |
| 420 getattr(message, field.name)[sub_message.key] = sub_message.value |
| 237 else: | 421 else: |
| 238 _MergeScalarField(tokenizer, message, field) | 422 _MergeScalarField(tokenizer, message, field, allow_multiple_scalars) |
| 423 |
| 424 # For historical reasons, fields may optionally be separated by commas or |
| 425 # semicolons. |
| 426 if not tokenizer.TryConsume(','): |
| 427 tokenizer.TryConsume(';') |
| 239 | 428 |
| 240 | 429 |
| 241 def _MergeScalarField(tokenizer, message, field): | 430 def _MergeScalarField(tokenizer, message, field, allow_multiple_scalars): |
| 242 """Merges a single protocol message scalar field into a message. | 431 """Merges a single protocol message scalar field into a message. |
| 243 | 432 |
| 244 Args: | 433 Args: |
| 245 tokenizer: A tokenizer to parse the field value. | 434 tokenizer: A tokenizer to parse the field value. |
| 246 message: A protocol message to record the data. | 435 message: A protocol message to record the data. |
| 247 field: The descriptor of the field to be merged. | 436 field: The descriptor of the field to be merged. |
| 437 allow_multiple_scalars: Determines if repeated values for a non-repeated |
| 438 field are permitted, e.g., the string "foo: 1 foo: 2" for a |
| 439 required/optional field named "foo". |
| 248 | 440 |
| 249 Raises: | 441 Raises: |
| 250 ParseError: In case of ASCII parsing problems. | 442 ParseError: In case of ASCII parsing problems. |
| 251 RuntimeError: On runtime errors. | 443 RuntimeError: On runtime errors. |
| 252 """ | 444 """ |
| 253 tokenizer.Consume(':') | 445 tokenizer.Consume(':') |
| 254 value = None | 446 value = None |
| 255 | 447 |
| 256 if field.type in (descriptor.FieldDescriptor.TYPE_INT32, | 448 if field.type in (descriptor.FieldDescriptor.TYPE_INT32, |
| 257 descriptor.FieldDescriptor.TYPE_SINT32, | 449 descriptor.FieldDescriptor.TYPE_SINT32, |
| (...skipping 23 matching lines...) Expand all Loading... |
| 281 else: | 473 else: |
| 282 raise RuntimeError('Unknown field type %d' % field.type) | 474 raise RuntimeError('Unknown field type %d' % field.type) |
| 283 | 475 |
| 284 if field.label == descriptor.FieldDescriptor.LABEL_REPEATED: | 476 if field.label == descriptor.FieldDescriptor.LABEL_REPEATED: |
| 285 if field.is_extension: | 477 if field.is_extension: |
| 286 message.Extensions[field].append(value) | 478 message.Extensions[field].append(value) |
| 287 else: | 479 else: |
| 288 getattr(message, field.name).append(value) | 480 getattr(message, field.name).append(value) |
| 289 else: | 481 else: |
| 290 if field.is_extension: | 482 if field.is_extension: |
| 291 message.Extensions[field] = value | 483 if not allow_multiple_scalars and message.HasExtension(field): |
| 484 raise tokenizer.ParseErrorPreviousToken( |
| 485 'Message type "%s" should not have multiple "%s" extensions.' % |
| 486 (message.DESCRIPTOR.full_name, field.full_name)) |
| 487 else: |
| 488 message.Extensions[field] = value |
| 292 else: | 489 else: |
| 293 setattr(message, field.name, value) | 490 if not allow_multiple_scalars and message.HasField(field.name): |
| 491 raise tokenizer.ParseErrorPreviousToken( |
| 492 'Message type "%s" should not have multiple "%s" fields.' % |
| 493 (message.DESCRIPTOR.full_name, field.name)) |
| 494 else: |
| 495 setattr(message, field.name, value) |
| 294 | 496 |
| 295 | 497 |
| 296 class _Tokenizer(object): | 498 class _Tokenizer(object): |
| 297 """Protocol buffer ASCII representation tokenizer. | 499 """Protocol buffer ASCII representation tokenizer. |
| 298 | 500 |
| 299 This class handles the lower level string parsing by splitting it into | 501 This class handles the lower level string parsing by splitting it into |
| 300 meaningful tokens. | 502 meaningful tokens. |
| 301 | 503 |
| 302 It was directly ported from the Java protocol buffer API. | 504 It was directly ported from the Java protocol buffer API. |
| 303 """ | 505 """ |
| 304 | 506 |
| 305 _WHITESPACE = re.compile('(\\s|(#.*$))+', re.MULTILINE) | 507 _WHITESPACE = re.compile('(\\s|(#.*$))+', re.MULTILINE) |
| 306 _TOKEN = re.compile( | 508 _TOKEN = re.compile( |
| 307 '[a-zA-Z_][0-9a-zA-Z_+-]*|' # an identifier | 509 '[a-zA-Z_][0-9a-zA-Z_+-]*|' # an identifier |
| 308 '[0-9+-][0-9a-zA-Z_.+-]*|' # a number | 510 '[0-9+-][0-9a-zA-Z_.+-]*|' # a number |
| 309 '\"([^\"\n\\\\]|\\\\.)*(\"|\\\\?$)|' # a double-quoted string | 511 '\"([^\"\n\\\\]|\\\\.)*(\"|\\\\?$)|' # a double-quoted string |
| 310 '\'([^\'\n\\\\]|\\\\.)*(\'|\\\\?$)') # a single-quoted string | 512 '\'([^\'\n\\\\]|\\\\.)*(\'|\\\\?$)') # a single-quoted string |
| 311 _IDENTIFIER = re.compile('\w+') | 513 _IDENTIFIER = re.compile(r'\w+') |
| 312 | 514 |
| 313 def __init__(self, text_message): | 515 def __init__(self, lines): |
| 314 self._text_message = text_message | |
| 315 | |
| 316 self._position = 0 | 516 self._position = 0 |
| 317 self._line = -1 | 517 self._line = -1 |
| 318 self._column = 0 | 518 self._column = 0 |
| 319 self._token_start = None | 519 self._token_start = None |
| 320 self.token = '' | 520 self.token = '' |
| 321 self._lines = deque(text_message.split('\n')) | 521 self._lines = iter(lines) |
| 322 self._current_line = '' | 522 self._current_line = '' |
| 323 self._previous_line = 0 | 523 self._previous_line = 0 |
| 324 self._previous_column = 0 | 524 self._previous_column = 0 |
| 525 self._more_lines = True |
| 325 self._SkipWhitespace() | 526 self._SkipWhitespace() |
| 326 self.NextToken() | 527 self.NextToken() |
| 327 | 528 |
| 328 def AtEnd(self): | 529 def AtEnd(self): |
| 329 """Checks the end of the text was reached. | 530 """Checks the end of the text was reached. |
| 330 | 531 |
| 331 Returns: | 532 Returns: |
| 332 True iff the end was reached. | 533 True iff the end was reached. |
| 333 """ | 534 """ |
| 334 return self.token == '' | 535 return not self.token |
| 335 | 536 |
| 336 def _PopLine(self): | 537 def _PopLine(self): |
| 337 while len(self._current_line) <= self._column: | 538 while len(self._current_line) <= self._column: |
| 338 if not self._lines: | 539 try: |
| 540 self._current_line = self._lines.next() |
| 541 except StopIteration: |
| 339 self._current_line = '' | 542 self._current_line = '' |
| 543 self._more_lines = False |
| 340 return | 544 return |
| 341 self._line += 1 | 545 else: |
| 342 self._column = 0 | 546 self._line += 1 |
| 343 self._current_line = self._lines.popleft() | 547 self._column = 0 |
| 344 | 548 |
| 345 def _SkipWhitespace(self): | 549 def _SkipWhitespace(self): |
| 346 while True: | 550 while True: |
| 347 self._PopLine() | 551 self._PopLine() |
| 348 match = self._WHITESPACE.match(self._current_line, self._column) | 552 match = self._WHITESPACE.match(self._current_line, self._column) |
| 349 if not match: | 553 if not match: |
| 350 break | 554 break |
| 351 length = len(match.group(0)) | 555 length = len(match.group(0)) |
| 352 self._column += length | 556 self._column += length |
| 353 | 557 |
| (...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 490 | 694 |
| 491 def ConsumeString(self): | 695 def ConsumeString(self): |
| 492 """Consumes a string value. | 696 """Consumes a string value. |
| 493 | 697 |
| 494 Returns: | 698 Returns: |
| 495 The string parsed. | 699 The string parsed. |
| 496 | 700 |
| 497 Raises: | 701 Raises: |
| 498 ParseError: If a string value couldn't be consumed. | 702 ParseError: If a string value couldn't be consumed. |
| 499 """ | 703 """ |
| 500 bytes = self.ConsumeByteString() | 704 the_bytes = self.ConsumeByteString() |
| 501 try: | 705 try: |
| 502 return unicode(bytes, 'utf-8') | 706 return unicode(the_bytes, 'utf-8') |
| 503 except UnicodeDecodeError, e: | 707 except UnicodeDecodeError, e: |
| 504 raise self._StringParseError(e) | 708 raise self._StringParseError(e) |
| 505 | 709 |
| 506 def ConsumeByteString(self): | 710 def ConsumeByteString(self): |
| 507 """Consumes a byte array value. | 711 """Consumes a byte array value. |
| 508 | 712 |
| 509 Returns: | 713 Returns: |
| 510 The array parsed (as a string). | 714 The array parsed (as a string). |
| 511 | 715 |
| 512 Raises: | 716 Raises: |
| 513 ParseError: If a byte array value couldn't be consumed. | 717 ParseError: If a byte array value couldn't be consumed. |
| 514 """ | 718 """ |
| 515 list = [self._ConsumeSingleByteString()] | 719 the_list = [self._ConsumeSingleByteString()] |
| 516 while len(self.token) > 0 and self.token[0] in ('\'', '"'): | 720 while self.token and self.token[0] in ('\'', '"'): |
| 517 list.append(self._ConsumeSingleByteString()) | 721 the_list.append(self._ConsumeSingleByteString()) |
| 518 return "".join(list) | 722 return ''.encode('latin1').join(the_list) ##PY25 |
| 723 ##!PY25 return b''.join(the_list) |
| 519 | 724 |
| 520 def _ConsumeSingleByteString(self): | 725 def _ConsumeSingleByteString(self): |
| 521 """Consume one token of a string literal. | 726 """Consume one token of a string literal. |
| 522 | 727 |
| 523 String literals (whether bytes or text) can come in multiple adjacent | 728 String literals (whether bytes or text) can come in multiple adjacent |
| 524 tokens which are automatically concatenated, like in C or Python. This | 729 tokens which are automatically concatenated, like in C or Python. This |
| 525 method only consumes one token. | 730 method only consumes one token. |
| 731 |
| 732 Raises: |
| 733 ParseError: When the wrong format data is found. |
| 526 """ | 734 """ |
| 527 text = self.token | 735 text = self.token |
| 528 if len(text) < 1 or text[0] not in ('\'', '"'): | 736 if len(text) < 1 or text[0] not in ('\'', '"'): |
| 529 raise self._ParseError('Expected string.') | 737 raise self._ParseError('Expected string but found: %r' % (text,)) |
| 530 | 738 |
| 531 if len(text) < 2 or text[-1] != text[0]: | 739 if len(text) < 2 or text[-1] != text[0]: |
| 532 raise self._ParseError('String missing ending quote.') | 740 raise self._ParseError('String missing ending quote: %r' % (text,)) |
| 533 | 741 |
| 534 try: | 742 try: |
| 535 result = _CUnescape(text[1:-1]) | 743 result = text_encoding.CUnescape(text[1:-1]) |
| 536 except ValueError, e: | 744 except ValueError, e: |
| 537 raise self._ParseError(str(e)) | 745 raise self._ParseError(str(e)) |
| 538 self.NextToken() | 746 self.NextToken() |
| 539 return result | 747 return result |
| 540 | 748 |
| 541 def ConsumeEnum(self, field): | 749 def ConsumeEnum(self, field): |
| 542 try: | 750 try: |
| 543 result = ParseEnum(field, self.token) | 751 result = ParseEnum(field, self.token) |
| 544 except ValueError, e: | 752 except ValueError, e: |
| 545 raise self._ParseError(str(e)) | 753 raise self._ParseError(str(e)) |
| (...skipping 21 matching lines...) Expand all Loading... |
| 567 return self._ParseError('Couldn\'t parse string: ' + str(e)) | 775 return self._ParseError('Couldn\'t parse string: ' + str(e)) |
| 568 | 776 |
| 569 def NextToken(self): | 777 def NextToken(self): |
| 570 """Reads the next meaningful token.""" | 778 """Reads the next meaningful token.""" |
| 571 self._previous_line = self._line | 779 self._previous_line = self._line |
| 572 self._previous_column = self._column | 780 self._previous_column = self._column |
| 573 | 781 |
| 574 self._column += len(self.token) | 782 self._column += len(self.token) |
| 575 self._SkipWhitespace() | 783 self._SkipWhitespace() |
| 576 | 784 |
| 577 if not self._lines and len(self._current_line) <= self._column: | 785 if not self._more_lines: |
| 578 self.token = '' | 786 self.token = '' |
| 579 return | 787 return |
| 580 | 788 |
| 581 match = self._TOKEN.match(self._current_line, self._column) | 789 match = self._TOKEN.match(self._current_line, self._column) |
| 582 if match: | 790 if match: |
| 583 token = match.group(0) | 791 token = match.group(0) |
| 584 self.token = token | 792 self.token = token |
| 585 else: | 793 else: |
| 586 self.token = self._current_line[self._column] | 794 self.token = self._current_line[self._column] |
| 587 | 795 |
| 588 | 796 |
| 589 # text.encode('string_escape') does not seem to satisfy our needs as it | |
| 590 # encodes unprintable characters using two-digit hex escapes whereas our | |
| 591 # C++ unescaping function allows hex escapes to be any length. So, | |
| 592 # "\0011".encode('string_escape') ends up being "\\x011", which will be | |
| 593 # decoded in C++ as a single-character string with char code 0x11. | |
| 594 def _CEscape(text, as_utf8): | |
| 595 def escape(c): | |
| 596 o = ord(c) | |
| 597 if o == 10: return r"\n" # optional escape | |
| 598 if o == 13: return r"\r" # optional escape | |
| 599 if o == 9: return r"\t" # optional escape | |
| 600 if o == 39: return r"\'" # optional escape | |
| 601 | |
| 602 if o == 34: return r'\"' # necessary escape | |
| 603 if o == 92: return r"\\" # necessary escape | |
| 604 | |
| 605 # necessary escapes | |
| 606 if not as_utf8 and (o >= 127 or o < 32): return "\\%03o" % o | |
| 607 return c | |
| 608 return "".join([escape(c) for c in text]) | |
| 609 | |
| 610 | |
| 611 _CUNESCAPE_HEX = re.compile('\\\\x([0-9a-fA-F]{2}|[0-9a-fA-F])') | |
| 612 | |
| 613 | |
| 614 def _CUnescape(text): | |
| 615 def ReplaceHex(m): | |
| 616 return chr(int(m.group(0)[2:], 16)) | |
| 617 # This is required because the 'string_escape' encoding doesn't | |
| 618 # allow single-digit hex escapes (like '\xf'). | |
| 619 result = _CUNESCAPE_HEX.sub(ReplaceHex, text) | |
| 620 return result.decode('string_escape') | |
| 621 | |
| 622 | |
| 623 def ParseInteger(text, is_signed=False, is_long=False): | 797 def ParseInteger(text, is_signed=False, is_long=False): |
| 624 """Parses an integer. | 798 """Parses an integer. |
| 625 | 799 |
| 626 Args: | 800 Args: |
| 627 text: The text to parse. | 801 text: The text to parse. |
| 628 is_signed: True if a signed integer must be parsed. | 802 is_signed: True if a signed integer must be parsed. |
| 629 is_long: True if a long integer must be parsed. | 803 is_long: True if a long integer must be parsed. |
| 630 | 804 |
| 631 Returns: | 805 Returns: |
| 632 The integer value. | 806 The integer value. |
| 633 | 807 |
| 634 Raises: | 808 Raises: |
| 635 ValueError: Thrown Iff the text is not a valid integer. | 809 ValueError: Thrown Iff the text is not a valid integer. |
| 636 """ | 810 """ |
| 637 # Do the actual parsing. Exception handling is propagated to caller. | 811 # Do the actual parsing. Exception handling is propagated to caller. |
| 638 try: | 812 try: |
| 639 result = int(text, 0) | 813 # We force 32-bit values to int and 64-bit values to long to make |
| 814 # alternate implementations where the distinction is more significant |
| 815 # (e.g. the C++ implementation) simpler. |
| 816 if is_long: |
| 817 result = long(text, 0) |
| 818 else: |
| 819 result = int(text, 0) |
| 640 except ValueError: | 820 except ValueError: |
| 641 raise ValueError('Couldn\'t parse integer: %s' % text) | 821 raise ValueError('Couldn\'t parse integer: %s' % text) |
| 642 | 822 |
| 643 # Check if the integer is sane. Exceptions handled by callers. | 823 # Check if the integer is sane. Exceptions handled by callers. |
| 644 checker = _INTEGER_CHECKERS[2 * int(is_long) + int(is_signed)] | 824 checker = _INTEGER_CHECKERS[2 * int(is_long) + int(is_signed)] |
| 645 checker.CheckValue(result) | 825 checker.CheckValue(result) |
| 646 return result | 826 return result |
| 647 | 827 |
| 648 | 828 |
| 649 def ParseFloat(text): | 829 def ParseFloat(text): |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 725 'Enum type "%s" has no value named %s.' % ( | 905 'Enum type "%s" has no value named %s.' % ( |
| 726 enum_descriptor.full_name, value)) | 906 enum_descriptor.full_name, value)) |
| 727 else: | 907 else: |
| 728 # Numeric value. | 908 # Numeric value. |
| 729 enum_value = enum_descriptor.values_by_number.get(number, None) | 909 enum_value = enum_descriptor.values_by_number.get(number, None) |
| 730 if enum_value is None: | 910 if enum_value is None: |
| 731 raise ValueError( | 911 raise ValueError( |
| 732 'Enum type "%s" has no value with number %d.' % ( | 912 'Enum type "%s" has no value with number %d.' % ( |
| 733 enum_descriptor.full_name, number)) | 913 enum_descriptor.full_name, number)) |
| 734 return enum_value.number | 914 return enum_value.number |
| OLD | NEW |