图片解析应用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

333 lines
12 KiB

  1. # Protocol Buffers - Google's data interchange format
  2. # Copyright 2008 Google Inc. All rights reserved.
  3. # https://developers.google.com/protocol-buffers/
  4. #
  5. # Redistribution and use in source and binary forms, with or without
  6. # modification, are permitted provided that the following conditions are
  7. # met:
  8. #
  9. # * Redistributions of source code must retain the above copyright
  10. # notice, this list of conditions and the following disclaimer.
  11. # * Redistributions in binary form must reproduce the above
  12. # copyright notice, this list of conditions and the following disclaimer
  13. # in the documentation and/or other materials provided with the
  14. # distribution.
  15. # * Neither the name of Google Inc. nor the names of its
  16. # contributors may be used to endorse or promote products derived from
  17. # this software without specific prior written permission.
  18. #
  19. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  20. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  21. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  22. # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  23. # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  24. # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  25. # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  26. # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  27. # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  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.
  30. """Contains FieldMask class."""
  31. from google.protobuf.descriptor import FieldDescriptor
  32. class FieldMask(object):
  33. """Class for FieldMask message type."""
  34. __slots__ = ()
  35. def ToJsonString(self):
  36. """Converts FieldMask to string according to proto3 JSON spec."""
  37. camelcase_paths = []
  38. for path in self.paths:
  39. camelcase_paths.append(_SnakeCaseToCamelCase(path))
  40. return ','.join(camelcase_paths)
  41. def FromJsonString(self, value):
  42. """Converts string to FieldMask according to proto3 JSON spec."""
  43. if not isinstance(value, str):
  44. raise ValueError('FieldMask JSON value not a string: {!r}'.format(value))
  45. self.Clear()
  46. if value:
  47. for path in value.split(','):
  48. self.paths.append(_CamelCaseToSnakeCase(path))
  49. def IsValidForDescriptor(self, message_descriptor):
  50. """Checks whether the FieldMask is valid for Message Descriptor."""
  51. for path in self.paths:
  52. if not _IsValidPath(message_descriptor, path):
  53. return False
  54. return True
  55. def AllFieldsFromDescriptor(self, message_descriptor):
  56. """Gets all direct fields of Message Descriptor to FieldMask."""
  57. self.Clear()
  58. for field in message_descriptor.fields:
  59. self.paths.append(field.name)
  60. def CanonicalFormFromMask(self, mask):
  61. """Converts a FieldMask to the canonical form.
  62. Removes paths that are covered by another path. For example,
  63. "foo.bar" is covered by "foo" and will be removed if "foo"
  64. is also in the FieldMask. Then sorts all paths in alphabetical order.
  65. Args:
  66. mask: The original FieldMask to be converted.
  67. """
  68. tree = _FieldMaskTree(mask)
  69. tree.ToFieldMask(self)
  70. def Union(self, mask1, mask2):
  71. """Merges mask1 and mask2 into this FieldMask."""
  72. _CheckFieldMaskMessage(mask1)
  73. _CheckFieldMaskMessage(mask2)
  74. tree = _FieldMaskTree(mask1)
  75. tree.MergeFromFieldMask(mask2)
  76. tree.ToFieldMask(self)
  77. def Intersect(self, mask1, mask2):
  78. """Intersects mask1 and mask2 into this FieldMask."""
  79. _CheckFieldMaskMessage(mask1)
  80. _CheckFieldMaskMessage(mask2)
  81. tree = _FieldMaskTree(mask1)
  82. intersection = _FieldMaskTree()
  83. for path in mask2.paths:
  84. tree.IntersectPath(path, intersection)
  85. intersection.ToFieldMask(self)
  86. def MergeMessage(
  87. self, source, destination,
  88. replace_message_field=False, replace_repeated_field=False):
  89. """Merges fields specified in FieldMask from source to destination.
  90. Args:
  91. source: Source message.
  92. destination: The destination message to be merged into.
  93. replace_message_field: Replace message field if True. Merge message
  94. field if False.
  95. replace_repeated_field: Replace repeated field if True. Append
  96. elements of repeated field if False.
  97. """
  98. tree = _FieldMaskTree(self)
  99. tree.MergeMessage(
  100. source, destination, replace_message_field, replace_repeated_field)
  101. def _IsValidPath(message_descriptor, path):
  102. """Checks whether the path is valid for Message Descriptor."""
  103. parts = path.split('.')
  104. last = parts.pop()
  105. for name in parts:
  106. field = message_descriptor.fields_by_name.get(name)
  107. if (field is None or
  108. field.label == FieldDescriptor.LABEL_REPEATED or
  109. field.type != FieldDescriptor.TYPE_MESSAGE):
  110. return False
  111. message_descriptor = field.message_type
  112. return last in message_descriptor.fields_by_name
  113. def _CheckFieldMaskMessage(message):
  114. """Raises ValueError if message is not a FieldMask."""
  115. message_descriptor = message.DESCRIPTOR
  116. if (message_descriptor.name != 'FieldMask' or
  117. message_descriptor.file.name != 'google/protobuf/field_mask.proto'):
  118. raise ValueError('Message {0} is not a FieldMask.'.format(
  119. message_descriptor.full_name))
  120. def _SnakeCaseToCamelCase(path_name):
  121. """Converts a path name from snake_case to camelCase."""
  122. result = []
  123. after_underscore = False
  124. for c in path_name:
  125. if c.isupper():
  126. raise ValueError(
  127. 'Fail to print FieldMask to Json string: Path name '
  128. '{0} must not contain uppercase letters.'.format(path_name))
  129. if after_underscore:
  130. if c.islower():
  131. result.append(c.upper())
  132. after_underscore = False
  133. else:
  134. raise ValueError(
  135. 'Fail to print FieldMask to Json string: The '
  136. 'character after a "_" must be a lowercase letter '
  137. 'in path name {0}.'.format(path_name))
  138. elif c == '_':
  139. after_underscore = True
  140. else:
  141. result += c
  142. if after_underscore:
  143. raise ValueError('Fail to print FieldMask to Json string: Trailing "_" '
  144. 'in path name {0}.'.format(path_name))
  145. return ''.join(result)
  146. def _CamelCaseToSnakeCase(path_name):
  147. """Converts a field name from camelCase to snake_case."""
  148. result = []
  149. for c in path_name:
  150. if c == '_':
  151. raise ValueError('Fail to parse FieldMask: Path name '
  152. '{0} must not contain "_"s.'.format(path_name))
  153. if c.isupper():
  154. result += '_'
  155. result += c.lower()
  156. else:
  157. result += c
  158. return ''.join(result)
  159. class _FieldMaskTree(object):
  160. """Represents a FieldMask in a tree structure.
  161. For example, given a FieldMask "foo.bar,foo.baz,bar.baz",
  162. the FieldMaskTree will be:
  163. [_root] -+- foo -+- bar
  164. | |
  165. | +- baz
  166. |
  167. +- bar --- baz
  168. In the tree, each leaf node represents a field path.
  169. """
  170. __slots__ = ('_root',)
  171. def __init__(self, field_mask=None):
  172. """Initializes the tree by FieldMask."""
  173. self._root = {}
  174. if field_mask:
  175. self.MergeFromFieldMask(field_mask)
  176. def MergeFromFieldMask(self, field_mask):
  177. """Merges a FieldMask to the tree."""
  178. for path in field_mask.paths:
  179. self.AddPath(path)
  180. def AddPath(self, path):
  181. """Adds a field path into the tree.
  182. If the field path to add is a sub-path of an existing field path
  183. in the tree (i.e., a leaf node), it means the tree already matches
  184. the given path so nothing will be added to the tree. If the path
  185. matches an existing non-leaf node in the tree, that non-leaf node
  186. will be turned into a leaf node with all its children removed because
  187. the path matches all the node's children. Otherwise, a new path will
  188. be added.
  189. Args:
  190. path: The field path to add.
  191. """
  192. node = self._root
  193. for name in path.split('.'):
  194. if name not in node:
  195. node[name] = {}
  196. elif not node[name]:
  197. # Pre-existing empty node implies we already have this entire tree.
  198. return
  199. node = node[name]
  200. # Remove any sub-trees we might have had.
  201. node.clear()
  202. def ToFieldMask(self, field_mask):
  203. """Converts the tree to a FieldMask."""
  204. field_mask.Clear()
  205. _AddFieldPaths(self._root, '', field_mask)
  206. def IntersectPath(self, path, intersection):
  207. """Calculates the intersection part of a field path with this tree.
  208. Args:
  209. path: The field path to calculates.
  210. intersection: The out tree to record the intersection part.
  211. """
  212. node = self._root
  213. for name in path.split('.'):
  214. if name not in node:
  215. return
  216. elif not node[name]:
  217. intersection.AddPath(path)
  218. return
  219. node = node[name]
  220. intersection.AddLeafNodes(path, node)
  221. def AddLeafNodes(self, prefix, node):
  222. """Adds leaf nodes begin with prefix to this tree."""
  223. if not node:
  224. self.AddPath(prefix)
  225. for name in node:
  226. child_path = prefix + '.' + name
  227. self.AddLeafNodes(child_path, node[name])
  228. def MergeMessage(
  229. self, source, destination,
  230. replace_message, replace_repeated):
  231. """Merge all fields specified by this tree from source to destination."""
  232. _MergeMessage(
  233. self._root, source, destination, replace_message, replace_repeated)
  234. def _StrConvert(value):
  235. """Converts value to str if it is not."""
  236. # This file is imported by c extension and some methods like ClearField
  237. # requires string for the field name. py2/py3 has different text
  238. # type and may use unicode.
  239. if not isinstance(value, str):
  240. return value.encode('utf-8')
  241. return value
  242. def _MergeMessage(
  243. node, source, destination, replace_message, replace_repeated):
  244. """Merge all fields specified by a sub-tree from source to destination."""
  245. source_descriptor = source.DESCRIPTOR
  246. for name in node:
  247. child = node[name]
  248. field = source_descriptor.fields_by_name[name]
  249. if field is None:
  250. raise ValueError('Error: Can\'t find field {0} in message {1}.'.format(
  251. name, source_descriptor.full_name))
  252. if child:
  253. # Sub-paths are only allowed for singular message fields.
  254. if (field.label == FieldDescriptor.LABEL_REPEATED or
  255. field.cpp_type != FieldDescriptor.CPPTYPE_MESSAGE):
  256. raise ValueError('Error: Field {0} in message {1} is not a singular '
  257. 'message field and cannot have sub-fields.'.format(
  258. name, source_descriptor.full_name))
  259. if source.HasField(name):
  260. _MergeMessage(
  261. child, getattr(source, name), getattr(destination, name),
  262. replace_message, replace_repeated)
  263. continue
  264. if field.label == FieldDescriptor.LABEL_REPEATED:
  265. if replace_repeated:
  266. destination.ClearField(_StrConvert(name))
  267. repeated_source = getattr(source, name)
  268. repeated_destination = getattr(destination, name)
  269. repeated_destination.MergeFrom(repeated_source)
  270. else:
  271. if field.cpp_type == FieldDescriptor.CPPTYPE_MESSAGE:
  272. if replace_message:
  273. destination.ClearField(_StrConvert(name))
  274. if source.HasField(name):
  275. getattr(destination, name).MergeFrom(getattr(source, name))
  276. else:
  277. setattr(destination, name, getattr(source, name))
  278. def _AddFieldPaths(node, prefix, field_mask):
  279. """Adds the field paths descended from node to field_mask."""
  280. if not node and prefix:
  281. field_mask.paths.append(prefix)
  282. return
  283. for name in sorted(node):
  284. if prefix:
  285. child_path = prefix + '.' + name
  286. else:
  287. child_path = name
  288. _AddFieldPaths(node[name], child_path, field_mask)