• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#
2# Copyright (C) 2012 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17"""
18A set of helpers for rendering Mako templates with a Metadata model.
19"""
20
21import metadata_model
22import re
23import markdown
24import textwrap
25import sys
26import bs4
27# Monkey-patch BS4. WBR element must not have an end tag.
28bs4.builder.HTMLTreeBuilder.empty_element_tags.add("wbr")
29
30from collections import OrderedDict
31
32# Relative path from HTML file to the base directory used by <img> tags
33IMAGE_SRC_METADATA="images/camera2/metadata/"
34
35# Prepend this path to each <img src="foo"> in javadocs
36JAVADOC_IMAGE_SRC_METADATA="/reference/" + IMAGE_SRC_METADATA
37NDKDOC_IMAGE_SRC_METADATA="../" + IMAGE_SRC_METADATA
38
39#Corresponds to Android Q, where the camera VNDK was added (minor version 4 and vndk version 29).
40# Minor version and vndk version must correspond to the same release
41FRAMEWORK_CAMERA_VNDK_HAL_MINOR_VERSION = 4
42FRAMEWORK_CAMERA_VNDK_STARTING_VERSION =  29
43
44_context_buf = None
45_enum = None
46
47def _is_sec_or_ins(x):
48  return isinstance(x, metadata_model.Section) or    \
49         isinstance(x, metadata_model.InnerNamespace)
50
51##
52## Metadata Helpers
53##
54
55def find_all_sections(root):
56  """
57  Find all descendants that are Section or InnerNamespace instances.
58
59  Args:
60    root: a Metadata instance
61
62  Returns:
63    A list of Section/InnerNamespace instances
64
65  Remarks:
66    These are known as "sections" in the generated C code.
67  """
68  return root.find_all(_is_sec_or_ins)
69
70def find_parent_section(entry):
71  """
72  Find the closest ancestor that is either a Section or InnerNamespace.
73
74  Args:
75    entry: an Entry or Clone node
76
77  Returns:
78    An instance of Section or InnerNamespace
79  """
80  return entry.find_parent_first(_is_sec_or_ins)
81
82# find uniquely named entries (w/o recursing through inner namespaces)
83def find_unique_entries(node):
84  """
85  Find all uniquely named entries, without recursing through inner namespaces.
86
87  Args:
88    node: a Section or InnerNamespace instance
89
90  Yields:
91    A sequence of MergedEntry nodes representing an entry
92
93  Remarks:
94    This collapses multiple entries with the same fully qualified name into
95    one entry (e.g. if there are multiple entries in different kinds).
96  """
97  if not isinstance(node, metadata_model.Section) and    \
98     not isinstance(node, metadata_model.InnerNamespace):
99      raise TypeError("expected node to be a Section or InnerNamespace")
100
101  d = OrderedDict()
102  # remove the 'kinds' from the path between sec and the closest entries
103  # then search the immediate children of the search path
104  search_path = isinstance(node, metadata_model.Section) and node.kinds \
105                or [node]
106  for i in search_path:
107      for entry in i.entries:
108          d[entry.name] = entry
109
110  for k,v in d.items():
111      yield v.merge()
112
113def path_name(node):
114  """
115  Calculate a period-separated string path from the root to this element,
116  by joining the names of each node and excluding the Metadata/Kind nodes
117  from the path.
118
119  Args:
120    node: a Node instance
121
122  Returns:
123    A string path
124  """
125
126  isa = lambda x,y: isinstance(x, y)
127  fltr = lambda x: not isa(x, metadata_model.Metadata) and \
128                   not isa(x, metadata_model.Kind)
129
130  path = node.find_parents(fltr)
131  path = list(path)
132  path.reverse()
133  path.append(node)
134
135  return ".".join((i.name for i in path))
136
137def ndk(name):
138  """
139  Return the NDK version of given name, which replace
140  the leading "android" to "acamera"
141
142  Args:
143    name: name string of an entry
144
145  Returns:
146    A NDK version name string of the input name
147  """
148  name_list = name.split(".")
149  if name_list[0] == "android":
150    name_list[0] = "acamera"
151  return ".".join(name_list)
152
153def protobuf_type(entry):
154  """
155  Return the protocol buffer message type for input metadata entry.
156  Only support types used by static metadata right now
157
158  Returns:
159    A string of protocol buffer type. Ex: "optional int32" or "repeated RangeInt"
160  """
161  typeName = None
162  if entry.typedef is None:
163    typeName = entry.type
164  else:
165    typeName = entry.typedef.name
166
167  typename_to_protobuftype = {
168    "rational"               : "Rational",
169    "size"                   : "Size",
170    "sizeF"                  : "SizeF",
171    "rectangle"              : "Rect",
172    "streamConfigurationMap" : "StreamConfigurations",
173    "mandatoryStreamCombination" : "MandatoryStreamCombination",
174    "rangeInt"               : "RangeInt",
175    "rangeLong"              : "RangeLong",
176    "rangeFloat"             : "RangeFloat",
177    "colorSpaceTransform"    : "ColorSpaceTransform",
178    "blackLevelPattern"      : "BlackLevelPattern",
179    "byte"                   : "int32", # protocol buffer don't support byte
180    "boolean"                : "bool",
181    "float"                  : "float",
182    "double"                 : "double",
183    "int32"                  : "int32",
184    "int64"                  : "int64",
185    "enumList"               : "int32",
186    "string"                 : "string",
187    "capability"             : "Capability",
188    "multiResolutionStreamConfigurationMap" : "MultiResolutionStreamConfigurations",
189    "deviceStateSensorOrientationMap"  : "DeviceStateSensorOrientationMap",
190    "dynamicRangeProfiles"   : "DynamicRangeProfiles",
191  }
192
193  if typeName not in typename_to_protobuftype:
194    print("  ERROR: Could not find protocol buffer type for {%s} type {%s} typedef {%s}" % \
195          (entry.name, entry.type, entry.typedef), file=sys.stderr)
196
197  proto_type = typename_to_protobuftype[typeName]
198
199  prefix = "optional"
200  if entry.container == 'array':
201    has_variable_size = False
202    for size in entry.container_sizes:
203      try:
204        size_int = int(size)
205      except ValueError:
206        has_variable_size = True
207
208    if has_variable_size:
209      prefix = "repeated"
210
211  return "%s %s" %(prefix, proto_type)
212
213
214def protobuf_name(entry):
215  """
216  Return the protocol buffer field name for input metadata entry
217
218  Returns:
219    A string. Ex: "android_colorCorrection_availableAberrationModes"
220  """
221  return entry.name.replace(".", "_")
222
223def has_descendants_with_enums(node):
224  """
225  Determine whether or not the current node is or has any descendants with an
226  Enum node.
227
228  Args:
229    node: a Node instance
230
231  Returns:
232    True if it finds an Enum node in the subtree, False otherwise
233  """
234  return bool(node.find_first(lambda x: isinstance(x, metadata_model.Enum)))
235
236def get_children_by_throwing_away_kind(node, member='entries'):
237  """
238  Get the children of this node by compressing the subtree together by removing
239  the kind and then combining any children nodes with the same name together.
240
241  Args:
242    node: An instance of Section, InnerNamespace, or Kind
243
244  Returns:
245    An iterable over the combined children of the subtree of node,
246    as if the Kinds never existed.
247
248  Remarks:
249    Not recursive. Call this function repeatedly on each child.
250  """
251
252  if isinstance(node, metadata_model.Section):
253    # Note that this makes jump from Section to Kind,
254    # skipping the Kind entirely in the tree.
255    node_to_combine = node.combine_kinds_into_single_node()
256  else:
257    node_to_combine = node
258
259  combined_kind = node_to_combine.combine_children_by_name()
260
261  return (i for i in getattr(combined_kind, member))
262
263def get_children_by_filtering_kind(section, kind_name, member='entries'):
264  """
265  Takes a section and yields the children of the merged kind under this section.
266
267  Args:
268    section: An instance of Section
269    kind_name: A name of the kind, i.e. 'dynamic' or 'static' or 'controls'
270
271  Returns:
272    An iterable over the children of the specified merged kind.
273  """
274
275  matched_kind = next((i for i in section.merged_kinds if i.name == kind_name), None)
276
277  if matched_kind:
278    return getattr(matched_kind, member)
279  else:
280    return ()
281
282##
283## Filters
284##
285
286# abcDef.xyz -> ABC_DEF_XYZ
287def csym(name):
288  """
289  Convert an entry name string into an uppercase C symbol.
290
291  Returns:
292    A string
293
294  Example:
295    csym('abcDef.xyz') == 'ABC_DEF_XYZ'
296  """
297  newstr = name
298  newstr = "".join([i.isupper() and ("_" + i) or i for i in newstr]).upper()
299  newstr = newstr.replace(".", "_")
300  return newstr
301
302# abcDef.xyz -> abc_def_xyz
303def csyml(name):
304  """
305  Convert an entry name string into a lowercase C symbol.
306
307  Returns:
308    A string
309
310  Example:
311    csyml('abcDef.xyz') == 'abc_def_xyz'
312  """
313  return csym(name).lower()
314
315# pad with spaces to make string len == size. add new line if too big
316def ljust(size, indent=4):
317  """
318  Creates a function that given a string will pad it with spaces to make
319  the string length == size. Adds a new line if the string was too big.
320
321  Args:
322    size: an integer representing how much spacing should be added
323    indent: an integer representing the initial indendation level
324
325  Returns:
326    A function that takes a string and returns a string.
327
328  Example:
329    ljust(8)("hello") == 'hello   '
330
331  Remarks:
332    Deprecated. Use pad instead since it works for non-first items in a
333    Mako template.
334  """
335  def inner(what):
336    newstr = what.ljust(size)
337    if len(newstr) > size:
338      return what + "\n" + "".ljust(indent + size)
339    else:
340      return newstr
341  return inner
342
343def _find_new_line():
344
345  if _context_buf is None:
346    raise ValueError("Context buffer was not set")
347
348  buf = _context_buf
349  x = -1 # since the first read is always ''
350  cur_pos = buf.tell()
351  while buf.tell() > 0 and buf.read(1) != '\n':
352    buf.seek(cur_pos - x)
353    x = x + 1
354
355  buf.seek(cur_pos)
356
357  return int(x)
358
359# Pad the string until the buffer reaches the desired column.
360# If string is too long, insert a new line with 'col' spaces instead
361def pad(col):
362  """
363  Create a function that given a string will pad it to the specified column col.
364  If the string overflows the column, put the string on a new line and pad it.
365
366  Args:
367    col: an integer specifying the column number
368
369  Returns:
370    A function that given a string will produce a padded string.
371
372  Example:
373    pad(8)("hello") == 'hello   '
374
375  Remarks:
376    This keeps track of the line written by Mako so far, so it will always
377    align to the column number correctly.
378  """
379  def inner(what):
380    wut = int(col)
381    current_col = _find_new_line()
382
383    if len(what) > wut - current_col:
384      return what + "\n".ljust(col)
385    else:
386      return what.ljust(wut - current_col)
387  return inner
388
389# int32 -> TYPE_INT32, byte -> TYPE_BYTE, etc. note that enum -> TYPE_INT32
390def ctype_enum(what):
391  """
392  Generate a camera_metadata_type_t symbol from a type string.
393
394  Args:
395    what: a type string
396
397  Returns:
398    A string representing the camera_metadata_type_t
399
400  Example:
401    ctype_enum('int32') == 'TYPE_INT32'
402    ctype_enum('int64') == 'TYPE_INT64'
403    ctype_enum('float') == 'TYPE_FLOAT'
404
405  Remarks:
406    An enum is coerced to a byte since the rest of the camera_metadata
407    code doesn't support enums directly yet.
408  """
409  return 'TYPE_%s' %(what.upper())
410
411
412# Calculate a java type name from an entry with a Typedef node
413def _jtypedef_type(entry):
414  typedef = entry.typedef
415  additional = ''
416
417  # Hacky way to deal with arrays. Assume that if we have
418  # size 'Constant x N' the Constant is part of the Typedef size.
419  # So something sized just 'Constant', 'Constant1 x Constant2', etc
420  # is not treated as a real java array.
421  if entry.container == 'array':
422    has_variable_size = False
423    for size in entry.container_sizes:
424      try:
425        size_int = int(size)
426      except ValueError:
427        has_variable_size = True
428
429    if has_variable_size:
430      additional = '[]'
431
432  try:
433    name = typedef.languages['java']
434
435    return "%s%s" %(name, additional)
436  except KeyError:
437    return None
438
439# Box if primitive. Otherwise leave unboxed.
440def _jtype_box(type_name):
441  mapping = {
442    'boolean': 'Boolean',
443    'byte': 'Byte',
444    'int': 'Integer',
445    'float': 'Float',
446    'double': 'Double',
447    'long': 'Long'
448  }
449
450  return mapping.get(type_name, type_name)
451
452def jtype_unboxed(entry):
453  """
454  Calculate the Java type from an entry type string, to be used whenever we
455  need the regular type in Java. It's not boxed, so it can't be used as a
456  generic type argument when the entry type happens to resolve to a primitive.
457
458  Remarks:
459    Since Java generics cannot be instantiated with primitives, this version
460    is not applicable in that case. Use jtype_boxed instead for that.
461
462  Returns:
463    The string representing the Java type.
464  """
465  if not isinstance(entry, metadata_model.Entry):
466    raise ValueError("Expected entry to be an instance of Entry")
467
468  metadata_type = entry.type
469
470  java_type = None
471
472  if entry.typedef:
473    typedef_name = _jtypedef_type(entry)
474    if typedef_name:
475      java_type = typedef_name # already takes into account arrays
476
477  if not java_type:
478    if not java_type and entry.enum and metadata_type == 'byte':
479      # Always map byte enums to Java ints, unless there's a typedef override
480      base_type = 'int'
481
482    else:
483      mapping = {
484        'int32': 'int',
485        'int64': 'long',
486        'float': 'float',
487        'double': 'double',
488        'byte': 'byte',
489        'rational': 'Rational'
490      }
491
492      base_type = mapping[metadata_type]
493
494    # Convert to array (enums, basic types)
495    if entry.container == 'array':
496      additional = '[]'
497    else:
498      additional = ''
499
500    java_type = '%s%s' %(base_type, additional)
501
502  # Now box this sucker.
503  return java_type
504
505def jtype_boxed(entry):
506  """
507  Calculate the Java type from an entry type string, to be used as a generic
508  type argument in Java. The type is guaranteed to inherit from Object.
509
510  It will only box when absolutely necessary, i.e. int -> Integer[], but
511  int[] -> int[].
512
513  Remarks:
514    Since Java generics cannot be instantiated with primitives, this version
515    will use boxed types when absolutely required.
516
517  Returns:
518    The string representing the boxed Java type.
519  """
520  unboxed_type = jtype_unboxed(entry)
521  return _jtype_box(unboxed_type)
522
523def _is_jtype_generic(entry):
524  """
525  Determine whether or not the Java type represented by the entry type
526  string and/or typedef is a Java generic.
527
528  For example, "Range<Integer>" would be considered a generic, whereas
529  a "MeteringRectangle" or a plain "Integer" would not be considered a generic.
530
531  Args:
532    entry: An instance of an Entry node
533
534  Returns:
535    True if it's a java generic, False otherwise.
536  """
537  if entry.typedef:
538    local_typedef = _jtypedef_type(entry)
539    if local_typedef:
540      match = re.search(r'<.*>', local_typedef)
541      return bool(match)
542  return False
543
544def _jtype_primitive(what):
545  """
546  Calculate the Java type from an entry type string.
547
548  Remarks:
549    Makes a special exception for Rational, since it's a primitive in terms of
550    the C-library camera_metadata type system.
551
552  Returns:
553    The string representing the primitive type
554  """
555  mapping = {
556    'int32': 'int',
557    'int64': 'long',
558    'float': 'float',
559    'double': 'double',
560    'byte': 'byte',
561    'rational': 'Rational'
562  }
563
564  try:
565    return mapping[what]
566  except KeyError as e:
567    raise ValueError("Can't map '%s' to a primitive, not supported" %what)
568
569def jclass(entry):
570  """
571  Calculate the java Class reference string for an entry.
572
573  Args:
574    entry: an Entry node
575
576  Example:
577    <entry name="some_int" type="int32"/>
578    <entry name="some_int_array" type="int32" container='array'/>
579
580    jclass(some_int) == 'int.class'
581    jclass(some_int_array) == 'int[].class'
582
583  Returns:
584    The ClassName.class string
585  """
586
587  return "%s.class" %jtype_unboxed(entry)
588
589def jkey_type_token(entry):
590  """
591  Calculate the java type token compatible with a Key constructor.
592  This will be the Java Class<T> for non-generic classes, and a
593  TypeReference<T> for generic classes.
594
595  Args:
596    entry: An entry node
597
598  Returns:
599    The ClassName.class string, or 'new TypeReference<ClassName>() {{ }}' string
600  """
601  if _is_jtype_generic(entry):
602    return "new TypeReference<%s>() {{ }}" %(jtype_boxed(entry))
603  else:
604    return jclass(entry)
605
606def jidentifier(what):
607  """
608  Convert the input string into a valid Java identifier.
609
610  Args:
611    what: any identifier string
612
613  Returns:
614    String with added underscores if necessary.
615  """
616  if re.match("\d", what):
617    return "_%s" %what
618  else:
619    return what
620
621def enum_calculate_value_string(enum_value):
622  """
623  Calculate the value of the enum, even if it does not have one explicitly
624  defined.
625
626  This looks back for the first enum value that has a predefined value and then
627  applies addition until we get the right value, using C-enum semantics.
628
629  Args:
630    enum_value: an EnumValue node with a valid Enum parent
631
632  Example:
633    <enum>
634      <value>X</value>
635      <value id="5">Y</value>
636      <value>Z</value>
637    </enum>
638
639    enum_calculate_value_string(X) == '0'
640    enum_calculate_Value_string(Y) == '5'
641    enum_calculate_value_string(Z) == '6'
642
643  Returns:
644    String that represents the enum value as an integer literal.
645  """
646
647  enum_value_siblings = list(enum_value.parent.values)
648  this_index = enum_value_siblings.index(enum_value)
649
650  def is_hex_string(instr):
651    return bool(re.match('0x[a-f0-9]+$', instr, re.IGNORECASE))
652
653  base_value = 0
654  base_offset = 0
655  emit_as_hex = False
656
657  this_id = enum_value_siblings[this_index].id
658  while this_index != 0 and not this_id:
659    this_index -= 1
660    base_offset += 1
661    this_id = enum_value_siblings[this_index].id
662
663  if this_id:
664    base_value = int(this_id, 0)  # guess base
665    emit_as_hex = is_hex_string(this_id)
666
667  if emit_as_hex:
668    return "0x%X" %(base_value + base_offset)
669  else:
670    return "%d" %(base_value + base_offset)
671
672def enumerate_with_last(iterable):
673  """
674  Enumerate a sequence of iterable, while knowing if this element is the last in
675  the sequence or not.
676
677  Args:
678    iterable: an Iterable of some sequence
679
680  Yields:
681    (element, bool) where the bool is True iff the element is last in the seq.
682  """
683  it = (i for i in iterable)
684
685  try:
686    first = next(it)  # OK: raises exception if it is empty
687  except StopIteration:
688    return
689
690  second = first  # for when we have only 1 element in iterable
691
692  try:
693    while True:
694      second = next(it)
695      # more elements remaining.
696      yield (first, False)
697      first = second
698  except StopIteration:
699    # last element. no more elements left
700    yield (second, True)
701
702def pascal_case(what):
703  """
704  Convert the first letter of a string to uppercase, to make the identifier
705  conform to PascalCase.
706
707  If there are dots, remove the dots, and capitalize the letter following
708  where the dot was. Letters that weren't following dots are left unchanged,
709  except for the first letter of the string (which is made upper-case).
710
711  Args:
712    what: a string representing some identifier
713
714  Returns:
715    String with first letter capitalized
716
717  Example:
718    pascal_case("helloWorld") == "HelloWorld"
719    pascal_case("foo") == "Foo"
720    pascal_case("hello.world") = "HelloWorld"
721    pascal_case("fooBar.fooBar") = "FooBarFooBar"
722  """
723  return "".join([s[0:1].upper() + s[1:] for s in what.split('.')])
724
725def jkey_identifier(what):
726  """
727  Return a Java identifier from a property name.
728
729  Args:
730    what: a string representing a property name.
731
732  Returns:
733    Java identifier corresponding to the property name. May need to be
734    prepended with the appropriate Java class name by the caller of this
735    function. Note that the outer namespace is stripped from the property
736    name.
737
738  Example:
739    jkey_identifier("android.lens.facing") == "LENS_FACING"
740  """
741  return csym(what[what.find('.') + 1:])
742
743def jenum_value(enum_entry, enum_value):
744  """
745  Calculate the Java name for an integer enum value
746
747  Args:
748    enum: An enum-typed Entry node
749    value: An EnumValue node for the enum
750
751  Returns:
752    String representing the Java symbol
753  """
754
755  cname = csym(enum_entry.name)
756  return cname[cname.find('_') + 1:] + '_' + enum_value.name
757
758def generate_extra_javadoc_detail(entry):
759  """
760  Returns a function to add extra details for an entry into a string for inclusion into
761  javadoc. Adds information about units, the list of enum values for this key, and the valid
762  range.
763  """
764  def inner(text):
765    if entry.units and not (entry.typedef and entry.typedef.name == 'string'):
766      text += '\n\n<b>Units</b>: %s\n' % (dedent(entry.units))
767    if entry.enum and not (entry.typedef and entry.typedef.languages.get('java')):
768      text += '\n\n<b>Possible values:</b>\n<ul>\n'
769      for value in entry.enum.values:
770        if not value.hidden:
771          text += '  <li>{@link #%s %s}</li>\n' % ( jenum_value(entry, value ), value.name )
772      text += '</ul>\n'
773    if entry.range:
774      if entry.enum and not (entry.typedef and entry.typedef.languages.get('java')):
775        text += '\n\n<b>Available values for this device:</b><br>\n'
776      else:
777        text += '\n\n<b>Range of valid values:</b><br>\n'
778      text += '%s\n' % (dedent(entry.range))
779    if entry.hwlevel != 'legacy': # covers any of (None, 'limited', 'full')
780      text += '\n\n<b>Optional</b> - The value for this key may be {@code null} on some devices.\n'
781    if entry.hwlevel == 'full':
782      text += \
783        '\n<b>Full capability</b> - \n' + \
784        'Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the\n' + \
785        'android.info.supportedHardwareLevel key\n'
786    if entry.hwlevel == 'limited':
787      text += \
788        '\n<b>Limited capability</b> - \n' + \
789        'Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the\n' + \
790        'android.info.supportedHardwareLevel key\n'
791    if entry.hwlevel == 'legacy':
792      text += "\nThis key is available on all devices."
793    if entry.permission_needed == "true":
794      text += "\n\n<b>Permission {@link android.Manifest.permission#CAMERA} is needed to access this property</b>\n\n"
795
796    return text
797  return inner
798
799
800def javadoc(metadata, indent = 4):
801  """
802  Returns a function to format a markdown syntax text block as a
803  javadoc comment section, given a set of metadata
804
805  Args:
806    metadata: A Metadata instance, representing the top-level root
807      of the metadata for cross-referencing
808    indent: baseline level of indentation for javadoc block
809  Returns:
810    A function that transforms a String text block as follows:
811    - Indent and * for insertion into a Javadoc comment block
812    - Trailing whitespace removed
813    - Entire body rendered via markdown to generate HTML
814    - All tag names converted to appropriate Javadoc {@link} with @see
815      for each tag
816
817  Example:
818    "This is a comment for Javadoc\n" +
819    "     with multiple lines, that should be   \n" +
820    "     formatted better\n" +
821    "\n" +
822    "    That covers multiple lines as well\n"
823    "    And references android.control.mode\n"
824
825    transforms to
826    "    * <p>This is a comment for Javadoc\n" +
827    "    * with multiple lines, that should be\n" +
828    "    * formatted better</p>\n" +
829    "    * <p>That covers multiple lines as well</p>\n" +
830    "    * and references {@link CaptureRequest#CONTROL_MODE android.control.mode}\n" +
831    "    *\n" +
832    "    * @see CaptureRequest#CONTROL_MODE\n"
833  """
834  def javadoc_formatter(text):
835    comment_prefix = " " * indent + " * "
836
837    # render with markdown => HTML
838    javatext = md(text, JAVADOC_IMAGE_SRC_METADATA)
839
840    # Identity transform for javadoc links
841    def javadoc_link_filter(target, target_ndk, shortname):
842      return '{@link %s %s}' % (target, shortname)
843
844    javatext = filter_links(javatext, javadoc_link_filter)
845
846    # Crossref tag names
847    kind_mapping = {
848        'static': 'CameraCharacteristics',
849        'dynamic': 'CaptureResult',
850        'controls': 'CaptureRequest' }
851
852    # Convert metadata entry "android.x.y.z" to form
853    # "{@link CaptureRequest#X_Y_Z android.x.y.z}"
854    def javadoc_crossref_filter(node):
855      if node.applied_visibility in ('public', 'java_public', 'fwk_java_public'):
856        return '{@link %s#%s %s}' % (kind_mapping[node.kind],
857                                     jkey_identifier(node.name),
858                                     node.name)
859      else:
860        return node.name
861
862    # For each public tag "android.x.y.z" referenced, add a
863    # "@see CaptureRequest#X_Y_Z"
864    def javadoc_crossref_see_filter(node_set):
865      node_set = (x for x in node_set if x.applied_visibility in \
866                  ('public', 'java_public', 'fwk_java_public'))
867
868      text = '\n'
869      for node in node_set:
870        text = text + '\n@see %s#%s' % (kind_mapping[node.kind],
871                                      jkey_identifier(node.name))
872
873      return text if text != '\n' else ''
874
875    javatext = filter_tags(javatext, metadata, javadoc_crossref_filter, javadoc_crossref_see_filter)
876
877    def line_filter(line):
878      # Indent each line
879      # Add ' * ' to it for stylistic reasons
880      # Strip right side of trailing whitespace
881      return (comment_prefix + line).rstrip()
882
883    # Process each line with above filter
884    javatext = "\n".join(line_filter(i) for i in javatext.split("\n")) + "\n"
885
886    return javatext
887
888  return javadoc_formatter
889
890def ndkdoc(metadata, indent = 4):
891  """
892  Returns a function to format a markdown syntax text block as a
893  NDK camera API C/C++ comment section, given a set of metadata
894
895  Args:
896    metadata: A Metadata instance, representing the top-level root
897      of the metadata for cross-referencing
898    indent: baseline level of indentation for comment block
899  Returns:
900    A function that transforms a String text block as follows:
901    - Indent and * for insertion into a comment block
902    - Trailing whitespace removed
903    - Entire body rendered via markdown
904    - All tag names converted to appropriate NDK tag name for each tag
905
906  Example:
907    "This is a comment for NDK\n" +
908    "     with multiple lines, that should be   \n" +
909    "     formatted better\n" +
910    "\n" +
911    "    That covers multiple lines as well\n"
912    "    And references android.control.mode\n"
913
914    transforms to
915    "    * This is a comment for NDK\n" +
916    "    * with multiple lines, that should be\n" +
917    "    * formatted better\n" +
918    "    * That covers multiple lines as well\n" +
919    "    * and references ACAMERA_CONTROL_MODE\n" +
920    "    *\n" +
921    "    * @see ACAMERA_CONTROL_MODE\n"
922  """
923  def ndkdoc_formatter(text):
924    # render with markdown => HTML
925    # Turn off the table plugin since doxygen doesn't recognize generated <thead> <tbody> tags
926    ndktext = md(text, NDKDOC_IMAGE_SRC_METADATA, False)
927
928    # Simple transform for ndk doc links
929    def ndkdoc_link_filter(target, target_ndk, shortname):
930      if target_ndk is not None:
931        return '{@link %s %s}' % (target_ndk, shortname)
932
933      # Create HTML link to Javadoc
934      if shortname == '':
935        lastdot = target.rfind('.')
936        if lastdot == -1:
937          shortname = target
938        else:
939          shortname = target[lastdot + 1:]
940
941      target = target.replace('.','/')
942      if target.find('#') != -1:
943        target = target.replace('#','.html#')
944      else:
945        target = target + '.html'
946
947      # Work around html links with inner classes.
948      target = target.replace('CaptureRequest/Builder', 'CaptureRequest.Builder')
949      target = target.replace('Build/VERSION', 'Build.VERSION')
950
951      return '<a href="https://developer.android.com/reference/%s">%s</a>' % (target, shortname)
952
953    ndktext = filter_links(ndktext, ndkdoc_link_filter)
954
955    # Convert metadata entry "android.x.y.z" to form
956    # NDK tag format of "ACAMERA_X_Y_Z"
957    def ndkdoc_crossref_filter(node):
958      if node.applied_ndk_visible == 'true':
959        return csym(ndk(node.name))
960      else:
961        return node.name
962
963    # For each public tag "android.x.y.z" referenced, add a
964    # "@see ACAMERA_X_Y_Z"
965    def ndkdoc_crossref_see_filter(node_set):
966      node_set = (x for x in node_set if x.applied_ndk_visible == 'true')
967
968      text = '\n'
969      for node in node_set:
970        text = text + '\n@see %s' % (csym(ndk(node.name)))
971
972      return text if text != '\n' else ''
973
974    ndktext = filter_tags(ndktext, metadata, ndkdoc_crossref_filter, ndkdoc_crossref_see_filter)
975
976    ndktext = ndk_replace_tag_wildcards(ndktext, metadata)
977
978    comment_prefix = " " * indent + " * ";
979
980    def line_filter(line):
981      # Indent each line
982      # Add ' * ' to it for stylistic reasons
983      # Strip right side of trailing whitespace
984      return (comment_prefix + line).rstrip()
985
986    # Process each line with above filter
987    ndktext = "\n".join(line_filter(i) for i in ndktext.split("\n")) + "\n"
988
989    return ndktext
990
991  return ndkdoc_formatter
992
993def hidldoc(metadata, indent = 4):
994  """
995  Returns a function to format a markdown syntax text block as a
996  HIDL camera HAL module C/C++ comment section, given a set of metadata
997
998  Args:
999    metadata: A Metadata instance, representing the top-level root
1000      of the metadata for cross-referencing
1001    indent: baseline level of indentation for comment block
1002  Returns:
1003    A function that transforms a String text block as follows:
1004    - Indent and * for insertion into a comment block
1005    - Trailing whitespace removed
1006    - Entire body rendered via markdown
1007    - All tag names converted to appropriate HIDL tag name for each tag
1008
1009  Example:
1010    "This is a comment for NDK\n" +
1011    "     with multiple lines, that should be   \n" +
1012    "     formatted better\n" +
1013    "\n" +
1014    "    That covers multiple lines as well\n"
1015    "    And references android.control.mode\n"
1016
1017    transforms to
1018    "    * This is a comment for NDK\n" +
1019    "    * with multiple lines, that should be\n" +
1020    "    * formatted better\n" +
1021    "    * That covers multiple lines as well\n" +
1022    "    * and references ANDROID_CONTROL_MODE\n" +
1023    "    *\n" +
1024    "    * @see ANDROID_CONTROL_MODE\n"
1025  """
1026  def hidldoc_formatter(text):
1027    # render with markdown => HTML
1028    # Turn off the table plugin since doxygen doesn't recognize generated <thead> <tbody> tags
1029    hidltext = md(text, NDKDOC_IMAGE_SRC_METADATA, False)
1030
1031    # Simple transform for hidl doc links
1032    def hidldoc_link_filter(target, target_ndk, shortname):
1033      if target_ndk is not None:
1034        return '{@link %s %s}' % (target_ndk, shortname)
1035
1036      # Create HTML link to Javadoc
1037      if shortname == '':
1038        lastdot = target.rfind('.')
1039        if lastdot == -1:
1040          shortname = target
1041        else:
1042          shortname = target[lastdot + 1:]
1043
1044      target = target.replace('.','/')
1045      if target.find('#') != -1:
1046        target = target.replace('#','.html#')
1047      else:
1048        target = target + '.html'
1049
1050      return '<a href="https://developer.android.com/reference/%s">%s</a>' % (target, shortname)
1051
1052    hidltext = filter_links(hidltext, hidldoc_link_filter)
1053
1054    # Convert metadata entry "android.x.y.z" to form
1055    # HIDL tag format of "ANDROID_X_Y_Z"
1056    def hidldoc_crossref_filter(node):
1057      return csym(node.name)
1058
1059    # For each public tag "android.x.y.z" referenced, add a
1060    # "@see ANDROID_X_Y_Z"
1061    def hidldoc_crossref_see_filter(node_set):
1062      text = '\n'
1063      for node in node_set:
1064        text = text + '\n@see %s' % (csym(node.name))
1065
1066      return text if text != '\n' else ''
1067
1068    hidltext = filter_tags(hidltext, metadata, hidldoc_crossref_filter, hidldoc_crossref_see_filter)
1069
1070    comment_prefix = " " * indent + " * ";
1071
1072    def line_filter(line):
1073      # Indent each line
1074      # Add ' * ' to it for stylistic reasons
1075      # Strip right side of trailing whitespace
1076      return (comment_prefix + line).rstrip()
1077
1078    # Process each line with above filter
1079    hidltext = "\n".join(line_filter(i) for i in hidltext.split("\n")) + "\n"
1080
1081    return hidltext
1082
1083  return hidldoc_formatter
1084
1085def dedent(text):
1086  """
1087  Remove all common indentation from every line but the 0th.
1088  This will avoid getting <code> blocks when rendering text via markdown.
1089  Ignoring the 0th line will also allow the 0th line not to be aligned.
1090
1091  Args:
1092    text: A string of text to dedent.
1093
1094  Returns:
1095    String dedented by above rules.
1096
1097  For example:
1098    assertEquals("bar\nline1\nline2",   dedent("bar\n  line1\n  line2"))
1099    assertEquals("bar\nline1\nline2",   dedent(" bar\n  line1\n  line2"))
1100    assertEquals("bar\n  line1\nline2", dedent(" bar\n    line1\n  line2"))
1101  """
1102  text = textwrap.dedent(text)
1103  text_lines = text.split('\n')
1104  text_not_first = "\n".join(text_lines[1:])
1105  text_not_first = textwrap.dedent(text_not_first)
1106  text = text_lines[0] + "\n" + text_not_first
1107
1108  return text
1109
1110def md(text, img_src_prefix="", table_ext=True):
1111    """
1112    Run text through markdown to produce HTML.
1113
1114    This also removes all common indentation from every line but the 0th.
1115    This will avoid getting <code> blocks in markdown.
1116    Ignoring the 0th line will also allow the 0th line not to be aligned.
1117
1118    Args:
1119      text: A markdown-syntax using block of text to format.
1120      img_src_prefix: An optional string to prepend to each <img src="target"/>
1121
1122    Returns:
1123      String rendered by markdown and other rules applied (see above).
1124
1125    For example, this avoids the following situation:
1126
1127      <!-- Input -->
1128
1129      <!--- can't use dedent directly since 'foo' has no indent -->
1130      <notes>foo
1131          bar
1132          bar
1133      </notes>
1134
1135      <!-- Bad Output -- >
1136      <!-- if no dedent is done generated code looks like -->
1137      <p>foo
1138        <code><pre>
1139          bar
1140          bar</pre></code>
1141      </p>
1142
1143    Instead we get the more natural expected result:
1144
1145      <!-- Good Output -->
1146      <p>foo
1147      bar
1148      bar</p>
1149
1150    """
1151    text = dedent(text)
1152
1153    # full list of extensions at http://pythonhosted.org/Markdown/extensions/
1154    md_extensions = ['tables'] if table_ext else []# make <table> with ASCII |_| tables
1155    # render with markdown
1156    text = markdown.markdown(text, extensions=md_extensions)
1157
1158    # prepend a prefix to each <img src="foo"> -> <img src="${prefix}foo">
1159    text = re.sub(r'src="([^"]*)"', 'src="' + img_src_prefix + r'\1"', text)
1160    return text
1161
1162def filter_tags(text, metadata, filter_function, summary_function = None):
1163    """
1164    Find all references to tags in the form outer_namespace.xxx.yyy[.zzz] in
1165    the provided text, and pass them through filter_function and summary_function.
1166
1167    Used to linkify entry names in HMTL, javadoc output.
1168
1169    Args:
1170      text: A string representing a block of text destined for output
1171      metadata: A Metadata instance, the root of the metadata properties tree
1172      filter_function: A Node->string function to apply to each node
1173        when found in text; the string returned replaces the tag name in text.
1174      summary_function: A Node list->string function that is provided the list of
1175        unique tag nodes found in text, and which must return a string that is
1176        then appended to the end of the text. The list is sorted alphabetically
1177        by node name.
1178    """
1179
1180    tag_set = set()
1181    def name_match(name):
1182      return lambda node: node.name == name
1183
1184    # Match outer_namespace.x.y or outer_namespace.x.y.z, making sure
1185    # to grab .z and not just outer_namespace.x.y.  (sloppy, but since we
1186    # check for validity, a few false positives don't hurt).
1187    # Try to ignore items of the form {@link <outer_namespace>...
1188    for outer_namespace in metadata.outer_namespaces:
1189
1190      tag_match = r"(?<!\{@link\s)" + outer_namespace.name + \
1191        r"\.([a-zA-Z0-9\n]+)\.([a-zA-Z0-9\n]+)(\.[a-zA-Z0-9\n]+)?([/]?)"
1192
1193      def filter_sub(match):
1194        whole_match = match.group(0)
1195        section1 = match.group(1)
1196        section2 = match.group(2)
1197        section3 = match.group(3)
1198        end_slash = match.group(4)
1199
1200        # Don't linkify things ending in slash (urls, for example)
1201        if end_slash:
1202          return whole_match
1203
1204        candidate = ""
1205
1206        # First try a two-level match
1207        candidate2 = "%s.%s.%s" % (outer_namespace.name, section1, section2)
1208        got_two_level = False
1209
1210        node = metadata.find_first(name_match(candidate2.replace('\n','')))
1211        if not node and '\n' in section2:
1212          # Linefeeds are ambiguous - was the intent to add a space,
1213          # or continue a lengthy name? Try the former now.
1214          candidate2b = "%s.%s.%s" % (outer_namespace.name, section1, section2[:section2.find('\n')])
1215          node = metadata.find_first(name_match(candidate2b))
1216          if node:
1217            candidate2 = candidate2b
1218
1219        if node:
1220          # Have two-level match
1221          got_two_level = True
1222          candidate = candidate2
1223        elif section3:
1224          # Try three-level match
1225          candidate3 = "%s%s" % (candidate2, section3)
1226          node = metadata.find_first(name_match(candidate3.replace('\n','')))
1227
1228          if not node and '\n' in section3:
1229            # Linefeeds are ambiguous - was the intent to add a space,
1230            # or continue a lengthy name? Try the former now.
1231            candidate3b = "%s%s" % (candidate2, section3[:section3.find('\n')])
1232            node = metadata.find_first(name_match(candidate3b))
1233            if node:
1234              candidate3 = candidate3b
1235
1236          if node:
1237            # Have 3-level match
1238            candidate = candidate3
1239
1240        # Replace match with crossref or complain if a likely match couldn't be matched
1241
1242        if node:
1243          tag_set.add(node)
1244          return whole_match.replace(candidate,filter_function(node))
1245        else:
1246          print("  WARNING: Could not crossref likely reference {%s}" % (match.group(0)),
1247                file=sys.stderr)
1248          return whole_match
1249
1250      text = re.sub(tag_match, filter_sub, text)
1251
1252    if summary_function is not None:
1253      return text + summary_function(sorted(tag_set, key=lambda x: x.name))
1254    else:
1255      return text
1256
1257def ndk_replace_tag_wildcards(text, metadata):
1258    """
1259    Find all references to tags in the form android.xxx.* or android.xxx.yyy.*
1260    in the provided text, and replace them by NDK format of "ACAMERA_XXX_*" or
1261    "ACAMERA_XXX_YYY_*"
1262
1263    Args:
1264      text: A string representing a block of text destined for output
1265      metadata: A Metadata instance, the root of the metadata properties tree
1266    """
1267    tag_match = r"android\.([a-zA-Z0-9\n]+)\.\*"
1268    tag_match_2 = r"android\.([a-zA-Z0-9\n]+)\.([a-zA-Z0-9\n]+)\*"
1269
1270    def filter_sub(match):
1271      return "ACAMERA_" + match.group(1).upper() + "_*"
1272    def filter_sub_2(match):
1273      return "ACAMERA_" + match.group(1).upper() + match.group(2).upper() + "_*"
1274
1275    text = re.sub(tag_match, filter_sub, text)
1276    text = re.sub(tag_match_2, filter_sub_2, text)
1277    return text
1278
1279def filter_links(text, filter_function, summary_function = None):
1280    """
1281    Find all references to tags in the form {@link xxx#yyy [zzz]} in the
1282    provided text, and pass them through filter_function and
1283    summary_function.
1284
1285    Used to linkify documentation cross-references in HMTL, javadoc output.
1286
1287    Args:
1288      text: A string representing a block of text destined for output
1289      metadata: A Metadata instance, the root of the metadata properties tree
1290      filter_function: A (string, string)->string function to apply to each 'xxx#yyy',
1291        zzz pair when found in text; the string returned replaces the tag name in text.
1292      summary_function: A string list->string function that is provided the list of
1293        unique targets found in text, and which must return a string that is
1294        then appended to the end of the text. The list is sorted alphabetically
1295        by node name.
1296
1297    """
1298
1299    target_set = set()
1300    def name_match(name):
1301      return lambda node: node.name == name
1302
1303    tag_match = r"\{@link\s+([^\s\}\|]+)(?:\|([^\s\}]+))*([^\}]*)\}"
1304
1305    def filter_sub(match):
1306      whole_match = match.group(0)
1307      target = match.group(1)
1308      target_ndk = match.group(2)
1309      shortname = match.group(3).strip()
1310
1311      #print("Found link '%s' ndk '%s' as '%s' -> '%s'" % (target, target_ndk, shortname, filter_function(target, target_ndk, shortname)))
1312
1313      # Replace match with crossref
1314      target_set.add(target)
1315      return filter_function(target, target_ndk, shortname)
1316
1317    text = re.sub(tag_match, filter_sub, text)
1318
1319    if summary_function is not None:
1320      return text + summary_function(sorted(target_set))
1321    else:
1322      return text
1323
1324def any_visible(section, kind_name, visibilities):
1325  """
1326  Determine if entries in this section have an applied visibility that's in
1327  the list of given visibilities.
1328
1329  Args:
1330    section: A section of metadata
1331    kind_name: A name of the kind, i.e. 'dynamic' or 'static' or 'controls'
1332    visibilities: An iterable of visibilities to match against
1333
1334  Returns:
1335    True if the section has any entries with any of the given visibilities. False otherwise.
1336  """
1337
1338  for inner_namespace in get_children_by_filtering_kind(section, kind_name,
1339                                                        'namespaces'):
1340    if any(filter_visibility(inner_namespace.merged_entries, visibilities)):
1341      return True
1342
1343  return any(filter_visibility(get_children_by_filtering_kind(section, kind_name,
1344                                                              'merged_entries'),
1345                               visibilities))
1346
1347
1348def filter_visibility(entries, visibilities):
1349  """
1350  Remove entries whose applied visibility is not in the supplied visibilities.
1351
1352  Args:
1353    entries: An iterable of Entry nodes
1354    visibilities: An iterable of visibilities to filter against
1355
1356  Yields:
1357    An iterable of Entry nodes
1358  """
1359  return (e for e in entries if e.applied_visibility in visibilities)
1360
1361def remove_hal_non_visible(entries):
1362  """
1363  Filter the given entries by removing those that are not HAL visible:
1364  synthetic, fwk_only, or fwk_java_public.
1365
1366  Args:
1367    entries: An iterable of Entry nodes
1368
1369  Yields:
1370    An iterable of Entry nodes
1371  """
1372  return (e for e in entries if not (e.synthetic or e.visibility == 'fwk_only'
1373                                     or e.visibility == 'fwk_java_public'))
1374
1375"""
1376  Return the vndk version for a given hal minor version. The major version is assumed to be 3
1377
1378  Args:
1379    hal_minor_version : minor version to retrieve the vndk version for
1380
1381  Yields:
1382    int representing the vndk version
1383  """
1384def get_vndk_version(hal_minor_version):
1385  if hal_minor_version <= FRAMEWORK_CAMERA_VNDK_HAL_MINOR_VERSION:
1386    return 0
1387  return hal_minor_version - FRAMEWORK_CAMERA_VNDK_HAL_MINOR_VERSION \
1388        + FRAMEWORK_CAMERA_VNDK_STARTING_VERSION
1389
1390"""
1391  Returns an api level -> dict of metadata tags corresponding to the api level
1392
1393  Args:
1394    sections : metadata sections to create the mapping for
1395    metadata: the metadata structure to be used to create the mapping
1396    kind : kind of entries to create a mapping for : 'static' or 'dynamic'
1397
1398  Yields:
1399    A dictionary mapping api level to a dictionary of metadata tags for the particular key (api level)
1400  """
1401def get_api_level_to_keys(sections, metadata, kind):
1402  api_level_to_keys = {}
1403  for sec in sections:
1404    for idx,entry in enumerate(remove_synthetic(find_unique_entries(sec))):
1405      if entry._hal_minor_version > FRAMEWORK_CAMERA_VNDK_HAL_MINOR_VERSION and \
1406          metadata.is_entry_this_kind(entry, kind):
1407        api_level = get_vndk_version(entry._hal_minor_version)
1408        try:
1409          api_level_to_keys[api_level].add(entry.name)
1410        except KeyError:
1411          api_level_to_keys[api_level] = {entry.name}
1412  #Insert the keys in sorted order since dicts in python (< 3.7, even OrderedDicts don't actually
1413  # sort keys)
1414  api_level_to_keys_ordered = OrderedDict()
1415  for api_level_ordered in sorted(api_level_to_keys.keys()):
1416    api_level_to_keys_ordered[api_level_ordered] = sorted(api_level_to_keys[api_level_ordered])
1417  return api_level_to_keys_ordered
1418
1419def remove_synthetic(entries):
1420  """
1421  Filter the given entries by removing those that are synthetic.
1422
1423  Args:
1424    entries: An iterable of Entry nodes
1425
1426  Yields:
1427    An iterable of Entry nodes
1428  """
1429  return (e for e in entries if not e.synthetic)
1430
1431def filter_added_in_hal_version(entries, hal_major_version, hal_minor_version):
1432  """
1433  Filter the given entries to those added in the given HIDL HAL version
1434
1435  Args:
1436    entries: An iterable of Entry nodes
1437    hal_major_version: Major HIDL version to filter for
1438    hal_minor_version: Minor HIDL version to filter for
1439
1440  Yields:
1441    An iterable of Entry nodes
1442  """
1443  return (e for e in entries if e.hal_major_version == hal_major_version and e.hal_minor_version == hal_minor_version)
1444
1445def filter_has_enum_values_added_in_hal_version(entries, hal_major_version, hal_minor_version):
1446  """
1447  Filter the given entries to those that have a new enum value added in the given HIDL HAL version
1448
1449  Args:
1450    entries: An iterable of Entry nodes
1451    hal_major_version: Major HIDL version to filter for
1452    hal_minor_version: Minor HIDL version to filter for
1453
1454  Yields:
1455    An iterable of Entry nodes
1456  """
1457  return (e for e in entries if e.has_new_values_added_in_hal_version(hal_major_version, hal_minor_version))
1458
1459def permission_needed_count(root):
1460  """
1461  Return the number entries that need camera permission.
1462
1463  Args:
1464    root: a Metadata instance
1465
1466  Returns:
1467    The number of entires that need camera permission.
1468
1469  """
1470  ret = 0
1471  for sec in find_all_sections(root):
1472      ret += len(list(filter_has_permission_needed(remove_hal_non_visible(find_unique_entries(sec)))))
1473
1474  return ret
1475
1476def filter_has_permission_needed(entries):
1477  """
1478    Filter the given entries by removing those that don't need camera permission.
1479
1480    Args:
1481      entries: An iterable of Entry nodes
1482
1483    Yields:
1484      An iterable of Entry nodes
1485  """
1486  return (e for e in entries if e.permission_needed == 'true')
1487
1488def filter_ndk_visible(entries):
1489  """
1490  Filter the given entries by removing those that are not NDK visible.
1491
1492  Args:
1493    entries: An iterable of Entry nodes
1494
1495  Yields:
1496    An iterable of Entry nodes
1497  """
1498  return (e for e in entries if e.applied_ndk_visible == 'true')
1499
1500def wbr(text):
1501  """
1502  Insert word break hints for the browser in the form of <wbr> HTML tags.
1503
1504  Word breaks are inserted inside an HTML node only, so the nodes themselves
1505  will not be changed. Attributes are also left unchanged.
1506
1507  The following rules apply to insert word breaks:
1508  - For characters in [ '.', '/', '_' ]
1509  - For uppercase letters inside a multi-word X.Y.Z (at least 3 parts)
1510
1511  Args:
1512    text: A string of text containing HTML content.
1513
1514  Returns:
1515    A string with <wbr> inserted by the above rules.
1516  """
1517  SPLIT_CHARS_LIST = ['.', '_', '/']
1518  SPLIT_CHARS = r'([.|/|_/,]+)' # split by these characters
1519  CAP_LETTER_MIN = 3 # at least 3 components split by above chars, i.e. x.y.z
1520  def wbr_filter(text):
1521      new_txt = text
1522
1523      # for johnyOrange.appleCider.redGuardian also insert wbr before the caps
1524      # => johny<wbr>Orange.apple<wbr>Cider.red<wbr>Guardian
1525      for words in text.split(" "):
1526        for char in SPLIT_CHARS_LIST:
1527          # match at least x.y.z, don't match x or x.y
1528          if len(words.split(char)) >= CAP_LETTER_MIN:
1529            new_word = re.sub(r"([a-z])([A-Z])", r"\1<wbr>\2", words)
1530            new_txt = new_txt.replace(words, new_word)
1531
1532      # e.g. X/Y/Z -> X/<wbr>Y/<wbr>/Z. also for X.Y.Z, X_Y_Z.
1533      new_txt = re.sub(SPLIT_CHARS, r"\1<wbr>", new_txt)
1534
1535      return new_txt
1536
1537  # Do not mangle HTML when doing the replace by using BeatifulSoup
1538  # - Use the 'html.parser' to avoid inserting <html><body> when decoding
1539  soup = bs4.BeautifulSoup(text, features='html.parser')
1540  wbr_tag = lambda: soup.new_tag('wbr') # must generate new tag every time
1541
1542  for navigable_string in soup.findAll(text=True):
1543      parent = navigable_string.parent
1544
1545      # Insert each '$text<wbr>$foo' before the old '$text$foo'
1546      split_by_wbr_list = wbr_filter(navigable_string).split("<wbr>")
1547      for (split_string, last) in enumerate_with_last(split_by_wbr_list):
1548          navigable_string.insert_before(split_string)
1549
1550          if not last:
1551            # Note that 'insert' will move existing tags to this spot
1552            # so make a new tag instead
1553            navigable_string.insert_before(wbr_tag())
1554
1555      # Remove the old unmodified text
1556      navigable_string.extract()
1557
1558  return soup.decode()
1559
1560def copyright_year():
1561  return _copyright_year
1562
1563def enum():
1564  return _enum
1565
1566def first_hal_minor_version(hal_major_version):
1567  return 2 if hal_major_version == 3 else 0
1568
1569def find_all_sections_added_in_hal(root, hal_major_version, hal_minor_version):
1570  """
1571  Find all descendants that are Section or InnerNamespace instances, which
1572  were added in HIDL HAL version major.minor. The section is defined to be
1573  added in a HAL version iff the lowest HAL version number of its entries is
1574  that HAL version.
1575
1576  Args:
1577    root: a Metadata instance
1578    hal_major/minor_version: HAL version numbers
1579
1580  Returns:
1581    A list of Section/InnerNamespace instances
1582
1583  Remarks:
1584    These are known as "sections" in the generated C code.
1585  """
1586  all_sections = find_all_sections(root)
1587  new_sections = []
1588  for section in all_sections:
1589    min_major_version = None
1590    min_minor_version = None
1591    for entry in remove_hal_non_visible(find_unique_entries(section)):
1592      min_major_version = (min_major_version or entry.hal_major_version)
1593      min_minor_version = (min_minor_version or entry.hal_minor_version)
1594      if entry.hal_major_version < min_major_version or \
1595          (entry.hal_major_version == min_major_version and entry.hal_minor_version < min_minor_version):
1596        min_minor_version = entry.hal_minor_version
1597        min_major_version = entry.hal_major_version
1598    if min_major_version == hal_major_version and min_minor_version == hal_minor_version:
1599      new_sections.append(section)
1600  return new_sections
1601
1602def find_first_older_used_hal_version(section, hal_major_version, hal_minor_version):
1603  hal_version = (0, 0)
1604  for v in section.hal_versions:
1605    if (v[0] > hal_version[0] or (v[0] == hal_version[0] and v[1] > hal_version[1])) and \
1606        (v[0] < hal_major_version or (v[0] == hal_major_version and v[1] < hal_minor_version)):
1607      hal_version = v
1608  return hal_version
1609
1610# Some exceptions need to be made regarding enum value identifiers in AIDL.
1611# Process them here.
1612def aidl_enum_value_name(name):
1613  if name == 'ANDROID_INFO_SUPPORTED_BUFFER_MANAGEMENT_VERSION_HIDL_DEVICE_3_5':
1614    name = 'ANDROID_INFO_SUPPORTED_BUFFER_MANAGEMENT_VERSION_AIDL_DEVICE'
1615  return name
1616
1617def aidl_enum_values(entry):
1618  ignoreList = [
1619    'ANDROID_SCALER_AVAILABLE_RECOMMENDED_STREAM_CONFIGURATIONS_PUBLIC_END',
1620    'ANDROID_SCALER_AVAILABLE_RECOMMENDED_STREAM_CONFIGURATIONS_PUBLIC_END_3_8'
1621  ]
1622  return [
1623    val for val in entry.enum.values if '%s_%s'%(csym(entry.name), val.name) not in ignoreList
1624  ]