Translate: reduce empty line

Translate: reduce empty 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 = ""
    }
}

# 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 && !hasAltTranslations)
            wShowPromptMessage = wShowLanguages = 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 = ""
    }
}

# 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)
    }
}


HvD: Extended the script a little bit so that duplicate ID lines will not be repeated in util_version.F. If the right property bits are set this should remove all duplication as all the ID lines in a single file SHOULD be the exactly the same.

#
# This script is used by the makefile in building util_version.f.
# It expects on standard input lines containing
#
# a CVS ID    -> produce a write statement with the ID ("Exp" at the end)
# a SVN ID    -> produce a write statement with the ID (no "Exp" at the end)
# module name -> produce a write statement for the module name
# prev_str    -> skip to eliminate duplicates
# other       -> produce nothing
#
# $Id$

BEGIN {
		underline = "---------------------------------------------------------";
		printf("      subroutine util_version\n");
                printf("#include \"global.fh\"\n");
                printf("      if (ga_nodeid().eq.0) then\n");
		printf("      write(6,*)\n");
		printf("      write(6,*) ' Software version information'\n");
		printf("      write(6,*) ' ----------------------------'\n");
		prev_str = underline;
}

/^module/			{
				 len = length($0);
				 printf("      write(6,*)\n");
				 printf("      write(6,*) ' %s'\n",$0);
				 printf("      write(6,*) ' %s'\n",substr(underline,1,len));
				}

0 != match($0,prev_str)		{
				  $0="";
				}

/\$\I\d: [^\n]*Exp \$/		{
				 i = index($0, "$Id: ") + 5;
				 j = index($0, "Exp $") - 1;
				 n = j - i + 1;
				 if (n > (72 - 20)) n = 72 - 20;
				 printf("      write(6,*) ' %s'\n",substr($0,i,n));
				 prev_str = substr($0,i,n);
				}

/\$\I\d: [^\n]* \$/		{
				 i = index($0, "$Id: ") + 5;
				 n = index(substr($0,i), " $") - 1;
				 if (n > (72 - 20)) n = 72 - 20;
				 printf("      write(6,*) ' %s'\n",substr($0,i,n));
				 prev_str = substr($0,i,n);
				}

END {
		printf("      write(6,*)\n");
		printf("      call util_flush(6)\n");
                printf("      endif\n");
                printf("      call ga_sync()\n");
		printf("      end\n");
}

#
# This script is used by the makefile in building util_version.f.
# It expects on standard input lines containing
#
# a CVS ID    -> produce a write statement with the ID ("Exp" at the end)
# a SVN ID    -> produce a write statement with the ID (no "Exp" at the end)
# module name -> produce a write statement for the module name
# other       -> produce nothing
#
# $Id$

BEGIN {
		underline = "---------------------------------------------------------";
		printf("      subroutine util_version\n");
                printf("#include \"global.fh\"\n");
                printf("      if (ga_nodeid().eq.0) then\n");
		printf("      write(6,*)\n");
		printf("      write(6,*) ' Software version information'\n");
		printf("      write(6,*) ' ----------------------------'\n");
}

/^module/			{
				 len = length($0);
				 printf("      write(6,*)\n");
				 printf("      write(6,*) ' %s'\n",$0);
				 printf("      write(6,*) ' %s'\n",substr(underline,1,len));
				}

/\$\I\d: [^\n]*Exp \$/		{
				 i = index($0, "$Id: ") + 5;
				 j = index($0, "Exp $") - 1;
				 n = j - i + 1;
				 if (n > (72 - 20)) n = 72 - 20;
				 printf("      write(6,*) ' %s'\n",substr($0,i,n));
				}

/\$\I\d: [^\n]* \$/		{
				 i = index($0, "$Id: ") + 5;
				 n = index(substr($0,i), " $") - 1;
				 if (n > (72 - 20)) n = 72 - 20;
				 printf("      write(6,*) ' %s'\n",substr($0,i,n));
				}

END {
		printf("      write(6,*)\n");
		printf("      call util_flush(6)\n");
                printf("      endif\n");
                printf("      call ga_sync()\n");
		printf("      end\n");
}


Fix overzealous missing seasons code

Fix overzealous missing seasons code

# Generate series URLs, Titles, Number of Seasons from MHz "Browse" page
#
# INVOCATION:
#       curl -s https://mhzchoice.vhx.tv/series https://mhzchoice.vhx.tv/series?page=2 \
#           | awk -v URL_FILE=$URL_FILE -v TITLE_FILE=$TITLE_FILE \
#           -v NUM_SEASONS_FILE=$NUM_SEASONS_FILE -v ERROR_FILE=$ERROR_FILE \
#           -f getMHzFrom-browsePage.awk
#
# INPUT:
#       A French Village
#   ---
#
6 seasons

#
# OUTPUT:
#       $URL_FILE, $TITLE_FILE, $NUM_SEASONS_FILE

/https:\/\/mhzchoice.vhx.tv\/.*strong title/ {
    split ($0,fld,"\"")
    URL = fld[2]
    TITLE = fld[4]
    print URL >> URL_FILE

    # Canonicalize Title
    gsub (/'/,"'",TITLE)
    gsub (/"/,"\"\"",TITLE)
    if (match (TITLE, /^The /)) {
        TITLE = substr(TITLE, 5) ", The"
    }
    print TITLE >> TITLE_FILE
    next
}

/h4 class="transparent.*media-count/ {
    numSeasons = $0
    sub (/.*media-count'>/,"",numSeasons)
    sub (/ season.*$/,"",numSeasons)
    if ((numSeasons + 0) == 0)
        print "==> No seasons: " TITLE >> ERROR_FILE
    print numSeasons >> NUM_SEASONS_FILE
}

# Generate series URLs, Titles, Number of Seasons from MHz "Browse" page
#
# INVOCATION:
#       curl -s https://mhzchoice.vhx.tv/series https://mhzchoice.vhx.tv/series?page=2 \
#           | awk -v URL_FILE=$URL_FILE -v TITLE_FILE=$TITLE_FILE \
#           -v NUM_SEASONS_FILE=$NUM_SEASONS_FILE -v ERROR_FILE=$ERROR_FILE \
#           -f getMHzFrom-browsePage.awk
#
# INPUT:
#       A French Village
#   ---
#
6 seasons

#
# OUTPUT:
#       $URL_FILE, $TITLE_FILE, $NUM_SEASONS_FILE

/https:\/\/mhzchoice.vhx.tv\/.*strong title/ {
    split ($0,fld,"\"")
    URL = fld[2]
    TITLE = fld[4]
    print URL >> URL_FILE

    # Canonicalize Title
    gsub (/'/,"'",TITLE)
    gsub (/"/,"\"\"",TITLE)
    if (match (TITLE, /^The /)) {
        TITLE = substr(TITLE, 5) ", The"
    }
    print TITLE >> TITLE_FILE
    next
}

/h4 class="transparent.*media-count/ {
    sub (/.*media-count'>/,"")
    sub (/ season.*$/,"")
    if ((numSeasons + 0) == 0)
        print "==> No seasons: " TITLE >> ERROR_FILE
    print $0 >> NUM_SEASONS_FILE
}


comment support

comment support

function parse_line(line,   ret) {
	if (gensub( /\[.+\]\(.+\)/, "", "g", line) ~ /\/\/.*/) {
		line = gensub( /\/\/.*/, "", "g", gensub( /\[.+\]\(.+\)/, "", "g", line))
	}
	if(line ~ /^==+/ || line ~ /^--+/) {
		if(line ~ /^==+/ && (isslide(slide))) {
		} else {
			ret = ret "
"
		}
		return ret
	} else if(line ~ /\*\*[^[:space:]].*[^[:space:]]\*\*/) {
		ret = ret parse_line(gensub( /\*\*([^[:space:]].*[^[:space:]])\*\*/, "\\1", "g", line))
	} else if(line ~ /__[^[:space:]].*[^[:space:]]__/) {
		ret = ret parse_line(gensub( /__([^[:space:]].*[^[:space:]])__/, "\\1", "g", line))
	} else if(line ~ /\*[^[:space:]*].*[^[:space:]*]\*/) {
		ret = ret parse_line(gensub( /\*([^[:space:]*].*[^[:space:]*])\*/, "\\1", "g", line))
	} else if(line ~ /_[^[:space:]_].*[^[:space:]_]_/) {
		ret = ret parse_line(gensub( /_([^[:space:]_].*[^[:space:]_])_/, "\\1", "g", line))
	} else if(line ~ /\[.+\]\(.+\)/) {
		ret = ret gensub( /\[(.+)\]\((.+)\)/, "\\1", "g", line)
	} else {
		gsub( /  $/, "
", line)
		gsub( /\\n/, "
", line)
		ret = ret line
	}
	return ret
}

function tag(stag, body, opt) {
	return "<" stag " " opt ">" body "</" stag ">"
}

function parse_para(para,   lines, ispara, ret, begp, endp, nest) {
	max_idx = split(para, lines)
	begp = ""
	endp = ""
	nest = 0
	ispara = 0
	for(i = 1; i <= max_idx; i++) {
		if(lines[i + 1] ~ /^==+/) {
			if(isslide(slide)) {
				headline = tag("h1", parse_line(lines[i])) "
"
				i++
			} else {
				ret = ret endp tag("h1", parse_line(lines[i]))
				ispara = 0
				endp = ""
			}
		} else if(lines[i + 1] ~ /^--+/) {
			if(isslide(slide)) {
				ret = ret endp parse_line(lines[i])
				ispara = 0
				endp = ""
			} else {
				ret = ret endp tag("h2", parse_line(lines[i]))
				ispara = 0
				endp = ""
			}
		} else if(lines[i] ~ /^######[[:space:]]*[^[:space:]].*[^[:space:]][[:space:]]*$/) {
			ret = ret endp tag("h6", parse_line(gensub( /^######[[:space:]]*([^[:space:]].*[^[:space:]])[[:space:]]*$/, "\\1", "g", lines[i])))
			ispara = 0
			endp = ""
		} else if(lines[i] ~ /^#####[[:space:]]*[^[:space:]].*[^[:space:]][[:space:]]*$/) {
			ret = ret endp tag("h5", parse_line(gensub( /^#####[[:space:]]*([^[:space:]].*[^[:space:]])[[:space:]]*$/, "\\1", "g", lines[i])))
			ispara = 0
			endp = ""
		} else if(lines[i] ~ /^####[[:space:]]*[^[:space:]].*[^[:space:]][[:space:]]*$/) {
			ret = ret endp tag("h4", parse_line(gensub( /^####[[:space:]]*([^[:space:]].*[^[:space:]])[[:space:]]*$/, "\\1", "g", lines[i])))
			ispara = 0
			endp = ""
		} else if(lines[i] ~ /^###[[:space:]]*[^[:space:]].*[^[:space:]][[:space:]]*$/) {
			ret = ret endp tag("h3", parse_line(gensub( /^###[[:space:]]*([^[:space:]].*[^[:space:]])[[:space:]]*$/, "\\1", "g", lines[i])))
			ispara = 0
			endp = ""
		} else if(lines[i] ~ /^##[[:space:]]*[^[:space:]].*[^[:space:]][[:space:]]*$/) {
			ret = ret endp tag("h2", parse_line(gensub( /^##[[:space:]]*([^[:space:]].*[^[:space:]])[[:space:]]*$/, "\\1", "g", lines[i])))
			ispara = 0
			endp = ""
		} else if(lines[i] ~ /^#[[:space:]]*[^[:space:]].*[^[:space:]][[:space:]]*$/) {
			ret = ret endp tag("h1", parse_line(gensub( /^#[[:space:]]*([^[:space:]].*[^[:space:]])[[:space:]]*$/, "\\1", "g", lines[i])))
			ispara = 0
			endp = ""
		} else if(lines[i] ~ /^[[:space:]]*>/) {
			ret = ret endp "
"
			for(; lines[i] ~ /^[[:space:]]*>/; i++) {
				ret = ret parse_line(gensub( /^[[:space:]]*>(.*)/, "\\1", "g", lines[i]))
			}
			i--
			ret = ret "
"
			ispara = 0
			endp = ""
		} else if(lines[i] ~ /^[[:space:]]*(\*|-)[[:space:]]+[^[:space:]].*/) {
			ret = ret endp "
"
			for(; lines[i] ~ /^[[:space:]]*(\*|-)/; i++) {
				ret = ret tag("li", parse_line(gensub( /^[[:space:]]*(\*|-)[[:space:]]+([^[:space:]].*)/, "\\2", "g", lines[i])))
			}
			i--
			ret = ret "
"
			ispara = 0
			endp = ""
		} else if(lines[i] ~ /^[[:space:]]*[[:digit:]]+\.[[:space:]]+[^[:space:]].*/) {
			ret = ret endp "
"
			for(; lines[i] ~ /^[[:space:]]*[[:digit:]]+\.[[:space:]]+[^[:space:]].*/; i++) {
				ret = ret tag("li", parse_line(gensub( /^[[:space:]]*[[:digit:]]+\.[[:space:]]+([^[:space:]].*)/, "\\1", "g", lines[i])))
			}
			i--
			ret = ret "
"
			ispara = 0
			endp = ""
		} else if(lines[i] ~ /^```/) {
			ret = ret endp "<pre><code>"
			for(i++; lines[i] !~ /^```/; i++) {
				gsub(/>/, "\\>", lines[i])
				gsub(/</, "\\<", lines[i])
				ret = ret  lines[i] "\n"
			}
			ret = ret "
"
			ispara = 0
			endp = ""
		} else {
			if(ispara != 0) {
				ret = ret parse_line(lines[i])
			} else {
			ret = ret "
" parse_line(lines[i])
			ispara = 1
			endp = "
"
			}
		}
	}
	if(ispara != 0) {
		ret = ret "
"
		ispara = 0
		endp = ""
	}
	return ret
}

function isslide(s) {
	if(s == "hamaji" || s == "shinh" || s == "simple" || s == "s") {
		return 1
	} else {
	return 0
	}
}

BEGIN {
	RS = ""
	FS = "\n"
	slide = ""
	filename = gensub(/(.*)\.md/, "\\1", "g", ARGV[1])
	if(isslide(slide)) {
		"mkdir -p " filename | getline
		print ARGV[1] "-> ./" filename "/" filename ".html"
	} else {
		print ARGV[1] "->" filename ".html"
}
}

{
	if(isslide(slide)) {
		body = parse_para($0)
		print "<!DOCTYPE html>" tag("html", tag("head","<meta charset=\"UTF-8\"> <link rel=\"stylesheet\" type=\"text/css\" href=\"../template.css\">" "") tag("body",
				tag("a", "<", "href = \"./" filename NR-1 ".html\"") \
				tag("a", ">", "href = \"./" filename NR+1 ".html\"") \
				headline tag("section", body), "onload = \"adjust_fontsize()\"" \
				)) >  "./" filename "/" filename NR ".html"
	} else {
		body = body tag("p", parse_para($0))
	}
}

END {
	if(!(isslide(slide))) {
		print "<!DOCTYPE html>" tag("html", tag("head","<meta charset=\"UTF-8\"> ") tag("body", body, "" )) > filename ".html"
	}
}

function parse_line(line,   ret) {
	if (line ~ /^==+/ || line ~ /^--+/) {
		if (line ~ /^==+/ && (slide == "hamaji" || slide == "shinh" || slide == "simple" || slide == "s")) {
		} else {
			ret = ret "
"
		}
	} else if (line ~ /\*\*[^[:space:]].*[^[:space:]]\*\*/) {
		ret = ret parse_line(gensub(/\*\*([^[:space:]].*[^[:space:]])\*\*/, "\\1", "g", line))
	} else if (line ~ /__[^[:space:]].*[^[:space:]]__/) {
		ret = ret parse_line(gensub(/__([^[:space:]].*[^[:space:]])__/, "\\1", "g", line))
	} else if (line ~ /\*[^[:space:]*].*[^[:space:]*]\*/) {
		ret = ret parse_line(gensub(/\*([^[:space:]*].*[^[:space:]*])\*/, "\\1", "g", line))
	} else if (line ~ /_[^[:space:]_].*[^[:space:]_]_/) {
		ret = ret parse_line(gensub(/_([^[:space:]_].*[^[:space:]_])_/, "\\1", "g", line))
	} else if (line ~ /\[.+\]\(.+\)/) {
		ret = ret gensub(/\[(.+)\]\((.+)\)/, "\\1", "g", line)
	} else {
		gsub(/  $/, "
", line)
		ret = ret line
	}
	return ret
}

function tag(stag, body, opt) {
	return "<" stag " " opt ">" body "</" stag ">"
}

function parse_para(para,   lines, ispara, ret, begp, endp) {
	max_idx = split(para, lines)
	begp = ""
	endp = ""
	ispara = 0
	for (i = 1; i <= max_idx; i++) {
		if (lines[i + 1] ~ /^==+/) {
			if (slide == "hamaji" || slide == "shinh" || slide == "simple" || slide == "s") {
				headline = tag("h1", parse_line(lines[i])) "
"
				i++
			} else {
				ret = ret endp tag("h1", parse_line(lines[i]))
				ispara = 0
				endp = ""
			}
		} else if (lines[i + 1] ~ /^--+/) {
			if (slide == "hamaji" || slide == "shinh" || slide == "simple" || slide == "s") {
				ret = ret endp parse_line(lines[i])
				ispara = 0
				endp = ""
			} else {
				ret = ret endp tag("h2", parse_line(lines[i]))
				ispara = 0
				endp = ""
			}
		} else if (lines[i] ~ /^#[[:space:]]+[^[:space:]].*[^[:space:]][[:space:]]*$/) {
			ret = ret endp tag("h1", parse_line(gensub(/^#[[:space:]]+([^[:space:]].*[^[:space:]])[[:space:]]*$/, "\\1", "g", lines[i])))
			ispara = 0
			endp = ""
		} else if (lines[i] ~ /^##[[:space:]]+[^[:space:]].*[^[:space:]][[:space:]]*$/) {
			ret = ret endp tag("h2", parse_line(gensub(/^##[[:space:]]+([^[:space:]].*[^[:space:]])[[:space:]]*$/, "\\1", "g", lines[i])))
			ispara = 0
			endp = ""
		} else if (lines[i] ~ /^###[[:space:]]+[^[:space:]].*[^[:space:]][[:space:]]*$/) {
			ret = ret endp tag("h3", parse_line(gensub(/^###[[:space:]]+([^[:space:]].*[^[:space:]])[[:space:]]*$/, "\\1", "g", lines[i])))
			ispara = 0
			endp = ""
		} else if (lines[i] ~ /^####[[:space:]]+[^[:space:]].*[^[:space:]][[:space:]]*$/) {
			ret = ret endp tag("h4", parse_line(gensub(/^####[[:space:]]+([^[:space:]].*[^[:space:]])[[:space:]]*$/, "\\1", "g", lines[i])))
			ispara = 0
			endp = ""
		} else if (lines[i] ~ /^#####[[:space:]]+[^[:space:]].*[^[:space:]][[:space:]]*$/) {
			ret = ret endp tag("h5", parse_line(gensub(/^#####[[:space:]]+([^[:space:]].*[^[:space:]])[[:space:]]*$/, "\\1", "g", lines[i])))
			ispara = 0
			endp = ""
		} else if (lines[i] ~ /^######[[:space:]]+[^[:space:]].*[^[:space:]][[:space:]]*$/) {
			ret = ret endp tag("h6", parse_line(gensub(/^######[[:space:]]+([^[:space:]].*[^[:space:]])[[:space:]]*$/, "\\1", "g", lines[i])))
			ispara = 0
			endp = ""
		}else if (lines[i] ~ /^[[:space:]]*>/) {
			ret = ret endp "
"
			for (; lines[i] ~ /^[[:space:]]*>/; i++) {
				ret = ret parse_line(gensub(/^[[:space:]]*>(.*)/, "\\1", "g", lines[i]))
			}
			i--
			ret = ret "
"
			ispara = 0
			endp = ""
		} else if (lines[i] ~ /^[[:space:]]*(\*|-)[[:space:]]+[^[:space:]].*/) {
			ret = ret endp "
"
			for (; lines[i] ~ /^[[:space:]]*(\*|-)/; i++) {
				ret = ret tag("li", parse_line(gensub(/^[[:space:]]*(\*|-)[[:space:]]+([^[:space:]].*)/, "\\2", "g", lines[i])))
			}
			i--
			ret = ret "
"
			ispara = 0
			endp = ""
		} else if (lines[i] ~ /^[[:space:]]*[[:digit:]]+\.[[:space:]]+[^[:space:]].*/) {
			ret = ret endp "
"
			for (; lines[i] ~ /^[[:space:]]*[[:digit:]]+\.[[:space:]]+[^[:space:]].*/; i++) {
				ret = ret tag("li", parse_line(gensub(/^[[:space:]]*[[:digit:]]+\.[[:space:]]+([^[:space:]].*)/, "\\1", "g", lines[i])))
			}
			i--
			ret = ret "
"
			ispara = 0
			endp = ""
		} else if (lines[i] ~ /^```/) {
			ret = ret endp "<pre><code>"
			for (i++; lines[i] !~ /^```/; i++) {
				gsub(/>/, "\\>", lines[i])
				gsub(/</, "\\<", lines[i])
				ret = ret  lines[i] "\n"
			}
			ret = ret "
"
			ispara = 0
			endp = ""
		} else {
			if (ispara != 0) {
				ret = ret parse_line(lines[i])
			} else {
			ret = ret "
" parse_line(lines[i])
			ispara = 1
			endp = "
"
			}
		}
	}
	if (ispara != 0) {
		ret = ret "
"
		ispara = 0
		endp = ""
	}
	return ret
}

BEGIN {
	RS = ""
	FS = "\n"
	slide = "simple"
	filename = gensub(/(.*)\.md/, "\\1", "g", ARGV[1])
	"mkdir -p " filename | getline
	print ARGV[1] "-> ./" filename "/" filename ".html"
}

{
	if (slide == "hamaji" || slide == "shinh" || slide == "simple" || slide == "s") {
		body = parse_para($0)
		print "<!DOCTYPE html>" tag("html", tag("head","<meta charset=\"UTF-8\"> <link rel=\"stylesheet\" type=\"text/css\" href=\"../template.css\">" "") tag("body",
				tag("a", "<", "href = \"./" filename NR-1 ".html\"") \
				tag("a", ">", "href = \"./" filename NR+1 ".html\"") \
				headline tag("section", body), "onload = \"adjust_fontsize()\"" \
				)) >  "./" filename "/" filename NR ".html"
	} else {
		body = body tag("p", parse_para($0))
	}

}

END {
}


Remove win32ver.awk from the CVS. It is not needed because
we have a .rc file in place.

git-svn-id: b819289d3a0e8888cd070d2ae628b7cf730b797f@300763 13f79535-47bb-0310-9956-ffa450edef68

BEGIN {

  # ff bits: 1(debug), 2(prerelease), 4(patched), 8(vendor) and 32(special)
  # debug is summed based on the /Define _DEBUG
  # prerelease is based on the -dev extension,
  # patched is based on a non-standard "-ver" extension,
  # special and vendor are toggled by their args.
  #
  ff = 0;

  file=ARGV[1];
  desc=ARGV[2];
  rel_h=ARGV[3];

  filename = file;
  if (match(file, /\./)) {
    sub(/\.[^\.]*$/, "", file);
  }

  i = 4;
  while (length(ARGV[i])) {
    if (match(ARGV[i], /icon=/)) {
      icon = substr(ARGV[i], 6);
    }
    if (match(ARGV[i], /vendor=/)) {
      vendor = substr(ARGV[i], 8);
      ff = ff + 8;
    }
    if (match(ARGV[i], /special=/)) {
      special = substr(ARGV[i], 9);
      ff = ff + 32;
    }
    i = i + 1
  }

  i = i - 1;
  while (i) {
    delete ARGV[i];
    i = i - 1;
  }

  while ((getline < rel_h) > 0) {
    if (match ($0, /^#define TC._MAJOR_VERSION/)) {
      ver_major = $3;
    }
    if (match ($0, /^#define TC._MINOR_VERSION/)) {
      ver_minor = $3;
    }
    if (match ($0, /^#define TC._PATCH_VERSION/)) {
      ver_patch = $3;
    }
    if (match ($0, /^#define TC._IS_DEV_VERSION/)) {
      ver_suffix = "-dev";
      ver_build = "0";
    }
    if (match ($0, /^#undef TC._IS_DEV_VERSION/)) {
      ver_build = "100";
    }
    if (match ($0, /^.*Copyright /)) {
      copyright = substr($0, RLENGTH + 1);
    }
  }
  ver = ver_major "." ver_minor "." ver_patch ver_suffix;
  verc = ver_major "," ver_minor "," ver_patch "," ver_build;

  if (length(vendor)) {
    ff = ff + 8;
  }

  if (length(icon)) {
    print "1 ICON DISCARDABLE \"" icon "\"";
  }
  print "1 VERSIONINFO";
  print " FILEVERSION " verc "";
  print " PRODUCTVERSION " verc "";
  print " FILEFLAGSMASK 0x3fL";
  print "#if defined(_DEBUG)"
  print " FILEFLAGS 0x" sprintf("%02x", ff + 1) "L";
  print "#else"
  print " FILEFLAGS 0x" sprintf("%02x", ff) "L";
  print "#endif"
  print " FILEOS 0x40004L";
  print " FILETYPE 0x1L";
  print " FILESUBTYPE 0x0L";
  print "BEGIN";
  print "  BLOCK \"StringFileInfo\"";
  print "  BEGIN";
  print "    BLOCK \"040904b0\"";
  print "    BEGIN";
  print "    VALUE \"Comments\", "\
     "\"Licensed under the Apache License, Version 2.0 (the \"\"License\"\"); "\
     "you may not use this file except in compliance with the License.  "\
     "You may obtain a copy of the License at\\r\\n\\r\\n"\
     "http://www.apache.org/licenses/LICENSE-2.0\\r\\n\\r\\n"\
     "Unless required by applicable law or agreed to in writing, "\
     "software distributed under the License is distributed on an "\
     "\"\"AS IS\"\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, "\
     "either express or implied.  See the License for the specific "\
     "language governing permissions and limitations under the License.\\0\"";
  print "      VALUE \"CompanyName\", \"Apache Software Foundation\\0\"";
  print "      VALUE \"FileDescription\", \"" desc "\\0\"";
  print "      VALUE \"FileVersion\", \"" ver "\\0\"";
  print "      VALUE \"InternalName\", \"" file "\\0\"";
  print "      VALUE \"LegalCopyright\", \"Copyright " copyright "\\0\"";
  print "      VALUE \"OriginalFilename\", \"" filename "\\0\"";
  if (vendor) {
    print "      VALUE \"PrivateBuild\", \"" vendor "\\0\"";
  }
  if (special) {
    print "      VALUE \"SpecialBuild\", \"" vendor "\\0\"";
  }
  print "      VALUE \"ProductName\", \"Apache Portable Runtime\\0\"";
  print "      VALUE \"ProductVersion\", \"" ver "\\0\"";
  print "    END";
  print "  END";
  print "  BLOCK \"VarFileInfo\"";
  print "  BEGIN";
  print "    VALUE \"Translation\", 0x409, 1200";
  print "  END";
  print "END";
}


Reduced season number noise

Reduced season number noise

# Create column files with lists of series titles, descriptions, number
# of seasons, and number of episodes, to be pasted into a spreadsheet.
# Note that IN_CANADA affects processing.
#
# Create a separate file with a line for each episode containing
# seriesNumber, episodeURL, seriesTitle, seasonNumber, episodeNumber,
# episodeTitle, & episodeDuration with the same columns as the primary
# spreadsheet so they can be combined into one.
#
# Create a separate file with a line for each series duration
#
# Return the total duration of all series
#
# INVOCATION:
#       curl -s https://acorn.tv/192/ \
#           | awk -v TITLE_FILE=$TITLE_FILE -v DESCRIPTION_FILE=$DESCRIPTION_FILE \
#           -v NUM_SEASONS_FILE=$NUM_SEASONS_FILE -v NUM_EPISODES_FILE=$NUM_EPISODES_FILE \
#           -v EPISODE_CURL_FILE=$EPISODE_CURL_FILE -v IN_CANADA=$IN_CANADA \
#           -v EPISODE_INFO_FILE=$EPISODE_INFO_FILE -v SERIES_NUMBER=$lastRow \
#           -v DURATION_FILE=$DURATION_FILE -v ERROR_FILE=$ERROR_FILE -f getAcornFrom-seriesPages.awk
#
# INPUT:
#        <title>Acorn TV | 19-2</title>
#  ---
#        19-2
#        <meta itemprop="numberOfEpisodes" content="30" />
#        <meta itemprop="numberOfSeasons" content="3" />
#  ---
#        <meta itemprop="numberOfEpisodes" content="10" />
#        <meta itemprop="numberOfEpisodes" content="10" />
#        <meta itemprop="numberOfEpisodes" content="10" />
#  ---
#
"The writing is sublime" (New \
#        York Times) in this anything-but-a-procedural cop drama ... as reluctant partners \
#        patrolling the streets of Montreal. Not Available in Canada. CC Available.

#  ---
#
We're sorry, but Acorn TV is not available in this territory.

#  ---
#        <meta itemprop="seasonNumber" content="1" />
#  ---
#
#  ---
#        <meta itemprop="timeRequired" content="T44M06S" />
#  ---
#
Partners

#  ---
#
Season 1: Episode 1

#
Season 9 Christmas Special: Episode 1

#
#
Series 1: Episode 1

#
Series 1 (Set 1): Episode 1

#
Series 10 (Sets 13 - 14): Episode 1

#
Series 19 (Part One): Episode 1

#
#
Set 1: Episode 1

#
#
Mini Series: Episode 1

#
Miniseries: Episode 1

#
#
Black Widows Season 1: Episode 1

#
#
A Dance to the Music of Time: Episode 1

#
Christmas Special : Episode 1

#
Feature Film: Episode 1

#
#
#
#  OUTPUT:
#       $EPISODE_INFO_FILE
#

# Extract the series title
/<title>/ {
    sub (/.*<title>Acorn TV \| /,"")
    sub (/<.*/,"")
    gsub (/&/,"\\&")
    # gsub (/'/,"'")
    # gsub (/"/,"\"\"")
    if (match ($0, /^The /)) {
        $0 = substr($0, 5) ", The"
    }
    episodeLinesFound = 0
    numEpisodesStr = ""
    seriesTitle = $0
    print seriesTitle >> TITLE_FILE
    next
}

# Extract the number of episodes in the series
/itemprop="numberOfEpisodes"/ {
    split ($0,fld,"\"")
    episodeLinesFound += 1
    if (episodeLinesFound == 1)
        totalEpisodes = fld[4]
    if (episodeLinesFound != 1)
        numEpisodesStr = numEpisodesStr "+" fld[4]
    next
}

# Extract the number of seasons in the series
/itemprop="numberOfSeasons"/ {
    split ($0,fld,"\"")
    numSeasons = fld[4]
    if (numSeasons == 0)
        printf ("==> No seasons: %d\t%s\n", SERIES_NUMBER, seriesTitle)  >> ERROR_FILE
    print numSeasons >> NUM_SEASONS_FILE
    next
}

/Acorn TV is not available in this territory/ {
    printf ("==> Not available here: %d\t%s\n", SERIES_NUMBER, seriesTitle)  >> ERROR_FILE
}

# Extract the series description
/id="franchise-description"/ {
    # get rid of boilerplate
    split ($0,fld,"[<>]")
    description = fld[3]
    # get rid of unnecessary characters and text
    gsub (/\\/,"",description)
    if (IN_CANADA != "yes") {
        sub (/Series [[:digit:]]* not available in Canada\./,"",description)
        sub (/Not [Aa]vailable in Canada\./,"",description)
        sub (/NOT AVAILABLE IN CANADA\./,"",description)
    }
    sub (/CC Available\. CC Available/,"CC Available",description)
    # fix sloppy input spacing
    sub (/\.CC Available/,". CC Available",description)
    gsub (/ \./,".",description)
    gsub (/  */," ",description)
    sub (/^ */,"",description)
    sub (/ *$/,"",description)
    # fix funky HTML characters
    gsub (/'/,"'",description)
    print description >> DESCRIPTION_FILE
    next
}

# Extract the seasonNumber
# Note that Acorn "season" numbers may not correspond to actual season numbers.
# They simply refer to the number of seasons available on Acorn.
/<meta itemprop="seasonNumber" content="/ {
    split ($0,fld,"\"")
    seasonNumber = fld[4]
    next
}

# Extract the episode URL
/> EPISODE_CURL_FILE
    next
}

# Extract the episode duration
/<meta itemprop="timeRequired"/ {
    split ($0,fld,"\"")
    split (fld[4],tm,/[TMS]/)
    secs = tm[3]
    mins = tm[2] + int(secs / 60)
    hrs =  int(mins / 60)
    secs %= 60; mins %= 60
    #
    seriesSecs += secs
    seriesMins += mins + int(seriesSecs / 60)
    seriesHrs += hrs + int(seriesMins / 60)
    seriesSecs %= 60; seriesMins %= 60
    #
    totalSecs += secs
    totalMins += mins + int(totalSecs / 60)
    totalHrs += hrs + int(totalMins / 60)
    totalSecs %= 60; totalMins %= 60
    #
    episodeDuration = sprintf ("%02d:%02d:%02d", hrs, mins, secs)
    if (episodeDuration == "00:00:00")
        printf ("==> No duration: %d\t%s  %s\n", SERIES_NUMBER, seriesTitle, episodeURL)  >> ERROR_FILE
    next
}

# Extract the episode title
/
/ {
    split ($0,fld,"[<>]")
    episodeTitle = fld[3]
    gsub (/&/,"\\&", episodeTitle)
    next
}

# There are a number of variants in the "episodeNumber" string
#
#
Season 1: Episode 1

#
Season 9 Christmas Special: Episode 1

#
#
Series 1: Episode 1

#
Series 1 (Set 1): Episode 1

#
Series 10 (Sets 13 - 14): Episode 1

#
Series 19 (Part One): Episode 1

#
#
Set 1: Episode 1

#
#
Mini Series: Episode 1

#
Miniseries: Episode 1

#
#
Black Widows Season 1: Episode 1

#
#
A Dance to the Music of Time: Episode 1

#
Christmas Special : Episode 1

#
Feature Film: Episode 1

# Extract episode number
/
.*span itemprop="episodeNumber">/ {
    # If show is a Prequel, use "P"
    if (episodeURL ~ /\/prequel/)
        showType = "P"
    # If show is a Miniseries, use "M"
    if (episodeURL ~ /\/miniseries/)
        showType = "M"
    # If we don't know what kind of show it is, use "S"
    if (showType == "")
        showType = "S"
    # Default episodeType to "E"
    episodeType = "E"
    # Default bonus, parttwo, and christmasspecials episodeType to "X"
    if (episodeURL ~ /bonus\/|[0-9]{1,2}parttwo\/|christmasspecial\//)
        episodeType = "X"
    # jamaicainn & newworlds bonus seasonNumber should be 1
    if (episodeURL ~ /\/jamaicainn\/bonus\/|\/newworlds\/bonus\// && seasonNumber != 1) {
        split (episodeURL, part, "/")
        printf ("==> Corrected mismatch: https://acorn.tv/%s/%s was series %d\n", \
               part[4], part[5], seasonNumber) >> ERROR_FILE
        seasonNumber = 1
    }
    # Plain christmasspecial, seriesfinale don't increment seasonNumber
    if (episodeURL ~ /\/christmasspecial\/|\/seriesfinale\//) {
        split (episodeURL, part, "/")
        printf ("==> Corrected mismatch: https://acorn.tv/%s/%s was series %d\n", \
               part[4], part[5], seasonNumber) >> ERROR_FILE
        seasonNumber -= 1
    }
    #
    split ($0,fld,"[<>]")
    episodeNumber = fld[5]
    # Feature films don't have episodes
    if (episodeURL ~ /\/featurefilm\//)
        next
    # neither should single episode series
    if (totalEpisodes == 1)
        next
    # The season number should match that in the URL
    # Birds of a Feather, Doc Martin, Murdoch, Poirot, Rebus, Vera, and others have problems
    if (episodeURL ~ \
           /\/series[0-9]{1,2}\/|[0-9]{1,2}bonus\/|[0-9]{1,2}parttwo\/|\/murdoch\/season[0-9]{1,2}/) {
        split (episodeURL, part, "/")
        URLseasonNumber = part[5]
        sub (/bonus/,"",URLseasonNumber)
        # Foyle's War has sets: foyleswar/series9set8bonus/
        sub (/set[0-9]/,"",URLseasonNumber)
        # Midsomer Murders has two parts: midsomermurders/series19parttwo/
        sub (/parttwo/,"",URLseasonNumber)
        sub (/christmasspecial/,"",URLseasonNumber)
        sub (/[[:alpha:]]*/,"",URLseasonNumber)
        if (URLseasonNumber != seasonNumber) {
            printf ("==> Corrected mismatch: https://acorn.tv/%s/%s was series %d\n", \
               part[4], part[5], seasonNumber) >> ERROR_FILE
            seasonNumber = URLseasonNumber
        }
    }
    printf ("%d\t=HYPERLINK(\"%s\";\"%s, %s%02d%s%02d, %s\"\)\t\t\t%s\n", \
        SERIES_NUMBER, episodeURL, seriesTitle, showType, seasonNumber, episodeType, episodeNumber, \
        episodeTitle, episodeDuration) >> EPISODE_INFO_FILE
    showType = ""
    next
}

/-- Viewers Also Watched --/ {
    print (episodeLinesFound == 1 ? "=0" : "=" numEpisodesStr) >> NUM_EPISODES_FILE
    if (description == "")
        printf ("==> No description: %d\t%s\n", SERIES_NUMBER, seriesTitle)  >> ERROR_FILE
    description = ""
    #
    seriesDuration = sprintf ("%02d:%02d:%02d", seriesHrs, seriesMins, seriesSecs)
    print seriesDuration >> DURATION_FILE
    if (seriesDuration == "00:00:00")
        printf ("==> No duration: %d\t%s\n", SERIES_NUMBER, seriesTitle)  >> ERROR_FILE
    seriesSecs = 0
    seriesMins = 0
    seriesHrs = 0
    seriesDuration = ""
    #
    SERIES_NUMBER += 1
}

END {
    # Return the total duration of all series
    printf ("%02d:%02d:%02d\n", totalHrs, totalMins, totalSecs)
}

# Create column files with lists of series titles, descriptions, number
# of seasons, and number of episodes, to be pasted into a spreadsheet.
# Note that IN_CANADA affects processing.
#
# Create a separate file with a line for each episode containing
# seriesNumber, episodeURL, seriesTitle, seasonNumber, episodeNumber,
# episodeTitle, & episodeDuration with the same columns as the primary
# spreadsheet so they can be combined into one.
#
# Create a separate file with a line for each series duration
#
# Return the total duration of all series
#
# INVOCATION:
#       curl -s https://acorn.tv/192/ \
#           | awk -v TITLE_FILE=$TITLE_FILE -v DESCRIPTION_FILE=$DESCRIPTION_FILE \
#           -v NUM_SEASONS_FILE=$NUM_SEASONS_FILE -v NUM_EPISODES_FILE=$NUM_EPISODES_FILE \
#           -v EPISODE_CURL_FILE=$EPISODE_CURL_FILE -v IN_CANADA=$IN_CANADA \
#           -v EPISODE_INFO_FILE=$EPISODE_INFO_FILE -v SERIES_NUMBER=$lastRow \
#           -v DURATION_FILE=$DURATION_FILE -v ERROR_FILE=$ERROR_FILE -f getAcornFrom-seriesPages.awk
#
# INPUT:
#        <title>Acorn TV | 19-2</title>
#  ---
#        19-2
#        <meta itemprop="numberOfEpisodes" content="30" />
#        <meta itemprop="numberOfSeasons" content="3" />
#  ---
#        <meta itemprop="numberOfEpisodes" content="10" />
#        <meta itemprop="numberOfEpisodes" content="10" />
#        <meta itemprop="numberOfEpisodes" content="10" />
#  ---
#
"The writing is sublime" (New \
#        York Times) in this anything-but-a-procedural cop drama ... as reluctant partners \
#        patrolling the streets of Montreal. Not Available in Canada. CC Available.

#  ---
#
We're sorry, but Acorn TV is not available in this territory.

#  ---
#        <meta itemprop="seasonNumber" content="1" />
#  ---
#
#  ---
#        <meta itemprop="timeRequired" content="T44M06S" />
#  ---
#
Partners

#  ---
#
Season 1: Episode 1

#
Season 9 Christmas Special: Episode 1

#
#
Series 1: Episode 1

#
Series 1 (Set 1): Episode 1

#
Series 10 (Sets 13 - 14): Episode 1

#
Series 19 (Part One): Episode 1

#
#
Set 1: Episode 1

#
#
Mini Series: Episode 1

#
Miniseries: Episode 1

#
#
Black Widows Season 1: Episode 1

#
#
A Dance to the Music of Time: Episode 1

#
Christmas Special : Episode 1

#
Feature Film: Episode 1

#
#
#
#  OUTPUT:
#       $EPISODE_INFO_FILE
#

# Extract the series title
/<title>/ {
    sub (/.*<title>Acorn TV \| /,"")
    sub (/<.*/,"")
    gsub (/&/,"\\&")
    # gsub (/'/,"'")
    # gsub (/"/,"\"\"")
    if (match ($0, /^The /)) {
        $0 = substr($0, 5) ", The"
    }
    episodeLinesFound = 0
    numEpisodesStr = ""
    seriesTitle = $0
    print seriesTitle >> TITLE_FILE
    next
}

# Extract the number of episodes in the series
/itemprop="numberOfEpisodes"/ {
    split ($0,fld,"\"")
    episodeLinesFound += 1
    if (episodeLinesFound == 1)
        totalEpisodes = fld[4]
    if (episodeLinesFound != 1)
        numEpisodesStr = numEpisodesStr "+" fld[4]
    next
}

# Extract the number of seasons in the series
/itemprop="numberOfSeasons"/ {
    split ($0,fld,"\"")
    numSeasons = fld[4]
    if (numSeasons == 0)
        printf ("==> No seasons: %d\t%s\n", SERIES_NUMBER, seriesTitle)  >> ERROR_FILE
    print numSeasons >> NUM_SEASONS_FILE
    next
}

/Acorn TV is not available in this territory/ {
    printf ("==> Not available here: %d\t%s\n", SERIES_NUMBER, seriesTitle)  >> ERROR_FILE
}

# Extract the series description
/id="franchise-description"/ {
    # get rid of boilerplate
    split ($0,fld,"[<>]")
    description = fld[3]
    # get rid of unnecessary characters and text
    gsub (/\\/,"",description)
    if (IN_CANADA != "yes") {
        sub (/Series [[:digit:]]* not available in Canada\./,"",description)
        sub (/Not [Aa]vailable in Canada\./,"",description)
        sub (/NOT AVAILABLE IN CANADA\./,"",description)
    }
    sub (/CC Available\. CC Available/,"CC Available",description)
    # fix sloppy input spacing
    sub (/\.CC Available/,". CC Available",description)
    gsub (/ \./,".",description)
    gsub (/  */," ",description)
    sub (/^ */,"",description)
    sub (/ *$/,"",description)
    # fix funky HTML characters
    gsub (/'/,"'",description)
    print description >> DESCRIPTION_FILE
    next
}

# Extract the seasonNumber
# Note that Acorn "season" numbers may not correspond to actual season numbers.
# They simply refer to the number of seasons available on Acorn.
/<meta itemprop="seasonNumber" content="/ {
    split ($0,fld,"\"")
    seasonNumber = fld[4]
    next
}

# Extract the episode URL
/> EPISODE_CURL_FILE
    next
}

# Extract the episode duration
/<meta itemprop="timeRequired"/ {
    split ($0,fld,"\"")
    split (fld[4],tm,/[TMS]/)
    secs = tm[3]
    mins = tm[2] + int(secs / 60)
    hrs =  int(mins / 60)
    secs %= 60; mins %= 60
    #
    seriesSecs += secs
    seriesMins += mins + int(seriesSecs / 60)
    seriesHrs += hrs + int(seriesMins / 60)
    seriesSecs %= 60; seriesMins %= 60
    #
    totalSecs += secs
    totalMins += mins + int(totalSecs / 60)
    totalHrs += hrs + int(totalMins / 60)
    totalSecs %= 60; totalMins %= 60
    #
    episodeDuration = sprintf ("%02d:%02d:%02d", hrs, mins, secs)
    if (episodeDuration == "00:00:00")
        printf ("==> No duration: %d\t%s  %s\n", SERIES_NUMBER, seriesTitle, episodeURL)  >> ERROR_FILE
    next
}

# Extract the episode title
/
/ {
    split ($0,fld,"[<>]")
    episodeTitle = fld[3]
    gsub (/&/,"\\&", episodeTitle)
    next
}

# There are a number of variants in the "episodeNumber" string
#
#
Season 1: Episode 1

#
Season 9 Christmas Special: Episode 1

#
#
Series 1: Episode 1

#
Series 1 (Set 1): Episode 1

#
Series 10 (Sets 13 - 14): Episode 1

#
Series 19 (Part One): Episode 1

#
#
Set 1: Episode 1

#
#
Mini Series: Episode 1

#
Miniseries: Episode 1

#
#
Black Widows Season 1: Episode 1

#
#
A Dance to the Music of Time: Episode 1

#
Christmas Special : Episode 1

#
Feature Film: Episode 1

# Extract episode number
/
.*span itemprop="episodeNumber">/ {
    # If show is a Prequel, use "P"
    if (episodeURL ~ /\/prequel/)
        showType = "P"
    # If show is a Miniseries, use "M"
    if (episodeURL ~ /\/miniseries/)
        showType = "M"
    # If we don't know what kind of show it is, use "S"
    if (showType == "")
        showType = "S"
    # Default episodeType to "E"
    episodeType = "E"
    # Default bonus, parttwo, and christmasspecials episodeType to "X"
    if (episodeURL ~ /bonus\/|[0-9]{1,2}parttwo\/|christmasspecial\//)
        episodeType = "X"
    # jamaicainn & newworlds bonus seasonNumber should be 1
    if (episodeURL ~ /\/jamaicainn\/bonus\/|\/newworlds\/bonus\//) {
        split (episodeURL, part, "/")
        printf ("==> Corrected mismatch: https://acorn.tv/%s/%s was series %d\n", \
               part[4], part[5], seasonNumber) >> ERROR_FILE
        seasonNumber = 1
    }
    # Plain christmasspecial, seriesfinale don't increment seasonNumber
    if (episodeURL ~ /\/christmasspecial\/|\/seriesfinale\//) {
        split (episodeURL, part, "/")
        printf ("==> Corrected mismatch: https://acorn.tv/%s/%s was series %d\n", \
               part[4], part[5], seasonNumber) >> ERROR_FILE
        seasonNumber -= 1
    }
    #
    split ($0,fld,"[<>]")
    episodeNumber = fld[5]
    # Feature films don't have episodes
    if (episodeURL ~ /\/featurefilm\//)
        next
    # neither should single episode series
    if (totalEpisodes == 1)
        next
    # The season number should match that in the URL
    # Birds of a Feather, Doc Martin, Murdoch, Poirot, Rebus, Vera, and others have problems
    if (episodeURL ~ \
           /\/series[0-9]{1,2}\/|[0-9]{1,2}bonus\/|[0-9]{1,2}parttwo\/|\/murdoch\/season[0-9]{1,2}/) {
        split (episodeURL, part, "/")
        URLseasonNumber = part[5]
        sub (/bonus/,"",URLseasonNumber)
        # Foyle's War has sets: foyleswar/series9set8bonus/
        sub (/set[0-9]/,"",URLseasonNumber)
        # Midsomer Murders has two parts: midsomermurders/series19parttwo/
        sub (/parttwo/,"",URLseasonNumber)
        sub (/christmasspecial/,"",URLseasonNumber)
        sub (/[[:alpha:]]*/,"",URLseasonNumber)
        if (URLseasonNumber != seasonNumber) {
            printf ("==> Corrected mismatch: https://acorn.tv/%s/%s was series %d\n", \
               part[4], part[5], seasonNumber) >> ERROR_FILE
            seasonNumber = URLseasonNumber
        }
    }
    printf ("%d\t=HYPERLINK(\"%s\";\"%s, %s%02d%s%02d, %s\"\)\t\t\t%s\n", \
        SERIES_NUMBER, episodeURL, seriesTitle, showType, seasonNumber, episodeType, episodeNumber, \
        episodeTitle, episodeDuration) >> EPISODE_INFO_FILE
    showType = ""
    next
}

/-- Viewers Also Watched --/ {
    print (episodeLinesFound == 1 ? "=0" : "=" numEpisodesStr) >> NUM_EPISODES_FILE
    if (description == "")
        printf ("==> No description: %d\t%s\n", SERIES_NUMBER, seriesTitle)  >> ERROR_FILE
    description = ""
    #
    seriesDuration = sprintf ("%02d:%02d:%02d", seriesHrs, seriesMins, seriesSecs)
    print seriesDuration >> DURATION_FILE
    if (seriesDuration == "00:00:00")
        printf ("==> No duration: %d\t%s\n", SERIES_NUMBER, seriesTitle)  >> ERROR_FILE
    seriesSecs = 0
    seriesMins = 0
    seriesHrs = 0
    seriesDuration = ""
    #
    SERIES_NUMBER += 1
}

END {
    # Return the total duration of all series
    printf ("%02d:%02d:%02d\n", totalHrs, totalMins, totalSecs)
}


Cleanup duration format

Cleanup duration format

# Create column files with lists of series titles, descriptions, number
# of seasons, and number of episodes, to be pasted into a spreadsheet.
# Note that IN_CANADA affects processing.
#
# Create a separate file with a line for each episode containing
# seriesNumber, episodeURL, seriesTitle, seasonNumber, episodeNumber,
# episodeTitle, & episodeDuration with the same columns as the primary
# spreadsheet so they can be combined into one.
#
# INVOCATION:
#       curl -s https://acorn.tv/192/ \
#           | awk -v TITLE_FILE=$TITLE_FILE -v DESCRIPTION_FILE=$DESCRIPTION_FILE \
#           -v NUM_SEASONS_FILE=$NUM_SEASONS_FILE -v NUM_EPISODES_FILE=$NUM_EPISODES_FILE \
#           -v EPISODE_URL_FILE=$EPISODE_URL_FILE -v IN_CANADA=$IN_CANADA \
#           -v EPISODE_INFO_FILE=$EPISODE_INFO_FILE -v SERIES_NUMBER=$lastRow \
#           -f getAcornFrom-seriesPages.awk
#
# INPUT:
#        <title>Acorn TV | 19-2</title>
#  ---
#        19-2
#        <meta itemprop="numberOfEpisodes" content="30" />
#        <meta itemprop="numberOfSeasons" content="3" />
#  ---
#
"The writing is sublime" (New \
#        York Times) in this anything-but-a-procedural cop drama ... as reluctant partners \
#        patrolling the streets of Montreal. Not Available in Canada. CC Available.

#  ---
#        <meta itemprop="seasonNumber" content="1" />
#  ---
#
#  ---
#        <meta itemprop="timeRequired" content="T44M06S" />
#  ---
#
Partners

#  ---
#
Season 1: Episode 1

#
Season 9 Christmas Special: Episode 1

#
#
Series 1: Episode 1

#
Series 1 (Set 1): Episode 1

#
Series 10 (Sets 13 - 14): Episode 1

#
Series 19 (Part One): Episode 1

#
#
Set 1: Episode 1

#
#
Mini Series: Episode 1

#
Miniseries: Episode 1

#
#
Black Widows Season 1: Episode 1

#
#
A Dance to the Music of Time: Episode 1

#
Christmas Special : Episode 1

#
Feature Film: Episode 1

#
#  OUTPUT:
#       $EPISODE_INFO_FILE
#

# Extract and save whether this is a miniseries
/
Miniseries:/ || /
Mini Series:/ {
    showType = "M"
}

# Extract the series title
/<title>/ {
    sub (/.*<title>Acorn TV \| /,"")
    sub (/<.*/,"")
    gsub (/&/,"\\&")
    # gsub (/'/,"'")
    # gsub (/"/,"\"\"")
    if (match ($0, /^The /)) {
        $0 = substr($0, 5) ", The"
    }
    episodeLinesFound = 0
    numEpisodesStr = ""
    seriesTitle = $0
    print seriesTitle >> TITLE_FILE
    next
}

# Extract the number of episodes in the series
/itemprop="numberOfEpisodes"/ {
    split ($0,fld,"\"")
    episodeLinesFound += 1
    if (episodeLinesFound != 1)
        numEpisodesStr = numEpisodesStr "+" fld[4]
    next
}

# Extract the number of seasons in the series
/itemprop="numberOfSeasons"/ {
    split ($0,fld,"\"")
    numSeasons = fld[4]
    print numSeasons >> NUM_SEASONS_FILE
    next
}

# Extract the series description
/id="franchise-description"/ {
    # get rid of boilerplate
    sub (/.*itemprop="description">/,"")
    sub (/<\/p>$/,"")
    # get rid of unnecessary characters and text
    gsub (/\\/,"")
    if (IN_CANADA != "yes") {
        sub (/Series 1 not available in Canada\./,"")
        sub (/Not [Aa]vailable in Canada\./,"")
        sub (/NOT AVAILABLE IN CANADA\./,"")
    }
    sub (/CC Available\. CC Available/,"CC Available")
    # fix sloppy input spacing
    sub (/\.CC Available/,". CC Available")
    gsub (/ \./,".")
    gsub (/  */," ")
    sub (/^ */,"")
    sub (/ *$/,"")
    # fix funky HTML characters
    gsub (/'/,"'")
    print >> DESCRIPTION_FILE
    next
}

# Extract the seasonNumber
/<meta itemprop="seasonNumber" content="/ {
    split ($0,fld,"\"")
    seasonNumber = fld[4]
}

# Extract the episode URL
/> EPISODE_URL_FILE
}

# Extract the episode duration
/<meta itemprop="timeRequired"/ {
    split ($0,fld,"\"")
    split (fld[4],tm,/[TMS]/)
    secs = tm[3]
    mins = tm[2] + int(secs / 60)
    hrs =  int(mins / 60)
    secs %= 60; mins %= 60
    episodeDuration = sprintf ("%02d:%02d:%02d", hrs, mins, secs)
    next
}

# Extract the episode title
/
/ {
    sub (/.*
/,"")
    sub (/<.*/,"")
    gsub (/&/,"\\&")
    episodeTitle = $0
    next
}

# There are a number of variants in the "episodeNumber" string
#
#
Season 1: Episode 1

#
Season 9 Christmas Special: Episode 1

#
#
Series 1: Episode 1

#
Series 1 (Set 1): Episode 1

#
Series 10 (Sets 13 - 14): Episode 1

#
Series 19 (Part One): Episode 1

#
#
Set 1: Episode 1

#
#
Mini Series: Episode 1

#
Miniseries: Episode 1

#
#
Black Widows Season 1: Episode 1

#
#
A Dance to the Music of Time: Episode 1

#
Christmas Special : Episode 1

#
Feature Film: Episode 1

# Extract episode number
/
.*span itemprop="episodeNumber">/ {
    # If we don't know what kind of show it is, use "S"
    if (showType == "")
        showType = "S"
    sub (/.*episodeNumber">/,"")
    sub (/<\/span>.*/,"")
    episodeNumber = $0
    # print seriesTitle " " showType seasonNumber "E" episodeNumber >> "debug.txt"
    printf ("%d\t=HYPERLINK(\"%s\";\"%s, %s%02dE%02d, %s\"\)\t\t\t%s\n", \
        SERIES_NUMBER, episodeURL, seriesTitle, showType, seasonNumber, episodeNumber, episodeTitle, \
        episodeDuration) >>EPISODE_INFO_FILE
}

END { print (episodeLinesFound == 1 ? "=0" : "=" numEpisodesStr) >> NUM_EPISODES_FILE }

# Create column files with lists of series titles, descriptions, number
# of seasons, and number of episodes, to be pasted into a spreadsheet.
# Note that IN_CANADA affects processing.
#
# Create a separate file with a line for each episode containing
# seriesNumber, episodeURL, seriesTitle, seasonNumber, episodeNumber,
# episodeTitle, & episodeDuration with the same columns as the primary
# spreadsheet so they can be combined into one.
#
# INVOCATION:
#       curl -s https://acorn.tv/192/ \
#           | awk -v TITLE_FILE=$TITLE_FILE -v DESCRIPTION_FILE=$DESCRIPTION_FILE \
#           -v NUM_SEASONS_FILE=$NUM_SEASONS_FILE -v NUM_EPISODES_FILE=$NUM_EPISODES_FILE \
#           -v EPISODE_URL_FILE=$EPISODE_URL_FILE -v IN_CANADA=$IN_CANADA \
#           -v EPISODE_INFO_FILE=$EPISODE_INFO_FILE -v SERIES_NUMBER=$lastRow \
#           -f getAcornFrom-seriesPages.awk
#
# INPUT:
#        <title>Acorn TV | 19-2</title>
#  ---
#        19-2
#        <meta itemprop="numberOfEpisodes" content="30" />
#        <meta itemprop="numberOfSeasons" content="3" />
#  ---
#
"The writing is sublime" (New \
#        York Times) in this anything-but-a-procedural cop drama ... as reluctant partners \
#        patrolling the streets of Montreal. Not Available in Canada. CC Available.

#  ---
#        <meta itemprop="seasonNumber" content="1" />
#  ---
#
#  ---
#        <meta itemprop="timeRequired" content="T44M06S" />
#  ---
#
Partners

#  ---
#
Season 1: Episode 1

#
Season 9 Christmas Special: Episode 1

#
#
Series 1: Episode 1

#
Series 1 (Set 1): Episode 1

#
Series 10 (Sets 13 - 14): Episode 1

#
Series 19 (Part One): Episode 1

#
#
Set 1: Episode 1

#
#
Mini Series: Episode 1

#
Miniseries: Episode 1

#
#
Black Widows Season 1: Episode 1

#
#
A Dance to the Music of Time: Episode 1

#
Christmas Special : Episode 1

#
Feature Film: Episode 1

#
#  OUTPUT:
#       $EPISODE_INFO_FILE
#

# Extract and save whether this is a miniseries
/
Miniseries:/ || /
Mini Series:/ {
    showType = "M"
}

# Extract the series title
/<title>/ {
    sub (/.*<title>Acorn TV \| /,"")
    sub (/<.*/,"")
    gsub (/&/,"\\&")
    # gsub (/'/,"'")
    # gsub (/"/,"\"\"")
    if (match ($0, /^The /)) {
        $0 = substr($0, 5) ", The"
    }
    episodeLinesFound = 0
    numEpisodesStr = ""
    seriesTitle = $0
    print seriesTitle >> TITLE_FILE
    next
}

# Extract the number of episodes in the series
/itemprop="numberOfEpisodes"/ {
    split ($0,fld,"\"")
    episodeLinesFound += 1
    if (episodeLinesFound != 1)
        numEpisodesStr = numEpisodesStr "+" fld[4]
    next
}

# Extract the number of seasons in the series
/itemprop="numberOfSeasons"/ {
    split ($0,fld,"\"")
    numSeasons = fld[4]
    print numSeasons >> NUM_SEASONS_FILE
    next
}

# Extract the series description
/id="franchise-description"/ {
    # get rid of boilerplate
    sub (/.*itemprop="description">/,"")
    sub (/<\/p>$/,"")
    # get rid of unnecessary characters and text
    gsub (/\\/,"")
    if (IN_CANADA != "yes") {
        sub (/Series 1 not available in Canada\./,"")
        sub (/Not [Aa]vailable in Canada\./,"")
        sub (/NOT AVAILABLE IN CANADA\./,"")
    }
    sub (/CC Available\. CC Available/,"CC Available")
    # fix sloppy input spacing
    sub (/\.CC Available/,". CC Available")
    gsub (/ \./,".")
    gsub (/  */," ")
    sub (/^ */,"")
    sub (/ *$/,"")
    # fix funky HTML characters
    gsub (/'/,"'")
    print >> DESCRIPTION_FILE
    next
}

# Extract the seasonNumber
/<meta itemprop="seasonNumber" content="/ {
    split ($0,fld,"\"")
    seasonNumber = fld[4]
}

# Extract the episode URL
/> EPISODE_URL_FILE
}

# Extract the episode duration
/<meta itemprop="timeRequired"/ {
    split ($0,fld,"\"")
    split (fld[4],tm,/[TMS]/)
    secs = tm[3]
    mins = tm[2] + int(secs / 60)
    hrs =  int(mins / 60)
    secs %= 60; mins %= 60
    episodeDuration = hrs ":" mins ":" secs
    next
}

# Extract the episode title
/
/ {
    sub (/.*
/,"")
    sub (/<.*/,"")
    gsub (/&/,"\\&")
    episodeTitle = $0
    next
}

# There are a number of variants in the "episodeNumber" string
#
#
Season 1: Episode 1

#
Season 9 Christmas Special: Episode 1

#
#
Series 1: Episode 1

#
Series 1 (Set 1): Episode 1

#
Series 10 (Sets 13 - 14): Episode 1

#
Series 19 (Part One): Episode 1

#
#
Set 1: Episode 1

#
#
Mini Series: Episode 1

#
Miniseries: Episode 1

#
#
Black Widows Season 1: Episode 1

#
#
A Dance to the Music of Time: Episode 1

#
Christmas Special : Episode 1

#
Feature Film: Episode 1

# Extract episode number
/
.*span itemprop="episodeNumber">/ {
    # If we don't know what kind of show it is, use "S"
    if (showType == "")
        showType = "S"
    sub (/.*episodeNumber">/,"")
    sub (/<\/span>.*/,"")
    episodeNumber = $0
    # print seriesTitle " " showType seasonNumber "E" episodeNumber >> "debug.txt"
    printf ("%d\t=HYPERLINK(\"%s\";\"%s, %s%02dE%02d, %s\"\)\t\t\t%s\n", \
        SERIES_NUMBER, episodeURL, seriesTitle, showType, seasonNumber, episodeNumber, episodeTitle, \
        episodeDuration) >>EPISODE_INFO_FILE
}

END { print (episodeLinesFound == 1 ? "=0" : "=" numEpisodesStr) >> NUM_EPISODES_FILE }


Add golang snippets

Add golang snippets

BEGIN {
  ft = ENVIRON["FT"]

  # global
  FIXED["--"]   = "-------------------------------------------------"
  FIXED["#--"]  = "#-------------------------------------------------"
  FIXED["isod"] = strftime("%F")
  FIXED["ts"]   = strftime("%s")
  FIXED["cr"]   = "⏎"

  # javascript
  FIXED["javascript:fn"]       = "function"
  FIXED["javascript:function"] = "function (|) {}"
  FIXED["javascript:fori"]     = "for (let i = 0; i < |; i + = 1) {"
  FIXED["javascript:fe"]       = "forEach"
  FIXED["javascript:)"]        = ") => "
  PREFIX["javascript:json"]       = "JSON.stringify"
  PREFIX["javascript:require"]    = "require(\"|\")"
  PREFIX["javascript:console"]    = "console."
  PREFIX["javascript:settimeout"] = "setTimeout"

  # r
  FIXED["r:fn"] = "function"
  FIXED["r:|"]  = "%>%"
  FIXED["r:>"]  = "%>%"

  # go
  FIXED["go://--"]    = "//-------------------------------------------------"
  FIXED["go:fn"]      = "func |() {\n}"
  FIXED["go:fori"]    = "for i := 0; i < |; i++ {}"
  FIXED["go:forr"]    = "for i, _ := range | {}"
  FIXED["go:handler"] = "func(w http.ResponseWriter, r *http.Request)"
  PREFIX["go:print"] = "fmt.Printf(\"%v\\n\", |)"
}

#-------------------------------------------------

# blank, skip
NF == 0 {
  print
  next
}

# exact matches
{
  result = FIXED[$0]
  if (result) {
    print result
    next
  }
}

# no ft, skip
ft == "" {
  print
  next
}

# exact matches, w/ ft
{
  result = FIXED[ft ":" $0]
  if (result) {
    print result
    next
  }
}

# prefix matches, w/ ft
{
  for (k in PREFIX) {
    if (index(k, ft ":" $0) == 1) {
      print PREFIX[k]
      next
    }
  }
}

# print everything else as-is
{
  print
}

BEGIN {
  ft = ENVIRON["FT"]

  # global
  FIXED["--"]   = "-------------------------------------------------"
  FIXED["isod"] = strftime("%F")
  FIXED["ts"]   = strftime("%s")
  FIXED["cr"]   = "⏎"

  # javascript
  FIXED["javascript:fn"]       = "function"
  FIXED["javascript:function"] = "function (|) {}"
  FIXED["javascript:fori"]     = "for (let i = 0; i < |; i + = 1) {"
  FIXED["javascript:fe"]       = "forEach"
  FIXED["javascript:)"]        = ") => "
  PREFIX["javascript:json"]       = "JSON.stringify"
  PREFIX["javascript:require"]    = "require(\"|\")"
  PREFIX["javascript:console"]    = "console."
  PREFIX["javascript:settimeout"] = "setTimeout"

  # r
  FIXED["r:fn"] = "function"
  FIXED["r:|"]  = "%>%"
  FIXED["r:>"]  = "%>%"
}

#-------------------------------------------------

# blank, skip
NF == 0 {
  print
  next
}

# exact matches
{
  result = FIXED[$0]
  if (result) {
    print result
    next
  }
}

# no ft, skip
ft == "" {
  print
  next
}

# exact matches, w/ ft
{
  result = FIXED[ft ":" $0]
  if (result) {
    print result
    next
  }
}

# prefix matches, w/ ft
{
  for (k in PREFIX) {
    if (index(k, ft ":" $0) == 1) {
      print PREFIX[k]
      next
    }
  }
}

# print everything else as-is
{
  print
}