Translate: add m(string) and a few debugging messages

####################################################################
# Translate.awk                                                    #
####################################################################

# Detect external terminal pager (less, more, most).
function initPager() {
    Pager = !system("less -V" SUPOUT SUPERR) ?
        "less" :
        (!system("more -V" SUPOUT SUPERR) ?
         "more" :
         (!system("most" SUPOUT SUPERR) ?
          "most" :
          ""))
}

# Detect external audio player (mplayer, mpv, mpg123).
function initAudioPlayer() {
    AudioPlayer = !system("mplayer" SUPOUT SUPERR) ?
        "mplayer" :
        (!system("mpv" SUPOUT SUPERR) ?
         "mpv" :
         (!system("mpg123 --version" SUPOUT SUPERR) ?
          "mpg123" :
          ""))
}

# Detect external speech synthesizer (say, espeak).
function initSpeechSynthesizer() {
    SpeechSynthesizer = !system("say ''" SUPOUT SUPERR) ?
        "say" :
        (!system("espeak ''" SUPOUT SUPERR) ?
         "espeak" :
         "")
}

# Initialize `HttpService`.
function initHttpService() {
    HttpProtocol = "http://"
    HttpHost = "translate.google.com"
    HttpPort = 80
    if (Option["proxy"]) {
        match(Option["proxy"], /^(http:\/*)?([^\/]*):([^\/:]*)/, HttpProxySpec)
        HttpService = "/inet/tcp/0/" HttpProxySpec[2] "/" HttpProxySpec[3]
        HttpPathPrefix = HttpProtocol HttpHost
    } else {
        HttpService = "/inet/tcp/0/" HttpHost "/" HttpPort
        HttpPathPrefix = ""
    }
}

# Return a message for debugging.
function m(string) {
    if (Option["debug"])
        return ansi("blue", string) RS
}

# Pre-process string (URL-encode before send).
function preprocess(text) {
    return quote(text)
}

# Post-process string (remove any redundant whitespace).
function postprocess(text) {
    text = gensub(/ ([.,;:?!"])/, "\\1", "g", text)
    text = gensub(/(["]) /, "\\1", "g", text)
    return text
}

# Send an HTTP request and get response from Google Translate.
function getResponse(text, sl, tl, hl,    content, url) {
    url = HttpPathPrefix "/translate_a/single?client=t"                 \
        "&ie=UTF-8&oe=UTF-8"                                            \
        "&dt=bd&dt=ex&dt=ld&dt=md&dt=qca&dt=rw&dt=rm&dt=ss&dt=t&dt=at"  \
        "&q=" preprocess(text) "&sl=" sl "&tl=" tl "&hl=" hl

    print "GET " url " HTTP/1.1\n"             \
          "Host: " HttpHost "\n"               \
          "Connection: close\n" |& HttpService
    while ((HttpService |& getline) > 0)
        content = $0
    close(HttpService)

    return assert(content, "[ERROR] Null response.")
}

# Print a string (to output file or terminal pager).
function p(string) {
    if (Option["view"])
        print string | Option["pager"]
    else
        print string > Option["output"]
}

# Play using Google Text-to-Speech engine.
function play(text, tl,    url) {
    url = HttpProtocol HttpHost "/translate_tts?ie=UTF-8"       \
        "&tl=" tl "&q=" preprocess(text)

    # Don't use getline from pipe here - the same pipe will be run only once for each AWK script!
    system(Option["player"] " " parameterize(url) SUPOUT SUPERR)
}

# Get the translation of a string.
function getTranslation(text, sl, tl, hl,
                        isVerbose, toSpeech, returnPlaylist,
                        ####
                        r,
                        content, tokens, ast,
                        il, ils, isPhonetic,
                        article, example, explanation, ref, word,
                        translation, translations, phonetics,
                        wordClasses, words, segments, altTranslations,
                        original, oPhonetics, oWordClasses, oWords,
                        oRefs, oSynonyms, oExamples, oSeeAlso,
                        wShowOriginal, wShowOriginalPhonetics,
                        wShowTranslation, wShowTranslationPhonetics,
                        wShowPromptMessage, wShowLanguages,
                        wShowOriginalDictionary, wShowDictionary,
                        wShowAlternatives,
                        hasWordClasses, hasAltTranslations,
                        i, j, k, group, temp, saveSortedIn) {
    isPhonetic = match(tl, /^@/)
    tl = substr(tl, 1 + isPhonetic)

    if (!getCode(tl)) {
        # Check if target language is supported
        w("[WARNING] Unknown target language code: " tl)
    } else if (isRTL(tl)) {
        # Check if target language is R-to-L
        if (!FriBidi)
            w("[WARNING] " getName(tl) " is a right-to-left language, but FriBidi cannot be found.")
    }

    content = getResponse(text, sl, tl, hl)
    plTokenize(tokens, content)
    plParse(ast, tokens)

    # Debug mode
    if (Option["debug"]) {
        d(content)
        da(tokens, "tokens[%s]='%s'")
        da(ast, "ast[%s]='%s'")
    }

    if (!anything(ast)) {
        e("[ERROR] Oops! Something went wrong and I can't translate it for you :(")
        ExitCode = 1
        return
    }

    saveSortedIn = PROCINFO["sorted_in"]
    PROCINFO["sorted_in"] = "@ind_num_asc"
    for (i in ast) {
        if (i ~ "^0" SUBSEP "0" SUBSEP "[[:digit:]]+" SUBSEP "0$")
            append(translations, postprocess(literal(ast[i])))
        if (i ~ "^0" SUBSEP "0" SUBSEP "[[:digit:]]+" SUBSEP "1$")
            append(original, literal(ast[i]))
        if (i ~ "^0" SUBSEP "0" SUBSEP "[[:digit:]]+" SUBSEP "2$")
            append(phonetics, literal(ast[i]))
        if (i ~ "^0" SUBSEP "0" SUBSEP "[[:digit:]]+" SUBSEP "3$")
            append(oPhonetics, literal(ast[i]))

        # 1 - word classes and explanations
        if (match(i, "^0" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            wordClasses[group[1]] = literal(ast[i])
        if (match(i, "^0" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "2" SUBSEP "([[:digit:]]+)" SUBSEP "([[:digit:]]+)$", group))
            words[group[1]][group[2]][group[3]] = literal(ast[i])
        if (match(i, "^0" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "2" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)$", group))
            words[group[1]][group[2]]["1"][group[3]] = literal(ast[i])

        # 5 - alternative translations
        if (match(i, "^0" SUBSEP "5" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group)) {
            segments[group[1]] = literal(ast[i])
            altTranslations[group[1]][0] = ""
        }
        if (match(i, "^0" SUBSEP "5" SUBSEP "([[:digit:]]+)" SUBSEP "2" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            altTranslations[group[1]][group[2]] = postprocess(literal(ast[i]))

        # 8 - identified source languages
        if (i ~ "^0" SUBSEP "8" SUBSEP "0" SUBSEP "[[:digit:]]+$" ||
            i ~ "^0" SUBSEP "2$")
            append(ils, literal(ast[i]))

        # 11 - (original) word classes and synonyms
        if (match(i, "^0" SUBSEP "11" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            oWordClasses[group[1]] = literal(ast[i])
        if (match(i, "^0" SUBSEP "11" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "1$", group))
            if (ast[i]) {
                oRefs[literal(ast[i])][1] = group[1]
                oRefs[literal(ast[i])][2] = group[2]
            }
        if (match(i, "^0" SUBSEP "11" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "0" SUBSEP "([[:digit:]]+)$", group))
            oSynonyms[group[1]][group[2]][group[3]] = literal(ast[i])

        # 12 - (original) word classes and explanations
        if (match(i, "^0" SUBSEP "12" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            oWordClasses[group[1]] = literal(ast[i])
        if (match(i, "^0" SUBSEP "12" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            oWords[group[1]][group[2]][0] = literal(ast[i])
        if (match(i, "^0" SUBSEP "12" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "1$", group))
            oWords[group[1]][group[2]][1] = literal(ast[i])
        if (match(i, "^0" SUBSEP "12" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "2$", group))
            oWords[group[1]][group[2]][2] = postprocess(literal(ast[i]))

        # 13 - (original) examples
        if (match(i, "^0" SUBSEP "13" SUBSEP "0" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            oExamples[group[1]] = postprocess(literal(ast[i]))

        # 14 - (original) see also
        if (match(i, "^0" SUBSEP "14" SUBSEP "0" SUBSEP "([[:digit:]]+)$", group))
            oSeeAlso[group[1]] = literal(ast[i])
    }
    PROCINFO["sorted_in"] = saveSortedIn

    translation = join(translations)

    il = !anything(ils) || belongsTo(sl, ils) ? sl : ils[0]

    # Generate output
    if (!isVerbose) {
        # Brief mode

        r = isPhonetic && anything(phonetics) ?
            join(phonetics) :  # phonetic transcription
            s(translation, tl) # target language

        if (toSpeech) {
            returnPlaylist[0]["text"] = translation
            returnPlaylist[0]["tl"] = tl
        }

    } else {
        # Verbose mode

        wShowOriginal = Option["show-original"]
        wShowOriginalPhonetics = Option["show-original-phonetics"]
        wShowTranslation = Option["show-translation"]
        wShowTranslationPhonetics = Option["show-translation-phonetics"]
        wShowPromptMessage = Option["show-prompt-message"]
        wShowLanguages = Option["show-languages"]
        wShowOriginalDictionary = Option["show-original-dictionary"]
        wShowDictionary = Option["show-dictionary"]
        wShowAlternatives = Option["show-alternatives"]

        if (!anything(oPhonetics)) wShowOriginalPhonetics = 0
        if (!anything(phonetics)) wShowTranslationPhonetics = 0
        if (il == tl && isarray(oWordClasses)) {
            wShowOriginalDictionary = 1
            wShowTranslation = 0
        }
        hasWordClasses = isarray(wordClasses) && anything(wordClasses)
        hasAltTranslations = isarray(altTranslations[0]) && anything(altTranslations[0])
        if (!hasWordClasses) wShowDictionary = 0
        if (hasWordClasses || !hasAltTranslations) wShowAlternatives = 0

        if (wShowOriginal) {
            # Display: original text
            if (r) r = r RS RS
            r = r m("-- display original text")
            r = r ansi("negative", ansi("bold", s(join(original), il)))
            if (wShowOriginalPhonetics)
                r = r RS showPhonetics(join(oPhonetics), il)
        }

        if (wShowTranslation) {
            # Display: major translation & phonetics
            if (r) r = r RS RS
            r = r m("-- display major translation & phonetics")
            r = r ansi("bold", s(translation, tl))
            if (wShowTranslationPhonetics)
                r = r RS showPhonetics(join(phonetics), tl)
        }

        if (wShowPromptMessage || wShowLanguages)
            if (r) r = r RS
        if (wShowPromptMessage) {
            # Display: prompt message
            if (hasWordClasses) {
                # Definitions of
                if (r) r = r RS
                r = r m("-- display prompt message (Definitions of ...)")
                if (isRTL(hl)) # home language is R-to-L
                    r = r s(showDefinitionsOf(hl, join(original)))
                else # home language is L-to-R
                    r = r showDefinitionsOf(hl, ansi("underline", show(join(original), il)))
            } else if (hasAltTranslations) {
                # Translations of
                if (r) r = r RS
                r = r m("-- display prompt message (Translations of ...)")
                if (isRTL(hl)) # home language is R-to-L
                    r = r s(showTranslationsOf(hl, join(original)))
                else # home language is L-to-R
                    r = r showTranslationsOf(hl, ansi("underline", show(join(original), il)))
            }
        }
        if (wShowLanguages) {
            # Display: source language -> target language
            if (hasWordClasses || hasAltTranslations) {
                if (r) r = r RS
                r = r m("-- display source language -> target language")
                r = r s(sprintf("[ %s -> %s ]", getEndonym(il), getEndonym(tl)))
            }
        }

        if (wShowOriginalDictionary) {
            # Display: original dictionary
            if (r) r = r RS
            r = r m("-- display original dictionary")
            if (isarray(oWordClasses) && anything(oWordClasses)) {
                for (i = 0; i < length(oWordClasses); i++) {
                    r = (i > 0 ? r RS : r) RS s(oWordClasses[i], hl)
                    if (isarray(oWords[i])) {
                        # Detailed explanation
                        for (j = 0; j < length(oWords[i]); j++) {
                            explanation = oWords[i][j][0]
                            ref = oWords[i][j][1]
                            example = oWords[i][j][2]

                            r = (j > 0 ? r RS : r) RS ansi("bold", ins(1, explanation, il))
                            if (example)
                                r = r RS ins(2, "- \"" example "\"", il)
                            if (ref && isarray(oRefs[ref])) {
                                temp = showSynonyms(hl) ": " oSynonyms[oRefs[ref][1]][oRefs[ref][2]][0]
                                for (k = 1; k < length(oSynonyms[oRefs[ref][1]][oRefs[ref][2]]); k++)
                                    temp = temp ", " oSynonyms[oRefs[ref][1]][oRefs[ref][2]][k]
                                r = r RS ins(1, temp)
                            }
                        }
                    } else {
                        # Synonyms only
                        for (j = 0; j < length(oSynonyms[i]); j++) {
                            temp = "* " oSynonyms[i][j][0]
                            for (k = 1; k < length(oSynonyms[i][j]); k++)
                                temp = temp ", " oSynonyms[i][j][k]
                            r = r RS ins(1, temp)
                        }
                    }
                }
            }
            # Examples
            if (isarray(oExamples) && anything(oExamples)) {
                r = r RS RS s(showExamples(hl), hl)
                for (i = 0; i < length(oExamples); i++) {
                    example = oExamples[i]

                    if (isRTL(il)) { # target language is R-to-L
                        sub(/\u003cb\u003e/, "", example)
                        sub(/\u003c\/b\u003e/, "", example)
                    } else { # target language is L-to-R
                        sub(/\u003cb\u003e/, AnsiCode["negative"] AnsiCode["bold"], example)
                        sub(/\u003c\/b\u003e/, AnsiCode["positive"] AnsiCode["no bold"], example)
                    }
                    r = (i > 0 ? r RS : r) RS ins(1, "- " example, il)
                }
            }
            # See also
            if (isarray(oSeeAlso) && anything(oSeeAlso)) {
                r = r RS RS s(showSeeAlso(hl), hl)
                temp = isRTL(il) ? oSeeAlso[0] : ansi("underline", oSeeAlso[0])
                for (k = 1; k < length(oSeeAlso); k++)
                    temp = temp ", " (isRTL(il) ? oSeeAlso[k] : ansi("underline", oSeeAlso[k]))
                r = r RS ins(1, temp, il)
            }
        }

        if (wShowDictionary) {
            # Display: dictionary entries
            if (r) r = r RS
            r = r m("-- display dictionary entries")
            for (i = 0; i < length(wordClasses); i++) {
                r = (i > 0 ? r RS : r) RS s(wordClasses[i], hl)
                for (j = 0; j < length(words[i]); j++) {
                    word = words[i][j][0]
                    explanation = join(words[i][j][1], ", ")
                    article = words[i][j][4]

                    r = r RS ansi("bold", ins(1, (article ? "(" article ") " : "") word, tl))
                    r = r RS ins(2, explanation, il)
                }
            }
        }

        if (wShowAlternatives) {
            # Display: alternative translations
            if (r) r = r RS RS
            r = r m("-- display alternative translations")
            for (i = 0; i < length(altTranslations); i++) {
                r = (i > 0 ? r RS : r) ansi("underline", show(segments[i]))
                temp = isRTL(tl) ? altTranslations[i][0] : ansi("bold", altTranslations[i][0])
                for (j = 1; j < length(altTranslations[i]); j++)
                    temp = temp ", " (isRTL(tl) ? altTranslations[i][j] : ansi("bold", altTranslations[i][j]))
                r = r RS ins(1, temp)
            }
        }

        if (toSpeech) {
            if (index(showTranslationsOf(hl, "%s"), "%s") > 2) {
                returnPlaylist[0]["text"] = showTranslationsOf(hl)
                returnPlaylist[0]["tl"] = hl
                returnPlaylist[1]["text"] = join(original)
                returnPlaylist[1]["tl"] = il
            } else {
                returnPlaylist[0]["text"] = join(original)
                returnPlaylist[0]["tl"] = il
                returnPlaylist[1]["text"] = showTranslationsOf(hl)
                returnPlaylist[1]["tl"] = hl
            }
            returnPlaylist[2]["text"] = translation
            returnPlaylist[2]["tl"] = tl
        }
    }

    return r
}

# Translate a file.
function fileTranslation(uri,    group, temp1, temp2) {
    temp1 = Option["input"]
    temp2 = Option["verbose"]

    match(uri, /^file:\/\/(.*)/, group)
    Option["input"] = group[1]
    Option["verbose"] = 0

    translateMain()

    Option["input"] = temp1
    Option["verbose"] = temp2
}

# Start a browser session and translate a web page.
function webTranslation(uri, sl, tl, hl) {
    system(Option["browser"] " " parameterize("https://translate.google.com/translate?" \
                                              "hl=" hl "&sl=" sl "&tl=" tl "&u=" uri) "&")
}

# Translate the source text (into all target languages).
function translate(text, inline,
                   ####
                   i, j, playlist, saveSortedIn) {

    if (!getCode(Option["hl"])) {
        # Check if home language is supported
        w("[WARNING] Unknown language code: " Option["hl"] ", fallback to English: en")
        Option["hl"] = "en" # fallback to English
    } else if (isRTL(Option["hl"])) {
        # Check if home language is R-to-L
        if (!FriBidi)
            w("[WARNING] " getName(Option["hl"]) " is a right-to-left language, but FriBidi cannot be found.")
    }

    if (!getCode(Option["sl"])) {
        # Check if source language is supported
        w("[WARNING] Unknown source language code: " Option["sl"])
    } else if (isRTL(Option["sl"])) {
        # Check if source language is R-to-L
        if (!FriBidi)
            w("[WARNING] " getName(Option["sl"]) " is a right-to-left language, but FriBidi cannot be found.")
    }

    saveSortedIn = PROCINFO["sorted_in"]
    PROCINFO["sorted_in"] = "@ind_num_asc"
    for (i in Option["tl"]) {
        # Non-interactive verbose mode: separator between targets
        if (!Option["interactive"])
            if (Option["verbose"] && i > 1)
                p(replicate("─", Option["width"]))

        if (inline &&
            startsWithAny(text, UriSchemes) == "file://") {
            # translate URL only from command-line parameters (inline)
            fileTranslation(text)
        } else if (inline &&
                   startsWithAny(text, UriSchemes) == "http://" ||
                   startsWithAny(text, UriSchemes) == "https://") {
            # translate URL only from command-line parameters (inline)
            webTranslation(text, Option["sl"], Option["tl"][i], Option["hl"])
        } else {
            p(getTranslation(text, Option["sl"], Option["tl"][i], Option["hl"], Option["verbose"], Option["play"], playlist))

            if (Option["play"])
                if (Option["player"])
                    for (j in playlist)
                        play(playlist[j]["text"], playlist[j]["tl"])
                else if (SpeechSynthesizer)
                    for (j in playlist)
                        print playlist[j]["text"] | SpeechSynthesizer
        }
    }
    PROCINFO["sorted_in"] = saveSortedIn
}

# Read from input and translate each line.
function translateMain(    i, line) {
    if (Option["interactive"])
        prompt()

    i = 0
    while (getline line < Option["input"]) {
        # Non-interactive verbose mode: separator between sources
        if (!Option["interactive"])
            if (Option["verbose"] && i++ > 0)
                p(replicate("═", Option["width"]))

        if (Option["interactive"]) {
            if (line ~ /:(q|quit)/)
                exit
            else if (line ~ /:(s|source)/)
                print Option["sl"]
            else if (line ~ /:(t|target)/) {
                printf "(" Option["tl"][1]
                for (i = 2; i <= length(Option["tl"]); i++)
                    printf ", " Option["tl"][i]
                print ")"
            }

            else {
                translate(line)

                # Interactive verbose mode: newline after each translation
                if (Option["verbose"]) printf RS
            }

            prompt()
        } else
            translate(line)
    }
}

####################################################################
# Translate.awk                                                    #
####################################################################

# Detect external terminal pager (less, more, most).
function initPager() {
    Pager = !system("less -V" SUPOUT SUPERR) ?
        "less" :
        (!system("more -V" SUPOUT SUPERR) ?
         "more" :
         (!system("most" SUPOUT SUPERR) ?
          "most" :
          ""))
}

# Detect external audio player (mplayer, mpv, mpg123).
function initAudioPlayer() {
    AudioPlayer = !system("mplayer" SUPOUT SUPERR) ?
        "mplayer" :
        (!system("mpv" SUPOUT SUPERR) ?
         "mpv" :
         (!system("mpg123 --version" SUPOUT SUPERR) ?
          "mpg123" :
          ""))
}

# Detect external speech synthesizer (say, espeak).
function initSpeechSynthesizer() {
    SpeechSynthesizer = !system("say ''" SUPOUT SUPERR) ?
        "say" :
        (!system("espeak ''" SUPOUT SUPERR) ?
         "espeak" :
         "")
}

# Initialize `HttpService`.
function initHttpService() {
    HttpProtocol = "http://"
    HttpHost = "translate.google.com"
    HttpPort = 80
    if (Option["proxy"]) {
        match(Option["proxy"], /^(http:\/*)?([^\/]*):([^\/:]*)/, HttpProxySpec)
        HttpService = "/inet/tcp/0/" HttpProxySpec[2] "/" HttpProxySpec[3]
        HttpPathPrefix = HttpProtocol HttpHost
    } else {
        HttpService = "/inet/tcp/0/" HttpHost "/" HttpPort
        HttpPathPrefix = ""
    }
}

# Pre-process string (URL-encode before send).
function preprocess(text) {
    return quote(text)
}

# Post-process string (remove any redundant whitespace).
function postprocess(text) {
    text = gensub(/ ([.,;:?!"])/, "\\1", "g", text)
    text = gensub(/(["]) /, "\\1", "g", text)
    return text
}

# Send an HTTP request and get response from Google Translate.
function getResponse(text, sl, tl, hl,    content, url) {
    url = HttpPathPrefix "/translate_a/single?client=t"                 \
        "&ie=UTF-8&oe=UTF-8"                                            \
        "&dt=bd&dt=ex&dt=ld&dt=md&dt=qca&dt=rw&dt=rm&dt=ss&dt=t&dt=at"  \
        "&q=" preprocess(text) "&sl=" sl "&tl=" tl "&hl=" hl

    print "GET " url " HTTP/1.1\n"             \
          "Host: " HttpHost "\n"               \
          "Connection: close\n" |& HttpService
    while ((HttpService |& getline) > 0)
        content = $0
    close(HttpService)

    return assert(content, "[ERROR] Null response.")
}

# Print a string (to output file or terminal pager).
function p(string) {
    if (Option["view"])
        print string | Option["pager"]
    else
        print string > Option["output"]
}

# Play using Google Text-to-Speech engine.
function play(text, tl,    url) {
    url = HttpProtocol HttpHost "/translate_tts?ie=UTF-8"       \
        "&tl=" tl "&q=" preprocess(text)

    # Don't use getline from pipe here - the same pipe will be run only once for each AWK script!
    system(Option["player"] " " parameterize(url) SUPOUT SUPERR)
}

# Get the translation of a string.
function getTranslation(text, sl, tl, hl,
                        isVerbose, toSpeech, returnPlaylist,
                        ####
                        r,
                        content, tokens, ast,
                        il, ils, isPhonetic,
                        article, example, explanation, ref, word,
                        translation, translations, phonetics,
                        wordClasses, words, segments, altTranslations,
                        original, oPhonetics, oWordClasses, oWords,
                        oRefs, oSynonyms, oExamples, oSeeAlso,
                        wShowOriginal, wShowOriginalPhonetics,
                        wShowTranslation, wShowTranslationPhonetics,
                        wShowPromptMessage, wShowLanguages,
                        wShowOriginalDictionary, wShowDictionary,
                        wShowAlternatives,
                        hasWordClasses, hasAltTranslations,
                        i, j, k, group, temp, saveSortedIn) {
    isPhonetic = match(tl, /^@/)
    tl = substr(tl, 1 + isPhonetic)

    if (!getCode(tl)) {
        # Check if target language is supported
        w("[WARNING] Unknown target language code: " tl)
    } else if (isRTL(tl)) {
        # Check if target language is R-to-L
        if (!FriBidi)
            w("[WARNING] " getName(tl) " is a right-to-left language, but FriBidi cannot be found.")
    }

    content = getResponse(text, sl, tl, hl)
    plTokenize(tokens, content)
    plParse(ast, tokens)

    # Debug mode
    if (Option["debug"]) {
        d(content)
        da(tokens, "tokens[%s]='%s'")
        da(ast, "ast[%s]='%s'")
    }

    if (!anything(ast)) {
        e("[ERROR] Oops! Something went wrong and I can't translate it for you :(")
        ExitCode = 1
        return
    }

    saveSortedIn = PROCINFO["sorted_in"]
    PROCINFO["sorted_in"] = "@ind_num_asc"
    for (i in ast) {
        if (i ~ "^0" SUBSEP "0" SUBSEP "[[:digit:]]+" SUBSEP "0$")
            append(translations, postprocess(literal(ast[i])))
        if (i ~ "^0" SUBSEP "0" SUBSEP "[[:digit:]]+" SUBSEP "1$")
            append(original, literal(ast[i]))
        if (i ~ "^0" SUBSEP "0" SUBSEP "[[:digit:]]+" SUBSEP "2$")
            append(phonetics, literal(ast[i]))
        if (i ~ "^0" SUBSEP "0" SUBSEP "[[:digit:]]+" SUBSEP "3$")
            append(oPhonetics, literal(ast[i]))

        # 1 - word classes and explanations
        if (match(i, "^0" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            wordClasses[group[1]] = literal(ast[i])
        if (match(i, "^0" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "2" SUBSEP "([[:digit:]]+)" SUBSEP "([[:digit:]]+)$", group))
            words[group[1]][group[2]][group[3]] = literal(ast[i])
        if (match(i, "^0" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "2" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)$", group))
            words[group[1]][group[2]]["1"][group[3]] = literal(ast[i])

        # 5 - alternative translations
        if (match(i, "^0" SUBSEP "5" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group)) {
            segments[group[1]] = literal(ast[i])
            altTranslations[group[1]][0] = ""
        }
        if (match(i, "^0" SUBSEP "5" SUBSEP "([[:digit:]]+)" SUBSEP "2" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            altTranslations[group[1]][group[2]] = postprocess(literal(ast[i]))

        # 8 - identified source languages
        if (i ~ "^0" SUBSEP "8" SUBSEP "0" SUBSEP "[[:digit:]]+$" ||
            i ~ "^0" SUBSEP "2$")
            append(ils, literal(ast[i]))

        # 11 - (original) word classes and synonyms
        if (match(i, "^0" SUBSEP "11" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            oWordClasses[group[1]] = literal(ast[i])
        if (match(i, "^0" SUBSEP "11" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "1$", group))
            if (ast[i]) {
                oRefs[literal(ast[i])][1] = group[1]
                oRefs[literal(ast[i])][2] = group[2]
            }
        if (match(i, "^0" SUBSEP "11" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "0" SUBSEP "([[:digit:]]+)$", group))
            oSynonyms[group[1]][group[2]][group[3]] = literal(ast[i])

        # 12 - (original) word classes and explanations
        if (match(i, "^0" SUBSEP "12" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            oWordClasses[group[1]] = literal(ast[i])
        if (match(i, "^0" SUBSEP "12" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            oWords[group[1]][group[2]][0] = literal(ast[i])
        if (match(i, "^0" SUBSEP "12" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "1$", group))
            oWords[group[1]][group[2]][1] = literal(ast[i])
        if (match(i, "^0" SUBSEP "12" SUBSEP "([[:digit:]]+)" SUBSEP "1" SUBSEP "([[:digit:]]+)" SUBSEP "2$", group))
            oWords[group[1]][group[2]][2] = postprocess(literal(ast[i]))

        # 13 - (original) examples
        if (match(i, "^0" SUBSEP "13" SUBSEP "0" SUBSEP "([[:digit:]]+)" SUBSEP "0$", group))
            oExamples[group[1]] = postprocess(literal(ast[i]))

        # 14 - (original) see also
        if (match(i, "^0" SUBSEP "14" SUBSEP "0" SUBSEP "([[:digit:]]+)$", group))
            oSeeAlso[group[1]] = literal(ast[i])
    }
    PROCINFO["sorted_in"] = saveSortedIn

    translation = join(translations)

    il = !anything(ils) || belongsTo(sl, ils) ? sl : ils[0]

    # Generate output
    if (!isVerbose) {
        # Brief mode

        r = isPhonetic && anything(phonetics) ?
            join(phonetics) :  # phonetic transcription
            s(translation, tl) # target language

        if (toSpeech) {
            returnPlaylist[0]["text"] = translation
            returnPlaylist[0]["tl"] = tl
        }

    } else {
        # Verbose mode

        wShowOriginal = Option["show-original"]
        wShowOriginalPhonetics = Option["show-original-phonetics"]
        wShowTranslation = Option["show-translation"]
        wShowTranslationPhonetics = Option["show-translation-phonetics"]
        wShowPromptMessage = Option["show-prompt-message"]
        wShowLanguages = Option["show-languages"]
        wShowOriginalDictionary = Option["show-original-dictionary"]
        wShowDictionary = Option["show-dictionary"]
        wShowAlternatives = Option["show-alternatives"]

        if (!anything(oPhonetics)) wShowOriginalPhonetics = 0
        if (!anything(phonetics)) wShowTranslationPhonetics = 0
        if (il == tl && isarray(oWordClasses)) {
            wShowOriginalDictionary = 1
            wShowTranslation = 0
        }
        hasWordClasses = isarray(wordClasses) && anything(wordClasses)
        hasAltTranslations = isarray(altTranslations[0]) && anything(altTranslations[0])
        if (!hasWordClasses) wShowDictionary = 0
        if (hasWordClasses || !hasAltTranslations) wShowAlternatives = 0

        if (wShowOriginal) {
            # Display: original text
            if (r) r = r RS RS
            r = r ansi("negative", ansi("bold", s(join(original), il)))
            if (wShowOriginalPhonetics)
                r = r RS showPhonetics(join(oPhonetics), il)
        }

        if (wShowTranslation) {
            # Display: major translation & phonetics
            if (r) r = r RS RS
            r = r ansi("bold", s(translation, tl))
            if (wShowTranslationPhonetics)
                r = r RS showPhonetics(join(phonetics), tl)
        }

        if (wShowPromptMessage || wShowLanguages)
            if (r) r = r RS
        if (wShowPromptMessage) {
            # Display: prompt message
            if (hasWordClasses) {
                # Definitions of
                if (r) r = r RS
                if (isRTL(hl)) # home language is R-to-L
                    r = r s(showDefinitionsOf(hl, join(original)))
                else # home language is L-to-R
                    r = r showDefinitionsOf(hl, ansi("underline", show(join(original), il)))
            } else if (hasAltTranslations) {
                # Translations of
                if (r) r = r RS
                if (isRTL(hl)) # home language is R-to-L
                    r = r s(showTranslationsOf(hl, join(original)))
                else # home language is L-to-R
                    r = r showTranslationsOf(hl, ansi("underline", show(join(original), il)))
            }
        }
        if (wShowLanguages) {
            # Display: source language -> target language
            if (hasWordClasses || hasAltTranslations) {
                if (r) r = r RS
                r = r s(sprintf("[ %s -> %s ]", getEndonym(il), getEndonym(tl)))
            }
        }

        if (wShowOriginalDictionary) {
            # Display: original dictionary
            if (r) r = r RS
            if (isarray(oWordClasses) && anything(oWordClasses)) {
                for (i = 0; i < length(oWordClasses); i++) {
                    r = (i > 0 ? r RS : r) RS s(oWordClasses[i], hl)
                    if (isarray(oWords[i])) {
                        # Detailed explanation
                        for (j = 0; j < length(oWords[i]); j++) {
                            explanation = oWords[i][j][0]
                            ref = oWords[i][j][1]
                            example = oWords[i][j][2]

                            r = (j > 0 ? r RS : r) RS ansi("bold", ins(1, explanation, il))
                            if (example)
                                r = r RS ins(2, "- \"" example "\"", il)
                            if (ref && isarray(oRefs[ref])) {
                                temp = showSynonyms(hl) ": " oSynonyms[oRefs[ref][1]][oRefs[ref][2]][0]
                                for (k = 1; k < length(oSynonyms[oRefs[ref][1]][oRefs[ref][2]]); k++)
                                    temp = temp ", " oSynonyms[oRefs[ref][1]][oRefs[ref][2]][k]
                                r = r RS ins(1, temp)
                            }
                        }
                    } else {
                        # Synonyms only
                        for (j = 0; j < length(oSynonyms[i]); j++) {
                            temp = "* " oSynonyms[i][j][0]
                            for (k = 1; k < length(oSynonyms[i][j]); k++)
                                temp = temp ", " oSynonyms[i][j][k]
                            r = r RS ins(1, temp)
                        }
                    }
                }
            }
            # Examples
            if (isarray(oExamples) && anything(oExamples)) {
                r = r RS RS s(showExamples(hl), hl)
                for (i = 0; i < length(oExamples); i++) {
                    example = oExamples[i]

                    if (isRTL(il)) { # target language is R-to-L
                        sub(/\u003cb\u003e/, "", example)
                        sub(/\u003c\/b\u003e/, "", example)
                    } else { # target language is L-to-R
                        sub(/\u003cb\u003e/, AnsiCode["negative"] AnsiCode["bold"], example)
                        sub(/\u003c\/b\u003e/, AnsiCode["positive"] AnsiCode["no bold"], example)
                    }
                    r = (i > 0 ? r RS : r) RS ins(1, "- " example, il)
                }
            }
            # See also
            if (isarray(oSeeAlso) && anything(oSeeAlso)) {
                r = r RS RS s(showSeeAlso(hl), hl)
                temp = isRTL(il) ? oSeeAlso[0] : ansi("underline", oSeeAlso[0])
                for (k = 1; k < length(oSeeAlso); k++)
                    temp = temp ", " (isRTL(il) ? oSeeAlso[k] : ansi("underline", oSeeAlso[k]))
                r = r RS ins(1, temp, il)
            }
        }

        if (wShowDictionary) {
            # Display: dictionary entries
            if (r) r = r RS
            for (i = 0; i < length(wordClasses); i++) {
                r = (i > 0 ? r RS : r) RS s(wordClasses[i], hl)
                for (j = 0; j < length(words[i]); j++) {
                    word = words[i][j][0]
                    explanation = join(words[i][j][1], ", ")
                    article = words[i][j][4]

                    r = r RS ansi("bold", ins(1, (article ? "(" article ") " : "") word, tl))
                    r = r RS ins(2, explanation, il)
                }
            }
        }

        if (wShowAlternatives) {
            # Display: alternative translations
            if (r) r = r RS RS
            for (i = 0; i < length(altTranslations); i++) {
                r = (i > 0 ? r RS : r) ansi("underline", show(segments[i]))
                temp = isRTL(tl) ? altTranslations[i][0] : ansi("bold", altTranslations[i][0])
                for (j = 1; j < length(altTranslations[i]); j++)
                    temp = temp ", " (isRTL(tl) ? altTranslations[i][j] : ansi("bold", altTranslations[i][j]))
                r = r RS ins(1, temp)
            }
        }

        if (toSpeech) {
            if (index(showTranslationsOf(hl, "%s"), "%s") > 2) {
                returnPlaylist[0]["text"] = showTranslationsOf(hl)
                returnPlaylist[0]["tl"] = hl
                returnPlaylist[1]["text"] = join(original)
                returnPlaylist[1]["tl"] = il
            } else {
                returnPlaylist[0]["text"] = join(original)
                returnPlaylist[0]["tl"] = il
                returnPlaylist[1]["text"] = showTranslationsOf(hl)
                returnPlaylist[1]["tl"] = hl
            }
            returnPlaylist[2]["text"] = translation
            returnPlaylist[2]["tl"] = tl
        }
    }

    return r
}

# Translate a file.
function fileTranslation(uri,    group, temp1, temp2) {
    temp1 = Option["input"]
    temp2 = Option["verbose"]

    match(uri, /^file:\/\/(.*)/, group)
    Option["input"] = group[1]
    Option["verbose"] = 0

    translateMain()

    Option["input"] = temp1
    Option["verbose"] = temp2
}

# Start a browser session and translate a web page.
function webTranslation(uri, sl, tl, hl) {
    system(Option["browser"] " " parameterize("https://translate.google.com/translate?" \
                                              "hl=" hl "&sl=" sl "&tl=" tl "&u=" uri) "&")
}

# Translate the source text (into all target languages).
function translate(text, inline,
                   ####
                   i, j, playlist, saveSortedIn) {

    if (!getCode(Option["hl"])) {
        # Check if home language is supported
        w("[WARNING] Unknown language code: " Option["hl"] ", fallback to English: en")
        Option["hl"] = "en" # fallback to English
    } else if (isRTL(Option["hl"])) {
        # Check if home language is R-to-L
        if (!FriBidi)
            w("[WARNING] " getName(Option["hl"]) " is a right-to-left language, but FriBidi cannot be found.")
    }

    if (!getCode(Option["sl"])) {
        # Check if source language is supported
        w("[WARNING] Unknown source language code: " Option["sl"])
    } else if (isRTL(Option["sl"])) {
        # Check if source language is R-to-L
        if (!FriBidi)
            w("[WARNING] " getName(Option["sl"]) " is a right-to-left language, but FriBidi cannot be found.")
    }

    saveSortedIn = PROCINFO["sorted_in"]
    PROCINFO["sorted_in"] = "@ind_num_asc"
    for (i in Option["tl"]) {
        # Non-interactive verbose mode: separator between targets
        if (!Option["interactive"])
            if (Option["verbose"] && i > 1)
                p(replicate("─", Option["width"]))

        if (inline &&
            startsWithAny(text, UriSchemes) == "file://") {
            # translate URL only from command-line parameters (inline)
            fileTranslation(text)
        } else if (inline &&
                   startsWithAny(text, UriSchemes) == "http://" ||
                   startsWithAny(text, UriSchemes) == "https://") {
            # translate URL only from command-line parameters (inline)
            webTranslation(text, Option["sl"], Option["tl"][i], Option["hl"])
        } else {
            p(getTranslation(text, Option["sl"], Option["tl"][i], Option["hl"], Option["verbose"], Option["play"], playlist))

            if (Option["play"])
                if (Option["player"])
                    for (j in playlist)
                        play(playlist[j]["text"], playlist[j]["tl"])
                else if (SpeechSynthesizer)
                    for (j in playlist)
                        print playlist[j]["text"] | SpeechSynthesizer
        }
    }
    PROCINFO["sorted_in"] = saveSortedIn
}

# Read from input and translate each line.
function translateMain(    i, line) {
    if (Option["interactive"])
        prompt()

    i = 0
    while (getline line < Option["input"]) {
        # Non-interactive verbose mode: separator between sources
        if (!Option["interactive"])
            if (Option["verbose"] && i++ > 0)
                p(replicate("═", Option["width"]))

        if (Option["interactive"]) {
            if (line ~ /:(q|quit)/)
                exit
            else if (line ~ /:(s|source)/)
                print Option["sl"]
            else if (line ~ /:(t|target)/) {
                printf "(" Option["tl"][1]
                for (i = 2; i <= length(Option["tl"]); i++)
                    printf ", " Option["tl"][i]
                print ")"
            }

            else {
                translate(line)

                # Interactive verbose mode: newline after each translation
                if (Option["verbose"]) printf RS
            }

            prompt()
        } else
            translate(line)
    }
}


Translate: preprocessByDump()

Translate: preprocessByDump()

####################################################################
# Translate.awk                                                    #
####################################################################

function provides(engineName) {
    Translator[tolower(engineName)] = TRUE
}

function engineMethod(methodName,    engine, translator) {
    if (!Translator[Option["engine"]]) {
        # case-insensitive match engine name
        engine = tolower(Option["engine"])
        if (!Translator[engine]) # fuzzy match engine name
            for (translator in Translator)
                if (Translator[translator] && # there IS such a translator
                    translator ~ "^"engine) {
                    engine = translator
                    break
                }
        if (!Translator[engine]) {
            e("[ERROR] Translator not found: " Option["engine"] "\n"    \
              "        Run '-list-engines / -S' to see a list of available engines.")
            exit 1
        }
        Option["engine"] = engine
    }
    return Option["engine"] methodName
}

# Detect external audio player (mplayer, mpv, mpg123).
function initAudioPlayer() {
    AudioPlayer = !system("mpv" SUPOUT SUPERR) ?
        "mpv --no-config" :
        (!system("mplayer" SUPOUT SUPERR) ?
         "mplayer" :
         (!system("mpg123 --version" SUPOUT SUPERR) ?
          "mpg123" :
          ""))
}

# Detect external speech synthesizer (say, espeak).
function initSpeechSynthesizer() {
    SpeechSynthesizer = !system("say ''" SUPOUT SUPERR) ?
        "say" :
        (!system("espeak ''" SUPOUT SUPERR) ?
         "espeak" :
         "")
}

# Detect external terminal pager (less, more, most).
function initPager() {
    Pager = !system("less -V" SUPOUT SUPERR) ?
        "less" :
        (!system("more -V" SUPOUT SUPERR) ?
         "more" :
         (!system("most" SUPOUT SUPERR) ?
          "most" :
          ""))
}

# Initialize `HttpService`.
function initHttpService(    inet) {
    _Init()

    inet = "inet"
    if (Option["ip-version"])
        inet = inet Option["ip-version"]
    if (Option["proxy"]) {
        match(Option["proxy"], /^(http:\/*)?(([^:]+):([^@]+)@)?([^\/]*):([^\/:]*)/, HttpProxySpec)
        HttpAuthUser = HttpProxySpec[3]
        HttpAuthPass = HttpProxySpec[4]
        HttpAuthCredentials = base64(unquote(HttpAuthUser) ":" HttpAuthPass)
        HttpService = "/" inet "/tcp/0/" HttpProxySpec[5] "/" HttpProxySpec[6]
        HttpPathPrefix = HttpProtocol HttpHost
    } else {
        HttpService = "/" inet "/tcp/0/" HttpHost "/" HttpPort
        HttpPathPrefix = ""
    }

    # How long to wait in milliseconds for IPv6 before trying IPv4
    PROCINFO[HttpService, "READ_TIMEOUT"] = 2000
}

# Pre-process string (URL-encode before send).
function preprocess(text) {
    return quote(text)
}

# Pre-process string (using hexdump to URL-encode everything).
function preprocessByDump(text,    arr, len, temp) {
    len = dumpX(text, arr)
    temp = ""
    for (i = 1; i <= len; i++)
        temp = temp "%" arr[i]
    return temp
}

# [OBSOLETE] Is this function still relevant?
# Post-process string (remove any redundant whitespace).
function postprocess(text) {
    text = gensub(/ ([.,;:?!"])/, "\\1", "g", text)
    text = gensub(/(["]) /, "\\1", "g", text)
    return text
}

# Send an HTTP GET request and get response from an online translator.
function getResponse(text, sl, tl, hl,
                     ####
                     content, header, isBody, url, group, status, location) {
    url = _RequestUrl(text, sl, tl, hl)

    header = "GET " url " HTTP/1.1\r\n"           \
        "Host: " HttpHost "\r\n"                  \
        "Connection: close\r\n"
    if (Option["user-agent"])
        header = header "User-Agent: " Option["user-agent"] "\r\n"
    if (Cookie)
        header = header "Cookie: " Cookie "\r\n"
    if (HttpAuthUser && HttpAuthPass)
        # TODO: digest auth
        header = header "Proxy-Authorization: Basic " HttpAuthCredentials "\r\n"

    content = NULLSTR; isBody = 0
    while (1) {
        print header |& HttpService
        while ((HttpService |& getline) > 0) {
            if (isBody)
                content = content ? content "\n" $0 : $0
            else if (length($0) <= 1)
                isBody = 1
            else { # interesting fields in header
                match($0, /^HTTP[^ ]* ([^ ]*)/, group)
                if (RSTART) status = group[1]
                match($0, /^Location: (.*)/, group)
                if (RSTART) location = squeeze(group[1]) # squeeze the URL!
            }
            l(sprintf("%4s bytes > %s", length($0), $0))
        }
        close(HttpService)

        if (ERRNO == "Connection timed out") {
            w("[WARNING] " ERRNO ". Retrying IPv4 connection.")
            Option["ip-version"] = 4
            initHttpService()
            PROCINFO[HttpService, "READ_TIMEOUT"] = 0
            ERRNO = ""
        } else
            break
    }

    if ((status == "301" || status == "302") && location)
        content = curl(location)

    return assert(content, "[ERROR] Null response.")
}

# Send an HTTP POST request and return response from an online translator.
function postResponse(text, sl, tl, hl, type,
                      ####
                      content, contentLength, contentType, group,
                      header, isBody, reqBody, url, status, location) {
    url = _PostRequestUrl(text, sl, tl, hl, type)
    contentType = _PostRequestContentType(text, sl, tl, hl, type)
    reqBody = _PostRequestBody(text, sl, tl, hl, type)
    if (DumpContentengths[reqBody])
        contentLength = DumpContentengths[reqBody]
    else
        contentLength = DumpContentengths[reqBody] = dump(reqBody, group)

    header = "POST " url " HTTP/1.1\r\n"                  \
        "Host: " HttpHost "\r\n"                          \
        "Connection: close\r\n"                           \
        "Content-Length: " contentLength "\r\n"           \
        "Content-Type: " contentType "\r\n"     # must!
    if (Option["user-agent"])
        header = header "User-Agent: " Option["user-agent"] "\r\n"
    if (Cookie)
        header = header "Cookie: " Cookie "\r\n"
    if (HttpAuthUser && HttpAuthPass)
        # TODO: digest auth
        header = header "Proxy-Authorization: Basic " HttpAuthCredentials "\r\n"

    content = NULLSTR; isBody = 0
    while (1) {
        print (header "\r\n" reqBody) |& HttpService
        while ((HttpService |& getline) > 0) {
            if (isBody)
                content = content ? content "\n" $0 : $0
            else if (length($0) <= 1)
                isBody = 1
            else { # interesting fields in header
                match($0, /^HTTP[^ ]* ([^ ]*)/, group)
                if (RSTART) status = group[1]
                match($0, /^Location: (.*)/, group)
                if (RSTART) location = squeeze(group[1]) # squeeze the URL!
            }
            l(sprintf("%4s bytes > %s", length($0), $0))
        }
        close(HttpService)

        if (ERRNO == "Connection timed out") {
            w("[WARNING] " ERRNO ". Retrying IPv4 connection.")
            Option["ip-version"] = 4
            initHttpService()
            PROCINFO[HttpService, "READ_TIMEOUT"] = 0
            ERRNO = ""
        } else
            break
    }

    if (status == "404") {
        e("[ERROR] 404 Not Found")
        exit 1
    }
    if ((status == "301" || status == "302") && location) {
        url = "https" substr(url, 5) # switch to HTTPS; don't use location!
        content = curlPost(url, reqBody)
    }

    return content
}

# Print a string (to output file or terminal pager).
function p(string) {
    if (Option["view"]) {
        print string | Option["pager"]
        close(Option["pager"])
    } else
        print string > Option["output"]
}

# Play using a Text-to-Speech engine.
function play(text, tl,    url, status) {
    url = _TTSUrl(text, tl)

    # Don't use getline from pipe here - the same pipe will be run only once for each AWK script!
    status = system(Option["player"] " " parameterize(url) SUPOUT SUPERR)

    if (status)
        w("Voice output isn't available for " getName(tl))

    return status
}

# Download audio from a Text-to-Speech engine.
function download_audio(text, tl,    url, output) {
    url = _TTSUrl(text, tl)

    if (Option["download-audio-as"])
        output = Option["download-audio-as"]
    else
        output = text " [" Option["engine"] "] (" Option["narrator"] ").ts"

    if (url ~ /^\//)
        system("mv -- " parameterize(url) " " parameterize(output))
    else
        curl(url, output)
}

# Get the translation of a string.
function getTranslation(text, sl, tl, hl,
                        isVerbose, toSpeech, returnPlaylist, returnIl) {
    return _Translate(text, sl, tl, hl,
                      isVerbose, toSpeech, returnPlaylist, returnIl)
}

# Translate a file.
function fileTranslation(uri,    group, temp1, temp2) {
    temp1 = Option["input"]
    temp2 = Option["verbose"]

    match(uri, /^file:\/\/(.*)/, group)
    Option["input"] = group[1]
    Option["verbose"] = 0

    translateMain()

    Option["input"] = temp1
    Option["verbose"] = temp2
}

# Start a browser session and translate a web page.
function webTranslation(uri, sl, tl, hl,    temp) {
    temp = _WebTranslateUrl(uri, sl, tl, hl)
    if (temp) {
        p(temp)
        if (Option["browser"] != NONE)
            system(Option["browser"] " " parameterize(temp) SUPOUT SUPERR)
    }
}

# Translate the source text (into all target languages).
function translate(text, inline,
                   ####
                   i, j, playlist, il, saveSortedIn) {

    if (!getCode(Option["hl"])) {
        # Check if home language is supported
        w("[WARNING] Unknown language code: " Option["hl"] ", fallback to English: en")
        Option["hl"] = "en" # fallback to English
    } else if (isRTL(Option["hl"])) {
        # Check if home language is R-to-L
        if (!FriBidi)
            w("[WARNING] " getName(Option["hl"]) " is a right-to-left language, but FriBidi is not found.")
    }

    if (!getCode(Option["sl"])) {
        # Check if source language is supported
        w("[WARNING] Unknown source language code: " Option["sl"])
    } else if (isRTL(Option["sl"])) {
        # Check if source language is R-to-L
        if (!FriBidi)
            w("[WARNING] " getName(Option["sl"]) " is a right-to-left language, but FriBidi is not found.")
    }

    saveSortedIn = PROCINFO["sorted_in"]
    PROCINFO["sorted_in"] = "@ind_num_asc"
    for (i in Option["tl"]) {
        # Non-interactive verbose mode: separator between targets
        if (!Option["interactive"])
            if (Option["verbose"] && i > 1)
                p(prettify("target-seperator", replicate(Option["chr-target-seperator"], Option["width"])))

        if (inline &&
            startsWithAny(text, UriSchemes) == "file://") {
            # translate URL only from command-line parameters (inline)
            fileTranslation(text)
        } else if (inline &&
                   startsWithAny(text, UriSchemes) == "http://" ||
                   startsWithAny(text, UriSchemes) == "https://") {
            # translate URL only from command-line parameters (inline)
            webTranslation(text, Option["sl"], Option["tl"][i], Option["hl"])
        } else {
            if (!Option["no-translate"])
                p(getTranslation(text, Option["sl"], Option["tl"][i], Option["hl"], Option["verbose"], Option["play"] || Option["download-audio"], playlist, il))
            else
                il[0] = Option["sl"] == "auto" ? "en" : Option["sl"]

            if (Option["play"] == 1) {
                if (Option["player"])
                    for (j in playlist)
                        play(playlist[j]["text"], playlist[j]["tl"])
                else if (SpeechSynthesizer)
                    for (j in playlist)
                        print playlist[j]["text"] | SpeechSynthesizer
            } else if (Option["play"] == 2) {
                if (Option["player"])
                    play(text, il[0])
                else if (SpeechSynthesizer)
                    print text | SpeechSynthesizer
            }

            if (Option["download-audio"] == 1) {
                # Download the translation unless used with -sp or -no-trans
                if (Option["play"] != 2 && !Option["no-translate"])
                    download_audio(playlist[length(playlist) - 1]["text"], \
                                   playlist[length(playlist) - 1]["tl"])
                else
                    download_audio(text, il[0])
            }
        }
    }
    PROCINFO["sorted_in"] = saveSortedIn
}

# Translate the source text (from all source languages).
function translates(text, inline,
                    ####
                    i) {
    saveSortedIn = PROCINFO["sorted_in"]
    PROCINFO["sorted_in"] = "@ind_num_asc"
    for (i in Option["sls"]) {
        # Non-interactive verbose mode: separator between sources
        if (!Option["interactive"])
            if (Option["verbose"] && i > 1)
                p(prettify("target-seperator", replicate(Option["chr-target-seperator"], Option["width"])))

        Option["sl"] = Option["sls"][i]
        translate(text, inline)
    }
    PROCINFO["sorted_in"] = saveSortedIn
}

# Read from input and translate each line.
function translateMain(    i, line) {
    if (Option["interactive"])
        prompt()

    if (Option["input"] == STDIN || fileExists(Option["input"])) {
        i = 0
        while (getline line < Option["input"])
            if (line) {
                # Non-interactive verbose mode: separator between sources
                if (!Option["interactive"])
                    if (Option["verbose"] && i++ > 0)
                        p(prettify("source-seperator",
                                   replicate(Option["chr-source-seperator"],
                                             Option["width"])))

                if (Option["interactive"])
                    repl(line)
                else
                    translates(line)
            } else {
                # Non-interactive brief mode: preserve line breaks
                if (!Option["interactive"])
                    if (!Option["verbose"])
                        p(line)
            }
    } else
        e("[ERROR] File not found: " Option["input"])
}

####################################################################
# Translate.awk                                                    #
####################################################################

function provides(engineName) {
    Translator[tolower(engineName)] = TRUE
}

function engineMethod(methodName,    engine, translator) {
    if (!Translator[Option["engine"]]) {
        # case-insensitive match engine name
        engine = tolower(Option["engine"])
        if (!Translator[engine]) # fuzzy match engine name
            for (translator in Translator)
                if (Translator[translator] && # there IS such a translator
                    translator ~ "^"engine) {
                    engine = translator
                    break
                }
        if (!Translator[engine]) {
            e("[ERROR] Translator not found: " Option["engine"] "\n"    \
              "        Run '-list-engines / -S' to see a list of available engines.")
            exit 1
        }
        Option["engine"] = engine
    }
    return Option["engine"] methodName
}

# Detect external audio player (mplayer, mpv, mpg123).
function initAudioPlayer() {
    AudioPlayer = !system("mpv" SUPOUT SUPERR) ?
        "mpv --no-config" :
        (!system("mplayer" SUPOUT SUPERR) ?
         "mplayer" :
         (!system("mpg123 --version" SUPOUT SUPERR) ?
          "mpg123" :
          ""))
}

# Detect external speech synthesizer (say, espeak).
function initSpeechSynthesizer() {
    SpeechSynthesizer = !system("say ''" SUPOUT SUPERR) ?
        "say" :
        (!system("espeak ''" SUPOUT SUPERR) ?
         "espeak" :
         "")
}

# Detect external terminal pager (less, more, most).
function initPager() {
    Pager = !system("less -V" SUPOUT SUPERR) ?
        "less" :
        (!system("more -V" SUPOUT SUPERR) ?
         "more" :
         (!system("most" SUPOUT SUPERR) ?
          "most" :
          ""))
}

# Initialize `HttpService`.
function initHttpService(    inet) {
    _Init()

    inet = "inet"
    if (Option["ip-version"])
        inet = inet Option["ip-version"]
    if (Option["proxy"]) {
        match(Option["proxy"], /^(http:\/*)?(([^:]+):([^@]+)@)?([^\/]*):([^\/:]*)/, HttpProxySpec)
        HttpAuthUser = HttpProxySpec[3]
        HttpAuthPass = HttpProxySpec[4]
        HttpAuthCredentials = base64(unquote(HttpAuthUser) ":" HttpAuthPass)
        HttpService = "/" inet "/tcp/0/" HttpProxySpec[5] "/" HttpProxySpec[6]
        HttpPathPrefix = HttpProtocol HttpHost
    } else {
        HttpService = "/" inet "/tcp/0/" HttpHost "/" HttpPort
        HttpPathPrefix = ""
    }

    # How long to wait in milliseconds for IPv6 before trying IPv4
    PROCINFO[HttpService, "READ_TIMEOUT"] = 2000
}

# Pre-process string (URL-encode before send).
function preprocess(text) {
    return quote(text)
}

# [OBSOLETE] Is this function still relevant?
# Post-process string (remove any redundant whitespace).
function postprocess(text) {
    text = gensub(/ ([.,;:?!"])/, "\\1", "g", text)
    text = gensub(/(["]) /, "\\1", "g", text)
    return text
}

# Send an HTTP GET request and get response from an online translator.
function getResponse(text, sl, tl, hl,
                     ####
                     content, header, isBody, url, group, status, location) {
    url = _RequestUrl(text, sl, tl, hl)

    header = "GET " url " HTTP/1.1\r\n"           \
        "Host: " HttpHost "\r\n"                  \
        "Connection: close\r\n"
    if (Option["user-agent"])
        header = header "User-Agent: " Option["user-agent"] "\r\n"
    if (Cookie)
        header = header "Cookie: " Cookie "\r\n"
    if (HttpAuthUser && HttpAuthPass)
        # TODO: digest auth
        header = header "Proxy-Authorization: Basic " HttpAuthCredentials "\r\n"

    content = NULLSTR; isBody = 0
    while (1) {
        print header |& HttpService
        while ((HttpService |& getline) > 0) {
            if (isBody)
                content = content ? content "\n" $0 : $0
            else if (length($0) <= 1)
                isBody = 1
            else { # interesting fields in header
                match($0, /^HTTP[^ ]* ([^ ]*)/, group)
                if (RSTART) status = group[1]
                match($0, /^Location: (.*)/, group)
                if (RSTART) location = squeeze(group[1]) # squeeze the URL!
            }
            l(sprintf("%4s bytes > %s", length($0), $0))
        }
        close(HttpService)

        if (ERRNO == "Connection timed out") {
            w("[WARNING] " ERRNO ". Retrying IPv4 connection.")
            Option["ip-version"] = 4
            initHttpService()
            PROCINFO[HttpService, "READ_TIMEOUT"] = 0
            ERRNO = ""
        } else
            break
    }

    if ((status == "301" || status == "302") && location)
        content = curl(location)

    return assert(content, "[ERROR] Null response.")
}

# Send an HTTP POST request and return response from an online translator.
function postResponse(text, sl, tl, hl, type,
                      ####
                      content, contentLength, contentType, group,
                      header, isBody, reqBody, url, status, location) {
    url = _PostRequestUrl(text, sl, tl, hl, type)
    contentType = _PostRequestContentType(text, sl, tl, hl, type)
    reqBody = _PostRequestBody(text, sl, tl, hl, type)
    if (DumpContentengths[reqBody])
        contentLength = DumpContentengths[reqBody]
    else
        contentLength = DumpContentengths[reqBody] = dump(reqBody, group)

    header = "POST " url " HTTP/1.1\r\n"                  \
        "Host: " HttpHost "\r\n"                          \
        "Connection: close\r\n"                           \
        "Content-Length: " contentLength "\r\n"           \
        "Content-Type: " contentType "\r\n"     # must!
    if (Option["user-agent"])
        header = header "User-Agent: " Option["user-agent"] "\r\n"
    if (Cookie)
        header = header "Cookie: " Cookie "\r\n"
    if (HttpAuthUser && HttpAuthPass)
        # TODO: digest auth
        header = header "Proxy-Authorization: Basic " HttpAuthCredentials "\r\n"

    content = NULLSTR; isBody = 0
    while (1) {
        print (header "\r\n" reqBody) |& HttpService
        while ((HttpService |& getline) > 0) {
            if (isBody)
                content = content ? content "\n" $0 : $0
            else if (length($0) <= 1)
                isBody = 1
            else { # interesting fields in header
                match($0, /^HTTP[^ ]* ([^ ]*)/, group)
                if (RSTART) status = group[1]
                match($0, /^Location: (.*)/, group)
                if (RSTART) location = squeeze(group[1]) # squeeze the URL!
            }
            l(sprintf("%4s bytes > %s", length($0), $0))
        }
        close(HttpService)

        if (ERRNO == "Connection timed out") {
            w("[WARNING] " ERRNO ". Retrying IPv4 connection.")
            Option["ip-version"] = 4
            initHttpService()
            PROCINFO[HttpService, "READ_TIMEOUT"] = 0
            ERRNO = ""
        } else
            break
    }

    if (status == "404") {
        e("[ERROR] 404 Not Found")
        exit 1
    }
    if ((status == "301" || status == "302") && location) {
        url = "https" substr(url, 5) # switch to HTTPS; don't use location!
        content = curlPost(url, reqBody)
    }

    return content
}

# Print a string (to output file or terminal pager).
function p(string) {
    if (Option["view"]) {
        print string | Option["pager"]
        close(Option["pager"])
    } else
        print string > Option["output"]
}

# Play using a Text-to-Speech engine.
function play(text, tl,    url, status) {
    url = _TTSUrl(text, tl)

    # Don't use getline from pipe here - the same pipe will be run only once for each AWK script!
    status = system(Option["player"] " " parameterize(url) SUPOUT SUPERR)

    if (status)
        w("Voice output isn't available for " getName(tl))

    return status
}

# Download audio from a Text-to-Speech engine.
function download_audio(text, tl,    url, output) {
    url = _TTSUrl(text, tl)

    if (Option["download-audio-as"])
        output = Option["download-audio-as"]
    else
        output = text " [" Option["engine"] "] (" Option["narrator"] ").ts"

    if (url ~ /^\//)
        system("mv -- " parameterize(url) " " parameterize(output))
    else
        curl(url, output)
}

# Get the translation of a string.
function getTranslation(text, sl, tl, hl,
                        isVerbose, toSpeech, returnPlaylist, returnIl) {
    return _Translate(text, sl, tl, hl,
                      isVerbose, toSpeech, returnPlaylist, returnIl)
}

# Translate a file.
function fileTranslation(uri,    group, temp1, temp2) {
    temp1 = Option["input"]
    temp2 = Option["verbose"]

    match(uri, /^file:\/\/(.*)/, group)
    Option["input"] = group[1]
    Option["verbose"] = 0

    translateMain()

    Option["input"] = temp1
    Option["verbose"] = temp2
}

# Start a browser session and translate a web page.
function webTranslation(uri, sl, tl, hl,    temp) {
    temp = _WebTranslateUrl(uri, sl, tl, hl)
    if (temp) {
        p(temp)
        if (Option["browser"] != NONE)
            system(Option["browser"] " " parameterize(temp) SUPOUT SUPERR)
    }
}

# Translate the source text (into all target languages).
function translate(text, inline,
                   ####
                   i, j, playlist, il, saveSortedIn) {

    if (!getCode(Option["hl"])) {
        # Check if home language is supported
        w("[WARNING] Unknown language code: " Option["hl"] ", fallback to English: en")
        Option["hl"] = "en" # fallback to English
    } else if (isRTL(Option["hl"])) {
        # Check if home language is R-to-L
        if (!FriBidi)
            w("[WARNING] " getName(Option["hl"]) " is a right-to-left language, but FriBidi is not found.")
    }

    if (!getCode(Option["sl"])) {
        # Check if source language is supported
        w("[WARNING] Unknown source language code: " Option["sl"])
    } else if (isRTL(Option["sl"])) {
        # Check if source language is R-to-L
        if (!FriBidi)
            w("[WARNING] " getName(Option["sl"]) " is a right-to-left language, but FriBidi is not found.")
    }

    saveSortedIn = PROCINFO["sorted_in"]
    PROCINFO["sorted_in"] = "@ind_num_asc"
    for (i in Option["tl"]) {
        # Non-interactive verbose mode: separator between targets
        if (!Option["interactive"])
            if (Option["verbose"] && i > 1)
                p(prettify("target-seperator", replicate(Option["chr-target-seperator"], Option["width"])))

        if (inline &&
            startsWithAny(text, UriSchemes) == "file://") {
            # translate URL only from command-line parameters (inline)
            fileTranslation(text)
        } else if (inline &&
                   startsWithAny(text, UriSchemes) == "http://" ||
                   startsWithAny(text, UriSchemes) == "https://") {
            # translate URL only from command-line parameters (inline)
            webTranslation(text, Option["sl"], Option["tl"][i], Option["hl"])
        } else {
            if (!Option["no-translate"])
                p(getTranslation(text, Option["sl"], Option["tl"][i], Option["hl"], Option["verbose"], Option["play"] || Option["download-audio"], playlist, il))
            else
                il[0] = Option["sl"] == "auto" ? "en" : Option["sl"]

            if (Option["play"] == 1) {
                if (Option["player"])
                    for (j in playlist)
                        play(playlist[j]["text"], playlist[j]["tl"])
                else if (SpeechSynthesizer)
                    for (j in playlist)
                        print playlist[j]["text"] | SpeechSynthesizer
            } else if (Option["play"] == 2) {
                if (Option["player"])
                    play(text, il[0])
                else if (SpeechSynthesizer)
                    print text | SpeechSynthesizer
            }

            if (Option["download-audio"] == 1) {
                # Download the translation unless used with -sp or -no-trans
                if (Option["play"] != 2 && !Option["no-translate"])
                    download_audio(playlist[length(playlist) - 1]["text"], \
                                   playlist[length(playlist) - 1]["tl"])
                else
                    download_audio(text, il[0])
            }
        }
    }
    PROCINFO["sorted_in"] = saveSortedIn
}

# Translate the source text (from all source languages).
function translates(text, inline,
                    ####
                    i) {
    saveSortedIn = PROCINFO["sorted_in"]
    PROCINFO["sorted_in"] = "@ind_num_asc"
    for (i in Option["sls"]) {
        # Non-interactive verbose mode: separator between sources
        if (!Option["interactive"])
            if (Option["verbose"] && i > 1)
                p(prettify("target-seperator", replicate(Option["chr-target-seperator"], Option["width"])))

        Option["sl"] = Option["sls"][i]
        translate(text, inline)
    }
    PROCINFO["sorted_in"] = saveSortedIn
}

# Read from input and translate each line.
function translateMain(    i, line) {
    if (Option["interactive"])
        prompt()

    if (Option["input"] == STDIN || fileExists(Option["input"])) {
        i = 0
        while (getline line < Option["input"])
            if (line) {
                # Non-interactive verbose mode: separator between sources
                if (!Option["interactive"])
                    if (Option["verbose"] && i++ > 0)
                        p(prettify("source-seperator",
                                   replicate(Option["chr-source-seperator"],
                                             Option["width"])))

                if (Option["interactive"])
                    repl(line)
                else
                    translates(line)
            } else {
                # Non-interactive brief mode: preserve line breaks
                if (!Option["interactive"])
                    if (!Option["verbose"])
                        p(line)
            }
    } else
        e("[ERROR] File not found: " Option["input"])
}


Added sortkey field

Added sortkey field

# Broduce a raw data spreadsheet of fields in BritBox Catalog --  apple_catalogue_feed.xml

BEGIN {
    # Print header
    printf ("Sortkey\tContent Type\tContent ID\tTitle\tEntity ID\tGenre\tRating\t")
    printf ("Show Type\tDuration\tDate Type\tOriginal Date\tShow ID\tSeason ID\t")
    printf ("Sn #\tEp #\tDescription\n")
}

# Don't process credits
/<credit role=/,/<\/credit>/ {
    next
}

# Don't use Canadian ratings
/<rating systemCode="ca-tv">/ {
    next
}

# Don't use pubDate - it's too random to be useful
# <pubDate>2020-06-05T21:51:00Z</pubDate>
/<pubDate>/ {
    next
}

# Don't use "cover_artwork_horizontal"
# <artwork url="https://britbox-images-prod.s3-eu-west-1.amazonaws.com/3840x2160px/p05wv7gy.png" type="cover_artwork_horizontal" />
/<artwork url=.*type="cover_artwork_horizontal"/ {
    next
}

# Grab contentType & contentId
# <item contentType="tv_episode" contentId="p079sxm9">
/<item contentType="/ {
    split ($0,fld,"\"")
    contentType = fld[2]
    contentId = fld[4]
    # print "contentType = " contentType
    # print "contentId = " contentId
    # Make sure no fields will be carried over due to missing keys
    sortkey = ""
    title = ""
    EntityId = ""
    genre = ""
    rating = ""
    showType = ""
    duration = ""
    dateType = ""
    originalDate = ""
    showContentId = ""
    seasonContentId = ""
    seasonNumber = ""
    episodeNumber = ""
    description = ""
}

# Grab title
# <title locale="en-US">Frequent Flyers</title>
/<title locale="en-US">/ {
    split ($0,fld,"[<>]")
    title = fld[3]
    # print "title = " title
}

# Grab genre
# <genre>drama</genre>
/<genre>/ {
    split ($0,fld,"[<>]")
    genre = fld[3]
    # print "genre = " genre
}

# Grab description
# <description>Set in the beautiful Yorkshire Dales, ...word.</description>
/<description>/ {
    split ($0,fld,"[<>]")
    description = fld[3]
    # print "description = " description
}

# Grab rating
# <rating systemCode="us-tv">TV-PG</rating>
/<rating systemCode=/ {
    split ($0,fld,"[<>]")
    rating = fld[3]
    # print "rating = " rating
}

# Grab type
# <type>series</type>
/<type>/ {
    split ($0,fld,"[<>]")
    showType = fld[3]
    # print "showType = " showType
}

# Grab duration
# <duration>3000</duration>
/<duration>/ {
    split ($0,fld,"[<>]")
    duration = fld[3]
    # print "duration = " duration
}

# Grab originalDate
# <originalAirDate>1978-03-25</originalAirDate>
# <originalPremiereDate>1978-01-08</originalPremiereDate>
# <originalReleaseDate>2017-12-25</originalReleaseDate>
/<original.*Date>/ {
    split ($0,fld,"[<>]")
    dateType = fld[2]
    sub (/original/,"",dateType)
    originalDate = fld[3]
    # print "dateType = " dateType
    # print "originalDate = " originalDate
}

# Grab showContentId
# <showContentId>b008yjd9</showContentId>
/<showContentId>/ {
    split ($0,fld,"[<>]")
    showContentId = fld[3]
    # print "showContentId = " showContentId
}

# Grab seasonContentId
# <seasonContentId>p0318ps9</seasonContentId>
/<seasonContentId>/ {
    split ($0,fld,"[<>]")
    seasonContentId = fld[3]
    # print "seasonContentId = " seasonContentId
}

# Grab seasonNumber
# <seasonNumber>1</seasonNumber>
/<seasonNumber>/ {
    split ($0,fld,"[<>]")
    seasonNumber = fld[3]
    # print "seasonNumber = " seasonNumber
}

# Grab episodeNumber
# <episodeNumber>10</episodeNumber>
/<episodeNumber>/ {
    split ($0,fld,"[<>]")
    episodeNumber = fld[3]
    # print "episodeNumber = " episodeNumber
}

# Grab EntityId from artwork
# <artwork url="https://us.britbox.com/isl/api/v1/dataservice/ResizeImage/$value?Format='jpg'&Quality=45&ImageId='176236'&EntityType='Item'&EntityId='16103'&Width=1920&Height=1080&ResizeAction='fit'" type="tile_artwork" />
/<artwork url=.*EntityId='/ {
    sub (/.*EntityId='/,"")
    sub (/&apos.*/,"")
    EntityId = "_" $0
    # print "EntityId = " EntityId
    next
}

# Print spacer between items
/<\/item>/ {
    if (title == "Shallow Grave") {
        EntityId = "_23576"
        # print "EntityId = " EntityId
    }
    if (contentId == "p07gnw9f") {
        EntityId = "_23842"
        # print "EntityId = " EntityId
    }

    if (contentType == "movie") {
        sortkey = sprintf ("%s (1) %s M%s", title, originalDate, EntityId)
    }

    if (contentType == "tv_show") {
        showTitle = title
        sortkey = sprintf ("%s (1) %s S%s", title, originalDate, EntityId)
    }

    if (contentType == "tv_season") {
        sortkey = sprintf ("%s (2) %s S%02d", showTitle, originalDate, seasonNumber)
    }

    if (contentType == "tv_episode") {
        sortkey = sprintf ("%s (2) %s S%02dE%03d", showTitle, originalDate, seasonNumber, episodeNumber)
    }

    printf ("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", sortkey, contentType, \
            contentId, title, EntityId, genre, rating, showType, duration, dateType, originalDate, \
            showContentId, seasonContentId, seasonNumber, episodeNumber, description)
}

# <artwork url="https://us.britbox.com/isl/api/v1/dataservice/ResizeImage/$value?Format='jpg'&Quality=45&ImageId='176236'&EntityType='Item'&EntityId='16103'&Width=1920&Height=1080&ResizeAction='fit'" type="tile_artwork" />
# https://www.britbox.com/us/movie/300_Years_of_French_and_Saunders_16103
/<item contentType="movie"/,/<\/item>/ {
    # print
}

# <artwork url="https://us.britbox.com/isl/api/v1/dataservice/ResizeImage/$value?Format='png'&Quality=45&ImageId='204656'&EntityType='Item'&EntityId='23740'&Width=1920&Height=1080&ResizeAction='fit'" type="tile_artwork" />
# https://www.britbox.com/us/episode/The_Last_Furlong_23740
# https://www.britbox.com/us/episode/24_Hours_In_Police_Custody_23576

# Broduce a raw data spreadsheet of fields in BritBox Catalog --  apple_catalogue_feed.xml

BEGIN {
    # Print header
    printf ("Content Type\tContent ID\tTitle\tEntity ID\tGenre\tRating\t")
    printf ("Show Type\tDuration\tDate Type\tOriginal Date\tShow ID\tSeason ID\t")
    printf ("Sn #\tEp #\tDescription\n")
}

# Don't process credits
/<credit role=/,/<\/credit>/ {
    next
}

# Don't use Canadian ratings
/<rating systemCode="ca-tv">/ {
    next
}

# Don't use pubDate - it's too random to be useful
# <pubDate>2020-06-05T21:51:00Z</pubDate>
/<pubDate>/ {
    next
}

# Don't use "cover_artwork_horizontal"
# <artwork url="https://britbox-images-prod.s3-eu-west-1.amazonaws.com/3840x2160px/p05wv7gy.png" type="cover_artwork_horizontal" />
/<artwork url=.*type="cover_artwork_horizontal"/ {
    next
}

# Grab contentType & contentId
# <item contentType="tv_episode" contentId="p079sxm9">
/<item contentType="/ {
    split ($0,fld,"\"")
    contentType = fld[2]
    contentId = fld[4]
    # print "contentType = " contentType
    # print "contentId = " contentId
    # Make sure no fields will be carried over due to missing keys
    title = ""
    EntityId = ""
    genre = ""
    rating = ""
    showType = ""
    duration = ""
    dateType = ""
    originalDate = ""
    showContentId = ""
    seasonContentId = ""
    seasonNumber = ""
    episodeNumber = ""
    description = ""
}

# Grab title
# <title locale="en-US">Frequent Flyers</title>
/<title locale="en-US">/ {
    split ($0,fld,"[<>]")
    title = fld[3]
    # print "title = " title
}

# Grab genre
# <genre>drama</genre>
/<genre>/ {
    split ($0,fld,"[<>]")
    genre = fld[3]
    # print "genre = " genre
}

# Grab description
# <description>Set in the beautiful Yorkshire Dales, ...word.</description>
/<description>/ {
    split ($0,fld,"[<>]")
    description = fld[3]
    # print "description = " description
}

# Grab rating
# <rating systemCode="us-tv">TV-PG</rating>
/<rating systemCode=/ {
    split ($0,fld,"[<>]")
    rating = fld[3]
    # print "rating = " rating
}

# Grab type
# <type>series</type>
/<type>/ {
    split ($0,fld,"[<>]")
    showType = fld[3]
    # print "showType = " showType
}

# Grab duration
# <duration>3000</duration>
/<duration>/ {
    split ($0,fld,"[<>]")
    duration = fld[3]
    # print "duration = " duration
}

# Grab originalDate
# <originalAirDate>1978-03-25</originalAirDate>
# <originalPremiereDate>1978-01-08</originalPremiereDate>
# <originalReleaseDate>2017-12-25</originalReleaseDate>
/<original.*Date>/ {
    split ($0,fld,"[<>]")
    dateType = fld[2]
    sub (/original/,"",dateType)
    originalDate = fld[3]
    # print "dateType = " dateType
    # print "originalDate = " originalDate
}

# Grab showContentId
# <showContentId>b008yjd9</showContentId>
/<showContentId>/ {
    split ($0,fld,"[<>]")
    showContentId = fld[3]
    # print "showContentId = " showContentId
}

# Grab seasonContentId
# <seasonContentId>p0318ps9</seasonContentId>
/<seasonContentId>/ {
    split ($0,fld,"[<>]")
    seasonContentId = fld[3]
    # print "seasonContentId = " seasonContentId
}

# Grab seasonNumber
# <seasonNumber>1</seasonNumber>
/<seasonNumber>/ {
    split ($0,fld,"[<>]")
    seasonNumber = fld[3]
    # print "seasonNumber = " seasonNumber
}

# Grab episodeNumber
# <episodeNumber>10</episodeNumber>
/<episodeNumber>/ {
    split ($0,fld,"[<>]")
    episodeNumber = fld[3]
    # print "episodeNumber = " episodeNumber
}

# Grab EntityId from artwork
# <artwork url="https://us.britbox.com/isl/api/v1/dataservice/ResizeImage/$value?Format='jpg'&Quality=45&ImageId='176236'&EntityType='Item'&EntityId='16103'&Width=1920&Height=1080&ResizeAction='fit'" type="tile_artwork" />
/<artwork url=.*EntityId='/ {
    sub (/.*EntityId='/,"")
    sub (/&apos.*/,"")
    EntityId = "_" $0
    # print "EntityId = " EntityId
    next
}

# Print spacer between items
/<\/item>/ {
    if (title == "Shallow Grave") {
        EntityId = "_23576"
        # print "EntityId = " EntityId
    }
    if (contentId == "p07gnw9f") {
        EntityId = "_23842"
        # print "EntityId = " EntityId
    }
    printf ("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", contentType, contentId, \
            title, EntityId, genre, rating, showType, duration, dateType, originalDate, \
            showContentId, seasonContentId, seasonNumber, episodeNumber, description)
}

# <artwork url="https://us.britbox.com/isl/api/v1/dataservice/ResizeImage/$value?Format='jpg'&Quality=45&ImageId='176236'&EntityType='Item'&EntityId='16103'&Width=1920&Height=1080&ResizeAction='fit'" type="tile_artwork" />
# https://www.britbox.com/us/movie/300_Years_of_French_and_Saunders_16103
/<item contentType="movie"/,/<\/item>/ {
    # print
}

# <artwork url="https://us.britbox.com/isl/api/v1/dataservice/ResizeImage/$value?Format='png'&Quality=45&ImageId='204656'&EntityType='Item'&EntityId='23740'&Width=1920&Height=1080&ResizeAction='fit'" type="tile_artwork" />
# https://www.britbox.com/us/episode/The_Last_Furlong_23740
# https://www.britbox.com/us/episode/24_Hours_In_Police_Custody_23576


Changed RENDERER to _Renderer

Changed RENDERER to _Renderer

# awk library to parse config file

# if we are in find_sec look for things that match brackets

# remove comment lines
/^[:space:]*#/ {next}

match($0, /\[([[:alnum:]_]).*\]/) {
  section = substr($0, (RSTART + 1), (RLENGTH - 2))

  # if we have encountered a new rule
  if (section != "_Renderers") {
	  if (section in rules) {
		  print "Error Parsing config @ line", FNR ": encountered section", section, "multiple times"
		exit -1
	  } else {
		  rules[section] = ""
	  }
  }
  next
}

# if we detect a setting of a key value
/.*=.*/ {

	# special exception for renderer: we use this to store converters
	if (section == "_Renderers") {
		renderers = renderers $0 "\n"
	} else { 		# we are parsing a rule
		rules[section] = rules[section] $0 "\n"
	}
}

# parses the multiline string in a rule into array
# ruletext is string to parse
# dest_array is used to return results
function parse_rule(ruletext, dest_array) {
	delete dest_array
	n_rules = split(ruletext, lines, "\n")
	for (i = 1; i <= n_rules; i++) {
		l = lines[i]
		if (l=="") {continue}
		n = split(l, kval, "=")
		if (n != 2) {
			print "Error Parsing config @ line", FNR ": Bad key val", l, "n == ", n
			exit -1
		}
		dest_array[kval[1]] = kval[2]
	}
}

# helper function to print all of the rules identified during the configuration
function dump_rules() {
	print "RULES"
	for (r in rules) {
		print "RULE:" r
		parse_rule(rules[r],rule_vals)
		for (k in rule_vals)
			print k ,"->", rule_vals[k]
	}
}

# awk library to parse config file

# if we are in find_sec look for things that match brackets

# remove comment lines
/^[:space:]*#/ {next}

match($0, /\[([[:alnum:]_]).*\]/) {
  section = substr($0, (RSTART + 1), (RLENGTH - 2))

  # if we have encountered a new rule
  if (section != "RENDERERS") {
	  if (section in rules) {
		  print "Error Parsing config @ line", FNR ": encountered section", section, "multiple times"
		exit -1
	  } else {
		  rules[section] = ""
	  }
  }
  next
}

# if we detect a setting of a key value
/.*=.*/ {

	# special exception for renderer: we use this to store converters
	if (section == "RENDERERS") {
		renderers = renderers $0 "\n"
	} else { 		# we are parsing a rule
		rules[section] = rules[section] $0 "\n"
	}
}

# parses the multiline string in a rule into array
# ruletext is string to parse
# dest_array is used to return results
function parse_rule(ruletext, dest_array) {
	delete dest_array
	n_rules = split(ruletext, lines, "\n")
	for (i = 1; i <= n_rules; i++) {
		l = lines[i]
		if (l=="") {continue}
		n = split(l, kval, "=")
		if (n != 2) {
			print "Error Parsing config @ line", FNR ": Bad key val", l, "n == ", n
			exit -1
		}
		dest_array[kval[1]] = kval[2]
	}
}

# helper function to print all of the rules identified during the configuration
function dump_rules() {
	print "RULES"
	for (r in rules) {
		print "RULE:" r
		parse_rule(rules[r],rule_vals)
		for (k in rule_vals)
			print k ,"->", rule_vals[k]
	}
}


Exclude any target with "Test" in the name

BEGIN {
    FS = "\n";
}

/Targets:/ {
    while (getline && $0 != "") {
        if ($0 ~ /Test/) continue;

        sub(/^ +/, "");
        print;
    }
}

BEGIN {
    FS = "\n";
}

/Targets:/ {
    while (getline && $0 != "") {
        if ($0 ~ /Tests/) continue;

        sub(/^ +/, "");
        print;
    }
}


change compare and title heads

change compare and title heads

git-svn-id: 293778eaa97c8c94097d610b1bd5133a8f478f36@22094 4f837ed2-42f5-46e7-a7a5-fa17313484d4

#!/usr/bin/awk -f
# input: publisher\tplugin\tcontract_year\tyear\ttester\tstatus

BEGIN {
  FS="\t"
  pn = 0
}

{
# add a loop to add line only if ending year is gt or eq to contract year
end = 0
if ((length($4) == 9) {
  end = substr($4,6,4)
} else if ((length($4) == 4 {
  end = substr($4,1,4)
}
if (end >= $3) {
  incontract = 1
} else {
  incontract = 0
}

if (incontract == 1) {
    nn = split($2,na,/\./)
    lp2 = na[nn]
  if (!(($1,lp2,$3,$4) in b)) {
    p[pn] = $1
    n[pn] = lp2
#    n[pn] = $2
    t[pn] = $3
    d[pn] = $4
    r[pn] = $5
    pn++
  }
  b[$1,lp2,$3,$4]++
  c[$1,lp2,$3,$4,$6]++
  x[$6]++
  tt++
}
}

END {
  s[0] = "expected"
  s[1] = "exists"
  s[2] = "manifest"
  s[3] = "wanted"
  s[4] = "crawling"
  s[5] = "testing"
  s[6] = "notReady"
  s[7] = "released"
  s[8] = "down"
  s[9] = "superseded"
  s[10] = "zapped"
  sn = 11

  sc[0] = "X"
  sc[1] = "E"
  sc[2] = "M"
  sc[3] = "W"
  sc[4] = "C"
  sc[5] = "T"
  sc[6] = "N"
  sc[7] = "R"
  sc[8] = "D"
  sc[9] = "S"
  sc[10] = "Z"
  scn = 11

  #print out header
  printf "Publisher\tPlugin\tContr\tYear\tT\tTotal"
  for (j = 0 ; j < scn ; j++) {
  	if (x[s[j]] > 0) {
    printf "\t%s", sc[j]
    }
  }
  printf "\n"

  #print out publisher, plugin, contract year, year, total aus
  for (i = 0 ; i < pn ; i++) {
    printf "%s\t%s\t%s\t%s\t%s\t%d", p[i], n[i], t[i], d[i], r[i], b[p[i],n[i],t[i],d[i]]
    for (j = 0 ; j < sn ; j++) {
      if (x[s[j]] > 0){
      if (c[p[i],n[i],t[i],d[i],s[j]] == 0) {
      printf "\t.."
    } else {
        printf "\t%d", c[p[i],n[i],t[i],d[i],s[j]]
      }
    }
    }
    printf "\n"
  }
    #print out bottom line sums
    printf "Publisher\tPlugin\tContr\tYear\tT\t%d", tt
    for (j = 0 ; j < sn ; j++) {
    	if (x[s[j]] > 0) {
    		printf "\t%d", x[s[j]]
      }
    }

  printf "\n"
}

#!/usr/bin/awk -f
# input: publisher\tplugin\tcontract_year\tyear\ttester\tstatus

BEGIN {
  FS="\t"
  pn = 0
}

{
# add a loop to add line only if ending year is gt or eq to contract year
end = 0
if ((length($4) == 9) {
  end = substr($4,6,4)
} else if ((length($4) == 4 {
  end = substr($4,1,4)
}
if (end >= $3) incontract = 1
else incontract = 0

if (incontract == 1) {
    nn = split($2,na,/\./)
    lp2 = na[nn]
  if (!(($1,lp2,$3,$4) in b)) {
    p[pn] = $1
    n[pn] = lp2
#    n[pn] = $2
    t[pn] = $3
    d[pn] = $4
    r[pn] = $5
    pn++
  }
  b[$1,lp2,$3,$4]++
  c[$1,lp2,$3,$4,$6]++
  x[$6]++
  tt++
}
}

END {
  s[0] = "expected"
  s[1] = "exists"
  s[2] = "manifest"
  s[3] = "wanted"
  s[4] = "crawling"
  s[5] = "testing"
  s[6] = "notReady"
  s[7] = "released"
  s[8] = "down"
  s[9] = "superseded"
  s[10] = "zapped"
  sn = 11

  sc[0] = "X"
  sc[1] = "E"
  sc[2] = "M"
  sc[3] = "W"
  sc[4] = "C"
  sc[5] = "T"
  sc[6] = "N"
  sc[7] = "R"
  sc[8] = "D"
  sc[9] = "S"
  sc[10] = "Z"
  scn = 11

  #print out header
  printf "Publisher\tPlugin\tContr\tYear\tT\tTotal"
  for (j = 0 ; j < scn ; j++) {
  	if (x[s[j]] > 0) {
    printf "\t%s", sc[j]
    }
  }
  printf "\n"

  #print out publisher, plugin, contract year, year, total aus
  for (i = 0 ; i < pn ; i++) {
    printf "%s\t%s\t%s\t%s\t%s\t%d", p[i], n[i], t[i], d[i], r[i], b[p[i],n[i],t[i],d[i]]
    for (j = 0 ; j < sn ; j++) {
      if (x[s[j]] > 0){
      if (c[p[i],n[i],t[i],d[i],s[j]] == 0) {
      printf "\t.."
    } else {
        printf "\t%d", c[p[i],n[i],t[i],d[i],s[j]]
      }
    }
    }
    printf "\n"
  }
    #print out bottom line sums
    printf "Publisher\tPlugin\tContr\tYear\tT\t%d", tt
    for (j = 0 ; j < sn ; j++) {
    	if (x[s[j]] > 0) {
    		printf "\t%d", x[s[j]]
      }
    }

  printf "\n"
}


Fix ' in Character names

Fix ' in Character names

# Extract cast & crew from MHz episode files

# INVOCATION:
#    awk -v ERRORS=$ERRORS -f getCast.awk >>$RAW_CREDITS

# 21     <meta property="og:url" content="https://watch.mhzchoice.com/captain-marleau/season:2/...
#
# 1135           data-meta-field-name="casts"
# 1136           data-meta-field-value="Corinne Masiero"
# 1137           data-track-event="metadata_click_cast"
# 1138           data-text-no-results="No results"
# 1139           data-text-load-more="Load More"
# 1140         >
# 1141           Corinne Masiero
# 1142
# 1143         Captain Marleau
#
# 1219           data-meta-field-name="crew"
# 1220           data-meta-field-value="Josée Dayan"
# 1221           data-track-event="metadata_click_crew"
# 1222           data-text-no-results="No results"
# 1223           data-text-load-more="Load More"
# 1224         >
# 1225           Josée Dayan
# 1226
# 1227         director

/<meta property="og:url" content/ {
    split ($0,fld,"/")
    title = fld[4]
    split ($0,fld,"\"")
    shortURL = fld[4]
    sub (/.*watch/,"watch",shortURL)
}

/data-meta-field-name="casts"/,// {
    if ($0 ~ /data-meta-field-value=/) {
        tvShowType = "tv_show"
        person_role = "actor"
        split ($0,fld,"\"")
        person_name = fld[2]
        sub (/\.$/,"",person_name)
        next
    }
    if ($0 ~ /<span class="small-8 capitalize/) {
        split ($0,fld,"[<>]")
        char_name = fld[3]
        gsub (/'/,"'",char_name)
        if (match (person_name, " and ") || match (person_name, " & ")) {
            pname = substr(person_name,1,RSTART-1)
            sub (/\.$/,"",pname)
            person_name = substr(person_name,RSTART+RLENGTH)
            printf ("%s\t%s\t%s\t%s\t%s\n", pname, person_role, tvShowType, title, char_name)
            if (pname !~ " ")
                print "==> Single name '" pname "' in " shortURL >> ERRORS
        }
        # Special case
        if (person_name ~ /Hendrik Toompere/)
            person_name = "Hendrik Toompere Jr."
        #
        printf ("%s\t%s\t%s\t%s\t%s\n", person_name, person_role, tvShowType, title, char_name)
        if (person_name !~ " ")
            print "==> Single name '" person_name "' in " shortURL >> ERRORS
        person_role = ""
        person_name = ""
        char_name = ""
        next
    }
}

/data-meta-field-name="crew"/,// {
        if ($0 ~ /data-meta-field-value=/) {
        tvShowType = "tv_show"
        split ($0,fld,"\"")
        person_name = fld[2]
        # Special case
        if (person_name ~ /Manetti Bros/)
            person_name = "The Manetti Bros."
        #
    }
    if ($0 ~ /<span class="small-8 capitalize/) {
        split ($0,fld,"[<>]")
        person_role = fld[3]
        printf ("%s\t%s\t%s\t%s\t%s\n", person_name, person_role, tvShowType, title, char_name)
        person_role = ""
        person_name = ""
    }
}

# Extract cast & crew from MHz episode files

# INVOCATION:
#    awk -v ERRORS=$ERRORS -f getCast.awk >>$RAW_CREDITS

# 21     <meta property="og:url" content="https://watch.mhzchoice.com/captain-marleau/season:2/...
#
# 1135           data-meta-field-name="casts"
# 1136           data-meta-field-value="Corinne Masiero"
# 1137           data-track-event="metadata_click_cast"
# 1138           data-text-no-results="No results"
# 1139           data-text-load-more="Load More"
# 1140         >
# 1141           Corinne Masiero
# 1142
# 1143         Captain Marleau
#
# 1219           data-meta-field-name="crew"
# 1220           data-meta-field-value="Josée Dayan"
# 1221           data-track-event="metadata_click_crew"
# 1222           data-text-no-results="No results"
# 1223           data-text-load-more="Load More"
# 1224         >
# 1225           Josée Dayan
# 1226
# 1227         director

/<meta property="og:url" content/ {
    split ($0,fld,"/")
    title = fld[4]
    split ($0,fld,"\"")
    shortURL = fld[4]
    sub (/.*watch/,"watch",shortURL)
}

/data-meta-field-name="casts"/,// {
    if ($0 ~ /data-meta-field-value=/) {
        tvShowType = "tv_show"
        person_role = "actor"
        split ($0,fld,"\"")
        person_name = fld[2]
        sub (/\.$/,"",person_name)
        next
    }
    if ($0 ~ /<span class="small-8 capitalize/) {
        split ($0,fld,"[<>]")
        char_name = fld[3]
        if (match (person_name, " and ") || match (person_name, " & ")) {
            pname = substr(person_name,1,RSTART-1)
            sub (/\.$/,"",pname)
            person_name = substr(person_name,RSTART+RLENGTH)
            printf ("%s\t%s\t%s\t%s\t%s\n", pname, person_role, tvShowType, title, char_name)
            if (pname !~ " ")
                print "==> Single name '" pname "' in " shortURL >> ERRORS
        }
        # Special case
        if (person_name ~ /Hendrik Toompere/)
            person_name = "Hendrik Toompere Jr."
        #
        printf ("%s\t%s\t%s\t%s\t%s\n", person_name, person_role, tvShowType, title, char_name)
        if (person_name !~ " ")
            print "==> Single name '" person_name "' in " shortURL >> ERRORS
        person_role = ""
        person_name = ""
        char_name = ""
        next
    }
}

/data-meta-field-name="crew"/,// {
        if ($0 ~ /data-meta-field-value=/) {
        tvShowType = "tv_show"
        split ($0,fld,"\"")
        person_name = fld[2]
        # Special case
        if (person_name ~ /Manetti Bros/)
            person_name = "The Manetti Bros."
        #
    }
    if ($0 ~ /<span class="small-8 capitalize/) {
        split ($0,fld,"[<>]")
        person_role = fld[3]
        printf ("%s\t%s\t%s\t%s\t%s\n", person_name, person_role, tvShowType, title, char_name)
        person_role = ""
        person_name = ""
    }
}


class: recognize "2012, Mar 17" as a date


#1 Debian jessie, mawk v 1.3.3-17; Syntax error in awk file

#!/usr/bin/awk -f
BEGIN {
  FS = "[ /^]+"
  while (("git ls-remote " ARGV[1] "| sort -Vk2") | getline) {
    if (!sha)
      sha = substr($0, 1, 7)
    tag = $3
  }
  while (("curl -s " ARGV[1] "/releases/tag/" tag) | getline)
    if ($3 ~ "commits")
      com = $2
  printf com ? "%s-%s-g%s\n" : "%s\n", tag, com, sha
}

#!/usr/bin/awk -f
BEGIN {
  FS = "[ /^]+"
  while ("git ls-remote " ARGV[1] "| sort -Vk2" | getline) {
    if (!sha)
      sha = substr($0, 1, 7)
    tag = $3
  }
  while ("curl -s " ARGV[1] "/releases/tag/" tag | getline)
    if ($3 ~ "commits")
      com = $2
  printf com ? "%s-%s-g%s\n" : "%s\n", tag, com, sha
}


Whoops tabs

Whoops tabs

#! /usr/bin/env awk -f

BEGIN {
    # @start-fragment default-config
    config["SYNTHESIZERS_SHOULD_BE_FORMATTED"]   = 1
    config["SYNTHESIZERS_SHOULD_HAVE_N_COLUMNS"] = 2
    # @end-fragment default-config

    # @include-fragment util.awk config

    # @start-fragment body
    while (getline line) {
        parseLine(line)
    }
    # @end-fragment body
}

# @start-fragment functions

function parseLine(line) {
    if (line ~ /@synthesize/) {
        readSynthesizers(line, properties, aliases)
    }
}

function readSynthesizers(line, properties, aliases) {
    while (line ~ /@synthesize/ && !index(line, ";")) {
        getline anotherLine
        line = line anotherLine
    }

    extractSynthesizers(line, properties, aliases)
    getline line

    if (line ~ /@synthesize/ || line ~ /^[ \t]*$/) {
        readSynthesizers(line, properties, aliases)
    } else {
        formatSynthesizers(properties, aliases)
        parseLine(line)
    }
}

function extractSynthesizers(line, properties, aliases) {
    gsub("@synthesize", "", line)
    gsub(";", "", line)
    gsub(/[ \t]*/, "", line)

    split(line, individualProperties, ",")

    for (i = 1; i <= length(individualProperties); i++) {
        split(individualProperties[i], components, "=")

        if (length(components[1]) > 0) { # avoid lines with only whitespace
            j = length(properties) + 1
            properties[j] = components[1]

            if (length(components) > 1) {
                aliases[j] = components[2]
            } else {
                aliases[j] = components[1]
            }
        }
    }
}

function formatSynthesizers(properties, aliases) {
    maxLength = 0

    for (i = 1; i <= length(properties); i++) {
        maxLength = max(maxLength, length(properties[i]));
    }

    for (i = 1; i <= length(properties); i++) {
        format = ""

        if (config["SYNTHESIZERS_SHOULD_HAVE_N_COLUMNS"] == 1) {
            format = "@synthesize %s = %s;\n"
        } else {
            format = "@synthesize %-" maxLength "s = %s;\n"
        }

        printf(format, properties[i], aliases[i])
    }
}

# @end-fragment functions

# @include-fragment util.awk functions