text: """Use this library to recognize morphology of the token. It'll look for an
  appropriate rule in DB and returns you the result.
  """
  
  from libs.arrproc import isSupsetTo
  import libs.strproc as strproc
  
  
  class MorphologyRecognizer:
      """This class contains methods for morphology processing.
      """
  
      def __init__(
          self, collection, tagparser=None, priorityList=None, applierFunc=None,
          applySpecial=True
      ):
          """Init the recognizer with specified db connection.
  
          Args:
              collection (Collection): A Collection from pymongo that will be
                  used for rules searching.
              tagparser (Class): Class with "parse" method which can parse XPOS
                  of the token.
              priorityList (list): Specify dominating of one type over another.
                  (See the example below).
              applierFunc (function): Searching might return a bundle of rules,
                  not just one correct, so you can specify a function which will
                  extract element you're really need. You can also use a static
                  method selectFirst() from this class to select the first
                  rule from the list.
              applySpecial (bool): Avoid making DB requests for punctuation.
  
          applierFunc Args:
              list: List of rules from DB.
              token: Current token.
  
          priorityList example:
              Suppose, we have this data:
              priorityList = [
                  {
                      __what: {
                          xpos: "Q"
                      }
                      __replace: {
                          xpos: "Css",
                          upos: "CCONJ",
                          ...additional parameters
                      }
                  },
                  ...
              ]
              That means the every occuring of "Q" XPOS will be replaced with
              "CCONJ Css" pos, but only when both of them are presented in DB
              response. Any other parameters ("additional parameters") will be
              added to the token's rule too.
              This may be useful when two equal words appears to be different
              POS, but one of them more frequent.
  
          """
  
          self.prioritizer = Prioritizer(priorityList)
          self.collection = collection
          self.tagparser = tagparser
          self.applier = applierFunc
          self.applySpecial = applySpecial
  
      def getRulesFor(self, token):
          """Guess all the rules that can be applied to this token.
  
          Args:
              token (str)
  
          Returns:
              list: A list of rules as they are stored in DB. An empty list if
                  nothing works.
  
          """
  
          query = self.collection.aggregate([
              {
                  "$match": {
                      "$expr": {
                          "$anyElementTrue": {
                              "$map": {
                                  "input": "$data",
                                  "as": "s",
                                  "in": {
                                      "$ne": [
                                          -1,
                                          {
                                              "$indexOfBytes": [
                                                  token, "$$s"
                                              ]
                                          }
                                      ]
                                  }
                              }
                          }
                      },
                      "data": {
                          "$type": "array"
                      },
                      "type": "rules"
                  }
              }
          ])
  
          return list(query)
  
      def getStatic(self, token):
          """Look if this token has static POS and returns a rule for it.
  
          Args:
              token (str)
  
          Returns:
              list:  A list of rules as they are stored in DB. An empty list if
                  nothing works.
  
          """
  
          return list(
              self.collection.find({
                  "type": "static",
                  "data": {
                      "$in": [token]
                  }
              })
          )
  
      def getExceptions(self, token):
          """Look if this token is an exception and returns a rule for it.
  
          Args:
              token (str)
  
          Returns:
              list: A list of rules as they are stored in DB. An empty list if
                  nothing works.
  
          """
  
          return list(
              self.collection.find({
                  "type": "exceptions",
                  "data": {
                      "$in": [token]
                  }
              })
          )
  
      def recognize(
          self, token, withApplier=True, showDB=False
      ):
          """Apply exceptions, static and rules searching in order to guess XPOS
          of the given token.
  
          Args:
              token (str)
              withApplier (bool): Execute applier function over the DB response.
                  This will throw an error, if self.applier is not defined.
              showDB (bool): If True, then function will also return result
                  without applier.
              applySpecial (bool): Avoid making DB requests for punctuation.
  
          Returns:
              dict: A rule as it stored in DB.
                  {
                      "_id": ...,
                      "upos": "...",
                      "xpos": "..."
                  }
                  If XPOS wasn't recognized, returns None.
                  Returns list of rules if one exist.
  
          """
  
          token = token.lower()
  
          if self.applySpecial:
              special = self.recognizeSpecial(token)
              if special:
                  return special if withApplier else [special]
              del special
  
          funcs = [self.getExceptions, self.getStatic, self.getRulesFor]
          query = None  # Response from DB
          result = None  # Result rule
          for func in funcs:
              query = func(token)
              if len(query) != 0:
                  result = self.applier(query, token) if withApplier else query
                  break
  
          if not result:
              return None
  
          # Prioritizer won't process a list of tokens, just one
          if withApplier:
              self.prioritizer.apply(result, query)
  
          # This will delete all the keys except upos and xpos and parse the XPOS
          if self.tagparser and withApplier:
              result = self.unwrapXPOS({
                  "upos": result["upos"],
                  "xpos": result["xpos"]
              })
  
          if showDB:
              query = [self.unwrapXPOS(rule) for rule in query]
  
          return result if not showDB else (result, query)
  
      def recognizeSpecial(self, token):
          """Recognize tokens where not db querying are needed (sym, punct etc.)
  
          Args:
              token (str): Token to be recognized.
  
          Returns:
              dict: Rule for token.
                  {
                      "xpos": ...,
                      "upos": ...
                  }
  
          """
  
          if strproc.isPunct(token):
              if strproc.isComma(token):
                  return {
                      "upos": "PUNCT",
                      "xpos": self.tagparser.stringify({"upos": "PUNCT",
                                                        "PunctType": "Comm"}),
                      "name": "Punctuation"
                  }
              elif strproc.isDash(token):
                  return {
                      "upos": "PUNCT",
                      "xpos": self.tagparser.stringify({"upos": "PUNCT",
                                                        "PunctType": "Dash"}),
                      "name": "Punctuation"
                  }
              elif strproc.isColon(token):
                  return {
                      "upos": "PUNCT",
                      "xpos": self.tagparser.stringify({"upos": "PUNCT",
                                                        "PunctType": "Colo"}),
                      "name": "Punctuation"
                  }
              elif strproc.isSemicolon(token):
                  return {
                      "upos": "PUNCT",
                      "xpos": self.tagparser.stringify({"upos": "PUNCT",
                                                        "PunctType": "Semi"}),
                      "name": "Punctuation"
                  }
              elif strproc.isLeftBracket(token):
                  return {
                      "upos": "PUNCT",
                      "xpos": self.tagparser.stringify({"upos": "PUNCT",
                                                        "PunctType": "LBra"}),
                      "name": "Punctuation"
                  }
              elif strproc.isRightBracket(token):
                  return {
                      "upos": "PUNCT",
                      "xpos": self.tagparser.stringify({"upos": "PUNCT",
                                                        "PunctType": "RBra"}),
                      "name": "Punctuation"
                  }
              else:
                  return {
                      "upos": "PUNCT",
                      "xpos": self.tagparser.stringify({"upos": "PUNCT"}),
                      "name": "Punctuation"
                  }
  
          if strproc.isSym(token):
              return {
                  "upos": "SYM",
                  "xpos": self.tagparser.stringify({"upos": "SYM"}),
                  "name": "Residual"
              }
  
          if strproc.hasNonUkrainian(token):
              return {
                  "xpos": "X",
                  "upos": self.tagparser.stringify({"upos": "X"}),
                  "name": "Residual"
              }
  
      def unwrapXPOS(self, rule):
          """Append properties of XPOS to the rule.
  
          Args:
              rule (dict): Rule with ["xpos"] property.
  
          Returns:
              {**rule, **parsedXPOS}
  
          """
  
          if self.tagparser:
              return {
                  **rule,
                  **self.tagparser.parse(rule["xpos"])
              }
          else:
              return rule
  
      @staticmethod
      def selectFirst(bundle, token):
          """Returns first document in bundle.
  
          Args:
              (See MorphologyRecognizer.recognize)
  
          Returns:
              dict: A document as it was stored in DB.
  
          """
  
          return bundle[0] if bundle else None
  
      @staticmethod
      def selectByEnding(bundle, token):
          """Returns first document in bundle, but except those which rule does
          not match with the very ending or the beginning of the token.
  
          Args:
              (See MorphologyRecognizer.recognize)
  
          Returns:
              dict: A document is it was stored in DB.
  
          """
  
          def getBiggestEdge(li, string):
              """Returns first item from list if that can be ending of beginning
              of the word.
              """
              # Longest rules first
              li.sort(key=len, reverse=True)
  
              for item in li:
                  # Do not allow strings with len=1 recognize as the beginnings
                  if len(item) == 1:
                      if string[-len(item):]:
                          return item
                  elif string[:len(item)] == item or string[-len(item):] == item:
                      return item
  
              return None
  
          for rule in bundle:
              # Replace list of rules with one-element list. Element will be
              # chosen by getBiggestEdge
              rule["data"] = [getBiggestEdge(rule["data"], token)]
  
          bundle.sort(
              # Move down empty rules
              key=lambda rule: len(rule["data"][0]) if rule["data"][0] else 0,
              reverse=True
          )
  
          return bundle[0] if bundle else None
  
  
  class Prioritizer:
      """This class provides interface to apply priority lists to morphology
      recognizing. For description see MorphologyRecognizer.recognize method.
      """
  
      def __init__(self, li=None):
          """Init the class and remember the list.
  
          Args:
              li (list): Priority list in the following format:
                  [
                      {
                          "__what": {  # Here listed properties to replace.
                              "upos": "...",
                              ...
                          }
                          "__replace": {  # New properties which will be applied.
                              "upos": "...",
                              ...
                          }
                      },
                      ...
                  ]
  
          """
  
          self.li = li
  
      def apply(self, token, response):
          """Apply the rules to the token.
  
          Args:
              token (dict): Dictionary of the token.
              response (list): Rules returned from DB.
  
          Return :
              dict: Resulting token.
  
          """
  
          if not self.li:
              return token
  
          # Unpack each {__what: .., __replace: ..} in self.li as (what, replace)
          for what, replace in [
              (rule["__what"], rule["__replace"]) for rule in self.li
          ]:
  
              if not isSupsetTo(token, what):
                  continue
  
              if self.responseContains(replace, response):
                  token.update(replace)
                  # Do only first replacement
                  break
  
          return token
  
      def responseContains(self, what, response):
          """Returns True if response contains rule with the properties defined
          in `what`.
  
          Args:
              what (dict): Dictionary to search.
              response (list): Response of DB.
  
          Returns:
              bool: True if found, False otherwise.
  
          """
  
          for item in response:
              if isSupsetTo(item, what):
                  return True
  
          return False
  