text: # SPDX-License-Identifier: Apache-2.0
  
  from typing import Iterable
  
  from .common import k2o_logger
  from .funcbook import get_converter
  from ._parser_tf import infer_variable_type, tsname_to_node, adjust_input_batch_size
  
  
  def extract_inbound_nodes(layer):
      if hasattr(layer, 'inbound_nodes'):
          return layer.inbound_nodes
      elif hasattr(layer, '_inbound_nodes'):
          return layer._inbound_nodes
      else:
          raise ValueError("Failed to find inbound_nodes and _inbound_nodes when parsing %s" % layer.name)
  
  
  def list_input_tensors(node):
      """
      Since Tensorflow 1.14, sometimes the node.input_tensors may not be a list, though the word is plural.
      """
      return [node.input_tensors] if hasattr(node.input_tensors, 'dtype') else node.input_tensors
  
  
  def list_output_tensors(node):
      """
      Since Tensorflow 1.14, sometimes the node.output_tensors may not be a list, though the output_tensors is plural.
      """
      return [node.output_tensors] if hasattr(node.output_tensors, 'dtype') else node.output_tensors
  
  
  def list_input_shapes(node):
      """
      Since Tensorflow 1.14, sometimes the node.input_shapes may not be a list, though the input_shapes is plural.
      """
      return node.input_shapes if isinstance(node.input_shapes[0], Iterable) else [node.input_shapes]
  
  
  def list_output_shapes(node):
      """
      Since Tensorflow 1.14, sometimes the node.output_shapes may not be a list, though the output_shapes is plural.
      """
      return node.output_shapes if isinstance(node.output_shapes[0], Iterable) else [node.output_shapes]
  
  
  def list_input_mask(layer):
      if hasattr(layer, 'input_mask'):
          if hasattr(layer.input_mask, 'dtype'):
              return [layer.input_mask]
          if layer.input_mask is not None:
              return [ts_ for ts_ in layer.input_mask if ts_ is not None]
  
      return []
  
  
  def list_output_mask(layer):
      if hasattr(layer, 'output_mask'):
          if hasattr(layer.output_mask, 'dtype'):
              return [layer.output_mask]
          if layer.output_mask is not None:
              return [ts_ for ts_ in layer.output_mask if ts_ is not None]
  
      return []
  
  
  def on_parsing_keras_layer(graph, node_list, layer, kenode, model, varset, prefix=None):
      operator = varset.declare_local_operator(type(layer), raw_model=layer, op_name=layer.name)
      operator.nodelist = node_list
  
      inputs = list_input_tensors(kenode)
      outputs = list_output_tensors(kenode)
  
      # This layer will be visited because its output is other layer's input
      assert len(node_list) == 0 or node_list[0] in [ts_.op for ts_ in outputs]
  
      if prefix is None:  # prefix is designed for the distinguish among the shared model instances.
          prefix = ''
  
      kenode_input_shapes = kenode.input_shapes if isinstance(kenode.input_shapes, list) else [kenode.input_shapes]
      for n_, i_ in enumerate(inputs):
          iname = prefix + i_.name
          k2o_logger().debug('\tinput : ' + iname)
          var_type = adjust_input_batch_size(infer_variable_type(i_, varset.target_opset, kenode_input_shapes[n_]))
          i0 = varset.get_local_variable_or_declare_one(iname, var_type)
          operator.add_input(i0)
  
      if hasattr(layer, 'input_mask') and layer.input_mask is not None:
          in_mask = layer.input_mask if isinstance(layer.input_mask, (list, tuple)) else [layer.input_mask]
          for im_ in [m_ for m_ in in_mask if m_ is not None]:
              mts_name = im_.name  # input mask in a shared model is not supported yet, why is it needed?
              k2o_logger().debug('\tinput mask: ' + mts_name)
              mts_var = varset.get_local_variable_or_declare_one(mts_name, infer_variable_type(im_, varset.target_opset))
              operator.add_input_mask(mts_var)
  
      kenode_output_shapes = kenode.output_shapes if isinstance(kenode.output_shapes, list) else [kenode.output_shapes]
      for n_, o_ in enumerate(outputs):
          oname = prefix + o_.name
          k2o_logger().debug('\toutput: ' + oname)
          o1 = varset.get_local_variable_or_declare_one(oname,
                                                        infer_variable_type(o_, varset.target_opset,
                                                                            kenode_output_shapes[n_]))
          operator.add_output(o1)
  
      if hasattr(layer, 'output_mask') and layer.output_mask is not None:
          out_mask = layer.output_mask if isinstance(layer.output_mask, (list, tuple)) else [layer.output_mask]
          for om_ in [m_ for m_ in out_mask if m_ is not None]:
              mts_name = prefix + om_.name
              k2o_logger().debug('\toutput mask: ' + mts_name)
              mts_var = varset.get_local_variable_or_declare_one(mts_name, infer_variable_type(om_, varset.target_opset))
              operator.add_output_mask(mts_var)
  
      if hasattr(layer, 'mask_value') and layer.mask_value is not None:
          operator.mask_value = layer.mask_value
  
      cvt = get_converter(operator.type)
      if cvt is not None and hasattr(cvt, 'shape_infer'):
          operator.shape_infer = cvt.shape_infer
  
      return operator
  
  
  def build_opdict_from_keras(model):
      output_dict = {}
      for l_ in model.layers:
          if hasattr(l_, 'layers'):
              submodel_dict = build_opdict_from_keras(l_)
              shared_layer = False
              for node_ in extract_inbound_nodes(l_):
                  shared_layer |= any(
                      ts_.name not in submodel_dict for ts_ in list_output_tensors(node_))
                  if shared_layer:
                      break
              if not shared_layer:  # shared layer(model) will be processed as a whole.
                  output_dict.update(submodel_dict)
                  continue
  
          for node_ in extract_inbound_nodes(l_):
              for ts_ in list_output_tensors(node_):
                  output_dict[ts_.name] = (l_, model)
  
          for ts_ in list_output_mask(l_):
              output_dict[ts_.name] = (l_, model)
  
      return {tsname_to_node(n_): v_ for n_, v_ in output_dict.items()}
  