text: """This module is used for Newgrounds parsing."""
  
  from collections import namedtuple
  from itertools import chain
  import re
  
  from yarl import URL
  
  from gd.utils.parser import ExtDict
  from gd.typing import Dict, HTMLElement, List, TypeVar, Union, XMLElement
  
  use_lxml, Element = False, XMLElement
  try:
      from lxml import html
  
      use_lxml, Element = True, HTMLElement
  except ImportError:
      try:
          from html5lib import parse
      except ImportError:
          print("Failed to import lxml and html5lib. Newgrounds parsing will not be supported.")
  
  __all__ = (
      "re_link",
      "re_name",
      "re_author",
      "re_size",
      "re_attrib",
      "find_song_info",
      "extract_info_from_endpoint",
      "search_song_data",
      "extract_user_songs",
      "extract_users",
  )
  
  T = TypeVar("T")
  U = TypeVar("U")
  
  re_link, re_size, re_name, re_author, re_attrib = (
      r"https://audio\.ngfiles\.com/([^\"']+)",
      r".filesize.:(\d+)",
      r"<title>([^<>]+)</title>",
      r".artist.:.([^\"']+).",
      r"{}[ ]*=[ ]*(?P<quote>[\"'])(.*?)(?P=quote)",
  )
  re_class = re_attrib.format("class")
  
  SongInfo = namedtuple("SongInfo", "link size name author")
  
  
  def html_parse(text: str) -> Element:
      text = re.sub(re_class, lambda match: match.group(0).replace(" ", ""), text)
  
      if use_lxml:
          return html.fromstring(text)
      else:
          return parse(text, "etree", False)
  
  
  def find_song_info(text: str) -> SongInfo:
      try:
          return SongInfo(
              link=re.search(re_link, text).group(0),
              size=int(re.search(re_size, text).group(1)),
              name=re.search(re_name, text).group(1),
              author=re.search(re_author, text).group(1),
          )
      except AttributeError:  # not found
          raise ValueError("Song info was not found.") from None
  
  
  def extract_info_from_endpoint(text: str) -> ExtDict:
      artist, whitelisted, scouted, song, api, *_ = filter(is_not_empty, re.split(r"</?br>", text))
      return ExtDict(
          artist=artist.split("Artist: ").pop(),
          song=song.split("Song: ").pop(),
          whitelisted=check_not(whitelisted),
          scouted=check_not(scouted),
          api=check_not(api),
      )
  
  
  def search_song_data(text: str) -> List[ExtDict]:
      tree, result = html_parse(text), []
  
      for a, div in zip(
          tree.findall(r'.//a[@class="item-audiosubmission"]'),
          tree.findall(r'.//div[@class="detail-title"]'),
      ):
          url = URL(a.attrib["href"]).with_scheme("https")
          song_id = int(url.parts[-1])
  
          h4, span, *_ = div.getchildren()
  
          name = switch_if_none(h4.text, "") + "".join(
              switch_if_none(mark.text, "") + switch_if_none(mark.tail, "")
              for mark in h4.getchildren()
          )
          author = span.getchildren()[0].text
  
          result.append(ExtDict(id=song_id, name=name, author=author, links={"normal": str(url)}))
  
      return result
  
  
  def extract_user_songs(
      json: Dict[str, Dict[str, Dict[str, Union[Dict[str, str], List[str]]]]]
  ) -> List[ExtDict]:
      result = []
  
      try:
          years = json["years"].values()
      except (TypeError, AttributeError):  # not found
          return result
  
      for entry in chain.from_iterable(year["items"] for year in years):
          tree = html_parse(entry)
          a = tree.findall(r'.//a[@class="item-link"]')[0]
  
          url = URL(a.attrib["href"]).with_scheme("https")
          song_id = int(url.parts[-1])
  
          name = a.attrib["title"]
  
          result.append(ExtDict(id=song_id, name=name, links={"normal": str(url)}))
  
      return result
  
  
  def extract_users(text: str) -> List[ExtDict]:
      tree, result = html_parse(text), []
  
      for a in tree.findall(r'.//div[@class="item-details-main"]/h4/a'):
          url = URL(a.attrib["href"]).with_scheme("https")
          name = a.text
  
          result.append(ExtDict(link=url, name=name))
  
      return result
  
  
  def switch_if_none(obj: T, to: U) -> Union[T, U]:
      if obj is None:
          return to
      return obj
  
  
  def check_not(string: str) -> bool:
      return "not" not in string.casefold()
  
  
  def is_not_empty(string: str) -> bool:
      return len(string) > 0
  