# Copyright 2009 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the 'License')
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an 'AS IS' BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Python implementation of the UA parser."""

from __future__ import absolute_import

import re
from objict import objict


__author__ = "Lindsey Simon <elsigh@gmail.com>"


def parse(user_agent):
    return objict.fromdict(Parse(user_agent))


class UserAgentParser(object):
    def __init__(
        self, pattern, family_replacement=None, v1_replacement=None, v2_replacement=None
    ):
        """Initialize UserAgentParser.

        Args:
          pattern: a regular expression string
          family_replacement: a string to override the matched family (optional)
          v1_replacement: a string to override the matched v1 (optional)
          v2_replacement: a string to override the matched v2 (optional)
        """
        self.pattern = pattern
        self.user_agent_re = re.compile(self.pattern)
        self.family_replacement = family_replacement
        self.v1_replacement = v1_replacement
        self.v2_replacement = v2_replacement

    def MatchSpans(self, user_agent_string):
        match_spans = []
        match = self.user_agent_re.search(user_agent_string)
        if match:
            match_spans = [
                match.span(group_index) for group_index in range(1, match.lastindex + 1)
            ]
        return match_spans

    def Parse(self, user_agent_string):
        family, v1, v2, v3 = None, None, None, None
        match = self.user_agent_re.search(user_agent_string)
        if match:
            if self.family_replacement:
                if re.search(r"\$1", self.family_replacement):
                    family = re.sub(r"\$1", match.group(1), self.family_replacement)
                else:
                    family = self.family_replacement
            else:
                family = match.group(1)

            if self.v1_replacement:
                v1 = self.v1_replacement
            elif match.lastindex and match.lastindex >= 2:
                v1 = match.group(2) or None

            if self.v2_replacement:
                v2 = self.v2_replacement
            elif match.lastindex and match.lastindex >= 3:
                v2 = match.group(3) or None

            if match.lastindex and match.lastindex >= 4:
                v3 = match.group(4) or None

        return family, v1, v2, v3


class OSParser(object):
    def __init__(
        self,
        pattern,
        os_replacement=None,
        os_v1_replacement=None,
        os_v2_replacement=None,
        os_v3_replacement=None,
        os_v4_replacement=None,
    ):
        """Initialize UserAgentParser.

        Args:
          pattern: a regular expression string
          os_replacement: a string to override the matched os (optional)
          os_v1_replacement: a string to override the matched v1 (optional)
          os_v2_replacement: a string to override the matched v2 (optional)
          os_v3_replacement: a string to override the matched v3 (optional)
          os_v4_replacement: a string to override the matched v4 (optional)
        """
        self.pattern = pattern
        self.user_agent_re = re.compile(self.pattern)
        self.os_replacement = os_replacement
        self.os_v1_replacement = os_v1_replacement
        self.os_v2_replacement = os_v2_replacement
        self.os_v3_replacement = os_v3_replacement
        self.os_v4_replacement = os_v4_replacement

    def MatchSpans(self, user_agent_string):
        match_spans = []
        match = self.user_agent_re.search(user_agent_string)
        if match:
            match_spans = [
                match.span(group_index) for group_index in range(1, match.lastindex + 1)
            ]
        return match_spans

    def Parse(self, user_agent_string):
        os, os_v1, os_v2, os_v3, os_v4 = None, None, None, None, None
        match = self.user_agent_re.search(user_agent_string)
        if match:
            if self.os_replacement:
                os = MultiReplace(self.os_replacement, match)
            elif match.lastindex:
                os = match.group(1)

            if self.os_v1_replacement:
                os_v1 = MultiReplace(self.os_v1_replacement, match)
            elif match.lastindex and match.lastindex >= 2:
                os_v1 = match.group(2)

            if self.os_v2_replacement:
                os_v2 = MultiReplace(self.os_v2_replacement, match)
            elif match.lastindex and match.lastindex >= 3:
                os_v2 = match.group(3)

            if self.os_v3_replacement:
                os_v3 = MultiReplace(self.os_v3_replacement, match)
            elif match.lastindex and match.lastindex >= 4:
                os_v3 = match.group(4)

            if self.os_v4_replacement:
                os_v4 = MultiReplace(self.os_v4_replacement, match)
            elif match.lastindex and match.lastindex >= 5:
                os_v4 = match.group(5)

        return os, os_v1, os_v2, os_v3, os_v4


def MultiReplace(string, match):
    def _repl(m):
        index = int(m.group(1)) - 1
        group = match.groups()
        if index < len(group):
            return group[index]
        return ""

    _string = re.sub(r"\$(\d)", _repl, string)
    _string = re.sub(r"^\s+|\s+$", "", _string)
    if _string == "":
        return None
    return _string


class DeviceParser(object):
    def __init__(
        self,
        pattern,
        regex_flag=None,
        device_replacement=None,
        brand_replacement=None,
        model_replacement=None,
    ):
        """Initialize UserAgentParser.

        Args:
          pattern: a regular expression string
          device_replacement: a string to override the matched device (optional)
        """
        self.pattern = pattern
        if regex_flag == "i":
            self.user_agent_re = re.compile(self.pattern, re.IGNORECASE)
        else:
            self.user_agent_re = re.compile(self.pattern)
        self.device_replacement = device_replacement
        self.brand_replacement = brand_replacement
        self.model_replacement = model_replacement

    def MatchSpans(self, user_agent_string):
        match_spans = []
        match = self.user_agent_re.search(user_agent_string)
        if match:
            match_spans = [
                match.span(group_index) for group_index in range(1, match.lastindex + 1)
            ]
        return match_spans

    def Parse(self, user_agent_string):
        device, brand, model = None, None, None
        match = self.user_agent_re.search(user_agent_string)
        if match:
            if self.device_replacement:
                device = MultiReplace(self.device_replacement, match)
            else:
                device = match.group(1)

            if self.brand_replacement:
                brand = MultiReplace(self.brand_replacement, match)

            if self.model_replacement:
                model = MultiReplace(self.model_replacement, match)
            elif len(match.groups()) > 0:
                model = match.group(1)

        return device, brand, model


MAX_CACHE_SIZE = 20
_parse_cache = {}


def Parse(user_agent_string, **jsParseBits):
    """ Parse all the things
    Args:
      user_agent_string: the full user agent string
      jsParseBits: javascript override bits
    Returns:
      A dictionary containing all parsed bits
    """
    jsParseBits = jsParseBits or {}
    key = (user_agent_string, repr(jsParseBits))
    cached = _parse_cache.get(key)
    if cached is not None:
        return cached
    if len(_parse_cache) > MAX_CACHE_SIZE:
        _parse_cache.clear()
    v = {
        "user_agent": ParseUserAgent(user_agent_string, **jsParseBits),
        "os": ParseOS(user_agent_string, **jsParseBits),
        "device": ParseDevice(user_agent_string, **jsParseBits),
        "string": user_agent_string,
    }
    _parse_cache[key] = v
    return v


def ParseUserAgent(user_agent_string, **jsParseBits):
    """ Parses the user-agent string for user agent (browser) info.
    Args:
      user_agent_string: The full user-agent string.
      jsParseBits: javascript override bits.
    Returns:
      A dictionary containing parsed bits.
    """
    if (
        "js_user_agent_family" in jsParseBits
        and jsParseBits["js_user_agent_family"] != ""
    ):
        family = jsParseBits["js_user_agent_family"]
        v1 = jsParseBits.get("js_user_agent_v1") or None
        v2 = jsParseBits.get("js_user_agent_v2") or None
        v3 = jsParseBits.get("js_user_agent_v3") or None
    else:
        for uaParser in USER_AGENT_PARSERS:
            family, v1, v2, v3 = uaParser.Parse(user_agent_string)
            if family:
                break

    # Override for Chrome Frame IFF Chrome is enabled.
    if "js_user_agent_string" in jsParseBits:
        js_user_agent_string = jsParseBits["js_user_agent_string"]
        if (
            js_user_agent_string
            and js_user_agent_string.find("Chrome/") > -1
            and user_agent_string.find("chromeframe") > -1
        ):
            jsOverride = {}
            jsOverride = ParseUserAgent(js_user_agent_string)
            family = "Chrome Frame (%s %s)" % (family, v1)
            v1 = jsOverride["major"]
            v2 = jsOverride["minor"]
            v3 = jsOverride["patch"]

    family = family or "Other"
    return {
        "family": family,
        "major": v1 or None,
        "minor": v2 or None,
        "patch": v3 or None,
    }


def ParseOS(user_agent_string, **jsParseBits):
    """ Parses the user-agent string for operating system info
    Args:
      user_agent_string: The full user-agent string.
      jsParseBits: javascript override bits.
    Returns:
      A dictionary containing parsed bits.
    """
    for osParser in OS_PARSERS:
        os, os_v1, os_v2, os_v3, os_v4 = osParser.Parse(user_agent_string)
        if os:
            break
    os = os or "Other"
    return {
        "family": os,
        "major": os_v1,
        "minor": os_v2,
        "patch": os_v3,
        "patch_minor": os_v4,
    }


def ParseDevice(user_agent_string):
    """ Parses the user-agent string for device info.
    Args:
        user_agent_string: The full user-agent string.
        ua_family: The parsed user agent family name.
    Returns:
        A dictionary containing parsed bits.
    """
    for deviceParser in DEVICE_PARSERS:
        device, brand, model = deviceParser.Parse(user_agent_string)
        if device:
            break

    if device is None:
        device = "Other"

    return {"family": device, "brand": brand, "model": model}


def PrettyUserAgent(family, v1=None, v2=None, v3=None):
    """Pretty user agent string."""
    if v3:
        if v3[0].isdigit():
            return "%s %s.%s.%s" % (family, v1, v2, v3)
        else:
            return "%s %s.%s%s" % (family, v1, v2, v3)
    elif v2:
        return "%s %s.%s" % (family, v1, v2)
    elif v1:
        return "%s %s" % (family, v1)
    return family


def PrettyOS(os, os_v1=None, os_v2=None, os_v3=None, os_v4=None):
    """Pretty os string."""
    if os_v4:
        return "%s %s.%s.%s.%s" % (os, os_v1, os_v2, os_v3, os_v4)
    if os_v3:
        if os_v3[0].isdigit():
            return "%s %s.%s.%s" % (os, os_v1, os_v2, os_v3)
        else:
            return "%s %s.%s%s" % (os, os_v1, os_v2, os_v3)
    elif os_v2:
        return "%s %s.%s" % (os, os_v1, os_v2)
    elif os_v1:
        return "%s %s" % (os, os_v1)
    return os


def ParseWithJSOverrides(
    user_agent_string,
    js_user_agent_string=None,
    js_user_agent_family=None,
    js_user_agent_v1=None,
    js_user_agent_v2=None,
    js_user_agent_v3=None,
):
    """ backwards compatible. use one of the other Parse methods instead! """

    # Override via JS properties.
    if js_user_agent_family is not None and js_user_agent_family != "":
        family = js_user_agent_family
        v1 = None
        v2 = None
        v3 = None
        if js_user_agent_v1 is not None:
            v1 = js_user_agent_v1
        if js_user_agent_v2 is not None:
            v2 = js_user_agent_v2
        if js_user_agent_v3 is not None:
            v3 = js_user_agent_v3
    else:
        for parser in USER_AGENT_PARSERS:
            family, v1, v2, v3 = parser.Parse(user_agent_string)
            if family:
                break

    # Override for Chrome Frame IFF Chrome is enabled.
    if (
        js_user_agent_string
        and js_user_agent_string.find("Chrome/") > -1
        and user_agent_string.find("chromeframe") > -1
    ):
        family = "Chrome Frame (%s %s)" % (family, v1)
        ua_dict = ParseUserAgent(js_user_agent_string)
        v1 = ua_dict["major"]
        v2 = ua_dict["minor"]
        v3 = ua_dict["patch"]

    return family or "Other", v1, v2, v3


def Pretty(family, v1=None, v2=None, v3=None):
    """ backwards compatible. use PrettyUserAgent instead! """
    if v3:
        if v3[0].isdigit():
            return "%s %s.%s.%s" % (family, v1, v2, v3)
        else:
            return "%s %s.%s%s" % (family, v1, v2, v3)
    elif v2:
        return "%s %s.%s" % (family, v1, v2)
    elif v1:
        return "%s %s" % (family, v1)
    return family


def GetFilters(
    user_agent_string,
    js_user_agent_string=None,
    js_user_agent_family=None,
    js_user_agent_v1=None,
    js_user_agent_v2=None,
    js_user_agent_v3=None,
):
    """Return the optional arguments that should be saved and used to query.

    js_user_agent_string is always returned if it is present. We really only need
    it for Chrome Frame. However, I added it in the generally case to find other
    cases when it is different. When the recording of js_user_agent_string was
    added, we created new records for all new user agents.

    Since we only added js_document_mode for the IE 9 preview case, it did not
    cause new user agent records the way js_user_agent_string did.

    js_document_mode has since been removed in favor of individual property
    overrides.

    Args:
      user_agent_string: The full user-agent string.
      js_user_agent_string: JavaScript ua string from client-side
      js_user_agent_family: This is an override for the family name to deal
          with the fact that IE platform preview (for instance) cannot be
          distinguished by user_agent_string, but only in javascript.
      js_user_agent_v1: v1 override - see above.
      js_user_agent_v2: v1 override - see above.
      js_user_agent_v3: v1 override - see above.
    Returns:
      {js_user_agent_string: '[...]', js_family_name: '[...]', etc...}
    """
    filters = {}
    filterdict = {
        "js_user_agent_string": js_user_agent_string,
        "js_user_agent_family": js_user_agent_family,
        "js_user_agent_v1": js_user_agent_v1,
        "js_user_agent_v2": js_user_agent_v2,
        "js_user_agent_v3": js_user_agent_v3,
    }
    for key, value in filterdict.items():
        if value is not None and value != "":
            filters[key] = value
    return filters


USER_AGENT_PARSERS = [
    UserAgentParser(
        '^(Indica.+?)/(\\d+)\\.(\\d+)\\.(\\d+)\s\((.+?)\)\s(.+?)/(.+?)$',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(GeoEvent Server) (\\d+)(?:\\.(\\d+)(?:\\.(\\d+)|)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(ArcGIS Pro)(?: (\\d+)\\.(\\d+)\\.([^ ]+)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        'ArcGIS Client Using WinInet',
        'ArcMap',
        None,
        None,
    ),
    UserAgentParser(
        '(OperationsDashboard)-(?:Windows)-(\\d+)\\.(\\d+)\\.(\\d+)',
        'Operations Dashboard for ArcGIS',
        None,
        None,
    ),
    UserAgentParser(
        '(arcgisearth)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        'ArcGIS Earth',
        None,
        None,
    ),
    UserAgentParser(
        'com.esri.(earth).phone/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        'ArcGIS Earth',
        None,
        None,
    ),
    UserAgentParser(
        '(arcgis-explorer)/(\\d+)\\.(\\d+)\\.(\\d+)',
        'Explorer for ArcGIS',
        None,
        None,
    ),
    UserAgentParser(
        'arcgis-(collector|aurora)/(\\d+)\\.(\\d+)\\.(\\d+)',
        'Collector for ArcGIS',
        None,
        None,
    ),
    UserAgentParser(
        '(arcgis-workforce)/(\\d+)\\.(\\d+)\\.(\\d+)',
        'Workforce for ArcGIS',
        None,
        None,
    ),
    UserAgentParser(
        '(Collector|Explorer|Workforce)-(?:Android|iOS)-(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        '$1 for ArcGIS',
        None,
        None,
    ),
    UserAgentParser(
        '(Explorer|Collector)/(\\d+) CFNetwork',
        '$1 for ArcGIS',
        None,
        None,
    ),
    UserAgentParser(
        'ArcGISRuntime-(Android|iOS|NET|Qt)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        'ArcGIS Runtime SDK for $1',
        None,
        None,
    ),
    UserAgentParser(
        'ArcGIS\\.?(iOS|Android|NET|Qt)(?:-|\\.)(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        'ArcGIS Runtime SDK for $1',
        None,
        None,
    ),
    UserAgentParser(
        'ArcGIS\\.Runtime\\.(Qt)\\.(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        'ArcGIS Runtime SDK for $1',
        None,
        None,
    ),
    UserAgentParser(
        '^(Luminary)[Stage]+/(\\d+) CFNetwork',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(ESPN)[%20| ]+Radio/(\\d+)\\.(\\d+)\\.(\\d+) CFNetwork',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Antenna)/(\\d+) CFNetwork',
        'AntennaPod',
        None,
        None,
    ),
    UserAgentParser(
        '(TopPodcasts)Pro/(\\d+) CFNetwork',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(MusicDownloader)Lite/(\\d+)\\.(\\d+)\\.(\\d+) CFNetwork',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '^(.*)-iPad\\/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)(?:\\.(\\d+)|) CFNetwork',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '^(.*)-iPhone/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)(?:\\.(\\d+)|) CFNetwork',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '^(.*)/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)(?:\\.(\\d+)|) CFNetwork',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '^(Luminary)/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(espn\\.go)',
        'ESPN',
        None,
        None,
    ),
    UserAgentParser(
        '(espnradio\\.com)',
        'ESPN',
        None,
        None,
    ),
    UserAgentParser(
        'ESPN APP$',
        'ESPN',
        None,
        None,
    ),
    UserAgentParser(
        '(audioboom\\.com)',
        'AudioBoom',
        None,
        None,
    ),
    UserAgentParser(
        ' (Rivo) RHYTHM',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(CFNetwork)(?:/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)|)',
        'CFNetwork',
        None,
        None,
    ),
    UserAgentParser(
        '(Pingdom\\.com_bot_version_)(\\d+)\\.(\\d+)',
        'PingdomBot',
        None,
        None,
    ),
    UserAgentParser(
        '(PingdomTMS)/(\\d+)\\.(\\d+)\\.(\\d+)',
        'PingdomBot',
        None,
        None,
    ),
    UserAgentParser(
        ' (PTST)/(\\d+)(?:\\.(\\d+)|)$',
        'WebPageTest.org bot',
        None,
        None,
    ),
    UserAgentParser(
        'X11; (Datanyze); Linux',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(NewRelicPinger)/(\\d+)\\.(\\d+)',
        'NewRelicPingerBot',
        None,
        None,
    ),
    UserAgentParser(
        '(Tableau)/(\\d+)\\.(\\d+)',
        'Tableau',
        None,
        None,
    ),
    UserAgentParser(
        'AppleWebKit/\\d+\\.\\d+.* Safari.* (CreativeCloud)/(\\d+)\\.(\\d+).(\\d+)',
        'Adobe CreativeCloud',
        None,
        None,
    ),
    UserAgentParser(
        '(Salesforce)(?:.)\\/(\\d+)\\.(\\d?)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(\\(StatusCake\\))',
        'StatusCakeBot',
        None,
        None,
    ),
    UserAgentParser(
        '(facebookexternalhit)/(\\d+)\\.(\\d+)',
        'FacebookBot',
        None,
        None,
    ),
    UserAgentParser(
        'Google.*/\\+/web/snippet',
        'GooglePlusBot',
        None,
        None,
    ),
    UserAgentParser(
        'via ggpht\\.com GoogleImageProxy',
        'GmailImageProxy',
        None,
        None,
    ),
    UserAgentParser(
        'YahooMailProxy; https://help\\.yahoo\\.com/kb/yahoo-mail-proxy-SLN28749\\.html',
        'YahooMailProxy',
        None,
        None,
    ),
    UserAgentParser(
        '(Twitterbot)/(\\d+)\\.(\\d+)',
        'Twitterbot',
        None,
        None,
    ),
    UserAgentParser(
        '/((?:Ant-|)Nutch|[A-z]+[Bb]ot|[A-z]+[Ss]pider|Axtaris|fetchurl|Isara|ShopSalad|Tailsweep)[ \\-](\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '\\b(008|Altresium|Argus|BaiduMobaider|BoardReader|DNSGroup|DataparkSearch|EDI|Goodzer|Grub|INGRID|Infohelfer|LinkedInBot|LOOQ|Nutch|OgScrper|PathDefender|Peew|PostPost|Steeler|Twitterbot|VSE|WebCrunch|WebZIP|Y!J-BR[A-Z]|YahooSeeker|envolk|sproose|wminer)/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(MSIE) (\\d+)\\.(\\d+)([a-z]\\d|[a-z]|);.* MSIECrawler',
        'MSIECrawler',
        None,
        None,
    ),
    UserAgentParser(
        '(DAVdroid)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Google-HTTP-Java-Client|Apache-HttpClient|Go-http-client|scalaj-http|http%20client|Python-urllib|HttpMonitor|TLSProber|WinHTTP|JNLP|okhttp|aihttp|reqwest|axios|unirest-(?:java|python|ruby|nodejs|php|net))(?:[ /](\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Pinterest(?:bot|))/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)[;\\s(]+\\+https://www.pinterest.com/bot.html',
        'Pinterestbot',
        None,
        None,
    ),
    UserAgentParser(
        '(CSimpleSpider|Cityreview Robot|CrawlDaddy|CrawlFire|Finderbots|Index crawler|Job Roboter|KiwiStatus Spider|Lijit Crawler|QuerySeekerSpider|ScollSpider|Trends Crawler|USyd-NLP-Spider|SiteCat Webbot|BotName\\/\\$BotVersion|123metaspider-Bot|1470\\.net crawler|50\\.nu|8bo Crawler Bot|Aboundex|Accoona-[A-z]{1,30}-Agent|AdsBot-Google(?:-[a-z]{1,30}|)|altavista|AppEngine-Google|archive.{0,30}\\.org_bot|archiver|Ask Jeeves|[Bb]ai[Dd]u[Ss]pider(?:-[A-Za-z]{1,30})(?:-[A-Za-z]{1,30}|)|bingbot|BingPreview|blitzbot|BlogBridge|Bloglovin|BoardReader Blog Indexer|BoardReader Favicon Fetcher|boitho.com-dc|BotSeer|BUbiNG|\\b\\w{0,30}favicon\\w{0,30}\\b|\\bYeti(?:-[a-z]{1,30}|)|Catchpoint(?: bot|)|[Cc]harlotte|Checklinks|clumboot|Comodo HTTP\\(S\\) Crawler|Comodo-Webinspector-Crawler|ConveraCrawler|CRAWL-E|CrawlConvera|Daumoa(?:-feedfetcher|)|Feed Seeker Bot|Feedbin|findlinks|Flamingo_SearchEngine|FollowSite Bot|furlbot|Genieo|gigabot|GomezAgent|gonzo1|(?:[a-zA-Z]{1,30}-|)Googlebot(?:-[a-zA-Z]{1,30}|)|Google SketchUp|grub-client|gsa-crawler|heritrix|HiddenMarket|holmes|HooWWWer|htdig|ia_archiver|ICC-Crawler|Icarus6j|ichiro(?:/mobile|)|IconSurf|IlTrovatore(?:-Setaccio|)|InfuzApp|Innovazion Crawler|InternetArchive|IP2[a-z]{1,30}Bot|jbot\\b|KaloogaBot|Kraken|Kurzor|larbin|LEIA|LesnikBot|Linguee Bot|LinkAider|LinkedInBot|Lite Bot|Llaut|lycos|Mail\\.RU_Bot|masscan|masidani_bot|Mediapartners-Google|Microsoft .{0,30} Bot|mogimogi|mozDex|MJ12bot|msnbot(?:-media {0,2}|)|msrbot|Mtps Feed Aggregation System|netresearch|Netvibes|NewsGator[^/]{0,30}|^NING|Nutch[^/]{0,30}|Nymesis|ObjectsSearch|OgScrper|Orbiter|OOZBOT|PagePeeker|PagesInventory|PaxleFramework|Peeplo Screenshot Bot|PlantyNet_WebRobot|Pompos|Qwantify|Read%20Later|Reaper|RedCarpet|Retreiver|Riddler|Rival IQ|scooter|Scrapy|Scrubby|searchsight|seekbot|semanticdiscovery|SemrushBot|Simpy|SimplePie|SEOstats|SimpleRSS|SiteCon|Slackbot-LinkExpanding|Slack-ImgProxy|Slurp|snappy|Speedy Spider|Squrl Java|Stringer|TheUsefulbot|ThumbShotsBot|Thumbshots\\.ru|Tiny Tiny RSS|Twitterbot|WhatsApp|URL2PNG|Vagabondo|VoilaBot|^vortex|Votay bot|^voyager|WASALive.Bot|Web-sniffer|WebThumb|WeSEE:[A-z]{1,30}|WhatWeb|WIRE|WordPress|Wotbox|www\\.almaden\\.ibm\\.com|Xenu(?:.s|) Link Sleuth|Xerka [A-z]{1,30}Bot|yacy(?:bot|)|YahooSeeker|Yahoo! Slurp|Yandex\\w{1,30}|YodaoBot(?:-[A-z]{1,30}|)|YottaaMonitor|Yowedo|^Zao|^Zao-Crawler|ZeBot_www\\.ze\\.bz|ZooShot|ZyBorg|ArcGIS Hub Indexer)(?:[ /]v?(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)|)|)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '\\b(Boto3?|JetS3t|aws-(?:cli|sdk-(?:cpp|go|java|nodejs|ruby2?|dotnet-(?:\\d{1,2}|core)))|s3fs)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(FME)\\/(\\d+\\.\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(QGIS)\\/(\\d)\\.?0?(\\d{1,2})\\.?0?(\\d{1,2})',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(JOSM)/(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Tygron Platform) \\((\\d+)\\.(\\d+)\\.(\\d+(?:\\.\\d+| RC \\d+\\.\\d+))',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '\\[(FBAN/MessengerForiOS|FB_IAB/MESSENGER);FBAV/(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)|)|)',
        'Facebook Messenger',
        None,
        None,
    ),
    UserAgentParser(
        '\\[FB.*;(FBAV)/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)',
        'Facebook',
        None,
        None,
    ),
    UserAgentParser(
        '\\[FB.*;',
        'Facebook',
        None,
        None,
    ),
    UserAgentParser(
        '(?:\\/[A-Za-z0-9\\.]+|) {0,5}([A-Za-z0-9 \\-_\\!\\[\\]:]{0,50}(?:[Aa]rchiver|[Ii]ndexer|[Ss]craper|[Bb]ot|[Ss]pider|[Cc]rawl[a-z]{0,50}))[/ ](\\d+)(?:\\.(\\d+)(?:\\.(\\d+)|)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '((?:[A-Za-z][A-Za-z0-9 -]{0,50}|)[^C][^Uu][Bb]ot)\\b(?:(?:[ /]| v)(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '((?:[A-z0-9]{1,50}|[A-z\\-]{1,50} ?|)(?: the |)(?:[Ss][Pp][Ii][Dd][Ee][Rr]|[Ss]crape|[Cc][Rr][Aa][Ww][Ll])[A-z0-9]{0,50})(?:(?:[ /]| v)(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(HbbTV)/(\\d+)\\.(\\d+)\\.(\\d+) \\(',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Chimera|SeaMonkey|Camino|Waterfox)/(\\d+)\\.(\\d+)\\.?([ab]?\\d+[a-z]*|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(SailfishBrowser)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        'Sailfish Browser',
        None,
        None,
    ),
    UserAgentParser(
        '\\[(Pinterest)/[^\\]]+\\]',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Pinterest)(?: for Android(?: Tablet|)|)/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        'Mozilla.*Mobile.*(Instagram).(\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        'Mozilla.*Mobile.*(Flipboard).(\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        'Mozilla.*Mobile.*(Flipboard-Briefing).(\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        'Mozilla.*Mobile.*(Onefootball)\\/Android.(\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Snapchat)\\/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Twitter for (?:iPhone|iPad)|TwitterAndroid)(?:\\/(\\d+)\\.(\\d+)|)',
        'Twitter',
        None,
        None,
    ),
    UserAgentParser(
        'Mozilla.*Mobile.*AspiegelBot',
        'Spider',
        None,
        None,
    ),
    UserAgentParser(
        'AspiegelBot',
        'Spider',
        None,
        None,
    ),
    UserAgentParser(
        '(Firefox)/(\\d+)\\.(\\d+) Basilisk/(\\d+)',
        'Basilisk',
        None,
        None,
    ),
    UserAgentParser(
        '(PaleMoon)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        'Pale Moon',
        None,
        None,
    ),
    UserAgentParser(
        '(Fennec)/(\\d+)\\.(\\d+)\\.?([ab]?\\d+[a-z]*)',
        'Firefox Mobile',
        None,
        None,
    ),
    UserAgentParser(
        '(Fennec)/(\\d+)\\.(\\d+)(pre)',
        'Firefox Mobile',
        None,
        None,
    ),
    UserAgentParser(
        '(Fennec)/(\\d+)\\.(\\d+)',
        'Firefox Mobile',
        None,
        None,
    ),
    UserAgentParser(
        '(?:Mobile|Tablet);.*(Firefox)/(\\d+)\\.(\\d+)',
        'Firefox Mobile',
        None,
        None,
    ),
    UserAgentParser(
        '(Namoroka|Shiretoko|Minefield)/(\\d+)\\.(\\d+)\\.(\\d+(?:pre|))',
        'Firefox ($1)',
        None,
        None,
    ),
    UserAgentParser(
        '(Firefox)/(\\d+)\\.(\\d+)(a\\d+[a-z]*)',
        'Firefox Alpha',
        None,
        None,
    ),
    UserAgentParser(
        '(Firefox)/(\\d+)\\.(\\d+)(b\\d+[a-z]*)',
        'Firefox Beta',
        None,
        None,
    ),
    UserAgentParser(
        '(Firefox)-(?:\\d+\\.\\d+|)/(\\d+)\\.(\\d+)(a\\d+[a-z]*)',
        'Firefox Alpha',
        None,
        None,
    ),
    UserAgentParser(
        '(Firefox)-(?:\\d+\\.\\d+|)/(\\d+)\\.(\\d+)(b\\d+[a-z]*)',
        'Firefox Beta',
        None,
        None,
    ),
    UserAgentParser(
        '(Namoroka|Shiretoko|Minefield)/(\\d+)\\.(\\d+)([ab]\\d+[a-z]*|)',
        'Firefox ($1)',
        None,
        None,
    ),
    UserAgentParser(
        '(Firefox).*Tablet browser (\\d+)\\.(\\d+)\\.(\\d+)',
        'MicroB',
        None,
        None,
    ),
    UserAgentParser(
        '(MozillaDeveloperPreview)/(\\d+)\\.(\\d+)([ab]\\d+[a-z]*|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(FxiOS)/(\\d+)\\.(\\d+)(\\.(\\d+)|)(\\.(\\d+)|)',
        'Firefox iOS',
        None,
        None,
    ),
    UserAgentParser(
        '(Flock)/(\\d+)\\.(\\d+)(b\\d+?)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(RockMelt)/(\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Navigator)/(\\d+)\\.(\\d+)\\.(\\d+)',
        'Netscape',
        None,
        None,
    ),
    UserAgentParser(
        '(Navigator)/(\\d+)\\.(\\d+)([ab]\\d+)',
        'Netscape',
        None,
        None,
    ),
    UserAgentParser(
        '(Netscape6)/(\\d+)\\.(\\d+)\\.?([ab]?\\d+|)',
        'Netscape',
        None,
        None,
    ),
    UserAgentParser(
        '(MyIBrow)/(\\d+)\\.(\\d+)',
        'My Internet Browser',
        None,
        None,
    ),
    UserAgentParser(
        '(UC? ?Browser|UCWEB|U3)[ /]?(\\d+)\\.(\\d+)\\.(\\d+)',
        'UC Browser',
        None,
        None,
    ),
    UserAgentParser(
        '(Opera Tablet).*Version/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Opera Mini)(?:/att|)/?(\\d+|)(?:\\.(\\d+)|)(?:\\.(\\d+)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Opera)/.+Opera Mobi.+Version/(\\d+)\\.(\\d+)',
        'Opera Mobile',
        None,
        None,
    ),
    UserAgentParser(
        '(Opera)/(\\d+)\\.(\\d+).+Opera Mobi',
        'Opera Mobile',
        None,
        None,
    ),
    UserAgentParser(
        'Opera Mobi.+(Opera)(?:/|\\s+)(\\d+)\\.(\\d+)',
        'Opera Mobile',
        None,
        None,
    ),
    UserAgentParser(
        'Opera Mobi',
        'Opera Mobile',
        None,
        None,
    ),
    UserAgentParser(
        '(Opera)/9.80.*Version/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(?:Mobile Safari).*(OPR)/(\\d+)\\.(\\d+)\\.(\\d+)',
        'Opera Mobile',
        None,
        None,
    ),
    UserAgentParser(
        '(?:Chrome).*(OPR)/(\\d+)\\.(\\d+)\\.(\\d+)',
        'Opera',
        None,
        None,
    ),
    UserAgentParser(
        '(Coast)/(\\d+).(\\d+).(\\d+)',
        'Opera Coast',
        None,
        None,
    ),
    UserAgentParser(
        '(OPiOS)/(\\d+).(\\d+).(\\d+)',
        'Opera Mini',
        None,
        None,
    ),
    UserAgentParser(
        'Chrome/.+( MMS)/(\\d+).(\\d+).(\\d+)',
        'Opera Neon',
        None,
        None,
    ),
    UserAgentParser(
        '(hpw|web)OS/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        'webOS Browser',
        None,
        None,
    ),
    UserAgentParser(
        '(luakit)',
        'LuaKit',
        None,
        None,
    ),
    UserAgentParser(
        '(Snowshoe)/(\\d+)\\.(\\d+).(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        'Gecko/\\d+ (Lightning)/(\\d+)\\.(\\d+)\\.?((?:[ab]?\\d+[a-z]*)|(?:\\d*))',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Firefox)/(\\d+)\\.(\\d+)\\.(\\d+(?:pre|)) \\(Swiftfox\\)',
        'Swiftfox',
        None,
        None,
    ),
    UserAgentParser(
        '(Firefox)/(\\d+)\\.(\\d+)([ab]\\d+[a-z]*|) \\(Swiftfox\\)',
        'Swiftfox',
        None,
        None,
    ),
    UserAgentParser(
        '(rekonq)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|) Safari',
        'Rekonq',
        None,
        None,
    ),
    UserAgentParser(
        'rekonq',
        'Rekonq',
        None,
        None,
    ),
    UserAgentParser(
        '(conkeror|Conkeror)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        'Conkeror',
        None,
        None,
    ),
    UserAgentParser(
        '(konqueror)/(\\d+)\\.(\\d+)\\.(\\d+)',
        'Konqueror',
        None,
        None,
    ),
    UserAgentParser(
        '(WeTab)-Browser',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Comodo_Dragon)/(\\d+)\\.(\\d+)\\.(\\d+)',
        'Comodo Dragon',
        None,
        None,
    ),
    UserAgentParser(
        '(Symphony) (\\d+).(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        'PLAYSTATION 3.+WebKit',
        'NetFront NX',
        None,
        None,
    ),
    UserAgentParser(
        'PLAYSTATION 3',
        'NetFront',
        None,
        None,
    ),
    UserAgentParser(
        '(PlayStation Portable)',
        'NetFront',
        None,
        None,
    ),
    UserAgentParser(
        '(PlayStation Vita)',
        'NetFront NX',
        None,
        None,
    ),
    UserAgentParser(
        'AppleWebKit.+ (NX)/(\\d+)\\.(\\d+)\\.(\\d+)',
        'NetFront NX',
        None,
        None,
    ),
    UserAgentParser(
        '(Nintendo 3DS)',
        'NetFront NX',
        None,
        None,
    ),
    UserAgentParser(
        '(Silk)/(\\d+)\\.(\\d+)(?:\\.([0-9\\-]+)|)',
        'Amazon Silk',
        None,
        None,
    ),
    UserAgentParser(
        '(Puffin)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        'Windows Phone .*(Edge)/(\\d+)\\.(\\d+)',
        'Edge Mobile',
        None,
        None,
    ),
    UserAgentParser(
        '(EdgiOS|EdgA)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)',
        'Edge Mobile',
        None,
        None,
    ),
    UserAgentParser(
        '(SamsungBrowser)/(\\d+)\\.(\\d+)',
        'Samsung Internet',
        None,
        None,
    ),
    UserAgentParser(
        '(SznProhlizec)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        u'Seznam prohl\xed\u017ee\u010d',
        None,
        None,
    ),
    UserAgentParser(
        '(coc_coc_browser)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        'Coc Coc',
        None,
        None,
    ),
    UserAgentParser(
        '(baidubrowser)[/\\s](\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)',
        'Baidu Browser',
        None,
        None,
    ),
    UserAgentParser(
        '(FlyFlow)/(\\d+)\\.(\\d+)',
        'Baidu Explorer',
        None,
        None,
    ),
    UserAgentParser(
        '(MxBrowser)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        'Maxthon',
        None,
        None,
    ),
    UserAgentParser(
        '(Crosswalk)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Line)/(\\d+)\\.(\\d+)\\.(\\d+)',
        'LINE',
        None,
        None,
    ),
    UserAgentParser(
        '(MiuiBrowser)/(\\d+)\\.(\\d+)\\.(\\d+)',
        'MiuiBrowser',
        None,
        None,
    ),
    UserAgentParser(
        '(Mint Browser)/(\\d+)\\.(\\d+)\\.(\\d+)',
        'Mint Browser',
        None,
        None,
    ),
    UserAgentParser(
        '(TopBuzz)/(\\d+).(\\d+).(\\d+)',
        'TopBuzz',
        None,
        None,
    ),
    UserAgentParser(
        'Mozilla.+Android.+(GSA)/(\\d+)\\.(\\d+)\\.(\\d+)',
        'Google',
        None,
        None,
    ),
    UserAgentParser(
        '(MQQBrowser/Mini)(?:(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)|)',
        'QQ Browser Mini',
        None,
        None,
    ),
    UserAgentParser(
        '(MQQBrowser)(?:/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)|)',
        'QQ Browser Mobile',
        None,
        None,
    ),
    UserAgentParser(
        '(QQBrowser)(?:/(\\d+)(?:\\.(\\d+)\\.(\\d+)(?:\\.(\\d+)|)|)|)',
        'QQ Browser',
        None,
        None,
    ),
    UserAgentParser(
        'Version/.+(Chrome)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)',
        'Chrome Mobile WebView',
        None,
        None,
    ),
    UserAgentParser(
        '; wv\\).+(Chrome)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)',
        'Chrome Mobile WebView',
        None,
        None,
    ),
    UserAgentParser(
        '(CrMo)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)',
        'Chrome Mobile',
        None,
        None,
    ),
    UserAgentParser(
        '(CriOS)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)',
        'Chrome Mobile iOS',
        None,
        None,
    ),
    UserAgentParser(
        '(Chrome)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+) Mobile(?:[ /]|$)',
        'Chrome Mobile',
        None,
        None,
    ),
    UserAgentParser(
        ' Mobile .*(Chrome)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)',
        'Chrome Mobile',
        None,
        None,
    ),
    UserAgentParser(
        '(chromeframe)/(\\d+)\\.(\\d+)\\.(\\d+)',
        'Chrome Frame',
        None,
        None,
    ),
    UserAgentParser(
        '(SLP Browser)/(\\d+)\\.(\\d+)',
        'Tizen Browser',
        None,
        None,
    ),
    UserAgentParser(
        '(SE 2\\.X) MetaSr (\\d+)\\.(\\d+)',
        'Sogou Explorer',
        None,
        None,
    ),
    UserAgentParser(
        '(Rackspace Monitoring)/(\\d+)\\.(\\d+)',
        'RackspaceBot',
        None,
        None,
    ),
    UserAgentParser(
        '(PRTG Network Monitor)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(PyAMF)/(\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(YaBrowser)/(\\d+)\\.(\\d+)\\.(\\d+)',
        'Yandex Browser',
        None,
        None,
    ),
    UserAgentParser(
        '(Chrome)/(\\d+)\\.(\\d+)\\.(\\d+).* MRCHROME',
        'Mail.ru Chromium Browser',
        None,
        None,
    ),
    UserAgentParser(
        '(AOL) (\\d+)\\.(\\d+); AOLBuild (\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(PodCruncher|Downcast)[ /]?(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)(?:\\.(\\d+)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        ' (BoxNotes)/(\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Whale)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+) Mobile(?:[ /]|$)',
        'Whale',
        None,
        None,
    ),
    UserAgentParser(
        '(Whale)/(\\d+)\\.(\\d+)\\.(\\d+)',
        'Whale',
        None,
        None,
    ),
    UserAgentParser(
        '(1Password)/(\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Ghost)/(\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Slack_SSB)/(\\d+)\\.(\\d+)\\.(\\d+)',
        'Slack Desktop Client',
        None,
        None,
    ),
    UserAgentParser(
        '(HipChat)/?(\\d+|)',
        'HipChat Desktop Client',
        None,
        None,
    ),
    UserAgentParser(
        '\\b(MobileIron|FireWeb|Jasmine|ANTGalio|Midori|Fresco|Lobo|PaleMoon|Maxthon|Lynx|OmniWeb|Dillo|Camino|Demeter|Fluid|Fennec|Epiphany|Shiira|Sunrise|Spotify|Flock|Netscape|Lunascape|WebPilot|NetFront|Netfront|Konqueror|SeaMonkey|Kazehakase|Vienna|Iceape|Iceweasel|IceWeasel|Iron|K-Meleon|Sleipnir|Galeon|GranParadiso|Opera Mini|iCab|NetNewsWire|ThunderBrowse|Iris|UP\\.Browser|Bunjalloo|Google Earth|Raven for Mac|Openwave|MacOutlook|Electron|OktaMobile)/(\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        'Microsoft Office Outlook 12\\.\\d+\\.\\d+|MSOffice 12',
        'Outlook',
        '2007',
        None,
    ),
    UserAgentParser(
        'Microsoft Outlook 14\\.\\d+\\.\\d+|MSOffice 14',
        'Outlook',
        '2010',
        None,
    ),
    UserAgentParser(
        'Microsoft Outlook 15\\.\\d+\\.\\d+',
        'Outlook',
        '2013',
        None,
    ),
    UserAgentParser(
        'Microsoft Outlook (?:Mail )?16\\.\\d+\\.\\d+|MSOffice 16',
        'Outlook',
        '2016',
        None,
    ),
    UserAgentParser(
        'Microsoft Office (Word) 2014',
        None,
        None,
        None,
    ),
    UserAgentParser(
        'Outlook-Express\\/7\\.0.*',
        'Windows Live Mail',
        None,
        None,
    ),
    UserAgentParser(
        '(Airmail) (\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Thunderbird)/(\\d+)\\.(\\d+)(?:\\.(\\d+(?:pre|))|)',
        'Thunderbird',
        None,
        None,
    ),
    UserAgentParser(
        '(Postbox)/(\\d+)\\.(\\d+)\\.(\\d+)',
        'Postbox',
        None,
        None,
    ),
    UserAgentParser(
        '(Barca(?:Pro)?)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        'Barca',
        None,
        None,
    ),
    UserAgentParser(
        '(Lotus-Notes)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        'Lotus Notes',
        None,
        None,
    ),
    UserAgentParser(
        'Superhuman',
        'Superhuman',
        None,
        None,
    ),
    UserAgentParser(
        '(Vivaldi)/(\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Edge?)/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)(?:\\.(\\d+)|)',
        'Edge',
        None,
        None,
    ),
    UserAgentParser(
        '(brave)/(\\d+)\\.(\\d+)\\.(\\d+) Chrome',
        'Brave',
        None,
        None,
    ),
    UserAgentParser(
        '(Chrome)/(\\d+)\\.(\\d+)\\.(\\d+)[\\d.]* Iron[^/]',
        'Iron',
        None,
        None,
    ),
    UserAgentParser(
        '\\b(Dolphin)(?: |HDCN/|/INT\\-)(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(HeadlessChrome)(?:/(\\d+)\\.(\\d+)\\.(\\d+)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Evolution)/(\\d+)\\.(\\d+)\\.(\\d+\\.\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(RCM CardDAV plugin)/(\\d+)\\.(\\d+)\\.(\\d+(?:-dev|))',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(bingbot|Bolt|AdobeAIR|Jasmine|IceCat|Skyfire|Midori|Maxthon|Lynx|Arora|IBrowse|Dillo|Camino|Shiira|Fennec|Phoenix|Flock|Netscape|Lunascape|Epiphany|WebPilot|Opera Mini|Opera|NetFront|Netfront|Konqueror|Googlebot|SeaMonkey|Kazehakase|Vienna|Iceape|Iceweasel|IceWeasel|Iron|K-Meleon|Sleipnir|Galeon|GranParadiso|iCab|iTunes|MacAppStore|NetNewsWire|Space Bison|Stainless|Orca|Dolfin|BOLT|Minimo|Tizen Browser|Polaris|Abrowser|Planetweb|ICE Browser|mDolphin|qutebrowser|Otter|QupZilla|MailBar|kmail2|YahooMobileMail|ExchangeWebServices|ExchangeServicesClient|Dragon|Outlook-iOS-Android)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Chromium|Chrome)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(IEMobile)[ /](\\d+)\\.(\\d+)',
        'IE Mobile',
        None,
        None,
    ),
    UserAgentParser(
        '(BacaBerita App)\\/(\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '^(bPod|Pocket Casts|Player FM)$',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '^(AlexaMediaPlayer|VLC)/(\\d+)\\.(\\d+)\\.([^.\\s]+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '^(AntennaPod|WMPlayer|Zune|Podkicker|Radio|ExoPlayerDemo|Overcast|PocketTunes|NSPlayer|okhttp|DoggCatcher|QuickNews|QuickTime|Peapod|Podcasts|GoldenPod|VLC|Spotify|Miro|MediaGo|Juice|iPodder|gPodder|Banshee)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '^(Peapod|Liferea)/([^.\\s]+)\\.([^.\\s]+|)\\.?([^.\\s]+|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '^(bPod|Player FM) BMID/(\\S+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '^(Podcast ?Addict)/v(\\d+) ',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '^(Podcast ?Addict) ',
        'PodcastAddict',
        None,
        None,
    ),
    UserAgentParser(
        '(Replay) AV',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(VOX) Music Player',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(CITA) RSS Aggregator/(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Pocket Casts)$',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Player FM)$',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(LG Player|Doppler|FancyMusic|MediaMonkey|Clementine) (\\d+)\\.(\\d+)\\.?([^.\\s]+|)\\.?([^.\\s]+|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(philpodder)/(\\d+)\\.(\\d+)\\.?([^.\\s]+|)\\.?([^.\\s]+|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Player FM|Pocket Casts|DoggCatcher|Spotify|MediaMonkey|MediaGo|BashPodder)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(QuickTime)\\.(\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Kinoma)(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Fancy) Cloud Music (\\d+)\\.(\\d+)',
        'FancyMusic',
        None,
        None,
    ),
    UserAgentParser(
        'EspnDownloadManager',
        'ESPN',
        None,
        None,
    ),
    UserAgentParser(
        '(ESPN) Radio (\\d+)\\.(\\d+)(?:\\.(\\d+)|) ?(?:rv:(\\d+)|) ',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(podracer|jPodder) v ?(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(ZDM)/(\\d+)\\.(\\d+)[; ]?',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Zune|BeyondPod) (\\d+)(?:\\.(\\d+)|)[\\);]',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(WMPlayer)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '^(Lavf)',
        'WMPlayer',
        None,
        None,
    ),
    UserAgentParser(
        '^(RSSRadio)[ /]?(\\d+|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(RSS_Radio) (\\d+)\\.(\\d+)',
        'RSSRadio',
        None,
        None,
    ),
    UserAgentParser(
        '(Podkicker) \\S+/(\\d+)\\.(\\d+)\\.(\\d+)',
        'Podkicker',
        None,
        None,
    ),
    UserAgentParser(
        '^(HTC) Streaming Player \\S+ / \\S+ / \\S+ / (\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '^(Stitcher)/iOS',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '^(Stitcher)/Android',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '^(VLC) .*version (\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        ' (VLC) for',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(vlc)/(\\d+)\\.(\\d+)\\.(\\d+)',
        'VLC',
        None,
        None,
    ),
    UserAgentParser(
        '^(foobar)\\S+/([^.\\s]+)\\.([^.\\s]+|)\\.?([^.\\s]+|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '^(Clementine)\\S+ ([^.\\s]+)\\.([^.\\s]+|)\\.?([^.\\s]+|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(amarok)/([^.\\s]+)\\.([^.\\s]+|)\\.?([^.\\s]+|)',
        'Amarok',
        None,
        None,
    ),
    UserAgentParser(
        '(Custom)-Feed Reader',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(iRider|Crazy Browser|SkipStone|iCab|Lunascape|Sleipnir|Maemo Browser) (\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(iCab|Lunascape|Opera|Android|Jasmine|Polaris|Microsoft SkyDriveSync|The Bat!) (\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Kindle)/(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Android) Donut',
        None,
        '1',
        '2',
    ),
    UserAgentParser(
        '(Android) Eclair',
        None,
        '2',
        '1',
    ),
    UserAgentParser(
        '(Android) Froyo',
        None,
        '2',
        '2',
    ),
    UserAgentParser(
        '(Android) Gingerbread',
        None,
        '2',
        '3',
    ),
    UserAgentParser(
        '(Android) Honeycomb',
        None,
        '3',
        None,
    ),
    UserAgentParser(
        '(MSIE) (\\d+)\\.(\\d+).*XBLWP7',
        'IE Large Screen',
        None,
        None,
    ),
    UserAgentParser(
        '(Nextcloud)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(mirall)/(\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(ownCloud-android)/(\\d+)\\.(\\d+)\\.(\\d+)',
        'Owncloud',
        None,
        None,
    ),
    UserAgentParser(
        '(OC)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+) \\(Skype for Business\\)',
        'Skype',
        None,
        None,
    ),
    UserAgentParser(
        '(Obigo)InternetBrowser',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Obigo)\\-Browser',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Obigo|OBIGO)[^\\d]*(\\d+)(?:.(\\d+)|)',
        'Obigo',
        None,
        None,
    ),
    UserAgentParser(
        '(MAXTHON|Maxthon) (\\d+)\\.(\\d+)',
        'Maxthon',
        None,
        None,
    ),
    UserAgentParser(
        '(Maxthon|MyIE2|Uzbl|Shiira)',
        None,
        '0',
        None,
    ),
    UserAgentParser(
        '(BrowseX) \\((\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(NCSA_Mosaic)/(\\d+)\\.(\\d+)',
        'NCSA Mosaic',
        None,
        None,
    ),
    UserAgentParser(
        '(POLARIS)/(\\d+)\\.(\\d+)',
        'Polaris',
        None,
        None,
    ),
    UserAgentParser(
        '(Embider)/(\\d+)\\.(\\d+)',
        'Polaris',
        None,
        None,
    ),
    UserAgentParser(
        '(BonEcho)/(\\d+)\\.(\\d+)\\.?([ab]?\\d+|)',
        'Bon Echo',
        None,
        None,
    ),
    UserAgentParser(
        '(TopBuzz) com.alex.NewsMaster/(\\d+).(\\d+).(\\d+)',
        'TopBuzz',
        None,
        None,
    ),
    UserAgentParser(
        '(TopBuzz) com.mobilesrepublic.newsrepublic/(\\d+).(\\d+).(\\d+)',
        'TopBuzz',
        None,
        None,
    ),
    UserAgentParser(
        '(TopBuzz) com.topbuzz.videoen/(\\d+).(\\d+).(\\d+)',
        'TopBuzz',
        None,
        None,
    ),
    UserAgentParser(
        '(iPod|iPhone|iPad).+GSA/(\\d+)\\.(\\d+)\\.(\\d+)(?:\\.(\\d+)|) Mobile',
        'Google',
        None,
        None,
    ),
    UserAgentParser(
        '(iPod|iPhone|iPad).+Version/(\\d+)\\.(\\d+)(?:\\.(\\d+)|).*[ +]Safari',
        'Mobile Safari',
        None,
        None,
    ),
    UserAgentParser(
        '(iPod|iPod touch|iPhone|iPad);.*CPU.*OS[ +](\\d+)_(\\d+)(?:_(\\d+)|).* AppleNews\\/\\d+\\.\\d+\\.\\d+?',
        'Mobile Safari UI/WKWebView',
        None,
        None,
    ),
    UserAgentParser(
        '(iPod|iPhone|iPad).+Version/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        'Mobile Safari UI/WKWebView',
        None,
        None,
    ),
    UserAgentParser(
        '(iPod|iPod touch|iPhone|iPad).* Safari',
        'Mobile Safari',
        None,
        None,
    ),
    UserAgentParser(
        '(iPod|iPod touch|iPhone|iPad)',
        'Mobile Safari UI/WKWebView',
        None,
        None,
    ),
    UserAgentParser(
        '(Watch)(\\d+),(\\d+)',
        'Apple $1 App',
        None,
        None,
    ),
    UserAgentParser(
        '(Outlook-iOS)/\\d+\\.\\d+\\.prod\\.iphone \\((\\d+)\\.(\\d+)\\.(\\d+)\\)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(AvantGo) (\\d+).(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(OneBrowser)/(\\d+).(\\d+)',
        'ONE Browser',
        None,
        None,
    ),
    UserAgentParser(
        '(Avant)',
        None,
        '1',
        None,
    ),
    UserAgentParser(
        '(QtCarBrowser)',
        None,
        '1',
        None,
    ),
    UserAgentParser(
        '^(iBrowser/Mini)(\\d+).(\\d+)',
        'iBrowser Mini',
        None,
        None,
    ),
    UserAgentParser(
        '^(iBrowser|iRAPP)/(\\d+).(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '^(Nokia)',
        'Nokia Services (WAP) Browser',
        None,
        None,
    ),
    UserAgentParser(
        '(NokiaBrowser)/(\\d+)\\.(\\d+).(\\d+)\\.(\\d+)',
        'Nokia Browser',
        None,
        None,
    ),
    UserAgentParser(
        '(NokiaBrowser)/(\\d+)\\.(\\d+).(\\d+)',
        'Nokia Browser',
        None,
        None,
    ),
    UserAgentParser(
        '(NokiaBrowser)/(\\d+)\\.(\\d+)',
        'Nokia Browser',
        None,
        None,
    ),
    UserAgentParser(
        '(BrowserNG)/(\\d+)\\.(\\d+).(\\d+)',
        'Nokia Browser',
        None,
        None,
    ),
    UserAgentParser(
        '(Series60)/5\\.0',
        'Nokia Browser',
        '7',
        '0',
    ),
    UserAgentParser(
        '(Series60)/(\\d+)\\.(\\d+)',
        'Nokia OSS Browser',
        None,
        None,
    ),
    UserAgentParser(
        '(S40OviBrowser)/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)',
        'Ovi Browser',
        None,
        None,
    ),
    UserAgentParser(
        '(Nokia)[EN]?(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(PlayBook).+RIM Tablet OS (\\d+)\\.(\\d+)\\.(\\d+)',
        'BlackBerry WebKit',
        None,
        None,
    ),
    UserAgentParser(
        '(Black[bB]erry|BB10).+Version/(\\d+)\\.(\\d+)\\.(\\d+)',
        'BlackBerry WebKit',
        None,
        None,
    ),
    UserAgentParser(
        '(Black[bB]erry)\\s?(\\d+)',
        'BlackBerry',
        None,
        None,
    ),
    UserAgentParser(
        '(OmniWeb)/v(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Blazer)/(\\d+)\\.(\\d+)',
        'Palm Blazer',
        None,
        None,
    ),
    UserAgentParser(
        '(Pre)/(\\d+)\\.(\\d+)',
        'Palm Pre',
        None,
        None,
    ),
    UserAgentParser(
        '(ELinks)/(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(ELinks) \\((\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Links) \\((\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(QtWeb) Internet Browser/(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(PhantomJS)/(\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(AppleWebKit)/(\\d+)(?:\\.(\\d+)|)\\+ .* Safari',
        'WebKit Nightly',
        None,
        None,
    ),
    UserAgentParser(
        '(Version)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|).*Safari/',
        'Safari',
        None,
        None,
    ),
    UserAgentParser(
        '(Safari)/\\d+',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(OLPC)/Update(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(OLPC)/Update()\\.(\\d+)',
        None,
        '0',
        None,
    ),
    UserAgentParser(
        '(SEMC\\-Browser)/(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Teleca)',
        'Teleca Browser',
        None,
        None,
    ),
    UserAgentParser(
        '(Phantom)/V(\\d+)\\.(\\d+)',
        'Phantom Browser',
        None,
        None,
    ),
    UserAgentParser(
        '(Trident)/(7|8)\\.(0)',
        'IE',
        '11',
        None,
    ),
    UserAgentParser(
        '(Trident)/(6)\\.(0)',
        'IE',
        '10',
        None,
    ),
    UserAgentParser(
        '(Trident)/(5)\\.(0)',
        'IE',
        '9',
        None,
    ),
    UserAgentParser(
        '(Trident)/(4)\\.(0)',
        'IE',
        '8',
        None,
    ),
    UserAgentParser(
        '(Espial)/(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(AppleWebKit)/(\\d+)\\.(\\d+)\\.(\\d+)',
        'Apple Mail',
        None,
        None,
    ),
    UserAgentParser(
        '(Firefox)/(\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Firefox)/(\\d+)\\.(\\d+)(pre|[ab]\\d+[a-z]*|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '([MS]?IE) (\\d+)\\.(\\d+)',
        'IE',
        None,
        None,
    ),
    UserAgentParser(
        '(python-requests)/(\\d+)\\.(\\d+)',
        'Python Requests',
        None,
        None,
    ),
    UserAgentParser(
        '\\b(Windows-Update-Agent|WindowsPowerShell|Microsoft-CryptoAPI|SophosUpdateManager|SophosAgent|Debian APT-HTTP|Ubuntu APT-HTTP|libcurl-agent|libwww-perl|urlgrabber|curl|PycURL|Wget|wget2|aria2|Axel|OpenBSD ftp|lftp|jupdate|insomnia|fetch libfetch|akka-http|got)(?:[ /](\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Python/3\\.\\d{1,3} aiohttp)/(\\d+)\\.(\\d+)\\.(\\d+)',
        'Python aiohttp',
        None,
        None,
    ),
    UserAgentParser(
        '(Java)[/ ]?\\d+\\.(\\d+)\\.(\\d+)[_-]*([a-zA-Z0-9]+|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '^(Cyberduck)/(\\d+)\\.(\\d+)\\.(\\d+)(?:\\.\\d+|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '^(S3 Browser) (\\d+)-(\\d+)-(\\d+)(?:\\s*http://s3browser\\.com|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(S3Gof3r)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '\\b(ibm-cos-sdk-(?:core|java|js|python))/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '^(rusoto)/(\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '^(rclone)/v(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '^(Roku)/DVP-(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '(Kurio)\\/(\\d+)\\.(\\d+)\\.(\\d+)',
        'Kurio App',
        None,
        None,
    ),
    UserAgentParser(
        '^(Box(?: Sync)?)/(\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
    ),
    UserAgentParser(
        '^(ViaFree|Viafree)-(?:tvOS-)?[A-Z]{2}/(\\d+)\\.(\\d+)\\.(\\d+)',
        'ViaFree',
        None,
        None,
    ),
]

DEVICE_PARSERS = [
    DeviceParser(
        '(?:(?:iPhone|Windows CE|Windows Phone|Android).*(?:(?:Bot|Yeti)-Mobile|YRSpider|BingPreview|bots?/\\d|(?:bot|spider)\\.html)|AdsBot-Google-Mobile.*iPhone)',
        'i',
        'Spider',
        'Spider',
        'Smartphone',
    ),
    DeviceParser(
        '^Indica.+?/.+?\s\((.+?)\)\s(.+?)/(.+?)$',
        None,
        "iPad",
        "$2",
        "$3"
    ),
    DeviceParser(
        '(?:DoCoMo|\\bMOT\\b|\\bLG\\b|Nokia|Samsung|SonyEricsson).*(?:(?:Bot|Yeti)-Mobile|bots?/\\d|(?:bot|crawler)\\.html|(?:jump|google|Wukong)bot|ichiro/mobile|/spider|YahooSeeker)',
        'i',
        'Spider',
        'Spider',
        'Feature Phone',
    ),
    DeviceParser(
        ' PTST/\\d+(?:\\.)?\\d+$',
        None,
        'Spider',
        'Spider',
        None,
    ),
    DeviceParser(
        'X11; Datanyze; Linux',
        None,
        'Spider',
        'Spider',
        None,
    ),
    DeviceParser(
        'Mozilla.*Mobile.*AspiegelBot',
        None,
        'Spider',
        'Spider',
        'Smartphone',
    ),
    DeviceParser(
        'Mozilla.*AspiegelBot',
        None,
        'Spider',
        'Spider',
        'Desktop',
    ),
    DeviceParser(
        '\\bSmartWatch {0,2}\\( {0,2}([^;]+) {0,2}; {0,2}([^;]+) {0,2};',
        None,
        '$1 $2',
        '$1',
        '$2',
    ),
    DeviceParser(
        'Android Application[^\\-]+ - (Sony) ?(Ericsson|) (.+) \\w+ - ',
        None,
        '$1 $2',
        '$1$2',
        '$3',
    ),
    DeviceParser(
        'Android Application[^\\-]+ - (?:HTC|HUAWEI|LGE|LENOVO|MEDION|TCT) (HTC|HUAWEI|LG|LENOVO|MEDION|ALCATEL)[ _\\-](.+) \\w+ - ',
        'i',
        '$1 $2',
        '$1',
        '$2',
    ),
    DeviceParser(
        'Android Application[^\\-]+ - ([^ ]+) (.+) \\w+ - ',
        None,
        '$1 $2',
        '$1',
        '$2',
    ),
    DeviceParser(
        '; *([BLRQ]C\\d{4}[A-Z]+?)(?: Build|\\) AppleWebKit)',
        None,
        '3Q $1',
        '3Q',
        '$1',
    ),
    DeviceParser(
        '; *(?:3Q_)([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '3Q $1',
        '3Q',
        '$1',
    ),
    DeviceParser(
        'Android [34].*; *(A100|A101|A110|A200|A210|A211|A500|A501|A510|A511|A700(?: Lite| 3G|)|A701|B1-A71|A1-\\d{3}|B1-\\d{3}|V360|V370|W500|W500P|W501|W501P|W510|W511|W700|Slider SL101|DA22[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Acer',
        '$1',
    ),
    DeviceParser(
        '; *Acer Iconia Tab ([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Acer',
        '$1',
    ),
    DeviceParser(
        '; *(Z1[1235]0|E320[^/]*|S500|S510|Liquid[^;/]*|Iconia A\\d+)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Acer',
        '$1',
    ),
    DeviceParser(
        '; *(Acer |ACER )([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1$2',
        'Acer',
        '$2',
    ),
    DeviceParser(
        '; *(Advent |)(Vega(?:Bean|Comb|)).*?(?: Build|\\) AppleWebKit)',
        None,
        '$1$2',
        'Advent',
        '$2',
    ),
    DeviceParser(
        '; *(Ainol |)((?:NOVO|[Nn]ovo)[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1$2',
        'Ainol',
        '$2',
    ),
    DeviceParser(
        '; *AIRIS[ _\\-]?([^/;\\)]+) *(?:;|\\)|Build)',
        'i',
        '$1',
        'Airis',
        '$1',
    ),
    DeviceParser(
        '; *(OnePAD[^;/]+?)(?: Build|\\) AppleWebKit)',
        'i',
        '$1',
        'Airis',
        '$1',
    ),
    DeviceParser(
        '; *Airpad[ \\-]([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Airpad $1',
        'Airpad',
        '$1',
    ),
    DeviceParser(
        '; *(one ?touch) (EVO7|T10|T20)(?: Build|\\) AppleWebKit)',
        None,
        'Alcatel One Touch $2',
        'Alcatel',
        'One Touch $2',
    ),
    DeviceParser(
        '; *(?:alcatel[ _]|)(?:(?:one[ _]?touch[ _])|ot[ \\-])([^;/]+?)(?: Build|\\) AppleWebKit)',
        'i',
        'Alcatel One Touch $1',
        'Alcatel',
        'One Touch $1',
    ),
    DeviceParser(
        '; *(TCL)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        '$1',
        '$2',
    ),
    DeviceParser(
        '; *(Vodafone Smart II|Optimus_Madrid)(?: Build|\\) AppleWebKit)',
        None,
        'Alcatel $1',
        'Alcatel',
        '$1',
    ),
    DeviceParser(
        '; *BASE_Lutea_3(?: Build|\\) AppleWebKit)',
        None,
        'Alcatel One Touch 998',
        'Alcatel',
        'One Touch 998',
    ),
    DeviceParser(
        '; *BASE_Varia(?: Build|\\) AppleWebKit)',
        None,
        'Alcatel One Touch 918D',
        'Alcatel',
        'One Touch 918D',
    ),
    DeviceParser(
        '; *((?:FINE|Fine)\\d[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Allfine',
        '$1',
    ),
    DeviceParser(
        '; *(ALLVIEW[ _]?|Allview[ _]?)((?:Speed|SPEED).*?)(?: Build|\\) AppleWebKit)',
        None,
        '$1$2',
        'Allview',
        '$2',
    ),
    DeviceParser(
        '; *(ALLVIEW[ _]?|Allview[ _]?|)(AX1_Shine|AX2_Frenzy)(?: Build|\\) AppleWebKit)',
        None,
        '$1$2',
        'Allview',
        '$2',
    ),
    DeviceParser(
        '; *(ALLVIEW[ _]?|Allview[ _]?)([^;/]*?)(?: Build|\\) AppleWebKit)',
        None,
        '$1$2',
        'Allview',
        '$2',
    ),
    DeviceParser(
        '; *(A13-MID)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Allwinner',
        '$1',
    ),
    DeviceParser(
        '; *(Allwinner)[ _\\-]?([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'Allwinner',
        '$1',
    ),
    DeviceParser(
        '; *(A651|A701B?|A702|A703|A705|A706|A707|A711|A712|A713|A717|A722|A785|A801|A802|A803|A901|A902|A1002|A1003|A1006|A1007|A9701|A9703|Q710|Q80)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Amaway',
        '$1',
    ),
    DeviceParser(
        '; *(?:AMOI|Amoi)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Amoi $1',
        'Amoi',
        '$1',
    ),
    DeviceParser(
        '^(?:AMOI|Amoi)[ _]([^;/]+?) Linux',
        None,
        'Amoi $1',
        'Amoi',
        '$1',
    ),
    DeviceParser(
        '; *(MW(?:0[789]|10)[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Aoc',
        '$1',
    ),
    DeviceParser(
        '; *(G7|M1013|M1015G|M11[CG]?|M-?12[B]?|M15|M19[G]?|M30[ACQ]?|M31[GQ]|M32|M33[GQ]|M36|M37|M38|M701T|M710|M712B|M713|M715G|M716G|M71(?:G|GS|T|)|M72[T]?|M73[T]?|M75[GT]?|M77G|M79T|M7L|M7LN|M81|M810|M81T|M82|M92|M92KS|M92S|M717G|M721|M722G|M723|M725G|M739|M785|M791|M92SK|M93D)(?: Build|\\) AppleWebKit)',
        None,
        'Aoson $1',
        'Aoson',
        '$1',
    ),
    DeviceParser(
        '; *Aoson ([^;/]+?)(?: Build|\\) AppleWebKit)',
        'i',
        'Aoson $1',
        'Aoson',
        '$1',
    ),
    DeviceParser(
        '; *[Aa]panda[ _\\-]([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Apanda $1',
        'Apanda',
        '$1',
    ),
    DeviceParser(
        '; *(?:ARCHOS|Archos) ?(GAMEPAD.*?)(?: Build|\\) AppleWebKit)',
        None,
        'Archos $1',
        'Archos',
        '$1',
    ),
    DeviceParser(
        'ARCHOS; GOGI; ([^;]+);',
        None,
        'Archos $1',
        'Archos',
        '$1',
    ),
    DeviceParser(
        '(?:ARCHOS|Archos)[ _]?(.*?)(?: Build|[;/\\(\\)\\-]|$)',
        None,
        'Archos $1',
        'Archos',
        '$1',
    ),
    DeviceParser(
        '; *(AN(?:7|8|9|10|13)[A-Z0-9]{1,4})(?: Build|\\) AppleWebKit)',
        None,
        'Archos $1',
        'Archos',
        '$1',
    ),
    DeviceParser(
        '; *(A28|A32|A43|A70(?:BHT|CHT|HB|S|X)|A101(?:B|C|IT)|A7EB|A7EB-WK|101G9|80G9)(?: Build|\\) AppleWebKit)',
        None,
        'Archos $1',
        'Archos',
        '$1',
    ),
    DeviceParser(
        '; *(PAD-FMD[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Arival',
        '$1',
    ),
    DeviceParser(
        '; *(BioniQ) ?([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'Arival',
        '$1 $2',
    ),
    DeviceParser(
        '; *(AN\\d[^;/]+|ARCHM\\d+)(?: Build|\\) AppleWebKit)',
        None,
        'Arnova $1',
        'Arnova',
        '$1',
    ),
    DeviceParser(
        '; *(?:ARNOVA|Arnova) ?([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Arnova $1',
        'Arnova',
        '$1',
    ),
    DeviceParser(
        '; *(?:ASSISTANT |)(AP)-?([1789]\\d{2}[A-Z]{0,2}|80104)(?: Build|\\) AppleWebKit)',
        None,
        'Assistant $1-$2',
        'Assistant',
        '$1-$2',
    ),
    DeviceParser(
        '; *(ME17\\d[^;/]*|ME3\\d{2}[^;/]+|K00[A-Z]|Nexus 10|Nexus 7(?: 2013|)|PadFone[^;/]*|Transformer[^;/]*|TF\\d{3}[^;/]*|eeepc)(?: Build|\\) AppleWebKit)',
        None,
        'Asus $1',
        'Asus',
        '$1',
    ),
    DeviceParser(
        '; *ASUS[ _]*([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Asus $1',
        'Asus',
        '$1',
    ),
    DeviceParser(
        '; *Garmin-Asus ([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Garmin-Asus $1',
        'Garmin-Asus',
        '$1',
    ),
    DeviceParser(
        '; *(Garminfone)(?: Build|\\) AppleWebKit)',
        None,
        'Garmin $1',
        'Garmin-Asus',
        '$1',
    ),
    DeviceParser(
        '; (@TAB-[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Attab',
        '$1',
    ),
    DeviceParser(
        '; *(T-(?:07|[^0]\\d)[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Audiosonic',
        '$1',
    ),
    DeviceParser(
        '; *(?:Axioo[ _\\-]([^;/]+?)|(picopad)[ _\\-]([^;/]+?))(?: Build|\\) AppleWebKit)',
        'i',
        'Axioo $1$2 $3',
        'Axioo',
        '$1$2 $3',
    ),
    DeviceParser(
        '; *(V(?:100|700|800)[^;/]*)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Azend',
        '$1',
    ),
    DeviceParser(
        '; *(IBAK\\-[^;/]*)(?: Build|\\) AppleWebKit)',
        'i',
        '$1',
        'Bak',
        '$1',
    ),
    DeviceParser(
        '; *(HY5001|HY6501|X12|X21|I5)(?: Build|\\) AppleWebKit)',
        None,
        'Bedove $1',
        'Bedove',
        '$1',
    ),
    DeviceParser(
        '; *(JC-[^;/]*)(?: Build|\\) AppleWebKit)',
        None,
        'Benss $1',
        'Benss',
        '$1',
    ),
    DeviceParser(
        '; *(BB) ([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'Blackberry',
        '$2',
    ),
    DeviceParser(
        '; *(BlackBird)[ _](I8.*?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        '$1',
        '$2',
    ),
    DeviceParser(
        '; *(BlackBird)[ _](.*?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        '$1',
        '$2',
    ),
    DeviceParser(
        '; *([0-9]+BP[EM][^;/]*|Endeavour[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Blaupunkt $1',
        'Blaupunkt',
        '$1',
    ),
    DeviceParser(
        '; *((?:BLU|Blu)[ _\\-])([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1$2',
        'Blu',
        '$2',
    ),
    DeviceParser(
        '; *(?:BMOBILE )?(Blu|BLU|DASH [^;/]+|VIVO 4\\.3|TANK 4\\.5)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Blu',
        '$1',
    ),
    DeviceParser(
        '; *(TOUCH\\d[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Blusens',
        '$1',
    ),
    DeviceParser(
        '; *(AX5\\d+)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Bmobile',
        '$1',
    ),
    DeviceParser(
        '; *([Bb]q) ([^;/]+?);?(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'bq',
        '$2',
    ),
    DeviceParser(
        '; *(Maxwell [^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'bq',
        '$1',
    ),
    DeviceParser(
        '; *((?:B-Tab|B-TAB) ?\\d[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Braun',
        '$1',
    ),
    DeviceParser(
        '; *(Broncho) ([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        '$1',
        '$2',
    ),
    DeviceParser(
        '; *CAPTIVA ([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Captiva $1',
        'Captiva',
        '$1',
    ),
    DeviceParser(
        '; *(C771|CAL21|IS11CA)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Casio',
        '$1',
    ),
    DeviceParser(
        '; *(?:Cat|CAT) ([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Cat $1',
        'Cat',
        '$1',
    ),
    DeviceParser(
        '; *(?:Cat)(Nova.*?)(?: Build|\\) AppleWebKit)',
        None,
        'Cat $1',
        'Cat',
        '$1',
    ),
    DeviceParser(
        '; *(INM8002KP|ADM8000KP_[AB])(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Cat',
        'Tablet PHOENIX 8.1J0',
    ),
    DeviceParser(
        '; *(?:[Cc]elkon[ _\\*]|CELKON[ _\\*])([^;/\\)]+) ?(?:Build|;|\\))',
        None,
        '$1',
        'Celkon',
        '$1',
    ),
    DeviceParser(
        'Build/(?:[Cc]elkon)+_?([^;/_\\)]+)',
        None,
        '$1',
        'Celkon',
        '$1',
    ),
    DeviceParser(
        '; *(CT)-?(\\d+)(?: Build|\\) AppleWebKit)',
        None,
        '$1$2',
        'Celkon',
        '$1$2',
    ),
    DeviceParser(
        '; *(A19|A19Q|A105|A107[^;/\\)]*) ?(?:Build|;|\\))',
        None,
        '$1',
        'Celkon',
        '$1',
    ),
    DeviceParser(
        '; *(TPC[0-9]{4,5})(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'ChangJia',
        '$1',
    ),
    DeviceParser(
        '; *(Cloudfone)[ _](Excite)([^ ][^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2 $3',
        'Cloudfone',
        '$1 $2 $3',
    ),
    DeviceParser(
        '; *(Excite|ICE)[ _](\\d+[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Cloudfone $1 $2',
        'Cloudfone',
        'Cloudfone $1 $2',
    ),
    DeviceParser(
        '; *(Cloudfone|CloudPad)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'Cloudfone',
        '$1 $2',
    ),
    DeviceParser(
        '; *((?:Aquila|Clanga|Rapax)[^;/]+?)(?: Build|\\) AppleWebKit)',
        'i',
        '$1',
        'Cmx',
        '$1',
    ),
    DeviceParser(
        '; *(?:CFW-|Kyros )?(MID[0-9]{4}(?:[ABC]|SR|TV)?)(\\(3G\\)-4G| GB 8K| 3G| 8K| GB)? *(?:Build|[;\\)])',
        None,
        'CobyKyros $1$2',
        'CobyKyros',
        '$1$2',
    ),
    DeviceParser(
        '; *([^;/]*)Coolpad[ _]([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1$2',
        'Coolpad',
        '$1$2',
    ),
    DeviceParser(
        '; *(CUBE[ _])?([KU][0-9]+ ?GT.*?|A5300)(?: Build|\\) AppleWebKit)',
        'i',
        '$1$2',
        'Cube',
        '$2',
    ),
    DeviceParser(
        '; *CUBOT ([^;/]+?)(?: Build|\\) AppleWebKit)',
        'i',
        '$1',
        'Cubot',
        '$1',
    ),
    DeviceParser(
        '; *(BOBBY)(?: Build|\\) AppleWebKit)',
        'i',
        '$1',
        'Cubot',
        '$1',
    ),
    DeviceParser(
        '; *(Dslide [^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Danew',
        '$1',
    ),
    DeviceParser(
        '; *(XCD)[ _]?(28|35)(?: Build|\\) AppleWebKit)',
        None,
        'Dell $1$2',
        'Dell',
        '$1$2',
    ),
    DeviceParser(
        '; *(001DL)(?: Build|\\) AppleWebKit)',
        None,
        'Dell $1',
        'Dell',
        'Streak',
    ),
    DeviceParser(
        '; *(?:Dell|DELL) (Streak)(?: Build|\\) AppleWebKit)',
        None,
        'Dell $1',
        'Dell',
        'Streak',
    ),
    DeviceParser(
        '; *(101DL|GS01|Streak Pro[^;/]*)(?: Build|\\) AppleWebKit)',
        None,
        'Dell $1',
        'Dell',
        'Streak Pro',
    ),
    DeviceParser(
        '; *([Ss]treak ?7)(?: Build|\\) AppleWebKit)',
        None,
        'Dell $1',
        'Dell',
        'Streak 7',
    ),
    DeviceParser(
        '; *(Mini-3iX)(?: Build|\\) AppleWebKit)',
        None,
        'Dell $1',
        'Dell',
        '$1',
    ),
    DeviceParser(
        '; *(?:Dell|DELL)[ _](Aero|Venue|Thunder|Mini.*?|Streak[ _]Pro)(?: Build|\\) AppleWebKit)',
        None,
        'Dell $1',
        'Dell',
        '$1',
    ),
    DeviceParser(
        '; *Dell[ _]([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Dell $1',
        'Dell',
        '$1',
    ),
    DeviceParser(
        '; *Dell ([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Dell $1',
        'Dell',
        '$1',
    ),
    DeviceParser(
        '; *(TA[CD]-\\d+[^;/]*)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Denver',
        '$1',
    ),
    DeviceParser(
        '; *(iP[789]\\d{2}(?:-3G)?|IP10\\d{2}(?:-8GB)?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Dex',
        '$1',
    ),
    DeviceParser(
        '; *(AirTab)[ _\\-]([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'DNS',
        '$1 $2',
    ),
    DeviceParser(
        '; *(F\\-\\d[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Fujitsu',
        '$1',
    ),
    DeviceParser(
        '; *(HT-03A)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'HTC',
        'Magic',
    ),
    DeviceParser(
        '; *(HT\\-\\d[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'HTC',
        '$1',
    ),
    DeviceParser(
        '; *(L\\-\\d[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'LG',
        '$1',
    ),
    DeviceParser(
        '; *(N\\-\\d[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Nec',
        '$1',
    ),
    DeviceParser(
        '; *(P\\-\\d[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Panasonic',
        '$1',
    ),
    DeviceParser(
        '; *(SC\\-\\d[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Samsung',
        '$1',
    ),
    DeviceParser(
        '; *(SH\\-\\d[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Sharp',
        '$1',
    ),
    DeviceParser(
        '; *(SO\\-\\d[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'SonyEricsson',
        '$1',
    ),
    DeviceParser(
        '; *(T\\-0[12][^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Toshiba',
        '$1',
    ),
    DeviceParser(
        '; *(DOOV)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'DOOV',
        '$2',
    ),
    DeviceParser(
        '; *(Enot|ENOT)[ -]?([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'Enot',
        '$2',
    ),
    DeviceParser(
        '; *[^;/]+ Build/(?:CROSS|Cross)+[ _\\-]([^\\)]+)',
        None,
        'CROSS $1',
        'Evercoss',
        'Cross $1',
    ),
    DeviceParser(
        '; *(CROSS|Cross)[ _\\-]([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'Evercoss',
        'Cross $2',
    ),
    DeviceParser(
        '; *Explay[_ ](.+?)(?:[\\)]| Build)',
        None,
        '$1',
        'Explay',
        '$1',
    ),
    DeviceParser(
        '; *(IQ.*?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Fly',
        '$1',
    ),
    DeviceParser(
        '; *(Fly|FLY)[ _](IQ[^;]+?|F[34]\\d+[^;]*?);?(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'Fly',
        '$2',
    ),
    DeviceParser(
        '; *(M532|Q572|FJL21)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Fujitsu',
        '$1',
    ),
    DeviceParser(
        '; *(G1)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Galapad',
        '$1',
    ),
    DeviceParser(
        '; *(Geeksphone) ([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        '$1',
        '$2',
    ),
    DeviceParser(
        '; *(G[^F]?FIVE) ([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'Gfive',
        '$2',
    ),
    DeviceParser(
        '; *(Gionee)[ _\\-]([^;/]+?)(?:/[^;/]+|)(?: Build|\\) AppleWebKit)',
        'i',
        '$1 $2',
        'Gionee',
        '$2',
    ),
    DeviceParser(
        '; *(GN\\d+[A-Z]?|INFINITY_PASSION|Ctrl_V1)(?: Build|\\) AppleWebKit)',
        None,
        'Gionee $1',
        'Gionee',
        '$1',
    ),
    DeviceParser(
        '; *(E3) Build/JOP40D',
        None,
        'Gionee $1',
        'Gionee',
        '$1',
    ),
    DeviceParser(
        '\\sGIONEE[-\\s_](\\w*)',
        'i',
        'Gionee $1',
        'Gionee',
        '$1',
    ),
    DeviceParser(
        '; *((?:FONE|QUANTUM|INSIGNIA) \\d+[^;/]*|PLAYTAB)(?: Build|\\) AppleWebKit)',
        None,
        'GoClever $1',
        'GoClever',
        '$1',
    ),
    DeviceParser(
        '; *GOCLEVER ([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'GoClever $1',
        'GoClever',
        '$1',
    ),
    DeviceParser(
        '; *(Glass \\d+)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Google',
        '$1',
    ),
    DeviceParser(
        '; *(Pixel.*?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Google',
        '$1',
    ),
    DeviceParser(
        '; *(GSmart)[ -]([^/]+)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'Gigabyte',
        '$1 $2',
    ),
    DeviceParser(
        '; *(imx5[13]_[^/]+)(?: Build|\\) AppleWebKit)',
        None,
        'Freescale $1',
        'Freescale',
        '$1',
    ),
    DeviceParser(
        '; *Haier[ _\\-]([^/]+)(?: Build|\\) AppleWebKit)',
        None,
        'Haier $1',
        'Haier',
        '$1',
    ),
    DeviceParser(
        '; *(PAD1016)(?: Build|\\) AppleWebKit)',
        None,
        'Haipad $1',
        'Haipad',
        '$1',
    ),
    DeviceParser(
        '; *(M701|M7|M8|M9)(?: Build|\\) AppleWebKit)',
        None,
        'Haipad $1',
        'Haipad',
        '$1',
    ),
    DeviceParser(
        '; *(SN\\d+T[^;\\)/]*)(?: Build|[;\\)])',
        None,
        'Hannspree $1',
        'Hannspree',
        '$1',
    ),
    DeviceParser(
        'Build/HCL ME Tablet ([^;\\)]+)[\\);]',
        None,
        'HCLme $1',
        'HCLme',
        '$1',
    ),
    DeviceParser(
        '; *([^;\\/]+) Build/HCL',
        None,
        'HCLme $1',
        'HCLme',
        '$1',
    ),
    DeviceParser(
        '; *(MID-?\\d{4}C[EM])(?: Build|\\) AppleWebKit)',
        None,
        'Hena $1',
        'Hena',
        '$1',
    ),
    DeviceParser(
        '; *(EG\\d{2,}|HS-[^;/]+|MIRA[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Hisense $1',
        'Hisense',
        '$1',
    ),
    DeviceParser(
        '; *(andromax[^;/]+?)(?: Build|\\) AppleWebKit)',
        'i',
        'Hisense $1',
        'Hisense',
        '$1',
    ),
    DeviceParser(
        '; *(?:AMAZE[ _](S\\d+)|(S\\d+)[ _]AMAZE)(?: Build|\\) AppleWebKit)',
        None,
        'AMAZE $1$2',
        'hitech',
        'AMAZE $1$2',
    ),
    DeviceParser(
        '; *(PlayBook)(?: Build|\\) AppleWebKit)',
        None,
        'HP $1',
        'HP',
        '$1',
    ),
    DeviceParser(
        '; *HP ([^/]+)(?: Build|\\) AppleWebKit)',
        None,
        'HP $1',
        'HP',
        '$1',
    ),
    DeviceParser(
        '; *([^/]+_tenderloin)(?: Build|\\) AppleWebKit)',
        None,
        'HP TouchPad',
        'HP',
        'TouchPad',
    ),
    DeviceParser(
        '; *(HUAWEI |Huawei-|)([UY][^;/]+) Build/(?:Huawei|HUAWEI)([UY][^\\);]+)\\)',
        None,
        '$1$2',
        'Huawei',
        '$2',
    ),
    DeviceParser(
        '; *([^;/]+) Build[/ ]Huawei(MT1-U06|[A-Z]+\\d+[^\\);]+)\\)',
        None,
        '$1',
        'Huawei',
        '$2',
    ),
    DeviceParser(
        '; *(S7|M860) Build',
        None,
        '$1',
        'Huawei',
        '$1',
    ),
    DeviceParser(
        '; *((?:HUAWEI|Huawei)[ \\-]?)(MediaPad) Build',
        None,
        '$1$2',
        'Huawei',
        '$2',
    ),
    DeviceParser(
        '; *((?:HUAWEI[ _]?|Huawei[ _]|)Ascend[ _])([^;/]+) Build',
        None,
        '$1$2',
        'Huawei',
        '$2',
    ),
    DeviceParser(
        '; *((?:HUAWEI|Huawei)[ _\\-]?)((?:G700-|MT-)[^;/]+) Build',
        None,
        '$1$2',
        'Huawei',
        '$2',
    ),
    DeviceParser(
        '; *((?:HUAWEI|Huawei)[ _\\-]?)([^;/]+) Build',
        None,
        '$1$2',
        'Huawei',
        '$2',
    ),
    DeviceParser(
        '; *(MediaPad[^;]+|SpringBoard) Build/Huawei',
        None,
        '$1',
        'Huawei',
        '$1',
    ),
    DeviceParser(
        '; *([^;]+) Build/(?:Huawei|HUAWEI)',
        None,
        '$1',
        'Huawei',
        '$1',
    ),
    DeviceParser(
        '; *([Uu])([89]\\d{3}) Build',
        None,
        '$1$2',
        'Huawei',
        'U$2',
    ),
    DeviceParser(
        '; *(?:Ideos |IDEOS )(S7) Build',
        None,
        'Huawei Ideos$1',
        'Huawei',
        'Ideos$1',
    ),
    DeviceParser(
        '; *(?:Ideos |IDEOS )([^;/]+\\s*|\\s*)Build',
        None,
        'Huawei Ideos$1',
        'Huawei',
        'Ideos$1',
    ),
    DeviceParser(
        '; *(Orange Daytona|Pulse|Pulse Mini|Vodafone 858|C8500|C8600|C8650|C8660|Nexus 6P|ATH-.+?) Build[/ ]',
        None,
        'Huawei $1',
        'Huawei',
        '$1',
    ),
    DeviceParser(
        '; *((?:[A-Z]{3})\\-L[A-Za0-9]{2})[\\)]',
        None,
        'Huawei $1',
        'Huawei',
        '$1',
    ),
    DeviceParser(
        '; *HTC[ _]([^;]+); Windows Phone',
        None,
        'HTC $1',
        'HTC',
        '$1',
    ),
    DeviceParser(
        '; *(?:HTC[ _/])+([^ _/]+)(?:[/\\\\]1\\.0 | V|/| +)\\d+\\.\\d[\\d\\.]*(?: *Build|\\))',
        None,
        'HTC $1',
        'HTC',
        '$1',
    ),
    DeviceParser(
        '; *(?:HTC[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)|)(?:[/\\\\]1\\.0 | V|/| +)\\d+\\.\\d[\\d\\.]*(?: *Build|\\))',
        None,
        'HTC $1 $2',
        'HTC',
        '$1 $2',
    ),
    DeviceParser(
        '; *(?:HTC[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/]+)|)|)(?:[/\\\\]1\\.0 | V|/| +)\\d+\\.\\d[\\d\\.]*(?: *Build|\\))',
        None,
        'HTC $1 $2 $3',
        'HTC',
        '$1 $2 $3',
    ),
    DeviceParser(
        '; *(?:HTC[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/]+)|)|)|)(?:[/\\\\]1\\.0 | V|/| +)\\d+\\.\\d[\\d\\.]*(?: *Build|\\))',
        None,
        'HTC $1 $2 $3 $4',
        'HTC',
        '$1 $2 $3 $4',
    ),
    DeviceParser(
        '; *(?:(?:HTC|htc)(?:_blocked|)[ _/])+([^ _/;]+)(?: *Build|[;\\)]| - )',
        None,
        'HTC $1',
        'HTC',
        '$1',
    ),
    DeviceParser(
        '; *(?:(?:HTC|htc)(?:_blocked|)[ _/])+([^ _/]+)(?:[ _/]([^ _/;\\)]+)|)(?: *Build|[;\\)]| - )',
        None,
        'HTC $1 $2',
        'HTC',
        '$1 $2',
    ),
    DeviceParser(
        '; *(?:(?:HTC|htc)(?:_blocked|)[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/;\\)]+)|)|)(?: *Build|[;\\)]| - )',
        None,
        'HTC $1 $2 $3',
        'HTC',
        '$1 $2 $3',
    ),
    DeviceParser(
        '; *(?:(?:HTC|htc)(?:_blocked|)[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ /;]+)|)|)|)(?: *Build|[;\\)]| - )',
        None,
        'HTC $1 $2 $3 $4',
        'HTC',
        '$1 $2 $3 $4',
    ),
    DeviceParser(
        'HTC Streaming Player [^\\/]*/[^\\/]*/ htc_([^/]+) /',
        None,
        'HTC $1',
        'HTC',
        '$1',
    ),
    DeviceParser(
        '(?:[;,] *|^)(?:htccn_chs-|)HTC[ _-]?([^;]+?)(?: *Build|clay|Android|-?Mozilla| Opera| Profile| UNTRUSTED|[;/\\(\\)]|$)',
        'i',
        'HTC $1',
        'HTC',
        '$1',
    ),
    DeviceParser(
        '; *(A6277|ADR6200|ADR6300|ADR6350|ADR6400[A-Z]*|ADR6425[A-Z]*|APX515CKT|ARIA|Desire[^_ ]*|Dream|EndeavorU|Eris|Evo|Flyer|HD2|Hero|HERO200|Hero CDMA|HTL21|Incredible|Inspire[A-Z0-9]*|Legend|Liberty|Nexus ?(?:One|HD2)|One|One S C2|One[ _]?(?:S|V|X\\+?)\\w*|PC36100|PG06100|PG86100|S31HT|Sensation|Wildfire)(?: Build|[/;\\(\\)])',
        'i',
        'HTC $1',
        'HTC',
        '$1',
    ),
    DeviceParser(
        '; *(ADR6200|ADR6400L|ADR6425LVW|Amaze|DesireS?|EndeavorU|Eris|EVO|Evo\\d[A-Z]+|HD2|IncredibleS?|Inspire[A-Z0-9]*|Inspire[A-Z0-9]*|Sensation[A-Z0-9]*|Wildfire)[ _-](.+?)(?:[/;\\)]|Build|MIUI|1\\.0)',
        'i',
        'HTC $1 $2',
        'HTC',
        '$1 $2',
    ),
    DeviceParser(
        '; *HYUNDAI (T\\d[^/]*)(?: Build|\\) AppleWebKit)',
        None,
        'Hyundai $1',
        'Hyundai',
        '$1',
    ),
    DeviceParser(
        '; *HYUNDAI ([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Hyundai $1',
        'Hyundai',
        '$1',
    ),
    DeviceParser(
        '; *(X700|Hold X|MB-6900)(?: Build|\\) AppleWebKit)',
        None,
        'Hyundai $1',
        'Hyundai',
        '$1',
    ),
    DeviceParser(
        '; *(?:iBall[ _\\-]|)(Andi)[ _]?(\\d[^;/]*)(?: Build|\\) AppleWebKit)',
        'i',
        '$1 $2',
        'iBall',
        '$1 $2',
    ),
    DeviceParser(
        '; *(IBall)(?:[ _]([^;/]+?)|)(?: Build|\\) AppleWebKit)',
        'i',
        '$1 $2',
        'iBall',
        '$2',
    ),
    DeviceParser(
        '; *(NT-\\d+[^ ;/]*|Net[Tt]AB [^;/]+|Mercury [A-Z]+|iconBIT)(?: S/N:[^;/]+|)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'IconBIT',
        '$1',
    ),
    DeviceParser(
        '; *(IMO)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)',
        'i',
        '$1 $2',
        'IMO',
        '$2',
    ),
    DeviceParser(
        '; *i-?mobile[ _]([^/]+)(?: Build|\\) AppleWebKit)',
        'i',
        'i-mobile $1',
        'imobile',
        '$1',
    ),
    DeviceParser(
        '; *(i-(?:style|note)[^/]*)(?: Build|\\) AppleWebKit)',
        'i',
        'i-mobile $1',
        'imobile',
        '$1',
    ),
    DeviceParser(
        '; *(ImPAD) ?(\\d+(?:.)*?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'Impression',
        '$1 $2',
    ),
    DeviceParser(
        '; *(Infinix)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'Infinix',
        '$2',
    ),
    DeviceParser(
        '; *(Informer)[ \\-]([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'Informer',
        '$2',
    ),
    DeviceParser(
        '; *(TAB) ?([78][12]4)(?: Build|\\) AppleWebKit)',
        None,
        'Intenso $1',
        'Intenso',
        '$1 $2',
    ),
    DeviceParser(
        '; *(?:Intex[ _]|)(AQUA|Aqua)([ _\\.\\-])([^;/]+?) *(?:Build|;)',
        None,
        '$1$2$3',
        'Intex',
        '$1 $3',
    ),
    DeviceParser(
        '; *(?:INTEX|Intex)(?:[_ ]([^\\ _;/]+))(?:[_ ]([^\\ _;/]+)|) *(?:Build|;)',
        None,
        '$1 $2',
        'Intex',
        '$1 $2',
    ),
    DeviceParser(
        '; *([iI]Buddy)[ _]?(Connect)(?:_|\\?_| |)([^;/]*) *(?:Build|;)',
        None,
        '$1 $2 $3',
        'Intex',
        'iBuddy $2 $3',
    ),
    DeviceParser(
        '; *(I-Buddy)[ _]([^;/]+?) *(?:Build|;)',
        None,
        '$1 $2',
        'Intex',
        'iBuddy $2',
    ),
    DeviceParser(
        '; *(iOCEAN) ([^/]+)(?: Build|\\) AppleWebKit)',
        'i',
        '$1 $2',
        'iOCEAN',
        '$2',
    ),
    DeviceParser(
        '; *(TP\\d+(?:\\.\\d+|)\\-\\d[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'ionik $1',
        'ionik',
        '$1',
    ),
    DeviceParser(
        '; *(M702pro)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Iru',
        '$1',
    ),
    DeviceParser(
        '; *itel ([^;/]*)(?: Build|\\) AppleWebKit)',
        None,
        'Itel $1',
        'Itel',
        '$1',
    ),
    DeviceParser(
        '; *(DE88Plus|MD70)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Ivio',
        '$1',
    ),
    DeviceParser(
        '; *IVIO[_\\-]([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Ivio',
        '$1',
    ),
    DeviceParser(
        '; *(TPC-\\d+|JAY-TECH)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Jaytech',
        '$1',
    ),
    DeviceParser(
        '; *(JY-[^;/]+|G[234]S?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Jiayu',
        '$1',
    ),
    DeviceParser(
        '; *(JXD)[ _\\-]([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'JXD',
        '$2',
    ),
    DeviceParser(
        '; *Karbonn[ _]?([^;/]+) *(?:Build|;)',
        'i',
        '$1',
        'Karbonn',
        '$1',
    ),
    DeviceParser(
        '; *([^;]+) Build/Karbonn',
        None,
        '$1',
        'Karbonn',
        '$1',
    ),
    DeviceParser(
        '; *(A11|A39|A37|A34|ST8|ST10|ST7|Smart Tab3|Smart Tab2|Titanium S\\d) +Build',
        None,
        '$1',
        'Karbonn',
        '$1',
    ),
    DeviceParser(
        '; *(IS01|IS03|IS05|IS\\d{2}SH)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Sharp',
        '$1',
    ),
    DeviceParser(
        '; *(IS04)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Regza',
        '$1',
    ),
    DeviceParser(
        '; *(IS06|IS\\d{2}PT)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Pantech',
        '$1',
    ),
    DeviceParser(
        '; *(IS11S)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'SonyEricsson',
        'Xperia Acro',
    ),
    DeviceParser(
        '; *(IS11CA)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Casio',
        'GzOne $1',
    ),
    DeviceParser(
        '; *(IS11LG)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'LG',
        'Optimus X',
    ),
    DeviceParser(
        '; *(IS11N)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Medias',
        '$1',
    ),
    DeviceParser(
        '; *(IS11PT)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Pantech',
        'MIRACH',
    ),
    DeviceParser(
        '; *(IS12F)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Fujitsu',
        'Arrows ES',
    ),
    DeviceParser(
        '; *(IS12M)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Motorola',
        'XT909',
    ),
    DeviceParser(
        '; *(IS12S)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'SonyEricsson',
        'Xperia Acro HD',
    ),
    DeviceParser(
        '; *(ISW11F)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Fujitsu',
        'Arrowz Z',
    ),
    DeviceParser(
        '; *(ISW11HT)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'HTC',
        'EVO',
    ),
    DeviceParser(
        '; *(ISW11K)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Kyocera',
        'DIGNO',
    ),
    DeviceParser(
        '; *(ISW11M)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Motorola',
        'Photon',
    ),
    DeviceParser(
        '; *(ISW11SC)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Samsung',
        'GALAXY S II WiMAX',
    ),
    DeviceParser(
        '; *(ISW12HT)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'HTC',
        'EVO 3D',
    ),
    DeviceParser(
        '; *(ISW13HT)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'HTC',
        'J',
    ),
    DeviceParser(
        '; *(ISW?[0-9]{2}[A-Z]{0,2})(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'KDDI',
        '$1',
    ),
    DeviceParser(
        '; *(INFOBAR [^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'KDDI',
        '$1',
    ),
    DeviceParser(
        '; *(JOYPAD|Joypad)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'Kingcom',
        '$1 $2',
    ),
    DeviceParser(
        '; *(Vox|VOX|Arc|K080)(?: Build|\\) AppleWebKit)',
        'i',
        '$1',
        'Kobo',
        '$1',
    ),
    DeviceParser(
        '\\b(Kobo Touch)\\b',
        None,
        '$1',
        'Kobo',
        '$1',
    ),
    DeviceParser(
        '; *(K-Touch)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)',
        'i',
        '$1 $2',
        'Ktouch',
        '$2',
    ),
    DeviceParser(
        '; *((?:EV|KM)-S\\d+[A-Z]?)(?: Build|\\) AppleWebKit)',
        'i',
        '$1',
        'KTtech',
        '$1',
    ),
    DeviceParser(
        '; *(Zio|Hydro|Torque|Event|EVENT|Echo|Milano|Rise|URBANO PROGRESSO|WX04K|WX06K|WX10K|KYL21|101K|C5[12]\\d{2})(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Kyocera',
        '$1',
    ),
    DeviceParser(
        '; *(?:LAVA[ _]|)IRIS[ _\\-]?([^/;\\)]+) *(?:;|\\)|Build)',
        'i',
        'Iris $1',
        'Lava',
        'Iris $1',
    ),
    DeviceParser(
        '; *LAVA[ _]([^;/]+) Build',
        None,
        '$1',
        'Lava',
        '$1',
    ),
    DeviceParser(
        '; *(?:(Aspire A1)|(?:LEMON|Lemon)[ _]([^;/]+))_?(?: Build|\\) AppleWebKit)',
        None,
        'Lemon $1$2',
        'Lemon',
        '$1$2',
    ),
    DeviceParser(
        '; *(TAB-1012)(?: Build|\\) AppleWebKit)',
        None,
        'Lenco $1',
        'Lenco',
        '$1',
    ),
    DeviceParser(
        '; Lenco ([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Lenco $1',
        'Lenco',
        '$1',
    ),
    DeviceParser(
        '; *(A1_07|A2107A-H|S2005A-H|S1-37AH0) Build',
        None,
        '$1',
        'Lenovo',
        '$1',
    ),
    DeviceParser(
        '; *(Idea[Tp]ab)[ _]([^;/]+);? Build',
        None,
        'Lenovo $1 $2',
        'Lenovo',
        '$1 $2',
    ),
    DeviceParser(
        '; *(Idea(?:Tab|pad)) ?([^;/]+) Build',
        None,
        'Lenovo $1 $2',
        'Lenovo',
        '$1 $2',
    ),
    DeviceParser(
        '; *(ThinkPad) ?(Tablet) Build/',
        None,
        'Lenovo $1 $2',
        'Lenovo',
        '$1 $2',
    ),
    DeviceParser(
        '; *(?:LNV-|)(?:=?[Ll]enovo[ _\\-]?|LENOVO[ _])(.+?)(?:Build|[;/\\)])',
        None,
        'Lenovo $1',
        'Lenovo',
        '$1',
    ),
    DeviceParser(
        '[;,] (?:Vodafone |)(SmartTab) ?(II) ?(\\d+) Build/',
        None,
        'Lenovo $1 $2 $3',
        'Lenovo',
        '$1 $2 $3',
    ),
    DeviceParser(
        '; *(?:Ideapad |)K1 Build/',
        None,
        'Lenovo Ideapad K1',
        'Lenovo',
        'Ideapad K1',
    ),
    DeviceParser(
        '; *(3GC101|3GW10[01]|A390) Build/',
        None,
        '$1',
        'Lenovo',
        '$1',
    ),
    DeviceParser(
        '\\b(?:Lenovo|LENOVO)+[ _\\-]?([^,;:/ ]+)',
        None,
        'Lenovo $1',
        'Lenovo',
        '$1',
    ),
    DeviceParser(
        '; *(MFC\\d+)[A-Z]{2}([^;,/]*),?(?: Build|\\) AppleWebKit)',
        None,
        '$1$2',
        'Lexibook',
        '$1$2',
    ),
    DeviceParser(
        '; *(E[34][0-9]{2}|LS[6-8][0-9]{2}|VS[6-9][0-9]+[^;/]+|Nexus 4|Nexus 5X?|GT540f?|Optimus (?:2X|G|4X HD)|OptimusX4HD) *(?:Build|;)',
        None,
        '$1',
        'LG',
        '$1',
    ),
    DeviceParser(
        '[;:] *(L-\\d+[A-Z]|LGL\\d+[A-Z]?)(?:/V\\d+|) *(?:Build|[;\\)])',
        None,
        '$1',
        'LG',
        '$1',
    ),
    DeviceParser(
        '; *(LG-)([A-Z]{1,2}\\d{2,}[^,;/\\)\\(]*?)(?:Build| V\\d+|[,;/\\)\\(]|$)',
        None,
        '$1$2',
        'LG',
        '$2',
    ),
    DeviceParser(
        '; *(LG[ \\-]|LG)([^;/]+)[;/]? Build',
        None,
        '$1$2',
        'LG',
        '$2',
    ),
    DeviceParser(
        '^(LG)-([^;/]+)/ Mozilla/.*; Android',
        None,
        '$1 $2',
        'LG',
        '$2',
    ),
    DeviceParser(
        '(Web0S); Linux/(SmartTV)',
        None,
        'LG $1 $2',
        'LG',
        '$1 $2',
    ),
    DeviceParser(
        '; *((?:SMB|smb)[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Malata',
        '$1',
    ),
    DeviceParser(
        '; *(?:Malata|MALATA) ([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Malata',
        '$1',
    ),
    DeviceParser(
        '; *(MS[45][0-9]{3}|MID0[568][NS]?|MID[1-9]|MID[78]0[1-9]|MID970[1-9]|MID100[1-9])(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Manta',
        '$1',
    ),
    DeviceParser(
        '; *(M1052|M806|M9000|M9100|M9701|MID100|MID120|MID125|MID130|MID135|MID140|MID701|MID710|MID713|MID727|MID728|MID731|MID732|MID733|MID735|MID736|MID737|MID760|MID800|MID810|MID820|MID830|MID833|MID835|MID860|MID900|MID930|MID933|MID960|MID980)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Match',
        '$1',
    ),
    DeviceParser(
        '; *(GenxDroid7|MSD7.*?|AX\\d.*?|Tab 701|Tab 722)(?: Build|\\) AppleWebKit)',
        None,
        'Maxx $1',
        'Maxx',
        '$1',
    ),
    DeviceParser(
        '; *(M-PP[^;/]+|PhonePad ?\\d{2,}[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Mediacom $1',
        'Mediacom',
        '$1',
    ),
    DeviceParser(
        '; *(M-MP[^;/]+|SmartPad ?\\d{2,}[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Mediacom $1',
        'Mediacom',
        '$1',
    ),
    DeviceParser(
        '; *(?:MD_|)LIFETAB[ _]([^;/]+?)(?: Build|\\) AppleWebKit)',
        'i',
        'Medion Lifetab $1',
        'Medion',
        'Lifetab $1',
    ),
    DeviceParser(
        '; *MEDION ([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Medion $1',
        'Medion',
        '$1',
    ),
    DeviceParser(
        '; *(M030|M031|M035|M040|M065|m9)(?: Build|\\) AppleWebKit)',
        None,
        'Meizu $1',
        'Meizu',
        '$1',
    ),
    DeviceParser(
        '; *(?:meizu_|MEIZU )(.+?) *(?:Build|[;\\)])',
        None,
        'Meizu $1',
        'Meizu',
        '$1',
    ),
    DeviceParser(
        '; *(?:Micromax[ _](A111|A240)|(A111|A240)) Build',
        'i',
        'Micromax $1$2',
        'Micromax',
        '$1$2',
    ),
    DeviceParser(
        '; *Micromax[ _](A\\d{2,3}[^;/]*) Build',
        'i',
        'Micromax $1',
        'Micromax',
        '$1',
    ),
    DeviceParser(
        '; *(A\\d{2}|A[12]\\d{2}|A90S|A110Q) Build',
        'i',
        'Micromax $1',
        'Micromax',
        '$1',
    ),
    DeviceParser(
        '; *Micromax[ _](P\\d{3}[^;/]*) Build',
        'i',
        'Micromax $1',
        'Micromax',
        '$1',
    ),
    DeviceParser(
        '; *(P\\d{3}|P\\d{3}\\(Funbook\\)) Build',
        'i',
        'Micromax $1',
        'Micromax',
        '$1',
    ),
    DeviceParser(
        '; *(MITO)[ _\\-]?([^;/]+?)(?: Build|\\) AppleWebKit)',
        'i',
        '$1 $2',
        'Mito',
        '$2',
    ),
    DeviceParser(
        '; *(Cynus)[ _](F5|T\\d|.+?) *(?:Build|[;/\\)])',
        'i',
        '$1 $2',
        'Mobistel',
        '$1 $2',
    ),
    DeviceParser(
        '; *(MODECOM |)(FreeTab) ?([^;/]+?)(?: Build|\\) AppleWebKit)',
        'i',
        '$1$2 $3',
        'Modecom',
        '$2 $3',
    ),
    DeviceParser(
        '; *(MODECOM )([^;/]+?)(?: Build|\\) AppleWebKit)',
        'i',
        '$1 $2',
        'Modecom',
        '$2',
    ),
    DeviceParser(
        '; *(MZ\\d{3}\\+?|MZ\\d{3} 4G|Xoom|XOOM[^;/]*) Build',
        None,
        'Motorola $1',
        'Motorola',
        '$1',
    ),
    DeviceParser(
        '; *(Milestone )(XT[^;/]*) Build',
        None,
        'Motorola $1$2',
        'Motorola',
        '$2',
    ),
    DeviceParser(
        '; *(Motoroi ?x|Droid X|DROIDX) Build',
        'i',
        'Motorola $1',
        'Motorola',
        'DROID X',
    ),
    DeviceParser(
        '; *(Droid[^;/]*|DROID[^;/]*|Milestone[^;/]*|Photon|Triumph|Devour|Titanium) Build',
        None,
        'Motorola $1',
        'Motorola',
        '$1',
    ),
    DeviceParser(
        '; *(A555|A85[34][^;/]*|A95[356]|ME[58]\\d{2}\\+?|ME600|ME632|ME722|MB\\d{3}\\+?|MT680|MT710|MT870|MT887|MT917|WX435|WX453|WX44[25]|XT\\d{3,4}[A-Z\\+]*|CL[iI]Q|CL[iI]Q XT) Build',
        None,
        '$1',
        'Motorola',
        '$1',
    ),
    DeviceParser(
        '; *(Motorola MOT-|Motorola[ _\\-]|MOT\\-?)([^;/]+) Build',
        None,
        '$1$2',
        'Motorola',
        '$2',
    ),
    DeviceParser(
        '; *(Moto[_ ]?|MOT\\-)([^;/]+) Build',
        None,
        '$1$2',
        'Motorola',
        '$2',
    ),
    DeviceParser(
        '; *((?:MP[DQ]C|MPG\\d{1,4}|MP\\d{3,4}|MID(?:(?:10[234]|114|43|7[247]|8[24]|7)C|8[01]1))[^;/]*)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Mpman',
        '$1',
    ),
    DeviceParser(
        '; *(?:MSI[ _]|)(Primo\\d+|Enjoy[ _\\-][^;/]+?)(?: Build|\\) AppleWebKit)',
        'i',
        '$1',
        'Msi',
        '$1',
    ),
    DeviceParser(
        '; *Multilaser[ _]([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Multilaser',
        '$1',
    ),
    DeviceParser(
        '; *(My)[_]?(Pad)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1$2 $3',
        'MyPhone',
        '$1$2 $3',
    ),
    DeviceParser(
        '; *(My)\\|?(Phone)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1$2 $3',
        'MyPhone',
        '$3',
    ),
    DeviceParser(
        '; *(A\\d+)[ _](Duo|)(?: Build|\\) AppleWebKit)',
        'i',
        '$1 $2',
        'MyPhone',
        '$1 $2',
    ),
    DeviceParser(
        '; *(myTab[^;/]*)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Mytab',
        '$1',
    ),
    DeviceParser(
        '; *(NABI2?-)([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1$2',
        'Nabi',
        '$2',
    ),
    DeviceParser(
        '; *(N-\\d+[CDE])(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Nec',
        '$1',
    ),
    DeviceParser(
        '; ?(NEC-)(.*?)(?: Build|\\) AppleWebKit)',
        None,
        '$1$2',
        'Nec',
        '$2',
    ),
    DeviceParser(
        '; *(LT-NA7)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Nec',
        'Lifetouch Note',
    ),
    DeviceParser(
        '; *(NXM\\d+[A-Za-z0-9_]*|Next\\d[A-Za-z0-9_ \\-]*|NEXT\\d[A-Za-z0-9_ \\-]*|Nextbook [A-Za-z0-9_ ]*|DATAM803HC|M805)(?: Build|[\\);])',
        None,
        '$1',
        'Nextbook',
        '$1',
    ),
    DeviceParser(
        '; *(Nokia)([ _\\-]*)([^;/]*) Build',
        'i',
        '$1$2$3',
        'Nokia',
        '$3',
    ),
    DeviceParser(
        '; *(TA\\-\\d{4})(?: Build|\\) AppleWebKit)',
        None,
        'Nokia $1',
        'Nokia',
        '$1',
    ),
    DeviceParser(
        '; *(Nook ?|Barnes & Noble Nook |BN )([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1$2',
        'Nook',
        '$2',
    ),
    DeviceParser(
        '; *(NOOK |)(BNRV200|BNRV200A|BNTV250|BNTV250A|BNTV400|BNTV600|LogicPD Zoom2)(?: Build|\\) AppleWebKit)',
        None,
        '$1$2',
        'Nook',
        '$2',
    ),
    DeviceParser(
        '; Build/(Nook)',
        None,
        '$1',
        'Nook',
        'Tablet',
    ),
    DeviceParser(
        '; *(OP110|OliPad[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Olivetti $1',
        'Olivetti',
        '$1',
    ),
    DeviceParser(
        '; *OMEGA[ _\\-](MID[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Omega $1',
        'Omega',
        '$1',
    ),
    DeviceParser(
        '^(MID7500|MID\\d+) Mozilla/5\\.0 \\(iPad;',
        None,
        'Omega $1',
        'Omega',
        '$1',
    ),
    DeviceParser(
        '; *((?:CIUS|cius)[^;/]*)(?: Build|\\) AppleWebKit)',
        None,
        'Openpeak $1',
        'Openpeak',
        '$1',
    ),
    DeviceParser(
        '; *(Find ?(?:5|7a)|R8[012]\\d{1,2}|T703\\d?|U70\\d{1,2}T?|X90\\d{1,2}|[AFR]\\d{1,2}[a-z]{1,2})(?: Build|\\) AppleWebKit)',
        None,
        'Oppo $1',
        'Oppo',
        '$1',
    ),
    DeviceParser(
        '; *OPPO ?([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Oppo $1',
        'Oppo',
        '$1',
    ),
    DeviceParser(
        '; *(CPH\\d{1,4}|RMX\\d{1,4}|P[A-Z]{3}\\d{2})(?: Build|\\) AppleWebKit)',
        None,
        'Oppo $1',
        'Oppo',
        None,
    ),
    DeviceParser(
        '; *(A1601)(?: Build|\\) AppleWebKit)',
        None,
        'Oppo F1s',
        'Oppo',
        '$1',
    ),
    DeviceParser(
        '; *(?:Odys\\-|ODYS\\-|ODYS )([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Odys $1',
        'Odys',
        '$1',
    ),
    DeviceParser(
        '; *(SELECT) ?(7)(?: Build|\\) AppleWebKit)',
        None,
        'Odys $1 $2',
        'Odys',
        '$1 $2',
    ),
    DeviceParser(
        '; *(PEDI)_(PLUS)_(W)(?: Build|\\) AppleWebKit)',
        None,
        'Odys $1 $2 $3',
        'Odys',
        '$1 $2 $3',
    ),
    DeviceParser(
        '; *(AEON|BRAVIO|FUSION|FUSION2IN1|Genio|EOS10|IEOS[^;/]*|IRON|Loox|LOOX|LOOX Plus|Motion|NOON|NOON_PRO|NEXT|OPOS|PEDI[^;/]*|PRIME[^;/]*|STUDYTAB|TABLO|Tablet-PC-4|UNO_X8|XELIO[^;/]*|Xelio ?\\d+ ?[Pp]ro|XENO10|XPRESS PRO)(?: Build|\\) AppleWebKit)',
        None,
        'Odys $1',
        'Odys',
        '$1',
    ),
    DeviceParser(
        '; (ONE [a-zA-Z]\\d+)(?: Build|\\) AppleWebKit)',
        None,
        'OnePlus $1',
        'OnePlus',
        '$1',
    ),
    DeviceParser(
        '; (ONEPLUS [a-zA-Z]\\d+)(?: Build|\\) AppleWebKit)',
        None,
        'OnePlus $1',
        'OnePlus',
        '$1',
    ),
    DeviceParser(
        '; *(TP-\\d+)(?: Build|\\) AppleWebKit)',
        None,
        'Orion $1',
        'Orion',
        '$1',
    ),
    DeviceParser(
        '; *(G100W?)(?: Build|\\) AppleWebKit)',
        None,
        'PackardBell $1',
        'PackardBell',
        '$1',
    ),
    DeviceParser(
        '; *(Panasonic)[_ ]([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        '$1',
        '$2',
    ),
    DeviceParser(
        '; *(FZ-A1B|JT-B1)(?: Build|\\) AppleWebKit)',
        None,
        'Panasonic $1',
        'Panasonic',
        '$1',
    ),
    DeviceParser(
        '; *(dL1|DL1)(?: Build|\\) AppleWebKit)',
        None,
        'Panasonic $1',
        'Panasonic',
        '$1',
    ),
    DeviceParser(
        '; *(SKY[ _]|)(IM\\-[AT]\\d{3}[^;/]+).* Build/',
        None,
        'Pantech $1$2',
        'Pantech',
        '$1$2',
    ),
    DeviceParser(
        '; *((?:ADR8995|ADR910L|ADR930L|ADR930VW|PTL21|P8000)(?: 4G|)) Build/',
        None,
        '$1',
        'Pantech',
        '$1',
    ),
    DeviceParser(
        '; *Pantech([^;/]+).* Build/',
        None,
        'Pantech $1',
        'Pantech',
        '$1',
    ),
    DeviceParser(
        '; *(papyre)[ _\\-]([^;/]+?)(?: Build|\\) AppleWebKit)',
        'i',
        '$1 $2',
        'Papyre',
        '$2',
    ),
    DeviceParser(
        '; *(?:Touchlet )?(X10\\.[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Pearl $1',
        'Pearl',
        '$1',
    ),
    DeviceParser(
        '; PHICOMM (i800)(?: Build|\\) AppleWebKit)',
        None,
        'Phicomm $1',
        'Phicomm',
        '$1',
    ),
    DeviceParser(
        '; PHICOMM ([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Phicomm $1',
        'Phicomm',
        '$1',
    ),
    DeviceParser(
        '; *(FWS\\d{3}[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Phicomm $1',
        'Phicomm',
        '$1',
    ),
    DeviceParser(
        '; *(D633|D822|D833|T539|T939|V726|W335|W336|W337|W3568|W536|W5510|W626|W632|W6350|W6360|W6500|W732|W736|W737|W7376|W820|W832|W8355|W8500|W8510|W930)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Philips',
        '$1',
    ),
    DeviceParser(
        '; *(?:Philips|PHILIPS)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Philips $1',
        'Philips',
        '$1',
    ),
    DeviceParser(
        'Android 4\\..*; *(M[12356789]|U[12368]|S[123])\\ ?(pro)?(?: Build|\\) AppleWebKit)',
        None,
        'Pipo $1$2',
        'Pipo',
        '$1$2',
    ),
    DeviceParser(
        '; *(MOMO[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Ployer',
        '$1',
    ),
    DeviceParser(
        '; *(?:Polaroid[ _]|)((?:MIDC\\d{3,}|PMID\\d{2,}|PTAB\\d{3,})[^;/]*?)(\\/[^;/]*|)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Polaroid',
        '$1',
    ),
    DeviceParser(
        '; *(?:Polaroid )(Tablet)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Polaroid',
        '$1',
    ),
    DeviceParser(
        '; *(POMP)[ _\\-](.+?) *(?:Build|[;/\\)])',
        None,
        '$1 $2',
        'Pomp',
        '$2',
    ),
    DeviceParser(
        '; *(TB07STA|TB10STA|TB07FTA|TB10FTA)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Positivo',
        '$1',
    ),
    DeviceParser(
        '; *(?:Positivo |)((?:YPY|Ypy)[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Positivo',
        '$1',
    ),
    DeviceParser(
        '; *(MOB-[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'POV',
        '$1',
    ),
    DeviceParser(
        '; *POV[ _\\-]([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'POV $1',
        'POV',
        '$1',
    ),
    DeviceParser(
        '; *((?:TAB-PLAYTAB|TAB-PROTAB|PROTAB|PlayTabPro|Mobii[ _\\-]|TAB-P)[^;/]*)(?: Build|\\) AppleWebKit)',
        None,
        'POV $1',
        'POV',
        '$1',
    ),
    DeviceParser(
        '; *(?:Prestigio |)((?:PAP|PMP)\\d[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Prestigio $1',
        'Prestigio',
        '$1',
    ),
    DeviceParser(
        '; *(PLT[0-9]{4}.*?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Proscan',
        '$1',
    ),
    DeviceParser(
        '; *(A2|A5|A8|A900)_?(Classic|)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'Qmobile',
        '$1 $2',
    ),
    DeviceParser(
        '; *(Q[Mm]obile)_([^_]+)_([^_]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Qmobile $2 $3',
        'Qmobile',
        '$2 $3',
    ),
    DeviceParser(
        '; *(Q\\-?[Mm]obile)[_ ](A[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Qmobile $2',
        'Qmobile',
        '$2',
    ),
    DeviceParser(
        '; *(Q\\-Smart)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'Qmobilevn',
        '$2',
    ),
    DeviceParser(
        '; *(Q\\-?[Mm]obile)[ _\\-](S[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'Qmobilevn',
        '$2',
    ),
    DeviceParser(
        '; *(TA1013)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Quanta',
        '$1',
    ),
    DeviceParser(
        '; (RCT\\w+)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'RCA',
        '$1',
    ),
    DeviceParser(
        '; RCA (\\w+)(?: Build|\\) AppleWebKit)',
        None,
        'RCA $1',
        'RCA',
        '$1',
    ),
    DeviceParser(
        '; *(RK\\d+),?(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Rockchip',
        '$1',
    ),
    DeviceParser(
        ' Build/(RK\\d+)',
        None,
        '$1',
        'Rockchip',
        '$1',
    ),
    DeviceParser(
        '; *(SAMSUNG |Samsung |)((?:Galaxy (?:Note II|S\\d)|GT-I9082|GT-I9205|GT-N7\\d{3}|SM-N9005)[^;/]*)\\/?[^;/]* Build/',
        None,
        'Samsung $1$2',
        'Samsung',
        '$2',
    ),
    DeviceParser(
        '; *(Google |)(Nexus [Ss](?: 4G|)) Build/',
        None,
        'Samsung $1$2',
        'Samsung',
        '$2',
    ),
    DeviceParser(
        '; *(SAMSUNG |Samsung )([^\\/]*)\\/[^ ]* Build/',
        None,
        'Samsung $2',
        'Samsung',
        '$2',
    ),
    DeviceParser(
        '; *(Galaxy(?: Ace| Nexus| S ?II+|Nexus S| with MCR 1.2| Mini Plus 4G|)) Build/',
        None,
        'Samsung $1',
        'Samsung',
        '$1',
    ),
    DeviceParser(
        '; *(SAMSUNG[ _\\-]|)(?:SAMSUNG[ _\\-])([^;/]+) Build',
        None,
        'Samsung $2',
        'Samsung',
        '$2',
    ),
    DeviceParser(
        '; *(SAMSUNG-|)(GT\\-[BINPS]\\d{4}[^\\/]*)(\\/[^ ]*) Build',
        None,
        'Samsung $1$2$3',
        'Samsung',
        '$2',
    ),
    DeviceParser(
        '(?:; *|^)((?:GT\\-[BIiNPS]\\d{4}|I9\\d{2}0[A-Za-z\\+]?\\b)[^;/\\)]*?)(?:Build|Linux|MIUI|[;/\\)])',
        None,
        'Samsung $1',
        'Samsung',
        '$1',
    ),
    DeviceParser(
        '; (SAMSUNG-)([A-Za-z0-9\\-]+).* Build/',
        None,
        'Samsung $1$2',
        'Samsung',
        '$2',
    ),
    DeviceParser(
        '; *((?:SCH|SGH|SHV|SHW|SPH|SC|SM)\\-[A-Za-z0-9 ]+)(/?[^ ]*|) Build',
        None,
        'Samsung $1',
        'Samsung',
        '$1',
    ),
    DeviceParser(
        '; *((?:SC)\\-[A-Za-z0-9 ]+)(/?[^ ]*|)\\)',
        None,
        'Samsung $1',
        'Samsung',
        '$1',
    ),
    DeviceParser(
        ' ((?:SCH)\\-[A-Za-z0-9 ]+)(/?[^ ]*|) Build',
        None,
        'Samsung $1',
        'Samsung',
        '$1',
    ),
    DeviceParser(
        '; *(Behold ?(?:2|II)|YP\\-G[^;/]+|EK-GC100|SCL21|I9300) Build',
        None,
        'Samsung $1',
        'Samsung',
        '$1',
    ),
    DeviceParser(
        '; *((?:SCH|SGH|SHV|SHW|SPH|SC|SM)\\-[A-Za-z0-9]{5,6})[\\)]',
        None,
        'Samsung $1',
        'Samsung',
        '$1',
    ),
    DeviceParser(
        '; *(SH\\-?\\d\\d[^;/]+|SBM\\d[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Sharp',
        '$1',
    ),
    DeviceParser(
        '; *(SHARP[ -])([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1$2',
        'Sharp',
        '$2',
    ),
    DeviceParser(
        '; *(SPX[_\\-]\\d[^;/]*)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Simvalley',
        '$1',
    ),
    DeviceParser(
        '; *(SX7\\-PEARL\\.GmbH)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Simvalley',
        '$1',
    ),
    DeviceParser(
        '; *(SP[T]?\\-\\d{2}[^;/]*)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Simvalley',
        '$1',
    ),
    DeviceParser(
        '; *(SK\\-.*?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'SKtelesys',
        '$1',
    ),
    DeviceParser(
        '; *(?:SKYTEX|SX)-([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Skytex',
        '$1',
    ),
    DeviceParser(
        '; *(IMAGINE [^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Skytex',
        '$1',
    ),
    DeviceParser(
        '; *(SmartQ) ?([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        '$1',
        '$2',
    ),
    DeviceParser(
        '; *(WF7C|WF10C|SBT[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Smartbitt',
        '$1',
    ),
    DeviceParser(
        '; *(SBM(?:003SH|005SH|006SH|007SH|102SH)) Build',
        None,
        '$1',
        'Sharp',
        '$1',
    ),
    DeviceParser(
        '; *(003P|101P|101P11C|102P) Build',
        None,
        '$1',
        'Panasonic',
        '$1',
    ),
    DeviceParser(
        '; *(00\\dZ) Build/',
        None,
        '$1',
        'ZTE',
        '$1',
    ),
    DeviceParser(
        '; HTC(X06HT) Build',
        None,
        '$1',
        'HTC',
        '$1',
    ),
    DeviceParser(
        '; *(001HT|X06HT) Build',
        None,
        '$1',
        'HTC',
        '$1',
    ),
    DeviceParser(
        '; *(201M) Build',
        None,
        '$1',
        'Motorola',
        'XT902',
    ),
    DeviceParser(
        '; *(ST\\d{4}.*)Build/ST',
        None,
        'Trekstor $1',
        'Trekstor',
        '$1',
    ),
    DeviceParser(
        '; *(ST\\d{4}.*?)(?: Build|\\) AppleWebKit)',
        None,
        'Trekstor $1',
        'Trekstor',
        '$1',
    ),
    DeviceParser(
        '; *(Sony ?Ericsson ?)([^;/]+) Build',
        None,
        '$1$2',
        'SonyEricsson',
        '$2',
    ),
    DeviceParser(
        '; *((?:SK|ST|E|X|LT|MK|MT|WT)\\d{2}[a-z0-9]*(?:-o|)|R800i|U20i) Build',
        None,
        '$1',
        'SonyEricsson',
        '$1',
    ),
    DeviceParser(
        '; *(Xperia (?:A8|Arc|Acro|Active|Live with Walkman|Mini|Neo|Play|Pro|Ray|X\\d+)[^;/]*) Build',
        'i',
        '$1',
        'SonyEricsson',
        '$1',
    ),
    DeviceParser(
        '; Sony (Tablet[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Sony $1',
        'Sony',
        '$1',
    ),
    DeviceParser(
        '; Sony ([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Sony $1',
        'Sony',
        '$1',
    ),
    DeviceParser(
        '; *(Sony)([A-Za-z0-9\\-]+)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        '$1',
        '$2',
    ),
    DeviceParser(
        '; *(Xperia [^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Sony',
        '$1',
    ),
    DeviceParser(
        '; *(C(?:1[0-9]|2[0-9]|53|55|6[0-9])[0-9]{2}|D[25]\\d{3}|D6[56]\\d{2})(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Sony',
        '$1',
    ),
    DeviceParser(
        '; *(SGP\\d{3}|SGPT\\d{2})(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Sony',
        '$1',
    ),
    DeviceParser(
        '; *(NW-Z1000Series)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Sony',
        '$1',
    ),
    DeviceParser(
        'PLAYSTATION 3',
        None,
        'PlayStation 3',
        'Sony',
        'PlayStation 3',
    ),
    DeviceParser(
        '(PlayStation (?:Portable|Vita|\\d+))',
        None,
        '$1',
        'Sony',
        '$1',
    ),
    DeviceParser(
        '; *((?:CSL_Spice|Spice|SPICE|CSL)[ _\\-]?|)([Mm][Ii])([ _\\-]|)(\\d{3}[^;/]*)(?: Build|\\) AppleWebKit)',
        None,
        '$1$2$3$4',
        'Spice',
        'Mi$4',
    ),
    DeviceParser(
        '; *(Sprint )(.+?) *(?:Build|[;/])',
        None,
        '$1$2',
        'Sprint',
        '$2',
    ),
    DeviceParser(
        '\\b(Sprint)[: ]([^;,/ ]+)',
        None,
        '$1$2',
        'Sprint',
        '$2',
    ),
    DeviceParser(
        '; *(TAGI[ ]?)(MID) ?([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1$2$3',
        'Tagi',
        '$2$3',
    ),
    DeviceParser(
        '; *(Oyster500|Opal 800)(?: Build|\\) AppleWebKit)',
        None,
        'Tecmobile $1',
        'Tecmobile',
        '$1',
    ),
    DeviceParser(
        '; *(TECNO[ _])([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1$2',
        'Tecno',
        '$2',
    ),
    DeviceParser(
        '; *Android for (Telechips|Techvision) ([^ ]+) ',
        'i',
        '$1 $2',
        '$1',
        '$2',
    ),
    DeviceParser(
        '; *(T-Hub2)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Telstra',
        '$1',
    ),
    DeviceParser(
        '; *(PAD) ?(100[12])(?: Build|\\) AppleWebKit)',
        None,
        'Terra $1$2',
        'Terra',
        '$1$2',
    ),
    DeviceParser(
        '; *(T[BM]-\\d{3}[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Texet',
        '$1',
    ),
    DeviceParser(
        '; *(tolino [^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Thalia',
        '$1',
    ),
    DeviceParser(
        '; *Build/.* (TOLINO_BROWSER)',
        None,
        '$1',
        'Thalia',
        'Tolino Shine',
    ),
    DeviceParser(
        '; *(?:CJ[ -])?(ThL|THL)[ -]([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'Thl',
        '$2',
    ),
    DeviceParser(
        '; *(T100|T200|T5|W100|W200|W8s)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Thl',
        '$1',
    ),
    DeviceParser(
        '; *(T-Mobile[ _]G2[ _]Touch) Build',
        None,
        '$1',
        'HTC',
        'Hero',
    ),
    DeviceParser(
        '; *(T-Mobile[ _]G2) Build',
        None,
        '$1',
        'HTC',
        'Desire Z',
    ),
    DeviceParser(
        '; *(T-Mobile myTouch Q) Build',
        None,
        '$1',
        'Huawei',
        'U8730',
    ),
    DeviceParser(
        '; *(T-Mobile myTouch) Build',
        None,
        '$1',
        'Huawei',
        'U8680',
    ),
    DeviceParser(
        '; *(T-Mobile_Espresso) Build',
        None,
        '$1',
        'HTC',
        'Espresso',
    ),
    DeviceParser(
        '; *(T-Mobile G1) Build',
        None,
        '$1',
        'HTC',
        'Dream',
    ),
    DeviceParser(
        '\\b(T-Mobile ?|)(myTouch)[ _]?([34]G)[ _]?([^\\/]*) (?:Mozilla|Build)',
        None,
        '$1$2 $3 $4',
        'HTC',
        '$2 $3 $4',
    ),
    DeviceParser(
        '\\b(T-Mobile)_([^_]+)_(.*) Build',
        None,
        '$1 $2 $3',
        'Tmobile',
        '$2 $3',
    ),
    DeviceParser(
        '\\b(T-Mobile)[_ ]?(.*?)Build',
        None,
        '$1 $2',
        'Tmobile',
        '$2',
    ),
    DeviceParser(
        ' (ATP[0-9]{4})(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Tomtec',
        '$1',
    ),
    DeviceParser(
        ' *(TOOKY)[ _\\-]([^;/]+?) ?(?:Build|;)',
        'i',
        '$1 $2',
        'Tooky',
        '$2',
    ),
    DeviceParser(
        '\\b(TOSHIBA_AC_AND_AZ|TOSHIBA_FOLIO_AND_A|FOLIO_AND_A)',
        None,
        '$1',
        'Toshiba',
        'Folio 100',
    ),
    DeviceParser(
        '; *([Ff]olio ?100)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Toshiba',
        'Folio 100',
    ),
    DeviceParser(
        '; *(AT[0-9]{2,3}(?:\\-A|LE\\-A|PE\\-A|SE|a|)|AT7-A|AT1S0|Hikari-iFrame/WDPF-[^;/]+|THRiVE|Thrive)(?: Build|\\) AppleWebKit)',
        None,
        'Toshiba $1',
        'Toshiba',
        '$1',
    ),
    DeviceParser(
        '; *(TM-MID\\d+[^;/]+|TOUCHMATE|MID-750)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Touchmate',
        '$1',
    ),
    DeviceParser(
        '; *(TM-SM\\d+[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Touchmate',
        '$1',
    ),
    DeviceParser(
        '; *(A10 [Bb]asic2?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Treq',
        '$1',
    ),
    DeviceParser(
        '; *(TREQ[ _\\-])([^;/]+?)(?: Build|\\) AppleWebKit)',
        'i',
        '$1$2',
        'Treq',
        '$2',
    ),
    DeviceParser(
        '; *(X-?5|X-?3)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Umeox',
        '$1',
    ),
    DeviceParser(
        '; *(A502\\+?|A936|A603|X1|X2)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Umeox',
        '$1',
    ),
    DeviceParser(
        '(TOUCH(?:TAB|PAD).+?)(?: Build|\\) AppleWebKit)',
        'i',
        'Versus $1',
        'Versus',
        '$1',
    ),
    DeviceParser(
        '(VERTU) ([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'Vertu',
        '$2',
    ),
    DeviceParser(
        '; *(Videocon)[ _\\-]([^;/]+?) *(?:Build|;)',
        None,
        '$1 $2',
        'Videocon',
        '$2',
    ),
    DeviceParser(
        ' (VT\\d{2}[A-Za-z]*)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Videocon',
        '$1',
    ),
    DeviceParser(
        '; *((?:ViewPad|ViewPhone|VSD)[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Viewsonic',
        '$1',
    ),
    DeviceParser(
        '; *(ViewSonic-)([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1$2',
        'Viewsonic',
        '$2',
    ),
    DeviceParser(
        '; *(GTablet.*?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Viewsonic',
        '$1',
    ),
    DeviceParser(
        '; *([Vv]ivo)[ _]([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'vivo',
        '$2',
    ),
    DeviceParser(
        '(Vodafone) (.*?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        '$1',
        '$2',
    ),
    DeviceParser(
        '; *(?:Walton[ _\\-]|)(Primo[ _\\-][^;/]+?)(?: Build|\\) AppleWebKit)',
        'i',
        'Walton $1',
        'Walton',
        '$1',
    ),
    DeviceParser(
        '; *(?:WIKO[ \\-]|)(CINK\\+?|BARRY|BLOOM|DARKFULL|DARKMOON|DARKNIGHT|DARKSIDE|FIZZ|HIGHWAY|IGGY|OZZY|RAINBOW|STAIRWAY|SUBLIM|WAX|CINK [^;/]+?)(?: Build|\\) AppleWebKit)',
        'i',
        'Wiko $1',
        'Wiko',
        '$1',
    ),
    DeviceParser(
        '; *WellcoM-([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Wellcom $1',
        'Wellcom',
        '$1',
    ),
    DeviceParser(
        '(?:(WeTab)-Browser|; (wetab) Build)',
        None,
        '$1',
        'WeTab',
        'WeTab',
    ),
    DeviceParser(
        '; *(AT-AS[^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Wolfgang $1',
        'Wolfgang',
        '$1',
    ),
    DeviceParser(
        '; *(?:Woxter|Wxt) ([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'Woxter $1',
        'Woxter',
        '$1',
    ),
    DeviceParser(
        '; *(?:Xenta |Luna |)(TAB[234][0-9]{2}|TAB0[78]-\\d{3}|TAB0?9-\\d{3}|TAB1[03]-\\d{3}|SMP\\d{2}-\\d{3})(?: Build|\\) AppleWebKit)',
        None,
        'Yarvik $1',
        'Yarvik',
        '$1',
    ),
    DeviceParser(
        '; *([A-Z]{2,4})(M\\d{3,}[A-Z]{2})([^;\\)\\/]*)(?: Build|[;\\)])',
        None,
        'Yifang $1$2$3',
        'Yifang',
        '$2',
    ),
    DeviceParser(
        '; *((Mi|MI|HM|MI-ONE|Redmi)[ -](NOTE |Note |)[^;/]*) (Build|MIUI)/',
        None,
        'XiaoMi $1',
        'XiaoMi',
        '$1',
    ),
    DeviceParser(
        '; *((Mi|MI|HM|MI-ONE|Redmi)[ -](NOTE |Note |)[^;/\\)]*)',
        None,
        'XiaoMi $1',
        'XiaoMi',
        '$1',
    ),
    DeviceParser(
        '; *(MIX) (Build|MIUI)/',
        None,
        'XiaoMi $1',
        'XiaoMi',
        '$1',
    ),
    DeviceParser(
        '; *((MIX) ([^;/]*)) (Build|MIUI)/',
        None,
        'XiaoMi $1',
        'XiaoMi',
        '$1',
    ),
    DeviceParser(
        '; *XOLO[ _]([^;/]*tab.*)(?: Build|\\) AppleWebKit)',
        'i',
        'Xolo $1',
        'Xolo',
        '$1',
    ),
    DeviceParser(
        '; *XOLO[ _]([^;/]+?)(?: Build|\\) AppleWebKit)',
        'i',
        'Xolo $1',
        'Xolo',
        '$1',
    ),
    DeviceParser(
        '; *(q\\d0{2,3}[a-z]?)(?: Build|\\) AppleWebKit)',
        'i',
        'Xolo $1',
        'Xolo',
        '$1',
    ),
    DeviceParser(
        '; *(PAD ?[79]\\d+[^;/]*|TelePAD\\d+[^;/])(?: Build|\\) AppleWebKit)',
        None,
        'Xoro $1',
        'Xoro',
        '$1',
    ),
    DeviceParser(
        '; *(?:(?:ZOPO|Zopo)[ _]([^;/]+?)|(ZP ?(?:\\d{2}[^;/]+|C2))|(C[2379]))(?: Build|\\) AppleWebKit)',
        None,
        '$1$2$3',
        'Zopo',
        '$1$2$3',
    ),
    DeviceParser(
        '; *(ZiiLABS) (Zii[^;/]*)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'ZiiLabs',
        '$2',
    ),
    DeviceParser(
        '; *(Zii)_([^;/]*)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'ZiiLabs',
        '$2',
    ),
    DeviceParser(
        '; *(ARIZONA|(?:ATLAS|Atlas) W|D930|Grand (?:[SX][^;]*?|Era|Memo[^;]*?)|JOE|(?:Kis|KIS)\\b[^;]*?|Libra|Light [^;]*?|N8[056][01]|N850L|N8000|N9[15]\\d{2}|N9810|NX501|Optik|(?:Vip )Racer[^;]*?|RacerII|RACERII|San Francisco[^;]*?|V9[AC]|V55|V881|Z[679][0-9]{2}[A-z]?)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'ZTE',
        '$1',
    ),
    DeviceParser(
        '; *([A-Z]\\d+)_USA_[^;]*(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'ZTE',
        '$1',
    ),
    DeviceParser(
        '; *(SmartTab\\d+)[^;]*(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'ZTE',
        '$1',
    ),
    DeviceParser(
        '; *(?:Blade|BLADE|ZTE-BLADE)([^;/]*)(?: Build|\\) AppleWebKit)',
        None,
        'ZTE Blade$1',
        'ZTE',
        'Blade$1',
    ),
    DeviceParser(
        '; *(?:Skate|SKATE|ZTE-SKATE)([^;/]*)(?: Build|\\) AppleWebKit)',
        None,
        'ZTE Skate$1',
        'ZTE',
        'Skate$1',
    ),
    DeviceParser(
        '; *(Orange |Optimus )(Monte Carlo|San Francisco)(?: Build|\\) AppleWebKit)',
        None,
        '$1$2',
        'ZTE',
        '$1$2',
    ),
    DeviceParser(
        '; *(?:ZXY-ZTE_|ZTE\\-U |ZTE[\\- _]|ZTE-C[_ ])([^;/]+?)(?: Build|\\) AppleWebKit)',
        None,
        'ZTE $1',
        'ZTE',
        '$1',
    ),
    DeviceParser(
        '; (BASE) (lutea|Lutea 2|Tab[^;]*?)(?: Build|\\) AppleWebKit)',
        None,
        '$1 $2',
        'ZTE',
        '$1 $2',
    ),
    DeviceParser(
        '; (Avea inTouch 2|soft stone|tmn smart a7|Movistar[ _]Link)(?: Build|\\) AppleWebKit)',
        'i',
        '$1',
        'ZTE',
        '$1',
    ),
    DeviceParser(
        '; *(vp9plus)\\)',
        None,
        '$1',
        'ZTE',
        '$1',
    ),
    DeviceParser(
        '; ?(Cloud[ _]Z5|z1000|Z99 2G|z99|z930|z999|z990|z909|Z919|z900)(?: Build|\\) AppleWebKit)',
        None,
        '$1',
        'Zync',
        '$1',
    ),
    DeviceParser(
        '; ?(KFOT|Kindle Fire) Build\\b',
        None,
        'Kindle Fire',
        'Amazon',
        'Kindle Fire',
    ),
    DeviceParser(
        '; ?(KFOTE|Amazon Kindle Fire2) Build\\b',
        None,
        'Kindle Fire 2',
        'Amazon',
        'Kindle Fire 2',
    ),
    DeviceParser(
        '; ?(KFTT) Build\\b',
        None,
        'Kindle Fire HD',
        'Amazon',
        'Kindle Fire HD 7"',
    ),
    DeviceParser(
        '; ?(KFJWI) Build\\b',
        None,
        'Kindle Fire HD 8.9" WiFi',
        'Amazon',
        'Kindle Fire HD 8.9" WiFi',
    ),
    DeviceParser(
        '; ?(KFJWA) Build\\b',
        None,
        'Kindle Fire HD 8.9" 4G',
        'Amazon',
        'Kindle Fire HD 8.9" 4G',
    ),
    DeviceParser(
        '; ?(KFSOWI) Build\\b',
        None,
        'Kindle Fire HD 7" WiFi',
        'Amazon',
        'Kindle Fire HD 7" WiFi',
    ),
    DeviceParser(
        '; ?(KFTHWI) Build\\b',
        None,
        'Kindle Fire HDX 7" WiFi',
        'Amazon',
        'Kindle Fire HDX 7" WiFi',
    ),
    DeviceParser(
        '; ?(KFTHWA) Build\\b',
        None,
        'Kindle Fire HDX 7" 4G',
        'Amazon',
        'Kindle Fire HDX 7" 4G',
    ),
    DeviceParser(
        '; ?(KFAPWI) Build\\b',
        None,
        'Kindle Fire HDX 8.9" WiFi',
        'Amazon',
        'Kindle Fire HDX 8.9" WiFi',
    ),
    DeviceParser(
        '; ?(KFAPWA) Build\\b',
        None,
        'Kindle Fire HDX 8.9" 4G',
        'Amazon',
        'Kindle Fire HDX 8.9" 4G',
    ),
    DeviceParser(
        '; ?Amazon ([^;/]+) Build\\b',
        None,
        '$1',
        'Amazon',
        '$1',
    ),
    DeviceParser(
        '; ?(Kindle) Build\\b',
        None,
        'Kindle',
        'Amazon',
        'Kindle',
    ),
    DeviceParser(
        '; ?(Silk)/(\\d+)\\.(\\d+)(?:\\.([0-9\\-]+)|) Build\\b',
        None,
        'Kindle Fire',
        'Amazon',
        'Kindle Fire$2',
    ),
    DeviceParser(
        ' (Kindle)/(\\d+\\.\\d+)',
        None,
        'Kindle',
        'Amazon',
        '$1 $2',
    ),
    DeviceParser(
        ' (Silk|Kindle)/(\\d+)\\.',
        None,
        'Kindle',
        'Amazon',
        'Kindle',
    ),
    DeviceParser(
        '(sprd)\\-([^/]+)/',
        None,
        '$1 $2',
        '$1',
        '$2',
    ),
    DeviceParser(
        '; *(H\\d{2}00\\+?) Build',
        None,
        '$1',
        'Hero',
        '$1',
    ),
    DeviceParser(
        '; *(iphone|iPhone5) Build/',
        None,
        'Xianghe $1',
        'Xianghe',
        '$1',
    ),
    DeviceParser(
        '; *(e\\d{4}[a-z]?_?v\\d+|v89_[^;/]+)[^;/]+ Build/',
        None,
        'Xianghe $1',
        'Xianghe',
        '$1',
    ),
    DeviceParser(
        '\\bUSCC[_\\-]?([^ ;/\\)]+)',
        None,
        '$1',
        'Cellular',
        '$1',
    ),
    DeviceParser(
        'Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:ALCATEL)[^;]*; *([^;,\\)]+)',
        None,
        'Alcatel $1',
        'Alcatel',
        '$1',
    ),
    DeviceParser(
        'Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?|)(?:ASUS|Asus)[^;]*; *([^;,\\)]+)',
        None,
        'Asus $1',
        'Asus',
        '$1',
    ),
    DeviceParser(
        'Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:DELL|Dell)[^;]*; *([^;,\\)]+)',
        None,
        'Dell $1',
        'Dell',
        '$1',
    ),
    DeviceParser(
        'Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?|)(?:HTC|Htc|HTC_blocked[^;]*)[^;]*; *(?:HTC|)([^;,\\)]+)',
        None,
        'HTC $1',
        'HTC',
        '$1',
    ),
    DeviceParser(
        'Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:HUAWEI)[^;]*; *(?:HUAWEI |)([^;,\\)]+)',
        None,
        'Huawei $1',
        'Huawei',
        '$1',
    ),
    DeviceParser(
        'Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:LG|Lg)[^;]*; *(?:LG[ \\-]|)([^;,\\)]+)',
        None,
        'LG $1',
        'LG',
        '$1',
    ),
    DeviceParser(
        'Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:rv:11; |)(?:NOKIA|Nokia)[^;]*; *(?:NOKIA ?|Nokia ?|LUMIA ?|[Ll]umia ?|)(\\d{3,10}[^;\\)]*)',
        None,
        'Lumia $1',
        'Nokia',
        'Lumia $1',
    ),
    DeviceParser(
        'Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:NOKIA|Nokia)[^;]*; *(RM-\\d{3,})',
        None,
        'Nokia $1',
        'Nokia',
        '$1',
    ),
    DeviceParser(
        '(?:Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)]|WPDesktop;) ?(?:ARM; ?Touch; ?|Touch; ?|)(?:NOKIA|Nokia)[^;]*; *(?:NOKIA ?|Nokia ?|LUMIA ?|[Ll]umia ?|)([^;\\)]+)',
        None,
        'Nokia $1',
        'Nokia',
        '$1',
    ),
    DeviceParser(
        'Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:Microsoft(?: Corporation|))[^;]*; *([^;,\\)]+)',
        None,
        'Microsoft $1',
        'Microsoft',
        '$1',
    ),
    DeviceParser(
        'Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?|)(?:SAMSUNG)[^;]*; *(?:SAMSUNG |)([^;,\\.\\)]+)',
        None,
        'Samsung $1',
        'Samsung',
        '$1',
    ),
    DeviceParser(
        'Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?|)(?:TOSHIBA|FujitsuToshibaMobileCommun)[^;]*; *([^;,\\)]+)',
        None,
        'Toshiba $1',
        'Toshiba',
        '$1',
    ),
    DeviceParser(
        'Windows Phone [^;]+; .*?IEMobile/[^;\\)]+[;\\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?|)([^;]+); *([^;,\\)]+)',
        None,
        '$1 $2',
        '$1',
        '$2',
    ),
    DeviceParser(
        '(?:^|; )SAMSUNG\\-([A-Za-z0-9\\-]+).* Bada/',
        None,
        'Samsung $1',
        'Samsung',
        '$1',
    ),
    DeviceParser(
        '\\(Mobile; ALCATEL ?(One|ONE) ?(Touch|TOUCH) ?([^;/]+?)(?:/[^;]+|); rv:[^\\)]+\\) Gecko/[^\\/]+ Firefox/',
        None,
        'Alcatel $1 $2 $3',
        'Alcatel',
        'One Touch $3',
    ),
    DeviceParser(
        '\\(Mobile; (?:ZTE([^;]+)|(OpenC)); rv:[^\\)]+\\) Gecko/[^\\/]+ Firefox/',
        None,
        'ZTE $1$2',
        'ZTE',
        '$1$2',
    ),
    DeviceParser(
        '\\(Mobile; ALCATEL([A-Za-z0-9\\-]+); rv:[^\\)]+\\) Gecko/[^\\/]+ Firefox/[^\\/]+ KaiOS/',
        None,
        'Alcatel $1',
        'Alcatel',
        '$1',
    ),
    DeviceParser(
        '\\(Mobile; LYF\\/([A-Za-z0-9\\-]+)\\/.+;.+rv:[^\\)]+\\) Gecko/[^\\/]+ Firefox/[^\\/]+ KAIOS/',
        None,
        'LYF $1',
        'LYF',
        '$1',
    ),
    DeviceParser(
        '\\(Mobile; Nokia_([A-Za-z0-9\\-]+)_.+; rv:[^\\)]+\\) Gecko/[^\\/]+ Firefox/[^\\/]+ KAIOS/',
        None,
        'Nokia $1',
        'Nokia',
        '$1',
    ),
    DeviceParser(
        'Nokia(N[0-9]+)([A-Za-z_\\-][A-Za-z0-9_\\-]*)',
        None,
        'Nokia $1',
        'Nokia',
        '$1$2',
    ),
    DeviceParser(
        '(?:NOKIA|Nokia)(?:\\-| *)(?:([A-Za-z0-9]+)\\-[0-9a-f]{32}|([A-Za-z0-9\\-]+)(?:UCBrowser)|([A-Za-z0-9\\-]+))',
        None,
        'Nokia $1$2$3',
        'Nokia',
        '$1$2$3',
    ),
    DeviceParser(
        'Lumia ([A-Za-z0-9\\-]+)',
        None,
        'Lumia $1',
        'Nokia',
        'Lumia $1',
    ),
    DeviceParser(
        '\\(Symbian; U; S60 V5; [A-z]{2}\\-[A-z]{2}; (SonyEricsson|Samsung|Nokia|LG)([^;/]+?)\\)',
        None,
        '$1 $2',
        '$1',
        '$2',
    ),
    DeviceParser(
        '\\(Symbian(?:/3|); U; ([^;]+);',
        None,
        'Nokia $1',
        'Nokia',
        '$1',
    ),
    DeviceParser(
        'BB10; ([A-Za-z0-9\\- ]+)\\)',
        None,
        'BlackBerry $1',
        'BlackBerry',
        '$1',
    ),
    DeviceParser(
        'Play[Bb]ook.+RIM Tablet OS',
        None,
        'BlackBerry Playbook',
        'BlackBerry',
        'Playbook',
    ),
    DeviceParser(
        'Black[Bb]erry ([0-9]+);',
        None,
        'BlackBerry $1',
        'BlackBerry',
        '$1',
    ),
    DeviceParser(
        'Black[Bb]erry([0-9]+)',
        None,
        'BlackBerry $1',
        'BlackBerry',
        '$1',
    ),
    DeviceParser(
        'Black[Bb]erry;',
        None,
        'BlackBerry',
        'BlackBerry',
        None,
    ),
    DeviceParser(
        '(Pre|Pixi)/\\d+\\.\\d+',
        None,
        'Palm $1',
        'Palm',
        '$1',
    ),
    DeviceParser(
        'Palm([0-9]+)',
        None,
        'Palm $1',
        'Palm',
        '$1',
    ),
    DeviceParser(
        'Treo([A-Za-z0-9]+)',
        None,
        'Palm Treo $1',
        'Palm',
        'Treo $1',
    ),
    DeviceParser(
        'webOS.*(P160U(?:NA|))/(\\d+).(\\d+)',
        None,
        'HP Veer',
        'HP',
        'Veer',
    ),
    DeviceParser(
        '(Touch[Pp]ad)/\\d+\\.\\d+',
        None,
        'HP TouchPad',
        'HP',
        'TouchPad',
    ),
    DeviceParser(
        'HPiPAQ([A-Za-z0-9]+)/\\d+.\\d+',
        None,
        'HP iPAQ $1',
        'HP',
        'iPAQ $1',
    ),
    DeviceParser(
        'PDA; (PalmOS)/sony/model ([a-z]+)/Revision',
        None,
        '$1',
        'Sony',
        '$1 $2',
    ),
    DeviceParser(
        '(Apple\\s?TV)',
        None,
        'AppleTV',
        'Apple',
        'AppleTV',
    ),
    DeviceParser(
        '(QtCarBrowser)',
        None,
        'Tesla Model S',
        'Tesla',
        'Model S',
    ),
    DeviceParser(
        '(iPhone|iPad|iPod)(\\d+,\\d+)',
        None,
        '$1',
        'Apple',
        '$1$2',
    ),
    DeviceParser(
        '(iPad)(?:;| Simulator;)',
        None,
        '$1',
        'Apple',
        '$1',
    ),
    DeviceParser(
        '(iPod)(?:;| touch;| Simulator;)',
        None,
        '$1',
        'Apple',
        '$1',
    ),
    DeviceParser(
        '(iPhone)(?:;| Simulator;)',
        None,
        '$1',
        'Apple',
        '$1',
    ),
    DeviceParser(
        '(Watch)(\\d+,\\d+)',
        None,
        'Apple $1',
        'Apple',
        '$1$2',
    ),
    DeviceParser(
        '(Apple Watch)(?:;| Simulator;)',
        None,
        '$1',
        'Apple',
        '$1',
    ),
    DeviceParser(
        '(HomePod)(?:;| Simulator;)',
        None,
        '$1',
        'Apple',
        '$1',
    ),
    DeviceParser(
        'iPhone',
        None,
        'iPhone',
        'Apple',
        'iPhone',
    ),
    DeviceParser(
        'CFNetwork/.* Darwin/\\d.*\\(((?:Mac|iMac|PowerMac|PowerBook)[^\\d]*)(\\d+)(?:,|%2C)(\\d+)',
        None,
        '$1$2,$3',
        'Apple',
        '$1$2,$3',
    ),
    DeviceParser(
        'CFNetwork/.* Darwin/\\d+\\.\\d+\\.\\d+ \\(x86_64\\)',
        None,
        'Mac',
        'Apple',
        'Mac',
    ),
    DeviceParser(
        'CFNetwork/.* Darwin/\\d',
        None,
        'iOS-Device',
        'Apple',
        'iOS-Device',
    ),
    DeviceParser(
        'Outlook-(iOS)/\\d+\\.\\d+\\.prod\\.iphone',
        None,
        'iPhone',
        'Apple',
        'iPhone',
    ),
    DeviceParser(
        'acer_([A-Za-z0-9]+)_',
        None,
        'Acer $1',
        'Acer',
        '$1',
    ),
    DeviceParser(
        '(?:ALCATEL|Alcatel)-([A-Za-z0-9\\-]+)',
        None,
        'Alcatel $1',
        'Alcatel',
        '$1',
    ),
    DeviceParser(
        '(?:Amoi|AMOI)\\-([A-Za-z0-9]+)',
        None,
        'Amoi $1',
        'Amoi',
        '$1',
    ),
    DeviceParser(
        '(?:; |\\/|^)((?:Transformer (?:Pad|Prime) |Transformer |PadFone[ _]?)[A-Za-z0-9]*)',
        None,
        'Asus $1',
        'Asus',
        '$1',
    ),
    DeviceParser(
        '(?:asus.*?ASUS|Asus|ASUS|asus)[\\- ;]*((?:Transformer (?:Pad|Prime) |Transformer |Padfone |Nexus[ _]|)[A-Za-z0-9]+)',
        None,
        'Asus $1',
        'Asus',
        '$1',
    ),
    DeviceParser(
        '(?:ASUS)_([A-Za-z0-9\\-]+)',
        None,
        'Asus $1',
        'Asus',
        '$1',
    ),
    DeviceParser(
        '\\bBIRD[ \\-\\.]([A-Za-z0-9]+)',
        None,
        'Bird $1',
        'Bird',
        '$1',
    ),
    DeviceParser(
        '\\bDell ([A-Za-z0-9]+)',
        None,
        'Dell $1',
        'Dell',
        '$1',
    ),
    DeviceParser(
        'DoCoMo/2\\.0 ([A-Za-z0-9]+)',
        None,
        'DoCoMo $1',
        'DoCoMo',
        '$1',
    ),
    DeviceParser(
        '([A-Za-z0-9]+)_W;FOMA',
        None,
        'DoCoMo $1',
        'DoCoMo',
        '$1',
    ),
    DeviceParser(
        '([A-Za-z0-9]+);FOMA',
        None,
        'DoCoMo $1',
        'DoCoMo',
        '$1',
    ),
    DeviceParser(
        '\\b(?:HTC/|HTC/[a-z0-9]+/|)HTC[ _\\-;]? *(.*?)(?:-?Mozilla|fingerPrint|[;/\\(\\)]|$)',
        None,
        'HTC $1',
        'HTC',
        '$1',
    ),
    DeviceParser(
        'Huawei([A-Za-z0-9]+)',
        None,
        'Huawei $1',
        'Huawei',
        '$1',
    ),
    DeviceParser(
        'HUAWEI-([A-Za-z0-9]+)',
        None,
        'Huawei $1',
        'Huawei',
        '$1',
    ),
    DeviceParser(
        'HUAWEI ([A-Za-z0-9\\-]+)',
        None,
        'Huawei $1',
        'Huawei',
        '$1',
    ),
    DeviceParser(
        'vodafone([A-Za-z0-9]+)',
        None,
        'Huawei Vodafone $1',
        'Huawei',
        'Vodafone $1',
    ),
    DeviceParser(
        'i\\-mate ([A-Za-z0-9]+)',
        None,
        'i-mate $1',
        'i-mate',
        '$1',
    ),
    DeviceParser(
        'Kyocera\\-([A-Za-z0-9]+)',
        None,
        'Kyocera $1',
        'Kyocera',
        '$1',
    ),
    DeviceParser(
        'KWC\\-([A-Za-z0-9]+)',
        None,
        'Kyocera $1',
        'Kyocera',
        '$1',
    ),
    DeviceParser(
        'Lenovo[_\\-]([A-Za-z0-9]+)',
        None,
        'Lenovo $1',
        'Lenovo',
        '$1',
    ),
    DeviceParser(
        '(HbbTV)/[0-9]+\\.[0-9]+\\.[0-9]+ \\( ?;(LG)E ?;([^;]{0,30})',
        None,
        '$1',
        '$2',
        '$3',
    ),
    DeviceParser(
        '(HbbTV)/1\\.1\\.1.*CE-HTML/1\\.\\d;(Vendor/|)(THOM[^;]*?)[;\\s].{0,30}(LF[^;]+);?',
        None,
        '$1',
        'Thomson',
        '$4',
    ),
    DeviceParser(
        '(HbbTV)(?:/1\\.1\\.1|) ?(?: \\(;;;;;\\)|); *CE-HTML(?:/1\\.\\d|); *([^ ]+) ([^;]+);',
        None,
        '$1',
        '$2',
        '$3',
    ),
    DeviceParser(
        '(HbbTV)/1\\.1\\.1 \\(;;;;;\\) Maple_2011',
        None,
        '$1',
        'Samsung',
        None,
    ),
    DeviceParser(
        '(HbbTV)/[0-9]+\\.[0-9]+\\.[0-9]+ \\([^;]{0,30}; ?(?:CUS:([^;]*)|([^;]+)) ?; ?([^;]{0,30})',
        None,
        '$1',
        '$2$3',
        '$4',
    ),
    DeviceParser(
        '(HbbTV)/[0-9]+\\.[0-9]+\\.[0-9]+',
        None,
        '$1',
        None,
        None,
    ),
    DeviceParser(
        'LGE; (?:Media\\/|)([^;]*);[^;]*;[^;]*;?\\); "?LG NetCast(\\.TV|\\.Media|)-\\d+',
        None,
        'NetCast$2',
        'LG',
        '$1',
    ),
    DeviceParser(
        'InettvBrowser/[0-9]+\\.[0-9A-Z]+ \\([^;]*;(Sony)([^;]*);[^;]*;[^\\)]*\\)',
        None,
        'Inettv',
        '$1',
        '$2',
    ),
    DeviceParser(
        'InettvBrowser/[0-9]+\\.[0-9A-Z]+ \\([^;]*;([^;]*);[^;]*;[^\\)]*\\)',
        None,
        'Inettv',
        'Generic_Inettv',
        '$1',
    ),
    DeviceParser(
        '(?:InettvBrowser|TSBNetTV|NETTV|HBBTV)',
        None,
        'Inettv',
        'Generic_Inettv',
        None,
    ),
    DeviceParser(
        'Series60/\\d\\.\\d (LG)[\\-]?([A-Za-z0-9 \\-]+)',
        None,
        '$1 $2',
        '$1',
        '$2',
    ),
    DeviceParser(
        '\\b(?:LGE[ \\-]LG\\-(?:AX|)|LGE |LGE?-LG|LGE?[ \\-]|LG[ /\\-]|lg[\\-])([A-Za-z0-9]+)\\b',
        None,
        'LG $1',
        'LG',
        '$1',
    ),
    DeviceParser(
        '(?:^LG[\\-]?|^LGE[\\-/]?)([A-Za-z]+[0-9]+[A-Za-z]*)',
        None,
        'LG $1',
        'LG',
        '$1',
    ),
    DeviceParser(
        '^LG([0-9]+[A-Za-z]*)',
        None,
        'LG $1',
        'LG',
        '$1',
    ),
    DeviceParser(
        '(KIN\\.[^ ]+) (\\d+)\\.(\\d+)',
        None,
        'Microsoft $1',
        'Microsoft',
        '$1',
    ),
    DeviceParser(
        '(?:MSIE|XBMC).*\\b(Xbox)\\b',
        None,
        '$1',
        'Microsoft',
        '$1',
    ),
    DeviceParser(
        '; ARM; Trident/6\\.0; Touch[\\);]',
        None,
        'Microsoft Surface RT',
        'Microsoft',
        'Surface RT',
    ),
    DeviceParser(
        'Motorola\\-([A-Za-z0-9]+)',
        None,
        'Motorola $1',
        'Motorola',
        '$1',
    ),
    DeviceParser(
        'MOTO\\-([A-Za-z0-9]+)',
        None,
        'Motorola $1',
        'Motorola',
        '$1',
    ),
    DeviceParser(
        'MOT\\-([A-z0-9][A-z0-9\\-]*)',
        None,
        'Motorola $1',
        'Motorola',
        '$1',
    ),
    DeviceParser(
        'Nintendo WiiU',
        None,
        'Nintendo Wii U',
        'Nintendo',
        'Wii U',
    ),
    DeviceParser(
        'Nintendo (DS|3DS|DSi|Wii);',
        None,
        'Nintendo $1',
        'Nintendo',
        '$1',
    ),
    DeviceParser(
        '(?:Pantech|PANTECH)[ _-]?([A-Za-z0-9\\-]+)',
        None,
        'Pantech $1',
        'Pantech',
        '$1',
    ),
    DeviceParser(
        'Philips([A-Za-z0-9]+)',
        None,
        'Philips $1',
        'Philips',
        '$1',
    ),
    DeviceParser(
        'Philips ([A-Za-z0-9]+)',
        None,
        'Philips $1',
        'Philips',
        '$1',
    ),
    DeviceParser(
        '(SMART-TV); .* Tizen ',
        None,
        'Samsung $1',
        'Samsung',
        '$1',
    ),
    DeviceParser(
        'SymbianOS/9\\.\\d.* Samsung[/\\-]([A-Za-z0-9 \\-]+)',
        None,
        'Samsung $1',
        'Samsung',
        '$1',
    ),
    DeviceParser(
        '(Samsung)(SGH)(i[0-9]+)',
        None,
        '$1 $2$3',
        '$1',
        '$2-$3',
    ),
    DeviceParser(
        'SAMSUNG-ANDROID-MMS/([^;/]+)',
        None,
        '$1',
        'Samsung',
        '$1',
    ),
    DeviceParser(
        'SAMSUNG(?:; |[ -/])([A-Za-z0-9\\-]+)',
        'i',
        'Samsung $1',
        'Samsung',
        '$1',
    ),
    DeviceParser(
        '(Dreamcast)',
        None,
        'Sega $1',
        'Sega',
        '$1',
    ),
    DeviceParser(
        '^SIE-([A-Za-z0-9]+)',
        None,
        'Siemens $1',
        'Siemens',
        '$1',
    ),
    DeviceParser(
        'Softbank/[12]\\.0/([A-Za-z0-9]+)',
        None,
        'Softbank $1',
        'Softbank',
        '$1',
    ),
    DeviceParser(
        'SonyEricsson ?([A-Za-z0-9\\-]+)',
        None,
        'Ericsson $1',
        'SonyEricsson',
        '$1',
    ),
    DeviceParser(
        'Android [^;]+; ([^ ]+) (Sony)/',
        None,
        '$2 $1',
        '$2',
        '$1',
    ),
    DeviceParser(
        '(Sony)(?:BDP\\/|\\/|)([^ /;\\)]+)[ /;\\)]',
        None,
        '$1 $2',
        '$1',
        '$2',
    ),
    DeviceParser(
        'Puffin/[\\d\\.]+IT',
        None,
        'iPad',
        'Apple',
        'iPad',
    ),
    DeviceParser(
        'Puffin/[\\d\\.]+IP',
        None,
        'iPhone',
        'Apple',
        'iPhone',
    ),
    DeviceParser(
        'Puffin/[\\d\\.]+AT',
        None,
        'Generic Tablet',
        'Generic',
        'Tablet',
    ),
    DeviceParser(
        'Puffin/[\\d\\.]+AP',
        None,
        'Generic Smartphone',
        'Generic',
        'Smartphone',
    ),
    DeviceParser(
        'Android[\\- ][\\d]+\\.[\\d]+; [A-Za-z]{2}\\-[A-Za-z]{0,2}; WOWMobile (.+)( Build[/ ]|\\))',
        None,
        None,
        'Generic_Android',
        '$1',
    ),
    DeviceParser(
        'Android[\\- ][\\d]+\\.[\\d]+\\-update1; [A-Za-z]{2}\\-[A-Za-z]{0,2} *; *(.+?)( Build[/ ]|\\))',
        None,
        None,
        'Generic_Android',
        '$1',
    ),
    DeviceParser(
        'Android[\\- ][\\d]+(?:\\.[\\d]+)(?:\\.[\\d]+|); *[A-Za-z]{2}[_\\-][A-Za-z]{0,2}\\-? *; *(.+?)( Build[/ ]|\\))',
        None,
        None,
        'Generic_Android',
        '$1',
    ),
    DeviceParser(
        'Android[\\- ][\\d]+(?:\\.[\\d]+)(?:\\.[\\d]+|); *[A-Za-z]{0,2}\\- *; *(.+?)( Build[/ ]|\\))',
        None,
        None,
        'Generic_Android',
        '$1',
    ),
    DeviceParser(
        'Android[\\- ][\\d]+(?:\\.[\\d]+)(?:\\.[\\d]+|); *[a-z]{0,2}[_\\-]?[A-Za-z]{0,2};?( Build[/ ]|\\))',
        None,
        'Generic Smartphone',
        'Generic',
        'Smartphone',
    ),
    DeviceParser(
        'Android[\\- ][\\d]+(?:\\.[\\d]+)(?:\\.[\\d]+|); *\\-?[A-Za-z]{2}; *(.+?)( Build[/ ]|\\))',
        None,
        None,
        'Generic_Android',
        '$1',
    ),
    DeviceParser(
        'Android \\d+?(?:\\.\\d+|)(?:\\.\\d+|); ([^;]+?)(?: Build|\\) AppleWebKit).+? Mobile Safari',
        None,
        None,
        'Generic_Android',
        '$1',
    ),
    DeviceParser(
        'Android \\d+?(?:\\.\\d+|)(?:\\.\\d+|); ([^;]+?)(?: Build|\\) AppleWebKit).+? Safari',
        None,
        None,
        'Generic_Android_Tablet',
        '$1',
    ),
    DeviceParser(
        'Android \\d+?(?:\\.\\d+|)(?:\\.\\d+|); ([^;]+?)(?: Build|\\))',
        None,
        None,
        'Generic_Android',
        '$1',
    ),
    DeviceParser(
        '(GoogleTV)',
        None,
        None,
        'Generic_Inettv',
        '$1',
    ),
    DeviceParser(
        '(WebTV)/\\d+.\\d+',
        None,
        None,
        'Generic_Inettv',
        '$1',
    ),
    DeviceParser(
        '^(Roku)/DVP-\\d+\\.\\d+',
        None,
        None,
        'Generic_Inettv',
        '$1',
    ),
    DeviceParser(
        '(Android 3\\.\\d|Opera Tablet|Tablet; .+Firefox/|Android.*(?:Tab|Pad))',
        'i',
        'Generic Tablet',
        'Generic',
        'Tablet',
    ),
    DeviceParser(
        '(Symbian|\\bS60(Version|V\\d)|\\bS60\\b|\\((Series 60|Windows Mobile|Palm OS|Bada); Opera Mini|Windows CE|Opera Mobi|BREW|Brew|Mobile; .+Firefox/|iPhone OS|Android|MobileSafari|Windows *Phone|\\(webOS/|PalmOS)',
        None,
        'Generic Smartphone',
        'Generic',
        'Smartphone',
    ),
    DeviceParser(
        '(hiptop|avantgo|plucker|xiino|blazer|elaine)',
        'i',
        'Generic Smartphone',
        'Generic',
        'Smartphone',
    ),
    DeviceParser(
        '(bot|BUbiNG|zao|borg|DBot|oegp|silk|Xenu|zeal|^NING|CCBot|crawl|htdig|lycos|slurp|teoma|voila|yahoo|Sogou|CiBra|Nutch|^Java/|^JNLP/|Daumoa|Daum|Genieo|ichiro|larbin|pompos|Scrapy|snappy|speedy|spider|msnbot|msrbot|vortex|^vortex|crawler|favicon|indexer|Riddler|scooter|scraper|scrubby|WhatWeb|WinHTTP|bingbot|BingPreview|openbot|gigabot|furlbot|polybot|seekbot|^voyager|archiver|Icarus6j|mogimogi|Netvibes|blitzbot|altavista|charlotte|findlinks|Retreiver|TLSProber|WordPress|SeznamBot|ProoXiBot|wsr\\-agent|Squrl Java|EtaoSpider|PaperLiBot|SputnikBot|A6\\-Indexer|netresearch|searchsight|baiduspider|YisouSpider|ICC\\-Crawler|http%20client|Python-urllib|dataparksearch|converacrawler|Screaming Frog|AppEngine-Google|YahooCacheSystem|fast\\-webcrawler|Sogou Pic Spider|semanticdiscovery|Innovazion Crawler|facebookexternalhit|Google.*/\\+/web/snippet|Google-HTTP-Java-Client|BlogBridge|IlTrovatore-Setaccio|InternetArchive|GomezAgent|WebThumbnail|heritrix|NewsGator|PagePeeker|Reaper|ZooShot|holmes|NL-Crawler|Pingdom|StatusCake|WhatsApp|masscan|Google Web Preview|Qwantify|Yeti|OgScrper)',
        'i',
        'Spider',
        'Spider',
        'Desktop',
    ),
    DeviceParser(
        '^(1207|3gso|4thp|501i|502i|503i|504i|505i|506i|6310|6590|770s|802s|a wa|acer|acs\\-|airn|alav|asus|attw|au\\-m|aur |aus |abac|acoo|aiko|alco|alca|amoi|anex|anny|anyw|aptu|arch|argo|bmobile|bell|bird|bw\\-n|bw\\-u|beck|benq|bilb|blac|c55/|cdm\\-|chtm|capi|comp|cond|dall|dbte|dc\\-s|dica|ds\\-d|ds12|dait|devi|dmob|doco|dopo|dorado|el(?:38|39|48|49|50|55|58|68)|el[3456]\\d{2}dual|erk0|esl8|ex300|ez40|ez60|ez70|ezos|ezze|elai|emul|eric|ezwa|fake|fly\\-|fly_|g\\-mo|g1 u|g560|gf\\-5|grun|gene|go.w|good|grad|hcit|hd\\-m|hd\\-p|hd\\-t|hei\\-|hp i|hpip|hs\\-c|htc |htc\\-|htca|htcg)',
        'i',
        'Generic Feature Phone',
        'Generic',
        'Feature Phone',
    ),
    DeviceParser(
        '^(htcp|htcs|htct|htc_|haie|hita|huaw|hutc|i\\-20|i\\-go|i\\-ma|i\\-mobile|i230|iac|iac\\-|iac/|ig01|im1k|inno|iris|jata|kddi|kgt|kgt/|kpt |kwc\\-|klon|lexi|lg g|lg\\-a|lg\\-b|lg\\-c|lg\\-d|lg\\-f|lg\\-g|lg\\-k|lg\\-l|lg\\-m|lg\\-o|lg\\-p|lg\\-s|lg\\-t|lg\\-u|lg\\-w|lg/k|lg/l|lg/u|lg50|lg54|lge\\-|lge/|leno|m1\\-w|m3ga|m50/|maui|mc01|mc21|mcca|medi|meri|mio8|mioa|mo01|mo02|mode|modo|mot |mot\\-|mt50|mtp1|mtv |mate|maxo|merc|mits|mobi|motv|mozz|n100|n101|n102|n202|n203|n300|n302|n500|n502|n505|n700|n701|n710|nec\\-|nem\\-|newg|neon)',
        'i',
        'Generic Feature Phone',
        'Generic',
        'Feature Phone',
    ),
    DeviceParser(
        '^(netf|noki|nzph|o2 x|o2\\-x|opwv|owg1|opti|oran|ot\\-s|p800|pand|pg\\-1|pg\\-2|pg\\-3|pg\\-6|pg\\-8|pg\\-c|pg13|phil|pn\\-2|pt\\-g|palm|pana|pire|pock|pose|psio|qa\\-a|qc\\-2|qc\\-3|qc\\-5|qc\\-7|qc07|qc12|qc21|qc32|qc60|qci\\-|qwap|qtek|r380|r600|raks|rim9|rove|s55/|sage|sams|sc01|sch\\-|scp\\-|sdk/|se47|sec\\-|sec0|sec1|semc|sgh\\-|shar|sie\\-|sk\\-0|sl45|slid|smb3|smt5|sp01|sph\\-|spv |spv\\-|sy01|samm|sany|sava|scoo|send|siem|smar|smit|soft|sony|t\\-mo|t218|t250|t600|t610|t618|tcl\\-|tdg\\-|telm|tim\\-|ts70|tsm\\-|tsm3|tsm5|tx\\-9|tagt)',
        'i',
        'Generic Feature Phone',
        'Generic',
        'Feature Phone',
    ),
    DeviceParser(
        '^(talk|teli|topl|tosh|up.b|upg1|utst|v400|v750|veri|vk\\-v|vk40|vk50|vk52|vk53|vm40|vx98|virg|vertu|vite|voda|vulc|w3c |w3c\\-|wapj|wapp|wapu|wapm|wig |wapi|wapr|wapv|wapy|wapa|waps|wapt|winc|winw|wonu|x700|xda2|xdag|yas\\-|your|zte\\-|zeto|aste|audi|avan|blaz|brew|brvw|bumb|ccwa|cell|cldc|cmd\\-|dang|eml2|fetc|hipt|http|ibro|idea|ikom|ipaq|jbro|jemu|jigs|keji|kyoc|kyok|libw|m\\-cr|midp|mmef|moto|mwbp|mywa|newt|nok6|o2im|pant|pdxg|play|pluc|port|prox|rozo|sama|seri|smal|symb|treo|upsi|vx52|vx53|vx60|vx61|vx70|vx80|vx81|vx83|vx85|wap\\-|webc|whit|wmlb|xda\\-|xda_)',
        'i',
        'Generic Feature Phone',
        'Generic',
        'Feature Phone',
    ),
    DeviceParser(
        '^(Ice)$',
        None,
        'Generic Feature Phone',
        'Generic',
        'Feature Phone',
    ),
    DeviceParser(
        '(wap[\\-\\ ]browser|maui|netfront|obigo|teleca|up\\.browser|midp|Opera Mini)',
        'i',
        'Generic Feature Phone',
        'Generic',
        'Feature Phone',
    ),
    DeviceParser(
        'Mac OS',
        None,
        'Mac',
        'Apple',
        'Mac',
    ),
]

OS_PARSERS = [
    OSParser(
        'HbbTV/\\d+\\.\\d+\\.\\d+ \\( ;(LG)E ;NetCast 4.0',
        None,
        '2013',
        None,
        None,
        None,
    ),
    OSParser(
        'HbbTV/\\d+\\.\\d+\\.\\d+ \\( ;(LG)E ;NetCast 3.0',
        None,
        '2012',
        None,
        None,
        None,
    ),
    OSParser(
        'HbbTV/1.1.1 \\(;;;;;\\) Maple_2011',
        'Samsung',
        '2011',
        None,
        None,
        None,
    ),
    OSParser(
        'HbbTV/\\d+\\.\\d+\\.\\d+ \\(;(Samsung);SmartTV([0-9]{4});.*FXPDEUC',
        None,
        None,
        'UE40F7000',
        None,
        None,
    ),
    OSParser(
        'HbbTV/\\d+\\.\\d+\\.\\d+ \\(;(Samsung);SmartTV([0-9]{4});.*MST12DEUC',
        None,
        None,
        'UE32F4500',
        None,
        None,
    ),
    OSParser(
        'HbbTV/1\\.1\\.1 \\(; (Philips);.*NETTV/4',
        None,
        '2013',
        None,
        None,
        None,
    ),
    OSParser(
        'HbbTV/1\\.1\\.1 \\(; (Philips);.*NETTV/3',
        None,
        '2012',
        None,
        None,
        None,
    ),
    OSParser(
        'HbbTV/1\\.1\\.1 \\(; (Philips);.*NETTV/2',
        None,
        '2011',
        None,
        None,
        None,
    ),
    OSParser(
        'HbbTV/\\d+\\.\\d+\\.\\d+.*(firetv)-firefox-plugin (\\d+).(\\d+).(\\d+)',
        'FireHbbTV',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        'HbbTV/\\d+\\.\\d+\\.\\d+ \\(.*; ?([a-zA-Z]+) ?;.*(201[1-9]).*\\)',
        None,
        None,
        None,
        None,
        None,
    ),
    OSParser(
        'AspiegelBot',
        'Other',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(Windows Phone) (?:OS[ /])?(\\d+)\\.(\\d+)',
        None,
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone)[ +]+(\\d+)[_\\.](\\d+)(?:[_\\.](\\d+)|).*Outlook-iOS-Android',
        'iOS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        'ArcGIS\\.?(iOS|Android)-\\d+\\.\\d+(?:\\.\\d+|)(?:[^\\/]+|)\\/(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)|)|)',
        None,
        None,
        None,
        None,
        None,
    ),
    OSParser(
        'ArcGISRuntime-(?:Android|iOS)\\/\\d+\\.\\d+(?:\\.\\d+|) \\((Android|iOS) (\\d+)(?:\\.(\\d+)(?:\\.(\\d+)|)|);',
        None,
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(Android)[ \\-/](\\d+)(?:\\.(\\d+)|)(?:[.\\-]([a-z0-9]+)|)',
        None,
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(Android) Donut',
        None,
        '1',
        '2',
        None,
        None,
    ),
    OSParser(
        '(Android) Eclair',
        None,
        '2',
        '1',
        None,
        None,
    ),
    OSParser(
        '(Android) Froyo',
        None,
        '2',
        '2',
        None,
        None,
    ),
    OSParser(
        '(Android) Gingerbread',
        None,
        '2',
        '3',
        None,
        None,
    ),
    OSParser(
        '(Android) Honeycomb',
        None,
        '3',
        None,
        None,
        None,
    ),
    OSParser(
        '(Android) (\\d+);',
        None,
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '^UCWEB.*; (Adr) (\\d+)\\.(\\d+)(?:[.\\-]([a-z0-9]+)|);',
        'Android',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '^UCWEB.*; (iPad|iPh|iPd) OS (\\d+)_(\\d+)(?:_(\\d+)|);',
        'iOS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '^UCWEB.*; (wds) (\\d+)\\.(\\d+)(?:\\.(\\d+)|);',
        'Windows Phone',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '^(JUC).*; ?U; ?(?:Android|)(\\d+)\\.(\\d+)(?:[\\.\\-]([a-z0-9]+)|)',
        'Android',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(android)\\s(?:mobile\\/)(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)|)|)',
        'Android',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(Silk-Accelerated=[a-z]{4,5})',
        'Android',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(x86_64|aarch64)\\ (\\d+)\\.(\\d+)\\.(\\d+).*Chrome.*(?:CitrixChromeApp)$',
        'Chrome OS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(XBLWP7)',
        'Windows Phone',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(Windows ?Mobile)',
        'Windows Mobile',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(Windows 10)',
        'Windows',
        '10',
        None,
        None,
        None,
    ),
    OSParser(
        '(Windows (?:NT 5\\.2|NT 5\\.1))',
        'Windows',
        'XP',
        None,
        None,
        None,
    ),
    OSParser(
        '(Win(?:dows NT |32NT\\/)6\\.1)',
        'Windows',
        '7',
        None,
        None,
        None,
    ),
    OSParser(
        '(Win(?:dows NT |32NT\\/)6\\.0)',
        'Windows',
        'Vista',
        None,
        None,
        None,
    ),
    OSParser(
        '(Win 9x 4\\.90)',
        'Windows',
        'ME',
        None,
        None,
        None,
    ),
    OSParser(
        '(Windows NT 6\\.2; ARM;)',
        'Windows',
        'RT',
        None,
        None,
        None,
    ),
    OSParser(
        '(Win(?:dows NT |32NT\\/)6\\.2)',
        'Windows',
        '8',
        None,
        None,
        None,
    ),
    OSParser(
        '(Windows NT 6\\.3; ARM;)',
        'Windows',
        'RT 8',
        '1',
        None,
        None,
    ),
    OSParser(
        '(Win(?:dows NT |32NT\\/)6\\.3)',
        'Windows',
        '8',
        '1',
        None,
        None,
    ),
    OSParser(
        '(Win(?:dows NT |32NT\\/)6\\.4)',
        'Windows',
        '10',
        None,
        None,
        None,
    ),
    OSParser(
        '(Windows NT 10\\.0)',
        'Windows',
        '10',
        None,
        None,
        None,
    ),
    OSParser(
        '(Windows NT 5\\.0)',
        'Windows',
        '2000',
        None,
        None,
        None,
    ),
    OSParser(
        '(WinNT4.0)',
        'Windows',
        'NT 4.0',
        None,
        None,
        None,
    ),
    OSParser(
        '(Windows ?CE)',
        'Windows',
        'CE',
        None,
        None,
        None,
    ),
    OSParser(
        'Win(?:dows)? ?(95|98|3.1|NT|ME|2000|XP|Vista|7|CE)',
        'Windows',
        '$1',
        None,
        None,
        None,
    ),
    OSParser(
        'Win16',
        'Windows',
        '3.1',
        None,
        None,
        None,
    ),
    OSParser(
        'Win32',
        'Windows',
        '95',
        None,
        None,
        None,
    ),
    OSParser(
        '^Box.*Windows/([\\d.]+);',
        'Windows',
        '$1',
        None,
        None,
        None,
    ),
    OSParser(
        '(Tizen)[/ ](\\d+)\\.(\\d+)',
        None,
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '((?:Mac[ +]?|; )OS[ +]X)[\\s+/](?:(\\d+)[_.](\\d+)(?:[_.](\\d+)|)|Mach-O)',
        'Mac OS X',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '\\w+\\s+Mac OS X\\s+\\w+\\s+(\\d+).(\\d+).(\\d+).*',
        'Mac OS X',
        '$1',
        '$2',
        '$3',
        None,
    ),
    OSParser(
        ' (Dar)(win)/(9).(\\d+).*\\((?:i386|x86_64|Power Macintosh)\\)',
        'Mac OS X',
        '10',
        '5',
        None,
        None,
    ),
    OSParser(
        ' (Dar)(win)/(10).(\\d+).*\\((?:i386|x86_64)\\)',
        'Mac OS X',
        '10',
        '6',
        None,
        None,
    ),
    OSParser(
        ' (Dar)(win)/(11).(\\d+).*\\((?:i386|x86_64)\\)',
        'Mac OS X',
        '10',
        '7',
        None,
        None,
    ),
    OSParser(
        ' (Dar)(win)/(12).(\\d+).*\\((?:i386|x86_64)\\)',
        'Mac OS X',
        '10',
        '8',
        None,
        None,
    ),
    OSParser(
        ' (Dar)(win)/(13).(\\d+).*\\((?:i386|x86_64)\\)',
        'Mac OS X',
        '10',
        '9',
        None,
        None,
    ),
    OSParser(
        'Mac_PowerPC',
        'Mac OS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(?:PPC|Intel) (Mac OS X)',
        None,
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '^Box.*;(Darwin)/(10)\\.(1\\d)(?:\\.(\\d+)|)',
        'Mac OS X',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(Apple\\s?TV)(?:/(\\d+)\\.(\\d+)|)',
        'ATV OS X',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone|CPU IPhone OS)[ +]+(\\d+)[_\\.](\\d+)(?:[_\\.](\\d+)|)',
        'iOS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(iPhone|iPad|iPod); Opera',
        'iOS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(iPhone|iPad|iPod).*Mac OS X.*Version/(\\d+)\\.(\\d+)',
        'iOS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(CFNetwork)/(5)48\\.0\\.3.* Darwin/11\\.0\\.0',
        'iOS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(CFNetwork)/(5)48\\.(0)\\.4.* Darwin/(1)1\\.0\\.0',
        'iOS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(CFNetwork)/(5)48\\.(1)\\.4',
        'iOS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(CFNetwork)/(4)85\\.1(3)\\.9',
        'iOS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(CFNetwork)/(6)09\\.(1)\\.4',
        'iOS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(CFNetwork)/(6)(0)9',
        'iOS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(CFNetwork)/6(7)2\\.(1)\\.13',
        'iOS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(CFNetwork)/6(7)2\\.(1)\\.(1)4',
        'iOS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(CF)(Network)/6(7)(2)\\.1\\.15',
        'iOS',
        '7',
        '1',
        None,
        None,
    ),
    OSParser(
        '(CFNetwork)/6(7)2\\.(0)\\.(?:2|8)',
        'iOS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(CFNetwork)/709\\.1',
        'iOS',
        '8',
        '0.b5',
        None,
        None,
    ),
    OSParser(
        '(CF)(Network)/711\\.(\\d)',
        'iOS',
        '8',
        None,
        None,
        None,
    ),
    OSParser(
        '(CF)(Network)/(720)\\.(\\d)',
        'Mac OS X',
        '10',
        '10',
        None,
        None,
    ),
    OSParser(
        '(CF)(Network)/(760)\\.(\\d)',
        'Mac OS X',
        '10',
        '11',
        None,
        None,
    ),
    OSParser(
        'CFNetwork/7.* Darwin/15\\.4\\.\\d+',
        'iOS',
        '9',
        '3',
        '1',
        None,
    ),
    OSParser(
        'CFNetwork/7.* Darwin/15\\.5\\.\\d+',
        'iOS',
        '9',
        '3',
        '2',
        None,
    ),
    OSParser(
        'CFNetwork/7.* Darwin/15\\.6\\.\\d+',
        'iOS',
        '9',
        '3',
        '5',
        None,
    ),
    OSParser(
        '(CF)(Network)/758\\.(\\d)',
        'iOS',
        '9',
        None,
        None,
        None,
    ),
    OSParser(
        'CFNetwork/808\\.3 Darwin/16\\.3\\.\\d+',
        'iOS',
        '10',
        '2',
        '1',
        None,
    ),
    OSParser(
        '(CF)(Network)/808\\.(\\d)',
        'iOS',
        '10',
        None,
        None,
        None,
    ),
    OSParser(
        'CFNetwork/.* Darwin/17\\.\\d+.*\\(x86_64\\)',
        'Mac OS X',
        '10',
        '13',
        None,
        None,
    ),
    OSParser(
        'CFNetwork/.* Darwin/16\\.\\d+.*\\(x86_64\\)',
        'Mac OS X',
        '10',
        '12',
        None,
        None,
    ),
    OSParser(
        'CFNetwork/8.* Darwin/15\\.\\d+.*\\(x86_64\\)',
        'Mac OS X',
        '10',
        '11',
        None,
        None,
    ),
    OSParser(
        'CFNetwork/.* Darwin/(9)\\.\\d+',
        'iOS',
        '1',
        None,
        None,
        None,
    ),
    OSParser(
        'CFNetwork/.* Darwin/(10)\\.\\d+',
        'iOS',
        '4',
        None,
        None,
        None,
    ),
    OSParser(
        'CFNetwork/.* Darwin/(11)\\.\\d+',
        'iOS',
        '5',
        None,
        None,
        None,
    ),
    OSParser(
        'CFNetwork/.* Darwin/(13)\\.\\d+',
        'iOS',
        '6',
        None,
        None,
        None,
    ),
    OSParser(
        'CFNetwork/6.* Darwin/(14)\\.\\d+',
        'iOS',
        '7',
        None,
        None,
        None,
    ),
    OSParser(
        'CFNetwork/7.* Darwin/(14)\\.\\d+',
        'iOS',
        '8',
        '0',
        None,
        None,
    ),
    OSParser(
        'CFNetwork/7.* Darwin/(15)\\.\\d+',
        'iOS',
        '9',
        '0',
        None,
        None,
    ),
    OSParser(
        'CFNetwork/8.* Darwin/16\\.5\\.\\d+',
        'iOS',
        '10',
        '3',
        None,
        None,
    ),
    OSParser(
        'CFNetwork/8.* Darwin/16\\.6\\.\\d+',
        'iOS',
        '10',
        '3',
        '2',
        None,
    ),
    OSParser(
        'CFNetwork/8.* Darwin/16\\.7\\.\\d+',
        'iOS',
        '10',
        '3',
        '3',
        None,
    ),
    OSParser(
        'CFNetwork/8.* Darwin/(16)\\.\\d+',
        'iOS',
        '10',
        None,
        None,
        None,
    ),
    OSParser(
        'CFNetwork/8.* Darwin/17\\.0\\.\\d+',
        'iOS',
        '11',
        '0',
        None,
        None,
    ),
    OSParser(
        'CFNetwork/8.* Darwin/17\\.2\\.\\d+',
        'iOS',
        '11',
        '1',
        None,
        None,
    ),
    OSParser(
        'CFNetwork/8.* Darwin/17\\.3\\.\\d+',
        'iOS',
        '11',
        '2',
        None,
        None,
    ),
    OSParser(
        'CFNetwork/8.* Darwin/17\\.4\\.\\d+',
        'iOS',
        '11',
        '2',
        '6',
        None,
    ),
    OSParser(
        'CFNetwork/8.* Darwin/17\\.5\\.\\d+',
        'iOS',
        '11',
        '3',
        None,
        None,
    ),
    OSParser(
        'CFNetwork/9.* Darwin/17\\.6\\.\\d+',
        'iOS',
        '11',
        '4',
        None,
        None,
    ),
    OSParser(
        'CFNetwork/9.* Darwin/17\\.7\\.\\d+',
        'iOS',
        '11',
        '4',
        '1',
        None,
    ),
    OSParser(
        'CFNetwork/8.* Darwin/(17)\\.\\d+',
        'iOS',
        '11',
        None,
        None,
        None,
    ),
    OSParser(
        'CFNetwork/9.* Darwin/18\\.0\\.\\d+',
        'iOS',
        '12',
        '0',
        None,
        None,
    ),
    OSParser(
        'CFNetwork/9.* Darwin/(18)\\.\\d+',
        'iOS',
        '12',
        None,
        None,
        None,
    ),
    OSParser(
        'CFNetwork/.* Darwin/',
        'iOS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '\\b(iOS[ /]|iOS; |iPhone(?:/| v|[ _]OS[/,]|; | OS : |\\d,\\d/|\\d,\\d; )|iPad/)(\\d{1,2})[_\\.](\\d{1,2})(?:[_\\.](\\d+)|)',
        'iOS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '\\((iOS);',
        None,
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(watchOS)/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        'WatchOS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        'Outlook-(iOS)/\\d+\\.\\d+\\.prod\\.iphone',
        None,
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(iPod|iPhone|iPad)',
        'iOS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(tvOS)[/ ](\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        'tvOS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(CrOS) [a-z0-9_]+ (\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        'Chrome OS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '([Dd]ebian)',
        'Debian',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(Linux Mint)(?:/(\\d+)|)',
        None,
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(Mandriva)(?: Linux|)/(?:[\\d.-]+m[a-z]{2}(\\d+).(\\d)|)',
        None,
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(Symbian[Oo][Ss])[/ ](\\d+)\\.(\\d+)',
        'Symbian OS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(Symbian/3).+NokiaBrowser/7\\.3',
        'Symbian^3 Anna',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(Symbian/3).+NokiaBrowser/7\\.4',
        'Symbian^3 Belle',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(Symbian/3)',
        'Symbian^3',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '\\b(Series 60|SymbOS|S60Version|S60V\\d|S60\\b)',
        'Symbian OS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(MeeGo)',
        None,
        None,
        None,
        None,
        None,
    ),
    OSParser(
        'Symbian [Oo][Ss]',
        'Symbian OS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        'Series40;',
        'Nokia Series 40',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        'Series30Plus;',
        'Nokia Series 30 Plus',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(BB10);.+Version/(\\d+)\\.(\\d+)\\.(\\d+)',
        'BlackBerry OS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(Black[Bb]erry)[0-9a-z]+/(\\d+)\\.(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        'BlackBerry OS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(Black[Bb]erry).+Version/(\\d+)\\.(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        'BlackBerry OS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(RIM Tablet OS) (\\d+)\\.(\\d+)\\.(\\d+)',
        'BlackBerry Tablet OS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(Play[Bb]ook)',
        'BlackBerry Tablet OS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(Black[Bb]erry)',
        'BlackBerry OS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(K[Aa][Ii]OS)\\/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        'KaiOS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '\\((?:Mobile|Tablet);.+Gecko/18.0 Firefox/\\d+\\.\\d+',
        'Firefox OS',
        '1',
        '0',
        '1',
        None,
    ),
    OSParser(
        '\\((?:Mobile|Tablet);.+Gecko/18.1 Firefox/\\d+\\.\\d+',
        'Firefox OS',
        '1',
        '1',
        None,
        None,
    ),
    OSParser(
        '\\((?:Mobile|Tablet);.+Gecko/26.0 Firefox/\\d+\\.\\d+',
        'Firefox OS',
        '1',
        '2',
        None,
        None,
    ),
    OSParser(
        '\\((?:Mobile|Tablet);.+Gecko/28.0 Firefox/\\d+\\.\\d+',
        'Firefox OS',
        '1',
        '3',
        None,
        None,
    ),
    OSParser(
        '\\((?:Mobile|Tablet);.+Gecko/30.0 Firefox/\\d+\\.\\d+',
        'Firefox OS',
        '1',
        '4',
        None,
        None,
    ),
    OSParser(
        '\\((?:Mobile|Tablet);.+Gecko/32.0 Firefox/\\d+\\.\\d+',
        'Firefox OS',
        '2',
        '0',
        None,
        None,
    ),
    OSParser(
        '\\((?:Mobile|Tablet);.+Gecko/34.0 Firefox/\\d+\\.\\d+',
        'Firefox OS',
        '2',
        '1',
        None,
        None,
    ),
    OSParser(
        '\\((?:Mobile|Tablet);.+Firefox/\\d+\\.\\d+',
        'Firefox OS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(BREW)[ /](\\d+)\\.(\\d+)\\.(\\d+)',
        None,
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(BREW);',
        None,
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(Brew MP|BMP)[ /](\\d+)\\.(\\d+)\\.(\\d+)',
        'Brew MP',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        'BMP;',
        'Brew MP',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(GoogleTV)(?: (\\d+)\\.(\\d+)(?:\\.(\\d+)|)|/[\\da-z]+)',
        None,
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(WebTV)/(\\d+).(\\d+)',
        None,
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(CrKey)(?:[/](\\d+)\\.(\\d+)(?:\\.(\\d+)|)|)',
        'Chromecast',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(hpw|web)OS/(\\d+)\\.(\\d+)(?:\\.(\\d+)|)',
        'webOS',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(VRE);',
        None,
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(Fedora|Red Hat|PCLinuxOS|Puppy|Ubuntu|Kindle|Bada|Sailfish|Lubuntu|BackTrack|Slackware|(?:Free|Open|Net|\\b)BSD)[/ ](\\d+)\\.(\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)',
        None,
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(Linux)[ /](\\d+)\\.(\\d+)(?:\\.(\\d+)|).*gentoo',
        'Gentoo',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '\\((Bada);',
        None,
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(Windows|Android|WeTab|Maemo|Web0S)',
        None,
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(Ubuntu|Kubuntu|Arch Linux|CentOS|Slackware|Gentoo|openSUSE|SUSE|Red Hat|Fedora|PCLinuxOS|Mageia|(?:Free|Open|Net|\\b)BSD)',
        None,
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '(Linux)(?:[ /](\\d+)\\.(\\d+)(?:\\.(\\d+)|)|)',
        None,
        None,
        None,
        None,
        None,
    ),
    OSParser(
        'SunOS',
        'Solaris',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '\\(linux-gnu\\)',
        'Linux',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '\\(x86_64-redhat-linux-gnu\\)',
        'Red Hat',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '\\((freebsd)(\\d+)\\.(\\d+)\\)',
        'FreeBSD',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        'linux',
        'Linux',
        None,
        None,
        None,
        None,
    ),
    OSParser(
        '^(Roku)/DVP-(\\d+)\\.(\\d+)',
        None,
        None,
        None,
        None,
        None,
    ),
]
