#!/usr/bin/env perl

use strict;
use warnings;
use File::Copy;                                 # Module for copying files. Used to replace output file with temp cleaned-up file.
use utf8;                                       # Required to process Unicode text (the phonetic symbols).
use Encode;                                     # Module for dealing with different text encodings, such as UTF-8.
use open qw(:std :encoding(UTF-8));
use Getopt::Long qw(:config no_auto_abbrev);    # Module for processing command line options.
use Term::ANSIColor qw(colored);                # For color output (works on Linux, macOS, and modern Windows terminals).

#################################################################################
#                  SUBROUTINE: WINDOWS-SAFE COLOR WRAPPER                       #
#################################################################################
# Both legacy Windows cmd.exe and PowerShell can display raw ANSI/VT100
# escape codes as literal clutter (e.g. "←[1;32m") instead of actual colors,
# rather than reliably processing them -- so rather than try to detect
# specific "known good" Windows terminals (which proved unreliable), color
# is simply disabled outright on Windows. Linux and macOS terminals are
# unaffected and keep full color output.
our $_use_color = ( $^O eq "MSWin32" ) ? 0 : 1;

sub safe_colored {
     my ( $text, @rest ) = @_;
     return $text unless $_use_color;
     return colored( $text, @rest );
}
use Roman;                                      # Convert Roman numerals
use Lingua::ES::Numeros;                        # Module to convert numerals to Spanish text.


my $version = "2.0.0";
# Released 2026-07-11

#######################################################################################
#                                       Perkins                                       #
#                         Copyright (c) 2026 Scott Sadowsky                           #
#                                                                                     #
#                 http://sadowsky.cl - ssadowsky at gmail period com                  #
#    Licensed under the GNU Affero General Public License, version 3 (GNU AGPLv3)     #
#                                                                                     #
#                   For help, run the program with the -h switch.                     #
#             For usage examples, run the program with the -u switch.                 #
#                                                                                     #
#######################################################################################
#                                                                                     #
# Script to phonetically transcribe, syllabify and determine accents in Spanish text. #
#                                                                                     #
# USE:    ./perkins.pl [OPTIONS] -i=input_file.txt [-o=output_file.txt]               #
#                                                                                     #
# NOTE:   Input and config files MUST be Unicode (UTF-8).                             #
#                                                                                     #
#######################################################################################

#################################################################################
# Perkins, the Phonetician's Assistant.                                         #
# Copyright © 2026 Scott Sadowsky                                             #
# http://sadowsky.cl · s s a d o w s k y A T g m a i l D O T com                #
#                                                                               #
# This program is free software: you can redistribute it and/or modify          #
# it under the terms of the GNU Affero General Public License as published      #
# by the Free Software Foundation, either version 3 of the License, or          #
# (at your option) any later version.                                           #
#                                                                               #
# This program is distributed in the hope that it will be useful,               #
# but WITHOUT ANY WARRANTY; without even the implied warranty of                #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 #
# GNU Affero General Public License for more details.                           #
#                                                                               #
# You should have received a copy of the GNU Affero General Public License      #
# along with this program.  If not, see <http://www.gnu.org/licenses/>.         #
#################################################################################

# CHANGELOG VIS-A-VIS PERKINS 1.0.6
#
# v2.0.0 (usage-guide update)
# - The -u/--usage guide hadn't been updated to reflect everything added
#     since it was last written -- it only demonstrated F/CV/CVG/CVN/
#     manner/place output modes. Added usage examples (both languages)
#     for: -voicing (voicing output mode); --decimal-period/
#     --decimal-comma (with a worked example showing the same source
#     number read two different ways); and the meta-configurations
#     -g2p, -phonword, -vrt, -syl-list, -coscach, and -word-list, each
#     with real, verified example output generated by actually running
#     the script (not hand-written/guessed).
#
# v1.9.119
# - Simplified the Windows color fix from v1.9.118: PowerShell showed the
#     same literal escape-code clutter as legacy cmd.exe, meaning the
#     "known ANSI-capable terminal" environment-variable detection
#     (WT_SESSION/ANSICON/ConEmuANSI/TERM_PROGRAM) wasn't reliable
#     enough in practice. Replaced with a simple, unconditional rule:
#     color is disabled outright on Windows ($^O eq "MSWin32"), full
#     stop, regardless of which terminal is in use. Linux and macOS are
#     unaffected and keep full color output in every case.
#
# v1.9.118
# - FIXED: on legacy Windows cmd.exe (which doesn't process ANSI/VT100
#     escape codes by default, unlike Linux/macOS terminals and modern
#     Windows terminals like Windows Terminal/ConEmu/ANSICON), the color
#     added in earlier versions was showing up as literal escape-code
#     clutter (e.g. "←[1;32m-dp←[0m") instead of actual colors -- the
#     raw ESC byte gets displayed per the console's default OEM codepage
#     (where it renders as "←") since VT100 processing was never
#     enabled. This affected -h/-u output and the ACTIVE SETTINGS table
#     shown on every normal run.
# - FIX: added safe_colored(), a wrapper around Term::ANSIColor's
#     colored() that detects Windows and checks for a known ANSI-
#     capable terminal (WT_SESSION, ANSICON, ConEmuANSI, or
#     TERM_PROGRAM env vars); if none is found, it returns the plain,
#     uncolored text instead. All ~15 call sites that used to call
#     colored() directly (error messages, the ACTIVE SETTINGS table,
#     color_header()/color_flags() for help text) now go through this
#     wrapper. No change in behavior on Linux/macOS or modern Windows
#     terminals; legacy cmd.exe now gets clean, readable plain text
#     instead of escape-code clutter.
# - NOTE: garbled Unicode/IPA symbols in cmd.exe (e.g. "├æ" instead of
#     "Ñ") are a separate, pre-existing, and already-documented
#     limitation (cmd.exe's default codepage isn't UTF-8), unrelated to
#     this fix -- see the existing banner note about this.
#
# v1.9.117
# - FIXED the long-standing "KNOWN ISSUE" (previously printed in the
#     startup banner): cross-word resyllabification (e.g. "es el" ->
#     "e.sel", "los hombres" -> "lo.som.bres") only ever worked in
#     phonemic mode. Root cause: word_level_syllab_adjust() (the
#     function that performs the resyllabification, run as a
#     post-processing cleanup pass over the whole output file) matches
#     literal lowercase IPA phoneme characters via regex. CV/CVG/
#     CVNLRG/manner/place/voicing modes used to convert phonemes to
#     their category letters (C/V/N/L/R/G, P/N/R/T/F/L/A/X/V, etc.)
#     PER WORD, inside transcribe_word(), before that cleanup pass ever
#     ran -- so by the time word_level_syllab_adjust() saw the line, the
#     literal IPA characters it was looking for no longer existed, and
#     the cross-word merge silently never fired for these modes. Each
#     word was syllabified correctly in isolation, then simply joined
#     to its neighbors with a plain word-boundary dot.
# - FIX: moved format conversion (the CV/CVG/CVNLRG/manner/place/voicing
#     phoneme-to-letter substitution, previously in transcribe_word())
#     to run AFTER word_level_syllab_adjust() in the output-file cleanup
#     pass, instead of before. transcribe_word() now always produces
#     pure IPA phonemes regardless of output mode; format conversion
#     happens once, on the whole already-resyllabified line. Also moved
#     the upper/lowercase-adjustment step to after format conversion
#     (it used to run first, which would have corrupted the IPA
#     characters word_level_syllab_adjust needs to match, once format
#     conversion moved past it).
# - Verified against CV, CVG, CVNLRG, MANNER, PLACE, and VOICING modes:
#     syllable-boundary positions (and which segment each consonant
#     letter belongs to) now match the phonemic-mode transcription
#     exactly, syllable-for-syllable, for the same input and settings.
# - Removed the now-obsolete "KNOWN ISSUE" message from the startup
#     banner (both English and Spanish).
# - Added 5 dedicated regression test cases (resyllab_phonemic,
#     resyllab_cvnlrg, resyllab_manner, resyllab_place,
#     resyllab_voicing) covering this fix.
#
# v1.9.116
# - Fixed: a number immediately followed by a unit abbreviation with no
#     space (e.g. "50kg", "3cm", "30km/h") was being caught by the
#     mixed-alphanumeric identifier detector (Category A, in prelc_pass)
#     and spelled out letter-by-letter/digit-by-digit ("cinco cero ka ge")
#     instead of being converted to words ("cincuenta kilogramos") by the
#     dedicated unit-conversion logic. Numbers with a space before the
#     unit (e.g. "4 km²") were already working correctly; this only
#     affected the no-space form. Fixed by excluding known unit suffixes
#     from Category A identifier detection so the unit-conversion logic
#     downstream gets a chance to handle them.
# - Fixed: a standalone capitalized letter that is itself an ordinary
#     Spanish word (most commonly "Y"/"O" at the start of a sentence or
#     clause, e.g. "...es? Y una exclamación...") was being read as its
#     letter NAME ("i griega") instead of the actual word, via Category B
#     identifier detection (also in prelc_pass), which matches any
#     standalone uppercase letter outside the Roman-numeral alphabet.
#     The code's own comment already documented the intended behavior as
#     requiring length >= 2, but the regex didn't actually enforce it;
#     fixed to match the documented intent. Multi-letter identifiers
#     (NASA, IXA, etc.) are unaffected and continue to be spelled out
#     correctly.
# - Added two dedicated regression test cases (unit_no_space,
#     single_letter_conjunction) covering both fixes.
#
# v2.0.1
# - Added a new global setting, $decimal_symbol ("," or "."), controlling
#     which symbol separates whole numbers from fractions (the decimal
#     point) throughout ALL number processing (currency, percentages,
#     units/temperatures, and plain standalone decimals). The OTHER
#     symbol is always treated as the thousands (grouping) separator.
#     Settable via --decimal-period/-dp (period=decimal) or
#     --decimal-comma/-dc (comma=decimal, the default, matching the
#     existing Spanish/Chilean convention). Also settable via
#     perkins.ini (decimal_symbol = , or .).
# - This replaces several independent, ad-hoc "try both European and
#     American conventions and guess" heuristics that were previously
#     scattered across the currency/percentage/unit parsing code with a
#     single shared helper, _split_by_decimal_convention(), used
#     consistently everywhere. As a direct, intended consequence, some
#     amounts that were previously flagged as genuinely ambiguous (e.g.
#     "$1.500" -- unclear whether European 1.5 or American 1,500) are now
#     resolved unambiguously according to the explicitly declared
#     convention (e.g. "$1.500" -> "1500 pesos" under the default
#     comma-decimal convention, since "." can now only mean thousands).
# - Added the new setting to the ACTIVE SETTINGS table (NUMBERS section),
#     the perkins.ini whitelist and documentation, and -h help text (both
#     languages).
#
# v1.9.114
# - Added a version/author banner box at the very start of
#     print_console_greeting(), printed before anything else in that sub
#     (before the "file successfully read" message):
#       ===========================================
#       = Perkins v$version
#       = Scott Sadowsky · ssadowsky at g mail dot com
#       ===========================================
#     The version number is pulled from $version dynamically, not
#     hardcoded. Shown regardless of interface language.
#
# v1.9.113
# - Changed help-text section headers from bold cyan to bold blue, to be
#     more visually distinct from the bold green used for flag names.
# - color_flags() now detects "=PLACEHOLDER" style dummy values in flag
#     tokens (e.g. "--format=MODE", "--input=MyFile.txt", "-sn=SYMBOL")
#     and colors the flag part green and the placeholder part yellow,
#     making it visually obvious which part is literal and which the
#     user is meant to replace with their own value.
#
# v1.9.112
# - Added color to the -h help output: section headers are now bold cyan,
#     and flag names are bold green -- but not the commas separating
#     multiple aliases for the same flag, which stay in the default
#     color. Implemented via two new small helpers, color_header() and
#     color_flags(), built on the Term::ANSIColor colored() function
#     already used elsewhere in the script.
# - Reorganized the help text (both languages) to follow the same section
#     order as perkins.ini: MAIN OPTIONS, META-CONFIGURATIONS,
#     TRANSCRIPTION MODE OPTIONS, SPECIFIC PHONEME OPTIONS, CH SHORTCUTS
#     (now its own section, previously interleaved into SPECIFIC
#     PHONEME OPTIONS), GLIDE OPTIONS, STRESS OPTIONS, SYLLABIFICATION
#     OPTIONS, PAUSE OPTIONS, NUMBER PROCESSING OPTIONS, SUBSTITUTION
#     OPTIONS, PRESENTATION OPTIONS.
# - While reorganizing, found (via an automated completeness check
#     comparing every CLI flag against the new help text) two more
#     flags that had never been documented at all, even before this
#     reorganization: -manner/-man and -place/-pl. Added them, alongside
#     the -phon/-voicing/-phonword entries added in v1.9.111, since
#     they're the same category (direct-switch equivalents of
#     --format=MANNER and --format=PLACE).
# - Verified: all 77 CLI flags are now documented in -h (zero gaps),
#     confirmed via the same automated check.
#
# v1.9.111
# - Added -h help-text documentation (both English and Spanish) for every
#     flag identified as undocumented in the previous audit: the whole CH
#     SHORTCUTS family (-chsc, -chrl, -chrnl, -chnrl, -chnrnl), -phon/-fon/
#     -ph (direct switch, equivalent to --format=PH), -voicing/-voice/-v
#     (direct switch, equivalent to --format=V), -phonword/-pw, -times/
#     -times-colon/-twc, and -split-syls/-div-sil/-sas/-des. No functional
#     changes -- these flags already worked, they just weren't documented.
#
# v1.9.110
# - Documentation audit: checked every CLI flag against the -h help text,
#     and every perkins.ini-whitelisted variable against perkins.ini itself.
# - Found and fixed a real gap from the MFA->G2P rename: perkins.ini still
#     had an entry for the old "mfa_format" variable name, which no longer
#     exists -- uncommenting it would have been silently rejected as an
#     unknown variable. Renamed to "g2p_format" and corrected the
#     description (it used to say tildes are stripped from the
#     orthographic column; that's no longer true since the G2P
#     tilde-preservation fix).
# - Added a help-text entry for -tvd (triple_vowels_to_double), which had
#     never been documented at all, even under its old name (-dvs).
# - Noted, but left as-is (pre-existing, not something this project
#     introduced): -split-syls, the whole CH SHORTCUTS family (-chsc,
#     -chrl, -chrnl, -chnrl, -chnrnl), -phon, -phonword, -times, and
#     -voicing have no dedicated help-text entries either. Flagged for
#     awareness; not fixed here since they predate all our changes.
#
# v1.9.109
# - Corrected where the IPA stress mark attaches in G2P mode: it now
#     skips past any onset consonant(s) and attaches specifically to the
#     VOWEL of the stressed syllable, e.g. "chile" -> "tʃ ˈi l e" (not
#     "ˈtʃ i l e"), "america" -> "a m ˈe ɾ i k a" (not "a ˈm e ɾ i k a"),
#     and "posee" -> "p o s ˈe e" (not "p o ˈs e e"). The v1.9.107 fix
#     had attached the mark to whichever phoneme came right after it
#     (consonant or vowel), which happened to look right for onset-only
#     stressed syllables but was wrong whenever a consonant intervened.
#
# v1.9.108
# - Renamed $double_vowels_to_singles to $triple_vowels_to_double
#     throughout (CLI flag --dvs -> --tvd, function
#     double_vowels_to_single_vowels -> triple_vowels_to_double_vowels,
#     settings-table label, perkins.ini entry, and comments), and changed
#     its behavior: a run of THREE OR MORE identical vowels now collapses
#     to TWO (not one), e.g. "nooo" -> "noo", "siii" -> "sii". A run of
#     exactly two is left untouched -- this fixes real Spanish double
#     vowels (e.g. "leer", "creer") that were previously, incorrectly,
#     being collapsed to a single vowel.
# - Also fixed a latent bug found in the process: the call to this
#     function was never actually gated on the flag at all -- it ran
#     unconditionally regardless of --tvd/--notvd or perkins.ini. Now
#     properly wired up: --notvd genuinely disables the collapsing.
# - Regression suite: added a corpus file and two new cases specifically
#     covering this (default vs --notvd) with both 3+-vowel runs and
#     genuine double vowels in the same input.
#
# v1.9.107
# - In G2P mode, the IPA stress mark (ˈ primary, ˌ secondary) now attaches
#     directly to the phoneme immediately following it, rather than
#     appearing as its own separate space-separated token, e.g.
#     "Chile" -> "ˈtʃ i l e" and "América" -> "a ˈm e ɾ i k a" (previously
#     "ˈ tʃ i l e" / "a ˈ m e ɾ i k a"). Only affects build_g2p_phoneme_
#     string()'s tokenization when stress_using_tildes==0; --st output
#     (tilde-marked vowels) is unchanged.
#
# v1.9.106
# - Renamed the MFA output feature to G2P (Grapheme-to-Phoneme) throughout:
#     the CLI flag (--mfa -> --g2p), the internal variable ($mfa_format ->
#     $g2p_format), the function that builds the phoneme list
#     (build_mfa_phoneme_string -> build_g2p_phoneme_string), the output
#     file extension (.MFA -> .G2P), the settings-table label, help text,
#     and surrounding comments. Explanatory mentions of the actual
#     Montreal Forced Aligner tool (MFA) were kept where they provide
#     genuine context (e.g. "compatible with tools such as the Montreal
#     Forced Aligner (MFA)"), since that's a real external tool's name,
#     not the renamed feature itself. Historical changelog entries were
#     left as written, since they're a record of what the feature was
#     called at the time.
# - Fixed the orthographic (left-hand) column of G2P output to preserve
#     any tildes/accent marks that existed in the original word (e.g.
#     "área", "está", "compañía") instead of stripping them. Previously
#     they were removed along with everything else non-alphabetic;
#     now only the accent-stripping step was removed, so real letters
#     (including ñ and accented vowels) survive, while punctuation and
#     other non-letter characters are still stripped as before.
#
# v1.9.105
# - The uploaded script still had one of the two conflicting MFA-preset
#     assignments to $stress_using_tildes from the previous bug (the
#     "= 0" one -- the "= 1" one had been removed, but not this one),
#     so it was still unconditionally forced to 0 in --mfa mode
#     regardless of --st or perkins.ini. Removed it -- the MFA preset no
#     longer touches stress_using_tildes at all.
# - Separately, build_mfa_phoneme_string()'s character whitelist never
#     allowed the IPA stress marks (ˈ primary, ˌ secondary) through at
#     all, so even with stress_using_tildes correctly at 0, MFA output
#     showed no stress marking whatsoever -- identical to stress being
#     off entirely, which is what made "-mfa" and "-mfa -st" look the
#     same. Added ˈ/ˌ to the whitelist, symmetric with how accented
#     vowels are already preserved for stress_using_tildes==1. Verified
#     "-mfa" (default) now shows ˈ before the onset of the stressed
#     syllable (standard IPA placement, e.g. "estrecha" -> "e s ˈ t ɾ e
#     tʃ a"), and "-mfa -st" is unaffected and still shows the tilde-
#     marked vowel (e.g. "e s t ɾ é tʃ a") -- the two modes now produce
#     genuinely different, and each individually correct, output.
# - Regression suite: re-recorded the default --mfa golden fixtures
#     (they previously had NO stress marking baked in as "correct").
#
# v1.9.104
# - Fixed a second, deeper bug behind the vowel-dropping report: the MFA
#     preset (inside forcibly_set_variables()) contained two conflicting,
#     unconditional assignments to $stress_using_tildes within the SAME
#     block -- first "= 0", then later "= 1" -- so MFA mode always forced
#     tilde-based stress internally regardless of what the user actually
#     set via --st or perkins.ini. Combined with the previous fix (which
#     made build_mfa_phoneme_string() correctly preserve accented vowels
#     when stress_using_tildes==1), this meant EVERY --mfa run showed
#     stressed vowels with tildes, even without --st, and there was no
#     way to get plain, unstressed MFA output at all. Fixed by removing
#     both forced assignments from the MFA preset, so it no longer
#     touches stress_using_tildes -- the setting now works as expected:
#     off by default (plain vowels), on when the user explicitly enables
#     it (stressed vowels keep their tilde in the phoneme column, e.g.
#     "perro" -> "p é r o" instead of "p e r o").
# - Regression suite: re-recorded the --mfa golden fixtures (they had
#     tildes baked in as "default" output, which was itself a symptom of
#     this bug) and confirmed diverse_mfa (no --st) and diverse_mfa_st
#     (--st) now produce genuinely different, correct output.
#
# v1.9.103
# - Fixed a serious bug in build_mfa_phoneme_string(): stressed vowels
#     were silently dropped from --mfa output whenever they reached this
#     function still marked with a written accent (á é í ó ú, ü) instead
#     of an IPA stress apostrophe. This is the internal representation
#     used while stress_using_tildes==1 (--st), and it turns out some
#     stressed vowels reach this function still in that form even by
#     default -- so the bug affected plain --mfa output too, not just
#     --st combined with --mfa, e.g. "importante" -> "i m p o ɾ t n t e"
#     (missing the stressed "a"), "cinco" -> "s n k o" (missing "i").
#     Fixed by normalizing any remaining accented vowel to its plain
#     phoneme form before the character whitelist filter runs. Verified
#     against the full regression corpus: several previously-recorded
#     golden .MFA fixtures had this exact bug baked in as "expected"
#     output and had to be corrected. Added a new case combining --st
#     with --mfa specifically to guard against this regressing.
#
# v1.9.102
# - Added a new CLI option, --config-file/--cfg-file/--configfile/-cf, to
#     specify the configuration file by name instead of the hardcoded
#     "perkins.ini" (e.g. --config-file=corpora.ini or -cf hardwork.cfg).
#     This can only be set on the command line, not from inside a config
#     file, for the obvious chicken-and-egg reason. All user-facing
#     messages that used to hardcode "perkins.ini" (the could-not-open
#     warning, the unknown-variable warning, the successfully-read/
#     successfully-processed confirmations, and the -h help text) now
#     reflect whatever filename was actually used.
#
# v1.9.101
# - Made x the default representation of "CH"
# - Found a serious, wide-reaching pre-existing bug while checking
#     perkins.ini for missing variables: the ENTIRE perkins.ini
#     variable-loading mechanism (the $$var = $value symbolic-reference
#     assignment in the config-file reader) had been silently broken for
#     every single variable since `use strict` was added (v2.0.3) --
#     uncommenting ANY setting in perkins.ini (not just the two below)
#     would crash with "Can't use string ('debug') as a SCALAR ref while
#     'strict refs' in use". This slipped through because no existing
#     test ever exercised a live, actively-set perkins.ini -- only a
#     missing/unopenable one. Fixed by scoping `no strict 'refs'` tightly
#     around just that one assignment; the whitelist check immediately
#     above it already guarantees $var is a known-safe package variable
#     name, which was always the intended safety mechanism per the
#     original comment.
# - Added coscach_mode and word_list to the perkins.ini allowed-variable
#     whitelist (_ALLOWED_CFG_VARS) -- previously only their sibling
#     meta-configs (corpus_running_text, syllable_list, mfa_format,
#     phonword_format, vrt_format) were configurable via perkins.ini.
# - Moved $coscach_mode and $word_list out of the internal-working-state
#     `my (...)` declaration block and into proper `our` package-global
#     declarations (matching every other config-relevant variable) --
#     required for perkins.ini to be able to reach them at all.
# - perkins.ini: added commented-out coscach_mode/word_list entries to
#     the META-CONFIGURATIONS section, matching sibling style.
# - Regression suite: added 3 new golden-master cases that use a real,
#     actively-set perkins.ini (not just a missing/empty one), closing
#     the coverage gap that let the above bug go undetected. Verified
#     these 3 cases immediately fail if the fix is reverted.
#
# v1.9.98
# - Fixed a real, pre-existing crash: --vrt never actually set
#     $output_filename in assign_default_output_filename() -- it only
#     appended ".vrt" to $in_basename. This left $output_filename empty,
#     so open_output_file() silently failed to open a file, and every
#     subsequent print to OUTPUTFILE died with "print() on closed
#     filehandle". Found via a comprehensive command-line stress test.
#     Fix: also assign $output_filename in that branch.
#
# v1.9.94
# - Dropped the ".txt" suffix from syllable-list output filenames:
#     .SYLLIST.txt -> .SYLLIST, matching the no-suffix style already used
#     for .MFA, .PHONWORD, and .COSCACH.
#
# v1.9.93
# - Made the "transcription was saved in..." confirmation message
#     consistent with the greeting fix (v2.0.5): both of its gating
#     checks (the MFA/phonword-specific one and the general one) now use
#     the explicit $explicit_batch_mode/$explicit_silent_mode snapshot
#     instead of the live $batch_mode value. Previously, meta-config
#     presets that force silent/batch mode internally (--syl-list,
#     --rt/--corpus) would show the greeting (after v2.0.5) but still
#     silently suppress this final confirmation line, which was
#     inconsistent. Now both the greeting and the confirmation message
#     are shown/hidden together, based on whether silent/batch mode was
#     set EXPLICITLY (default, perkins.ini, or CLI flag).
#
# v1.9.90
# - Gave coscach_mode (--coscach/--csc) its own output extension, .COSCACH
#     (no further suffix), and corpus_running_text (--rt/--corpus) its own
#     extension, .RUNTXT. Both previously fell through to the generic
#     .PHNM.txt, indistinguishable from a plain default run despite each
#     applying a distinct, fixed transcription style. Placed before the
#     $output_PHON check in assign_default_output_filename(), same as
#     $syllable_list, so they aren't shadowed by output_PHON's default-true
#     value.
#
# v1.9.87
# - Fixed --syl-list/-sl always producing .PHNM.txt output instead of the
#     intended .SYLLIST.txt: the $syllable_list check in
#     assign_default_output_filename() was placed after the $output_PHON
#     check, which defaults to true, so it was unreachable in the common
#     case. Moved it earlier in the elsif chain, alongside the other
#     format-specific branches.
# - Fixed the console greeting/settings banner being silently suppressed
#     by --syl-list (and, by the same underlying mechanism, --word-list,
#     --rt/--corpus, and other meta-config presets): those presets force
#     $silent_mode = 1 internally, which in turn forces $batch_mode = 1,
#     and the greeting was gated on the live $batch_mode value. Now the
#     greeting is gated on a snapshot of $silent_mode/$batch_mode taken
#     right after defaults/perkins.ini/CLI flags are applied but BEFORE
#     any meta-config preset runs -- so the greeting only stays hidden
#     when silent/batch mode was set EXPLICITLY (by default, perkins.ini,
#     or a CLI flag), not as a preset's internal side effect.
#
# v1.9.83
# - Fixed a flood of "Use of uninitialized value" warnings (rindex,
#     pattern match, substitution, join) coming from stress_accent_routine
#     on real-world input. This sub does the same kind of legitimate
#     rindex()/@character boundary-position work as perform_syllabification
#     (already covered in v2.0.3) -- it was simply missed in that pass.
#     Added the same scoped `no warnings 'uninitialized'` treatment.
#     No logic changed; purely noise suppression for intentional,
#     pre-existing undef-as-"not found" behavior.
#
# v1.9.81
# - Added `use strict; use warnings;` to the whole script.
# - Fixed the 3 resulting "Bareword ... not allowed" compile errors by
#     quoting three string-literal defaults (pésos, eslách, becórta) that
#     were previously relying on Perl's legacy bareword-autoquoting.
# - Fixed several genuine latent bugs that `use warnings` surfaced:
#     - $eng, $esp, $word_list, $coscach_mode, $output_format, and 9
#       pause-tracking flags (sentence_break, comma_pause, etc.) were used
#       in numeric/string comparisons before ever being assigned. Gave
#       each an explicit default matching its always-intended value
#       (0 for booleans, "" for output_format). No behavioral change --
#       except that debug output (-d) now shows "0" instead of a blank
#       for these variables before they're set, which is more correct.
#     - $temp_num, used inside `eval q{...}` blocks in
#       convert_numerals_to_words() for 1-3 digit numeral conversion, was
#       NEVER declared with my/our anywhere. Under non-strict Perl this
#       silently auto-vivified as a global and worked by accident; under
#       `use strict` the eval string failed to compile, which eval
#       swallows silently -- causing s///ge to replace the matched
#       numeral with an empty string instead of its word form. This was
#       a REAL bug (silently dropped numerals under certain conditions,
#       e.g. "43 BC" support), now fixed by declaring `my $temp_num;` in
#       that subroutine. Verified via golden-master regression testing
#       that this fix restores byte-identical output to pre-strict
#       behavior.
#     - Added `no warnings 'uninitialized'` (scoped to single subs) in
#       convert_numerals_to_words, final_touches_broad_transcr, and
#       perform_syllabification, which all intentionally probe array
#       positions or an optional leading capture group that may
#       legitimately be undef/out-of-range as part of correct,
#       pre-existing boundary-detection logic -- not bugs, just noisy
#       under warnings.
# - Verified with the golden-master regression suite: after this fix,
#     output is unchanged from v2.0.2 for every case, EXCEPT the -d/--debug
#     info dump, which now prints "0" instead of a blank for a couple of
#     previously-uninitialized variables -- an intentional, expected
#     improvement, not a regression.
#
# v1.9.98
# - Fixed the "will not stay shared" closure bug in print_console_greeting's
#     nested row_str() sub: it now takes $div and $LC as explicit parameters
#     (like row3()/row2() already did) instead of closing over the outer
#     lexicals. Harmless today (print_console_greeting only runs once per
#     invocation) but was a landmine for any future refactor that called
#     it more than once. Verified with the golden-master regression suite:
#     no observable output change across all 13 test cases.
#
# v1.9.80
# - Removed dead $semi_narrow reference in the debug CLI info dump. The
#     variable was never declared or assigned anywhere in the program (a
#     leftover from a renamed/removed option); printing it always yielded
#     an uninitialized value.
# - Removed assign_lang_str and its call site. The sub was an empty no-op
#     stub, never implemented and not wired to any localization mechanism.
#     All bilingual (en/es) output is handled inline elsewhere, so no
#     functionality is lost.
#
# v1.9.75
# - Changed input file encoding from ISO-8859-1 to UTF-8.
# - Eliminated dependency on abandoned and unnecessary File::Unicode::Slurp.
# - Fixed config file (perkins.ini) processing, It not allows you to set every
#   possible variable in the program.
# - Created a perkins.ini skeleton file with all variables listed but commented
#     out so users can take advantage of it far more easily.
# - Modified pause processing so "!" and "?" are handled the same way as "." is
#     (as a long pause, assuming user wants to retain pauses).
# - Fixed collision of the "-sl" flag, which was assigned to both slash processing
#     and single line processing. The long forms of these options always worked
#     properly.
# - Fixed edge case of handling of negative sign at left edge of numbers. It was
#     interpreted correctly EXCEPT when followed by a comma and space (e.g. in
#     "Allá, -30 grados es común" the "-" was dropped due to incorrect processing
#     of pauses earlier in the script.
# - Fixed handling of dollar/peso sign at beginning of positive and negative numbers.
#     previously it was left in place (e.g. "pesos mil doscientos"). Now, it is moved
#     to the end of the amount (e.g. "mil doscientos pesos"), which is how it's said.
# - Suppressed program banner except when help (-h) and usage (-u) are called.
# - Added routine to show all ACTIVE variables and their values whenever the program is run.
# - Fixed incorrect handling of manner of articulation: vowels were formerly converted
#     into dashes; now they are properly displayed as "V".
# - Added functionality (triggered by -mfa) to produce MFA-compatible output for segment-
#     level transcriptions. This consists of one item per line output made up of the
#     orthographic word (in lower case and with tildes removed), a tab, and the phonemic
#     transcription of the word with a space between each phoneme (with no stress marking,
#     no syllable division, and using the same phoneme inventory as the target MFA acoustic
#     model).
# - Added functionality (triggered by -phonword or -pw) that produces a different output,
#     for word-level transcription, geared toward Praat textgrids and other types of
#     linguistic work. This function produces on each line the orthographic word with
#     capitalization and tildes maintained, a tab, and a phonemic word transcription that
#     maintains syllable divisions and stress marking. This is geared toward Praat word-level
#     segments. Note that this produces more accurate phonemic representations than -MFA,
#     which adheres to the MFA usage (doubtlessly motivated by ASCII compatibility) of using
#     /t/ for /t̪/, /d/ for /d̪/, /tʃ/ for /t͡ʃ/ (or the single character variant).
# - Fixed an inconsistency when processing "ió" to maintain hiatus (e.g. "crió" /kɾi.ˈo/,
#     "guión" /gi.ˈon/) when appropriate, but to produce a diphthong when relevant (e.g.
#     situación /si.t̪wa.ˈsjon/).
# - Fixed issues when processing various allomorphs of -uir verbs.
# - Fixed a series of additional diphthong processing edge-case bugs.
# - Improved processing of numbers.
# - Greatly improved handling of Roman numerals. They MUST be capitalized to be recognized
#     as such. Otherwise, they're processed as lexical items if appropriate ("vi" from "ver") or
#     not processed at all.
# - Added handling of times (e.g. 1:30, 21:28).
# - Added full support for mathematical operations.
# - Fixed heretofore basic handling of negative numbers.
# - Added support for multiple currencies.
# - Made the currency unit represented by "$" configurable ($moneda = ...). Must be given as plural.
# - Normalized decimal processing in large numbers.
# - Greatly improved handling of currencies.
# - Improved handling of odd punctuation (e.g. "..", ",,")
# - Fixed handling of dates so they're never misinterpreted as mathematical expressions.
# - Specifically excluded US-style dates (e.g. 12/31/2000). They just cause problems are are very
#   unlikely to be found in the wild.
# - Added additional date formats (e.g. ISO-8601).
# - Added support for temperatures.
#   · All positive and negative numbers followed by °C, °F, ºC, ºF, oC, oF are treated as temperatures.
# - Added support for ordinal numbers.
#   · Numerals from 1-10 are processed according to their idiosyncrasies.
#   · Positive numerals up to 100 that have º, ª, ° attached are converted to ordinals (e.g. 4º = cuarto).
#   · All positive numerals followed by o, a, ero, era, eros, eras, vo, va, etc. are converted to ordinals.
# - Other minor bug fixes.
# - Drastically cleaned up code base.

# TODO
# - Document -phonword in help and elsewhere.
# - Document -mfa option in help and elsewhere.
# - Add missing flags (from here) to config file.
# - Reorganize configuration definitions here and then in the help file to be more logical.
# - Add options to set specific "ch" conversions (direct to single char, multichar with
#   retraction and tiebar, multicar with just tiebar, multichar with just retraction, and
#   multichar with nothing. "ch" as single char seems to already exist.
# - Advise user that on linux and mac, chmod +x perkins.bin is likely necessary,
#   depending on how I package it.


#################################################################################
#                 SET DEFAULT VALUES FOR PROGRAM OPTIONS AND VARIABLES          #
#################################################################################

###################################################
#         OPTIONS THAT USERS MAY CONFIGURE        #
# These options can be edited here, or overridden #
#   in perkins.ini without changing the script    #
###################################################

# USER OPTIONS - PROGRAM BEHAVIOR
our $debug               = 0;
our $debug_syllab_sub    = 0;
our $debug_to_logfile    = 0;
our $silent_mode         = 0;
our $batch_mode          = 0;    # Set to 1 for batch mode. This suppresses output to stdout, etc.
my $use_config_file      = 0;    # Read perkins.ini if present.
my $config_filename      = "perkins.ini"; # Name of the configuration file to read.
                                          # Change with --config-file/-cf on the command line.
my $lang                 = "en"; # Default program message language

# META OPTIONS - FOR SPECIFIC COMBINATIONS OF VARIABLES
our $corpus_running_text = 0;    # Select settings for the Coscach / Codicach: Running text syllabified at sentence
                                 #   level, one character per phoneme, etc. This overrides most other settings, to make
                                 #   sure that the corpus is processed uniformly, so turn it off to specify other options.

our $syllable_list       = 0;    # Select settings for producing output that is easy to process at the syllable level (each
                                 #   syllable is separated from others by a space). This overrides most other settings, to make
                                 #   sure that the corpus is processed uniformly, so turn it off to specify other options.

our $g2p_format          = 0;    # Produces a word-level Grapheme-to-Phoneme (G2P) dictionary: one entry per
                                 #   line, where EACH LINE CONSISTS OF THE ORTHOGRAPHIC WORD
                                 #   STRIPPED OF ACCENT MARKS, A TAB SYMBOL, AND THEN THE INDIVIDUAL PHONEMES OF THE WORD IN IPA,
                                 #   WITH A SPACE BETWEEN EACH. For maximum compatibility with tools such as the
                                 #   Montreal Forced Aligner (MFA), the following symbols must
                                 #   be used instead of alternatives:
                                 #   a  e  i  o  u    p  t  k  b  d  g    f  s  x    m  n  ɲ    l    ɾ  r    j  w    ʝ    tʃ
                                 #   The purpose of this is to populate a segment-level tier with phonemes.

our $phonword_format     = 0;    # Takes the orthographic word-level transcriptions produced by MFA and converts
                                 #   them into a one-item-per line format, where EACH LINE CONSISTS OF THE ORTHOGRAPHIC WORD
                                 #   WITH TILDES, A TAB, AND THE PHONEMIC TRANSCRIPTION AS A SINGLE WORD, WITH STRESS AND SYLLABLE
                                 #   BOUNDARY MARKERS. The purpose of this is to populate the WORD-LEVEL orthographic and phonemic
                                 #   transcription tiers in Praat.

our $vrt_format          = 0;    # Select settings for generating one-word-per-line IMS Corpus Workbench-compatible
                                 # vertical text (.vrt). Currently (381), Perkins only produces the transcribed form.
                                 # This overrides most other settings to make sure that the corpus is processed
                                 #uniformly, so turn it off to specify other options.

our $word_list           = 0;    # Select settings for producing output that is easy to process at the word level
                                 #   (syllables separated by a dot, words separated by a space). Doesn't itself
                                 #   specify a transcription output format, so it can be combined with one of the
                                 #   output_* variables above.

our $coscach_mode        = 0;    # Select settings tuned for the COSCACH/CODICACH corpus transcription conventions
                                 #   (retracted affricates without ligatures, dental diacritics, tilde-marked stress,
                                 #   syllable dots as spaces). This overrides most other settings, so turn it off to
                                 #   specify other options.

# USER OPTIONS - FORMAT OF TRANSCRIPTION OUTPUT
our $output_PHON   = 1;          # Set output to phonemic transcription
our $output_CV     = 0;          # Instead of outputting phonemes, output C (consonant) or V (vowel)
our $output_CVG    = 0;          # Instead of outputting phonemes, output C (consonant), V (vowel) or G (glide)
our $output_CVNLRG = 0;          # Instead of outputting phonemes, output Vowel, Nasal, Liquid, Rhotic,
                                 #    Glide or (misc) Consonant
our $output_manner = 0;          # Instead of outputting phonemes, output the manner of articulation
our $output_place  = 0;          # Instead of outputting phonemes, output the mode of articulation
our $voicing       = 0;          # Instead of outputting phonemes, output the voicing


# USER OPTIONS - FOR SPECIFIC PHONEMES
our $multichars              = 1;    # Represent certain phonemes using more than one character (e.g. use "t̠͡ʃ" instead of "ʧ")

# TR
our $tr_is_group             = 0;    # Treat /tr/ as a group. If on, it produces "t͡ɾ" with multichars and "ʂ" without it.

# CH + DZH
our $ch_dzh_retracted        = 0;    # Add retracted diacritic to the /t̠͡ʃ/ and /d̠͡ʒ/ digraphs (if /ʝ/ is not used)
our $use_ligatures           = 1;    # Use ligatures in /ʧ/ and /ʤ/ (/ʝ/)

#CH
our $use_one_char_ch_symbol  = 0;    # Use the /ʧ/ symbol to represent /ʧ/ regardless of the multichar setting. It was a dumb decision to link the two!

# YE
our $ye_phoneme_is_fricative = 1;    # Represent the phoneme "ye" (<ll> in "ella", <y> in "yo") with the palatal frictive symbol ʝ
                                     #    instead of the affricate ʤ
our $ye_phoneme_is_affricate = 0;    # MUST have opposite value of $ye_phoneme_is_fricative !
our $use_one_char_ye_symbol  = 1;    # Use the /ʝ/ symbol to represent /ʝ/ regardless of the multichar setting. It was a dumb decision to link the two!

# D + T
our $use_dental_diacr        = 1;    # Use dental diacritic with /d/ and /t/

# G
our $add_epenthetic_g        = 0;    # Add an epenthetic [g] before /w/. Traditionally believed ubiquitous in Chilean Spanish, but turns out to be rather uncommon.

# USER OPTIONS - FOR GLIDES
our $glides_with_diacritics = 0;     # 1 HERE is equivalent to turning both the following options on. If set here, that's unnecessary.
our $non_syl_u_with_u_diacr = 0;     # Represent the non-syllabic /u/ as /u̯/, not /w/
our $non_syl_i_with_i_diacr = 0;     # Represent the non-syllabic /i/ as /i̯/, not /j/

# USER OPTIONS - EASY SELECTION OF CH REPRESENTATION
# The standard system requires properly setting three variables to get the representation of "ch" that you want. This makes it much easier.
our $ch_is_single_char                  = 0; # -chsc        ʧ
our $ch_is_retracted_with_ligature      = 0; # -chrl        t̠͡ʃ
our $ch_is_retracted_no_ligature        = 0; # -chrnl       t̠ʃ
our $ch_not_retracted_with_ligature     = 0; # -chnrl       t͡ʃ
our $ch_not_retracted_no_ligature       = 0; # -chnrnl      tʃ

# USER OPTIONS - FOR PHONEMES IN GENERAL
our $space_between_phonemes   = 0;    # Separates phonemes with a space
our $word_plus_phonemes       = 0;    # Make the output format be the input word followed by a tab and the list of phonemes
                                      #   (implies space_between_phonemes is 1)

# USER OPTIONS - STRESS
our $stress_using_tildes = 0;        # If 1, mark stress with tildes on stressed vowels; if 0, mark it with IPA apostrophes.
our $no_stress_marks     = 0;        # Don't include any type of stress indication. Only for special cases.
our $non_ipa_apostrophes = 0;        # Use the standard orthographic apostrophe (') instead of the IPA one (which doesn't show in Praat)
                                     # Added in 376

# USER OPTIONS - SYLLABLES
our $insert_syllable_dots     = 1;    # Insert dots between each syllable
our $split_at_syllables       = 0;    # Put a newline at each syllable break
our $syllabify_by_sentence    = 1;    # Syllabifies by sentence instead of by word. This means "los hombres" > /lo.som.bres/
                                      # instead of /los om.bres/
our $syllable_dots_are_spaces = 0;    # Separate syllables with spaces instead of dots. Designed to facilitate processing
                                      #   and research on syllables in the Codicach, etc. (lets syllables be treated as words)
# USER OPTIONS - PAUSES
our $ipa_pause_symbols    = 1;        # Use the IPA's | and ‖ method of representing pauses
our $ipa_long_pause_two_singles = 0;  # For long IPA pauses, use two single pause bars (||) instead of one#
                                      # double bar (‖).
our $add_comma_pauses     = 1;        # Commas are converted into short pauses:                 (.)
our $add_colon_pauses     = 1;        # Colons are converted into medium pauses:                (..)
our $add_semicolon_pauses = 1;        # Semicolons are converted into long pauses:              (...)
our $add_sentence_breaks  = 1;        # Periods are converted into absolute breaks:             ##
our $add_paragraph_breaks = 1;        # Paragraph breaks are converted into longest pause:      ###
                                      #    This can be in addition to newlines.
our $add_ellipsis_pauses  = 1;        # Ellipses are converted into a long pause:               (...)
our $add_bracket_pauses   = 1;        # Convert [ and ] into short pauses                       (.)
our $add_paren_pauses     = 1;        # Convert ( and ) into short pauses                       (.)

# USER OPTIONS - NUMBERS
our $numerals_to_words    = 1;        # Convert numerals (123) to words (uno dos tres)
our $num_symbol           = "·";      # Symbol used to replace numerals if that option is chosen
our $decimal_symbol       = ",";      # Which symbol separates whole numbers from fractions (the
                                      #  decimal point): "," or ".". The OTHER symbol is treated as
                                      #  the thousands (grouping) separator throughout number
                                      #  processing (currency, percentages, units, plain decimals).
                                      #  Default "," matches the existing Spanish/Chilean convention
                                      #  (e.g. "12,35" is twelve point thirty-five; "1.500" is one
                                      #  thousand five hundred). Set via --decimal-period/-dp or
                                      #  --decimal-comma/-dc.
our $narrow_year_ranges   = 1;        # Treat two consecutive 4-digit numbers separated by "-" as a range of years
                                      #  (e.g. "1900-1950" > "1900 a 1950") instead of an equation (1900 minus 1950)
                                      #  IF AND ONLY IF the second number is larger than the first.
our $broad_year_ranges    = 1;        # Treat two consecutive 1-4 digit numbers separated by "-" as a range of years
                                      #  (e.g. "211-300" > "211 a 300") instead of an equation (211 minus 300)
                                      #  IF AND ONLY IF the second number is larger than the first.
our $bc_dates_included    = 1;        # Remove restriction on second number having to be larger than the first from
                                      #  $narrow_year_ranges and $broad_year_ranges.
our $all_year_ranges      = 1;        # Process all date ranges (1-3 digit, 4 digit, BC dates).
our $times_with_colon     = 1;        # Process times - 1-2 numbers, a colon and two numbers.

# USER OPTIONS - ODD CHARACTERS       #
our $fix_umlauts          = 1;        # Change vowels with umlauts to bare vowels (except ü for diéresis)
our $fix_grave_accents    = 1;        # Change vowels with umlauts to bare vowels (tù, està > tú, está)
our $fix_circumflexes     = 1;        # Change vowels with circumflexes to bare vowels (hôstel > hostel)
our $fix_nasal_tildes     = 1;        # Change vowels with nasal tildes to bare vowels (S~ao > Sao)

# USER OPTIONS - SUBSTITUTIONS       #
our $moneda        = "pesos";        # String used to replace "$" character
our $rut_k_mode    = "letter";       # How to read RUT check digit K: "letter" (literal k) or "ka" (spoken)
our $slash         = "eslách";       # String used to replace "/" character
our $process_urls  = 1;              # Process URLs, treating them as linguistic strings.
our $process_email = 1;              # Process e-mail addresses, treating them as linguistic strings.

# NEW IN 0406 ->
our $name_for_v    = "becórta";      # Name for letter "v" to be used when spelling individual letters out.
                                     # Suggested values: "becórta", "bechíca", "úve" (with tilde on
                                     # accented syl. and written as one word).

# USER OPTIONS - MISC                #
our $keep_paragraphs          = 1;   # Keep paragraphs in the source text. Otherwise, output will be one big block.
our $vertical_output          = 0;   # Output contains one word per line.
our $triple_vowels_to_double = 1;   # Reduce three or more identical vowels ("aaa", "eee", "iii", etc.) to two.


###################################################
#                 ADVANCED OPTIONS                #
# You can change the values of these variables if #
# you know what you're doing, but be sure you do  #
# first!                                          #
###################################################
my $upper_case = 0;                   # Sets whether output is uppercased (CV, CVG, etc.) This is disastrous for phonemic
                                      # transcription, the most common use case, and thus is kept disabled. It does work properly
                                      # with all other types of transcription, though, so you can use it if you want.)
our $kill_common_words = 0;           # FOR TESTING PURPOSES. Eliminate common words to make it easier to find novel mistakes.


###################################################
#      OPTIONS THAT USERS MAY *NOT* CONFIGURE     #
#    DANGER DANGER DANGER DANGER DANGER DANGER    #
#    Do NOT modify ANY of the following values!   #
###################################################

our $cleanup_output_file = 1;         # WARNING Must ALWAYS be 1! After processing the input file and producing the output
                                      #         file, this option makes Perkins open the output file again to perform cleanup.

our $no_separate_cleanup_file = 1;    # WARNING Must ALWAYS be 1! Determines if the cleaned up version goes to a separate text#
                                      #         file or replaces the output file ONLY IN BETA MODE.

# OUTPUT: CLEANUP OPTIONS             #
our $preclean_orthographic = 1;       # WARNING Must ALWAYS be 1! Apply the clean-up routine for words in their orthographic form.
our $preclean_semiphonemic = 1;       # WARNING Must ALWAYS be 1! Apply the clean-up routine for words in their semi-phonemic form.
our $preclean_phonemic     = 0;       # WARNING NOT IMPLEMENTED YET! # Apply the clean-up routine for words in their phonemic form.

# INTERNAL VARIABLES THAT NEED INITIALIZED - DO NOT TOUCH THEM!
my $helpme              = 0;          # Should help message be printed to terminal?
my $useme               = 0;          # Should usage message be printed to terminal?
my $config_file_present = 0;
my $output_filename     = "";
my $progname            = "";

# Set program name according to OS. Assumes Windows users will use the .exe version of Perkins.
if ( $^O eq "MSWin32" ) {
    $progname = "perkins.exe";
}
elsif ( $^O eq "linux" ) {
    $progname = "perkins.pl";
}
elsif ( $^O eq "darwin" ) {
    $progname = "perkins.pl";
}
else {
    die "Unsupported operating system: $^O\n";
}

############################################################################
#            DECLARE VARIABLES THAT REQUIRE A (DEFAULT) VALUE              #
#  DON'T CHANGE ANYTHING HERE UNLESS YOU ARE REWRITING THE ENTIRE PROGRAM  #
############################################################################
my $accent                    = "";
my $acute_consonants          = "(b|ʧ|d|ʤ|ʝ|f|g|j|k|l|m|ɲ|p|ɾ|r|ʂ|t|w|x|y|z)";
my $acute_consonants_except_j = "(b|ʧ|d|ʤ|ʝ|f|g|k|l|m|ɲ|p|ɾ|r|ʂ|t|w|x|y|z)";
my $all_consonants            = "(b|ʧ|d|ʤ|ʝ|f|g|j|k|l|m|ɲ|n|p|ɾ|r|s|ʂ|t|w|x|y|z)";
my $all_consonants_but_glides = "(b|ʧ|d|ʤ|ʝ|f|g|k|l|m|ɲ|n|p|ɾ|r|s|ʂ|t|x|y|z)";
my $non_liquids               = "(b|ʧ|d|ʤ|ʝ|f|g|j|k|m|ɲ|n|p|s|ʂ|t|w|x|y|z)";
my $orthog_epenth_e_cons      = "(b|c|d|f|g|j|k|l|m|n|p|q|r|s|t|v|x|y|z)";
my $stressed_vowels           = "(á|é|í|ó|ú)";
my $unstressed_vowels         = "(a|e|i|o|u)";
my $all_vowels                = "(a|e|i|o|u|á|é|í|ó|ú)";
my $strong_vowels             = "(a|e|o)";
my $weak_vowels               = "(i|u)";

# For sentence-level syllabification
my $all_consonants_diacr = "(b|ʧ|t͡ʃ|t̠͡ʃ|d|d̪|ʤ|d͡ʒ|d̠͡ʒ|ʝ|f|g|j|k|l|m|ɲ|n|p|ɾ|r|s|ʂ|t|t̪|tɾ|t̪ɾ|t͡ɾ|t̪͡ɾ|w|x|y|z)";
my $all_vowels_diacr     = "(a|e|i|o|u|á|é|í|ó|ú|i̯|u̯)";

# The following are for the routine that replaces phonemes with C, V, N, L, G, etc.
my $misc_cons     = "(b|ʧ|d|ʤ|ʝ|f|g|k|p|s|ʂ|t|x)";
my $nasals        = "(m|n|ɲ)";
my $liquids       = "(l)";
my $glides        = "(j|w)";
my $rhotics       = "(r|ɾ)";
my $vowels_glides = "(a|e|i|o|u|á|é|í|ó|ú|j|w)";

# The /tr/ group representation. "ʂ" is used to represent the phonemic status of "tr" in Chilean Spanish with a single symbol.
my $tr = "(ʂ)";

# The following are for the routine that replaces phonemes with MANNERS of articulation
my $plosives = "(b|d|g|k|p|t)";

# $nasals is declared above.
my $trills      = "(r)";
my $taps        = "(ɾ)";
my $fricatives  = "(f|s|x|ʝ)";
my $laterals    = "(l)";
my $affricates  = "(ʧ|ʤ)";
my $aproximants = "(j|w)";

# The following are for the routine that replaces phonemes with PLACES of articulation
my $bilabials     = "(b|m|p)";
my $labiodentals  = "(f)";
my $dentals       = "(d|t)";
my $alveolars     = "(n|ɾ|r|s|l)";
my $postalveolars = "(ʧ|ʤ)";
my $palatals      = "(j|ʝ|ɲ)";
my $velars        = "(k|g|x)";
my $labiovelars   = "(w)";

# The following are for the routine that replaces phonemes with VOICING
my $voiced   = "(a|e|i|o|u|á|é|í|ó|ú|b|d|g|ʤ|ʝ|j|l|m|ɲ|n|ɾ|r|w)";
my $unvoiced = "(ʧ|f|k|p|s|ʂ|t|x)";

############################################################################
#                       DECLARE EMPTY LOCAL VARIABLES                      #
############################################################################
my (
     @character,             $character,           $input_line,          $last_vowel_pos,      @rev_vowels,            $sec_last_vowel_pos,
     @syllable,              $curr_syl,            $vowel_count,         $vowels,              $word,                  @word_array,
     $i,                     $sentence_break,      $comma_pause,         $semicolon_pause,     $colon_pause,           $ellipsis_pause,
     $initial_bracket_pause, $final_bracket_pause, $initial_paren_pause, $final_paren_pause,   $syl_count,             $prev_input_line,
     @whole_file_array,      $whole_file,          $input_filename,      $in_basename,         $beta_file,             @utterance_array,
     $utterance,             $item,                @uniquearray,         $next_char_syl_break, $sr_input_line,         @sr_array,
     $output_format,         $cfg_file_was_read,   $print_usage,         $current_numeral,     $numeral_converter_obj,
     $temp,                  $debug_message,       $user_message,        $eng,                 $esp,                   $long_pause_symbol
);

# Explicit defaults for the booleans/strings above that are tested (==, eq,
# lc, etc.) before any command-line option or config-file value is read.
# Without these, "use warnings" fires "Use of uninitialized value" on every
# run. Values match the behavior the code already relied on when undef
# (false/empty), so this changes no observable output -- it only silences
# the warning by making the always-intended default explicit.
$eng           = 0;
$esp           = 0;
$word_list     = 0;
$coscach_mode  = 0;
$output_format = "";
$sentence_break        = 0;
$comma_pause            = 0;
$semicolon_pause        = 0;
$colon_pause            = 0;
$ellipsis_pause         = 0;
$initial_bracket_pause  = 0;
$final_bracket_pause    = 0;
$initial_paren_pause    = 0;
$final_paren_pause      = 0;

#################################################################################
#                       READ AND CLEAN UP COMMAND-LINE OPTIONS                  #
#################################################################################

############################################################################
# Die with a red error message if an unknown option is given, rather than  #
# silently falling back to defaults.                                       #
############################################################################
local $SIG{__WARN__} = sub {
     my $msg = shift;
     if ( $msg =~ /^Unknown option/ ) {
          $msg =~ s/\s*\n*$//;    # strip trailing newline
          print STDERR safe_colored( "\nERROR: $msg\n", "bold red" );
          print STDERR safe_colored( "Run with -h for help.\n\n", "bold red" );
          exit 1;
     }
     warn $msg;    # pass through any other warnings unchanged
};

# Snapshot of all configurable variables at their DEFAULT values.
# Used at display time to determine which values came from which source.
our %_VAL_DEFAULT = ("debug" => $debug, "debug_syllab_sub" => $debug_syllab_sub, "debug_to_logfile" => $debug_to_logfile, "silent_mode" => $silent_mode, "batch_mode" => $batch_mode, "corpus_running_text" => $corpus_running_text, "syllable_list" => $syllable_list, "g2p_format" => $g2p_format, "phonword_format" => $phonword_format, "vrt_format" => $vrt_format, "output_PHON" => $output_PHON, "output_CV" => $output_CV, "output_CVG" => $output_CVG, "output_CVNLRG" => $output_CVNLRG, "output_manner" => $output_manner, "output_place" => $output_place, "voicing" => $voicing, "multichars" => $multichars, "tr_is_group" => $tr_is_group, "ch_dzh_retracted" => $ch_dzh_retracted, "use_ligatures" => $use_ligatures, "use_one_char_ch_symbol" => $use_one_char_ch_symbol, "ye_phoneme_is_fricative" => $ye_phoneme_is_fricative, "ye_phoneme_is_affricate" => $ye_phoneme_is_affricate, "use_one_char_ye_symbol" => $use_one_char_ye_symbol, "use_dental_diacr" => $use_dental_diacr, "add_epenthetic_g" => $add_epenthetic_g, "glides_with_diacritics" => $glides_with_diacritics, "non_syl_u_with_u_diacr" => $non_syl_u_with_u_diacr, "non_syl_i_with_i_diacr" => $non_syl_i_with_i_diacr, "ch_is_single_char" => $ch_is_single_char, "ch_is_retracted_with_ligature" => $ch_is_retracted_with_ligature, "ch_is_retracted_no_ligature" => $ch_is_retracted_no_ligature, "ch_not_retracted_with_ligature" => $ch_not_retracted_with_ligature, "ch_not_retracted_no_ligature" => $ch_not_retracted_no_ligature, "space_between_phonemes" => $space_between_phonemes, "word_plus_phonemes" => $word_plus_phonemes, "stress_using_tildes" => $stress_using_tildes, "no_stress_marks" => $no_stress_marks, "non_ipa_apostrophes" => $non_ipa_apostrophes, "insert_syllable_dots" => $insert_syllable_dots, "split_at_syllables" => $split_at_syllables, "syllabify_by_sentence" => $syllabify_by_sentence, "syllable_dots_are_spaces" => $syllable_dots_are_spaces, "ipa_pause_symbols" => $ipa_pause_symbols, "ipa_long_pause_two_singles" => $ipa_long_pause_two_singles, "add_comma_pauses" => $add_comma_pauses, "add_colon_pauses" => $add_colon_pauses, "add_semicolon_pauses" => $add_semicolon_pauses, "add_sentence_breaks" => $add_sentence_breaks, "add_paragraph_breaks" => $add_paragraph_breaks, "add_ellipsis_pauses" => $add_ellipsis_pauses, "add_bracket_pauses" => $add_bracket_pauses, "add_paren_pauses" => $add_paren_pauses, "numerals_to_words" => $numerals_to_words, "num_symbol" => $num_symbol, "decimal_symbol" => $decimal_symbol, "narrow_year_ranges" => $narrow_year_ranges, "broad_year_ranges" => $broad_year_ranges, "bc_dates_included" => $bc_dates_included, "all_year_ranges" => $all_year_ranges, "times_with_colon" => $times_with_colon, "fix_umlauts" => $fix_umlauts, "fix_grave_accents" => $fix_grave_accents, "fix_circumflexes" => $fix_circumflexes, "fix_nasal_tildes" => $fix_nasal_tildes, "moneda" => $moneda, "rut_k_mode" => $rut_k_mode, "slash" => $slash, "process_urls" => $process_urls, "process_email" => $process_email, "name_for_v" => $name_for_v, "keep_paragraphs" => $keep_paragraphs, "vertical_output" => $vertical_output, "triple_vowels_to_double" => $triple_vowels_to_double, "kill_common_words" => $kill_common_words, "upper_case" => $upper_case);

GetOptions(
     "afr|ar!"                                => \$ch_dzh_retracted,           # Affricates retracted
     "batch|bm|b|quiet|callate!"              => \$batch_mode,                 # Run Perkins in batch mode: no STDOUT output
     "debug-log|log|dl!"                      => \$debug_to_logfile,           # Write debug info to log file
     "debug|d!"                               => \$debug,                      # Debug Perkins.
     "en"                                     => \$eng,                        # English language program messages selected (alt method)
     "es"                                     => \$esp,                        # Spanish language program messages selected (alt method)
     "format|formato|f:s"                     => \$output_format,              # Output format:    --f=manner
     "glides-dia|gd!"                         => \$glides_with_diacritics,     # Represent glides with vowel + non-syllabic diacritic
     "help|ayuda|h|?"                         => \$helpme,                     # See if user needs help
     "input|i=s"                              => \$input_filename,
     "config-file|cfg-file|configfile|cf=s"   => \$config_filename,       # Name/path of the config file to read (default: perkins.ini)
     "lang|l=s"                               => \$lang,                       # Language for program messages
     "lo:s"                                   => \$slash,                      # Replace / symbol with this text
     "lp2|pl2!"                               => \$ipa_long_pause_two_singles,  # Represent IPA long pause as 2 single pause bars
     "mon|cur:s"                              => \$moneda,                     # Replace $ symbol with this text
     "mp|kp!"                                 => \$keep_paragraphs,            # Keep paragraph breaks. Otherwise, output is a wall of transcription
     "multi|mc|ms!"                           => \$multichars,                 # Represent certain phonemes (e.g. t̠͡ʃ) with multiple IPA symbols
     "n2w|nap!"                               => \$numerals_to_words,          # Convert numerals into words
     "no-stress-marks|nsm|nma!"               => \$no_stress_marks,            # Don't include any type of stress indication
     "output|o=s"                             => \$output_filename,
     "pausas-afi|ipa-pauses|ip|pi!"           => \$ipa_pause_symbols,          # Represent pauses using IPA's | and || symbols
     "pco|cmp!"                               => \$add_comma_pauses,           # Convert commas into pauses
     "pcr|brp!"                               => \$add_bracket_pauses,         # Convert brackets into pauses
     "pdp|clp!"                               => \$add_colon_pauses,           # Convert colons into pauses
     "pel|elp!"                               => \$add_ellipsis_pauses,        # Convert ellipses into pauses
     "por|snp!"                               => \$add_sentence_breaks,        # Add breaks between sentences
     "ppa|ppp!"                               => \$add_paragraph_breaks,       # Add breaks between paragraphs
     "ppc|scp!"                               => \$add_semicolon_pauses,       # Convert semicolons into pauses
     "ppn|pnp!"                               => \$add_paren_pauses,           # Convert parentheses into pauses
     "sn:s"                                   => \$num_symbol,                 # Replace numbers with this symbol
     "split-syls|div-sil|sas|des!"            => \$split_at_syllables,         # Put each syllable on a line of its own
     "st|at!"                                 => \$stress_using_tildes,        # Represent stress with tilde, NOT IPA primary stress symbol
     "trg|tg!"                                => \$tr_is_group,                # Treat TR as a group of two phonemes
     "upl|owl|saw|dep|vo|split-words!"        => \$vertical_output,            # Output has one word per line
     "usage|use|uso|u"                        => \$useme,                      # Print usage information to terminal
     "wvd|wv!"                                => \$non_syl_u_with_u_diacr,     # Represent wau with a u + non-syllabic diacritic
     "yea|ya!"                                => \$ye_phoneme_is_affricate,    # Render YE as affricate
     "yef|yf!"                                => \$ye_phoneme_is_fricative,    # Render YE as fricative
     "yvd|yv!"                                => \$non_syl_i_with_i_diacr,     # Represent yod with an i + non-syllabic diacritic

     # PERMIT OUTPUT FORMAT OPTIONS TO BE CHOSEN AS SIMPLE SWITCHES:
     "aeg|age!"                                    => \$add_epenthetic_g,
     "ayr|tra"                                     => \$all_year_ranges,
     "bcy|aac!"                                    => \$bc_dates_included,
     "byr|raa!"                                    => \$broad_year_ranges,
     "cfg|ini!"                                    => \$use_config_file,
     "chsc"                                        => \$ch_is_single_char,              # "ch" is the single IPA character:       ʧ
     "chrl"                                        => \$ch_is_retracted_with_ligature,  # "ch" is retracted and has ligature:     t̠͡ʃ
     "chrnl"                                       => \$ch_is_retracted_no_ligature,    # "ch" is retracted and with no ligature: t̠ʃ
     "chnrl"                                       => \$ch_not_retracted_with_ligature, # "ch" not retracted and has ligature:    t͡ʃ
     "chnrnl"                                      => \$ch_not_retracted_no_ligature,   # "ch" not retracted and has no ligature: tʃ
     "coscach|csc"                                 => \$coscach_mode,
     "cv!"                                         => \$output_CV,
     "cvg!"                                        => \$output_CVG,
     "cvn|cvnlrg!"                                 => \$output_CVNLRG,
     "dent|dd!"                                    => \$use_dental_diacr,
     "decimal-period|dp"                           => sub { $decimal_symbol = "."; },  # "." separates whole from fraction; "," becomes the thousands separator
     "decimal-comma|dc"                             => sub { $decimal_symbol = ","; },  # "," separates whole from fraction; "." becomes the thousands separator (default)
     "tvd!"                                        => \$triple_vowels_to_double,
     "email|pe!"                                   => \$process_email,
     "kill-common|eliminar-comunes|kcw|kc|epc|ep!" => \$kill_common_words,
     "use-lig|ul|lig!"                             => \$use_ligatures,
     "manner|man|modo|m!"                          => \$output_manner,
     "g2p!"                                        => \$g2p_format,
     "nia|oa|ao!"                                  => \$non_ipa_apostrophes,
     "nyr|rae!"                                    => \$narrow_year_ranges,
     "och!"                                        => \$use_one_char_ch_symbol,
     "oye!"                                        => \$use_one_char_ye_symbol,
     "phon|fon|ph|IPA|AFI"                         => \$output_PHON,
     "phonword|pw!"                                => \$phonword_format,
     "place|pl|punto|p!"                           => \$output_place,
     "rt|tc|corpus!"                               => \$corpus_running_text,
     "sbs|sbu|spe|spo!"                            => \$syllabify_by_sentence,
     "silent-mode|silent|sil|sm!"                  => \$silent_mode,
     "syl-dots|sil-puntos|sd|sp!"                  => \$insert_syllable_dots,
     "syl-list|lista-sil|sl|ls!"                   => \$syllable_list,
     "syl-spaces|sil-esp|ss|se!"                   => \$syllable_dots_are_spaces,
     "times|times-colon|twc!"                      => \$times_with_colon,
     "urls|pu!"                                    => \$process_urls,
     "voicing|voice|v|sonoridad|son|s!"            => \$voicing,
     "vrt!"                                        => \$vrt_format,
     "word-list|lista-pal|wl|lp!"                  => \$word_list,
);

# Snapshot after CLI flags have been applied, before perkins.ini.
# Any var that differs from %_VAL_DEFAULT was set on the command line.
our %_SET_BY_INI = ();    # vars set by perkins.ini; populated in ini reader loop
our %_VAL_AFTER_CLI = ("debug" => $debug, "debug_syllab_sub" => $debug_syllab_sub, "debug_to_logfile" => $debug_to_logfile, "silent_mode" => $silent_mode, "batch_mode" => $batch_mode, "corpus_running_text" => $corpus_running_text, "syllable_list" => $syllable_list, "g2p_format" => $g2p_format, "phonword_format" => $phonword_format, "vrt_format" => $vrt_format, "output_PHON" => $output_PHON, "output_CV" => $output_CV, "output_CVG" => $output_CVG, "output_CVNLRG" => $output_CVNLRG, "output_manner" => $output_manner, "output_place" => $output_place, "voicing" => $voicing, "multichars" => $multichars, "tr_is_group" => $tr_is_group, "ch_dzh_retracted" => $ch_dzh_retracted, "use_ligatures" => $use_ligatures, "use_one_char_ch_symbol" => $use_one_char_ch_symbol, "ye_phoneme_is_fricative" => $ye_phoneme_is_fricative, "ye_phoneme_is_affricate" => $ye_phoneme_is_affricate, "use_one_char_ye_symbol" => $use_one_char_ye_symbol, "use_dental_diacr" => $use_dental_diacr, "add_epenthetic_g" => $add_epenthetic_g, "glides_with_diacritics" => $glides_with_diacritics, "non_syl_u_with_u_diacr" => $non_syl_u_with_u_diacr, "non_syl_i_with_i_diacr" => $non_syl_i_with_i_diacr, "ch_is_single_char" => $ch_is_single_char, "ch_is_retracted_with_ligature" => $ch_is_retracted_with_ligature, "ch_is_retracted_no_ligature" => $ch_is_retracted_no_ligature, "ch_not_retracted_with_ligature" => $ch_not_retracted_with_ligature, "ch_not_retracted_no_ligature" => $ch_not_retracted_no_ligature, "space_between_phonemes" => $space_between_phonemes, "word_plus_phonemes" => $word_plus_phonemes, "stress_using_tildes" => $stress_using_tildes, "no_stress_marks" => $no_stress_marks, "non_ipa_apostrophes" => $non_ipa_apostrophes, "insert_syllable_dots" => $insert_syllable_dots, "split_at_syllables" => $split_at_syllables, "syllabify_by_sentence" => $syllabify_by_sentence, "syllable_dots_are_spaces" => $syllable_dots_are_spaces, "ipa_pause_symbols" => $ipa_pause_symbols, "ipa_long_pause_two_singles" => $ipa_long_pause_two_singles, "add_comma_pauses" => $add_comma_pauses, "add_colon_pauses" => $add_colon_pauses, "add_semicolon_pauses" => $add_semicolon_pauses, "add_sentence_breaks" => $add_sentence_breaks, "add_paragraph_breaks" => $add_paragraph_breaks, "add_ellipsis_pauses" => $add_ellipsis_pauses, "add_bracket_pauses" => $add_bracket_pauses, "add_paren_pauses" => $add_paren_pauses, "numerals_to_words" => $numerals_to_words, "num_symbol" => $num_symbol, "decimal_symbol" => $decimal_symbol, "narrow_year_ranges" => $narrow_year_ranges, "broad_year_ranges" => $broad_year_ranges, "bc_dates_included" => $bc_dates_included, "all_year_ranges" => $all_year_ranges, "times_with_colon" => $times_with_colon, "fix_umlauts" => $fix_umlauts, "fix_grave_accents" => $fix_grave_accents, "fix_circumflexes" => $fix_circumflexes, "fix_nasal_tildes" => $fix_nasal_tildes, "moneda" => $moneda, "rut_k_mode" => $rut_k_mode, "slash" => $slash, "process_urls" => $process_urls, "process_email" => $process_email, "name_for_v" => $name_for_v, "keep_paragraphs" => $keep_paragraphs, "vertical_output" => $vertical_output, "triple_vowels_to_double" => $triple_vowels_to_double, "kill_common_words" => $kill_common_words, "upper_case" => $upper_case);

############################################################################
#        PROCESS THE CONFIGURATION FILE (perkins.ini) IF IT EXISTS         #
#  If $use_config_file is true, then these options supersede the defaults  #
############################################################################

# Open config file if requested

if ( $use_config_file == 1 ) {

     if ( open( CONFIGFILE, '<:encoding(UTF-8)', "$config_filename" ) ) {

          $config_file_present = 1;

          # Set of variables that perkins.ini is allowed to set.
          # Using $$var = $value without this guard would let a
          # malicious (or typo-ridden) config file overwrite any
          # package global, including internal-only variables.
          my %_ALLOWED_CFG_VARS = map { $_ => 1 } qw(
               debug debug_syllab_sub debug_to_logfile silent_mode batch_mode
               corpus_running_text syllable_list g2p_format phonword_format vrt_format
               word_list coscach_mode
               output_PHON output_CV output_CVG output_CVNLRG output_manner output_place voicing
               multichars tr_is_group ch_dzh_retracted use_ligatures use_one_char_ch_symbol
               ye_phoneme_is_fricative ye_phoneme_is_affricate use_one_char_ye_symbol
               use_dental_diacr add_epenthetic_g
               glides_with_diacritics non_syl_u_with_u_diacr non_syl_i_with_i_diacr
               ch_is_single_char ch_is_retracted_with_ligature ch_is_retracted_no_ligature
               ch_not_retracted_with_ligature ch_not_retracted_no_ligature
               space_between_phonemes word_plus_phonemes
               stress_using_tildes no_stress_marks non_ipa_apostrophes
               insert_syllable_dots split_at_syllables syllabify_by_sentence
               syllable_dots_are_spaces
               ipa_pause_symbols ipa_long_pause_two_singles
               add_comma_pauses add_colon_pauses add_semicolon_pauses add_sentence_breaks
               add_paragraph_breaks add_ellipsis_pauses add_bracket_pauses add_paren_pauses
               numerals_to_words num_symbol decimal_symbol
               narrow_year_ranges broad_year_ranges bc_dates_included all_year_ranges
               times_with_colon
               fix_umlauts fix_grave_accents fix_circumflexes fix_nasal_tildes
               moneda rut_k_mode slash process_urls process_email name_for_v
               keep_paragraphs vertical_output triple_vowels_to_double
               kill_common_words upper_case
          );

          while (<CONFIGFILE>) {

               chomp;
               s/#.*//;
               s/^\s+//;
               s/\s+$//;
               next unless length;

               my ( $var, $value ) = split( /\s*=\s*/, $_, 2 );

               $var =~ s/^\$//;   # Strip leading '$' if present in config file entry

               # Reject unknown variable names so a typo in perkins.ini
               # does not silently create a new package global (or worse,
               # overwrite an internal variable).
               unless ( exists $_ALLOWED_CFG_VARS{$var} ) {
                    print STDERR safe_colored(
                         "WARNING: $config_filename: unknown variable '$var' ignored.\n",
                         "bold yellow"
                    );
                    next;
               }

               {
                    # $var has already been validated against
                    # %_ALLOWED_CFG_VARS above, so this symbolic reference
                    # can only ever reach one of those known-safe package
                    # globals -- exactly the guarantee the whitelist check
                    # exists to provide. `use strict` (added later) forbids
                    # symbolic references by default; scope the exception
                    # as tightly as possible, to just this one assignment.
                    no strict 'refs';
                    $$var = $value;
               }
               $main::_SET_BY_INI{$var} = 1;   # record that ini set this var

               print STDOUT "Non-Default Option Set: $var = $value\n";
          }

          close(CONFIGFILE);
     }
     else {

          &config_file_failed;
     }
}

############################################################################
#          SET PROGRAM INTERFACE LANGUAGE IF BINARY SWITCHES USED          #
############################################################################
if ( $eng == 1 ) { $lang = "en" }
if ( $esp == 1 ) { $lang = "es" }

############################################################################
# NOTE: A "DEFINE LANGUAGE STRINGS FOR LOCALIZATION" step (assign_lang_str)  #
# used to be called here. It was an empty no-op stub -- never implemented   #
# and not wired to anything -- so it was removed. All bilingual (en/es)     #
# text in this program is handled inline via if($lang eq "es"){...}else{...}#
# branches, so no functionality is lost by removing it.                    #
############################################################################

############################################################################
#              IF USER NEEDS HELP, RUN HELP SUBROUTINE AND DIE             #
############################################################################
if ( $helpme == 1 ) {
     &print_help;
     exit;
}

############################################################################
#         IF  USER REQUESTS USAGE INFO, RUN THAT SUBROUTINE AND DIE        #
############################################################################
if ( $useme == 1 ) {
     &print_usage;
     exit;
}

# Lowercase any text provided with the -f (output format) switch           #
$output_format = lc($output_format);

############################################################################
# SET OPTIONS FOR DEBUGGING TO LOG FILE                                    #
############################################################################
if ( $debug_to_logfile == 1 ) {
     $debug = 1;
}

############################################################################
#                             VERIFY CLI OPTIONS                           #
#                         Only checks some of them.                        #
############################################################################

&verify_cli_options;

############################################################################
#      AUTOMATICALLY SELECT VRT FORMAT IF INPUT FILE ENDS IN ".VRT"        #
############################################################################
if ( $input_filename =~ m/\.vrt$/ ) {
     $vrt_format = 1;
}

############################################################################
# OPEN INPUT FILE, CREATE LOG FILE                                         #
############################################################################
&open_input_and_log_files;

############################################################################
# Print bug log header, status messages, CLI info and cfg file status      #
# message                                                                  #
############################################################################
my $current_time = localtime();    # Get local time for log file
&print_header_CLI_status_msgs;

############################################################################
# ASSIGN DEFAULT OUTPUT FILENAME IF NECESSARY (.phnm extension)            #
############################################################################
if ( $output_filename eq "" ) {
     &assign_default_output_filename;
}

############################################################################
#              OPEN OUTPUT FILE - Output will be UTF-8                     #
############################################################################
&open_output_file;

############################################################################
# SNAPSHOT EXPLICIT SILENT/BATCH MODE STATE                                #
# Taken here -- after defaults, perkins.ini, and CLI flags have all been   #
# applied, but BEFORE forcibly_set_variables() runs. Some meta-config      #
# presets (--syl-list, --word-list, --rt/--corpus, --mfa, etc.) forcibly   #
# turn on silent mode (and, via it, batch mode) as a side effect, but that #
# should not by itself suppress the greeting -- only an EXPLICIT default,  #
# perkins.ini setting, or command-line flag should.                       #
############################################################################
our $explicit_silent_mode = $silent_mode;
our $explicit_batch_mode  = $batch_mode;

############################################################################
#                   PROCESS COMMAND LINE OPTIONS                           #
# If there are any, they supersede both the defaults and the .ini file     #
############################################################################
&process_command_line_options;

############################################################################
#         FORCIBLY SET VARIABLES THAT DEPEND ON OTHER VARIABLES            #
# This has to be done here, and not earlier, so as to receive any changed  #
# variable values from the config file or command line options.            #
############################################################################
&forcibly_set_variables;

############################################################################
#                             CONSOLE GREETING                             #
# This is printed in normal (non-batch/non-silent) mode, but only if the   #
# debug to log file option is OFF. "Non-batch/non-silent" is judged by the #
# EXPLICIT snapshot above, not by $batch_mode/$silent_mode as they stand   #
# now -- a meta-config preset forcing silent mode on internally should not #
# by itself hide the greeting; only an explicit default/ini/CLI setting    #
# should.                                                                  #
############################################################################
if ( ( $explicit_batch_mode == 0 ) && ( $explicit_silent_mode == 0 ) && ( $debug_to_logfile == 0 ) ) {
     &print_console_greeting;
}

############################################################################
# Print message to log file if config file couldn't be read.               #
# Only done if debug is ON and debug to log file is ON.                    #
############################################################################
if ( ( $debug == 1 ) && ( $debug_to_logfile == 1 ) ) {
     print LOGFILE "\nDEBUG IO:  *WARINING* External config file couldn't be opened.";
     print LOGFILE "\nDEBUG IO:             Using defaults instead.";
}

sub start_of_program {

     # This is a dummy subroutine. Its only purpose is to generate an automatic
     # link in my IDE.
}

############################################################################
#                                                                          #
#                    BEGIN PROCESSING THE INPUT TEXT FILE                  #
#                                                                          #
############################################################################
while (<INPUTFILE>) {
     chomp;

     ########################################################################
     # PRE-LC PASS                                                           #
     # Case-sensitive tokens must be expanded BEFORE lc() destroys their    #
     # uppercase letters.  We produce all-lowercase Spanish words here so   #
     # they survive the lc() call below unchanged.                          #
     ########################################################################

     # ── PRE-LC PASS ──────────────────────────────────────────────────────
     # All case-sensitive token expansions (identifiers, era markers,
     # currency codes, Roman numerals) are handled in prelc_pass().
     # See the subroutine for the required processing order and rationale.
     $_ = prelc_pass($_);

     $input_line = lc;

     # ── LINE-LEVEL PIPELINE ──────────────────────────────────────────────
     # VRT stripping, era abbreviations, structured-token normalisation,
     # numeral expansion, symbol conversion, and all other line-level
     # preprocessing.  See process_line() for stage order and rationale.
     $input_line = process_line($input_line);

     ############################################################################
     #                             SPLIT LINES INTO WORDS                       #
     ############################################################################
     @word_array = split / /, $input_line;

     ############################################################################
     # BUFFERS FOR THIS LINE'S FINISHED WORDS                                   #
     # Words are not printed immediately. Instead they are collected here so   #
     # that fragments produced by the "-mente" splitting routine (which begin  #
     # with a leading "_") can be re-joined onto the previous word regardless  #
     # of whether output is horizontal (words on one line) or vertical (one    #
     # word per line, e.g. -g2p, -vo). $output_ortho stores the corresponding  #
     # orthographic form of each finished entry, for use by -g2p output.       #
     ############################################################################
     my @output_words = ();
     my @output_ortho  = ();

     foreach my $word (@word_array) {

          #print "\nBEF-CHANGE-¬-TO-SPACE :$word\n";           # AD-HOC DEBUG

          # Change ¬ to space (for VRT format). If the ¬ is not inserted previously, #
          # then the above split will break up VRT lines into one word per line,     #
          # utterly mangling the output file's line-by-line correspondence to the    #
          # input .vrt file.                                                         #
          $word =~ s/¬/ /g;

          #print "AFT-CHANGE-¬-TO-SPACE :$word\n";           # AD-HOC DEBUG

          ########################################################################
          # ERROR SENTINEL BYPASS                                                 #
          # Tokens that are error sentinels ([MalformedNumber], [InvalidDate],   #
          # etc.) must NOT be phonemicized. They are control tokens, not Spanish  #
          # words. Output them verbatim and skip all phonological processing.     #
          ########################################################################
          if ($word =~ /^\[(?:MalformedNumber|InvalidDate|InvalidCurrency|InvalidFraction|UnknownToken)\]/) {
               push @output_words, $word;
               push @output_ortho,  $word;
               next;
          }

          ############################################################################
          # SNAPSHOT THE ORTHOGRAPHIC FORM OF THE WORD                               #
          # Taken before any phonemic conversion, for use by -g2p output (which      #
          # needs the dictionary-form orthographic word alongside its transcription).#
          ############################################################################
          my $orthographic_word = $word;

          # Orthographic cleanup: kill-common-words test mode, vowel collapse,
          # letter spelling, pattern normalization.  See prepare_word_orthographic().
          $word = prepare_word_orthographic($word);

          # Full phonemic transcription pipeline: grapheme conversion, stress,
          # syllabification, optional symbol/format transforms.
          $word = transcribe_word($word);

          # Buffer this word (or join _mente continuation to previous word).
          buffer_word($word, $orthographic_word,
               \@output_words, \@output_ortho, $stress_using_tildes);

     }

     ############################################################################
     # OUTPUT EACH FINISHED WORD ON THIS LINE TO FILE                           #
     # format_output_word() handles G2P/phonword formatting; undef = skip.      #
     ############################################################################
     foreach my $i ( 0 .. $#output_words ) {
          my $formatted = format_output_word($output_words[$i], $output_ortho[$i]);
          next unless defined $formatted;

          if ( $vertical_output == 1 ) {
               print OUTPUTFILE "$formatted\r\n";
          }
          else {
               print OUTPUTFILE "$formatted ";
          }
     }


     ############################################################################
     # PROCESS PARAGRAPH BREAKS                                                 #
     ############################################################################

     # Add longest pause symbol ("###") if this option is chosen.               #
     if ( $add_paragraph_breaks == 1 ) { print OUTPUTFILE "###"; }

     # Add newlines at paragraph breaks if desired.                             #
     if ( $keep_paragraphs == 1 ) { print OUTPUTFILE "\n"; }

}

#################################################################################
#                                                                               #
#                         CLOSE UP SHOP ON STAGE 1                              #
#                                                                               #
#################################################################################
close(INPUTFILE);
close(OUTPUTFILE);



#################################################################################
#                                                                               #
#                                  STAGE 2                                      #
#                        PERFORM OUTPUT FILE CLEANING                           #
#                     (This should ALWAYS be activated)                         #
#################################################################################

#################################################################################
#                         Open output and temp files                            #
#################################################################################

###########################################################################
# WARN USER IF OUTPUT FILE CAN'T BE OPENED
###########################################################################
if ( $lang eq "es" ) {
     $user_message = "ADVERTENCIA: No fue posible abrir el archivo de salida \'$output_filename\' para lectura (input) y limpieza adicional.";
}
else {
     $user_message = "WARNING: The output file \'$output_filename\' could not be opened for reading and additional cleaning.";
}

###########################################################################
# MFA AND PHONWORD OUTPUT ARE ALREADY IN THEIR FINAL FORM. SKIP CLEANUP.
###########################################################################
if ( $g2p_format == 1 || $phonword_format == 1 ) {

     if ( ( $explicit_batch_mode == 0 ) && ( $explicit_silent_mode == 0 ) ) {

          if ( $lang eq "es" ) {
               print STDOUT "\nLa transcripción se guardó en el archivo UTF-8 \"$output_filename\".\n";
          }
          else {
               print STDOUT "\nThe transcription was saved in the UTF-8 file \"$output_filename\".\n";
          }

     }

     exit;
}

###########################################################################
# OPEN THE OUTPUT FILE
###########################################################################
open( OUTPUTFILE, '<:encoding(UTF-8)', "$output_filename" )
  || die "\n\n$user_message\n\n";

#print STDOUT "\n >>> OFC: Opened the file $output_filename to read and then clean it.\n";             # NOTE DEBUGGING

# OPEN TEMP FILE. OUTPUT WILL BE UTF-8.
if ( $lang eq "es" ) {
     $user_message = "ADVERTENCIA: No fue posible crear el archivo temporal \($output_filename\).";
}
else {
     $user_message = "WARNING: The temporary file \'$output_filename\' could not be opened.";
}

open( TEMPFILE, '>:encoding(UTF-8)', "$output_filename\.cleaned" )
  || die "\n\n$user_message\n\n";

#print STDOUT " >>> OFC: Opened the file cleaned-$output_filename to write cleaned text into.\n";      # NOTE DEBUGGING

# BEGIN PROCESSING OUTPUT FILE
while (<OUTPUTFILE>) {
     chomp;

     # NOTE: case-adjustment (uc/lc) used to happen here, before cross-word
     # resyllabification. Moved to AFTER format conversion below, so that
     # word_level_syllab_adjust() (which matches literal lowercase IPA
     # phoneme characters) always sees real phonemes to work with,
     # regardless of output mode or eventual letter case.
     $input_line = $_;

     # ELIMINATE EXTRA WHITESPACE
     $input_line =~ s/\s(\s+)/$1/g;

     ############################################################################
     #                             CLEAN UP PAUSES                              #
     ############################################################################
     $input_line = &clean_up_pauses($input_line);

     # Print input line to debug log file, if desired.
     if ( $debug == 1 && $debug_to_logfile == 1 ) {
          print LOGFILE "\n==>$input_line";    #AD HOC DEBUG
     }

     # ELIMINATE UNDERSCORES (IN WORDS ENDING IN "-MENTE")
     $input_line =~ s/ _//g;
     $input_line =~ s/ _//g;

     #######################################################################
     #      PERFORM WORD-LEVEL SYLLABIFICATION ADJUSMENTS, IF DESIRED      #
     # This fixes things like "los hombres" = /los om.bres/, instead of    #
     # /lo.som.bres/. Runs BEFORE format conversion below (see the NOTE    #
     # in transcribe_word() for why this order matters) so this always    #
     # sees genuine IPA phonemes, in every output mode.                    #
     #######################################################################
     if ( $syllabify_by_sentence == 1 ) {

          #print STDOUT "\n\n$input_line"; # AD HOC DEBUG
          $input_line = word_level_syllab_adjust($input_line);
     }

     #######################################################################
     #                                                                     #
     #             OUTPUT FORMAT TRANSFORMATIONS (MOVED HERE)              #
     #                                                                     #
     #    Runs on the whole, already cross-word-resyllabified line, so    #
     #    C/V/N/L/R/G (etc.) letters correctly reflect any syllable       #
     #    boundary that shifted across a word boundary -- previously      #
     #    this ran per-word, before resyllabification, in                 #
     #    transcribe_word(), which silently prevented resyllabification   #
     #    from ever firing for these modes (its regex only matches        #
     #    literal IPA characters, not the converted letters).             #
     #######################################################################
     if ( $output_CV == 1 ) {
          $input_line = opt_convert_phonemes_cv($input_line);
     }
     elsif ( $output_CVG == 1 ) {
          $input_line = opt_convert_phonemes_cvg($input_line);
     }
     elsif ( $output_CVNLRG == 1 ) {
          $input_line = opt_convert_phonemes_cvnlrg($input_line);
     }
     elsif ( $output_manner == 1 ) {
          $input_line = opt_convert_phonemes_manners($input_line);
     }
     elsif ( $output_place == 1 ) {
          $input_line = opt_convert_phonemes_places($input_line);
     }
     elsif ( $voicing == 1 ) {
          $input_line = opt_convert_phonemes_voicing($input_line);
     }

     # ADJUST CASE AS REQUIRED (moved here -- see NOTE above)
     if ( $upper_case == 1 ) {
          $input_line = uc($input_line);
     }
     else {
          $input_line = lc($input_line);
     }

     ############################################################################
     #                   PROCESS SYLLABLE DOTS, IF DESIRED                      #
     ############################################################################

     # Convert syllable dots into spaces, if that option is selected       #
     if ( $syllable_dots_are_spaces == 1 ) {
          $input_line =~ s/\./ /g;
     }

     # ELIMINATE SYLLABLE DOTS IF THAT OPTION IS SELECTED                  #
     if ( $insert_syllable_dots == 0 ) {
          $input_line =~ s/\.//g;
     }

     #######################################################################
     # Print each cleaned-up line to the temporary file                    #
     #######################################################################
     print TEMPFILE "$input_line";

}

#################################################################################
#                        Close unneeded files                                   #
#################################################################################
close(OUTPUTFILE);
close(TEMPFILE);

###################################################################################
# Copy temp (cleaned) file on top of partially-finished file and delete temp file #
###################################################################################
copy( "$output_filename\.cleaned", "$output_filename" )
  or die " ***** Copying of cleaned file to output file failed! *****\n";

unlink("$output_filename\.cleaned");    # Deletes de file from disk

#################################################################################
# PRINT FINAL MESSAGE TELLING WHAT FILE THE TRANSCRIPTION IS IN                 #
#################################################################################
if ( ( $explicit_batch_mode == 0 ) && ( $explicit_silent_mode == 0 ) ) {

     if ( $lang eq "es" ) {
          $user_message = "La transcripción se guardó en el archivo UTF-8 \"$output_filename\".";
     }
     else {
          $user_message = "The transcription was saved in the UTF-8 file \"$output_filename\".";
     }

     print STDOUT "\n$user_message\n\n";
}

#################################################################################
#                                  Exit program                                 #
#################################################################################
exit;

#################################################################################
#                                                                               #
#                                                                               #
#                                                                               #
#                                                                               #
#                            SUBROUTINES LIVE HERE...                           #
#                                                                               #
#                                                                               #
#                                                                               #
#################################################################################

###############################################################################
#                SUBROUTINE: CONFIG FILE COULDN'T BE OPENED                   #
# NOTE: Must define lang strings HERE, and not in subroutine.                 #
###############################################################################
sub config_file_failed {

     if ( $lang eq "es" ) {
          $user_message = "ADVERTENCIA: No fue posible abrir el archivo de configuración ($config_filename).";
     }
     else {
          $user_message = "WARNING: The configuration file (\'$config_filename\') could not be opened.";
     }

     # Notify user that config file could NOT be opened.
     if ( ( $debug_to_logfile == 0 ) && ( $silent_mode == 0 ) ) {
          print STDOUT "\n\n$user_message\n";
     }
     elsif ( $debug_to_logfile == 1 ) {
          print LOGFILE "\n\n$user_message\n";
     }
     $config_file_present = 0;
}

###############################################################################
#                        SUBROUTINE: VERIFY CLI OPTIONS                       #
###############################################################################
sub verify_cli_options {

     # Make sure an input file was given, else die.
     if ( $input_filename eq "" ) {

          if ( $lang eq "es" ) {
               $user_message = "ERROR: Debes proporcionar el nombre del archivo a procesar. Esto se hace con la opción \'-i=input.txt\'";
          }
          else {
               $user_message = "ERROR: You must specify the name of the file to be processed with the -i flag (\'-i=input.txt\')";
          }

          &print_info_header;

          die "\n$user_message\n\n";
     }

     # TODO: Die when a non-existent option is provided

}

#################################################################################
#                        SUBROUTINE: COLOR HELPERS FOR HELP TEXT                #
#################################################################################
# Used only by print_help() below. Section headers get one color; the flag
# name(s) at the start of each option line get another -- but NOT the comma
# (and following space) that separates multiple aliases for the same option,
# which stays in the terminal's default color. See safe_colored() above for
# why this doesn't just call colored() directly.
sub color_header {
     my ($text) = @_;
     return safe_colored( $text, "bold blue" );
}

sub color_flags {
     my ($flags_str) = @_;
     return join( ", ", map {
          # If this flag token has a "=VALUE" placeholder (e.g. "--format=MODE",
          # "-sn=SYMBOL"), color the flag part green and the dummy/placeholder
          # value yellow, so it's visually obvious which part is literal and
          # which part the user is meant to replace.
          if ( /^([^=]+=)(.+)$/ ) {
               safe_colored( $1, "bold green" ) . safe_colored( $2, "yellow" );
          }
          else {
               safe_colored( $_, "bold green" );
          }
     } split( /,\s*/, $flags_str ) );
}

#################################################################################
#                        SUBROUTINE: HELP SECTION                               #
#################################################################################
sub print_help {

     &print_info_header;

     binmode STDOUT, ":utf8";
     if ( $lang eq "es" ) {

          print STDOUT "OPCIONES PRINCIPALES:";
          print STDOUT "\n " . color_flags("-i, --input=MIARCHIVO.TXT") . "     Especificar el archivo que se transcribirá.";
          print STDOUT "\n                               ESTE PARÁMETRO ES OBLIGATORIO.";
          print STDOUT "\n " . color_flags("-o, --output=MITRANSCR.TXT") . "    Especificar el archivo en el que se guardará";
          print STDOUT "\n                               la transcripción. Si no se especifica, se";
          print STDOUT "\n                               creará un archivo con el mismo nombre que el";
          print STDOUT "\n                               de entrada, pero con la extensión \".phnm\".";
          print STDOUT "\n";
          print STDOUT "\n NOTA: Si contienen espacios, los nombres de archivos deben ir entre comillas.";
          print STDOUT "\n";
          print STDOUT "\n " . color_flags("-h, --help") . "                    Ver la ayuda de Perkins.";
          print STDOUT "\n " . color_flags("-u, --uso") . "                     Ver ejemplos del uso de Perkins.";
          print STDOUT "\n " . color_flags("-cfg, -ini") . "                    Usar el archivo de configuración ($config_filename).";
          print STDOUT "\n " . color_flags("-nocfg, -noini") . "                NO usar el archivo de configuración.";
          print STDOUT "\n " . color_flags("-cf, --config-file=NOMBRE") . "     Usar NOMBRE en vez de \"perkins.ini\" como archivo";
          print STDOUT "\n                               de configuración (ej.: --config-file=corpora.ini).";
          print STDOUT "\n " . color_flags("-b, -batch") . "                    Ejecutar Perkins en modo batch (lotes). No envia";
          print STDOUT "\n                               mensajes a STDOUT en este caso.";
          print STDOUT "\n " . color_flags("-sil, --silent-mode") . "           Modo silencioso. Se suprime todo mensaje y archivo";
          print STDOUT "\n                               de registro (incluyendo los de errores).";
          print STDOUT "\n " . color_flags("-l, --lang [en|es]") . "            Seleccionar el idioma de la interfaz de Perkins";
          print STDOUT "\n                               (\"es\" para español; \"en\" para inglés).";
          print STDOUT "\n " . color_flags("-en") . "                           Seleccionar la interfaz en inglés.";
          print STDOUT "\n " . color_flags("-es") . "                           Seleccionar la interfaz en español.";
          print STDOUT "\n " . color_flags("-d, --debug") . "                   Proporcionar información de debugging";
          print STDOUT "\n " . color_flags("-dl, --debug-log") . "              Guardar la información de debugging en un archivo";
          print STDOUT "\n                               de registro (termina en .log).";
          print STDOUT "\n";
          print STDOUT "\n" . color_header("METACONFIGURACIONES:");
          print STDOUT "\n " . color_flags("-tc, --corpus") . "                 Configuración para procesar un corpus como texto corrido";
          print STDOUT "\n " . color_flags("-ls, --lista-sil") . "              Configuración para generar transcripciones que";
          print STDOUT "\n                               pueden procesarse fácilmente a nivel de sílaba.";
          print STDOUT "\n " . color_flags("-g2p") . "                          Configuración para generar un diccionario Grafema-a-Fonema (G2P),";
          print STDOUT "\n                               compatible con herramientas como el Montreal Forced Aligner.";
          print STDOUT "\n " . color_flags("-vrt") . "                          Configuración para generar archivos verticales (.vrt)";
          print STDOUT "\n                               compatibles con IMS CWB. No puede realizar todos los";
          print STDOUT "\n                               análisis (e.g. expansión de abreviaturas).";
          print STDOUT "\n " . color_flags("-lp, --lista-pal") . "              Configuración que trata el texto como una lista de";
          print STDOUT "\n                               palabras: silabiza a nivel de palabra en vez de";
          print STDOUT "\n                               enunciado.";
          print STDOUT "\n " . color_flags("-csc, --coscach") . "               Configuación para procesar transcripciones del Coscach.";
          print STDOUT "\n";
          print STDOUT "\n" . color_header("OPCIONES DE MODO DE TRANSCRIPCIÓN:");
          print STDOUT "\n " . color_flags("-f=, --formato=MODO") . "           Especificar el modo o formato de la transcripción.";
          print STDOUT "\n                               NO distingue mayúsculas de minúsculas. Las";
          print STDOUT "\n                               posibilidades son las siguientes:";
          print STDOUT "\n";
          print STDOUT "\n                                   F        (transcripción fonémica)";
          print STDOUT "\n                                   CV       (consonante/vocal)";
          print STDOUT "\n                                   CVG      (consonante/vocal/glide)";
          print STDOUT "\n                                   CVN      (cons/vocal/nasal/líqida/rótica/glide)";
          print STDOUT "\n                                   M, MODO  (modo de articulación)";
          print STDOUT "\n                                   P, PUNTO (punto de articulación)";
          print STDOUT "\n                                   S, SON   (sonoridad)";
          print STDOUT "\n";
          print STDOUT "\n " . color_flags("-phon, -fon, -ph") . "              Transcripción fonémica (equivale a --formato=F). Es el";
          print STDOUT "\n                               modo por defecto.";
          print STDOUT "\n " . color_flags("-voicing, -son, -s") . "            En vez de fonemas, mostrar la sonoridad de cada segmento";
          print STDOUT "\n                               (equivale a --formato=SON).";
          print STDOUT "\n " . color_flags("-phonword, -pw") . "                Formato \"palabra-fonología\": una palabra por línea, como";
          print STDOUT "\n                               \"Ortográfica<TAB>tran.scrip.ción\" (con tildes conservadas";
          print STDOUT "\n                               en la columna ortográfica, y puntos de sílaba y tildes";
          print STDOUT "\n                               de acento en la columna fonológica).";
          print STDOUT "\n " . color_flags("-manner, -man, -modo") . "        Mostrar el modo de articulación de cada segmento en vez";
          print STDOUT "\n                               de fonemas (equivale a --formato=MODO).";
          print STDOUT "\n " . color_flags("-place, -pl, -punto") . "         Mostrar el punto de articulación de cada segmento en vez";
          print STDOUT "\n                               de fonemas (equivale a --formato=PUNTO).";
          print STDOUT "\n";
          print STDOUT "\n" . color_header("OPCIONES PARA DETERMINADOS FONEMAS:");
          print STDOUT "\n " . color_flags("-multi, -ms") . "                   Usar múltiples símbolos AFI para algunos fonemas.";
          print STDOUT "\n " . color_flags("-och") . "                          Representar el fonema \"ch\" como /ʧ/ independiente de";
          print STDOUT "\n                               cualquier otra configuración.";
          print STDOUT "\n " . color_flags("-tg") . "                           Tratar /tr/ como fonema: usar ligadura (agregando -mc) o";
          print STDOUT "\n                               representarlo como frica. retrofl. /ʂ/ (agregando -nomc).";
          print STDOUT "\n " . color_flags("-yf") . "                           Representar el fonema \"ye\" como fricativa /ʝ/.";
          print STDOUT "\n " . color_flags("-ya") . "                           Representar el fonema \"ye\" como africada /d͡ʒ/.";
          print STDOUT "\n " . color_flags("-oye") . "                          Representar el fonema \"ye\" como /ʝ/ independiente de";
          print STDOUT "\n                               cualquier otra configuración.";
          print STDOUT "\n " . color_flags("-ar") . "                           Usar diacrítico \"retraído\" en algunas africadas";
          print STDOUT "\n                               (e.g. /t̠͡ʃ/).";
          print STDOUT "\n " . color_flags("-lig") . "                          Usar ligaduras en /t͡ʃ/ y /d͡ʒ/. \'-nolig\' impide su uso.";
          print STDOUT "\n                               Debe usarse con la opción \'-mc\'!";
          print STDOUT "\n " . color_flags("-dd, --dent") . "                   Usar diacrítico dental con /t/ y /d/. Debe usarse con \'-mc\'!";
          print STDOUT "\n " . color_flags("-age") . "                          Agregar [g] epentética antes de /w/ (e.g. <hueco> > [gweko].";
          print STDOUT "\n";
          print STDOUT "\n" . color_header("ATAJOS PARA \"CH\":");
          print STDOUT "\n " . color_flags("-chsc") . "                         Atajo CH: carácter único, ej. ʧ. Anula -mc/-ar/-lig";
          print STDOUT "\n                               para este fonema únicamente.";
          print STDOUT "\n " . color_flags("-chrl") . "                         Atajo CH: retraído, con ligadura, ej. t̠͡ʃ.";
          print STDOUT "\n " . color_flags("-chrnl") . "                        Atajo CH: retraído, sin ligadura, ej. t̠ʃ.";
          print STDOUT "\n " . color_flags("-chnrl") . "                        Atajo CH: no retraído, con ligadura, ej. t͡ʃ.";
          print STDOUT "\n " . color_flags("-chnrnl") . "                       Atajo CH: no retraído, sin ligadura, ej. tʃ.";
          print STDOUT "\n";
          print STDOUT "\n" . color_header("OPCIONES PARA GLIDES (SEMIVOCALES):");
          print STDOUT "\n " . color_flags("-gd, --glides-dia") . "             Representar glides como vocal + diacrítico \"no silábico\".";
          print STDOUT "\n " . color_flags("-nogd, --noglides-dia") . "         Representar glides como /j/ o /w/.";
          print STDOUT "\n " . color_flags("-wv") . "                           Representar wau con u + diacrítico \"no silábico\".";
          print STDOUT "\n " . color_flags("-yv") . "                           Representar yod con i + diacrítico \"no silábico\".";
          print STDOUT "\n";
          print STDOUT "\n" . color_header("OPCIONES DE ACENTUACIÓN:");
          print STDOUT "\n " . color_flags("-at") . "                           Representar el acento con tilde en vez del apóstrofo AFI.";
          print STDOUT "\n " . color_flags("-ao") . "                           Representar el acento con un apóstrofo ortográfico (no AFI).";
          print STDOUT "\n";
          print STDOUT "\n" . color_header("OPCIONES DE SILABIFICACIÓN:");
          print STDOUT "\n " . color_flags("-sp, --sil-puntos") . "             Representar las divisiones silábicas con puntos.";
          print STDOUT "\n " . color_flags("-se, --sil-esp") . "                Representar las divisiones silábicas con espacios.";
          print STDOUT "\n " . color_flags("-nosd") . "                         No representar las divisiones silábicas.";
          print STDOUT "\n " . color_flags("-spe") . "                          Silabificar por enunciado/oración, y no por palabra (\"los";
          print STDOUT "\n                               hombres\" > /lo.som.bres/ en vez de /los om.bres/).";
          print STDOUT "\n " . color_flags("-nospe") . "                        Silabificar por palabra, y no por enunciado/oración (\"los";
          print STDOUT "\n                               hombres\" > /los om.bres/ en vez de /lo.som.bres/).";
          print STDOUT "\n " . color_flags("-des, --div-sil") . "               Poner un salto de línea en cada división silábica (una";
          print STDOUT "\n                               sílaba por línea).";
          print STDOUT "\n";
          print STDOUT "\n" . color_header("OPCIONES PARA LAS PAUSAS:");
          print STDOUT "\n " . color_flags("-pi, --pausas-afi") . "             Representar pausas con los símbolos AFI | y ‖";
          print STDOUT "\n " . color_flags("-pl2") . "                          Representar la pausa larga con dos barras de pausa corta (||) en";
          print STDOUT "\n                               vez de una sola barra doble (‖), la cual no puede verse en Praat.";
          print STDOUT "\n " . color_flags("-pco") . "                          Tratar comas como pausas.";
          print STDOUT "\n " . color_flags("-pdp") . "                          Tratar el símbolo : como pausa.";
          print STDOUT "\n " . color_flags("-ppc") . "                          Tratar el símbolo ; como pausa.";
          print STDOUT "\n " . color_flags("-por") . "                          Mantener divisiones entre oraciones.";
          print STDOUT "\n " . color_flags("-ppa") . "                          Mantener divisiones entre párrafos.";
          print STDOUT "\n " . color_flags("-pel") . "                          Tratar elipsis (...) como pausa.";
          print STDOUT "\n " . color_flags("-pcr") . "                          Convertir corchetes [] en pausas.";
          print STDOUT "\n " . color_flags("-ppn") . "                          Convertir parénthesis en pausas.";
          print STDOUT "\n";
          print STDOUT "\n" . color_header("OPCIONES PARA NÚMEROS:");
          print STDOUT "\n " . color_flags("-nap") . "                          Convertir numerales en palabras (\"4\" > \"cuatro\").";
          print STDOUT "\n " . color_flags("-sn=SIMBOLO") . "                   Reemplazar números por el símbolo que se especifica aquí.";
          print STDOUT "\n " . color_flags("-rae") . "                          Procesar dos grupos de 4 dígios con \"-\" entre medio como un rango";
          print STDOUT "\n                               de años (\"1900-2000\" > \"1900 a 2000\" y no \"1900 menos 2000\").";
          print STDOUT "\n " . color_flags("-raa") . "                          Procesar dos grupos de 1-4 dígitos con \"-\" entre medio";
          print STDOUT "\n                               como un rango de años (43-103 > 43 a 103).";
          print STDOUT "\n " . color_flags("-tra") . "                          Procesar todos los grupos de 1-4 dígitos con un \"-\" entre medio como";
          print STDOUT "\n                               como un rango de años.";
          print STDOUT "\n " . color_flags("-aac") . "                          Procesar también los años A.C.";
          print STDOUT "\n " . color_flags("-twc, --times-colon") . "           Procesar horas (1-2 dígitos, dos puntos, dos dígitos) como";
          print STDOUT "\n                               expresiones horarias (ej.: \"14:30\"). Activado por defecto.";
          print STDOUT "\n " . color_flags("-dp, --decimal-period") . "         Usar el punto como separador decimal (12.35) y la coma";
          print STDOUT "\n                               como separador de miles (1,500 = mil quinientos).";
          print STDOUT "\n " . color_flags("-dc, --decimal-comma") . "          Usar la coma como separador decimal (12,35) y el punto";
          print STDOUT "\n                               como separador de miles (1.500 = mil quinientos). Por defecto.";
          print STDOUT "\n";
          print STDOUT "\n" . color_header("SUSTITUCIONES:");
          print STDOUT "\n " . color_flags("-mon=TEXTO") . "                    Reemplazar el símbolo \"\$\" por lo que se especifica como TEXTO.";
          print STDOUT "\n " . color_flags("-lo=TEXTO") . "                     Reemplazar el símbolo \"/\" por lo que se especifica como TEXTO.";
          print STDOUT "\n " . color_flags("-nma") . "                          No marcar el acento tónico.";
          print STDOUT "\n " . color_flags("-pu") . "                           Procesar los URL lingüísticamente. De lo contrario, se suprimen";
          print STDOUT "\n " . color_flags("-pe") . "                           Procesar las direcciones de e-mail lingüísticamente.";
          print STDOUT "\n                               De lo contrario, éstas se suprimen.";
          print STDOUT "\n";
          print STDOUT "\n" . color_header("OPCIONES DE PRESENTACIÓN:");
          print STDOUT "\n " . color_flags("-upl") . "                          Una palabra por línea (dividir en palabras).";
          print STDOUT "\n " . color_flags("-usl") . "                          Una sílaba por línea (dividir en sílabas).";
          print STDOUT "\n " . color_flags("-mp") . "                           Mantener la separación de párrafos. De lo contrario";
          print STDOUT "\n                               la transcripción será un solo gran muro de texto.";
          print STDOUT "\n " . color_flags("-epc") . "                          Eliminar palabras comunes (para fines de testeo).";
          print STDOUT "\n " . color_flags("-tvd") . "                          Reducir 3 o más vocales idénticas seguidas a 2 (ej.:";
          print STDOUT "\n                               \"nooo\" -> \"noo\"). Activado por defecto.";
          print STDOUT "\n";
          print STDOUT "\nNOTA: A la mayoría de las opciones que no toman un argumento (es decir, las opciones";
          print STDOUT "\n      binarias), se les puede agregar \"no\" entre el guión y la opción misma para";
          print STDOUT "\n      producir el efecto contrario: --nomulti, --nodebug, -notrg, --nod, etc.";
          print STDOUT "\n";
          print STDOUT "\n";
     }
     else {

          print STDOUT "MAIN OPTIONS:";
          print STDOUT "\n " . color_flags("-i, --input=MYFILE.TXT") . "        Specify the file to be processed. MANDATORY.";
          print STDOUT "\n " . color_flags("-o, --output=TRANSCR.TXT") . "      Specify the file in which to save Perkins' output.";
          print STDOUT "\n                               If not specified, a name will be automatically";
          print STDOUT "\n                               generated using the input file basename and an";
          print STDOUT "\n                               appropriate extension (e.g. \'.phnm\').";
          print STDOUT "\n";
          print STDOUT "\n NOTE: If filenames contain spaces, they must be enclosed in quotation marks.";
          print STDOUT "\n";
          print STDOUT "\n " . color_flags("-h, --help") . "                    Show this help information.";
          print STDOUT "\n " . color_flags("-u, --usage") . "                   Show usage information and examples.";
          print STDOUT "\n " . color_flags("-cfg, -ini") . "                    Use the configuration file ($config_filename).";
          print STDOUT "\n " . color_flags("-nocfg, -noini") . "                DON'T use the configuration file.";
          print STDOUT "\n " . color_flags("-cf, --config-file=NAME") . "       Use NAME instead of \"perkins.ini\" as the config";
          print STDOUT "\n                               file (e.g. --config-file=corpora.ini).";
          print STDOUT "\n " . color_flags("-b, --batch") . "                   Run Perkins in batch mode: suppresses all messages";
          print STDOUT "\n                               to the command line.";
          print STDOUT "\n " . color_flags("-sm, --silent-mode") . "            Silent mode. Suppresses all messages and logging";
          print STDOUT "\n                               (including error messages).";
          print STDOUT "\n " . color_flags("-l, --lang [en|es]") . "            Select Perkins' interface language (\"es\" for Spanish;";
          print STDOUT "\n                               \"en\" for English).";
          print STDOUT "\n " . color_flags("-en") . "                           Set interface language to English.";
          print STDOUT "\n " . color_flags("-es") . "                           Set interface language to Spanish.";
          print STDOUT "\n " . color_flags("-d, --debug") . "                   Show debugging information.";
          print STDOUT "\n " . color_flags("-dl, --debug-log") . "              Save debugging information to a log file (same name";
          print STDOUT "\n                               as input file, but with \".log\" extension).";
          print STDOUT "\n";
          print STDOUT "\n" . color_header("META-CONFIGURATIONS:");
          print STDOUT "\n " . color_flags("-rt, --corpus") . "                 For processing corpora of running text.";
          print STDOUT "\n " . color_flags("-sl, --syl-list") . "               For creating transcriptions that permit easy processing at the";
          print STDOUT "\n                               syllable level.";
          print STDOUT "\n " . color_flags("-g2p") . "                          For generating a Grapheme-to-Phoneme (G2P) dictionary, compatible";
          print STDOUT "\n                               with tools such as the Montreal Forced Aligner (MFA).";
          print STDOUT "\n " . color_flags("-vrt") . "                          For processing verticalized text (one word per line of input). Can't";
          print STDOUT "\n                               perform all analyses (e.g. expanding abbreviations).";
          print STDOUT "\n " . color_flags("-wl, --word-list") . "              For processing word lists (syllabifies at word level, not sentence level).";
          print STDOUT "\n " . color_flags("-csc, --coscach") . "               For processing Coscach transcriptions.";
          print STDOUT "\n";
          print STDOUT "\n" . color_header("TRANSCRIPTION MODE OPTIONS:");
          print STDOUT "\n " . color_flags("-f=, --format=MODE") . "            Specify the transcription mode or format. NOT case-sensitive.";
          print STDOUT "\n                               The possible formats are:";
          print STDOUT "\n";
          print STDOUT "\n                                   PH        (phonemic transcription)";
          print STDOUT "\n                                   CV        (consonant/vowel transcription)";
          print STDOUT "\n                                   CVG       (consonant/vowel/glide transcription)";
          print STDOUT "\n                                   CVN       (cons/vowel/nasal/liquid/rhotic/glide)";
          print STDOUT "\n                                   M, MANNER (manner of articulation)";
          print STDOUT "\n                                   P, PLACE  (place of articulation)";
          print STDOUT "\n                                   V         (voicing)";
          print STDOUT "\n";
          print STDOUT "\n " . color_flags("-phon, -fon, -ph") . "              Phonemic transcription (same as --format=PH). This is";
          print STDOUT "\n                               the default mode.";
          print STDOUT "\n " . color_flags("-voicing, -voice, -v") . "          Instead of phonemes, output the voicing (voiced/voiceless)";
          print STDOUT "\n                               of each segment (same as --format=V).";
          print STDOUT "\n " . color_flags("-phonword, -pw") . "                \"Word-phonology\" format: one word per line, as";
          print STDOUT "\n                               \"Ortho<TAB>tran.scrip.tion\" (with tildes kept in the";
          print STDOUT "\n                               orthographic column, and syllable dots plus stress";
          print STDOUT "\n                               tildes in the phonological column).";
          print STDOUT "\n " . color_flags("-manner, -man") . "                Instead of phonemes, output the manner of articulation of";
          print STDOUT "\n                               each segment (same as --format=MANNER).";
          print STDOUT "\n " . color_flags("-place, -pl") . "                  Instead of phonemes, output the place of articulation of";
          print STDOUT "\n                               each segment (same as --format=PLACE).";
          print STDOUT "\n";
          print STDOUT "\n" . color_header("SPECIFIC PHONEME OPTIONS:");
          print STDOUT "\n " . color_flags("-multi, -mc") . "                   Use multi-character IPA symbols for some phonemes.";
          print STDOUT "\n " . color_flags("-och") . "                          Represent the \"ch\" phoneme as /ʧ/ regardless of any other setting";
          print STDOUT "\n " . color_flags("-tg") . "                           Treat /tr/ as a single phoneme - use ligature (adding -mc) or";
          print STDOUT "\n                               represent it as voiceless retroflex fricative /ʂ/ (adding -nomc).";
          print STDOUT "\n " . color_flags("-yf") . "                           Represent the \"ye\" phoneme as a fricative /ʝ/.";
          print STDOUT "\n " . color_flags("-ya") . "                           Represent the \"ye\" phoneme as an affricate /d͡ʒ/.";
          print STDOUT "\n " . color_flags("-oye") . "                          Represent the \"ye\" phoneme as /ʝ/ regardless of any other setting";
          print STDOUT "\n " . color_flags("-ar") . "                           Use the \"retracted\" diacritic in some affricates (e.g. t̠͡ʃ).";
          print STDOUT "\n " . color_flags("-lig") . "                          Use ligatures in /t͡ʃ/ and /d͡ʒ/. \'-nolig\' prevents their use.";
          print STDOUT "\n                               Must be used in conjunctin with the \'-mc\' switch!";
          print STDOUT "\n " . color_flags("-dd, --dent") . "                   Use dental diacritic with /t/ and /d/. Must use with \'-mc\'!";
          print STDOUT "\n " . color_flags("-aeg") . "                          Add epenthetic [g] before /w/ (e.g. <hueco> > [gweko].";
          print STDOUT "\n";
          print STDOUT "\n" . color_header("CH SHORTCUTS:");
          print STDOUT "\n " . color_flags("-chsc") . "                         CH shortcut: single character, e.g. ʧ. Overrides -mc/-ar/-lig";
          print STDOUT "\n                               for this phoneme only.";
          print STDOUT "\n " . color_flags("-chrl") . "                         CH shortcut: retracted, with ligature, e.g. t̠͡ʃ.";
          print STDOUT "\n " . color_flags("-chrnl") . "                        CH shortcut: retracted, no ligature, e.g. t̠ʃ.";
          print STDOUT "\n " . color_flags("-chnrl") . "                        CH shortcut: not retracted, with ligature, e.g. t͡ʃ.";
          print STDOUT "\n " . color_flags("-chnrnl") . "                       CH shortcut: not retracted, no ligature, e.g. tʃ.";
          print STDOUT "\n";
          print STDOUT "\n" . color_header("GLIDE OPTIONS:");
          print STDOUT "\n " . color_flags("-gd, --glides-dia") . "             Represent glides as vowel + the \"non-syllabic\" diacritic.";
          print STDOUT "\n " . color_flags("-nogd, --noglides-dia") . "         Represent glides as  /j/ and /w/.";
          print STDOUT "\n " . color_flags("-wv") . "                           Represent wau as u + the \"non-syllabic\" diacritic.";
          print STDOUT "\n " . color_flags("-yv") . "                           Represent yod as i + the \"non-syllabic\" diacritic.";
          print STDOUT "\n";
          print STDOUT "\n" . color_header("STRESS OPTIONS:");
          print STDOUT "\n " . color_flags("-st") . "                           Mark stress with tilde over vowel rather than IPA apostrophe.";
          print STDOUT "\n " . color_flags("-oa") . "                           Mark stress with orthographic apostrophe, rather than the IPA one.";
          print STDOUT "\n";
          print STDOUT "\n" . color_header("SYLLABIFICATION OPTIONS:");
          print STDOUT "\n " . color_flags("-sd, --syl-dots") . "               Represent syllable divisions with periods.";
          print STDOUT "\n " . color_flags("-ss, --syl-spaces") . "             Represent syllable divisions with spaces.";
          print STDOUT "\n " . color_flags("-nosd") . "                         Do not separate syllables nor mark them in any way.";
          print STDOUT "\n " . color_flags("-sbu") . "                          Syllabify by utterance/sentence, not by word (\"los";
          print STDOUT "\n                               hombres\" becomes /lo.som.bres/ instead of /los om.bres/).";
          print STDOUT "\n " . color_flags("-nosbu") . "                        Syllabify by word, not by utterance/sentence (\"los";
          print STDOUT "\n                               hombres\" > /los om.bres/ instead of /lo.som.bres/).";
          print STDOUT "\n " . color_flags("-sas, --split-syls") . "            Put a newline at each syllable break (one syllable per line).";
          print STDOUT "\n";
          print STDOUT "\n" . color_header("PAUSE AND GROUP OPTIONS:");
          print STDOUT "\n " . color_flags("-ip, --ipa-pauses") . "             Represent pauses with the IPA symbols | and  ||";
          print STDOUT "\n " . color_flags("-lp2") . "                          Represent long pauses with two short pause bars (||) instead of one";
          print STDOUT "\n                               long pause double bar (‖), which Praat can't display.";
          print STDOUT "\n " . color_flags("-cmp") . "                          Treat commas as pauses.";
          print STDOUT "\n " . color_flags("-clp") . "                          Treat colons as pauses.";
          print STDOUT "\n " . color_flags("-scp") . "                          Treat  semicolons as pauses.";
          print STDOUT "\n " . color_flags("-snp") . "                          Treat sentence breaks as pauses.";
          print STDOUT "\n " . color_flags("-ppp") . "                          Treat paragraph breaks as pauses.";
          print STDOUT "\n " . color_flags("-elp") . "                          Treat ellipses (...) as pauses.";
          print STDOUT "\n " . color_flags("-brp") . "                          Treat brackets [] as pauses.";
          print STDOUT "\n " . color_flags("-pnp") . "                          Treat parentheses as pauses.";
          print STDOUT "\n";
          print STDOUT "\n" . color_header("NUMBER PROCESSING OPTIONS:");
          print STDOUT "\n " . color_flags("-n2w") . "                          Convert numerals into word form (\"4\" > \"cuatro\").";
          print STDOUT "\n " . color_flags("-sn=SYMBOL") . "                    Replace numerals with the symbol specified here.";
          print STDOUT "\n " . color_flags("-nyr") . "                          Treat two groups of 4 digits separated by \"-\" as a range of";
          print STDOUT "\n                               years (\"1900-2000\" > \"1900 a 2000\", not \"1900 menos 2000\").";
          print STDOUT "\n " . color_flags("-byr") . "                          Treat two groups of 1 to 4 digits separated by \"-\" as a range of";
          print STDOUT "\n                               years (\"43-103\" > \"43 a 103\", not \"43 menos 103\").";
          print STDOUT "\n " . color_flags("-ayr") . "                          Treat ALL groups of 1 to 4 digits separated by a \"-\" as ranges of years.";
          print STDOUT "\n " . color_flags("-bcy") . "                          Also process BCE years.";
          print STDOUT "\n " . color_flags("-twc, --times-colon") . "           Process times (1-2 digits, a colon, two digits) as time";
          print STDOUT "\n                               expressions (e.g. \"14:30\"). On by default.";
          print STDOUT "\n " . color_flags("-dp, --decimal-period") . "         Use the period as the decimal point (12.35) and the comma";
          print STDOUT "\n                               as the thousands separator (1,500 = one thousand five hundred).";
          print STDOUT "\n " . color_flags("-dc, --decimal-comma") . "          Use the comma as the decimal point (12,35) and the period";
          print STDOUT "\n                               as the thousands separator (1.500 = one thousand five hundred). Default.";
          print STDOUT "\n";
          print STDOUT "\n" . color_header("SUBSTITUTION OPTIONS:");
          print STDOUT "\n " . color_flags("-cur=TEXT") . "                     Replace the \"\$\" symbol with the text specified here.";
          print STDOUT "\n " . color_flags("-lo=TEXT") . "                      Replace the \"/\" symbol with the text specified here.";
          print STDOUT "\n " . color_flags("-nsm, --no-stress-marks") . "       Do not indicate stress in any way.";
          print STDOUT "\n " . color_flags("-pu") . "                           Treat URLs as linguistic items. Otherwise, they're deleted";
          print STDOUT "\n " . color_flags("-pe") . "                           Treat e-mail addresses as linguistic items. Otherwise, they're";
          print STDOUT "\n                               deleted.";
          print STDOUT "\n";
          print STDOUT "\n" . color_header("PRESENTATION OPTIONS:");
          print STDOUT "\n " . color_flags("-owl") . "                          One word per line (split at words).";
          print STDOUT "\n " . color_flags("-osl") . "                          One syllable per line (split at syllables).";
          print STDOUT "\n " . color_flags("-kp") . "                           Keep paragraph breaks. Otherwise, output will be a wall of text.";
          print STDOUT "\n " . color_flags("-kc") . "                           Eliminate common words (for testing purposes).";
          print STDOUT "\n " . color_flags("-tvd") . "                          Reduce 3+ identical consecutive vowels down to 2";
          print STDOUT "\n                               (e.g. \"nooo\" -> \"noo\"). On by default.";
          print STDOUT "\n";
          print STDOUT "\nNOTE: Most options that don't take an argument (i.e. binary options) can be reversed or negated by adding";
          print STDOUT "\n      \"no\" between the dash and the option itself (e.g. --nomulti, --nodebug, -notr, -nod).";
          print STDOUT "\n";
          print STDOUT "\n";
     }
}


#################################################################################
#                        SUBROUTINE: USAGE SECTION                              #
#################################################################################
sub print_usage {

     &print_info_header;

     binmode STDOUT, ":utf8";

     if ( $lang eq "es" ) {
          print STDOUT "                             GUÍA DE USO";
          print STDOUT "\n     Perkins ofrece gran cantidad de opciones para la transcripción.";
          print STDOUT "\n         A continuación, se presentan algunas de las más útiles";
          print STDOUT "\n";
          print STDOUT "\nNOTAS GENERALES:";
          print STDOUT "\n - Las opciones se pueden ingresar con \"-\" o \"--\", indistintamente.";
          print STDOUT "\n - El uso del símbolo \'=\' es optativo.";
          print STDOUT "\n - El orden de las opciones y de los nombres de archivos no tiene importancia.";
          print STDOUT "\n - Las opciones binarias pueden invertirse agregando \'no\' entre el guión";
          print STDOUT "\n     y la opción misma (e.g. \'-mc\' puede desactivarse con \'-nomc\').";
          print STDOUT "\n - El número de opciones que se pueden utilizar no tiene límite.";
          print STDOUT "\n - Si el nombre de un archivo contiene un espacio, debe ir entre comillas.";
          print STDOUT "\n - Si no se especifica un archivo de salida, se genera un nombre automáticamente";
          print STDOUT "\n     a partir del nombre del archivo fuente y según el modo de transcripción.";
          print STDOUT "\n";
          print STDOUT "\nSELECCIÓN DE UN MODO DE TRANSCRIPCIÓN:";
          print STDOUT "\n - El modo de la transcripción puede elegirse desde la línea de comandos de";
          print STDOUT "\n   dos maneras distintas: -f=MODO y -MODO.";
          print STDOUT "\n - Algunos modos válidos: F, CV, CVG, CVN, M, P, S. Para más información, ver abajo.";
          print STDOUT "\n";
          print STDOUT "\n                           EJEMPLOS DE USO:";
          print STDOUT "\n";
          print STDOUT "\nTEXTO FUENTE: En Concepción, se trata de aguantar la lluvia durante 5";
          print STDOUT "\n              meses del año. ¿Cachái?";
          print STDOUT "\n";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMANDO:      $progname -i=fuente.txt";
          print STDOUT "\n";
          print STDOUT "\nSALIDA:       en.kon.sep.ˈsjon | se.ˈt̪ɾa.t̪a.d̪e.a.gwan.ˈt̪aɾ.la.ˈʝu.bja.";
          print STDOUT "\n              d̪u.ˈɾan.t̪e.ˈsin.ko.ˈme.ses.d̪e.ˈla.ɲo ‖ ka.ˈt͡ʃaj";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPCIÓN:  Opciones preconfiguradas. Transcripción fonémica. Africadas con";
          print STDOUT "\n              ligadura. Yod y wau son /j/ y /w/. Apóstrofo AFI. Dentales con";
          print STDOUT "\n              diacrítico. Símbolos de múltiples caracteres (t͡ʃ). Procesamiento";
          print STDOUT "\n              a nivel de enunciado. Fonema \"ye\" es /ʝ/.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMANDO:      $progname -i=fuente.txt -at";
          print STDOUT "\n";
          print STDOUT "\nSALIDA:       en.kon.sep.sjón | se.t̪ɾá.t̪a.d̪e.a.gwan.t̪áɾ.la.ʝú.bja.d̪u.ɾán.t̪e.sín.";
          print STDOUT "\n              ko.mé.ses.d̪e.lá.ɲo ‖ ka.t͡ʃáj";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPCIÓN:  El acento tónico se señala con tildes sobre las vocales tónicas";
          print STDOUT "\n              en vez de apóstrofos AFI.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMANDO:      $progname -i=fuente.txt -ya";
          print STDOUT "\n";
          print STDOUT "\nSALIDA:       en.kon.sep.ˈsjon | se.ˈt̪ɾa.t̪a.d̪e.a.gwan.ˈt̪aɾ.la.ˈd͡ʒu.bja.d̪u.ˈɾan.";
          print STDOUT "\n              t̪e.ˈsin.ko.ˈme.ses.d̪e.ˈla.ɲo ‖ ka.ˈt͡ʃaj";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPCIÓN:  El fonema \"ye\" se representa como la africada /d͡ʒ/.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMANDO:      $progname -i=fuente.txt -ya -ar";
          print STDOUT "\n";
          print STDOUT "\nSALIDA:       en.kon.sep.ˈsjon | se.ˈt̪ɾa.t̪a.d̪e.a.gwan.ˈt̪aɾ.la.ˈd̠͡ʒu.bja.d̪u.ˈɾan.";
          print STDOUT "\n              t̪e.ˈsin.ko.ˈme.ses.d̪e.ˈla.ɲo ‖ ka.ˈt̠͡ʃaj";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPCIÓN:  Se utiliza el diacrítico \"retraído\" en la representación de las";
          print STDOUT "\n              dos africados (/d̠͡ʒ/ y /t̠͡ʃ/).";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMANDO:      $progname -i=fuente.txt -ya -tg";
          print STDOUT "\n";
          print STDOUT "\nSALIDA:       en.kon.sep.ˈsjon | se.ˈt̪͡ɾa.t̪a.d̪e.a.gwan.ˈt̪aɾ.la.ˈd͡ʒu.bja.d̪u.ˈɾan.";
          print STDOUT "\n              t̪e.ˈsin.ko.ˈme.ses.d̪e.ˈla.ɲo ‖ ka.ˈt͡ʃaj";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPCIÓN:  Se trata el grupo \"tr\" como fonema (tal como se comporta en";
          print STDOUT "\n              muchos hablantes chilenos).";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMANDO:      $progname -i=fuente.txt -ya -tg -nomc";
          print STDOUT "\n";
          print STDOUT "\nSALIDA:       en.kon.sep.ˈsjon | se.ˈʂa.ta.de.a.gwan.ˈtaɾ.la.ˈʤu.bja.du.ˈɾan.te.";
          print STDOUT "\n              ˈsin.ko.ˈme.ses.de.ˈla.ɲo ‖ ka.ˈʧaj";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPCIÓN:  Los fonemas se representan con símbolos de un solo carácter (/ʤ/; ";
          print STDOUT "\n              /ʧ/; /ʂ/ en vez de /t̪͡ɾ/), salvo los glides, que pueden configurarse";
          print STDOUT "\n              por separado con \'-gd\' y \'-nogd\'.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMANDO:      $progname -i=fuente.txt -gd";
          print STDOUT "\n";
          print STDOUT "\nSALIDA:       en.kon.sep.ˈsi̯on | se.ˈt̪ɾa.t̪a.d̪e.a.gu̯an.ˈt̪aɾ.la.ˈʝu.bi̯a.d̪u.ˈɾan.t̪e.";
          print STDOUT "\n              ˈsin.ko.ˈme.ses.d̪e.ˈla.ɲo ‖ ka.ˈt͡ʃai̯";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPCIÓN:  Representar los glides (semivocales) como vocal + diacrítco";
          print STDOUT "\n              \"no silábico\" (/i̯/, /u̯/) en vez de /j/ y /w/.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMANDO:      $progname -i=fuente.txt -nospe";
          print STDOUT "\n";
          print STDOUT "\nSALIDA:       en kon.sep.ˈsjon | se ˈt̪ɾa.t̪a d̪e a.gwan.ˈt̪aɾ la ˈʝu.bja d̪u.ˈɾan.t̪e";
          print STDOUT "\n              ˈsin.ko ˈme.ses d̪el ˈa.ɲo ‖ ka.ˈt͡ʃaj";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPCIÓN:  Silabizar a nivel de palabra, y no enunciado/oración.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMANDO:      $progname -i=fuente.txt -cv";
          print STDOUT "\n";
          print STDOUT "\nSALIDA:       VC.CVC.CVC.ˈCVVC | CV.ˈCCV.CV.CV.V.CVVC.ˈCVC.CV.ˈCV.CVV.CV.ˈCVC.";
          print STDOUT "\n              CV.ˈCVC.CV.ˈCV.CVC.CVC.ˈV.CV ‖ CV.ˈCVV";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPCIÓN:  Analizar el texto fuente en términos de consonantes y vocales.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMANDO:      $progname -i=fuente.txt -cvg";
          print STDOUT "\n";
          print STDOUT "\nSALIDA:       VC.CVC.CVC.ˈCGVC | CV.ˈCCV.CV.CV.V.CGVC.ˈCVC.CV.ˈCV.CGV.CV.ˈCVC.";
          print STDOUT "\n              CV.ˈCVC.CV.ˈCV.CVC.CVC.ˈV.CV ‖ CV.ˈCVG";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPCIÓN:  Analizar el texto fuente en términos de consonantes, vocales y";
          print STDOUT "\n              glides";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMANDO:      $progname -i=fuente.txt -cvn";
          print STDOUT "\n";
          print STDOUT "\nSALIDA:       VN.CVN.CVC.ˈCGVN | CV.ˈCRV.CV.CV.V.CGVN.ˈCVR.LV.ˈCV.CGV.CV.ˈRVN.";
          print STDOUT "\n              CV.ˈCVN.CV.ˈNV.CVC.CVL.ˈV.NV ‖ CV.ˈCVG";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPCIÓN:  Analizar el texto fuente en términos de consonantes, vocales,";
          print STDOUT "\n              glides, nasales, líquidas y róticas.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMANDO:      $progname -i=fuente.txt -m";
          print STDOUT "\n";
          print STDOUT "\nSALIDA:       VN.PVN.FVP.ˈFXVN | FV.ˈPTV.PV.PV.V.PXVN.ˈPVT.LV.ˈFV.PXV.PV.ˈTVN.";
          print STDOUT "\n              PV.ˈFVN.PV.ˈNV.FVF.PVL.ˈV.NV ‖ PV.ˈAVX";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPCIÓN:  Analizar el texto fuente en términos de los MODOS de articulación";
          print STDOUT "\n              (P=plosiva, N=nasal, R=multivibrante, T=vibrante simple,";
          print STDOUT "\n              F=fricativa, L=lateral, A=africada, X=aproximante, V=vocal).";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMANDO:      $progname -i=fuente.txt -p";
          print STDOUT "\n";
          print STDOUT "\nSALIDA:       -A.V-A.A-B.ˈAP-A | A-.ˈDA-.D-.D-.-.VW-A.ˈD-A.A-.ˈP-.BP-.D-.ˈA-A.D-.";
          print STDOUT "\n              ˈA-A.V-.ˈB-.A-A.D-A.ˈ-.P- ‖ V-.ˈT-P";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPCIÓN:  Analizar el texto fuente en términos de los PUNTOS de articulación";
          print STDOUT "\n              (B=bilabial, L=labiodental, D=dental, A=alveolar, T=postalveolar, ";
          print STDOUT "\n              P=palatal, V=velar, W=labiovelar, -=vocal).";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMANDO:      $progname -i=fuente.txt -voicing";
          print STDOUT "\n";
          print STDOUT "\nSALIDA:       SS.ASS.ASA.ˈASSS.AS.ˈASS.AS.SS.S.SSSS.ˈASS.SS.ˈSS.SSS.SS.ˈSSS.AS.ˈSAA.SA.";
          print STDOUT "\n              ˈSS.ASA.SS.ˈSS.SS.AS.ˈASS";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPCIÓN:  Analizar el texto fuente en términos de sonoridad (S=sorda,";
          print STDOUT "\n              A=sonora, incluyendo todas las vocales).";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\n";
          print STDOUT "\nSEPARADOR DECIMAL (--decimal-period / --decimal-comma):";
          print STDOUT "\n";
          print STDOUT "\nTEXTO FUENTE: El total es \$12,35.";
          print STDOUT "\n";
          print STDOUT "\nCOMANDO:      $progname -i=fuente2.txt";
          print STDOUT "\n";
          print STDOUT "\nSALIDA:       el.t̪o.ˈt̪a.les.ˈd̪o.se.ˈpe.sos.kon.ˈt̪ɾejn.ta.i.ˈsin.ko.sen.ˈt̪a.bos";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPCIÓN:  Por defecto (--decimal-comma), la coma es el separador decimal:";
          print STDOUT "\n              \"12,35\" son doce pesos con treinta y cinco centavos.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMANDO:      $progname -i=fuente2.txt --decimal-period";
          print STDOUT "\n";
          print STDOUT "\nSALIDA:       el.t̪o.ˈt̪a.les.mil.d̪os.ˈsjen.tos.ˈt̪ɾejn.ta.i.ˈsin.ko.ˈpe.sos";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPCIÓN:  Con --decimal-period, el punto pasa a ser el separador decimal";
          print STDOUT "\n              y la coma el separador de miles: \"12,35\" se lee como \"1235\",";
          print STDOUT "\n              es decir, mil doscientos treinta y cinco pesos.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\n";
          print STDOUT "\nOTRAS METACONFIGURACIONES:";
          print STDOUT "\n(Usando el mismo texto fuente original de más arriba)";
          print STDOUT "\n";
          print STDOUT "\nCOMANDO:      $progname -i=fuente.txt -g2p";
          print STDOUT "\n";
          print STDOUT "\nSALIDA:       Diccionario Grafema-a-Fonema, una entrada por palabra, con los";
          print STDOUT "\n              fonemas separados por espacios (compatible con el Montreal";
          print STDOUT "\n              Forced Aligner). Por ejemplo:";
          print STDOUT "\n";
          print STDOUT "\n                concepción\tk o n s e p s j ˈo n";
          print STDOUT "\n                aguantar\ta g w a n t ˈa ɾ";
          print STDOUT "\n                cinco\ts ˈi n k o";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMANDO:      $progname -i=fuente.txt -phonword";
          print STDOUT "\n";
          print STDOUT "\nSALIDA:       Una palabra por línea, como \"Ortográfica<TAB>tran.scrip.ción\"";
          print STDOUT "\n              (con tildes conservadas en la columna ortográfica). Por ejemplo:";
          print STDOUT "\n";
          print STDOUT "\n                concepción\tkon.sep.ˈsjon";
          print STDOUT "\n                cachái\tka.ˈt͡ʃaj";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMANDO:      $progname -i=fuente.txt -vrt";
          print STDOUT "\n";
          print STDOUT "\nSALIDA:       en kon.sep.si.ón se tɾá.ta de a.gwan.táɾ la ʝú.bja du.ɾán.te sín.ko";
          print STDOUT "\n              mé.ses del á.ɲo ka.ʧáj";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPCIÓN:  Para procesar texto verticalizado (una palabra de entrada por";
          print STDOUT "\n              línea), compatible con IMS Corpus Workbench. Silabiza a nivel";
          print STDOUT "\n              de palabra, no de enunciado.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMANDO:      $progname -i=fuente.txt -syl-list";
          print STDOUT "\n";
          print STDOUT "\nSALIDA:       en kon sep 'sjon se 'ʂa ta de a gwan 'taɾ la 'ʝu bja du 'ɾan te";
          print STDOUT "\n              'sin ko 'me ses de 'la ɲo ka 'ʧaj";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPCIÓN:  Para generar transcripciones fáciles de procesar a nivel de";
          print STDOUT "\n              sílaba: sílabas separadas por espacios, acento con apóstrofo.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMANDO:      $progname -i=fuente.txt -coscach";
          print STDOUT "\n";
          print STDOUT "\nSALIDA:       en kon sep sjón se tɾá ta de a gwan táɾ la ʝú bja du ɾán te";
          print STDOUT "\n              sín ko mé ses de lá ɲo ka tʃáj";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPCIÓN:  Configuración ajustada a las convenciones del corpus COSCACH/";
          print STDOUT "\n              CODICACH: acento con tilde, sílabas separadas por espacios.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMANDO:      $progname -i=fuente.txt -word-list";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPCIÓN:  Para procesar listas de palabras: silabiza a nivel de palabra";
          print STDOUT "\n              en vez de enunciado (útil para listas donde cada línea es una";
          print STDOUT "\n              palabra independiente, no una oración).";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\n";
          print STDOUT "\nPara más información, véase la ayuda: $progname -h";
          print STDOUT "\n\n";
     }
     else {
          print STDOUT "                                 USAGE GUIDE";
          print STDOUT "\n     Perkins provides a great many options for Spanish transcription.";
          print STDOUT "\n             Some of the more useful ones are presented here.";
          print STDOUT "\n";
          print STDOUT "\nGENERAL TIPS:";
          print STDOUT "\n - Options can be entered with either \"-\" or \"--\". The \'=\' is optional.";
          print STDOUT "\n - The order of options and filenames is irrelevant.";
          print STDOUT "\n - Most binary options can be inverted by inserting \'no\' between the hyphen";
          print STDOUT "\n     and the option itself (e.g. \'-mc\' can be deactivated with \'-nomc\').";
          print STDOUT "\n - There is no limit on the number of options that can be selected.";
          print STDOUT "\n - If a filename contains spaces or certain special characters, it must be";
          print STDOUT "\n     entered in quotation marks.";
          print STDOUT "\n - If an output file name is not specified, its name will be automatically";
          print STDOUT "\n     generated using the input file's base name and an extension that";
          print STDOUT "\n     reflects the transcription mode chosen.";
          print STDOUT "\n";
          print STDOUT "\nSELECTING A TRANSCRIPTION MODE:";
          print STDOUT "\n - The transcription mode to be used can be selected from the command line";
          print STDOUT "\n   in two different ways: -f=MODE and -MODE.";
          print STDOUT "\n - Valid transcription modes include: F, CV, CVG, CVN, M, P, S. See below for details.";
          print STDOUT "\n";
          print STDOUT "\n                             USAGE EXAMPLES:";
          print STDOUT "\n";
          print STDOUT "\nSOURCE TEXT:  En Concepción, se trata de aguantar la lluvia durante 5";
          print STDOUT "\n              meses del año. ¿Cachái?";
          print STDOUT "\n";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMMAND:      $progname -i=source.txt";
          print STDOUT "\n";
          print STDOUT "\nOUTPUT:       en.kon.sep.ˈsjon | se.ˈt̪ɾa.t̪a.d̪e.a.gwan.ˈt̪aɾ.la.ˈʝu.bja.";
          print STDOUT "\n              d̪u.ˈɾan.t̪e.ˈsin.ko.ˈme.ses.d̪e.ˈla.ɲo ‖ ka.ˈt͡ʃaj";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPTION:  Default options. Phonemic transcription. Affricates have ligature.";
          print STDOUT "\n              Yod and wau are represented as /j/ and /w/. IPA stress apostrophe.";
          print STDOUT "\n              Dentals have diacritic. Multi-character symbols (e.g. /t͡ʃ/).";
          print STDOUT "\n              Utterance-level processing. The \"ye\" phoneme is transcribed as /ʝ/.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMMAND:      $progname -i=source.txt -at";
          print STDOUT "\n";
          print STDOUT "\nOUTPUT:       en.kon.sep.sjón | se.t̪ɾá.t̪a.d̪e.a.gwan.t̪áɾ.la.ʝú.bja.d̪u.ɾán.t̪e.sín.";
          print STDOUT "\n              ko.mé.ses.d̪e.lá.ɲo ‖ ka.t͡ʃáj";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPTION:  Stress accent is marked with a tilde on the vowel instead of an";
          print STDOUT "\n              IPA apostrophe before the syllable.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMMAND:      $progname -i=source.txt -ya";
          print STDOUT "\n";
          print STDOUT "\nOUTPUT:       en.kon.sep.ˈsjon | se.ˈt̪ɾa.t̪a.d̪e.a.gwan.ˈt̪aɾ.la.ˈd͡ʒu.bja.d̪u.ˈɾan.";
          print STDOUT "\n              t̪e.ˈsin.ko.ˈme.ses.d̪e.ˈla.ɲo ‖ ka.ˈt͡ʃaj";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPTION:  The \"ye\" phoneme is transcribed as the affricate /d͡ʒ/.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMMAND:      $progname -i=source.txt -ya -ar";
          print STDOUT "\n";
          print STDOUT "\nOUTPUT:       en.kon.sep.ˈsjon | se.ˈt̪ɾa.t̪a.d̪e.a.gwan.ˈt̪aɾ.la.ˈd̠͡ʒu.bja.d̪u.ˈɾan.";
          print STDOUT "\n              t̪e.ˈsin.ko.ˈme.ses.d̪e.ˈla.ɲo ‖ ka.ˈt̠͡ʃaj";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPTION:  The \"retracted\" diacritic is used to represent the affricates";
          print STDOUT "\n              /d̠͡ʒ/ and /t̠͡ʃ/.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMMAND:      $progname -i=source.txt -ya -tg";
          print STDOUT "\n";
          print STDOUT "\nOUTPUT:       en.kon.sep.ˈsjon | se.ˈt̪͡ɾa.t̪a.d̪e.a.gwan.ˈt̪aɾ.la.ˈd͡ʒu.bja.d̪u.ˈɾan.";
          print STDOUT "\n              t̪e.ˈsin.ko.ˈme.ses.d̪e.ˈla.ɲo ‖ ka.ˈt͡ʃaj";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPTION:  The \"tr\" cluster is treated as a phoneme (which is how it behaves";
          print STDOUT "\n              in many Chilean speakers).";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMMAND:      $progname -i=source.txt -ya -tg -nomc";
          print STDOUT "\n";
          print STDOUT "\nOUTPUT:       en.kon.sep.ˈsjon | se.ˈʂa.ta.de.a.gwan.ˈtaɾ.la.ˈʤu.bja.du.ˈɾan.te.";
          print STDOUT "\n              ˈsin.ko.ˈme.ses.de.ˈla.ɲo ‖ ka.ˈʧaj";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPTION:  Phonemes are represented only with one-character symbols (/ʤ/; ";
          print STDOUT "\n              /ʧ/; /ʂ/ instead of /t̪͡ɾ/) except for glides, which may be";
          print STDOUT "\n              configured separately with the \'-gd\' and \'-nogd\' switches.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMMAND:      $progname -i=source.txt -gd";
          print STDOUT "\n";
          print STDOUT "\nOUTPUT:       en.kon.sep.ˈsi̯on | se.ˈt̪ɾa.t̪a.d̪e.a.gu̯an.ˈt̪aɾ.la.ˈʝu.bi̯a.d̪u.ˈɾan.t̪e.";
          print STDOUT "\n              ˈsin.ko.ˈme.ses.d̪e.ˈla.ɲo ‖ ka.ˈt͡ʃai̯";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPTION:  Transcribe glides as vowel + \"non-syllabic\ diacritic\" (/i̯/, /u̯/)";
          print STDOUT "\n              instead of /j/ and /w/.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMMAND:      $progname -i=source.txt -nospe";
          print STDOUT "\n";
          print STDOUT "\nOUTPUT:       en kon.sep.ˈsjon | se ˈt̪ɾa.t̪a d̪e a.gwan.ˈt̪aɾ la ˈʝu.bja d̪u.ˈɾan.t̪e";
          print STDOUT "\n              ˈsin.ko ˈme.ses d̪el ˈa.ɲo ‖ ka.ˈt͡ʃaj";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPTION:  Syllabify at word-level rather than utterance/sentence-level.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMMAND:      $progname -i=source.txt -cv";
          print STDOUT "\n";
          print STDOUT "\nOUTPUT:       VC.CVC.CVC.ˈCVVC | CV.ˈCCV.CV.CV.V.CVVC.ˈCVC.CV.ˈCV.CVV.CV.ˈCVC.";
          print STDOUT "\n              CV.ˈCVC.CV.ˈCV.CVC.CVC.ˈV.CV ‖ CV.ˈCVV";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPTION:  Analyze input in terms of consonant/vowel.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMMAND:      $progname -i=source.txt -cvg";
          print STDOUT "\n";
          print STDOUT "\nOUTPUT:       VC.CVC.CVC.ˈCGVC | CV.ˈCCV.CV.CV.V.CGVC.ˈCVC.CV.ˈCV.CGV.CV.ˈCVC.";
          print STDOUT "\n              CV.ˈCVC.CV.ˈCV.CVC.CVC.ˈV.CV ‖ CV.ˈCVG";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPTION:  Analyze input in terms of consonant/vowel/glide.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMMAND:      $progname -i=source.txt -cvn";
          print STDOUT "\n";
          print STDOUT "\nOUTPUT:       VN.CVN.CVC.ˈCGVN | CV.ˈCRV.CV.CV.V.CGVN.ˈCVR.LV.ˈCV.CGV.CV.ˈRVN.";
          print STDOUT "\n              CV.ˈCVN.CV.ˈNV.CVC.CVL.ˈV.NV ‖ CV.ˈCVG";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPTION:  Analyze input in terms of consonant/vowel/glide/nasal/liquid/rhotic.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMMAND:      $progname -i=source.txt -m";
          print STDOUT "\n";
          print STDOUT "\nOUTPUT:       VN.PVN.FVP.ˈFXVN | FV.ˈPTV.PV.PV.V.PXVN.ˈPVT.LV.ˈFV.PXV.PV.ˈTVN.";
          print STDOUT "\n              PV.ˈFVN.PV.ˈNV.FVF.PVL.ˈV.NV ‖ PV.ˈAVX";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPTION:  Analyze input in terms of MANNERS of articulation.";
          print STDOUT "\n              (P=plosive, N=nasal, R=trill, T=tap/flap, F=fricative, L=lateral,";
          print STDOUT "\n              A=affricate, X=approximant, V=vowel).";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMMAND:      $progname -i=source.txt -p";
          print STDOUT "\n";
          print STDOUT "\nOUTPUT:       -A.V-A.A-B.ˈAP-A | A-.ˈDA-.D-.D-.-.VW-A.ˈD-A.A-.ˈP-.BP-.D-.ˈA-A.D-.";
          print STDOUT "\n              ˈA-A.V-.ˈB-.A-A.D-A.ˈ-.P- ‖ V-.ˈT-P";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPTION:  Analyze input in terms of PLACES of articulation.";
          print STDOUT "\n              (B=bilabial, L=labiodental, D=dental, A=alveolar, T=post-alveolar, ";
          print STDOUT "\n              P=palatal, V=velar, W=labiovelar, -=vowel).";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMMAND:      $progname -i=source.txt -voicing";
          print STDOUT "\n";
          print STDOUT "\nOUTPUT:       SS.ASS.ASA.ˈASSS.AS.ˈASS.AS.SS.S.SSSS.ˈASS.SS.ˈSS.SSS.SS.ˈSSS.AS.ˈSAA.SA.";
          print STDOUT "\n              ˈSS.ASA.SS.ˈSS.SS.AS.ˈASS";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPTION:  Analyze input in terms of voicing (S=voiceless, A=voiced,";
          print STDOUT "\n              including all vowels).";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\n";
          print STDOUT "\nDECIMAL SEPARATOR (--decimal-period / --decimal-comma):";
          print STDOUT "\n";
          print STDOUT "\nSOURCE TEXT:  El total es \$12,35.";
          print STDOUT "\n";
          print STDOUT "\nCOMMAND:      $progname -i=source2.txt";
          print STDOUT "\n";
          print STDOUT "\nOUTPUT:       el.t̪o.ˈt̪a.les.ˈd̪o.se.ˈpe.sos.kon.ˈt̪ɾejn.ta.i.ˈsin.ko.sen.ˈt̪a.bos";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPTION:  By default (--decimal-comma), the comma is the decimal";
          print STDOUT "\n              separator: \"12,35\" is twelve pesos and thirty-five cents.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMMAND:      $progname -i=source2.txt --decimal-period";
          print STDOUT "\n";
          print STDOUT "\nOUTPUT:       el.t̪o.ˈt̪a.les.mil.d̪os.ˈsjen.tos.ˈt̪ɾejn.ta.i.ˈsin.ko.ˈpe.sos";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPTION:  With --decimal-period, the period becomes the decimal";
          print STDOUT "\n              separator and the comma the thousands separator: \"12,35\" is";
          print STDOUT "\n              read as \"1235\", i.e. one thousand two hundred thirty-five pesos.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\n";
          print STDOUT "\nOTHER META-CONFIGURATIONS:";
          print STDOUT "\n(Using the same original source text from above)";
          print STDOUT "\n";
          print STDOUT "\nCOMMAND:      $progname -i=source.txt -g2p";
          print STDOUT "\n";
          print STDOUT "\nOUTPUT:       Grapheme-to-Phoneme dictionary, one entry per word, with";
          print STDOUT "\n              phonemes space-separated (compatible with the Montreal Forced";
          print STDOUT "\n              Aligner). For example:";
          print STDOUT "\n";
          print STDOUT "\n                concepción\tk o n s e p s j ˈo n";
          print STDOUT "\n                aguantar\ta g w a n t ˈa ɾ";
          print STDOUT "\n                cinco\ts ˈi n k o";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMMAND:      $progname -i=source.txt -phonword";
          print STDOUT "\n";
          print STDOUT "\nOUTPUT:       One word per line, as \"Ortho<TAB>tran.scrip.tion\" (with tildes";
          print STDOUT "\n              kept in the orthographic column). For example:";
          print STDOUT "\n";
          print STDOUT "\n                concepción\tkon.sep.ˈsjon";
          print STDOUT "\n                cachái\tka.ˈt͡ʃaj";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMMAND:      $progname -i=source.txt -vrt";
          print STDOUT "\n";
          print STDOUT "\nOUTPUT:       en kon.sep.si.ón se tɾá.ta de a.gwan.táɾ la ʝú.bja du.ɾán.te sín.ko";
          print STDOUT "\n              mé.ses del á.ɲo ka.ʧáj";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPTION:  For processing verticalized text (one input word per line),";
          print STDOUT "\n              compatible with the IMS Corpus Workbench. Syllabifies at the";
          print STDOUT "\n              word level, not the utterance level.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMMAND:      $progname -i=source.txt -syl-list";
          print STDOUT "\n";
          print STDOUT "\nOUTPUT:       en kon sep 'sjon se 'ʂa ta de a gwan 'taɾ la 'ʝu bja du 'ɾan te";
          print STDOUT "\n              'sin ko 'me ses de 'la ɲo ka 'ʧaj";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPTION:  For generating transcriptions that are easy to process at";
          print STDOUT "\n              the syllable level: syllables space-separated, stress marked";
          print STDOUT "\n              with an apostrophe.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMMAND:      $progname -i=source.txt -coscach";
          print STDOUT "\n";
          print STDOUT "\nOUTPUT:       en kon sep sjón se tɾá ta de a gwan táɾ la ʝú bja du ɾán te";
          print STDOUT "\n              sín ko mé ses de lá ɲo ka tʃáj";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPTION:  Configuration tuned to the COSCACH/CODICACH corpus";
          print STDOUT "\n              conventions: stress marked with a tilde, syllables";
          print STDOUT "\n              space-separated.";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\nCOMMAND:      $progname -i=source.txt -word-list";
          print STDOUT "\n";
          print STDOUT "\nDESCRIPTION:  For processing word lists: syllabifies at the word level";
          print STDOUT "\n              rather than the utterance level (useful for lists where each";
          print STDOUT "\n              line is an independent word, not a sentence).";
          print STDOUT "\n-------------------------------------------------------------------------------";
          print STDOUT "\n";
          print STDOUT "\nFor more information, see program help: $progname -h";
          print STDOUT "\n\n";

     }
}

############################################################################
# SUBROUTINE: OPEN INPUT FILE, CREATE LOG FILE                             #
############################################################################
sub open_input_and_log_files {

     if ( $lang eq "es" ) {
          $user_message = "ERROR: No fue posible abrir el archivo de input \'$input_filename\'. Probablemente tipeaste mal su nombre.";
     }
     else {
          $user_message = "ERROR: The input file \'$input_filename\' could not be opened. Make sure you typed its name correctly.";
     }

     # Open INPUT FILE or die     # Input must be ISO-8859-1               #
     open( INPUTFILE, '<:encoding(UTF-8)', "$input_filename" )
       || die "\n$user_message\n\n";

     # Extract BASENAME of input file name                                 #
     $in_basename = $input_filename;
     $in_basename =~ s/(.+)\.(.+)/$1/;

     # Create DEBUG LOG file if required, now that we have the info to name it #
     if ( $debug_to_logfile == 1 ) {
          open( LOGFILE, '>:encoding(UTF-8)', "$in_basename.log" );
     }
}

############################################################################
#                                                                          #
# SUBROUTINE: PRINT DEBUG LOG HEADER, STATUS MESSAGES, CLI INFO AND        #
#             CFG FILE STATUS MESSAGES                                     #
#                                                                          #
############################################################################
sub print_header_CLI_status_msgs {

     # Print debugging log header                                               #
     if ( ( $debug == 1 ) && ( $debug_to_logfile == 1 ) ) {

          &print_info_header;

          print LOGFILE "\n|                        Perkins v$version                              |";

          print LOGFILE "\n|                          DEBUGGING LOG                               |";
          print LOGFILE "\n|                    $current_time                          |";
          print LOGFILE "\n ---------------------------------------------------------------------- ";
     }

     # Print status messages, either to console or log file.                    #
     if ( $debug == 1 ) {
          if ( $debug_to_logfile == 0 ) {
               print STDOUT "\nDEBUG CLI: \$input_filename =\t\t\t$input_filename";
               print STDOUT "\nDEBUG CLI: CLI-provided \$output_filename =\t$output_filename";
               print STDOUT "\nDEBUG CLI: CLI-provided \$output_format =\t$output_format";
          }
          if ( $debug_to_logfile == 1 ) {
               print LOGFILE "\nDEBUG CLI: \$input_filename\t\t\t\t=\t$input_filename";
               print LOGFILE "\nDEBUG CLI: CLI-provided \$output_filename\t=\t$output_filename";
               print LOGFILE "\nDEBUG CLI: CLI-provided \$output_format\t\t=\t$output_format";
          }
     }

     # Print info about CLI OPTIONS if desired, either to STDOUT or debug log   #
     if ( $debug == 1 ) {
          if ( $debug_to_logfile == 0 ) {
               print STDOUT "\nDEBUG CLI: \$in_basename =\t\t\t$in_basename";
          }
          elsif ( $debug_to_logfile == 1 ) {
               print LOGFILE "\nDEBUG CLI: \$in_basename\t\t\t\t\t=\t$in_basename";
          }
     }

     # Notify user that config file was read, either on STDOUT or in LOG FILE   #
     if ( $config_file_present == 1 ) {
          if ( ( $debug == 1 ) && ( $debug_to_logfile == 0 ) ) {
               print STDOUT "\n\n*** Configuration file ($config_filename) successfully read. ***\n\n";
          }
          elsif ( ( $debug == 1 ) && ( $debug_to_logfile == 1 ) ) {
               print LOGFILE "\n\n*** Configuration file ($config_filename) successfully read. ***\n";
          }
     }
}    # END SUBROUTINE: PRINT DEBUG LOG HEADER, STATUS MESSAGES, CLI INFO, ETC.

####################################################################################
# SUBROUTINE: ASSIGN DEFAULT OUTPUT FILENAME IF NECESSARY                          #
# V2: For ease of opening, this now postpends ".txt" to the old filenames (e.g.    #
#     "testfile.phnm" is now "testfile.phnm.txt". You might want to assign         #
#     different filetypes and icons to each of these to facilitate working w/ them #
####################################################################################
sub assign_default_output_filename {

     # If format is VRT, append ".vrt" to BASENAME. This does NOT get ".txt" at the end
     # because a lot of software expects the ".vrt" extension"
     if ( $vrt_format == 1 ) {
          $in_basename     = "$in_basename.vrt";
          $output_filename = "$in_basename";
     }
     elsif ( $output_CV == 1 ) {
               $output_filename = "$in_basename.CV.txt";
          }
     elsif ( $output_CVG == 1 ) {
          $output_filename = "$in_basename.CVG.txt";
     }
     elsif ( $output_CVNLRG == 1 ) {
          $output_filename = "$in_basename.CVNLRG.txt";
     }
     elsif ( $output_manner == 1 ) {
          $output_filename = "$in_basename.MANNER.txt";
     }
     elsif ( $output_place == 1 ) {
          $output_filename = "$in_basename.PLACE.txt";
     }
     elsif ( $voicing == 1 ) {
          $output_filename = "$in_basename.VOICING.txt";
     }
     elsif ( $g2p_format == 1 ) {
          $output_filename = "$in_basename.G2P";
     }
     elsif ( $phonword_format == 1 ) {
          $output_filename = "$in_basename.PHONWORD";
     }
     elsif ( $syllable_list == 1 ) {
          $output_filename = "$in_basename.SYLLIST";
     }
     elsif ( $coscach_mode == 1 ) {
          $output_filename = "$in_basename.COSCACH";
     }
     elsif ( $corpus_running_text == 1 ) {
          $output_filename = "$in_basename.RUNTXT";
     }
     elsif ( $output_PHON == 1 ) {
          $output_filename = "$in_basename.PHNM.txt";
     }
     else {
          $output_filename = "$in_basename.PHNM.txt";
     }
}

     if ( $debug == 1 ) {
          if ( $debug_to_logfile == 0 ) {
               print STDOUT "\nDEBUG CLI: Default assigned OUTPUT filename =\t$output_filename";      # NOTE DEBUG IF REQUESTED
          }
          elsif ( $debug_to_logfile == 1 ) {
               print LOGFILE "\nDEBUG CLI: Default assigned OUTPUT filename\t=\t$output_filename";    # NOTE DEBUG DEBUGGING IF REQUESTED
          }
     }


############################################################################
#            SUBROUTINE: OPEN OUTPUT FILE - Output will be UTF-8           #
############################################################################
sub open_output_file {
     open( OUTPUTFILE, '>:encoding(UTF-8)', "$output_filename" );

     # Print any debug info that may be required
     if ( $debug == 1 ) {
          if ( $debug_to_logfile == 0 ) {
               print STDOUT "\nDEBUG CLI: Will use \$output_filename = \t\t$output_filename";       # NOTE IF DEBUGING REQUESTED
          }
          elsif ( $debug_to_logfile == 1 ) {
               print LOGFILE "\nDEBUG CLI: Will use \$output_filename\t\t=\t$output_filename\n";    # NOTE IF DEBUGGING REQUESTED
          }
     }
}

############################################################################
#               SUBROUTINE: PROCESS COMMAND LINE OPTIONS                   #
# If there are any, they supersede both the defaults and the .ini file     #
############################################################################
sub process_command_line_options {

     # Clear all default and config-file output format variables if any was given on the CLI #
     if ( $output_format ne "" ) {
          $output_CV     = 0;
          $output_CVG    = 0;
          $output_CVNLRG = 0;
          $output_manner = 0;
          $output_place  = 0;
          $voicing       = 0;
          $output_PHON   = 0;
     }

     # Set OUTPUT FORMAT (CV, CVG, etc.) VARIABLE, IF APPROPRIATE
     if ( $output_format eq "cv" ) {
          $output_CV = 1;
     }
     elsif ( $output_format eq "cvg" ) {
          $output_CVG = 1;
     }
     elsif ( ( $output_format eq "cvn" )
          || ( $output_format eq "cvnlrg" ) )
     {
          $output_CVNLRG = 1;
     }
     elsif ( ( $output_format eq "manner" )
          || ( $output_format eq "man" )
          || ( $output_format eq "modo" )
          || ( $output_format eq "m" ) )
     {
          $output_manner = 1;
     }
     elsif ( ( $output_format eq "place" )
          || ( $output_format eq "pl" )
          || ( $output_format eq "punto" )
          || ( $output_format eq "p" ) )
     {
          $output_place = 1;
     }
     elsif ( ( $output_format eq "voicing" )
          || ( $output_format eq "voice" )
          || ( $output_format eq "v" )
          || ( $output_format eq "sonoridad" )
          || ( $output_format eq "son" )
          || ( $output_format eq "s" ) )
     {
          $voicing = 1;
     }

     elsif ( ( $output_format eq "phon" )
          || ( $output_format eq "fon" )
          || ( $output_format eq "ph" )
          || ( $output_format eq "f" )
          || ( $output_format eq "IPA" )
          || ( $output_format eq "AFI" ) )
     {
          $output_PHON = 1;
     }

     if ( $debug == 1 ) {
          &print_cli_debug_info;
     }

}    #END SUBROUTINE

############################################################################
#                                                                          #
#                     SUBROUTINE: PRINT CLI DEBUGGING INFO                 #
#                                                                          #
############################################################################
sub print_cli_debug_info {

     our $debug_message = qq(

DEBUG CLI: USER OPTIONS - PROGRAM BEHAVIOR
DEBUG CLI: \$debug                     =    $debug
DEBUG CLI: \$debug_syllab_sub          =    $debug_syllab_sub
DEBUG CLI: \$debug_to_logfile          =    $debug_to_logfile
DEBUG CLI: \$silent_mode               =    $silent_mode
DEBUG CLI: \$batch_mode                =    $batch_mode
DEBUG CLI: \$use_config_file           =    $use_config_file

DEBUG CLI: META OPTIONS - FOR SPECIFIC COMBINATIONS OF VARIABLES
DEBUG CLI: \$coscach_mode              =    $coscach_mode
DEBUG CLI: \$corpus_running_text       =    $corpus_running_text
DEBUG CLI: \$syllable_list             =    $syllable_list
DEBUG CLI: \$vrt_format                =    $vrt_format
DEBUG CLI: \$g2p_format                =    $g2p_format

DEBUG CLI: USER OPTIONS - FORMAT OF TRANSCRIPTION OUTPUT
DEBUG CLI: \$output_PHON               =    $output_PHON
DEBUG CLI: \$output_CV                 =    $output_CV
DEBUG CLI: \$output_CVG                =    $output_CVG
DEBUG CLI: \$output_CVNLRG             =    $output_CVNLRG
DEBUG CLI: \$output_manner             =    $output_manner
DEBUG CLI: \$output_place              =    $output_place
DEBUG CLI: \$voicing                   =    $voicing

DEBUG CLI: USER OPTIONS - FOR SPECIFIC PHONEMES
DEBUG CLI: \$multichars                =    $multichars
DEBUG CLI: \$tr_is_group               =    $tr_is_group
DEBUG CLI: \$ch_dzh_retracted          =    $ch_dzh_retracted
DEBUG CLI:
DEBUG CLI: \$ye_phoneme_is_fricative   =    $ye_phoneme_is_fricative

DEBUG CLI: USER OPTIONS - FOR GLIDES
DEBUG CLI: \$glides_with_diacritics    =    $glides_with_diacritics
DEBUG CLI: \$non_syl_u_with_u_diacr    =    $non_syl_u_with_u_diacr
DEBUG CLI: \$non_syl_i_with_i_diacr    =    $non_syl_i_with_i_diacr

DEBUG CLI: USER OPTIONS - STRESS
DEBUG CLI: \$stress_using_tildes       =    $stress_using_tildes
DEBUG CLI: \$no_stress_marks           =    $no_stress_marks
DEBUG CLI: \$non_ipa_apostrophes       =    $non_ipa_apostrophes

DEBUG CLI: USER OPTIONS - SYLLABLES
DEBUG CLI: \$insert_syllable_dots      =    $insert_syllable_dots
DEBUG CLI: \$split_at_syllables        =    $split_at_syllables
DEBUG CLI: \$syllabify_by_sentence     =    $syllabify_by_sentence
DEBUG CLI: \$syllable_dots_are_spaces  =    $syllable_dots_are_spaces

DEBUG CLI: USER OPTIONS - PAUSES
DEBUG CLI: \$ipa_pause_symbols         =    $ipa_pause_symbols
DEBUG CLI: \$add_comma_pauses          =    $add_comma_pauses
DEBUG CLI: \$add_colon_pauses          =    $add_colon_pauses
DEBUG CLI: \$add_semicolon_pauses      =    $add_semicolon_pauses
DEBUG CLI: \$add_sentence_breaks       =    $add_sentence_breaks
DEBUG CLI: \$add_paragraph_breaks      =    $add_paragraph_breaks
DEBUG CLI: \$add_ellipsis_pauses       =    $add_ellipsis_pauses
DEBUG CLI: \$add_bracket_pauses        =    $add_bracket_pauses
DEBUG CLI: \$add_paren_pauses          =    $add_paren_pauses

DEBUG CLI: USER OPTIONS - NUMBERS
DEBUG CLI: \$numerals_to_words         =    $numerals_to_words
DEBUG CLI: \$num_symbol                =    $num_symbol
DEBUG CLI: \$narrow_year_ranges        =    $narrow_year_ranges
DEBUG CLI: \$broad_year_ranges         =    $broad_year_ranges
DEBUG CLI: \$bc_dates_included         =    $bc_dates_included
DEBUG CLI: \$all_year_ranges           =    $all_year_ranges
DEBUG CLI: \$times_with_colon          =    $times_with_colon

DEBUG CLI: USER OPTIONS - ODD CHARACTERS
DEBUG CLI: \$fix_umlauts               =    $fix_umlauts
DEBUG CLI: \$fix_grave_accents         =    $fix_grave_accents
DEBUG CLI: \$fix_circumflexes          =    $fix_circumflexes
DEBUG CLI: \$fix_nasal_tildes          =    $fix_nasal_tildes

DEBUG CLI: USER OPTIONS - SUBSTITUTIONS
DEBUG CLI: \$moneda                    =    $moneda
DEBUG CLI: \$slash                     =    $slash
DEBUG CLI: \$process_urls              =    $process_urls
DEBUG CLI: \$process_email             =    $process_email
DEBUG CLI: \$name_for_v                =    $name_for_v
DEBUG CLI: \$keep_paragraphs           =    $keep_paragraphs
DEBUG CLI: \$vertical_output           =    $vertical_output

DEBUG CLI: OPTIONS THAT USERS MAY *NOT* CONFIGURE
DEBUG CLI: \$cleanup_output_file       =    $cleanup_output_file
DEBUG CLI: \$no_separate_cleanup_file  =    $no_separate_cleanup_file

DEBUG CLI: OUTPUT: CLEANUP OPTIONS
DEBUG CLI: \$preclean_orthographic     =    $preclean_orthographic
DEBUG CLI: \$preclean_semiphonemic     =    $preclean_semiphonemic
DEBUG CLI: \$preclean_phonemic         =    $preclean_phonemic

DEBUG CLI: MISC
DEBUG CLI: \$upper_case                =    $upper_case
DEBUG CLI: \$kill_common_words         =    $kill_common_words

DEBUG CLI: \ INTERNAL VARIABLES THAT NEED INITIALIZED - DON'T CHANGE!
DEBUG CLI: \$helpme                    =    $helpme
DEBUG CLI: \$useme                     =    $useme
DEBUG CLI: \$config_file_present       =    $config_file_present
DEBUG CLI: \$output_filename           =    $output_filename
);

     if ( $debug_to_logfile == 0 ) {
          print STDOUT $debug_message;
     }
     elsif ( $debug_to_logfile == 1 ) {
          print LOGFILE $debug_message;
     }
}


############################################################################
# SUBROUTINE: FORCIBLY SET VARIABLES THAT DEPEND ON OTHER VARIABLES        #
# This has to be done here, and not earlier, so as to receive any changed  #
# variable values from the config file or command line options.            #
############################################################################
sub forcibly_set_variables {

     # Make sure values for the two "ye" phoneme variables don't contradict each other
     if ( $ye_phoneme_is_affricate == 1 ) {
          $ye_phoneme_is_fricative = 0;
     }
     else { $ye_phoneme_is_fricative = 1 }

     #######################################################################
     #                        METACONFIGURATIONS                           #
     #######################################################################

     ###################################
     # ALLOW ONLY ONE META-CONFIGURATION
     ###################################

     # Running text for corpora
     if ( $corpus_running_text == 1 ) {
          $syllable_list = 0;
          $vrt_format    = 0;
          $word_list     = 0;
          $g2p_format    = 0;
          $coscach_mode  = 0;
     }

     # Syllable list
     if ( $syllable_list == 1 ) {
          $corpus_running_text = 0;
          $vrt_format          = 0;
          $word_list           = 0;
          $g2p_format          = 0;
          $coscach_mode        = 0;
     }

     # Vertical text (VRT) format
     if ( $vrt_format == 1 ) {
          $corpus_running_text = 0;
          $syllable_list       = 0;
          $word_list           = 0;
          $g2p_format          = 0;
          $coscach_mode        = 0;
     }

     # Word list format
     if ( $word_list == 1 ) {
          $corpus_running_text = 0;
          $syllable_list       = 0;
          $vrt_format          = 0;
          $g2p_format          = 0;
          $coscach_mode        = 0;
     }

     # COSCACH mode
     if ( $coscach_mode == 1 ) {
          $corpus_running_text = 0;
          $syllable_list       = 0;
          $vrt_format          = 0;
          $word_list           = 0;
          $g2p_format          = 0;
     }

     # CORPUS OF RUNNING TEXT SETTINGS                                     #
     # Running text, syllabified at sentence level, accent using tildes,   #
     # single character per phoneme, output is phonemic transcription      #
     if ( $corpus_running_text == 1 ) {

          #$output_PHON             = 1; # Might have to disable this
          $multichars              = 0;
          $syllabify_by_sentence   = 1;
          $tr_is_group             = 0;
          $ye_phoneme_is_fricative = 1;
          $glides_with_diacritics  = 0;    # NEEDED ???
          $stress_using_tildes     = 1;
          $non_ipa_apostrophes     = 0;
          $insert_syllable_dots    = 1;
          $ipa_pause_symbols       = 1;
          $add_comma_pauses        = 1;
          $add_colon_pauses        = 1;
          $add_semicolon_pauses    = 1;
          $add_sentence_breaks     = 1;
          $add_paragraph_breaks    = 1;
          $add_ellipsis_pauses     = 1;
          $add_bracket_pauses      = 1;
          $add_paren_pauses        = 1;
          $numerals_to_words       = 1;
          $keep_paragraphs         = 1;
          $vertical_output         = 0;
          $kill_common_words       = 0;
          $silent_mode             = 1;
          $all_year_ranges         = 1;
          $times_with_colon         = 1;
     }

     # SYLLABLE LIST SETTINGS                                                      #
     # Doesn't specify transcription format, to allow different formats to be used.#
     # Syllabified at sentence level, running text, syllables separated by SPACE,  #
     # non-IPA apostrophe used,
     if ( $syllable_list == 1 ) {
          $multichars               = 0;
          $syllabify_by_sentence    = 1;
          $tr_is_group              = 1;
          $ye_phoneme_is_fricative  = 1;
          $glides_with_diacritics   = 0;    # NEEDED ???
          $stress_using_tildes      = 0;
          $non_ipa_apostrophes      = 1;
          $insert_syllable_dots     = 1;
          $syllable_dots_are_spaces = 1;
          $ipa_pause_symbols        = 1;
          $add_comma_pauses         = 1;
          $add_colon_pauses         = 1;
          $add_semicolon_pauses     = 1;
          $add_sentence_breaks      = 1;
          $add_paragraph_breaks     = 1;
          $add_ellipsis_pauses      = 1;
          $add_bracket_pauses       = 1;
          $add_paren_pauses         = 1;
          $numerals_to_words        = 1;
          $keep_paragraphs          = 1;
          $vertical_output          = 0;
          $kill_common_words        = 0;
          $silent_mode              = 1;
          $all_year_ranges          = 1;
          $times_with_colon         = 1;
     }

     # WORD LIST SETTINGS                                                            #
     # Doesn't specify transcription format, to allow different formats to be used.  #
     # Syllabified at word level, syllables separated by dot and words by a space.   #
     # Note that pauses are indeed processed (unless user indicates otherwise).      #
     # Also, note that this metaconfig sets relatively few variables.                #
     if ( $word_list == 1 ) {
          $multichars            = 0;
          $syllabify_by_sentence = 0;

          #$tr_is_group              = 1;
          #$ye_phoneme_is_fricative  = 1;
          #$glides_with_diacritics   = 0;
          #$stress_using_tildes      = 0;
          #$non_ipa_apostrophes      = 1;
          $insert_syllable_dots     = 1;
          $syllable_dots_are_spaces = 0;
          $ipa_pause_symbols        = 1;
          $add_comma_pauses         = 1;
          $add_colon_pauses         = 1;
          $add_semicolon_pauses     = 1;
          $add_sentence_breaks      = 1;
          $add_paragraph_breaks     = 1;
          $add_ellipsis_pauses      = 1;
          $add_bracket_pauses       = 1;
          $add_paren_pauses         = 1;
          $numerals_to_words        = 1;
          $keep_paragraphs          = 1;
          $vertical_output          = 0;
          $kill_common_words        = 0;
          $silent_mode              = 1;
          $times_with_colon         = 1;
          #$all_year_ranges          = 1;
     }

     # G2P (GRAPHEME-TO-PHONEME) SETTINGS
     if ( $g2p_format == 1 ) {
          $output_PHON                  = 1;
          $output_CV                    = 0;
          $output_CVG                   = 0;
          $output_CVNLRG                = 0;
          $output_manner                = 0;
          $output_place                 = 0;
          $voicing                      = 0;
          $vrt_format                   = 0;

          $multichars                   = 1;
          $tr_is_group                  = 0;
          $ch_dzh_retracted             = 0;
          $use_ligatures                = 0;
          $ye_phoneme_is_fricative      = 1;
          $use_one_char_ch_symbol       = 0;
          $use_one_char_ye_symbol       = 1;
          $use_dental_diacr             = 0;
          $add_epenthetic_g             = 0;
          $glides_with_diacritics       = 0;

          $space_between_phonemes       = 1;
          $word_plus_phonemes           = 1;

          $no_stress_marks              = 0;
          $insert_syllable_dots         = 0;

          $ipa_pause_symbols            = 0;
          $ipa_long_pause_two_singles   = 0;
          $add_comma_pauses             = 0;
          $add_colon_pauses             = 0;
          $add_semicolon_pauses         = 0;
          $add_sentence_breaks          = 0;
          $add_paragraph_breaks         = 0;
          $add_ellipsis_pauses          = 0;
          $add_bracket_pauses           = 0;
          $add_paren_pauses             = 1;

          $fix_umlauts                  = 1;
          $fix_grave_accents            = 1;
          $fix_circumflexes             = 1;
          $fix_nasal_tildes             = 1;

          $numerals_to_words            = 1;
          $num_symbol                   = "·";
          $narrow_year_ranges           = 1;
          $broad_year_ranges            = 1;
          $bc_dates_included            = 1;
          $all_year_ranges              = 1;
          $times_with_colon             = 1;

          $moneda                       = "pésos";
          $slash                        = "eslách";
          $process_urls                 = 0;
          $process_email                = 0;
          $name_for_v                   = "becórta";

          $triple_vowels_to_double     = 1;
          $vertical_output              = 1;
     }

     # PHONWORD (PRAAT WORD/PHONWORD TIER) SETTINGS
     # Produces one word per line: orthographic form (capitalization and tildes
     # preserved) followed by a tab and the phonemic transcription with syllable
     # dots and stress markers intact.
     if ( $phonword_format == 1 ) {
          $output_PHON                  = 1;
          $output_CV                    = 0;
          $output_CVG                   = 0;
          $output_CVNLRG                = 0;
          $output_manner                = 0;
          $output_place                 = 0;
          $voicing                      = 0;

          $no_stress_marks              = 0;    # Preserve stress markers (ˈ ˌ)
          $insert_syllable_dots         = 1;    # Preserve syllable dots

          $space_between_phonemes       = 0;    # No spaces between phonemes
          $word_plus_phonemes           = 0;

          $ipa_pause_symbols            = 0;
          $ipa_long_pause_two_singles   = 0;
          $add_comma_pauses             = 0;
          $add_colon_pauses             = 0;
          $add_semicolon_pauses         = 0;
          $add_sentence_breaks          = 0;
          $add_paragraph_breaks         = 0;
          $add_ellipsis_pauses          = 0;
          $add_bracket_pauses           = 0;
          $add_paren_pauses             = 1;

          $fix_umlauts                  = 1;
          $fix_grave_accents            = 1;
          $fix_circumflexes             = 1;
          $fix_nasal_tildes             = 1;

          $numerals_to_words            = 1;
          $all_year_ranges              = 1;
          $times_with_colon             = 1;

          $triple_vowels_to_double     = 1;
          $vertical_output              = 1;    # One word per line
     }


     # VERTICAL TEXT FILE (.vrt) SETTINGS                                       #
     # Doesn't specify transcription format, to allow different ones to be used.#
     # Vertical output (one word per line), syllables NOT separated, stress     #
     # using tildes.                                                            #
     if ( $vrt_format == 1 ) {

          # $output_PHON = 1;
          $multichars               = 0;
          $syllabify_by_sentence    = 0;
          $tr_is_group              = 0;
          $ye_phoneme_is_fricative  = 1;
          $glides_with_diacritics   = 0;    # NEEDED ???
          $stress_using_tildes      = 1;
          $non_ipa_apostrophes      = 0;
          $insert_syllable_dots     = 1;
          $syllable_dots_are_spaces = 0;
          $ipa_pause_symbols        = 1;
          $add_comma_pauses         = 1;
          $add_colon_pauses         = 0;
          $add_semicolon_pauses     = 1;
          $add_sentence_breaks      = 1;
          $add_paragraph_breaks     = 1;
          $add_ellipsis_pauses      = 1;
          $add_bracket_pauses       = 1;
          $add_paren_pauses         = 1;
          $numerals_to_words        = 1;
          $keep_paragraphs          = 1;
          $vertical_output          = 1;
          $kill_common_words        = 0;
          $silent_mode              = 1;
          $all_year_ranges          = 1;
     }

     # COSCACH MODE SETTINGS
     if ( $coscach_mode == 1 ) {

          # $output_PHON = 1;
          $multichars               = 1;
          $syllabify_by_sentence    = 1;
          $tr_is_group              = 0;
          $ye_phoneme_is_fricative  = 1;
          $glides_with_diacritics   = 0;    # NEEDED ???
          $use_ligatures            = 0;
          $use_dental_diacr         = 0;
          $stress_using_tildes      = 1;
          $non_ipa_apostrophes      = 0;
          $insert_syllable_dots     = 1;
          $syllable_dots_are_spaces = 1;    # Not sure if I really like this in Coscach transcriptions
          $ipa_pause_symbols        = 1;
          $add_comma_pauses         = 1;
          $add_colon_pauses         = 1;
          $add_semicolon_pauses     = 1;
          $add_sentence_breaks      = 1;
          $add_paragraph_breaks     = 1;
          $add_ellipsis_pauses      = 1;
          $add_bracket_pauses       = 1;
          $add_paren_pauses         = 1;
          $numerals_to_words        = 1;
          $keep_paragraphs          = 1;
          $vertical_output          = 0;
          $kill_common_words        = 0;
          $silent_mode              = 0;
          $all_year_ranges          = 1;

          $ipa_long_pause_two_singles = 1;
     }

     # SPECIFIC CONFIGURATIONS

     # PROCESS NUMBER RANGES AS DATES
     if ( $all_year_ranges == 1 ) {
          $narrow_year_ranges = 1;
          $broad_year_ranges  = 1;
          $bc_dates_included  = 1;
     }

     # SET OPTIONS FOR USING NO STRESS MARKS
     if ( $no_stress_marks == 1 ) { $stress_using_tildes = 0 }

     # SET OPTIONS FOR SPLITTING AT SYLLABLES
     if ( $split_at_syllables == 1 ) {
          $vertical_output          = 1;
          $insert_syllable_dots     = 1;
          $no_stress_marks          = 1;
          $syllable_dots_are_spaces = 0;
     }

     # SET VALUES FOR MULTICHARS AND DIACRITICS IF A NON-PHONEMIC TRANSCRIPTION IS USED
     if (    $output_CV       == 1
          || $output_CVG      == 1
          || $output_CVNLRG   == 1
          || $output_manner   == 1
          || $output_place    == 1
          || $voicing         == 1 )
     {
          $multichars             = 0;
          $non_syl_u_with_u_diacr = 0;
          $non_syl_i_with_i_diacr = 0;

          $upper_case = 1;
     }

     # SET OPTIONS FOR VERTICAL OUTPUT
     if ( $vertical_output == 1 ) {
          $add_comma_pauses     = 0;
          $add_colon_pauses     = 0;
          $add_semicolon_pauses = 0;
          $add_sentence_breaks  = 0;
          $add_paragraph_breaks = 0;
          $add_ellipsis_pauses  = 0;
          $add_bracket_pauses   = 0;
          $add_paren_pauses     = 0;
     }

     # SET OPTIONS FOR SILENT MODE
     if ( $silent_mode == 1 ) {
          $batch_mode       = 1;
          $debug            = 0;
          $debug_syllab_sub = 0;
          $debug_to_logfile = 0;
     }

     # SET OPTIONS FOR GLIDES WITH/WITHOUT DIACRITICS
     if ( $glides_with_diacritics == 1 ) {
          $non_syl_u_with_u_diacr = 1;
          $non_syl_i_with_i_diacr = 1;
     }
     elsif ( $glides_with_diacritics == 0 ) {
          $non_syl_u_with_u_diacr = 0;
          $non_syl_i_with_i_diacr = 0;
     }

     # SET OPTIONS FOR NON-IPA APOSTROPHES
     if ( $non_ipa_apostrophes == 1 ) {
          $stress_using_tildes = 0;
     }

     # PREPARE FOR SENTENCE-LEVEL SYLLABIFICATION (INSTEAD OF WORD-LEVEL)
     if ( $syllabify_by_sentence == 1 ) {
          $split_at_syllables = 0;
     }

     # SET OPTIONS FOR SYLLABLES DOTS AS SPACES
     if ( $syllable_dots_are_spaces == 1 ) { $insert_syllable_dots = 1 }

     # SET REPRESENTATION OF "ch" +++++

}

############################################################################
#                             CONSOLE GREETING                             #
# This is printed in normal (non-batch/non-silent) mode, but only if the   #
# debug to log file option is OFF.                                         #
############################################################################
sub print_console_greeting {

     print STDOUT "\n";
     print STDOUT "\n===============================================================================";
     print STDOUT "\n= Perkins v$version";
     print STDOUT "\n= Scott Sadowsky · ssadowsky at g mail dot com";
     print STDOUT "\n===============================================================================";
     print STDOUT "\n";

     if   ( $lang eq "es" ) { $user_message = "El archivo a transcribir (\"$input_filename\") se leyó exitosamente..."; }
     else                   { $user_message = "The file to be transcribed (\"$input_filename\") was successfully read..."; }
     print STDOUT "\n$user_message";

     if ( $config_file_present == 1 ) {
          if   ( $lang eq "es" ) { $user_message = "El archivo de configuración (\"$config_filename\") fue procesado exitosamente..."; }
          else                   { $user_message = "The configuration file (\"$config_filename\") was successfully processed..."; }
          print STDOUT "\n$user_message\n";
     }

     if ( ( $use_config_file == 1 ) && ( $config_file_present == 0 ) ) {

          if   ( $lang eq "es" ) { $user_message = "** ADVERTENCIA: No se pudo procesar el archivo de configuración externo."; }
          else                   { $user_message = "** WARNING: The configuration file could not be processed."; }
          print STDOUT "\n$user_message";

          if   ( $lang eq "es" ) { $user_message = "**              Se utilizarán los valores preconfigurados..."; }
          else                   { $user_message = "**              The default configuration will be used..."; }
          print STDOUT "\n$user_message";
     }

     # Determine output mode label
     my $out_mode;
     if    ( $output_CV     == 1 ) { $out_mode = "CV"; }
     elsif ( $output_CVG    == 1 ) { $out_mode = "CVG"; }
     elsif ( $output_CVNLRG == 1 ) { $out_mode = "CVNLRG"; }
     elsif ( $output_manner == 1 ) { $out_mode = "Manner of articulation"; }
     elsif ( $output_place  == 1 ) { $out_mode = "Place of articulation"; }
     elsif ( $voicing       == 1 ) { $out_mode = "Voicing"; }
     else                          { $out_mode = "Phonemic (IPA)"; }

     ##########################################################################
     # SETTINGS TABLE                                                         #
     # Three-column layout: each cell holds "Label: value".                   #
     # Label column: 20 visual chars. Value: 1 char for booleans, wider for   #
     # string values (which span all 3 columns on a full-width row).          #
     # Total width: 3*(20+2+1+2)+4 = 79 chars (fits 80-column terminals).     #
     # CH shortcuts: 2-column, label col 30, total 73 chars.                  #
     # Values: green = default, yellow = from perkins.ini, orange = CLI flag. #
     # Section names print in light blue on a blank line before the rule.     #
     ##########################################################################
     {
          # vis_len: strip Unicode combining marks so IPA labels pad correctly.
          sub vis_len {
               use Unicode::Normalize 'NFD';
               my $s = NFD(shift // '');
               $s =~ s/\p{M}//g;
               return length($s);
          }

          # Geometry
          my $LC  = 20;                   # label column visual width (non-CH)
          my $CI  = $LC + 2 + 1;         # cell interior: label + ': ' + 1 value = 23
          my $div = ( '+' . '-' x ($CI + 2) ) x 3 . '+';   # 79-char divider

          my $LC2 = 30;                   # label column for CH shortcuts
          my $CI2 = $LC2 + 2 + 1;
          my $div2 = ( '+' . '-' x ($CI2 + 2) ) x 2 . '+'; # 73-char divider

          # Format a value with source-aware colour:
          #   green      = default value (unchanged from compiled-in default)
          #   bold yellow = set by perkins.ini
          #   red         = set by a command-line flag
          # Source detection:
          #   ini:  %_SET_BY_INI{var} is set in the ini reader loop
          #   CLI:  %_VAL_AFTER_CLI{var} differs from %_VAL_DEFAULT{var}
          #   default: neither of the above
          # src_color($varname): return the Term::ANSIColor colour string
          # for the source of this variable's current value.
          sub src_color {
               my $var  = shift;
               my $def  = $main::_VAL_DEFAULT{$var}   // '';
               my $acli = $main::_VAL_AFTER_CLI{$var} // '';
               return $main::_SET_BY_INI{$var}    ? 'bold yellow'
                    : ("$acli" ne "$def")          ? 'red'
                    :                                'green';
          }

          sub fmt_val {
               my ($var, $val) = @_;
               my $s = defined($val) ? "$val" : '';
               return safe_colored($s, src_color($var));
          }

          # Three-column row. @items = (label, varname, value,  x3).
          # Empty label → blank cell.
          sub row3 {
               my ($lc, @items) = @_;
               my $ci = $lc + 2 + 1;
               my $out = '';
               for my $i (0 .. 2) {
                    my $lbl = $items[$i * 3]       // '';
                    my $var = $items[$i * 3 + 1]   // '';
                    my $val = $items[$i * 3 + 2]   // '';
                    if ($lbl eq '') {
                         $out .= '| ' . ' ' x $ci . ' ';
                    } else {
                         my $pad = $lc - vis_len($lbl);
                         $pad = 0 if $pad < 0;
                         my $c = src_color($var);
                         $out .= '| ' . safe_colored($lbl, $c) . ' ' x $pad . ': ' . fmt_val($var, "$val") . ' ';
                    }
               }
               print STDOUT "\n$out|";
          }

          # Two-column row for CH shortcuts (wider labels).
          # @items = (label, varname, value,  x2).
          sub row2 {
               my ($lc, @items) = @_;
               my $ci = $lc + 2 + 1;
               my $out = '';
               for my $i (0 .. 1) {
                    my $lbl = $items[$i * 3]       // '';
                    my $var = $items[$i * 3 + 1]   // '';
                    my $val = $items[$i * 3 + 2]   // '';
                    if ($lbl eq '') {
                         $out .= '| ' . ' ' x $ci . ' ';
                    } else {
                         my $pad = $lc - vis_len($lbl);
                         $pad = 0 if $pad < 0;
                         my $c = src_color($var);
                         $out .= '| ' . safe_colored($lbl, $c) . ' ' x $pad . ': ' . fmt_val($var, "$val") . ' ';
                    }
               }
               print STDOUT "\n$out|";
          }

          # Full-width string-value row (spans all 3 columns).
          # Args: label, varname, value.
          sub row_str {
               my ($lbl, $var, $val, $div, $LC) = @_;
               my $inner = length($div) - 2;          # 77 chars
               my $lpad  = $LC - vis_len($lbl);
               $lpad = 0 if $lpad < 0;
               my $fval  = fmt_val($var, $val // '');
               # entry length for padding must use raw value (no ANSI codes)
               my $raw_entry = $lbl . ' ' x $lpad . ': ' . ($val // '');
               my $rpad  = $inner - 2 - length($raw_entry);
               $rpad = 0 if $rpad < 0;
               my $c   = src_color($var);
               print STDOUT "\n| " . safe_colored($lbl, $c) . ' ' x $lpad . ': ' . $fval . ' ' x $rpad . ' |';
          }

          # Section separator: blank line + name in cyan + divider.
          sub sect {
               my ($name, $d) = @_;
               print STDOUT "\n\n" . safe_colored($name, 'cyan') . "\n$d";
          }

          # ── Print the table ───────────────────────────────────────────────
          my $key = "Value color code: "
               . safe_colored("default", "green")      . " = green,  "
               . safe_colored("config file", "bold yellow") . " = yellow,  "
               . safe_colored("command line flags", "red") . " = red";
          print STDOUT "\n$key";
          print STDOUT "\n";
          print STDOUT "\n" . safe_colored('ACTIVE SETTINGS', 'bold cyan') . "\n$div";

          sect( 'PROGRAM BEHAVIOR', $div );
          row3( $LC,
               'Debug', 'debug', $debug,
               'Debug syllab. sub', 'debug_syllab_sub', $debug_syllab_sub,
               'Debug to logfile', 'debug_to_logfile', $debug_to_logfile );
          row3( $LC,
               'Silent mode', 'silent_mode', $silent_mode,
               'Batch mode', 'batch_mode', $batch_mode,
               '','' );
          print STDOUT "\n$div";

          sect( 'META OPTIONS', $div );
          row3( $LC,
               'Corpus/running text', 'corpus_running_text', $corpus_running_text,
               'Syllable list', 'syllable_list', $syllable_list,
               'G2P format', 'g2p_format', $g2p_format );
          row3( $LC,
               'Phonword format', 'phonword_format', $phonword_format,
               'VRT format', 'vrt_format', $vrt_format,
               '','' );
          print STDOUT "\n$div";

          sect( 'OUTPUT FORMAT', $div );
          row3( $LC,
               'Phonemic (IPA)', 'output_PHON', $output_PHON,
               'CV skeleton', 'output_CV', $output_CV,
               'CVG skeleton', 'output_CVG', $output_CVG );
          row3( $LC,
               'CVNLRG skeleton', 'output_CVNLRG', $output_CVNLRG,
               'Manner of artic.', 'output_manner', $output_manner,
               'Place of artic.', 'output_place', $output_place );
          row3( $LC,
               'Voicing', 'voicing', $voicing,
               '','' ,               '','' );
          print STDOUT "\n$div";

          sect( 'PHONEMES', $div );
          row3( $LC,
               'Multichars', 'multichars', $multichars,
               'TR is group', 'tr_is_group', $tr_is_group,
               'CH/DZH retracted', 'ch_dzh_retracted', $ch_dzh_retracted );
          row3( $LC,
               'Use ligatures', 'use_ligatures', $use_ligatures,
               '1-char CH symbol', 'use_one_char_ch_symbol', $use_one_char_ch_symbol,
               'YE = fricative', 'ye_phoneme_is_fricative', $ye_phoneme_is_fricative );
          row3( $LC,
               'YE = affricate', 'ye_phoneme_is_affricate', $ye_phoneme_is_affricate,
               '1-char YE symbol', 'use_one_char_ye_symbol', $use_one_char_ye_symbol,
               'Dental diacritics', 'use_dental_diacr', $use_dental_diacr );
          row3( $LC,
               'Epenthetic G', 'add_epenthetic_g', $add_epenthetic_g,
               '','' ,               '','' );
          print STDOUT "\n$div";

          sect( 'CH SHORTCUTS', $div2 );
          row2( $LC2,
               "CH: single char  (\x{02A7})", 'ch_is_single_char', $ch_is_single_char,
               "CH: retracted + lig  (t\x{0320}\x{0361}\x{0283})", 'ch_is_retracted_with_ligature', $ch_is_retracted_with_ligature );
          row2( $LC2,
               "CH: retracted no lig  (t\x{0320}\x{0283})", 'ch_is_retracted_no_ligature', $ch_is_retracted_no_ligature,
               "CH: not retracted + lig  (t\x{0361}\x{0283})", 'ch_not_retracted_with_ligature', $ch_not_retracted_with_ligature );
          row2( $LC2,
               "CH: not retracted no lig  (t\x{0283})", 'ch_not_retracted_no_ligature', $ch_not_retracted_no_ligature,
               '','' );
          print STDOUT "\n$div2";

          sect( 'GLIDES', $div );
          row3( $LC,
               'Glides w/ diacritics', 'glides_with_diacritics', $glides_with_diacritics,
               'Non-syl /u/ diacr', 'non_syl_u_with_u_diacr', $non_syl_u_with_u_diacr,
               'Non-syl /i/ diacr', 'non_syl_i_with_i_diacr', $non_syl_i_with_i_diacr );
          print STDOUT "\n$div";

          sect( 'STRESS', $div );
          row3( $LC,
               'Stress with tildes', 'stress_using_tildes', $stress_using_tildes,
               'No stress marks', 'no_stress_marks', $no_stress_marks,
               'Non-IPA apostrophes', 'non_ipa_apostrophes', $non_ipa_apostrophes );
          print STDOUT "\n$div";

          sect( 'SYLLABLES', $div );
          row3( $LC,
               'Syllable dots', 'insert_syllable_dots', $insert_syllable_dots,
               'Split at syllables', 'split_at_syllables', $split_at_syllables,
               'Syllabify by sent.', 'syllabify_by_sentence', $syllabify_by_sentence );
          row3( $LC,
               'Dots are spaces', 'syllable_dots_are_spaces', $syllable_dots_are_spaces,
               '','' ,                '','' );
          print STDOUT "\n$div";

          sect( 'PAUSES', $div );
          row3( $LC,
               'IPA pause symbols', 'ipa_pause_symbols', $ipa_pause_symbols,
               'Long pause = ||', 'ipa_long_pause_two_singles', $ipa_long_pause_two_singles,
               'Comma pauses', 'add_comma_pauses', $add_comma_pauses );
          row3( $LC,
               'Colon pauses', 'add_colon_pauses', $add_colon_pauses,
               'Semicolon pauses', 'add_semicolon_pauses', $add_semicolon_pauses,
               'Sentence breaks', 'add_sentence_breaks', $add_sentence_breaks );
          row3( $LC,
               'Paragraph breaks', 'add_paragraph_breaks', $add_paragraph_breaks,
               'Ellipsis pauses', 'add_ellipsis_pauses', $add_ellipsis_pauses,
               'Bracket pauses', 'add_bracket_pauses', $add_bracket_pauses );
          row3( $LC,
               'Paren pauses', 'add_paren_pauses', $add_paren_pauses,
               '','' ,                '','' );
          print STDOUT "\n$div";

          sect( 'NUMBERS', $div );
          row3( $LC,
               'Numerals to words', 'numerals_to_words', $numerals_to_words,
               'Narrow year ranges', 'narrow_year_ranges', $narrow_year_ranges,
               'Broad year ranges', 'broad_year_ranges', $broad_year_ranges );
          row3( $LC,
               'BC dates included', 'bc_dates_included', $bc_dates_included,
               'All year ranges', 'all_year_ranges', $all_year_ranges,
               'Times with colon', 'times_with_colon', $times_with_colon );
          row_str( 'Numeral symbol', 'num_symbol', $num_symbol, $div, $LC );
          row_str( 'Decimal symbol', 'decimal_symbol', $decimal_symbol, $div, $LC );
          print STDOUT "\n$div";

          sect( 'ODD CHARACTERS', $div );
          row3( $LC,
               'Fix umlauts', 'fix_umlauts', $fix_umlauts,
               'Fix grave accents', 'fix_grave_accents', $fix_grave_accents,
               'Fix circumflexes', 'fix_circumflexes', $fix_circumflexes );
          row3( $LC,
               'Fix nasal tildes', 'fix_nasal_tildes', $fix_nasal_tildes,
               '','' ,                '','' );
          print STDOUT "\n$div";

          sect( 'SUBSTITUTIONS & MISC', $div );
          row3( $LC,
               'Process URLs', 'process_urls', $process_urls,
               'Process email', 'process_email', $process_email,
               'Keep paragraphs', 'keep_paragraphs', $keep_paragraphs );
          row3( $LC,
               'Vertical output', 'vertical_output', $vertical_output,
               'Triple vwls→double', 'triple_vowels_to_double', $triple_vowels_to_double,
               '','' );
          row_str( 'Currency symbol', 'moneda', $moneda, $div, $LC );
          row_str( 'RUT K mode', 'rut_k_mode', $rut_k_mode, $div, $LC );
          row_str( 'Slash', 'slash', $slash, $div, $LC );
          row_str( 'Name for V', 'name_for_v', $name_for_v, $div, $LC );
          print STDOUT "\n$div";

          sect( 'ADVANCED', $div );
          row3( $LC,
               'Upper case', 'upper_case', $upper_case,
               'Kill common words', 'kill_common_words', $kill_common_words,
               'Space betw. phonemes', 'space_between_phonemes', $space_between_phonemes );
          row3( $LC,
               'Word plus phonemes', 'word_plus_phonemes', $word_plus_phonemes,
               '','' ,                '','' );
          print STDOUT "\n$div";

          print STDOUT "\n";
     }

     if   ( $lang eq "es" ) { $user_message = "PROCESANDO..."; }
     else                   { $user_message = "PROCESSING..."; }
     print STDOUT "\n$user_message\n";

}    # END SUBROUTINE: PRINT CONSOLE GREETING

#################################################################################
#                                                                               #
# SUBROUTINE: .VRT DISASTROUS CHARACTER ELIMINATION                            #
# Kill odd characters that are used internally by Perkins, before anything      #
# else is replaced.                                                             #
#                                                                               #
#################################################################################
sub kill_disasterous_vrt_chars {
     my $current_item = $_[0];

     $current_item =~ s/¬/ø/g;
     $current_item =~ s/\|/ø/g;
     $current_item =~ s/¯/ø/g;
     $current_item =~ s/§/ø/g;
     $current_item =~ s/©/ø/g;
     $current_item =~ s/®/ø/g;
     $current_item =~ s/¥/ø/g;
     $current_item =~ s/\*/ø/g;

     return ($current_item);
}

#################################################################################
#                                                                               #
#   SUBROUTINE: PROCESS INTERNET STUFF                                          #
#   Note: Doesn't process certain characters in URLs (% ! = & ? # etc.), but    #
#         simply eliminates them.                                               #
#                                                                               #
#################################################################################
sub process_internet_stuff {

     my $current_item = $_[0];

     # <-- NEW IN 393
     # EXPERIMENTAL!
     my @current_segments = split /\s/, $current_item;

     foreach my $current_segment (@current_segments) {

          # END NEW IN 393 -->

          #print "\n==>PROCESS-INTERNET-STUFF--START=$current_item\n";    # AD-HOC DEBUG

          ############################################################################
          # PROCESS URLS as linguistic strings, if desired                           #
          ############################################################################

          if ( $process_urls == 1 ) {

               # Extract URLs from text                                              #

               if (

                    # NOTE: Added "?" after "(\/\/)" in the following expression in 396
                    $current_segment =~
m/<?((http|https|ftp|sftp|news|gopher|mailto):(\/\/)?|(www\.))([a-zA-Z0-9_\$\&\+\,\/\:\;\=\?\@\#\%\{\}\|\\\^\~\[\]\`\'\"\!\*\(\)\+\.\>\-]+)/
                 )
               {
                    # PROTOCOLS (Careful with ordering!)
                    $current_segment =~ s{sftp:\/\/}{eseefetepé dos puntos $slash $slash }g;
                    $current_segment =~ s{https:\/\/}{achetetepeése dos puntos $slash $slash }g;
                    $current_segment =~ s{http:\/\/}{achetetepé dos puntos $slash $slash }g;
                    $current_segment =~ s{ftp:\/\/}{efetepé dos puntos $slash $slash }g;
                    $current_segment =~ s{news:\/\/}{nius dos puntos $slash $slash }g;
                    $current_segment =~ s{gopher:\/\/}{gófer dos puntos $slash $slash }g;
                    $current_segment =~ s{mailto:}{meiltu dos puntos }g;

                    # Odd characters
                    $current_segment =~ s/\`/ acento grave /g;
                    $current_segment =~ s/\'/ apóstrofo /g;
                    $current_segment =~ s/\@/ arroba /g;
                    $current_segment =~ s/\*/ asterisco /g;
                    $current_segment =~ s/\\/ bákeslatch /g;
                    $current_segment =~ s/\|/ barra vertical /g;
                    $current_segment =~ s/\^/ circunflejo /g;
                    $current_segment =~ s/\,/ coma /g;
                    $current_segment =~ s/\"/ comillas /g;
                    $current_segment =~ s/\[/ corchete de apertura /g;
                    $current_segment =~ s/\]/ corchete de cierre /g;
                    $current_segment =~ s/\:/ dos puntos /g;
                    $current_segment =~ s/\_/ guión bajo /g;
                    $current_segment =~ s/\-/ guión /g;
                    $current_segment =~ s/\{/ llave de apertura /g;
                    $current_segment =~ s/\}/ llave de cierre /g;
                    $current_segment =~ s/\(/ paréntesis de apertura /g;
                    $current_segment =~ s/\)/ paréntesis de cierre /g;
                    $current_segment =~ s/\./ punto /g;
                    $current_segment =~ s/\;/ puntoycoma /g;
                    $current_segment =~ s/\!/ signo de exclamación /g;
                    $current_segment =~ s/\?/ signo de interrogación /g;
                    $current_segment =~ s/\%/ signo de porcentaje /g;
                    $current_segment =~ s/\#/ signo gato /g;
                    $current_segment =~ s/\=/ signo igual a /g;
                    $current_segment =~ s/\+/ signo mas /g;
                    $current_segment =~ s/\$/ signo peso /g;
                    $current_segment =~ s/\&/ signo y /g;
                    $current_segment =~ s/\// $slash /g;
                    $current_segment =~ s/\~/ tilde /g;

                    #print "==>PROCESS-INTERNET-STUFF-AFTPROT=$current_segment\n";    # AD-HOC DEBUGGING

                    # TLDs                                                           #

                    # TLDs with 3+ letters
                    $current_segment =~ s/ punto aero(\W|$)/ punto aero $1/g;
                    $current_segment =~ s/ punto asia(\W|$)/ punto asia $1/g;
                    $current_segment =~ s/ punto biz(\W|$)/ punto bis $1/g;
                    $current_segment =~ s/ punto cat(\W|$)/ punto kat $1/g;
                    $current_segment =~ s/ punto com(\W|$)/ punto kom $1/g;
                    $current_segment =~ s/ punto coop(\W|$)/ punto c o o p $1/g;
                    $current_segment =~ s/ punto edu(\W|$)/ punto e d u $1/g;
                    $current_segment =~ s/ punto gob(\W|$)/ punto g o b $1/g;
                    $current_segment =~ s/ punto gov(\W|$)/ punto g o v $1/g;
                    $current_segment =~ s/ punto info(\W|$)/ punto info $1/g;
                    $current_segment =~ s/ punto int(\W|$)/ punto i n t $1/g;
                    $current_segment =~ s/ punto jobs(\W|$)/ punto jobs $1/g;
                    $current_segment =~ s/ punto mil(\W|$)/ punto m i l $1/g;
                    $current_segment =~ s/ punto mobi(\W|$)/ punto mobi $1/g;
                    $current_segment =~ s/ punto museum(\W|$)/ punto muséum $1/g;
                    $current_segment =~ s/ punto name(\W|$)/ punto  $1/g;
                    $current_segment =~ s/ punto net(\W|$)/ punto net $1/g;
                    $current_segment =~ s/ punto org(\W|$)/ punto o r g $1/g;
                    $current_segment =~ s/ punto pro(\W|$)/ punto pro $1/g;
                    $current_segment =~ s/ punto tel(\W|$)/ punto tel $1/g;
                    $current_segment =~ s/ punto travel(\W|$)/ punto trável $1/g;

                    #print "==>PROCESS-INTERNET-STUFF-AF.xxx=$current_segment\n";    # AD-HOC DEBUGGING

                    # TLDs with 2 letters
                    $current_segment =~ s/ punto ac(\W|$)/ punto a c $1/g;
                    $current_segment =~ s/ punto ad(\W|$)/ punto a d $1/g;
                    $current_segment =~ s/ punto ae(\W|$)/ punto a e $1/g;
                    $current_segment =~ s/ punto af(\W|$)/ punto a f $1/g;
                    $current_segment =~ s/ punto ag(\W|$)/ punto a g $1/g;
                    $current_segment =~ s/ punto ai(\W|$)/ punto a i $1/g;
                    $current_segment =~ s/ punto al(\W|$)/ punto a l $1/g;
                    $current_segment =~ s/ punto am(\W|$)/ punto a m $1/g;
                    $current_segment =~ s/ punto an(\W|$)/ punto a n $1/g;
                    $current_segment =~ s/ punto ao(\W|$)/ punto a o $1/g;
                    $current_segment =~ s/ punto aq(\W|$)/ punto a q $1/g;
                    $current_segment =~ s/ punto ar(\W|$)/ punto a r $1/g;
                    $current_segment =~ s/ punto as(\W|$)/ punto a s $1/g;
                    $current_segment =~ s/ punto at(\W|$)/ punto a t $1/g;
                    $current_segment =~ s/ punto au(\W|$)/ punto a u $1/g;
                    $current_segment =~ s/ punto aw(\W|$)/ punto a w $1/g;
                    $current_segment =~ s/ punto ax(\W|$)/ punto a x $1/g;
                    $current_segment =~ s/ punto az(\W|$)/ punto a z $1/g;
                    $current_segment =~ s/ punto ba(\W|$)/ punto b a $1/g;
                    $current_segment =~ s/ punto bb(\W|$)/ punto b b $1/g;
                    $current_segment =~ s/ punto bd(\W|$)/ punto b d $1/g;
                    $current_segment =~ s/ punto be(\W|$)/ punto b e $1/g;
                    $current_segment =~ s/ punto bf(\W|$)/ punto b f $1/g;
                    $current_segment =~ s/ punto bg(\W|$)/ punto b g $1/g;
                    $current_segment =~ s/ punto bh(\W|$)/ punto b h $1/g;
                    $current_segment =~ s/ punto bi(\W|$)/ punto b i $1/g;
                    $current_segment =~ s/ punto bj(\W|$)/ punto b j $1/g;
                    $current_segment =~ s/ punto bm(\W|$)/ punto b m $1/g;
                    $current_segment =~ s/ punto bn(\W|$)/ punto b n $1/g;
                    $current_segment =~ s/ punto bo(\W|$)/ punto b o $1/g;
                    $current_segment =~ s/ punto br(\W|$)/ punto b r $1/g;
                    $current_segment =~ s/ punto bs(\W|$)/ punto b s $1/g;
                    $current_segment =~ s/ punto bt(\W|$)/ punto b t $1/g;
                    $current_segment =~ s/ punto bv(\W|$)/ punto b v $1/g;
                    $current_segment =~ s/ punto bw(\W|$)/ punto b w $1/g;
                    $current_segment =~ s/ punto by(\W|$)/ punto b igriega $1/g;
                    $current_segment =~ s/ punto bz(\W|$)/ punto b z $1/g;
                    $current_segment =~ s/ punto ca(\W|$)/ punto c a $1/g;
                    $current_segment =~ s/ punto cc(\W|$)/ punto c c $1/g;
                    $current_segment =~ s/ punto cd(\W|$)/ punto c d $1/g;
                    $current_segment =~ s/ punto cf(\W|$)/ punto c f $1/g;
                    $current_segment =~ s/ punto cg(\W|$)/ punto c g $1/g;
                    $current_segment =~ s/ punto ch(\W|$)/ punto c h $1/g;
                    $current_segment =~ s/ punto ci(\W|$)/ punto c i $1/g;
                    $current_segment =~ s/ punto ck(\W|$)/ punto c k $1/g;
                    $current_segment =~ s/ punto cl(\W|$)/ punto c l $1/g;
                    $current_segment =~ s/ punto cm(\W|$)/ punto c m $1/g;
                    $current_segment =~ s/ punto cn(\W|$)/ punto c n $1/g;
                    $current_segment =~ s/ punto co(\W|$)/ punto c o $1/g;
                    $current_segment =~ s/ punto cr(\W|$)/ punto c r $1/g;
                    $current_segment =~ s/ punto cu(\W|$)/ punto c u $1/g;
                    $current_segment =~ s/ punto cv(\W|$)/ punto c v $1/g;
                    $current_segment =~ s/ punto cx(\W|$)/ punto c x $1/g;
                    $current_segment =~ s/ punto cy(\W|$)/ punto c igriega $1/g;
                    $current_segment =~ s/ punto cz(\W|$)/ punto c z $1/g;
                    $current_segment =~ s/ punto de(\W|$)/ punto d e $1/g;
                    $current_segment =~ s/ punto dj(\W|$)/ punto d j $1/g;
                    $current_segment =~ s/ punto dk(\W|$)/ punto d k $1/g;
                    $current_segment =~ s/ punto dm(\W|$)/ punto d m $1/g;
                    $current_segment =~ s/ punto do(\W|$)/ punto d o $1/g;
                    $current_segment =~ s/ punto dz(\W|$)/ punto d z $1/g;
                    $current_segment =~ s/ punto ec(\W|$)/ punto e c $1/g;
                    $current_segment =~ s/ punto ee(\W|$)/ punto e e $1/g;
                    $current_segment =~ s/ punto eg(\W|$)/ punto e g $1/g;
                    $current_segment =~ s/ punto er(\W|$)/ punto e r $1/g;
                    $current_segment =~ s/ punto es(\W|$)/ punto e s $1/g;
                    $current_segment =~ s/ punto et(\W|$)/ punto e t $1/g;
                    $current_segment =~ s/ punto eu(\W|$)/ punto e u $1/g;
                    $current_segment =~ s/ punto fi(\W|$)/ punto f i $1/g;
                    $current_segment =~ s/ punto fj(\W|$)/ punto f j $1/g;
                    $current_segment =~ s/ punto fk(\W|$)/ punto f k $1/g;
                    $current_segment =~ s/ punto fm(\W|$)/ punto f m $1/g;
                    $current_segment =~ s/ punto fo(\W|$)/ punto f o $1/g;
                    $current_segment =~ s/ punto fr(\W|$)/ punto f r $1/g;
                    $current_segment =~ s/ punto ga(\W|$)/ punto g a $1/g;
                    $current_segment =~ s/ punto gb(\W|$)/ punto g b $1/g;
                    $current_segment =~ s/ punto gd(\W|$)/ punto g d $1/g;
                    $current_segment =~ s/ punto ge(\W|$)/ punto g e $1/g;
                    $current_segment =~ s/ punto gf(\W|$)/ punto g f $1/g;
                    $current_segment =~ s/ punto gg(\W|$)/ punto g g $1/g;
                    $current_segment =~ s/ punto gh(\W|$)/ punto g h  $1/g;
                    $current_segment =~ s/ punto gi(\W|$)/ punto g i  $1/g;
                    $current_segment =~ s/ punto gl(\W|$)/ punto g l $1/g;
                    $current_segment =~ s/ punto gm(\W|$)/ punto g m $1/g;
                    $current_segment =~ s/ punto gn(\W|$)/ punto g n $1/g;
                    $current_segment =~ s/ punto gp(\W|$)/ punto g p $1/g;
                    $current_segment =~ s/ punto gq(\W|$)/ punto g q $1/g;
                    $current_segment =~ s/ punto gr(\W|$)/ punto g r $1/g;
                    $current_segment =~ s/ punto gs(\W|$)/ punto g s $1/g;
                    $current_segment =~ s/ punto gt(\W|$)/ punto g t $1/g;
                    $current_segment =~ s/ punto gu(\W|$)/ punto g u $1/g;
                    $current_segment =~ s/ punto gw(\W|$)/ punto g w $1/g;
                    $current_segment =~ s/ punto gy(\W|$)/ punto g igriega $1/g;
                    $current_segment =~ s/ punto hk(\W|$)/ punto h k $1/g;
                    $current_segment =~ s/ punto hm(\W|$)/ punto h m $1/g;
                    $current_segment =~ s/ punto hn(\W|$)/ punto h n $1/g;
                    $current_segment =~ s/ punto hr(\W|$)/ punto h r $1/g;
                    $current_segment =~ s/ punto ht(\W|$)/ punto h t $1/g;
                    $current_segment =~ s/ punto hu(\W|$)/ punto h u $1/g;
                    $current_segment =~ s/ punto id(\W|$)/ punto i d $1/g;
                    $current_segment =~ s/ punto ie(\W|$)/ punto i e $1/g;
                    $current_segment =~ s/ punto il(\W|$)/ punto i l $1/g;
                    $current_segment =~ s/ punto im(\W|$)/ punto i m $1/g;
                    $current_segment =~ s/ punto in(\W|$)/ punto i n $1/g;
                    $current_segment =~ s/ punto io(\W|$)/ punto i o $1/g;
                    $current_segment =~ s/ punto iq(\W|$)/ punto i q $1/g;
                    $current_segment =~ s/ punto ir(\W|$)/ punto i r $1/g;
                    $current_segment =~ s/ punto is(\W|$)/ punto i s $1/g;
                    $current_segment =~ s/ punto it(\W|$)/ punto i t $1/g;
                    $current_segment =~ s/ punto je(\W|$)/ punto j e $1/g;
                    $current_segment =~ s/ punto jm(\W|$)/ punto j m $1/g;
                    $current_segment =~ s/ punto jo(\W|$)/ punto j o $1/g;
                    $current_segment =~ s/ punto jp(\W|$)/ punto j p $1/g;
                    $current_segment =~ s/ punto ke(\W|$)/ punto k e $1/g;
                    $current_segment =~ s/ punto kg(\W|$)/ punto k g $1/g;
                    $current_segment =~ s/ punto kh(\W|$)/ punto k h $1/g;
                    $current_segment =~ s/ punto ki(\W|$)/ punto k i $1/g;
                    $current_segment =~ s/ punto km(\W|$)/ punto k m $1/g;
                    $current_segment =~ s/ punto kn(\W|$)/ punto k n $1/g;
                    $current_segment =~ s/ punto kp(\W|$)/ punto k p $1/g;
                    $current_segment =~ s/ punto kr(\W|$)/ punto k r $1/g;
                    $current_segment =~ s/ punto kw(\W|$)/ punto k w $1/g;
                    $current_segment =~ s/ punto ky(\W|$)/ punto k igriega $1/g;
                    $current_segment =~ s/ punto kz(\W|$)/ punto k z $1/g;
                    $current_segment =~ s/ punto la(\W|$)/ punto l a $1/g;
                    $current_segment =~ s/ punto lb(\W|$)/ punto l b $1/g;
                    $current_segment =~ s/ punto lc(\W|$)/ punto l c $1/g;
                    $current_segment =~ s/ punto li(\W|$)/ punto l i $1/g;
                    $current_segment =~ s/ punto lk(\W|$)/ punto l k $1/g;
                    $current_segment =~ s/ punto lr(\W|$)/ punto l r $1/g;
                    $current_segment =~ s/ punto ls(\W|$)/ punto l s $1/g;
                    $current_segment =~ s/ punto lt(\W|$)/ punto l t $1/g;
                    $current_segment =~ s/ punto lu(\W|$)/ punto l u $1/g;
                    $current_segment =~ s/ punto lv(\W|$)/ punto l v $1/g;
                    $current_segment =~ s/ punto ly(\W|$)/ punto l igriega $1/g;
                    $current_segment =~ s/ punto ma(\W|$)/ punto m a $1/g;
                    $current_segment =~ s/ punto mc(\W|$)/ punto m c $1/g;
                    $current_segment =~ s/ punto md(\W|$)/ punto m d $1/g;
                    $current_segment =~ s/ punto me(\W|$)/ punto m e $1/g;
                    $current_segment =~ s/ punto mg(\W|$)/ punto m g $1/g;
                    $current_segment =~ s/ punto mh(\W|$)/ punto m h $1/g;
                    $current_segment =~ s/ punto mk(\W|$)/ punto m k $1/g;
                    $current_segment =~ s/ punto ml(\W|$)/ punto m l $1/g;
                    $current_segment =~ s/ punto mm(\W|$)/ punto m m $1/g;
                    $current_segment =~ s/ punto mn(\W|$)/ punto m n $1/g;
                    $current_segment =~ s/ punto mo(\W|$)/ punto m o $1/g;
                    $current_segment =~ s/ punto mp(\W|$)/ punto m p $1/g;
                    $current_segment =~ s/ punto mq(\W|$)/ punto m q $1/g;
                    $current_segment =~ s/ punto mr(\W|$)/ punto m r $1/g;
                    $current_segment =~ s/ punto ms(\W|$)/ punto m s $1/g;
                    $current_segment =~ s/ punto mt(\W|$)/ punto m t $1/g;
                    $current_segment =~ s/ punto mu(\W|$)/ punto m u $1/g;
                    $current_segment =~ s/ punto mv(\W|$)/ punto m v $1/g;
                    $current_segment =~ s/ punto mw(\W|$)/ punto m w $1/g;
                    $current_segment =~ s/ punto mx(\W|$)/ punto m x $1/g;
                    $current_segment =~ s/ punto my(\W|$)/ punto m igriega $1/g;
                    $current_segment =~ s/ punto mz(\W|$)/ punto m z $1/g;
                    $current_segment =~ s/ punto na(\W|$)/ punto n a $1/g;
                    $current_segment =~ s/ punto nc(\W|$)/ punto n c $1/g;
                    $current_segment =~ s/ punto ne(\W|$)/ punto n e $1/g;
                    $current_segment =~ s/ punto nf(\W|$)/ punto n f $1/g;
                    $current_segment =~ s/ punto ng(\W|$)/ punto n g $1/g;
                    $current_segment =~ s/ punto ni(\W|$)/ punto n i $1/g;
                    $current_segment =~ s/ punto nl(\W|$)/ punto n l $1/g;
                    $current_segment =~ s/ punto no(\W|$)/ punto n o $1/g;
                    $current_segment =~ s/ punto np(\W|$)/ punto n p $1/g;
                    $current_segment =~ s/ punto nr(\W|$)/ punto n r $1/g;
                    $current_segment =~ s/ punto nu(\W|$)/ punto n u $1/g;
                    $current_segment =~ s/ punto nz(\W|$)/ punto n z $1/g;
                    $current_segment =~ s/ punto om(\W|$)/ punto o m $1/g;
                    $current_segment =~ s/ punto pa(\W|$)/ punto p a $1/g;
                    $current_segment =~ s/ punto pe(\W|$)/ punto p e $1/g;
                    $current_segment =~ s/ punto pf(\W|$)/ punto p f $1/g;
                    $current_segment =~ s/ punto pg(\W|$)/ punto p g $1/g;
                    $current_segment =~ s/ punto ph(\W|$)/ punto p h $1/g;
                    $current_segment =~ s/ punto pk(\W|$)/ punto p k $1/g;
                    $current_segment =~ s/ punto pl(\W|$)/ punto p l $1/g;
                    $current_segment =~ s/ punto pm(\W|$)/ punto p m $1/g;
                    $current_segment =~ s/ punto pn(\W|$)/ punto p n $1/g;
                    $current_segment =~ s/ punto pr(\W|$)/ punto p r $1/g;
                    $current_segment =~ s/ punto ps(\W|$)/ punto p s $1/g;
                    $current_segment =~ s/ punto pt(\W|$)/ punto p t $1/g;
                    $current_segment =~ s/ punto pw(\W|$)/ punto p w $1/g;
                    $current_segment =~ s/ punto py(\W|$)/ punto p y $1/g;
                    $current_segment =~ s/ punto qa(\W|$)/ punto q a $1/g;
                    $current_segment =~ s/ punto re(\W|$)/ punto r e $1/g;
                    $current_segment =~ s/ punto ro(\W|$)/ punto r o $1/g;
                    $current_segment =~ s/ punto rs(\W|$)/ punto r s $1/g;
                    $current_segment =~ s/ punto ru(\W|$)/ punto r u $1/g;
                    $current_segment =~ s/ punto rw(\W|$)/ punto r w $1/g;
                    $current_segment =~ s/ punto sa(\W|$)/ punto s a $1/g;
                    $current_segment =~ s/ punto sb(\W|$)/ punto s b $1/g;
                    $current_segment =~ s/ punto sc(\W|$)/ punto s c $1/g;
                    $current_segment =~ s/ punto sd(\W|$)/ punto s d $1/g;
                    $current_segment =~ s/ punto se(\W|$)/ punto s e $1/g;
                    $current_segment =~ s/ punto sg(\W|$)/ punto s g $1/g;
                    $current_segment =~ s/ punto sh(\W|$)/ punto s h $1/g;
                    $current_segment =~ s/ punto si(\W|$)/ punto s i $1/g;
                    $current_segment =~ s/ punto sj(\W|$)/ punto s j $1/g;
                    $current_segment =~ s/ punto sk(\W|$)/ punto s k $1/g;
                    $current_segment =~ s/ punto sl(\W|$)/ punto s l $1/g;
                    $current_segment =~ s/ punto sm(\W|$)/ punto s m $1/g;
                    $current_segment =~ s/ punto sn(\W|$)/ punto s n $1/g;
                    $current_segment =~ s/ punto so(\W|$)/ punto s o $1/g;
                    $current_segment =~ s/ punto sr(\W|$)/ punto s r $1/g;
                    $current_segment =~ s/ punto st(\W|$)/ punto s t $1/g;
                    $current_segment =~ s/ punto su(\W|$)/ punto s u $1/g;
                    $current_segment =~ s/ punto sv(\W|$)/ punto s v $1/g;
                    $current_segment =~ s/ punto sy(\W|$)/ punto s y $1/g;
                    $current_segment =~ s/ punto sz(\W|$)/ punto s z $1/g;
                    $current_segment =~ s/ punto tc(\W|$)/ punto t c $1/g;
                    $current_segment =~ s/ punto td(\W|$)/ punto t d $1/g;
                    $current_segment =~ s/ punto tf(\W|$)/ punto t f $1/g;
                    $current_segment =~ s/ punto tg(\W|$)/ punto t g $1/g;
                    $current_segment =~ s/ punto th(\W|$)/ punto t h $1/g;
                    $current_segment =~ s/ punto tj(\W|$)/ punto t j $1/g;
                    $current_segment =~ s/ punto tk(\W|$)/ punto t k $1/g;
                    $current_segment =~ s/ punto tl(\W|$)/ punto t l $1/g;
                    $current_segment =~ s/ punto tm(\W|$)/ punto t m $1/g;
                    $current_segment =~ s/ punto tn(\W|$)/ punto t n $1/g;
                    $current_segment =~ s/ punto to(\W|$)/ punto t o $1/g;
                    $current_segment =~ s/ punto tp(\W|$)/ punto t p $1/g;
                    $current_segment =~ s/ punto tr(\W|$)/ punto t r $1/g;
                    $current_segment =~ s/ punto tt(\W|$)/ punto t t $1/g;
                    $current_segment =~ s/ punto tv(\W|$)/ punto t v $1/g;
                    $current_segment =~ s/ punto tw(\W|$)/ punto t w $1/g;
                    $current_segment =~ s/ punto tz(\W|$)/ punto t z $1/g;
                    $current_segment =~ s/ punto ua(\W|$)/ punto u a $1/g;
                    $current_segment =~ s/ punto ug(\W|$)/ punto u g $1/g;
                    $current_segment =~ s/ punto uk(\W|$)/ punto u k $1/g;
                    $current_segment =~ s/ punto us(\W|$)/ punto u s $1/g;
                    $current_segment =~ s/ punto uy(\W|$)/ punto u igriega $1/g;
                    $current_segment =~ s/ punto uz(\W|$)/ punto u z $1/g;
                    $current_segment =~ s/ punto va(\W|$)/ punto v a $1/g;
                    $current_segment =~ s/ punto vc(\W|$)/ punto v c $1/g;
                    $current_segment =~ s/ punto ve(\W|$)/ punto v e $1/g;
                    $current_segment =~ s/ punto vg(\W|$)/ punto v g $1/g;
                    $current_segment =~ s/ punto vi(\W|$)/ punto v i $1/g;
                    $current_segment =~ s/ punto vn(\W|$)/ punto v n $1/g;
                    $current_segment =~ s/ punto vu(\W|$)/ punto v u $1/g;
                    $current_segment =~ s/ punto wf(\W|$)/ punto w f $1/g;
                    $current_segment =~ s/ punto ws(\W|$)/ punto w s $1/g;
                    $current_segment =~ s/ punto ye(\W|$)/ punto igriega  $1/g;
                    $current_segment =~ s/ punto yt(\W|$)/ punto igriega  $1/g;
                    $current_segment =~ s/ punto za(\W|$)/ punto z a $1/g;
                    $current_segment =~ s/ punto zm(\W|$)/ punto z m $1/g;
                    $current_segment =~ s/ punto zw(\W|$)/ punto z w $1/g;
               }

               #print "==>PROCESS-INTERNET-STUFF-AFT.xx=$current_segment\n";    # AD-HOC DEBUGGING
          }
          else {
               #######################################################################
               # ELIMINATE URLs, replacing them with a space                         #
               #######################################################################
               $current_segment =~ s/<?(http|https|ftp|sftp|gopher|news|mailto|file):\/\/\S+/ /g;
          }

          ############################################################################
          # PROCESS e-mail addresses as linguistic strings, if desired               #
          ############################################################################

          if ( $process_email == 1 ) {

               #if ( $current_segment =~ m/[\w\-\.\+]+\@[\w.]+/ ) {
               if ( $current_segment =~ m/[\w\-\.\+]+\@[\w.]+/ ) {

                    if ( $vrt_format == 1 ) {
                         $current_segment =~ s/\-/¬guión¬/g;
                         $current_segment =~ s/\-/¬guión¬/g;
                         $current_segment =~ s/\@/¬arroba¬/g;
                         $current_segment =~ s/\_/¬guión bajo¬/g;
                         $current_segment =~ s/\./¬punto¬/g;
                         $current_segment =~ s/\+/¬signo mas¬/g;
                    }
                    else {
                         $current_segment =~ s/\-/ guión /g;
                         $current_segment =~ s/\@/ arroba /g;
                         $current_segment =~ s/\_/ guión bajo /g;
                         $current_segment =~ s/\./ punto /g;
                         $current_segment =~ s/\+/ signo mas /g;

                         #print STDOUT "***PROCESS E-MAIL (no VRT) subroutine executed\n";
                    }
               }
          }
          else {
               #######################################################################
               # ELIMINATE e-mail addresses, replacing them with a space             #
               #######################################################################
               $current_segment =~ s/[\w\-\.\+]+\@[\w]+\.?[\w]+\.?[\w]+\.?[\w]+(\.?)[\w]+/ /g;

               #print STDOUT "***ELIMINATE E-MAIL subroutine executed\n";       # AD-HOC DEBUGGING
          }

          #print "==>PROCESS-INTERNET-STUFF---END=$current_segment\n\n";        # AD-HOC DEBUGGING

          # CHANGE "." WHEN IT IS SURROUNDED BY LETTERS TO "PUNTO".  FOR URLs, MAINLY

          if ( $vrt_format == 1 ) {
               $current_segment =~ s/([a-zA-Z]+)øøø([a-zA-Z]+)/$1¬punto¬$2/g;
               $current_segment =~ s/([a-zA-Z]+)øøø([a-zA-Z]+)/$1¬punto¬$2/g;    # Not sure why the prev line doesn't catch all instances...
               $current_segment =~ s/([a-zA-Z]+)øøø([a-zA-Z]+)/$1¬punto¬$2/g;    # This is here just for good measure.
               $current_segment =~ s/([a-zA-Z]+)øøø([a-zA-Z]+)/$1¬punto¬$2/g;    # Ditto.
               $current_segment =~ s/([a-zA-Z]+)øøø([a-zA-Z]+)/$1¬punto¬$2/g;    # Ditto.
          }
          else {
               $current_segment =~ s/([a-zA-Z]+)\.([a-zA-Z]+)/$1 punto $2/g;
               $current_segment =~ s/([a-zA-Z]+)\.([a-zA-Z]+)/$1 punto $2/g;          # Not sure why the prev line doesn't catch all instances...
               $current_segment =~ s/([a-zA-Z]+)\.([a-zA-Z]+)/$1 punto $2/g;          # This is here just for good measure.
               $current_segment =~ s/([a-zA-Z]+)\.([a-zA-Z]+)/$1 punto $2/g;          # Ditto.
               $current_segment =~ s/([a-zA-Z]+)\.([a-zA-Z]+)/$1 punto $2/g;          # Ditto.
          }

          #print "==>EXPER-INT-STUFF-SPLIT----END=$current_segment\n";                 # AD-HOC DEBUGGING
     }

     $current_item = join( " ", @current_segments );

     return ($current_item);

}    # END SUBROUTINE: PROCESS INTERNET STUFF

##############################################################################
#                                                                            #
#   SUBROUTINE: FIX MISCELLANEOUS PUNCTUATION                                #
#                                                                            #
##############################################################################
sub fix_misc_punctuation {

     # Read value passed into subroutine and assign it to $current_item
     my $current_item = $_[0];

     ############################################################################
     # INSERT .VRT FORMAT PLACEHOLDERS                                          #
     # This protects characters that would otherwise be eliminated.             #
     ############################################################################
     if ( $vrt_format == 1 ) {

          #$temp = Encode::encode_utf8($current_item);    # AD-HOC DEBUG
          #print STDOUT "\nVRT-FORMAT-PLCHLD-BEF:$temp:\n";    # AD-HOC DEBUG

          # Things that appear alone on a line
          $current_item =~ s/^$/øø/g;           # Blank line
          $current_item =~ s/^\.$/þ/g;          # Period
          $current_item =~ s/^,$/¢/g;           # Comma
          $current_item =~ s/^\:$/ĸ/g;          # Colon
          $current_item =~ s/^\;$/ĸ/g;          # Semi-colon
          $current_item =~ s/^\-$/ð/g;          # Dash
          $current_item =~ s/^\"$/ł/g;          # Quotation mark
          $current_item =~ s/^¿$/ŋ/g;           # Opening question mark
          $current_item =~ s/^\+$/øø/g;         # Plus sign
          $current_item =~ s/^\.\.\.$/øø/g;     # Ellipsis
          $current_item =~ s/^\@$/øø/g;         # At sign

          # Things that don't necessarily have to appear alone on a line
          $current_item =~ s/\(/ø/g;            # Open paren.
          $current_item =~ s/\)/ø/g;            # Close paren.
          $current_item =~ s/¡/øø/g;            # Opening exclamation point
          $current_item =~ s/#/øø/g;            # Number sign
          $current_item =~ s/º/øø/g;            # Male ord.
          $current_item =~ s/ª/øø/g;            # Female ord.
          $current_item =~ s/°/øø/g;            # Degree symbol
          $current_item =~ s/\\/øø/g;           # Backslash
          $current_item =~ s/\|/øø/g;           # Pipe
          $current_item =~ s/\€/ euros /g;      # Euro sign
          $current_item =~ s/&/øø/g;            # Ampersand
          $current_item =~ s/~/øø/g;            # Tilde
          $current_item =~ s/\^/øø/g;           # Caret
          $current_item =~ s/'/øø/g;            # Apostrophe
          $current_item =~ s/\*/øø/g;           # Asterisk
          $current_item =~ s/_/øø/g;            # Underscore

          #$temp = Encode::encode_utf8($current_item);      # AD-HOC DEBUG
          #print STDOUT "VRT-FORMAT-PLCHLD-AFT:$temp:\n";   # AD-HOC DEBUG

     }

     #   ADD SPACE AFTER PERIOD+BRACKETS. Prevents problems with .[ and .( in cites.
     $current_item =~ s/\.[\[\]\{\}\(\}]/\. /g;

     # PROCESS QUOTATION MARKS

     # CONNEXOR MAINTAINS QUOTATION MARKS, ASSIGNING THEM TO THEIR OWN LINE. THUS
     # FOR THE .VRT FORMAT, THESE MUST BE PRESERVED.
     if ( $vrt_format == 1 ) {
          $current_item =~ s/"/½/g;
     }

     # REMOVE QUOTATION MARKS IN ALL OTHER CASES.
     else {
          $current_item =~ s/"//g;
     }

     # ELIMINATE ELLIPSES AT BEGINNING OF LINE
     $current_item =~ s/^\.\.\.//g;

     # ADD SPACES AROUND ELLIPSES
     $current_item =~ s/\.\.\./ \.\.\. /g;

     # ADD SPACE AFTER ). COMBINATION
     $current_item =~ s/\)\./\)\. /g;

     # ADD SPACE AFTER )
     $current_item =~ s/\)/\) /g;

     # ADD SPACE BEFORE (
     $current_item =~ s/\(/ \(/g;

     # print "\n==>  DASH PROCESSING BEF:: $current_item";   # AD-HOC DEBUG

     ##############################################################################
     # Replace em and en dashes with "--"                                         #
     ##############################################################################
     $current_item =~ s//--/g;
     $current_item =~ s/–/--/g;
     $current_item =~ s/—/--/g;

     ##############################################################################
     # REMOVE SINGLE DASH WITH LETTERS TOUCHING ON BOTH SIDES: FOR COMPOUND WORDS #
     # (e.g. "tetra-pack", "Bío-Bío", "palato-alveolar".                          #
     ##############################################################################
     $current_item =~ s/([a-z])-([a-z])-([a-z])/$1 $2 $3/g;    # For possible phrases with two dashes
     $current_item =~ s/([a-z])-([a-z])/$1 $2/g;

     ############################################################################
     # SEPARATE LETTERS FROM NUMBERS IN WORDS CONSISTING OF LETTERS+NUMBERS     #
     # OR LETTER + DASH + NUMBER (e.g. F0, mc2, t1000, T-1000)                  #
     ############################################################################
     #print STDOUT "SEPARATE-LETTS-FROM-NUMS-BEF:$current_item\n";         # AD-HOC DEBUG

     # DISABLED: Alphanumeric-split rules convert "abc123"\xe2\x80\x94 spec requires identifiers to pass through.
     # $current_item =~ s/(\W|^)([a-záéíóúñ]+)([0-9]+)(\W|$)/ $1$2 $3$4 /g;
     # $current_item =~ s/(\W|^)([a-záéíóúñ]+)([0-9]+)(\W|$)/ $1$2 $3$4 /g;  # repetition disabled

     # DISABLED: Letter+Dash+Numbers
     # $current_item =~ s/(\W|^)([a-záéíóúñ]+)\-([0-9]+)(\W|$)/ $1$2 $3$4 /g;

     #print STDOUT "SEPARATE-LETTS-FROM-NUMS-AFT:$current_item\n";         # AD-HOC DEBUG

     ##############################################################################
     # CHANGE MATHEMATICAL OPERATORS BETWEEN NUMERALS INTO WORDS                  #
     # (e.g. "12.345.678-9", 10-2=6)                                              #
     ##############################################################################
     # RUT handler: this fires only if normalize_structured_tokens section 6
     # missed the RUT (e.g. non-standard format). Since normalize now runs first
     # and section 6 handles all standard RUT formats, this is a safety fallback.
     # Disabled to prevent partial expansion (12.345.678 with dots still present
     # would then be misread as a large number by convert_numerals_to_words).
     # $current_item =~ s/([0-9])\.([0-9]{3})\.([0-9]{3})( |)-( |)([0-9])/$1.$2.$3 guión $6/g;

     $current_item =~ s/([0-9])( |)\-( |)([0-9])/$1 menos $4/g;
     $current_item =~ s/([0-9])( |)=( |)([0-9])/$1 es igual a $4/g;
     $current_item =~ s/([0-9])( |)\+( |)([0-9])/$1 más $4/g;
     $current_item =~ s/([0-9])( |)\/( |)([0-9])/$1 dividido por $4/g;
     $current_item =~ s/([0-9])( |)\*( |)([0-9])/$1 multiplicado por $4/g;

     $current_item =~ s/([0-9])( |)×( |)([0-9])/$1 multiplicado por $4/g;
     $current_item =~ s/([0-9])( |)÷( |)([0-9])/$1 dividido por $4/g;
     $current_item =~ s/±( |)([0-9])/ más o menos $1$2/g;                  # This symbol can just have a num after it

     ############################################################################
     # CONVERT 2 or 3 DASHES INTO COMMA (TO MAKE PAUSE).                        #
     # (e.g. "se fue -- rápidamente --- y lloró".                               #
     # ONLY for non-VRT formats.                                                #
     ############################################################################
     if ( $vrt_format == 0 ) {
          $current_item =~ s/\-\-\-/, /g;
          $current_item =~ s/\-\-/, /g;
     }
     else {
          $current_item =~ s/\-\-\-/ guión /g;
          $current_item =~ s/\-\-/ guión /g;
     }

     ###########################################################################
     # CONVERT A MINUS SIGN ATTACHED TO A DIGIT (NEGATIVE NUMBER) INTO "MENOS" #
     # *BEFORE* the next rule turns remaining dashes into commas. This must    #
     # happen here, while the minus sign can still be distinguished from a     #
     # genuine comma. Once it becomes ", " there is no way to tell a real      #
     # comma + number (e.g. "Allá, 400 personas") from a minus-sign-turned-    #
     # comma (e.g. "es -400"), which previously caused "Allá, 400" to be       #
     # misread as a negative number further down the pipeline.                 #
     #                                                                         #
     # The optional "\$?" lets this also catch negative currency amounts       #
     # (e.g. "-$200.000"). The "$" itself is matched via lookahead, not        #
     # consumed, so it's left in place for change_some_symbols_to_words() /    #
     # the currency-amount handling in convert_numerals_to_words() to convert  #
     # later (e.g. "-$200.000" -> "menos $200.000" -> "menos doscientos mil    #
     # " . $moneda . " " (whatever currency name is configured, "pésos" by     #
     # default)).                                                              #
     ###########################################################################
     if ( $vrt_format == 0 ) {
          $current_item =~ s/(^|\s)-(?=\$?[0-9])/${1}menos /g;
     }

     ###########################################################################
     # CONVERT REMAINING CASES OF 1 DASH INTO COMMA + SPACE (TO MAKE PAUSE)    #
     ###########################################################################
     $current_item =~ s/\-/, /g;

     # print "\n==  DASH PROCESSING AFT:: $current_item";   # DEBUG

     ###########################################################################
     # ELIMINATE MOST PUNCTUATION AT END OF WORD (UTTERANCE) FOR VRT           #
     ###########################################################################
     # WARNING: Added in 401
     if ( $vrt_format == 1 ) {

          # NEW IN 402: Removed "\>" from search expression, to preserve <s> and <p>
          $current_item =~ s/(\,|\-|\_|\@|\+|\&|\.) *$//g;
     }

     ###########################################################################
     # CONVERT COMMAS INTO THE WORD "kó.ma"                                    #
     ###########################################################################
     if ( $vrt_format == 1 ) {
          $current_item =~ s/ , / coma /g;
     }

     return ($current_item);

}    # END SUBROUTINE: FIX MISC PUNCTUATION

#################################################################################
#                                                                               #
#   SUBROUTINE: EXPAND ABBREVIATIONS IN ORTHOGRAPHIC FORM                       #
#                                                                               #
#################################################################################
sub expand_abbreviations_ortho {

     my $current_item = $_[0];

     ############################################################################
     # Process abbreviations in .VRT files                                      #
     ############################################################################
     if ( $vrt_format == 1 ) {

          #print STDOUT "VRT_ORTHO-ABBRV-BEF:$current_item\n";

          # VRT: ABBREVIATIONS WITH SLASHES
          $current_item =~ s/^a\/c$/a cuenta/g;
          $current_item =~ s/^a\/f$/a favor/g;
          $current_item =~ s/^c\/u$/cada uno/g;
          $current_item =~ s/^s\/a$/sin año/g;
          $current_item =~ s/^s\/e$/sin editorial/g;
          $current_item =~ s/^s\/f$/sin fecha/g;
          $current_item =~ s/^s\/l$/sin lugar/g;
          $current_item =~ s/^s\/n$/sin número/g;
          $current_item =~ s/^v\/s$/versus/g;
          $current_item =~ s/^y\/o$/i o/g;

          # VRT: ABBREVIATIONS WITH TWO PERIODS
          $current_item =~ s/^a\.c(|\.)$/ántes de crísto/g;
          $current_item =~ s/^a\.d(|\.)$/áno dómini/g;
          $current_item =~ s/^a\.de(|\.)$/ántes de/g;
          $current_item =~ s/^a\.de¬c(|\.)$/ántes de crísto/g;
          $current_item =~ s/^a\.m(|\.)$/ánte merídiem/g;
          $current_item =~ s/^a\.r(|\.)$/altéza reál/g;
          $current_item =~ s/^a\.t(|\.)$/antíguo testaménto/g;
          $current_item =~ s/^bs\.as(|\.)$/buénos áires/g;
          $current_item =~ s/^c\.a(|\.)$/compañía anónima/g;
          $current_item =~ s/^c\.f(|\.)$/capitál federál/g;
          $current_item =~ s/^c\.i(|\.)$/cédula de identidád/g;
          $current_item =~ s/^c\.p(|\.)$/código postál/g;
          $current_item =~ s/^cap\.fed(|\.)$/capitál federál/g;
          $current_item =~ s/^d\.c(|\.)$/después de crísto/g;
          $current_item =~ s/^d\.de(|\.)$/después de/g;
          $current_item =~ s/^d\.de¬c(|\.)$/después de crísto/g;
          $current_item =~ s/^d\.f(|\.)$/distríto federál/g;
          $current_item =~ s/^dd\.hh$/deréchos humános/g;
          $current_item =~ s/^e\.c(|\.)$/éra común/g;
          $current_item =~ s/^e\.u(|\.)$/estádos unídos/g;
          $current_item =~ s/^ee\.uu(|\.)$/estádos unídos/g;
          $current_item =~ s/^f\.c(|\.)$/ferrocarríl/g;
          $current_item =~ s/^ff\.aa(|\.)$/fuérzas armádas/g;
          $current_item =~ s/^ff\.cc(|\.)$/ferrocarríles/g;
          $current_item =~ s/^ff\.ee(|\.)$/ferrocarríles del estádo/g;
          $current_item =~ s/^i\.e(|\.)$/íd ést/g;
          $current_item =~ s/^jj\.cc(|\.)$/juventúdes comunístas/g;
          $current_item =~ s/^jj\.oo(|\.)$/juégos olímpicos/g;
          $current_item =~ s/^k\.o(|\.)$/nocáut/g;
          $current_item =~ s/^l\.c(|\.)$/lóco citáto/g;
          $current_item =~ s/^loc\.cit(|\.)$/lóco citáto/g;
          $current_item =~ s/^m\.n(|\.)$/monéda nacionál/g;
          $current_item =~ s/^n\.b(|\.)$/nóta béne/g;
          $current_item =~ s/^n\.del(|\.)$/nóta del/g;
          $current_item =~ s/^ob\.cit(|\.)$/óbra citáda/g;
          $current_item =~ s/^oo\.pp(|\.)$/óbras públicas/g;
          $current_item =~ s/^p\.d(|\.)$/posdáta/g;
          $current_item =~ s/^p\.ej(|\.)$/por ejémplo/g;
          $current_item =~ s/^p\.ejem(|\.)$/por ejémplo/g;
          $current_item =~ s/^p\.m(|\.)$/póst merídiem/g;
          $current_item =~ s/^p\.s(|\.)$/póst scríptum/g;
          $current_item =~ s/^rr\.hh(|\.)$/recúrsos humános/g;
          $current_item =~ s/^s\.a(|\.)$/sin áño/g;
          $current_item =~ s/^s\.a(|\.)$/sociedád anónima/g;
          $current_item =~ s/^s\.d(|\.)$/síne dáta/g;
          $current_item =~ s/^s\.f(|\.)$/sín fécha/g;
          $current_item =~ s/^s\.l(|\.)$/sín lugár/g;
          $current_item =~ s/^s\.n(|\.)$/sín número/g;
          $current_item =~ s/^u\.e(|\.)$/unión européa/g;
          $current_item =~ s/^v\.g(|\.)$/vérbi grátia/g;
          $current_item =~ s/^v\.gr(|\.)$/vérbi grátia/g;
          $current_item =~ s/^v\.s(|\.)$/vuéstra señoría/g;
          $current_item =~ s/^w\.c(|\.)$/doblevesé/g;
          $current_item =~ s/^óp\.cit(|\.)$/ópere citáto/g;

          # VRT: ABBREVIATIONS WITH THREE PERIODS
          $current_item =~ s/^a\.de¬j\.c(|\.)$/ántes de jesucrísto/g;
          $current_item =~ s/^a\.j\.c(|\.)$/ántes de jesucrísto/g;
          $current_item =~ s/^d\.de¬j\.c(|\.)$/después de jesucrísto/g;
          $current_item =~ s/^d\.j\.c(|\.)$/después de jesucrísto/g;
          $current_item =~ s/^o\.e\.a(|\.)$/oéa/g;
          $current_item =~ s/^o\.n\.u(|\.)$/ónu/g;
          $current_item =~ s/^p\.v\.p(|\.)$/précio de vénta al público/g;
          $current_item =~ s/^r\.p\.m(|\.)$/revoluciónes por minúto/g;

          # VRT: ABBREVIATIONS WITH NO PUNCTUATION, BUT ODD CHARACTERS
          $current_item =~ s/^n°$/número /g;
          $current_item =~ s/^nº$/número /g;
          $current_item =~ s/^v°( |)b°$/vísto buéno/g;
          $current_item =~ s/^vº( |)bº$/vísto buéno/g;

          # VRT: ABBREVIATIONS WITH ONE PERIOD, AT END.
          $current_item =~ s/^afma(|\.)$/afectísima/g;
          $current_item =~ s/^afmo(|\.)$/afectísimo/g;
          $current_item =~ s/^alfz(|\.)$/alférez/g;
          $current_item =~ s/^almte(|\.)$/almirante/g;
          $current_item =~ s/^apdo(|\.)$/apartado/g;
          $current_item =~ s/^art(|\.)$/artículo/g;
          $current_item =~ s/^arz(|\.)$/arzobispo/g;
          $current_item =~ s/^atta(|\.)$/atenta/g;
          $current_item =~ s/^atte(|\.)$/atentamente/g;
          $current_item =~ s/^atto(|\.)$/atento/g;
          $current_item =~ s/^av(|\.)$/avenida/g;
          $current_item =~ s/^avda(|\.)$/avenida/g;
          $current_item =~ s/^bco(|\.)$/banco/g;
          $current_item =~ s/^bibl(|\.)$/biblioteca/g;
          $current_item =~ s/^cap(|\.)$/capítulo/g;
          $current_item =~ s/^cf(|\.)$/confere/g;
          $current_item =~ s/^cfr(|\.)$/confere/g;
          $current_item =~ s/^cmdt(|\.)$/comandante/g;
          $current_item =~ s/^cmte(|\.)$/comandante/g;
          $current_item =~ s/^cnel(|\.)$/coronel/g;
          $current_item =~ s/^comod(|\.)$/comodoro/g;
          $current_item =~ s/^comte(|\.)$/comandante/g;
          $current_item =~ s/^conf(|\.)$/confere/g;
          $current_item =~ s/^confr(|\.)$/confere/g;
          $current_item =~ s/^contralmte(|\.)$/contralmirante/g;
          $current_item =~ s/^coord(|\.)$/coordinador/g;
          $current_item =~ s/^cp(|\.)$/compárese/g;
          $current_item =~ s/^cta(|\.)$/cuenta/g;
          $current_item =~ s/^cte(|\.)$/corriente/g;
          $current_item =~ s/^ctv(|\.)$/centavo/g;
          $current_item =~ s/^ctvo(|\.)$/centavo/g;
          $current_item =~ s/^cént(|\.)$/céntimo/g;
          $current_item =~ s/^cía(|\.)$/compañía/g;
          $current_item =~ s/^cód(|\.)$/código/g;
          $current_item =~ s/^cónf(|\.)$/confere/g;
          $current_item =~ s/^cónfr(|\.)$/confere/g;
          $current_item =~ s/^depto(|\.)$/departamento/g;
          $current_item =~ s/^der(|\.)$/derecho/g;
          $current_item =~ s/^diag(|\.)$/diagonal/g;
          $current_item =~ s/^dicc(|\.)$/diccionario/g;
          $current_item =~ s/^dir(|\.)$/director/g;
          $current_item =~ s/^dn(|\.)$/don/g;
          $current_item =~ s/^dpto(|\.)$/departamento/g;
          $current_item =~ s/^dr(|\.)$/doctor/g;
          $current_item =~ s/^dra(|\.)$/doctora/g;
          $current_item =~ s/^dña(|\.)$/doña/g;
          $current_item =~ s/^edo(|\.)$/estado/g;
          $current_item =~ s/^ej(|\.)$/ejemplo/g;
          $current_item =~ s/^et ál(|\.)$/et álii/g;
          $current_item =~ s/^etc(|\.)$/etcétera/g;
          $current_item =~ s/^etc\.(|\.)$/etcétera\./g;           # Catch "etc..", i.e. "etc." at end of sentence.
          $current_item =~ s/^excmo(|\.)$/excelentísimo/g;
          $current_item =~ s/^fdo(|\.)$/firmado/g;
          $current_item =~ s/^fig(|\.)$/figura/g;
          $current_item =~ s/^gen(|\.)$/general/g;
          $current_item =~ s/^gral(|\.)$/general/g;
          $current_item =~ s/^ib(|\.)$/ibídem/g;
          $current_item =~ s/^ibíd(|\.)$/ibídem/g;
          $current_item =~ s/^ilmo(|\.)$/ilustrísimo/g;
          $current_item =~ s/^incl(|\.)$/inclusive/g;
          $current_item =~ s/^izq(|\.)$/izquierda/g;
          $current_item =~ s/^lcda(|\.)$/licenciada/g;
          $current_item =~ s/^lcdo(|\.)$/licenciado/g;
          $current_item =~ s/^lic(|\.)$/licenciado/g;
          $current_item =~ s/^ltda(|\.)$/limitada/g;
          $current_item =~ s/^ltdo(|\.)$/limitado/g;
          $current_item =~ s/^mons(|\.)$/monseñor/g;
          $current_item =~ s/^ms(|\.)$/manuscrito/g;
          $current_item =~ s/^máx(|\.)$/máximo/g;
          $current_item =~ s/^mín(|\.)$/mínimo/g;
          $current_item =~ s/^ne(|\.)$/noreste/g;
          $current_item =~ s/^nro(|\.)$/número/g;
          $current_item =~ s/^ntra(|\.)$/nuestra/g;
          $current_item =~ s/^ntro(|\.)$/nuestro/g;
          $current_item =~ s/^núm(|\.)$/número/g;
          $current_item =~ s/^ob(|\.)$/obispo/g;
          $current_item =~ s/^ofc(|\.)$/oficina/g;
          $current_item =~ s/^pbro(|\.)$/presbítero/g;
          $current_item =~ s/^pdta(|\.)$/presidenta/g;
          $current_item =~ s/^pdte(|\.)$/presidente/g;
          $current_item =~ s/^pg(|\.)$/página/g;
          $current_item =~ s/^plza(|\.)$/plaza/g;
          $current_item =~ s/^pp(|\.)$/páginas/g;
          $current_item =~ s/^presb(|\.)$/presbítero/g;
          $current_item =~ s/^prof(|\.)$/profesor/g;
          $current_item =~ s/^profa(|\.)$/profesora/g;
          $current_item =~ s/^prov(|\.)$/provincia/g;
          $current_item =~ s/^pza(|\.)$/plaza/g;
          $current_item =~ s/^pág(|\.)$/páginas/g;
          $current_item =~ s/^párr(|\.)$/párrafo/g;
          $current_item =~ s/^rte(|\.)$/remitente/g;
          $current_item =~ s/^se(|\.)$/sudeste/g;
          $current_item =~ s/^sgto(|\.)$/sargento/g;
          $current_item =~ s/^sig(|\.)$/siguiente/g;
          $current_item =~ s/^so(|\.)$/sudoeste/g;
          $current_item =~ s/^sr(|\.)$/señor/g;
          $current_item =~ s/^sra(|\.)$/señora/g;
          $current_item =~ s/^srta(|\.)$/señorita/g;
          $current_item =~ s/^ss(|\.)$/siguientes/g;
          $current_item =~ s/^sta(|\.)$/santa/g;
          $current_item =~ s/^sto(|\.)$/santo/g;
          $current_item =~ s/^tel(|\.)$/teléfono/g;
          $current_item =~ s/^teléf(|\.)$/teléfono/g;
          $current_item =~ s/^tte(|\.)$/teniente/g;
          $current_item =~ s/^ud(|\.)$/usted/g;
          $current_item =~ s/^uds(|\.)$/ustedes/g;
          $current_item =~ s/^univ(|\.)$/universidad/g;
          $current_item =~ s/^vd(|\.)$/usted/g;
          $current_item =~ s/^vda(|\.)$/viuda/g;
          $current_item =~ s/^vdo(|\.)$/viudo/g;
          $current_item =~ s/^vds(|\.)$/ustedes/g;
          $current_item =~ s/^vid(|\.)$/vide/g;
          $current_item =~ s/^vol(|\.)$/volumen/g;
          $current_item =~ s/^vs(|\.)$/versus/g;
          $current_item =~ s/^íd(|\.)$/ídem/g;

          # VRT: SINGLE LETTERS WITH ONE PERIOD FOLLOWING ("J. L. Borges")
          $current_item =~ s/^a(|\.)$/a /g;
          $current_item =~ s/^b(|\.)$/be /g;
          $current_item =~ s/^c(|\.)$/se /g;
          $current_item =~ s/^d(|\.)$/de /g;
          $current_item =~ s/^e(|\.)$/e /g;
          $current_item =~ s/^f(|\.)$/éfe /g;
          $current_item =~ s/^g(|\.)$/je /g;
          $current_item =~ s/^h(|\.)$/áche /g;
          $current_item =~ s/^i(|\.)$/i /g;
          $current_item =~ s/^j(|\.)$/jóta /g;
          $current_item =~ s/^k(|\.)$/ka /g;
          $current_item =~ s/^l(|\.)$/éle /g;
          $current_item =~ s/^m(|\.)$/éme /g;
          $current_item =~ s/^n(|\.)$/éne /g;
          $current_item =~ s/^o(|\.)$/o /g;
          $current_item =~ s/^p(|\.)$/pe /g;
          $current_item =~ s/^q(|\.)$/cu /g;
          $current_item =~ s/^r(|\.)$/érre /g;
          $current_item =~ s/^s(|\.)$/ése /g;
          $current_item =~ s/^t(|\.)$/te /g;
          $current_item =~ s/^u(|\.)$/u /g;
          $current_item =~ s/^v(|\.)$/$name_for_v /g;
          $current_item =~ s/^w(|\.)$/doblebé /g;
          $current_item =~ s/^x(|\.)$/équis /g;
          $current_item =~ s/^ñ(|\.)$/éñe /g;

          # $current_item =~ s/^y(|\.)$/igriega /g; # For VRT, this turns conjunction "y" into letter.
          $current_item =~ s/^z(|\.)$/séta /g;

          #print STDOUT "VRT_ORTHO-ABBRV-AFT:$current_item\n\n";
     }
     else {
          ############################################################################
          # Process abbreviations in .VRT files                                      #
          ############################################################################

          #print STDOUT "NON-VRT_ABBREV-DEBUG-BEF:$current_item:\n";    # AD HOC DEBUG

          # ABBREVIATIONS WITH SLASHES
          $current_item =~ s/(\W|^)a\/c(\W)/$1a cuenta /g;
          $current_item =~ s/(\W|^)a\/f(\W)/$1a favor /g;
          $current_item =~ s/(\W|^)c\/u(\W)/$1cada uno /g;
          $current_item =~ s/(\W|^)s\/a(\W)/$1sin año /g;
          $current_item =~ s/(\W|^)s\/e(\W)/$1sin editorial /g;
          $current_item =~ s/(\W|^)s\/f(\W)/$1sin fecha /g;
          $current_item =~ s/(\W|^)s\/l(\W)/$1sin lugar /g;
          $current_item =~ s/(\W|^)s\/n(\W)/$1sin número /g;
          $current_item =~ s/(\W|^)v\/s(\W)/$1versus /g;
          $current_item =~ s/(\W|^)y\/o(\W)/$1i o /g;

          # ABBREVIATIONS WITH THREE PERIODS
          $current_item =~ s/(\W|^)a\.( |)de j\.( |)c\./$1antes de jesucristo/g;
          $current_item =~ s/(\W|^)a\.( |)j\.( |)c\./$1antes de jesucristo/g;
          $current_item =~ s/(\W|^)d\.( |)de j\.( |)c\./$1después de jesucristo/g;
          $current_item =~ s/(\W|^)d\.( |)j\.( |)c\./$1después de jesucristo/g;
          $current_item =~ s/(\W|^)o\.( |)e\.( |)a\./$1oéa/g;
          $current_item =~ s/(\W|^)o\.( |)n\.( |)u\./$1ónu/g;
          $current_item =~ s/(\W|^)p\.( |)v\.( |)p\./$1precio de venta al público/g;
          $current_item =~ s/(\W|^)r\.( |)p\.( |)m\./$1revoluciones por minuto/g;

          # ABBREVIATIONS WITH TWO PERIODS
          $current_item =~ s/(\W|^)a\.( |)c\./$1antes de cristo/g;
          $current_item =~ s/(\W|^)a\.( |)d\./$1ano dómini/g;
          $current_item =~ s/(\W|^)a\.( |)de c\./$1antes de cristo/g;
          $current_item =~ s/(\W|^)a\.( |)m\./$1ante merídiem/g;
          $current_item =~ s/(\W|^)a\.( |)r\./$1alteza real/g;
          $current_item =~ s/(\W|^)a\.( |)t\./$1antiguo testamento/g;
          $current_item =~ s/(\W|^)bs\.( |)as\./$1buenos aires/g;
          $current_item =~ s/(\W|^)c\.( |)a\./$1compañía anónima/g;
          $current_item =~ s/(\W|^)c\.( |)f\./$1capital federal/g;
          $current_item =~ s/(\W|^)c\.( |)i\./$1cédula de identidad/g;
          $current_item =~ s/(\W|^)c\.( |)p\./$1código postal/g;
          $current_item =~ s/(\W|^)cap\.( |)fed\./$1capital federal/g;
          $current_item =~ s/(\W|^)d\.( |)c\./$1después de cristo/g;
          $current_item =~ s/(\W|^)d\.( |)de c\./$1después de cristo/g;
          $current_item =~ s/(\W|^)d\.( |)f\./$1distrito federal/g;
          $current_item =~ s/(\W|^)dd\.( |)hh\./$1derechos humanos/g;
          $current_item =~ s/(\W|^)e\.( |)c\./$1era común/g;
          $current_item =~ s/(\W|^)e\.( |)u\./$1estados unidos/g;
          $current_item =~ s/(\W|^)ee\.( |)uu\./$1estados unidos/g;
          $current_item =~ s/(\W|^)f\.( |)c\./$1ferrocarril/g;
          $current_item =~ s/(\W|^)ff\.( |)aa\./$1fuerzas armadas/g;
          $current_item =~ s/(\W|^)ff\.( |)cc\./$1ferrocarriles/g;
          $current_item =~ s/(\W|^)ff\.( |)ee\./$1ferrocarriles del estado/g;
          $current_item =~ s/(\W|^)i\.( |)e\./$1id est/g;
          $current_item =~ s/(\W|^)jj\.( |)cc\./$1juventudes comunistas/g;
          $current_item =~ s/(\W|^)jj\.( |)oo\./$1juegos olímpicos/g;
          $current_item =~ s/(\W|^)k\.( |)o\./$1nocaut/g;
          $current_item =~ s/(\W|^)l\.( |)c\./$1loco citato/g;
          $current_item =~ s/(\W|^)loc\.( |)cit\./$1loco citato/g;
          $current_item =~ s/(\W|^)m\.( |)n\./$1moneda nacional/g;
          $current_item =~ s/(\W|^)n\.( |)b\./$1nota bene/g;
          $current_item =~ s/(\W|^)n\.( |)del t\./$1nota del traductor/g;
          $current_item =~ s/(\W|^)ob\.( |)cit\./$1obra citada/g;
          $current_item =~ s/(\W|^)oo\.( |)pp\./$1obras públicas/g;
          $current_item =~ s/(\W|^)p\.( |)d\./$1posdata/g;
          $current_item =~ s/(\W|^)p\.( |)ej\./$1por ejemplo/g;
          $current_item =~ s/(\W|^)p\.( |)ejem\./$1por ejemplo/g;
          $current_item =~ s/(\W|^)p\.( |)m\./$1post merídiem/g;
          $current_item =~ s/(\W|^)p\.( |)s\./$1post scríptum/g;
          $current_item =~ s/(\W|^)rr\.( |)hh\./$1recursos humanos/g;
          $current_item =~ s/(\W|^)s\.( |)a\./$1sin año/g;
          $current_item =~ s/(\W|^)s\.( |)a\./$1sociedad anónima/g;
          $current_item =~ s/(\W|^)s\.( |)d\./$1sine data/g;
          $current_item =~ s/(\W|^)s\.( |)f\./$1sin fecha/g;
          $current_item =~ s/(\W|^)s\.( |)l\./$1sin lugar/g;
          $current_item =~ s/(\W|^)s\.( |)n\./$1sin número/g;
          $current_item =~ s/(\W|^)u\.( |)e\./$1unión europea/g;
          $current_item =~ s/(\W|^)v\.( |)g\./$1verbi gratia/g;
          $current_item =~ s/(\W|^)v\.( |)gr\./$1verbi gratia/g;
          $current_item =~ s/(\W|^)v\.( |)s\./$1vuestra señoría/g;
          $current_item =~ s/(\W|^)w\.( |)c\./$1doblevesé/g;
          $current_item =~ s/(\W|^)óp\.( |)cit\./$1ópere citato/g;

          # ABBREVIATIONS WITH NO PUNCTUATION, BUT ODD CHARACTERS
          $current_item =~ s/(\W|^)n°/$1número /g;
          $current_item =~ s/(\W|^)nº/$1número /g;
          $current_item =~ s/(\W|^)v°( |)b°/$1visto bueno/g;
          $current_item =~ s/(\W|^)vº( |)bº/$1visto bueno/g;

          # MISC ABBREVIATIONS

          # ABBREVIATIONS WITH ONE PERIOD, AT END.
          $current_item =~ s/(\W|^)afma\./$1afectísima/g;
          $current_item =~ s/(\W|^)afmo\./$1afectísimo/g;
          $current_item =~ s/(\W|^)alfz\./$1alférez/g;
          $current_item =~ s/(\W|^)almte\./$1almirante/g;
          $current_item =~ s/(\W|^)apdo\./$1apartado/g;
          $current_item =~ s/(\W|^)art\./$1artículo/g;
          $current_item =~ s/(\W|^)arz\./$1arzobispo/g;
          $current_item =~ s/(\W|^)atta\./$1atenta/g;
          $current_item =~ s/(\W|^)atte\./$1atentamente/g;
          $current_item =~ s/(\W|^)atto\./$1atento/g;
          $current_item =~ s/(\W|^)av\./$1avenida/g;
          $current_item =~ s/(\W|^)avda\./$1avenida/g;
          $current_item =~ s/(\W|^)bco\./$1banco/g;
          $current_item =~ s/(\W|^)bibl\./$1biblioteca/g;
          $current_item =~ s/(\W|^)cap\./$1capítulo/g;
          $current_item =~ s/(\W|^)cént\./$1céntimo/g;
          $current_item =~ s/(\W|^)cf\./$1confere/g;
          $current_item =~ s/(\W|^)cfr\./$1confere/g;
          $current_item =~ s/(\W|^)cía\./$1 compañía/g;
          $current_item =~ s/(\W|^)cmdt\./$1comandante/g;
          $current_item =~ s/(\W|^)cmte\./$1comandante/g;
          $current_item =~ s/(\W|^)cnel\./$1coronel/g;
          $current_item =~ s/(\W|^)cód\./$1código/g;
          $current_item =~ s/(\W|^)comod\./$1comodoro/g;
          $current_item =~ s/(\W|^)comte\./$1comandante/g;
          $current_item =~ s/(\W|^)conf\./$1confere/g;
          $current_item =~ s/(\W|^)cónf\./$1confere/g;
          $current_item =~ s/(\W|^)confr\./$1confere/g;
          $current_item =~ s/(\W|^)cónfr\./$1confere/g;
          $current_item =~ s/(\W|^)contralmte\./$1contralmirante/g;
          $current_item =~ s/(\W|^)coord\./$1coordinador/g;
          $current_item =~ s/(\W|^)cp\./$1compárese/g;
          $current_item =~ s/(\W|^)cta\./$1cuenta/g;
          $current_item =~ s/(\W|^)cte\./$1corriente/g;
          $current_item =~ s/(\W|^)ctv\./$1centavo/g;
          $current_item =~ s/(\W|^)ctvo\./$1centavo/g;
          $current_item =~ s/(\W|^)depto\./$1departamento/g;
          $current_item =~ s/(\W|^)der\./$1derecho/g;
          $current_item =~ s/(\W|^)diag\./$1diagonal/g;
          $current_item =~ s/(\W|^)dicc\./$1diccionario/g;
          $current_item =~ s/(\W|^)dir\./$1director/g;
          $current_item =~ s/(\W|^)dn\./$1don/g;
          $current_item =~ s/(\W|^)dña\./$1doña/g;
          $current_item =~ s/(\W|^)dpto\./$1departamento/g;
          $current_item =~ s/(\W|^)dr\./$1doctor/g;
          $current_item =~ s/(\W|^)dra\./$1doctora/g;
          $current_item =~ s/(\W|^)edo\./$1estado/g;
          $current_item =~ s/(\W|^)ej\./$1ejemplo/g;
          $current_item =~ s/(\W|^)et ál\./$1et álii/g;
          $current_item =~ s/(\W|^)etc\./$1etcétera/g;
          $current_item =~ s/(\W|^)etc\.\./$1etcétera\./g;           # Catch "etc..", i.e. "etc." at end of sentence.
          $current_item =~ s/(\W|^)excmo\./$1excelentísimo/g;
          $current_item =~ s/(\W|^)fdo\./$1firmado/g;
          $current_item =~ s/(\W|^)fig\./$1figura/g;
          $current_item =~ s/(\W|^)gen\./$1general/g;
          $current_item =~ s/(\W|^)gral\./$1general/g;
          $current_item =~ s/(\W|^)ib\./$1ibídem/g;
          $current_item =~ s/(\W|^)ibíd\./$1ibídem/g;
          $current_item =~ s/(\W|^)íd\./$1ídem/g;
          $current_item =~ s/(\W|^)ilmo\./$1ilustrísimo/g;
          $current_item =~ s/(\W|^)incl\./$1inclusive/g;
          $current_item =~ s/(\W|^)izq\./$1izquierda/g;
          $current_item =~ s/(\W|^)lcda\./$1licenciada/g;
          $current_item =~ s/(\W|^)lcdo\./$1licenciado/g;
          $current_item =~ s/(\W|^)lic\./$1licenciado/g;
          $current_item =~ s/(\W|^)ltda\./$1limitáda/g;
          $current_item =~ s/(\W|^)ltdo\./$1limitádo/g;
          $current_item =~ s/(\W|^)máx\./$1máximo/g;
          $current_item =~ s/(\W|^)mín\./$1mínimo/g;
          $current_item =~ s/(\W|^)mons\./$1monseñór/g;
          $current_item =~ s/(\W|^)ms\./$1manuscríto/g;
          $current_item =~ s/(\W|^)ne\./$1noréste/g;
          $current_item =~ s/(\W|^)no\.( |)(\d)/$1número$2$3/g;      # Fixed in 325: Only acts on "no." followed by number
          $current_item =~ s/(\W|^)nro\./$1número/g;
          $current_item =~ s/(\W|^)ntra\./$1nuéstra/g;
          $current_item =~ s/(\W|^)ntro\./$1nuéstro/g;
          $current_item =~ s/(\W|^)núm\./$1número/g;
          $current_item =~ s/(\W|^)ob\./$1obíspo/g;
          $current_item =~ s/(\W|^)ofc\./$1oficína/g;
          $current_item =~ s/(\W|^)pág\./$1páginas/g;
          $current_item =~ s/(\W|^)párr\./$1párrafo/g;
          $current_item =~ s/(\W|^)pbro\./$1presbítero/g;
          $current_item =~ s/(\W|^)pdta\./$1presidénta/g;
          $current_item =~ s/(\W|^)pdte\./$1presidénte/g;
          $current_item =~ s/(\W|^)pg\./$1página/g;
          $current_item =~ s/(\W|^)plza\./$1pláza/g;
          $current_item =~ s/(\W|^)pp\./$1páginas/g;
          $current_item =~ s/(\W|^)presb\./$1presbítero/g;
          $current_item =~ s/(\W|^)prof\./$1profesór/g;
          $current_item =~ s/(\W|^)profa\./$1profesóra/g;
          $current_item =~ s/(\W|^)prov\./$1província/g;
          $current_item =~ s/(\W|^)pza\./$1 pláza/g;
          $current_item =~ s/(\W|^)rte\./$1remiténte/g;
          $current_item =~ s/(\W|^)se\./$1sudéste/g;
          $current_item =~ s/(\W|^)sgto\./$1sargénto/g;
          $current_item =~ s/(\W|^)sig\./$1siguiénte/g;
          $current_item =~ s/(\W|^)so\./$1sudoéste/g;
          $current_item =~ s/(\W|^)sr\./$1señór/g;
          $current_item =~ s/(\W|^)sra\./$1señóra/g;
          $current_item =~ s/(\W|^)srta\./$1señoríta/g;
          $current_item =~ s/(\W|^)ss\./$1siguiéntes/g;
          $current_item =~ s/(\W|^)sta\./$1sánta/g;
          $current_item =~ s/(\W|^)sto\./$1sánto/g;
          $current_item =~ s/(\W|^)tel\./$1teléfono/g;
          $current_item =~ s/(\W|^)teléf\./$1teléfono/g;
          $current_item =~ s/(\W|^)tte\./$1teniénte/g;
          $current_item =~ s/(\W|^)ud\./$1ustéd/g;
          $current_item =~ s/(\W|^)uds\./$1ustédes/g;
          $current_item =~ s/(\W|^)univ\./$1universidád/g;
          $current_item =~ s/(\W|^)vd\./$1ustéd/g;
          $current_item =~ s/(\W|^)vda\./$1viúda/g;
          $current_item =~ s/(\W|^)vdo\./$1viúdo/g;
          $current_item =~ s/(\W|^)vds\./$1ustédes/g;
          $current_item =~ s/(\W|^)vid\./$1víde/g;
          $current_item =~ s/(\W|^)vol\./$1volúmen/g;
          $current_item =~ s/(\W|^)vs\./$1vérsus/g;

          # SINGLE LETTERS WITH ONE PERIOD FOLLOWING ("J. L. Borges")
          $current_item =~ s/(\W|^)a\./$1a /g;
          $current_item =~ s/(\W|^)b\./$1be /g;
          $current_item =~ s/(\W|^)c\./$1se /g;
          $current_item =~ s/(\W|^)d\./$1de /g;
          $current_item =~ s/(\W|^)e\./$1e /g;
          $current_item =~ s/(\W|^)f\./$1éfe /g;
          $current_item =~ s/(\W|^)g\./$1je /g;
          $current_item =~ s/(\W|^)h\./$1áche /g;
          $current_item =~ s/(\W|^)i\./$1i /g;
          $current_item =~ s/(\W|^)j\./$1jóta /g;
          $current_item =~ s/(\W|^)k\./$1ka /g;
          $current_item =~ s/(\W|^)l\./$1éle /g;
          $current_item =~ s/(\W|^)m\./$1éme /g;
          $current_item =~ s/(\W|^)n\./$1éne /g;
          $current_item =~ s/(\W|^)ñ\./$1éñe /g;
          $current_item =~ s/(\W|^)o\./$1o /g;
          $current_item =~ s/(\W|^)p\./$1pe /g;
          $current_item =~ s/(\W|^)q\./$1cu /g;
          $current_item =~ s/(\W|^)r\./$1érre /g;
          $current_item =~ s/(\W|^)s\./$1ése /g;
          $current_item =~ s/(\W|^)t\./$1te /g;
          $current_item =~ s/(\W|^)u\./$1u /g;
          $current_item =~ s/(\W|^)v\./$1$name_for_v /g;
          $current_item =~ s/(\W|^)w\./$1doblebé /g;
          $current_item =~ s/(\W|^)x\./$1équis /g;
          $current_item =~ s/(\W|^)y\./$1igriéga /g;
          $current_item =~ s/(\W|^)z\./$1séta /g;

          #print STDOUT "NON-VRT_ABBREV-DEBUG-BEF:$current_item:\n\n";    # AD HOC DEBUG
     }

     return ($current_item);

}

#################################################################################
#                                                                               #
#   SUBROUTINE: CONVERT NUMERALS TO WORDS & PROCESS PUNCT. INSIDE NUMBERS       #
#                                                                               #
#################################################################################
##############################################################################
#                                                                            #
# SUBROUTINE: NORMALIZE STRUCTURED TOKENS                                   #
#                                                                            #
# Applies high-priority recognizers in a fixed order so that structured     #
# patterns (dates, currency, %, measurements, RUT identifiers) are locked   #
# in BEFORE any generic symbol/number/hyphen rules can fire on their parts. #
#                                                                            #
# Order of recognition:                                                      #
#   1. Structured identifiers (RUT)                                          #
#   2. Dates (d/m/y, d-m-y, d.m.y, y-m-d)                                  #
#   3. Currency expressions (currency code/symbol + number)                  #
#   4. Percentages                                                            #
#   5. Measurements with units (kg, km, °C, m², km/h, etc.)                 #
#   6. Year ranges (1990-2000)                                               #
#   7. Mathematical expressions on bare numbers (x multiplication)           #
#                                                                            #
# All output is plain Spanish words/phrases. The existing pipeline then      #
# processes any remaining numerals and symbols normally.                     #
#                                                                            #
##############################################################################
##############################################################################
#                                                                            #
# SUBROUTINE: PRELC_PASS                                                     #
#                                                                            #
# Applies all case-sensitive token expansions that MUST occur BEFORE lc()   #
# destroys uppercase distinctions. Returns the modified line.                #
#                                                                            #
# Processing order (each stage consumes tokens before the next can see them):#
#   1. Identifier detection  — mixed alphanumeric + all-caps non-Roman      #
#   2. Era markers           — "200 DC" / "43 BC" etc.                      #
#   3. Currency codes        — USD$, MX$, CLP$, S/ (before Roman sees MX)  #
#   4. Roman numerals        — uppercase [IVXLCDM]{2+} sequences            #
#                                                                            #
# This subroutine owns NO state.  All package-level globals it uses          #
# (%_ROM_VAL, $_CURRENCY_CONV) are initialized with //= on first use.       #
#                                                                            #
##############################################################################
##############################################################################
#          SUBROUTINE: SPLIT A NUMBER BY THE CONFIGURED DECIMAL CONVENTION #
##############################################################################
# Given a raw numeric string (digits plus "." and/or "," as separators),
# splits it into (integer_string, decimal_string, is_malformed) according to
# the globally-configured $decimal_symbol -- the OTHER symbol is always
# treated as the thousands (grouping) separator, wherever it appears. This
# is the single, shared implementation used by every number-processing code
# path (currency, percentages, units, temperatures, plain decimals), so the
# decimal/thousands convention is applied consistently everywhere, rather
# than each call site guessing independently.
#
#   decimal_symbol == ","  (default):  "1.234,56" -> ("1234", "56", 0)
#                                       "1.500"    -> ("1500", undef, 0)
#   decimal_symbol == "." :            "1,234.56" -> ("1234", "56", 0)
#                                       "1,500"    -> ("1500", undef, 0)
#
# A trailing bare separator (e.g. "1.234." or "500,"), or the decimal
# symbol followed by 3+ digits with no thousands symbol anywhere else in
# the string (genuinely ambiguous under the declared convention -- could be
# a mistakenly-placed thousands group), are reported as malformed; callers
# that don't track malformed amounts (percentages, units, etc.) can simply
# ignore the third return value and fall through to the plain-integer case.
##############################################################################
sub _split_by_decimal_convention {
     my ($raw) = @_;
     my $dec  = $decimal_symbol;
     my $tho  = ( $dec eq "," ) ? "." : ",";
     my $qdec = quotemeta($dec);
     my $qtho = quotemeta($tho);

     return ( undef, undef, 1 ) if $raw =~ /[.,]$/;

     if ( $raw =~ /$qdec(\d{1,2})$/ ) {
          my ( $int_str, $dec_str ) = split /$qdec/, $raw, 2;
          $int_str =~ s/$qtho//g;
          return ( $int_str, $dec_str, 0 );
     }
     elsif ( $raw =~ /$qdec\d{3,}$/ && $raw !~ /\Q$tho\E/ ) {
          return ( undef, undef, 1 );
     }
     else {
          ( my $int_str = $raw ) =~ s/$qtho//g;
          $int_str =~ s/$qdec//g;
          return ( $int_str, undef, 0 );
     }
}



sub prelc_pass {
     my $line = shift;

     # ── Era markers: runs FIRST — before identifier detection and Roman numerals ──
     # DC = D(500)+C(100) = 600 in Roman numeral arithmetic. Must run before
     # the Roman numeral pass converts DC→600.  Also must run before Category B
     # identifier detection, which would otherwise expand lone "BC", "AD", "CE"
     # letter by letter instead of treating them as era abbreviations.
     # Dotted forms (D.C., A.C., A.D., B.C. etc.) are handled post-lc.
     $line =~ s{\b(\d+)\s+DC\b}{$1 después de cristo}gi;
     $line =~ s{\b(\d+)\s+AC\b}{$1 antes de cristo}gi;
     $line =~ s{\b(\d+)\s+AD\b}{$1 después de cristo}gi;
     $line =~ s{\b(\d+)\s+CE\b}{$1 después de cristo}gi;
     $line =~ s{\b(\d+)\s+BC\b(?!E)}{$1 antes de cristo}gi;
     $line =~ s{\b(\d+)\s+BCE\b}{$1 antes de cristo}gi;

     # ── Currency codes (runs BEFORE identifier detection and Roman numerals) ─
     # Must run before identifier detection so that currency codes immediately
     # followed by digits (USD100, CLP100) are expanded as currency, not spelled
     # out letter by letter. Must also run before Roman numerals to prevent MX
     # (M=1000, X=10) from being mis-expanded before the $ is seen.
     {
          # $_CURRENCY_CONV is a package global so it is constructed only
          # once for the entire run, not once per input line.
          our $_CURRENCY_CONV //= Lingua::ES::Numeros->new(
               DECIMAL => $decimal_symbol, SEPARADORES => ($decimal_symbol eq "," ? "." : ","), UNMIL => '0');

          # Parse a raw amount string → (integer_value, decimal_value), per
          # the globally-configured $decimal_symbol (see
          # _split_by_decimal_convention). Malformed input (ambiguous under
          # the declared convention) falls back to being read as a plain
          # integer -- this helper has no [MalformedNumber] mechanism of its
          # own (only the bare-$ block does); callers that need one should
          # check the third return value of _split_by_decimal_convention
          # directly instead of calling this wrapper.
          sub _pre_parse_amount {
               my $raw = shift;
               my ( $int_str, $dec_str, $malformed ) = _split_by_decimal_convention($raw);
               if ($malformed) {
                    ( $int_str = $raw ) =~ s/[.,]//g;
                    $dec_str = undef;
               }
               return ( int( $int_str || 0 ), defined $dec_str ? int($dec_str) : 0 );
          }

          # _format_currency_amount($iv, $dv, $sg, $pl, $dec_sg, $dec_pl)
          # Converts an already-parsed (int, dec) pair into a Spanish phrase.
          #   $sg/$pl      — singular/plural name for the main monetary unit
          #   $dec_sg/$dec_pl — singular/plural name for the fractional unit
          # Handles apocopation: "uno" → "un", "veintiuno" → "veintiún".
          sub _format_currency_amount {
               my ($iv, $dv, $sg, $pl, $dec_sg, $dec_pl) = @_;
               my $conv = $main::_CURRENCY_CONV;

               my $iw = eval { $conv->cardinal($iv) } // $iv;
               $iw =~ s/\buno\b/un/g;

               if ($dv > 0) {
                    my $iu = ($iv == 1) ? $sg : $pl;
                    my $dw = eval { $conv->cardinal($dv) } // $dv;
                    $dw =~ s/\buno\b(?=\s*$)/un/;
                    $dw =~ s/veintiuno$/veintiún/;
                    $dw =~ s/(\w+) y uno$/$1 y un/;
                    my $du = ($dv == 1) ? $dec_sg : $dec_pl;
                    return "$iw $iu con $dw $du";
               }
               return "$iw " . (($iv == 1) ? $sg : $pl);
          }

          # US$/USD  →  "N dólares estadounidenses [con M centavos]"
          $line =~ s{\b(?:USD?\$?|US\$)\s*(\d[\d.,]*)}{
               my ($iv,$dv) = _pre_parse_amount($1);
               _format_currency_amount($iv,$dv,
                    "dólar estadounidense","dólares estadounidenses",
                    "centavo","centavos")
          }geix;

          # MX$/MXN$  →  "N pesos mexicanos [con M centavos]"
          $line =~ s{\b(?:MX|MXN)\$\s*(\d[\d.,]*)}{
               my ($iv,$dv) = _pre_parse_amount($1);
               _format_currency_amount($iv,$dv,
                    "peso mexicano","pesos mexicanos",
                    "centavo","centavos")
          }geix;

          # CLP$  →  "N pesos chilenos [con M centavos]"
          $line =~ s{\bCLP\$?\s*(\d[\d.,]*)}{
               my ($iv,$dv) = _pre_parse_amount($1);
               _format_currency_amount($iv,$dv,
                    "peso chileno","pesos chilenos",
                    "centavo","centavos")
          }geix;
     }

     # ── S/ (Peruvian sol) ────────────────────────────────────────────────
     # After lc, "S/" becomes "s/" — the normalize S/ regex won't match.
     # Also, change_some_symbols_to_words converts / to eslách (= "s eslách").
     # Expand here before lc destroys the uppercase S.
     # Uses the shared _format_currency_amount helper defined above.
     {
          # Handle S/-N (negative sol): move minus before S/
          $line =~ s{\bS\/-(\d)}{menos S/$1}g;
          # S/N, S/.N, S/ N
          $line =~ s{\bS\/\.?\s*(\d[\d.,]*)}{
               my ($iv,$dv) = _pre_parse_amount($1);
               _format_currency_amount($iv,$dv,
                    "sol","soles",
                    "céntimo","céntimos")
          }gex;
     }
     # ── IDENTIFIER DETECTION (after era markers and currency, before Roman) ─
     # Two categories of tokens are classified as identifiers here, both
     # producing space-separated lowercase Spanish letter/digit names that
     # survive lc() unchanged:
     #
     # Category A — mixed alphanumeric tokens (MUST contain ≥1 letter AND ≥1 digit):
     #   IX01, XX01, R2D2, A380, B52, ABC123, IX-01, ABC-123
     #
     # Category B — all-uppercase letter-only tokens that contain at least one
     #   letter NOT in the Roman numeral alphabet {I,V,X,L,C,D,M}:
     #   IXA (A∉Roman), AIX (A∉Roman), XIVA (A∉Roman), ABC (none Roman)
     #   Excluded: IX, XIV, MMXXV (all letters ∈ {IVXLCDM}) → Roman numeral pass
     #
     # The non-Roman letter set is [ABEFGHJKNOPQRSTUWYZ] — every uppercase letter
     # except I, V, X, L, C, D, M.
     #
     # Era markers (bare DC, AC etc.) always appear with a preceding digit in
     # the patterns we care about (e.g. "200 DC") and are expanded by the era-
     # marker block above, so they never reach this regex.
     #
     # This block MUST fire before Roman numerals so that IX-01, IXA etc. are
     # consumed here rather than being partially matched by the Roman pass.
     # It runs AFTER era markers so that abbreviations like BC, AD, CE (which
     # may appear alone as 2-letter tokens) are already consumed before
     # Category B identifier detection can expand them letter by letter.
     {
          my %__LN = (
               A=>'a',     B=>'be larga', C=>'se',     D=>'de',
               E=>'e',     F=>'efe',      G=>'ge',     H=>'hache',
               I=>'i',     J=>'jota',     K=>'ka',     L=>'ele',
               M=>'eme',   N=>'ene',      O=>'o',      P=>'pe',
               Q=>'cu',    R=>'erre',     S=>'ese',    T=>'te',
               U=>'u',     V=>'ve corta', W=>'doble ve',X=>'equis',
               Y=>'i griega',Z=>'seta',
          );
          my %__DN = (0=>'cero',1=>'uno',2=>'dos',3=>'tres',4=>'cuatro',
                      5=>'cinco',6=>'seis',7=>'siete',8=>'ocho',9=>'nueve');

          # Known unit abbreviations (matching @units_ordered in
          # normalize_structured_tokens): a plain number immediately followed
          # by one of these (e.g. "50kg", "3cm") is a measurement, not an
          # identifier -- leave it alone here so the dedicated MEASUREMENTS
          # WITH UNITS logic can convert it properly instead of spelling it
          # out letter by letter. Case-insensitive since this runs pre-lc.
          my $_unit_suffix_re = qr{(?:km3|cm3|mm3|km2|cm2|mm2|kg|km|cm|mm|ml|m|l|g)}i;

          # Shared expansion: split token into individual chars/hyphens,
          # map each to its Spanish name, join with spaces.
          # Digits → cardinal name; hyphens → "guión"; letters → name from %__LN.
          sub _expand_identifier {
               my ($tok, $ln_ref, $dn_ref) = @_;
               join(' ', map {
                    if (/^\d$/)  { $dn_ref->{$_} // $_ }
                    elsif (/^-$/) { 'guión' }
                    elsif (/^[A-Z]$/) { $ln_ref->{$_} // lc($_) }
                    elsif (/^[a-z]$/) { $ln_ref->{uc($_)} // $_ }
                    else { $_ }
               } split //, $tok);
          }

          # Category A: tokens containing BOTH letters and digits (with optional hyphens).
          # The alternation handles letter-first and digit-first forms.
          $line =~ s{(?<![\w])([A-Za-z][A-Za-z\d]*(?:-[A-Za-z\d]+)*[\d][A-Za-z\d]*(?:-[A-Za-z\d]+)*|[A-Za-z\d]*[\d][A-Za-z\d]*(?:-[A-Za-z\d]+)*[A-Za-z][A-Za-z\d]*)(?![\w])}{
               my $tok = $1;
               ( $tok =~ /^\d+$_unit_suffix_re$/ ) ? $tok : _expand_identifier($tok, \%__LN, \%__DN)
          }gex;

          # Category B: all-uppercase letter sequences that contain ≥1 non-Roman letter.
          # [ABEFGHJKNOPQRSTUWYZ] = uppercase letters NOT in {I,V,X,L,C,D,M}.
          # The token must consist entirely of uppercase letters, length ≥ 2 --
          # a standalone single uppercase letter (e.g. a capitalized "Y" or "O"
          # starting a sentence/clause) is virtually always the ordinary word
          # ("y"/"o" = "and"/"or"), not a real identifier, so it's left alone
          # here and simply lowercased and read as a normal word later.
          # Pure Roman numeral sequences (IX, XIV, MMXXV) contain none of
          # [ABEFGHJKNOPQRSTUWYZ] and so are correctly excluded.
          $line =~ s{(?<![\w])([A-Z]*[ABEFGHJKNOPQRSTUWYZ][A-Z]*)(?![\w\d])}{
               my $tok = $1;
               ( length($tok) == 1 ) ? $tok : _expand_identifier($tok, \%__LN, \%__DN)
          }gex;
     }

     # ── Roman numerals (runs AFTER currency codes) ───────────────────────
     # Inline parser: our %_ROM_VAL is a package global (avoids the Perl
     # "Variable will not stay shared" closure bug with named subs + my vars).
     # Only uppercase sequences matched; lowercase "mil", "vil" etc. are safe.
     # NOTE: Currency codes that contain Roman-letter sequences (MX$) must be
     # expanded BEFORE this block so they are not misinterpreted as numerals.
     {
          our %_ROM_VAL = (I=>1,V=>5,X=>10,L=>50,C=>100,D=>500,M=>1000)
               unless %_ROM_VAL;
          # Reuse the run-level singleton; all Lingua::ES::Numeros objects
          # in this codebase share identical settings.
          our $_CURRENCY_CONV //= Lingua::ES::Numeros->new(
               DECIMAL=>$decimal_symbol, SEPARADORES=>($decimal_symbol eq "," ? "." : ","), UNMIL=>'0');
          my $_lc_conv = $_CURRENCY_CONV;

          # Only match pure Roman numeral tokens:
          # - No adjacent digits (XX01 excluded by \b between X and 0)
          # - Token must consist ONLY of Roman numeral letters (no other chars)
          # This regex uses \b which relies on Perl word boundaries;
          # tokens like "XX01" have no \b between the Roman letters and digits
          # so they are automatically excluded.
          $line =~ s{(?<![\d])\b([IVXLCDM]{2,})\b(?![\d])}{
               my $rom = $1;
               my @c   = split //, $rom;
               my $n   = 0;
               for my $i (0..$#c) {
                    my $cv = $main::_ROM_VAL{$c[$i]} // 0;
                    my $nv = ($i < $#c) ? ($main::_ROM_VAL{$c[$i+1]} // 0) : 0;
                    $n += ($cv < $nv) ? -$cv : $cv;
               }
               $n > 0 ? $_lc_conv->cardinal($n) : $rom
          }gex;

          # Single uppercase I when isolated → "uno" (Roman numeral I).
          $line =~ s{(?<!\w)I(?!\w)}{uno}g;

          # Uppercase X between digits → "multiplicado por" (arithmetic context).
          # Must fire BEFORE X→diez below, so "2 X 4" is caught as multiply
          # and the X is consumed before the standalone rule can see it.
          $line =~ s{\b(\d+)\s*X\s*(\d+)\b}{"$1 multiplicado por $2"}ge;

          # Standalone uppercase X → "diez" (Roman numeral X).
          # Spec §4: lowercase x = equis (handled by spell_out_single_letters after lc).
          #          uppercase X = diez (handled here before lc destroys the case).
          # After the multiply rule above, any remaining isolated uppercase X is
          # a genuine Roman numeral ten (e.g. "capítulo X", "siglo X", "el año X").
          $line =~ s{(?<!\w)X(?!\w)}{diez}g;
     }

     # NOTE: DD/MM/YYYY date validation is handled entirely by
     # normalize_structured_tokens (which runs before fix_misc_punctuation).
     # No pre-lc date filter needed — it caused over-rejection of valid dates
     # like 01/01/2000 (both fields ≤ 12 were incorrectly rejected as ambiguous).

     return $line;
}    # END SUBROUTINE: PRELC_PASS


##############################################################################
#                                                                            #
# SUBROUTINE: POSTLC_LINE_PASS                                               #
#                                                                            #
# Purpose:                                                                   #
#   Applies line-level expansions that must occur AFTER lc() has been        #
#   applied but BEFORE token-level processing (normalize_structured_tokens,  #
#   process_internet_stuff).                                                 #
#                                                                            #
#   Currently handles: dotted and undotted era abbreviations                 #
#     d.C.  a.C.  A.D.  B.C.  C.E.  B.C.E.  (dotted)                      #
#     200 DC  43 BC  100 AD  753 BCE  etc.   (undotted, digit-prefixed)     #
#                                                                            #
# Why a separate pass:                                                       #
#   process_internet_stuff destroys letter.letter sequences (e.g. d.C. →    #
#   "d punto C"), so era abbreviations must be expanded before it runs.      #
#   The pre-lc pass handles UPPERCASE bare forms (DC, AC) when paired with   #
#   a digit; this pass handles lowercase dotted forms that survive lc().     #
#                                                                            #
# Args:    $line  — the lowercased input line                                #
# Returns: $line with era abbreviations replaced by Spanish phrases          #
# Side effects: none                                                         #
#                                                                            #
##############################################################################
sub postlc_line_pass {
     my $line = shift;

     # Dotted Spanish forms (already lowercase after lc)
     $line =~ s{\b[Dd]\.\s*[Cc]\.?}{después de cristo}g;
     $line =~ s{\b[Aa]\.\s*[Cc]\.?}{antes de cristo}g;
     # Dotted English forms: A.D.  B.C.  C.E.  B.C.E.
     $line =~ s{\b[Aa]\.\s*[Dd]\.?}{después de cristo}g;
     $line =~ s{\b[Bb]\.\s*[Cc]\.?(?!\s*[Ee])}{antes de cristo}g;
     $line =~ s{\b[Cc]\.\s*[Ee]\.?}{después de cristo}g;
     $line =~ s{\b[Bb]\.\s*[Cc]\.?\s*[Ee]\.?}{antes de cristo}g;
     # Undotted forms — require a preceding digit to avoid false positives
     $line =~ s{\b(\d+)\s+AD\b}{$1 después de cristo}gi;
     $line =~ s{\b(\d+)\s+CE\b}{$1 después de cristo}gi;
     $line =~ s{\b(\d+)\s+BC\b(?!E)}{$1 antes de cristo}gi;
     $line =~ s{\b(\d+)\s+BCE\b}{$1 antes de cristo}gi;
     $line =~ s{\b(\d+)\s+AC\b}{$1 antes de cristo}gi;
     $line =~ s{\b(\d+)\s+DC\b}{$1 después de cristo}gi;

     return $line;
}    # END SUBROUTINE: POSTLC_LINE_PASS


##############################################################################
#                                                                            #
# SUBROUTINE: PREPARE_WORD_ORTHOGRAPHIC                                      #
#                                                                            #
# Purpose:                                                                   #
#   Applies all orthographic-form cleanup to a single word token BEFORE      #
#   phonemic conversion begins.  Operates on the word as written (Spanish    #
#   orthography) after lc() has been applied to the whole line.              #
#                                                                            #
#   Stages (in order):                                                       #
#     1. Kill common words (testing mode only; $kill_common_words flag)      #
#     2. Modify single orthographic words (lexical exceptions)               #
#     3. Collapse 3+ consecutive identical vowels to two                     #
#     4. Spell out isolated single letters by name                           #
#     5. Fix multi-letter patterns (xml → equisemeéle, ch → CH, etc.)       #
#                                                                            #
# Args:    $word — one word token in orthographic form (already lowercased)  #
# Returns: $word with orthographic cleanup applied                           #
# Side effects: none                                                         #
# Globals read: $kill_common_words, $preclean_orthographic                  #
#                                                                            #
##############################################################################
sub prepare_word_orthographic {
     my $word = shift;

     #######################################################################
     # KILL COMMON WORDS (FOR TESTING PURPOSES ONLY)                       #
     # Eliminates high-frequency words to expose novel transcription errors.#
     #######################################################################
     if ( $kill_common_words == 1 ) {
          $word = kill_common_words($word);
     }

     #######################################################################
     # CLEAN UP WORDS IN THEIR ORTHOGRAPHIC FORM                           #
     # All stages below run only when $preclean_orthographic is set (always #
     # true in normal operation; the flag exists for debugging).            #
     #######################################################################
     if ( $preclean_orthographic == 1 ) {

          # Fix lexical exceptions and known odd spellings
          $word = modify_single_ortho_words($word);

          # Collapse "aaa", "eee", "iii", etc. (3+ repeats) down to two
          if ( $triple_vowels_to_double == 1 ) {
               $word = triple_vowels_to_double_vowels($word);
          }

          # Expand isolated letters to their Spanish names (e.g. "x" → "equis")
          $word = spell_out_single_letters($word);

          # Fix multi-letter patterns (ch, ll, xml, etc.)
          $word = fix_letter_patterns($word);
     }

     return $word;
}    # END SUBROUTINE: PREPARE_WORD_ORTHOGRAPHIC


##############################################################################
#                                                                            #
# SUBROUTINE: TRANSCRIBE_WORD                                                #
#                                                                            #
# Purpose:                                                                   #
#   Applies the full phonemic transcription pipeline to a single             #
#   orthographic word token.  Called once per word from the main loop,       #
#   after prepare_word_orthographic() has finished.                          #
#                                                                            #
#   Pipeline stages (in order):                                              #
#     1.  Punctuation → pause markers                                        #
#     2.  Graphemes → intermediate phoneme representations                   #
#     3.  Intermediate representations → canonical one-character forms       #
#     4.  Diphthong processing                                               #
#     5.  Stress accent placement                                            #
#     6.  Final touches to broad transcription                               #
#     7.  Syllabification                                                    #
#     8.  Backchannel vocalization conversion                                #
#     9.  Tilde-to-IPA-apostrophe conversion (optional)                     #
#     10. YE phoneme representation (optional)                               #
#     11. CH symbol selection (optional)                                     #
#     12. Multi-character phoneme symbols (optional)                         #
#     13. Phoneme transforms (optional)                                      #
#     14. Output format conversion: CV / CVG / CVNLRG / manner / place /    #
#         voicing (all optional)                                             #
#     15. Pause symbol formatting                                            #
#     16. Syllable splitting (optional)                                      #
#     17. Stress mark removal / apostrophe conversion (optional)             #
#     18. VRT placeholder restoration (optional)                             #
#                                                                            #
# Args:    $word — one word token, orthographic cleanup already applied      #
# Returns: $word fully transcribed and formatted per current options         #
# Side effects: none                                                         #
# Globals read: $vrt_format, $stress_using_tildes, $ye_phoneme_is_fricative, #
#   $ch_is_*, $multichars, $output_CV, $output_CVG, $output_CVNLRG,         #
#   $output_manner, $output_place, $voicing, $ipa_pause_symbols,            #
#   $split_at_syllables, $no_stress_marks, $non_ipa_apostrophes,            #
#   $debug_syllab_sub                                                        #
#                                                                            #
##############################################################################
sub transcribe_word {
     my $word = shift;

     #######################################################################
     #                                                                     #
     #                     BROAD (PHONEMIC) TRANSCRIPTION                  #
     #                                                                     #
     #######################################################################

     #######################################################################
     # CONVERT PUNCTUATION TO PAUSES                                       #
     #######################################################################
     $word = convert_punctuation_to_pauses($word);

     #print "CNVRT-PUNCT-TO.PAUSES:$word:\n";    # AD-HOC DEBUG
     #print "AFT-CNVT-PUNCT-PAUSES :$word\n";    # AD-HOC DEBUG

     ############################################################################
     # CONVERT (MOST) GRAPHEMES TO INTERMEDIATE REPRESENTATIONS OF PHONEMES     #
     ############################################################################
     $word = convert_graphemes_to_interm_phonemes($word);

     ############################################################################
     #     CHANGE INTERMEDIATE REPRESENTATIONS OF PHONEMES TO THEIR DEFINITIVE  #
     #                        **ONE-CHARACTER** FORMS                           #
     ############################################################################
     $word = change_interm_phonemes_to_one_char($word);

     #print "CHNG-INTERM-PHNM-1CHR:$word:\n";    # AD-HOC DEBUG

     ############################################################################
     #                           PROCESS DIPHTHONGS                             #
     #           /j/ and /w/ are used as intermediate representations of        #
     #                           non-syllabic vowels                            #
     ############################################################################
     $word = process_diphthongs($word);

     ###############################################################################
     #                                                                             #
     #                   STRESS ACCENT ROUTINE - PREPARATION                       #
     #                                                                             #
     ###############################################################################
     # NOTE: This section need to be *here*, before tildes are removed from vowels.#
     ###############################################################################
     if ( $vrt_format == 0 ) {
          $word = stress_accent_routine($word);
     }
     else {    # Process VRT Files.

          ############################################################################
          # Split each line (utterance) into individual words if VRT format is used. #
          # Multi-word lines are (mainly, if not exclusively) generated by the       #
          # numbers > words routine, while VRT files must be one word per line.      #
          ############################################################################
          my @current_segments = split /\s/, $word;

          foreach my $current_segment (@current_segments) {
               $current_segment = stress_accent_routine($current_segment);
               # Strip the artificial tilde from the -mente suffix (added by
               # replace_multiword_phrases to guide stress placement). The stress
               # engine has already used it; now remove it so it never appears
               # in output. The root tilde/mark already encodes secondary stress.
               $current_segment =~ s/([áéíóú].*)ménte$/$1mente/;
          }

          $word = join( " ", @current_segments );

          #$temp = Encode::encode_utf8( $word );          # AD-HOC DEBUG
          #print STDOUT "AFT-SPLIT-VRT-LINES2WD:$temp\n";    # AD-HOC DEBUG

     }

     ############################################################################
     #                FINAL TOUCHES TO THE BROAD TRANSCRIPTION                  #
     ############################################################################
     $word = final_touches_broad_transcr($word);

     #print STDOUT "AFT-FINAL-TCH-BRD-TR  :$temp\n";           # AD-HOC DEBUG

     ############################################################################
     #                           PERFORM SYLLABIFICATION                        #
     #    If there are any, they supersede both the defaults and the .ini file  #
     ############################################################################
     $word = perform_syllabification($word);

     #$temp = Encode::encode_utf8( $word );                    # AD-HOC DEBUG
     #print STDOUT "AFT-PERFORM-SYLLABIFIC:$temp\n";           # AD-HOC DEBUG

     # REMOVE SPURIOUS SYLLABIFICATION DOTS INSERTED AT BEGINNING OF WORDS     #
     # WHEN A "WORD" IS ACTUALLY A PHRASE (FOR .VRT FORMAT)                    #
     if ( $vrt_format == 1 ) {
          $word =~ s/ \./ /g;

          #print STDOUT "AFT-PERFORM-SYLLABIFIC:$temp\n";           # AD-HOC DEBUG
     }

     #print STDOUT "AFT-PERFORM-SYLLABIFIC :$word\n";           # AD-HOC DEBUG

     if ( $debug_syllab_sub == 1 ) {
          print STDOUT "\t\$word = $word";    # NOTE DEBUG
     }


     ############################################################################
     # CONVERT BACKCHANNEL VOCALIZATIONS (MM, HM, EH...) TO UNICODE IPA         #
     #                         DANGER! WARNING!
     ############################################################################
     $word = convert_backchannel_vocalizations($word);


     #################################################################################
     #                                ==OPTIONAL==                                   #
     #              CONVERT VOWELS WITH TILDES INTO NORMAL VOWELS WITH IPA           #
     #           STRESS ACCENT APOSTROPHES AT THE BEGINNING OF THEIR SYLLABLE        #
     #                             ( es.tá > es.'ta )                                #
     #################################################################################
     if ( $stress_using_tildes == 0 ) {
          $word = opt_tildes_to_ipa_apostrophes($word);

          # Safety net: if a -mente adverb somehow arrived as a single token
          # (not split by replace_multiword_phrases), opt_tildes_to_ipa_apostrophes
          # will have placed two ˈ marks — one on the root, one on "men".
          # Downgrade the root's ˈ to secondary stress ˌ.
          # The regex matches "men" followed by an optional dental diacritic
          # on "t" (t or t̪) and then "e" at end of word or syllable boundary.
          if ( $word =~ /ˈ.*ˈ/ && $word =~ /ˈmen\.t/ ) {
               $word =~ s/ˈ/ˌ/;    # First ˈ only → secondary stress on root
          }
     }

     #######################################################################
     #           REPRESENT PHONEME "YE" AS FRICATIVE (ʝ) (OPTIONAL)        #
     #######################################################################
     if ( $ye_phoneme_is_fricative == 1 ) {
          $word =~ s/ʤ/ʝ/g;
     }

     #######################################################################
     # APPLY SPECIAL "ch" FLAGS THAT MAKE IT EASY TO SELECT SYMBOL USED    #
     #######################################################################
     # This must be run before opt_use_multichars so that it can run using
     # the ʧ symbol alone. It must use a temporary dummy symbol for ʧ and t
     # and later restore them, as multichars will change ʧ to other things
     # and add dental diacritics to freestanding "t" in "ch" representations.
     if ( $ch_is_single_char == 1 ) {
          $word =~ s/ʧ/CHE_PLACEHOLDER/g;
     }
     if ( $ch_is_retracted_with_ligature == 1 ) {
          $word =~ s/ʧ/TEE_PLACEHOLDER̠͡ʃ/g;
     }
     if ( $ch_is_retracted_no_ligature == 1 ) {
          $word =~ s/ʧ/TEE_PLACEHOLDER̠ʃ/g;
     }
     if ( $ch_not_retracted_with_ligature == 1 ) {
          $word =~ s/ʧ/TEE_PLACEHOLDER͡ʃ/g;
     }
     if ( $ch_not_retracted_no_ligature == 1 ) {
          $word =~ s/ʧ/TEE_PLACEHOLDERʃ/g;
     }

     #######################################################################
     #                             ==OPTIONAL==                            #
     #                  USE MULTI-CHARACTER PHONEME SYMBOLS                #
     #                   ( ʧ = t̠͡ʃ,   ʤ = d̠͡ʒ,  etc. )                   #
     #######################################################################
     if ( $multichars == 1 ) {
          $word = opt_use_multichars($word);
     }

     #######################################################################
     # CHANGE DUMMY ʧ and t REPRESENTATIONS BACK to ʧ and t
     #######################################################################
     $word =~ s/CHE_PLACEHOLDER/ʧ/g;
     $word =~ s/TEE_PLACEHOLDER/t/g;

     #######################################################################
     #                   OPTIONAL PHONEME TRANSFORMATIONS                  #
     #######################################################################
     $word = opt_phoneme_transforms($word);

     #######################################################################
     #                                                                     #
     #                    OUTPUT FORMAT TRANSFORMATIONS                    #
     #                                                                     #
     #######################################################################
     # NOTE: CV/CVG/CVNLRG/manner/place/voicing conversion used to happen
     # here, per-word, before words were ever joined into a line. That
     # meant cross-word resyllabification (word_level_syllab_adjust, in
     # the post-processing cleanup pass) ran on already-converted C/V/N/
     # L/R/G (etc.) letters instead of real IPA phonemes, so its regex
     # (which matches literal IPA consonant/vowel characters) never
     # fired for these output modes -- syllabification silently stayed
     # word-level only for every mode but phonemic.
     #
     # Fixed by moving format conversion to AFTER cross-word
     # resyllabification, in the output-file cleanup pass below (see
     # "OUTPUT FORMAT TRANSFORMATIONS (MOVED)" there). $word stays in
     # pure IPA phonemic form all the way through transcribe_word() now,
     # regardless of the selected output mode.
     #######################################################################

     #######################################################################
     # IMPLEMENT PUNCTUATION > PAUSES                                      #
     #######################################################################
     if ( $ipa_pause_symbols == 1 ) {
          $word = punct_to_pauses_ipa($word);
     }

     # Insert non-IPA pauses: (.) (..) (...) ## ###                        #
     else {
          $word = punct_to_pauses_non_ipa($word);
     }

     ##########################################################################
     # SPLIT AT SYLLABLES: PUT A NEWLINE AT EACH SYLLABLE BOUNDARY IF DESIRED #
     ##########################################################################
     if ( $split_at_syllables == 1 ) {
          $word = opt_split_at_syllables($word);
     }

     #######################################################################
     # ELIMINATE STRESS MARKS IF DESIRED                                   #
     #######################################################################
     if ( $no_stress_marks == 1 ) {
          $word =~ s/ˈ//g;
     }

     #######################################################################
     # CONVERT IPA STRESS MARKS TO ORTHOGRAPHIC APOSTROPHES IF DESIRED     #
     #######################################################################
     if ( $non_ipa_apostrophes == 1 ) {
          $word =~ s/ˈ/\'/g;    # MAKE SURE THE APOSTROPHE NEEDS TO BE ESCAPED!
     }

     #######################################################################
     # TRY TO ELIMINATE EXTRA SPACES, WHICH SEEM TO CROP UP AFTER THE LAST #
     # IN A SERIES OF NUMBERS THAT ARE AUTO-CONVERTED.                     #
     #######################################################################
     #print STDOUT "word=$word\n"; # AD-HOC DEBUGGING
     $word =~ s/ $//g;
     $word =~ s/^ //g;

     ############################################################################
     # REPLACE .VRT PLACEHOLDERS                                                #
     ############################################################################
     if ( $vrt_format == 1 ) {
          $word = replace_vrt_placeholders($word);
     }


     return $word;
}    # END SUBROUTINE: TRANSCRIBE_WORD


##############################################################################
#                                                                            #
# SUBROUTINE: PROCESS_LINE                                                   #
#                                                                            #
# Purpose:                                                                   #
#   Applies the full line-level preprocessing pipeline to a single           #
#   lowercased input line.  Called once per input line from the main loop,   #
#   after prelc_pass() and lc() have already been applied.                   #
#                                                                            #
#   Pipeline stages (in order):                                              #
#     1.  VRT column stripping    — extract word column from Connexor .vrt   #
#     2.  VRT char elimination    — remove chars that clash with internals   #
#     3.  postlc_line_pass        — dotted era abbreviations (d.C., B.C.E.)  #
#     4.  normalize_structured_tokens — dates, currency, %, RUT, ordinals,  #
#                                       Roman numerals, math, ranges         #
#     5.  process_internet_stuff  — URLs, e-mail, domain names               #
#     6.  fix_misc_punctuation    — dashes, quotes, odd punct                #
#     7.  expand_abbreviations_ortho — Sr., Prof., etc.                     #
#     8.  convert_times_to_words  — "14:30" → "catorce treinta"             #
#     9.  convert_numerals_to_words — integers, decimals, fractions         #
#    10.  change_some_symbols_to_words — $, &, @, /                        #
#    11.  replace_multiword_phrases — "data show", "blue jeans"             #
#    12.  fix_specific_rarities    — edge cases not covered elsewhere        #
#    13.  fix_odd_characters       — ligatures, unusual Unicode             #
#    14.  VRT preprocessing        — tag/punct → placeholder tokens          #
#                                                                            #
# Args:    $line — the lowercased input line (prelc_pass and lc already done)#
# Returns: $line fully preprocessed, ready to split into word tokens         #
# Side effects: none                                                         #
# Globals read: $vrt_format, $times_with_colon, $numerals_to_words,         #
#               $num_symbol                                                  #
#                                                                            #
##############################################################################
sub process_line {
     my $line = shift;


     ############################################################################
     # .VRT COLUMN STRIPPING                                                    #
     # If reading .vrt text files produced by Connexor, eliminate all text but  #
     # the second column (word forms)                                           #
     ############################################################################
     # TODO: Allow user to specify the column to keep.
     if ( $vrt_format == 1 ) {

          #print STDOUT "VRT-STRIPPING-BEF:$current_item:\n";    # AD-HOC DEBUG

          $line =~ s/^.+?\t(.+?)\t.+$/$1/g;

          #print STDOUT "VRT-STRIPPING-AFT:$current_item:\n";    # AD-HOC DEBUG
     }

     ############################################################################
     # .VRT DISASTEROUS CHARACTER ELIMINATION                                   #
     # Kill odd characters that are used internally by Perkins, before anything #
     # else is replaced.                                                        #
     ############################################################################
     if ( $vrt_format == 1 ) {
          $line = kill_disasterous_vrt_chars($line);
     }

     # ── POST-LC ERA ABBREVIATION EXPANSION ──────────────────────────────
     # Dotted/undotted era markers (d.C., a.C., 43 BC, etc.) must be expanded
     # before process_internet_stuff destroys letter.letter sequences.
     $line = postlc_line_pass($line);

     ############################################################################
     #                          PROCESS INTERNET STUFF                          #
     #        This must be run before "-" is converted into a space, etc.       #
     ############################################################################
     ############################################################################
     # NORMALIZE STRUCTURED TOKENS (dates, currency, %, measurements, RUT IDs) #
     # Runs BEFORE process_internet_stuff to protect structured patterns:       #
     # - "$45,00" must be parsed as currency before "," is touched              #
     # - "12.345.678-9" (RUT) must be handled before "." → "punto"             #
     # - "3.14159" (decimal) must be caught before digits are split             #
     # process_internet_stuff does NOT need to precede this — its concern       #
     # about "-" → space only applies to fix_misc_punctuation which runs later. #
     ############################################################################
     $line = &normalize_structured_tokens($line);

     $line = &process_internet_stuff($line);

     ############################################################################
     #  FIX MISCELLANEOUS PUNCTUATION                                           #
     ############################################################################
     $line = &fix_misc_punctuation($line);

     ############################################################################
     # EXPAND ABBREVIATIONS IN ORTHOGRAPHIC FORM                                #
     ############################################################################
     $line = &expand_abbreviations_ortho($line);

     ############################################################################
     # CONVERT TIMES (e.g. "1:30", "16:44") TO SPANISH WORDS                 #
     ############################################################################
     if ( $times_with_colon == 1 ) {
          $line = &convert_times_to_words($line);
     }

     ############################################################################
     # CONVERT NUMERALS TO WORDS                                                #
     ############################################################################
     if ( $numerals_to_words == 1 ) {
          $line = &convert_numerals_to_words($line);
     }

     # If numeral to word option not selected, turn numbers into a special symbol
     else {
          $line =~ s/[0-9]/$num_symbol/g;
     }

     ############################################################################
     #                   CHANGE CERTAIN SYMBOLS TO WORD FORM                    #
     ############################################################################
     $line = &change_some_symbols_to_words($line);

     ############################################################################
     #  SEARCH AND REPLACE MULTI-WORD PHRASES                                   #
     #  (e.g. "data show", "blue jeans")                                        #
     ############################################################################
     $line = &replace_multiword_phrases($line);

     ############################################################################
     #                   PRE-PROCESSING: Fix specific rarities                  #
     ############################################################################
     $line = &fix_specific_rarities($line);

     ############################################################################
     #                   PRE-PROCESSING: Fix odd characters                     #
     ############################################################################
     $line = &fix_odd_characters($line);

     ############################################################################
     # VRT PRE-PROCESSING                                                       #
     # If reading .vrt text files, replace <tags>, commas, periods, etc. with   #
     # special characters (which will be converted into original symbols later) #
     ############################################################################
     if ( $vrt_format == 1 ) {
          $line = &do_vrt_preprocessing($line);

          #print "\nAFT-VRT-PRE-PROCESSING:$line\n";    # AD-HOC DEBUG
     }

     return $line;
}    # END SUBROUTINE: PROCESS_LINE


##############################################################################
#                                                                            #
# SUBROUTINE: BUFFER_WORD                                                    #
#                                                                            #
# Purpose:                                                                   #
#   Appends a processed word (and its orthographic form) to the per-line     #
#   output buffers, handling the special case of -mente adverb fragments.   #
#                                                                            #
#   The replace_multiword_phrases() routine splits -mente adverbs into two   #
#   tokens: the root ("rápida") and a continuation fragment ("_mente").      #
#   Continuation fragments are marked with a leading underscore.  When one   #
#   is detected, it is appended to the PREVIOUS buffered word rather than    #
#   stored as a new entry, so that MFA and phonword output always see the    #
#   complete adverb as one unit regardless of output mode.                   #
#                                                                            #
#   Stress adjustment: before joining a _mente fragment the root word's     #
#   primary stress ˈ is downgraded to secondary stress ˌ, because the root  #
#   carries secondary stress while "men" carries primary stress.             #
#                                                                            #
# Args:                                                                      #
#   $word          — transcribed word (may begin with "_" for continuation)  #
#   $ortho_word    — orthographic snapshot of the same word                  #
#   $words_ref     — ref to the per-line output word buffer (array)          #
#   $ortho_ref     — ref to the per-line orthographic buffer (array)         #
#   $stress_tildes — 1 if stress is represented by tildes, 0 for IPA ˈ/ˌ   #
# Returns: nothing (mutates $$words_ref and $$ortho_ref in place)            #
# Side effects: none beyond mutating the passed arrays                       #
#                                                                            #
##############################################################################
sub buffer_word {
     my ($word, $ortho_word, $words_ref, $ortho_ref, $stress_tildes) = @_;

     if ( $word =~ /^_/ && @$words_ref ) {
          $word       =~ s/^_//;
          $ortho_word =~ s/^_//;

          # Strip the artificial tilde on "é" in the _mente fragment.
          # It was added by replace_multiword_phrases() to guide stress
          # placement; the engine has used it and it must not appear in output.
          $ortho_word =~ s/ménte/mente/;
          $word       =~ s/é/e/g;

          # Downgrade root primary stress to secondary before joining.
          if ( !$stress_tildes ) {
               $words_ref->[-1] =~ s/ˈ/ˌ/;
          }

          $words_ref->[-1] .= $word;
          $ortho_ref->[-1] .= $ortho_word;
     }
     else {
          $word       =~ s/^_//;
          $ortho_word =~ s/^_//;
          $ortho_word =~ s/ménte/mente/;
          push @$words_ref, $word;
          $ortho_word =~ s/[^\p{L}]//g;
          push @$ortho_ref, $ortho_word;
     }
}    # END SUBROUTINE: BUFFER_WORD


##############################################################################
#                                                                            #
# SUBROUTINE: FORMAT_OUTPUT_WORD                                             #
#                                                                            #
# Purpose:                                                                   #
#   Applies the selected output format to a single transcribed word,         #
#   returning the final string to be written to the output file.             #
#   Returns undef when the word should be skipped (empty G2P entries, etc.). #
#                                                                            #
#   Output formats:                                                          #
#     Default:   the transcribed word verbatim                               #
#     -g2p:      "ortho<TAB>p h o n e m e s"  (G2P dictionary format)       #
#     -phonword: "Ortho<TAB>tran.scrip.tion"  (Praat word/phonword tiers)   #
#                                                                            #
# Args:                                                                      #
#   $final_word  — the transcribed word (post all phonological processing)   #
#   $ortho       — the orthographic form of the same word                    #
# Returns: the formatted output string, or undef to skip this word           #
# Side effects: none (I/O is done by the caller)                             #
# Globals read: $g2p_format, $phonword_format                                #
#                                                                            #
##############################################################################
sub format_output_word {
     # Args: $final_word, $ortho
     # Optional flags can be passed as a hashref third arg for testing:
     #   format_output_word($w, $o, { g2p => 1 })
     # When no flags hashref is given, globals $g2p_format and $phonword_format are used.
     my ($final_word, $ortho, $flags) = @_;
     my $g2p_fmt = $flags ? ($flags->{g2p}      // 0) : $g2p_format;
     my $pw_fmt  = $flags ? ($flags->{phonword}  // 0) : $phonword_format;

     if ( $g2p_fmt == 1 ) {
          # G2P format: "ortho<TAB>p h o n e m e s"
          # orthographic form keeps any accent marks (tildes) exactly as they
          # appeared in the original word, is stripped of non-letter chars,
          # and lowercased; phonemes are space-separated.
          my $phoneme_string = build_g2p_phoneme_string($final_word);
          return undef if $phoneme_string eq '';

          my $clean_ortho = $ortho;
          $clean_ortho =~ s/[^\p{L}]//g;
          $clean_ortho = lc($clean_ortho);
          return undef if $clean_ortho eq '';

          return "$clean_ortho\t$phoneme_string";
     }
     elsif ( $pw_fmt == 1 ) {
          # Phonword format: "Ortho<TAB>tran.scrip.tion"
          # orthographic form retains original capitalisation and tildes.
          return undef if $ortho eq '';
          return "$ortho\t$final_word";
     }

     # Default: transcribed word as-is
     return $final_word;
}    # END SUBROUTINE: FORMAT_OUTPUT_WORD


sub normalize_structured_tokens {

     my $current_item = $_[0];

     # Use the run-level singleton converter rather than constructing a new
     # Lingua::ES::Numeros object on every call to this function.
     # $_CURRENCY_CONV is initialized in the pre-lc currency block on the
     # first line of input that contains a currency token. For lines processed
     # before any currency appears we fall back to a local construction, but
     # after that the singleton is reused for the entire run.
     our $_CURRENCY_CONV //= Lingua::ES::Numeros->new(
          DECIMAL     => $decimal_symbol,
          SEPARADORES => ($decimal_symbol eq "," ? "." : ","),
          UNMIL       => '0',
     );
     my $conv = $_CURRENCY_CONV;

     # Month name lookup
     my @MONTH_ES = qw(
          enero febrero marzo abril mayo junio
          julio agosto septiembre octubre noviembre diciembre
     );

     ########################################################################
     # PRE-PASS 0: DOUBLE PUNCTUATION NORMALIZATION                          #
     #   ",," → ","  |  "..." is preserved  |  "...." → "..."               #
     ########################################################################
     # Collapse multiple commas to one
     $current_item =~ s/,{2,}/,/g;
     # Four or more dots → ellipsis (three dots)
     $current_item =~ s/\.{4,}/.../g;
     # Exactly two dots → single dot (not ellipsis)
     $current_item =~ s/(?<!\.)\.{2}(?!\.)/./g;

     ########################################################################
     # PRE-PASS: LEADING MINUS SIGN (negative numbers / negative currency)   #
     # Must run FIRST — before currency blocks consume the $ symbol.         #
     # S/-N is handled specially: the minus is moved to before "S/" so that  #
     # the S/ regex still sees a complete "S/N" token.                       #
     ########################################################################
     # S/-N → menos S/N  (keep the S/ intact for the currency block)
     $current_item =~ s{\bS\/-(\d)}{menos S/$1}g;
     # -$N, -€N, -£N → menos $N etc.
     $current_item =~ s{(?<![\w\d,.])-(?=\s*[\$€£]\d)}{menos }g;
     # bare -N
     $current_item =~ s{(?<![\w\d,.])-(?=\s*\d)}{menos }g;

     ########################################################################
     # PRE-PASS 1b: STANDALONE DECIMAL NUMBERS → digit-by-digit             #
     # Catches "3,14159", "0,25", "-0,25", "250,75", "-250,75" (or the same #
     # with "." if --decimal-period is set) BEFORE the generic integer      #
     # rules expand them incorrectly.                                        #
     # Pattern: (optional "menos ") integer part + decimal symbol + 2 or     #
     # more digits. Does NOT fire on date/measurement/currency patterns      #
     # (already consumed above), and requires 2+ decimal digits to avoid     #
     # conflict with "N." sentence-final punctuation when "." is the        #
     # configured decimal symbol.                                            #
     ########################################################################
     {
          my %_DN = (0=>'cero',1=>'uno',2=>'dos',3=>'tres',4=>'cuatro',
                     5=>'cinco',6=>'seis',7=>'siete',8=>'ocho',9=>'nueve');
          my $qdec = quotemeta($decimal_symbol);
          my $qtho = quotemeta(($decimal_symbol eq ',') ? '.' : ',');
          # Match: optional "menos ", integer part, decimal symbol, 2+ digits.
          # Use negative lookbehind/lookahead to avoid matching inside larger
          # patterns (e.g. already-converted currency or date strings, or a
          # thousands-grouped number with no currency prefix).
          $current_item =~ s{
               (?<![\d$qdec$qtho\x{24}\x{20ac}\x{a3}])  # not preceded by digit, decimal/thousands symbol, \$, €, £
               (menos\s+)?             # optional negative marker
               (\d+)                   # integer part
               $qdec                   # decimal point (per configured convention)
               (\d{2,})               # 2+ decimal digits (not a sentence period)
               (?![\d])                # not followed by a digit
               (?![.,])                # not followed by dot or comma (thousands context)
          }{
               my ($neg, $int_p, $frac) = ($1//'', $2, $3);
               my $iw = eval { $conv->cardinal(int($int_p)) } // $int_p;
               my @fw = map { $_DN{$_} // $_ } split //, $frac;
               $neg . $iw . " coma " . join(' ', @fw)
          }gex;
     }

     ########################################################################
     # 0. IDENTIFIER DETECTION AND VERBALIZATION                             #
     # Mixed letter+digit tokens (ABC123, XX01, IX01, R2D2, A380, B52)      #
     # are classified as identifiers and verbalized component-by-component.  #
     # This must fire BEFORE Roman numeral and numeric processing.           #
     ########################################################################
     {
          my %_LETTER_NAMES = (
               a=>'a',     b=>'be larga', c=>'se',     d=>'de',
               e=>'e',     f=>'efe',      g=>'ge',     h=>'hache',
               i=>'i',     j=>'jota',     k=>'ka',     l=>'ele',
               m=>'eme',   n=>'ene',      'ñ'=>'eñe',  o=>'o',
               p=>'pe',    q=>'cu',       r=>'erre',   s=>'ese',
               t=>'te',    u=>'u',        v=>'ve corta',w=>'doble ve',
               x=>'equis', y=>'i griega', z=>'seta',
          );
          my %_DIGIT_NAMES = (
               0=>'cero',1=>'uno',2=>'dos',3=>'tres',4=>'cuatro',
               5=>'cinco',6=>'seis',7=>'siete',8=>'ocho',9=>'nueve',
          );
          # Known unit abbreviations (matching @units_ordered below): a plain
          # number immediately followed by one of these (e.g. "50kg", "3cm")
          # is a measurement, not an identifier -- leave it alone here so the
          # dedicated MEASUREMENTS WITH UNITS logic (section 5, below) can
          # convert it properly instead of spelling it out letter by letter.
          my $_unit_suffix_re = qr{(?:km3|cm3|mm3|km2|cm2|mm2|kg|km|cm|mm|ml|m|l|g)};
          # Detect mixed tokens: contain BOTH letters (\p{L}) and digits (\d).
          # Process word-by-word so we only affect genuine mixed tokens.
          $current_item =~ s{\b([a-z\x{e1}\x{e9}\x{ed}\x{f3}\x{fa}\x{f1}]+(?:\d[a-z\x{e1}\x{e9}\x{ed}\x{f3}\x{fa}\x{f1}0-9]*|[a-z\x{e1}\x{e9}\x{ed}\x{f3}\x{fa}\x{f1}0-9]*\d[a-z\x{e1}\x{e9}\x{ed}\x{f3}\x{fa}\x{f1}0-9]*))\b}{
               my $tok = $1;
               # Only expand if it contains both letters and digits
               if ($tok =~ /^\d+$_unit_suffix_re$/) {
                    $tok
               } elsif ($tok =~ /[a-z\x{e1}\x{e9}\x{ed}\x{f3}\x{fa}\x{f1}]/ && $tok =~ /\d/) {
                    join(' ', map {
                         if (/^\d$/) { $_DIGIT_NAMES{$_} // $_ }
                         elsif (/^[a-z\x{e1}\x{e9}\x{ed}\x{f3}\x{fa}\x{f1}]$/) { $_LETTER_NAMES{$_} // $_ }
                         else { $_ }
                    } split //, $tok)
               } else { $tok }
          }gex;
     }

     ########################################################################
     # 0a. TIME EXPRESSIONS (HH:MM)                                          #
     #     Expand times to words here (line-level) before the integer        #
     #     expansion rules in convert_numerals_to_words can fire on the       #
     #     hour digits, consuming them and leaving ":MM" unparseable.        #
     ########################################################################
     {
          # Reuse singleton converter (same settings throughout the codebase)
          my $_tc = $main::_CURRENCY_CONV
               // Lingua::ES::Numeros->new(DECIMAL=>$decimal_symbol,SEPARADORES=>($decimal_symbol eq "," ? "." : ","),UNMIL=>'0');
          $current_item =~ s{\b(\d{1,2}):(\d{2})\b}{
               my ($h,$m) = (int($1), int($2));
               my $hw = ($h==1) ? "una" : (eval { $_tc->cardinal($h) } // $h);
               my $mw = ($m==0) ? "" : " " . (eval { $_tc->cardinal($m) } // $m);
               "$hw$mw"
          }ge;
     }

     ########################################################################
     # 0b. d.C. / a.C. — ERA ABBREVIATIONS                                  #
     #     Expand compact forms (d.C., a.C.) before date and range rules     #
     #     to prevent "d punto se" in the phonological output.               #
     #     Lowercase and uppercase handled; dot after C is optional.         #
     ########################################################################
     $current_item =~ s{\b[Dd]\.[Cc]\.?}{después de cristo}g;
     $current_item =~ s{\b[Aa]\.[Cc]\.?}{antes de cristo}g;

     ########################################################################
     # 1. DATES                                                              #
     #    Recognize BEFORE any slash/hyphen/arithmetic rules.                #
     #    Formats supported (all produce "D de MES de YYYY"):               #
     #      yyyy-mm-dd   yyyy/mm/dd   yyyy.mm.dd                            #
     #      dd/mm/yyyy   dd-mm-yyyy   dd.mm.yyyy                            #
     ########################################################################

     # ISO format: yyyy-mm-dd
     $current_item =~ s{
          \b(\d{4})-(\d{1,2})-(\d{1,2})\b
     }{
          my ($y,$mo,$d) = (int($1), int($2), int($3));
          my $dw  = $conv->cardinal($d);
          my $mw  = ($mo >= 1 && $mo <= 12) ? $MONTH_ES[$mo-1] : $conv->cardinal($mo);
          my $yw  = $conv->cardinal($y);
          "$dw de $mw de $yw"
     }gex;

     # ISO with slashes: yyyy/mm/dd
     $current_item =~ s{
          \b(\d{4})/(\d{1,2})/(\d{1,2})\b
     }{
          my ($y,$mo,$d) = (int($1), int($2), int($3));
          my $dw  = $conv->cardinal($d);
          my $mw  = ($mo >= 1 && $mo <= 12) ? $MONTH_ES[$mo-1] : $conv->cardinal($mo);
          my $yw  = $conv->cardinal($y);
          "$dw de $mw de $yw"
     }gex;

     # ISO with dots: yyyy.mm.dd
     $current_item =~ s{
          \b(\d{4})\.(\d{1,2})\.(\d{1,2})\b
     }{
          my ($y,$mo,$d) = (int($1), int($2), int($3));
          my $dw  = $conv->cardinal($d);
          my $mw  = ($mo >= 1 && $mo <= 12) ? $MONTH_ES[$mo-1] : $conv->cardinal($mo);
          my $yw  = $conv->cardinal($y);
          "$dw de $mw de $yw"
     }gex;

     # dd/mm/yyyy — validate calendar date before expanding.
     $current_item =~ s{
          \b(\d{1,2})/(\d{1,2})/(\d{2,4})\b
     }{
          my ($d,$mo,$y) = (int($1), int($2), int($3));
          # Days-per-month table (Feb gets special treatment for leap years)
          my @_MAX_DAYS = (0,31,28,31,30,31,30,31,31,30,31,30,31);
          # Leap year: divisible by 4, except centuries unless divisible by 400
          my $is_leap = ($y % 4 == 0 && ($y % 100 != 0 || $y % 400 == 0));
          $_MAX_DAYS[2] = 29 if $is_leap;
          if ($mo >= 1 && $mo <= 12 && $d >= 1 && $d <= $_MAX_DAYS[$mo]) {
               my $dw = $conv->cardinal($d);
               my $mw = $MONTH_ES[$mo-1];
               my $yw = $conv->cardinal($y);
               "$dw de $mw de $yw"
          } else {
               "[InvalidDate]"
          }
     }gex;

     # dd-mm-yyyy with calendar validation
     $current_item =~ s{
          \b(\d{1,2})-(\d{1,2})-(\d{2,4})\b
     }{
          my ($d,$mo,$y) = (int($1), int($2), int($3));
          my @_MD2 = (0,31,28,31,30,31,30,31,31,30,31,30,31);
          my $lp2 = ($y%4==0 && ($y%100!=0 || $y%400==0));
          $_MD2[2] = 29 if $lp2;
          if ($mo >= 1 && $mo <= 12 && $d >= 1 && $d <= $_MD2[$mo]) {
               my $dw = $conv->cardinal($d);
               my $mw = $MONTH_ES[$mo-1];
               my $yw = $conv->cardinal($y);
               "$dw de $mw de $yw"
          } else {
               "$1-$2-$3"
          }
     }gex;

     # dd.mm.yyyy with calendar validation
     $current_item =~ s{
          \b(\d{1,2})\.(\d{1,2})\.(\d{2,4})\b
     }{
          my ($d,$mo,$y) = (int($1), int($2), int($3));
          my @_MD3 = (0,31,28,31,30,31,30,31,31,30,31,30,31);
          my $lp3 = ($y%4==0 && ($y%100!=0 || $y%400==0));
          $_MD3[2] = 29 if $lp3;
          if ($mo >= 1 && $mo <= 12 && $d >= 1 && $d <= $_MD3[$mo]) {
               my $dw = $conv->cardinal($d);
               my $mw = $MONTH_ES[$mo-1];
               my $yw = $conv->cardinal($y);
               "$dw de $mw de $yw"
          } else {
               "$1.$2.$3"
          }
     }gex;

     ########################################################################
     # 3. CURRENCY EXPRESSIONS                                               #
     #    Recognized BEFORE generic symbol and numeral rules.                #
     #    The leading-minus pre-pass above ensures "-$N" → "menos $N"        #
     #    so negative currency amounts like "-$1.000,89" become              #
     #    "menos mil pesos con ochenta y nueve centavos" here.               #
     ########################################################################

     # ── CURRENCY CLASSIFICATION TABLE ─────────────────────────────────────
     # Each entry: [plural_unit, singular_unit, decimal_plural, decimal_singular]
     # singular_unit: derived by rule — strip trailing -es (if root ends in consonant)
     # else strip -s. Stored explicitly to avoid ambiguity.
     my %CURR = (
          'USD' => ['dólares estadounidenses', 'dólar estadounidense', 'centavos', 'centavo'],
          'US'  => ['dólares estadounidenses', 'dólar estadounidense', 'centavos', 'centavo'],
          'MXN' => ['pesos mexicanos',         'peso mexicano',        'centavos', 'centavo'],
          'MX'  => ['pesos mexicanos',         'peso mexicano',        'centavos', 'centavo'],
          'CLP' => ['pesos chilenos',          'peso chileno',         'centavos', 'centavo'],
          'EUR' => ['euros',                   'euro',                 'céntimos', 'céntimo'],
          'GBP' => ['libras esterlinas',       'libra esterlina',      'peniques', 'penique'],
          'PEN' => ['soles',                   'sol',                  'céntimos', 'céntimo'],
     );
     # Decimal units for the bare-$ currency (determined by $moneda).
     # Rule: centavo/centavos for peso-family; céntimo/céntimos for euro/sol family.
     # The general derivation: look up $moneda in a map; default to centavo/centavos.
     my %MONEDA_DEC = (
          'pesos'   => ['centavos','centavo'],   'peso'   => ['centavos','centavo'],
          'dólares' => ['centavos','centavo'],   'dólar'  => ['centavos','centavo'],
          'euros'   => ['céntimos','céntimo'],   'euro'   => ['céntimos','céntimo'],
          'libras'  => ['peniques','penique'],   'libra'  => ['peniques','penique'],
          'soles'   => ['céntimos','céntimo'],   'sol'    => ['céntimos','céntimo'],
     );

     # ── SINGULAR DERIVATION (general rule, no hardcodes) ──────────────────
     # Given a plural currency name (possibly multi-word), derive the singular.
     # Rule: the LAST word of the plural determines singularity.
     #   If it ends in -es AND the character before -es is a consonant → strip -es.
     #   Otherwise strip trailing -s.
     # Examples: dólares→dólar, soles→sol, euros→euro, libras→libra, pesos→peso.
     sub _sg {
          my $pl = shift;
          my @words = split /\s+/, $pl;
          for my $w (@words) {
               if ($w =~ /[^aeiouáéíóúü]es$/i) { $w =~ s/es$// }
               elsif ($w =~ /s$/)               { $w =~ s/s$// }
          }
          join(' ', @words)
     }

     # ── AMOUNT FORMATTER (single function for all currencies) ─────────────
     # Args: $int_val, $dec_val, $unit_pl, $unit_sg, $dec_pl, $dec_sg, $conv
     # Returns the fully formatted Spanish string.
     sub _fmt_amount {
          my ($int_val, $dec_val, $unit_pl, $unit_sg, $dec_pl, $dec_sg, $cv) = @_;
          my $iw = eval { $cv->cardinal($int_val) } // $int_val;
          $iw =~ s/\buno\b/un/g;
          my $unit = ($int_val == 1 && $dec_val == 0) ? $unit_sg : $unit_pl;
          if ($dec_val > 0) {
               my $dw = eval { $cv->cardinal($dec_val) } // $dec_val;
               # Apocopation before masculine decimal unit
               $dw =~ s/\buno\b(?=\s*$)/un/;
               $dw =~ s/veintiuno$/veintiún/;
               $dw =~ s/(\w+) y uno$/$1 y un/;
               my $du = ($dec_val == 1) ? $dec_sg : $dec_pl;
               my $iu = ($int_val == 1) ? $unit_sg : $unit_pl;
               return "$iw $iu con $dw $du";
          }
          return "$iw $unit";
     }

     # Legacy map for code still using %CURR_NAME directly
     my %CURR_NAME = map { $_ => $CURR{$_}[0] } keys %CURR;
     # Make CURR table accessible to named subs that can't close over lexicals
     our %_CURR_TABLE = %CURR;

     # USD/US$ amount  (e.g. USD100, USD 100, US$100, US$100.00, US$1234.56)
     # Amount parsing delegated to the canonical _pre_parse_amount helper.
     $current_item =~ s{
          \b(USD?)\$?\s*(\d[\d.,]*)
     }{
          my ($code, $raw) = ($1, $2);
          my $c = $CURR{$code} // $CURR{'USD'};
          my ($iv, $dv) = _pre_parse_amount($raw);
          _fmt_amount($iv, $dv, $c->[0], $c->[1], $c->[2], $c->[3], $conv)
     }gex;

     # MX$amount (Mexican peso)
     $current_item =~ s{
          \bMX\$\s*(\d[\d.,]*)
     }{
          my $c = $CURR{'MX'};
          my ($iv, $dv) = _pre_parse_amount($1);
          _fmt_amount($iv, $dv, $c->[0], $c->[1], $c->[2], $c->[3], $conv)
     }gex;

     # CLP$amount or CLP amount or CLP100
     $current_item =~ s{
          \b(CLP)\$?\s*(\d[\d.,]*)
     }{
          my $c = $CURR{$1} // $CURR{'CLP'};
          my ($iv, $dv) = _pre_parse_amount($2);
          _fmt_amount($iv, $dv, $c->[0], $c->[1], $c->[2], $c->[3], $conv)
     }gex;

     # S/amount  (Peruvian sol)
     # Handles: S/100, S/.100, S/ 100, S/120,50, S/120.50
     # Negative: S/-250.75 → pre-pass converted to "menos S/250.75"; the minus
     # prefix is left in place and the S/ regex handles the positive amount.
     {
          sub _parse_sol_amount {
               # Parse a raw S/ amount string and return the Spanish phrase.
               # Delegates amount parsing to the canonical _pre_parse_amount helper
               # so there is a single implementation of the parsing logic.
               my ($raw) = @_;
               my $cv = $main::_CURRENCY_CONV
                    // Lingua::ES::Numeros->new(DECIMAL=>$decimal_symbol,SEPARADORES=>($decimal_symbol eq "," ? "." : ","),UNMIL=>'0');
               my ($iv, $dv) = _pre_parse_amount($raw);
               my $c = $main::_CURR_TABLE{'PEN'} // ['soles','sol','céntimos','céntimo'];
               return _fmt_amount($iv, $dv, $c->[0], $c->[1], $c->[2], $c->[3], $cv);
          }

          # Match the full amount string after S/ (including decimal separator)
          # and pass it to _parse_sol_amount for parsing.
          # "menos S/250.75": the "menos" prefix is already in $current_item before
          # the S/ token; the regex matches only the S/... part, leaving "menos " intact.
          $current_item =~ s{
               \bS\/\.?\s*(\d[\d.,]*)
          }{
               _parse_sol_amount($1)
          }gex;
     }

     # €amount or amount€
     $current_item =~ s{
          €\s*(\d[\d.,]*)
     }{
          my $num = $1;
          $num =~ s/\./X/g; $num =~ s/,/./g; $num =~ s/X/,/g;
          my $nw = eval { $conv->cardinal($num) } // $num;
          "$nw euros"
     }gex;
     $current_item =~ s{
          (\d[\d.,]*)\s*€
     }{
          my $num = $1;
          $num =~ s/\./X/g; $num =~ s/,/./g; $num =~ s/X/,/g;
          my $nw = eval { $conv->cardinal($num) } // $num;
          "$nw euros"
     }gex;

     # £amount
     $current_item =~ s{
          £\s*(\d[\d.,]*)
     }{
          my $num = $1;
          $num =~ s/\./X/g; $num =~ s/,/./g; $num =~ s/X/,/g;
          my $nw = eval { $conv->cardinal($num) } // $num;
          "$nw libras"
     }gex;

     # Bare $amount — after all prefixed-currency rules.
     # Parsed according to the globally-configured $decimal_symbol (see
     # _split_by_decimal_convention): the OTHER symbol is always the
     # thousands separator. Genuinely malformed/ambiguous amounts (under
     # the declared convention) become [MalformedNumber].
     $current_item =~ s{
          (?<![A-Za-z])\$\s*(\d[\d.,]*\d|\d)   # must end in digit, not separator
     }{
          my $raw = $1;
          my ($int_str, $dec_str, $malformed) = _split_by_decimal_convention($raw);
          if ($malformed) {
               " [MalformedNumber] "
          } else {

          my $int_val = int($int_str || 0);
          my $iw = eval { $conv->cardinal($int_val) } // $int_str;
          $iw =~ s/\buno\b/un/g;
          my $dec_val = (defined $dec_str && $dec_str ne '') ? int($dec_str) : 0;
          # Singular/plural for $moneda: "un peso" vs "dos pesos".
          # $moneda is the plural form (e.g. "pesos"); derive singular by
          # stripping trailing "s" or "es" heuristically, or use a lookup.
          # Singular/plural from $moneda using the general _sg() rule.
          # Decimal unit from %MONEDA_DEC table (no hardcodes).
          my $_moneda_pl = $main::moneda;
          my $_moneda_sg = _sg($_moneda_pl);
          my $_dec_pair  = $MONEDA_DEC{$_moneda_pl} // $MONEDA_DEC{$_moneda_sg} // ['centavos','centavo'];
          _fmt_amount($int_val, $dec_val, $_moneda_pl, $_moneda_sg, $_dec_pair->[0], $_dec_pair->[1], $conv)
          } # end else (non-malformed)
     }gex;

     ########################################################################
     # 4. PERCENTAGES                                                        #
     #    Before the % symbol is stripped by other rules.                    #
     #    Decimal separator per the configured $decimal_symbol (see          #
     #    _split_by_decimal_convention); the other symbol is thousands.      #
     #    Decimal part is read as a cardinal: 40,45% → "cuarenta y cinco".   #
     ########################################################################
     $current_item =~ s{
          (\d+(?:[.,]\d+)?)\s*%
     }{
          my $num = $1;
          my ($int_str, $dec_str, $malformed) = _split_by_decimal_convention($num);
          if ($malformed) {
               ($int_str = $num) =~ s/[.,]//g;
               $dec_str = undef;
          }
          my $nw;
          if (defined $dec_str) {
               my $iw = eval { $conv->cardinal(int($int_str)) } // $int_str;
               my $dw = eval { $conv->cardinal(int($dec_str)) } // $dec_str;
               $nw = "$iw coma $dw";
          } else {
               $nw = eval { $conv->cardinal(int($int_str)) } // $int_str;
          }
          "$nw por ciento"
     }gex;

     ########################################################################
     # 5. MEASUREMENTS WITH UNITS                                            #
     #    Number (integer or decimal) followed by a unit.                    #
     #    Most-specific units first (cubic before squared before base).       #
     ########################################################################
     my %UNIT_ES = (
          'km/h'  => 'kilómetros por hora',
          'm/s'   => 'metros por segundo',
          'km2'   => 'kilómetros cuadrados',
          'km²'   => 'kilómetros cuadrados',
          'm2'    => 'metros cuadrados',
          'm²'    => 'metros cuadrados',
          'cm2'   => 'centímetros cuadrados',
          'cm²'   => 'centímetros cuadrados',
          'mm2'   => 'milímetros cuadrados',
          'mm²'   => 'milímetros cuadrados',
          'km3'   => 'kilómetros cúbicos',
          'km³'   => 'kilómetros cúbicos',
          'm3'    => 'metros cúbicos',
          'm³'    => 'metros cúbicos',
          'cm3'   => 'centímetros cúbicos',
          'cm³'   => 'centímetros cúbicos',
          'mm3'   => 'milímetros cúbicos',
          'mm³'   => 'milímetros cúbicos',

          'kg'    => 'kilogramos',
          'g'     => 'gramos',
          'km'    => 'kilómetros',
          'cm'    => 'centímetros',
          'mm'    => 'milímetros',
          'ml'    => 'mililitros',
          'l'     => 'litros',
          'm'     => 'metros',
     );
     # Singular forms used when the numeric value is exactly 1
     my %UNIT_ES_SG = (
          'km/h'  => 'kilómetro por hora',
          'm/s'   => 'metro por segundo',
          'km2'   => 'kilómetro cuadrado',
          'km²'   => 'kilómetro cuadrado',
          'm2'    => 'metro cuadrado',
          'm²'    => 'metro cuadrado',
          'cm2'   => 'centímetro cuadrado',
          'cm²'   => 'centímetro cuadrado',
          'mm2'   => 'milímetro cuadrado',
          'mm²'   => 'milímetro cuadrado',
          'km3'   => 'kilómetro cúbico',
          'km³'   => 'kilómetro cúbico',
          'm3'    => 'metro cúbico',
          'm³'    => 'metro cúbico',
          'cm3'   => 'centímetro cúbico',
          'cm³'   => 'centímetro cúbico',
          'mm3'   => 'milímetro cúbico',
          'mm³'   => 'milímetro cúbico',

          'kg'    => 'kilogramo',
          'g'     => 'gramo',
          'km'    => 'kilómetro',
          'cm'    => 'centímetro',
          'mm'    => 'milímetro',
          'ml'    => 'mililitro',
          'l'     => 'litro',
          'm'     => 'metro',
     );
     my @units_ordered = ('km/h','m/s',
          'km3','km³','km2','km²',
          'm3','m³','m2','m²',
          'cm3','cm³','cm2','cm²',
          'mm3','mm³','mm2','mm²',
          'kg','km','cm','mm','ml','l','g','m');

     # Temperature expressions: handled separately from the generic measurement
     # loop because they require negative number support and accept multiple
     # Unicode variants of the degree symbol plus the plain letter "o".
     #
     # Recognised degree symbols:
     #   °  U+00B0  DEGREE SIGN
     #   º  U+00BA  MASCULINE ORDINAL INDICATOR  (visually identical, often confused)
     #   o  plain letter o (common in informal/digital text: 30oC, -5oF)
     #
     # Negative numbers: the minus sign is captured as part of the match and
     # converted to "bajo cero" appended after the unit name, which is the
     # natural Spanish phrasing: "-10°C" → "diez grados celsius bajo cero".
     {
          my $deg_re  = qr{[°º]|o(?=[CF])}; # degree symbol variants + letter o
          my $num_re  = qr{-?\d+(?:[.,]\d+)?}; # integer or decimal, optional minus

          for my $pair (
               [ qr{${deg_re}C}i, 'grado celsius',    'grados celsius'    ],
               [ qr{${deg_re}F}i, 'grado fahrenheit', 'grados fahrenheit' ],
          ) {
               my ($unit_re, $sg, $pl) = @$pair;
               $current_item =~ s{
                    (?<![\w°º])   # not preceded by a word char or degree symbol
                    (-?\d+(?:[.,]\d+)?)  # number, possibly negative
                    \s*
                    $unit_re
                    (?=\W|\$)
               }{
                    my $raw = $1;
                    my $neg = ($raw =~ s/^-//);
                    my ($int_str, $dec_str, $malformed) = _split_by_decimal_convention($raw);
                    if ($malformed) {
                         ($int_str = $raw) =~ s/[.,]//g;
                         $dec_str = undef;
                    }
                    my $nw;
                    if (defined $dec_str) {
                         my $iw = eval { $conv->cardinal($int_str) } // $int_str;
                         my %dn = (0,'cero',1,'uno',2,'dos',3,'tres',4,'cuatro',
                                   5,'cinco',6,'seis',7,'siete',8,'ocho',9,'nueve');
                         $nw = "$iw coma " . join(' ', map { $dn{$_}//$_ } split //,$dec_str);
                    } else {
                         $nw = eval { $conv->cardinal($int_str) } // $int_str;
                    }
                    $nw =~ s/^uno$/un/;
                    my $unit_name = (!defined($dec_str) && $int_str == 1 && !$neg) ? $sg : $pl;
                    $neg ? "$nw $unit_name bajo cero" : "$nw $unit_name"
               }gex;
          }
     }

     for my $unit (@units_ordered) {
          my $unit_re = quotemeta($unit);
          $current_item =~ s{
               \b(\d+(?:[.,]\d+)?)\s*$unit_re(?=\W|$)
          }{
               my $num = $1;
               my ($int_str, $dec_str, $malformed) = _split_by_decimal_convention($num);
               if ($malformed) {
                    ($int_str = $num) =~ s/[.,]//g;
                    $dec_str = undef;
               }
               my $nw;
               if (defined $dec_str) {
                    my $iw = eval { $conv->cardinal($int_str) } // $int_str;
                    my %dn = (0=>'cero',1=>'uno',2=>'dos',3=>'tres',4=>'cuatro',
                              5=>'cinco',6=>'seis',7=>'siete',8=>'ocho',9=>'nueve');
                    my @dw = map { $dn{$_} // $_ } split //, $dec_str;
                    $nw = "$iw coma " . join(' ', @dw);
               } else {
                    $nw = eval { $conv->cardinal($int_str) } // $int_str;
               }
               # Singular/plural: choose form based on numeric value
               my $unit_name;
               if (!defined($dec_str) && $int_str == 1) {
                    $nw =~ s/^uno$/un/;   # apocope: "un metro" not "uno metro"
                    $unit_name = $UNIT_ES_SG{$unit} // $UNIT_ES{$unit};
               } else {
                    $unit_name = $UNIT_ES{$unit};
               }
               "$nw $unit_name"
          }gex;
     }

     ########################################################################
     # 6. NATIONAL IDs — RUT (Chilean tax ID)                               #
     #    Pattern: N.NNN.NNN-V or NN.NNN.NNN-V (dot-grouped + hyphen)       #
     #    Read digit by digit: "uno dos tres... guión nueve"                  #
     ########################################################################
     {
          my %_DW = (0,"cero",1,"uno",2,"dos",3,"tres",4,"cuatro",
                     5,"cinco",6,"seis",7,"siete",8,"ocho",9,"nueve");
          $current_item =~ s{
               \b
               (\d{1,2}(?:\.\d{3}){1,2})
               -
               (\d|[Kk])
               \b
          }{
               my ($body, $ver) = ($1, $2);
               $body =~ s/\.//g;
               my $body_w = join(" ", map { $_DW{$_} // $_ } split(//, $body));
               my $ver_w  = ($ver =~ /[Kk]/)
                    ? ($main::rut_k_mode eq "ka" ? "ka" : "k")
                    : ($_DW{int($ver)} // $ver);
               "$body_w guión $ver_w"
          }gex;
     }

     ########################################################################
     # 6b. ORDINAL NUMBERS                                                   #
     #                                                                       #
     # Two distinct mechanisms, with different coverage:                     #
     #                                                                       #
     # A) SYMBOL SUFFIXES  º (U+00BA)  ª (U+00AA)  and bare ° (U+00B0)     #
     #    Range: 1–100. The symbol characters are normalised to ° first so   #
     #    one pattern handles all three. Genuine temperatures (°C, °F, ºC,   #
     #    ºF, oC, oF) were consumed by the measurements block above, so a    #
     #    bare ° here can only be an ordinal.                                 #
     #    Ordinal words are looked up in %_ORD (1–100, computed at compile    #
     #    time). Numbers above 100 with these symbols are left untouched.    #
     #                                                                       #
     # B) PLAIN-LETTER SUFFIXES  o a er era ero eras eros vo va  etc.        #
     #    Range: unlimited — any positive integer.                            #
     #    1–10 get specific patterns that also cover their unique abbreviated  #
     #    forms (2do, 3er, 4to, 7mo, 8vo, 9no, etc.).                        #
     #    11+ get a single generic pattern: \b(\d+)\.?  followed by any of   #
     #    the recognised suffixes.                                            #
     #    No upper limit. The cardinal of the number is used as the output    #
     #    word (since Spanish does not commonly use full ordinal forms above   #
     #    20 in speech).                                                      #
     ########################################################################
     {
          # %_ORD: full Spanish ordinals 1–100, computed.
          # Decades: 10=décimo, 20=vigésimo, 30=trigésimo, 40=cuadragésimo,
          # 50=quincuagésimo, 60=sexagésimo, 70=septuagésimo, 80=octogésimo,
          # 90=nonagésimo, 100=centésimo.
          # Compounds (21–99): decade + space + unit ordinal.
          my %_ORD_DECADE = (
               1,'primero',   2,'segundo',    3,'tercero',   4,'cuarto',
               5,'quinto',    6,'sexto',      7,'séptimo',   8,'octavo',
               9,'noveno',
              10,'décimo',   20,'vigésimo',  30,'trigésimo',
              40,'cuadragésimo',  50,'quincuagésimo', 60,'sexagésimo',
              70,'septuagésimo',  80,'octogésimo',    90,'nonagésimo',
             100,'centésimo',
          );
          my %_UNIT_ORD = (
               1,'primero',2,'segundo',3,'tercero',4,'cuarto',5,'quinto',
               6,'sexto',7,'séptimo',8,'octavo',9,'noveno',
          );
          my %_ORD;
          for my $n (1..100) {
               if (exists $_ORD_DECADE{$n}) {
                    $_ORD{$n} = $_ORD_DECADE{$n};
               } else {
                    my $d = int($n/10)*10;
                    my $u = $n % 10;
                    $_ORD{$n} = "$_ORD_DECADE{$d} $_UNIT_ORD{$u}";
               }
          }
          # Override the computed compound forms for 11 and 12, which have
          # dedicated Latin-derived words in Spanish.
          $_ORD{11} = 'undécimo';
          $_ORD{12} = 'duodécimo';

          # ── A) Symbol suffix path ─────────────────────────────────────────
          # Normalise º (U+00BA) and ª (U+00AA) to ° (U+00B0).
          # Safe here: ºC/ºF/°C/°F/oC/oF were already consumed above.
          $current_item =~ s/º/°/g;   # U+00BA → U+00B0
          $current_item =~ s/ª/°/g;   # U+00AA → U+00B0

          # Match 1–100 followed by ° (with optional dot separator).
          # Above 100 left untouched; these are unlikely to be ordinals.
          $current_item =~ s{
               \b(\d{1,3})\.?\s*°(?![CF])   # digit(s), optional dot, °, not C or F
          }{
               my $n = int($1);
               exists $_ORD{$n} ? $_ORD{$n} : "${n}°"
          }gex;

          # ── B) Plain-letter suffix path ───────────────────────────────────
          # 1–3 have unique abbreviated forms in addition to o/a/ero/era.
          $current_item =~ s{\b1\.?(?:ero?s?|era?s?|r[oa]s?|[oa]s?)\b}{primero}gi;
          $current_item =~ s{\b2\.?(?:n?do?s?|n?da?s?|[oa]s?)\b}{segundo}gi;
          $current_item =~ s{\b3\.?(?:ero?s?|era?s?|r[oa]s?|[oa]s?)\b}{tercero}gi;
          # 4–10 have specific abbreviated forms.
          $current_item =~ s{\b4\.?(?:to?s?|ta?s?|[oa]s?)\b}{cuarto}gi;
          $current_item =~ s{\b5\.?(?:to?s?|ta?s?|[oa]s?)\b}{quinto}gi;
          $current_item =~ s{\b6\.?(?:to?s?|ta?s?|[oa]s?)\b}{sexto}gi;
          $current_item =~ s{\b7\.?(?:mo?s?|ma?s?|[oa]s?)\b}{séptimo}gi;
          $current_item =~ s{\b8\.?(?:vo?s?|va?s?|[oa]s?)\b}{octavo}gi;
          $current_item =~ s{\b9\.?(?:no?s?|na?s?|[oa]s?)\b}{noveno}gi;
          $current_item =~ s{\b10\.?(?:mo?s?|ma?s?|[oa]s?)\b}{décimo}gi;
          # 11+: generic pattern. Uses cardinal of the number as output word,
          # since full ordinal forms above 20 are rare in natural Spanish speech.
          # Accepted suffixes: o a os as ero era eros eras (covers "8vo" → already
          # handled above, but also e.g. "21ero", "50o", "100a").
          $current_item =~ s{
               \b(\d+)
               \.?
               (?:ero?s?|era?s?|[oa]s?)
               \b
          }{
               my $n = int($1);
               $n < 11 ? "${n}°"   # should have been caught above; pass through
                       : (eval { $conv->cardinal($n) } // $n)
          }gex;
     }

     ########################################################################
     # 7. ROMAN NUMERALS (via Roman.pm, already imported)                  #
     #    Two-or-more character sequences first, then single chars.         #
     #    Running here (line level) ensures the ORTHOGRAPHIC token is       #
     #    already the Spanish word before the word-level snapshot occurs,   #
     #    so phonword output shows "uno" not "i".                           #
     #    Runs after measurements so unit abbreviations are already gone.   #
     ########################################################################
     # 7a. UPPERCASE Roman numerals only (MMXVI, XIV, XX, etc.)
     #
     # IMPORTANT: only uppercase sequences are matched here.
     # Rationale: Spanish words that consist entirely of Roman-numeral letters
     # (mil, vil, civil, mix, ml, cm, xl, etc.) are written in lowercase in
     # normal text. After earlier normalization sections expand numbers to
     # Spanish words ("dos mil veinticinco", "ciento veinte soles") the word
     # "mil" would match [IVXLCDMivxlcdm]{2,} and be converted to
     # arabic("MIL") = 1049 → "mil cuarenta y nueve", inserting spurious
     # "cuarenta y nueve" into every output that contains the word "mil".
     # Restricting to uppercase eliminates all such false matches.
     #
     # Lowercase roman numerals (i, ii, iii, iv ... xxi etc.) are handled
     # at word level by the existing rules in fix_letter_patterns, which
     # apply after the full normalization pass and have proper word-boundary
     # guards.
     #
     # The parser uses a package-level hash (our %_ROM_VAL) so that the
     # named sub _rom2int can always see it (avoids the Perl
     # "Variable will not stay shared" bug that occurs when a named sub
     # is defined inside a block alongside a lexical "my" variable).
     our %_ROM_VAL = (I=>1,V=>5,X=>10,L=>50,C=>100,D=>500,M=>1000)
          unless %_ROM_VAL;

     sub _rom2int {
          my $s = uc(shift);
          return 0 unless defined $s && $s =~ /^[IVXLCDM]+$/;
          my @c = split //, $s;
          my $n = 0;
          for my $i (0..$#c) {
               my $cv = $main::_ROM_VAL{$c[$i]} // 0;
               my $nv = ($i < $#c) ? ($main::_ROM_VAL{$c[$i+1]} // 0) : 0;
               $n += ($cv < $nv) ? -$cv : $cv;
          }
          return $n;
     }

     # Match only UPPERCASE sequences of 2+ Roman numeral characters.
     # Tokens containing BOTH letters and digits (XX01, IXA01, R2D2) are
     # automatically excluded because Perl \b does not fire between two \w chars
     # of the same class — but we add an explicit digit lookahead/lookbehind
     # to be absolutely safe.
     $current_item =~ s{
          (?<![\d])\b([IVXLCDM]{2,})\b(?![\d])
     }{
          my $rom = $1;    # already uppercase (matched [IVXLCDM])
          my $val = _rom2int($rom);
          $val ||= eval { arabic($rom) } // 0;
          ($val > 0) ? $conv->cardinal($val) : $rom
     }gex;

     ########################################################################
     # 8. MATHEMATICAL EXPRESSIONS — runs BEFORE 7b single-char Roman rules #
     #    so that "2 X 4" is caught as multiplication before X→diez fires.  #
     ########################################################################
     $current_item =~ s{
          \b(\d+)\s*[xX×*]\s*(\d+)\b
     }{
          "$1 multiplicado por $2"
     }gex;

     # 7b. Single-char Roman numerals: handled in pre-lc pass (uppercase V, X).
     # Lowercase v, x, i are NOT converted here — they are Spanish letters.
     # "vi" (preterite of ver), "ix" (rare), "xi" (Greek) must not become numbers.

     ########################################################################
     # 7c. LOWERCASE ROMAN-LETTER SEQUENCES — spell out letter by letter    #
     #                                                                       #
     # Tokens consisting entirely of [ivxlcdm] letters (the lowercase Roman  #
     # numeral alphabet) are ambiguous: they are neither valid Spanish words  #
     # (in most cases) nor Roman numerals (which require uppercase). Left     #
     # untouched, they would be phonemically transcribed as if Spanish, e.g. #
     # "xi" → /ksi/, "ix" → /iks/, which is incorrect.                      #
     #                                                                       #
     # Rule: isolated [ivxlcdm]{2,} tokens NOT in the Spanish-word exclusion  #
     # list are spelled out character by character using standard letter      #
     # names:                                                                 #
     #   i→i   v→ve corta   x→equis   l→ele   c→se   d→de   m→eme           #
     #                                                                       #
     # Exclusion list — genuine Spanish words that happen to use only these  #
     # letters and must NOT be spelled out:                                  #
     #   vi   (preterite of "ver": "lo vi ayer")                             #
     #   vil  (adjective: "acto vil")                                        #
     #   civil (adjective: "código civil")                                   #
     #   mix  (loanword: "un mix de estilos")                                #
     #   mil  (cardinal: "mil personas") — CRITICAL: must be protected       #
     #                                                                       #
     # Uppercase Roman numerals are handled in the pre-lc pass and are       #
     # already converted to Spanish words before this rule runs.             #
     ########################################################################
     {
          # Letter names for [ivxlcdm] — consistent with the pre-lc %__LN table.
          my %_ROM_LC_NAMES = (
               i => 'i',        v => 've corta',  x => 'equis',
               l => 'ele',      c => 'se',        d => 'de',
               m => 'eme',
          );
          # Spanish words composed entirely of [ivxlcdm] letters that must
          # NOT be spelled out. Add to this list if new conflicts are found.
          my %_ROM_LC_WORDS = map { $_ => 1 } qw(vi vil civil mix mil);

          $current_item =~ s{
               (?<![\w])          # not preceded by word char
               ([ivxlcdm]{2,})     # 2+ lowercase Roman-alphabet letters
               (?![\w])           # not followed by word char
          }{
               my $tok = $1;
               # Protect known Spanish words; spell out everything else.
               # Must use ternary (not "if…or"): the replacement block needs
               # an expression, not a statement-modifier.
               $_ROM_LC_WORDS{$tok}
                    ? $tok
                    : join(' ', map { $_ROM_LC_NAMES{$_} // $_ } split //, $tok)
          }gex;
     }

     ########################################################################
     # 9. NUMBER RANGES                                                      #
     #    a) 4-digit year ranges: 1990-2000                                  #
     #    b) Short historical ranges: 43-103 d.C.                            #
     ########################################################################
     # 4-digit year ranges: only fire when no = sign follows (not arithmetic)
     # Also handles en-dash (\x{2013}) and em-dash (\x{2014}).
     $current_item =~ s{
          \b(\d{4})\s*[-\x{2013}\x{2014}]\s*(\d{4})\b(?![^=\n]*=)
     }{
          my ($y1, $y2) = (int($1), int($2));
          my $w1 = $conv->cardinal($y1);
          my $w2 = $conv->cardinal($y2);
          "$w1 a $w2"
     }gex;

     # Short ranges: only fire when no = sign follows (not arithmetic)
     $current_item =~ s{
          \b(\d{1,3})\s*-\s*(\d{1,3})\b(?![^=\n]*=)(\s*(?:después de Cristo|antes de Cristo|d\.C\.|a\.C\.|d\.?c\.?|a\.?c\.?|AC|DC|BCE|CE))?
     }{
          my ($n1, $n2, $era) = (int($1), int($2), $3 // '');
          if ($n2 >= $n1) {
               my $w1 = $conv->cardinal($n1);
               my $w2 = $conv->cardinal($n2);
               # d.C./a.C. already expanded by block 0b above;
               # handle any remaining alternate forms:
               $era =~ s/d\.C\.?/después de Cristo/i;
               $era =~ s/a\.C\.?/antes de Cristo/i;
               $era =~ s/\bDC\b/después de Cristo/;
               $era =~ s/\bAC\b/antes de Cristo/;
               $era =~ s/\bCE\b/de la era común/;
               $era =~ s/\bBCE\b/antes de la era común/;
               $era = " $era" if $era =~ /\S/;
               "$w1 a $w2$era"
          } else {
               "$1-$2$era"
          }
     }gex;

     ########################################################################
     # 10. LEADING-ZERO IDENTIFIERS                                          #
     #     Tokens starting with "0" followed by more digits are identifiers, #
     #     not cardinal numbers. Read each digit individually.               #
     ########################################################################
     {
          my %_D = (0,'cero',1,'uno',2,'dos',3,'tres',4,'cuatro',
                    5,'cinco',6,'seis',7,'siete',8,'ocho',9,'nueve');
          $current_item =~ s{
               (?<![\d.])0(\d+)(?![\d.,])
          }{
               join(' ', map { $_D{$_} // $_ } split(//, "0$1"))
          }gex;
     }


     ########################################################################
     # POST-PASS: STRIP TRAILING PUNCTUATION ATTACHED TO NUMBERS            #
     # Prevents "12.." → "doce punto punto" and "US$100." → "cien... punto" #
     # Remove sequences of dots/commas immediately after a numeral token    #
     # that was normalized (now all letters).                                #
     ########################################################################
     # Remove trailing dots that appear after an expanded numeral word
     # (but not ellipsis which is handled elsewhere).
     ########################################################################
     # MALFORMED NUMBER DETECTION                                            #
     # A digit sequence followed by 1+ dots (not followed by more digits)   #
     # is a malformed number: "12." "12.." must not be verbalized.          #
     ########################################################################
     # Trailing punctuation after numbers: strip cleanly.
     # Per spec: "12." → "doce", "1," → "uno" — trailing punct is sentence punctuation,
     # not part of the number. Ellipsis "N..." is handled by collapsing to N + "..."
     # which is later treated as a pause/boundary, not verbalized as "punto punto".
     # Step 1: N... (3+ dots) → expand number first, dots stay as ellipsis
     # Step 2: N. or N.. (1-2 trailing dots not followed by digit) → strip the dots
     $current_item =~ s{\b(\d+)\.{2,}(?!\d)}{
          my $n=int($1); my $nw=eval{$conv->cardinal($n)}//$1; "$nw..."
     }ge;
     $current_item =~ s{\b(\d+)\.(?!\d)}{
          my $n=int($1); eval{$conv->cardinal($n)}//$1
     }ge;
     # Strip trailing commas after digits (e.g. "123," → "123")
     $current_item =~ s{\b(\d+),(?!\d)}{
          my $n=int($1); eval{$conv->cardinal($n)}//$1
     }ge;
     # Clean up any double spaces introduced
     $current_item =~ s/  +/ /g;
     $current_item =~ s/^ | $//g;

     # Strip trailing sentence punctuation from expanded number/currency words
     # APOCOPATION: "uno" → "un" before masculine singular nouns.
     # The general grammatical rule: "uno" apocopates before masculine singular nouns.
     # We apply this when "uno" precedes any word that is NOT a:
     #   number word (cero, dos, tres, ..., cien, mil, millón, coma)
     #   function word (de, con, y, o, a, el, la, por, en, al, del, que, se, etc.)
     #   arithmetic word (más, menos, dividido, multiplicado, igual)
     # This correctly handles: peso, dólar, euro, sol, metro, kilogramo, litro, etc.
     # without listing them, while preserving: "uno de ellos", "uno con uno",
     # "treinta y uno por ciento", "uno más uno".
     {
          # NO_APOCOPE: words after which "uno" should NOT apocopate to "un".
          # Slashes in inline comments would break qr// so we use a plain string here.
          my $NO_APOCOPE = qr/(?:
               cero|dos|tres|cuatro|cinco|seis|siete|ocho|nueve|diez|
               once|doce|trece|catorce|quince|veinte|treinta|cuarenta|
               cincuenta|sesenta|setenta|ochenta|noventa|cien|ciento|
               doscientos|trescientos|cuatrocientos|quinientos|seiscientos|
               setecientos|ochocientos|novecientos|
               mil|mill[oó]n|millones|
               coma|punto|
               de|con|y|o|a|el|la|los|las|por|en|al|del|que|se|su|un|una|
               m[aá]s|menos|dividido|multiplicado|igual|siendo|
               durante|antes|después|también
          )/xi;
          $current_item =~ s{\buno\b(?=\s+(?!$NO_APOCOPE\b)\p{L})}{un}g;
     }

     # Strip trailing sentence punctuation from any expanded token (word-final).
     # No hardcoded word list needed — the rule applies universally.
     $current_item =~ s/(\p{L}+)\s*[.,;:!?]+(?=\s|$)/$1/g;
     # Strip trailing sentence punctuation from any word token (fríamente. etc.)
     $current_item =~ s/(\p{L})[.,;:!?]+(?=\s|$)/$1/g;

     return $current_item;

}    # END SUBROUTINE: NORMALIZE STRUCTURED TOKENS

sub convert_times_to_words {

     my $current_item = $_[0];

     # Use the run-level singleton converter.
     # All Lingua::ES::Numeros objects in this codebase use identical settings,
     # so a single instance is shared across all calls.
     our $_CURRENCY_CONV //= Lingua::ES::Numeros->new(
          DECIMAL     => $decimal_symbol,
          SEPARADORES => ($decimal_symbol eq "," ? "." : ","),
          UNMIL       => '0',
     );
     my $conv = $_CURRENCY_CONV;

     ##########################################################################
     # Match 1-2 digit hour + colon + exactly 2 digit minutes.               #
     # The hour is captured as $1, the minutes as $2.                         #
     # NOTE: No article ("la"/"las") is added before the time; only the      #
     # numeral words for the hour (and minutes, if non-zero) are produced.   #
     ##########################################################################
     $current_item =~ s/\b(\d{1,2}):(\d{2})\b/
          my ( $h, $m ) = ( int($1), int($2) );
          # In Spanish, "1:xx" is spoken with the feminine "una" (la una),
          # because "hora" is feminine. All other hours use the masculine cardinal.
          my $h_words   = ($h == 1) ? "una" : $conv->cardinal($h);
          my $m_words   = ( $m == 0 ) ? "" : " " . $conv->cardinal($m);
          "$h_words$m_words";
     /ge;

     return $current_item;
}

#################################################################################
#                                                                               #
#   SUBROUTINE: BUILD G2P PHONEME STRING                                       #
#   Takes a fully-transcribed phonemic word (as produced by the rest of the    #
#   pipeline, under -g2p settings) and turns it into a space-separated list    #
#   of individual phonemes, suitable for the -g2p output format.               #
#                                                                               #
#################################################################################
sub build_g2p_phoneme_string {

     my $current_item = $_[0];

     ##########################################################################
     # STRESSED VOWELS: KEEP OR NORMALIZE, DEPENDING ON stress_using_tildes  #
     # Stress is marked with an accented vowel (á é í ó ú, and ü) instead of #
     # the IPA apostrophe when stress_using_tildes==1. If the user has asked #
     # for tilde-marked stress, that accent should survive into the G2P      #
     # phoneme column too (á/é/í/ó/ú/ü become their own phoneme symbols,     #
     # distinct from unstressed a/e/i/o/u). Otherwise (the default), any     #
     # leftover accented vowel is normalized to its plain counterpart before #
     # filtering -- without this, such vowels would be silently dropped by  #
     # the whitelist below, since it wouldn't recognize the accented forms.  #
     ##########################################################################
     unless ( $stress_using_tildes == 1 ) {
          $current_item =~ tr/áéíóúü/aeiouu/;
     }

     ##########################################################################
     # KEEP ONLY PHONEME CHARACTERS (PLUS IPA STRESS MARKS)                   #
     # Strips anything left over that is not itself a phoneme (syllable      #
     # dots, pause symbols, stray spaces, punctuation, etc.). The allowed    #
     # inventory matches the one documented for -g2p:                        #
     # a e i o u   p t k b d g   f s x   m n ɲ   l   ɾ r   j w   ʝ   tʃ      #
     # ("ʃ" is included here only so it survives long enough to be combined #
     # with a preceding "t" into the single phoneme "tʃ", below). Also       #
     # allows the accented vowels á é í ó ú ü, which represent stressed      #
     # vowels when stress_using_tildes==1 (see above -- with that setting   #
     # off, none of these will actually appear here, since they were just   #
     # normalized to their plain form) -- and the IPA primary/secondary      #
     # stress marks ˈ/ˌ, which are how stress is represented in the word     #
     # by this point when stress_using_tildes==0. Without keeping these,     #
     # stress_using_tildes==0 would produce no visible stress marking at    #
     # all in G2P output, identical to stress being off entirely.           #
     ##########################################################################
     $current_item =~ s/[^aeiouáéíóúüˈˌptkbdgfsxmnɲlɾrjwʝʃ]//g;

     my @phonemes = ();
     my @chars    = split //, $current_item;
     my $pending_stress_mark = '';
     my %is_vowel = map { $_ => 1 } qw(a e i o u);

     while (@chars) {
          my $char = shift @chars;

          # IPA stress marks (ˈ primary, ˌ secondary) are not phonemes in
          # their own right -- they mark the start of a stressed syllable.
          # Rather than appear as their own space-separated token, attach
          # them to the VOWEL of the stressed syllable specifically --
          # skipping past any onset consonant(s) in between (e.g.
          # "chile" -> "tʃ ˈi l e", "america" -> "a m ˈe ɾ i k a",
          # "posee" -> "p o s ˈe e").
          if ( $char eq "ˈ" || $char eq "ˌ" ) {
               $pending_stress_mark .= $char;
               next;
          }

          my $phoneme;

          # "tʃ" (the "ch" sound) is represented as two plain characters    #
          # under -g2p settings (no ligature/diacritic), but is a SINGLE    #
          # phoneme and must not be split into "t" and "ʃ".                 #
          if ( $char eq "t" && @chars && $chars[0] eq "ʃ" ) {
               shift @chars;    # Consume the "ʃ" as part of the same phoneme.
               $phoneme = "tʃ";
          }
          else {
               $phoneme = $char;
          }

          if ( $pending_stress_mark ne '' && $is_vowel{$phoneme} ) {
               push @phonemes, "$pending_stress_mark" . "$phoneme";
               $pending_stress_mark = '';
          }
          else {
               push @phonemes, $phoneme;
          }
     }

     # Safety net: a stress mark with nothing after it (e.g. stray input,
     # or no vowel ever found -- shouldn't normally happen) would
     # otherwise be silently lost -- keep it as its own token.
     push @phonemes, $pending_stress_mark if $pending_stress_mark ne '';

     return join( " ", @phonemes );
}

sub convert_numerals_to_words {

     # This routine intentionally relies on undef-as-empty-string behavior
     # in a few places (e.g. an optional prefix character that may be
     # absent at the start of a string). That is correct, pre-existing
     # behavior, not a bug -- silence the warning rather than change logic.
     no warnings 'uninitialized';

     my $current_item = $_[0];
     my $temp_num;  # used inside eval-string blocks below for 1-3 digit numeral conversion

     # Use the run-level singleton; avoid constructing a new object every call.
     # $numeral_converter_obj is kept as the name because it is referenced
     # inside eval strings throughout this subroutine.
     our $_CURRENCY_CONV //= Lingua::ES::Numeros->new(
          DECIMAL     => $decimal_symbol,
          SEPARADORES => ($decimal_symbol eq "," ? "." : ","),
          UNMIL       => '0',
     );
     $numeral_converter_obj = $_CURRENCY_CONV;


     #print STDOUT "ALL-NUM_CONV-BEGINNING-OF-SUB:$current_item\n";    # AD-HOC DEBUG

     ############################################################################
     #                                                                          #
     #          PROCESS DATE RANGES AS SUCH, RATHER THAN AS EQUATIONS           #
     #                                                                          #
     ############################################################################

     # BROAD DATE RANGES (1-4 digit numbers)                                    #
     # Treat two consecutive 1-4 digit numbers with a minus sign in between as  #
     # a range of years rather than as an equation (e.g. 24-110 = "24 a 110",   #
     # not "24-110"                                                             #
     if ( $broad_year_ranges == 1 ) {    # Process 1-4 digit numbers

          if ( $bc_dates_included == 0 ) {    # DON'T process when 2nd is smaller than 1st
               # Guard: if "es igual a" appears on this line, it is arithmetic,
               # not a range. Don't convert "N menos M" to "N a M" in that case.
               if ( ( $current_item =~ m/([0-9]{1,4}) menos ([0-9]{1,4})/ ) && ( $2 > $1 )
                    && ( $current_item !~ m/es igual a/ ) ) {
                    $current_item =~ s/([0-9]{1,4}) menos ([0-9]{1,4})/$1 a $2/g;
               }
          }
          elsif ( $bc_dates_included == 1 ) {    # DO process when 2nd is smaller than 1st
               if ( $current_item =~ m/([0-9]{1,4}) menos ([0-9]{1,4})/
                    && ( $current_item !~ m/es igual a/ ) ) {
                    $current_item =~ s/([0-9]{1,4}) menos ([0-9]{1,4})/$1 a $2/g;
               }
          }
     }

     # NARROW DATE RANGES (only 4-digit numbers)                                #
     # Treat two consecutive 4-digit numbers with a minus sign in betwen as a   #
     # range of years rather than as an equation (e.g. 1900-1990 = "1900 a      #
     # 1990", not "1900-1990"                                                   #
     elsif ( $narrow_year_ranges == 1 ) {        # Process two 4-digit nums

          if ( $bc_dates_included == 0 ) {       # DON'T process when 2nd is smaller than 1st
               if ( ( $current_item =~ m/([0-9]{4}) menos ([0-9]{4})/ ) && ( $2 > $1 ) ) {
                    $current_item =~ s/([0-9]{4}) menos ([0-9]{4})/$1 a $2/g;
               }
          }
          elsif ( $bc_dates_included == 1 ) {    # DO process when 2nd is smaller than 1st
               if ( $current_item =~ m/([0-9]{4}) menos ([0-9]{4})/ ) {
                    $current_item =~ s/([0-9]{4}) menos ([0-9]{4})/$1 a $2/g;
               }
          }
     }

     ############################################################################
     #                                                                          #
     #                    CONVERT ANY FORMAT **EXCEPT** VRT                     #
     #                                                                          #
     ############################################################################
     if ( $vrt_format == 0 ) {

          #print STDOUT "NON-VRT-NUM_CONV-BEF-CnvNm:$current_item\n";    # AD-HOC DEBUG

          # NOTE: The minus-sign-to-"menos" conversion for negative numbers now
          # happens earlier, in fix_misc_punctuation(), before dashes are
          # turned into comma pauses. Doing it here (matching ", [0-9]") used
          # to misfire on genuine commas before a number (e.g. "Allá, 400
          # personas" was wrongly turned into "Allá, menos 400 personas").

          ####################################################################
          # CURRENCY AMOUNTS: all $ forms are now handled by
          # normalize_structured_tokens (which runs earlier in the pipeline)
          # producing correct output: "mil doscientos pesos con cincuenta
          # centavos", "cien dólares estadounidenses", etc.
          # The old handler here produced "kóma" instead of "con", wrong
          # currency names ("uscien pesos" for US$N), and "pesoscero"
          # concatenation bugs. It has been removed.
          #print STDOUT "NON-VRT-NUM_CONV-AFT-CURR:$current_item\n";    # AD-HOC DEBUG

          # PROCESS 4-DIGIT INTEGERS WITHOUT PERIOD SEPARATOR, WHICH ARE NOT
          # CAUGHT BY THE REGEX PATTERN USED HERE.
          # NOTE: "\$" was added to the allowed prefix characters so that a
          # currency symbol directly touching a number (e.g. "$1500") doesn't
          # block the match. $1 (the prefix) is now prepended verbatim, rather
          # than being discarded and replaced with a hardcoded space, so the
          # "$" survives to be converted into the word stored in $moneda
          # afterward.
          $current_item =~ s/( |\(|\{|\[|\/|^)(\d{4})(\D|$)/eval q{
               $1 .                                     # Prepend the original prefix char (space, bracket, $, etc.)
               $numeral_converter_obj->cardinal($2)     # Convert numeral to words
               . " ";                                   # Postpend a space
          } /ge;

          #print STDOUT "NON-VRT-NUM_CONV-AFT-4DIGT:$current_item\n";    # AD-HOC DEBUG

          # PROCESS ALL OTHER INTEGER NUMBERS
          # Don't use "\b" in the first search expr - it catches too much ("2" in "3,2")
          # NOTE: "\$" added to the prefix class, and $1 is now preserved (see above).
          $current_item =~ s/( |\(|\{|\[|\/|^)(\d{1,3})((\.\d{3})*)(\D|$)/eval q{

               # print STDOUT "1=$1\t2=$2\t3=$3\t4=$4\t5=$5\t6=$6\ttemp_num=$temp_num\n"; # AD-HOC DEBUG

               $temp_num = join ('', $2, $3);               # Concatenate the parts of the extracted integer

               if ( $5 eq "," ) {                                # If $5 is a comma, do this...
                    $1 .                                         # Prepend the original prefix char
                    $numeral_converter_obj->cardinal($temp_num)  # Convert numeral to words
                    . ">"                                        # Add special > char to indicate $5 came after number
                    . $5                                         # Add the non-number trailing char
                    #. " ";                                      # Postpend a space
               }
               else {                                            # If $5 isn't a comma, do this...
                    $1 .                                         # Prepend the original prefix char
                    $numeral_converter_obj->cardinal($temp_num)  # Convert numeral to words
                    . ($5 =~ m{\w} ? " $5" : $5)               # Space before word char to prevent concat
               }
          } /ge;

          #print STDOUT "NON-VRT-NUM_CONV-AFT-AOINT:$current_item\n";    # AD-HOC DEBUG

          # Process fractions (numbers to right of decimal point)               #
          $current_item =~ s/(,)(\d+)/eval q{

               # print STDOUT "1=$1\t2=$2\n"; # AD-HOC DEBUG

               " kóma " .                                        # Convert ">," to " kóma "
               &convert_fractional_nums_to_words($2)             # Send fractional num to conversion subroutine
          }/ge;

          #print STDOUT "NON-VRT-NUM_CONV-AFT-FRACT:$current_item\n";    # AD-HOC DEBUG

          # Process numbers with leading 0s (01, 001, 0001, 00001) and after commas #
          $current_item =~ s/( |\(|\{|\[|\/|\$|^)([\d]+)(\D|$)/eval q{
               $1 .                                     # Prepend a space if there already was one
               &convert_fractional_nums_to_words($2)    # Send number to conversion subroutine
               . $3;                                    # Postpend a space
          } /ge;

          #print STDOUT "NON-VRT-NUM_CONV-AFT-LDNG0:$current_item\n";    # AD-HOC DEBUG

          ############################################################################
          # CONVERT PERIODS AND COMMAS IN NUMERALS BUT NOT ELSEWHERE                 #
          # NOTE: A space is prepended to "punto"/"coma" because the preceding       #
          # integer was converted to a word (e.g. "cincuenta") with no trailing      #
          # space; without it we get "cincuentapunto" instead of "cincuenta punto".  #
          ############################################################################
          $current_item =~ s/\.([0-9])/ punto $1/g;
          $current_item =~ s/,([0-9])/ coma $1/g;

          ############################################################################
          # ELIMINATE COLONS BETWEEN NUMERALS BUT NOT ELSEWHERE (E.G. "15:30")       #
          ############################################################################
          $current_item =~ s/([0-9]):([0-9])/$1 $2/g;

          #######################################################################
          # Convert certain number-related symbols                              #
          #######################################################################
          # NOTE: "%" is handled earlier by normalize_structured_tokens which
          # produces "por ciento". Any residual "%" that slipped through (e.g.
          # not attached to a number) still gets expanded here, correctly.
          $current_item =~ s/(\d)\s*%/ $1 por ciento /g;
          $current_item =~ s/%/ por ciento /g;
          $current_item =~ s/\+/ más /g;

          ############################################################################
          # CHANGE SPECIFIC COMBINATIONS OF LETTERS AND NUMBERS TO WORD FORM         #
          # (e.g. "abc1", "mc2")                                                     #
          ############################################################################
          $current_item =~ s/(\W|^)abc1/$1abeseúno /g;
          $current_item =~ s/(\W|^)mc2/$1emesedós /g;

          #print STDOUT "\n>LET+NUM AFT: $current_item";    # DEBUG
     }

     ############################################################################
     #                                                                          #
     #                             CONVERT .VRT FORMAT                          #
     #                                                                          #
     ############################################################################

     # If generating .VRT files, put ¬ after numerals converted into            #
     # words instead of a space, so "2000" doesn't end up occupying 4 lines     #
     # (dos\nsero\nsero\nsero)                                                  #

     if ( $vrt_format == 1 ) {

          #print STDOUT "\nVRT-NUM_CONV-----START:$current_item:\n";    # AD-HOC DEBUG

          # Convert multiple spaces to just one space.                          #
          $current_item =~ s/ +/ /g;

          # NEW IN 417 #

          # If a number starts with a minus sign (negative number), change that #
          # sign to "menos".                                                    #
          if ( $current_item =~ m/^, [0-9]/ ) {
               $current_item =~ s/^,/menos /g;
          }

          # Convert "," at beginning of line to the word "coma" followed by space. #
          # This catches (rare) things like ",333".                                #
          $current_item =~ s/^\,/coma /g;

          ####################################################################
          # CURRENCY AMOUNTS: handled by normalize_structured_tokens (removed here).
          #print STDOUT "VRT-NUM_CONV-AFT-CURR:$current_item:\n";    # AD-HOC DEBUG

          # Process 4-digit integers without period separator, which are not    #
          # caught by the regex pattern used here.                              #
          # NOTE: "\$" added to prefix class; $1 now preserved (see non-VRT     #
          # branch above for rationale).                                        #
          $current_item =~ s/( |\(|\{|\[|\/|^)(\d{4})(\D|$)/eval q{
               $1 .                                     # Prepend the original prefix char
               $numeral_converter_obj->cardinal($2)     # Convert numeral to words
               . " ";                                   # Postpend a space
          } /ge;

          #print STDOUT "VRT-NUM_CONV-AFT-4DIGT:$current_item:\n";    # AD-HOC DEBUG

          # Process all other integer numbers                                            #
          # Don't use "\b" in the first search expr - it catches too much ("2" in "3,2") #
          $current_item =~ s/( |\(|\{|\[|\/|\$|^)(\d{1,3})((\.\d{3})*)(\D|$)/eval q{

               #print STDOUT "1=$1\t2=$2\t3=$3\t4=$4\t5=$5\t6=$6\ttemp_num=$temp_num\n"; # AD-HOC DEBUG

               $temp_num = join ('', $2, $3);               # Concatenate the parts of the extracted integer

               if ( $5 eq "," ) {                                # If $5 is a comma, do this...
                    $1 .                                         # Prepend the original prefix char
                    $numeral_converter_obj->cardinal($temp_num)  # Convert numeral to words
                    . ">"                                        # Add special > char to indicate $5 came after number
                    . $5                                         # Add the non-number trailing char
                    #. " ";                                      # Postpend a space
               }
               else {                                            # If $5 isn't a comma, do this...
                    $1 .                                         # Prepend the original prefix char
                    $numeral_converter_obj->cardinal($temp_num)  # Convert numeral to words
                    . $5                                         # Add the non-number trailing char
                    #. " ";                                      # Postpend a space
               }
          } /ge;

          #print STDOUT "VRT-NUM_CONV-AFT-AOINT:$current_item:\n";    # AD-HOC DEBUG

          # Process fractions (numbers to right of decimal point)               #
          # 392: TESTING: Changed "(\d+)?" to "(\d)", as I think it was changing all commas.
          $current_item =~ s/(>?,)(\d+)/eval q{

               #print STDOUT "1=$1\t2=$2\n"; # AD-HOC DEBUG

               " kóma " .                                        # Convert ">," to " kóma "
               &convert_fractional_nums_to_words($2)             # Send fractional num to conversion subroutine
          }/ge;

          #print STDOUT "VRT-NUM_CONV-AFT-FRACT:$current_item:\n";    # AD-HOC DEBUG

          # Process numbers with leading 0s (01, 001, 0001, 00001) and after commas #
          $current_item =~ s/( |\(|\{|\[|\/|\$|^)([\d]+)(\D|$)/eval q{
               $1 .                                     # Prepend a space if there already was one
               &convert_fractional_nums_to_words($2)    # Send number to conversion subroutine
               . $3;                                    # Postpend a space
          } /ge;

          #print STDOUT "VRT-NUM_CONV-AFT-LDNG0:$current_item:\n";    # AD-HOC DEBUG

          ############################################################################
          # CONVERT PERIODS AND COMMAS BEFORE NUMERALS BUT NOT ELSEWHERE             #
          # NEW IN 401: Also catch orphaned ">," groups.                             #
          # NOTE: Space prepended to "punto" to prevent "cincuentapunto" fusion      #
          # (the integer was converted to a word with no trailing space).            #
          ############################################################################
          $current_item =~ s/\.([0-9])/ punto $1/g;
          $current_item =~ s/>, / guión /g;

          #print STDOUT "VRT-NUM_CONV-AFT-CNV.,:$current_item:\n";    # AD-HOC DEBUG

          #############################################################################
          ## ELIMINATE COLONS BETWEEN NUMERALS BUT NOT ELSEWHERE (E.G. "15:30")       #
          #############################################################################
          #if ( $vrt_format == 0 ) {
          #     $current_item =~ s/([0-9]):([0-9])/$1 $2/g;
          #}

          #######################################################################
          # Convert certain number-related symbols                              #
          #######################################################################
          # TODO Move up to non-VRT section
          $current_item =~ s/(\d)\s*%/ $1 por ciento /g;
          $current_item =~ s/%/ por ciento /g;
          $current_item =~ s/\+/ más /g;

          #print STDOUT "NON-VRT-NUM_CONV-AFT-NMSYM:$current_item\n";    # AD-HOC DEBUG

          #print "\n>LET+NUM AFT: $current_item";    # DEBUG

          # Convert conjunction "y" to "i" (it's inserted by the number conversion routine, #
          # but is analyzed as the phoneme "ye" by Perkins without this fix)                #
          $current_item =~ s/ y / i /g;

          # APOCOPATION: "uno" → "un" before masculine singular nouns.             #
          # Applied here (after integer expansion) so that "1 dólar" → "uno dólar" #
          # → "un dólar" correctly. The normalize post-pass handles other contexts. #
          $current_item =~ s{\buno\b(?=\s+(?!(?:cero|dos|tres|cuatro|cinco|seis|siete|ocho|nueve|diez|once|doce|trece|catorce|quince|veinte|treinta|cuarenta|cincuenta|sesenta|setenta|ochenta|noventa|cien|ciento|doscientos|trescientos|cuatrocientos|quinientos|seiscientos|setecientos|ochocientos|novecientos|mil|mill[oó]n|millones|coma|punto|de|con|y|o|a|el|la|los|las|por|en|al|del|que|se|su|un|una|m[aá]s|menos|dividido|multiplicado|igual)\b)\p{L})}{un}g;
     }
     return ($current_item);

}    # END SUBROUTINE: CONVERT NUMERALS TO WORDS

#################################################################################
#                                                                               #
#   SUBROUTINE: CONVERT FRACTIONAL NUMBERS TO WORDS                             #
#                                                                               #
#################################################################################
sub convert_fractional_nums_to_words {

     my $current_item = $_[0];

     my @current_fractional_digit = split //, $current_item;

     foreach my $digit (@current_fractional_digit) {
          $digit =~ s/0/cero/g;
          $digit =~ s/1/uno/g;
          $digit =~ s/2/dos/g;
          $digit =~ s/3/tres/g;
          $digit =~ s/4/cuatro/g;
          $digit =~ s/5/cinco/g;
          $digit =~ s/6/seis/g;
          $digit =~ s/7/siete/g;
          $digit =~ s/8/ocho/g;
          $digit =~ s/9/nueve/g;
     }

     $current_item = join( " ", @current_fractional_digit );

     #$temp = Encode::encode_utf8($current_item);    # AD-HOC DEBUG
     #print STDOUT "FRACT-NUM-AFT-PROCESS :$temp\n";    # AD-HOC DEBUG

     #print STDOUT "CURR_FRAC_DIG=$current_item\n";     # AD-HOC DEBUGGING

     return ($current_item);

}    # END SUBROUTINE: CONVERT FRACTIONAL NUMBERS TO WORDS

##############################################################################
#                                                                            #
#   SUBROUTINE: CHANGE CERTAIN SYMBOLS TO WORD FORM                          #
#                                                                            #
##############################################################################
sub change_some_symbols_to_words {

     my $current_item = $_[0];

     if ( $vrt_format == 1 ) {
          $current_item =~ s/@/¬arroba¬/g;
          $current_item =~ s/\$/$moneda¬/g;         # Replace "$" character with word stored in $moneda
          $current_item =~ s/&/¬y¬/g;
          $current_item =~ s/_/¬guión¬bajo¬/g;
          $current_item =~ s/\//ø$slash\ø/g;
     }
     else {
          $current_item =~ s/@/ arroba /g;
          $current_item =~ s/\$/$moneda /g;          # Replace "$" character with word stored in $moneda
          $current_item =~ s/&/ y /g;
          $current_item =~ s/_/ guión bajo /g;
          $current_item =~ s/\// $slash /g;
     }

     # Change remaining dashes to spaces
     if ( $vrt_format == 1 ) {
          $current_item =~ s/\-/¬/g;
     }
     else {
          $current_item =~ s/\-/ /g;
     }

     return ($current_item);

}

##############################################################################
#                                                                            #
#   SUBROUTINE: SEARCH AND REPLACE MULTI-WORD PHRASES                        #
#                                                                            #
##############################################################################
sub replace_multiword_phrases {

     my $current_item = $_[0];

     # Adverbs ending in "-mente"

     # EPIPHANY : VITAL ADDITION: REGEX WAS SELECTING FROM BEG OF LINE TO LAST -MENTE! #
     # NOTE Changed in 383 to process .vrt format                                      #
     if ( $vrt_format == 1 ) {
          $current_item =~ s/(\w+)mente/$1ménte/g;    # Space and underscore added to allow temporary processing and later rejoining
     }
     else {
          $current_item =~ s/(\w+)mente/$1 _ménte/g;    # Space and underscore added to allow temporary processing and later rejoining
     }

                    # FOR DEBUGGING
                    #print STDOUT "\n$current_item"; # FOR DEBUGGING

     # Other phrases and things which need spaces on either end or in the middle.
     $current_item =~ s/afdd/asociación de familiares de detenidos desaparecidos/g;
     $current_item =~ s/beethoven/betoven/g;
     $current_item =~ s/bio bio/bíobío/g;
     $current_item =~ s/bio\-bio/bíobío/g;
     $current_item =~ s/biobio/bíobío/g;
     $current_item =~ s/bío bio/bíobío/g;
     $current_item =~ s/bío\-bio/bíobío/g;
     $current_item =~ s/bíobio/bíobío/g;
     $current_item =~ s/bio bío/bíobío/g;
     $current_item =~ s/bio\-bío/bíobío/g;
     $current_item =~ s/biobío/bíobío/g;
     $current_item =~ s/bío \- bío/bíobío/g;
     $current_item =~ s/bío bío/bíobío/g;
     $current_item =~ s/bío\-bío/bíobío/g;
     $current_item =~ s/bíobío/bíobío/g;
     $current_item =~ s/blue jeans/bluyíns/g;
     $current_item =~ s/byte(s|)($| )/báit$1$2/g;
     $current_item =~ s/canada dry/cánada drai/g;
     $current_item =~ s/christian/cristian/g;
     $current_item =~ s/christopher/crístofer/g;
     $current_item =~ s/data show/dátacho/g;
     $current_item =~ s/eeuu/estados unidos/g;
     $current_item =~ s/( |^)eua/$1estados unidos/g;
     $current_item =~ s/gamecube/gueim kiub/g;
     $current_item =~ s/ginger ale/yínyereil/g;
     $current_item =~ s/higgins/jíguins/g;
     $current_item =~ s/hip hoper(o|a)(s|)/jipjopér$1$2/g;
     $current_item =~ s/hip(\-| |)hop/jipjóp/g;
     $current_item =~ s/hot dog/jotdóg/g;
     $current_item =~ s/hot pant(s|)/jótpant/g;
     $current_item =~ s/jet( |)set/yetsét/g;
     $current_item =~ s/jhonny/yóni/g;
     $current_item =~ s/nintendo, seis cuatro/nintendo sesentaicuatro/g;
     $current_item =~ s/o'higgins/ojíguins/g;
     $current_item =~ s/pie de limón/pai de limón/g;
     $current_item =~ s/ping pong/pinpón/g;
     $current_item =~ s/play station, dos/plei esteichon dos/g;
     $current_item =~ s/seven up/sevenap/g;
     $current_item =~ s/skateboard/skeit bord/g;
     $current_item =~ s/webmaster/webmáster/g;
     $current_item =~ s/website/wébsait/g;
     $current_item =~ s/jean pier(r|)e/yonpier/g;
     $current_item =~ s/jean paul/yonpól/g;
     $current_item =~ s/plan b/planbé/g;
     $current_item =~ s/data show/datachó/g;

     # PROCESS MULTI-WORD BACKCHANNEL VOCALIZATIONS
     #    These must be in ALL CAPS.
     $current_item =~ s/mm hm/MMHM/g;
     $current_item =~ s/nn nn/NNNN/g;
     $current_item =~ s/uh huh/UHHUH/g;
     $current_item =~ s/uh oh/UHOH/g;
     $current_item =~ s/uh uh/UHUH/g;

     # MISCELLANEOUS WORDS
     $current_item =~ s/wi fi/wáifai/g;

     # REMOVE MULTIPLE SPACES
     $current_item =~ s/ ( )+/ /g;

     return ($current_item);

}

############################################################################
#                                                                          #
# SUBROUTINE: FIX SPECIFIC RARITIES                                        #
#                                                                          #
############################################################################
sub fix_specific_rarities {

     my $current_item = $_[0];

     $current_item =~ s/(\w+)mente/_ménte/g;     # Adds a temporary _ to permit double accentuation in one word
     $current_item =~ s/º//g;
     $current_item =~ s/°//g;
     $current_item =~ s/ª//g;
     $current_item =~ s/¿//g;
     $current_item =~ s/¡//g;
     $current_item =~ s/\^//g;
     $current_item =~ s/~//g;
     $current_item =~ s/¬//g;
     $current_item =~ s/|//g;
     $current_item =~ s/¯//g;
     $current_item =~ s/§//g;
     $current_item =~ s/©//g;
     $current_item =~ s/®//g;
     $current_item =~ s/¥//g;
     $current_item =~ s/#//g;

     return ($current_item);

}


############################################################################
#                                                                          #
# SUBROUTINE: CONVERT 3+ IDENTICAL VOWELS TO DOUBLES                      #
#                                                                          #
############################################################################
sub triple_vowels_to_double_vowels {

     my $current_item = $_[0];

     # DEBUG:
     #print STDOUT "BEFORE: $current_item\n";

     # Collapse a run of THREE OR MORE consecutive identical vowels down to
     # TWO (a run of exactly two, e.g. genuine Spanish double vowels like
     # "leer"/"creer", is left untouched) -- but NOT when the entire word
     # is just that repeated vowel (e.g. "iii" must not become "ii").
     $current_item =~ s/([a])\1{2,}/$1$1/g unless $current_item =~ /^a+$/;
     $current_item =~ s/([e])\1{2,}/$1$1/g unless $current_item =~ /^e+$/;
     $current_item =~ s/([i])\1{2,}/$1$1/g unless $current_item =~ /^i+$/;
     $current_item =~ s/([o])\1{2,}/$1$1/g unless $current_item =~ /^o+$/;
     $current_item =~ s/([u])\1{2,}/$1$1/g unless $current_item =~ /^u+$/;

     # DEBUG:
     #print STDOUT "AFTER: $current_item\n\n";

     return ($current_item);
}

#################################################################################
#                SUBROUTINE: PRE-PROCESSING: FIX ODD CHARACTERS                 #
#################################################################################
sub fix_odd_characters {

     my $current_item = $_[0];

     # UMLAUTS > BARE VOWELS (EXCEPT Ü, WHICH IS DIERESIS)
     if ( $fix_umlauts == 1 ) {
          $current_item =~ s/ä/a/g;
          $current_item =~ s/ë/e/g;
          $current_item =~ s/ï/i/g;
          $current_item =~ s/ö/o/g;
     }

     # GRAVE ACCENTS > ACUTE ACCENTS (USING GRAVES IS A PRETTY COMMON TYPING MISTAKE)
     if ( $fix_grave_accents == 1 ) {
          $current_item =~ s/à/á/g;
          $current_item =~ s/è/é/g;
          $current_item =~ s/ì/í/g;
          $current_item =~ s/ò/ó/g;
          $current_item =~ s/ò/ú/g;
     }

          # CIRCUMFLEXES > BARE VOWELS
     if ( $fix_circumflexes == 1 ) {
          $current_item =~ s/â/a/g;
          $current_item =~ s/ê/e/g;
          $current_item =~ s/î/i/g;
          $current_item =~ s/ô/o/g;
          $current_item =~ s/û/u/g;
     }

     # NASAL TILDES > BARE VOWELS
     if ( $fix_nasal_tildes == 1 ) {
          $current_item =~ s/ã/a/g;
          $current_item =~ s/õ/o/g;
     }

     return ($current_item);
}

############################################################################
# VRT PRE-PROCESSING                                                       #
# If reading .vrt text files, replace <tags>, commas, periods, etc. with   #
# special characters (which will be converted back into the original       #
# symbols later)                                                           #
############################################################################
sub do_vrt_preprocessing {

     my $current_item = $_[0];

     # $temp = Encode::encode_utf8($current_item);       # AD-HOC DEBUG
     # print STDOUT "VRT-PREPROCESSING-BEF:$temp:\n";    # AD-HOC DEBUG

     # STRIP PUNCTUATION FROM NUMBERS
     $current_item =~ s/([0-9])(-)+/$1/g;

     # PROCESS CERTAIN CHARACTERS AND SYMBOLS, ESPECIALLY FOR CONNEXOR SOFTWARE
     $current_item =~ s/ /¬/g;                     # Connexor treats things like "estados unidos" as a unit, but w/ a space.
     $current_item =~ s/-/ø/g;                     # Dashes. Connexor keeps them in things like "fonético-fonológico".
     $current_item =~ s/<p>/µµ/g;                  # Connexor paragraph symbols.
     $current_item =~ s/<s>/µ/g;                   # Connexor sentence symbols.
     $current_item =~ s/\./øøø/g;
     $current_item =~ s/\+/øøø/g;

     # $temp = Encode::encode_utf8($current_item);    # AD-HOC DEBUG
                                                      #print STDOUT "VRT-PREPROCESSING-AFT:$temp:\n";    # AD-HOC DEBUG
     return ($current_item);
}

##############################################################################
#                                                                            #
#   SUBROUTINE: KILL COMMON WORDS (FOR TESTING PURPOSES)                     #
#               This allows you to more easily find errors in output files   #
#               by eliminating high-frequency, known-correct words.          #
#                                                                            #
##############################################################################
sub kill_common_words {

     my $current_item = $_[0];

     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)aquella($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)aquellas($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)aquello($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)aquél($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)como($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)como($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)con($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cual($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cuales($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cuando($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cuanta($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cuantas($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cuanto($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cuantos($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cuál($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cuáles($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cuándo($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cuánta($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cuántas($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cuánto($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cuántos($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cómo($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)de($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)del($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)desde($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)donde($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)dónde($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)e($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)el($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)en($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)es($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)esa($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)ese($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)eso($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)esos($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)esta($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)estas($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)este($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)esto($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)estos($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)ha($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)hacia($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)han($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)hasta($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)la($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)las($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)los($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)menos($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)más($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)o($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)para($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)por($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)porque($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)que($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)quien($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)quienes($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)quién($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)quiénes($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)qué($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)sea($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)sean($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)sin($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)sino($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)sola($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)solas($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)solo($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)solos($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)son($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)sólo($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)u($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)un($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)una($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)unas($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)uno($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)unos($|\.|,|:|;|"|-|‖|\]|\)|\})//g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)y($|\.|,|:|;|"|-|‖|\]|\)|\})//g;

     return ($current_item);
}


##############################################################################
#                                                                            #
# SUBROUTINE: MODIFY SINGLE WORDS IN ORTHOGRAPHIC FORM                       #
#             This modifies a series of orthographic words (mostly loanwords #
#             and foreign names) to forms that correspond to how they are    #
#             pronounced in Spanish.                                         #
#                                                                            #
##############################################################################
sub modify_single_ortho_words {
     my $current_item = $_[0];

     #print STDOUT "\nSingle-Ortho-Words-BEF:$current_item:";       # AD HOC DEBUG

     # PROCESS MAIN ITEMS
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)(brian|brallan)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1bráyan$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)(c|k)atchup($|\.|,|:|;|"|-|‖|\]|\)|\})/$1$2áchup$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)(c|k)atherin(e|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1káterin$4/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)(e|)slip(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1eslíp$3$4/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)(e|)snob(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1esnób$3$4/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)(e|)spot(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1espót$3$4/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)(e|é)lite($|\.|,|:|;|"|-|‖|\]|\)|\})/$1elít$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)(j|y)e(f|ff)r(y|i|ie)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yéfri$5/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)(j|y)e(s|ss|c)e(n|nn)ia($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yesenia$5/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)(j|y)e(s|ss|c)i(c|cc|k)a($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yésika$5/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)(j|y)eimy(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yeimi$3$4/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)(j|y)o(s|ss|c)el(i|í|y)n($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yóselin$5/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)(j|y)ustin($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yastin$3$4/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)(y|j)on(n|)at(t|h|)an($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yónatan$5/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)abp($|\.|,|:|;|"|-|‖|\]|\)|\})/$1abepé$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)acdc($|\.|,|:|;|"|-|‖|\]|\)|\})/$1ásedése$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)addend(a|um|ums)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1adénd$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)affaire(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1afér$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)afp($|\.|,|:|;|"|-|‖|\]|\)|\})/$1aefepé$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)age($|\.|,|:|;|"|-|‖|\]|\)|\})/$1eich$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)alzheimer(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1alsjéimer$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)angie($|\.|,|:|;|"|-|‖|\]|\)|\})/$1anyi$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)anthony($|\.|,|:|;|"|-|‖|\]|\)|\}|>)/$1ántoni$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)antoine($|\.|,|:|;|"|-|‖|\]|\)|\})/$1antwán$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)antonella($|\.|,|:|;|"|-|‖|\]|\)|\})/$1antonela$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)aol($|\.|,|:|;|"|-|‖|\]|\)|\})/$1aoele$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)apple(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1ápel$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)arlette($|\.|,|:|;|"|-|‖|\]|\)|\})/$1arlet$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)avf($|\.|,|:|;|"|-|‖|\]|\)|\})/$1abeefe$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)aylton($|\.|,|:|;|"|-|‖|\]|\)|\})/$1eilton$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)aylwin($|\.|,|:|;|"|-|‖|\]|\)|\})/$1eilwin$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)baby(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1beibi$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)ballet(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1balét$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)barbie(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1barbi$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)bayron($|\.|,|:|;|"|-|‖|\]|\)|\})/$1bairon$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)bbc($|\.|,|:|;|"|-|‖|\]|\)|\})/$1bebesé$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)becker(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1béquer$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)benji($|\.|,|:|;|"|-|‖|\]|\)|\}|>)/$1benyi$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)berger(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1bérguer$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)berries($|\.|,|:|;|"|-|‖|\]|\)|\})/$1bérris$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)berry($|\.|,|:|;|"|-|‖|\]|\)|\})/$1bérri$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)bestseller(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1bestséler$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)bieber($|\.|,|:|;|"|-|‖|\]|\)|\})/$1bíber$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)blockbuster(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1blokbáster$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)blogger(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1blóguer$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)blogspot(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1blógspot$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)blue(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1blu$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)boulevard(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1bulevár$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)break(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1brék$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)bridge($|\.|,|:|;|"|-|‖|\]|\)|\})/$1brích$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)brit(t|)an(n|)(i|y|ie)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1brítani$5/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)bulldog(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1búldog$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)bungalow(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1búngalow$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)byron(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1bairon$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)byronian(o|a)(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1baironian$2$3$4/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)c(u|ú)chen(e|)(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1c$2jen$3$4$5/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cabaret(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1kabarét$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cal(i|e)fon(t|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1cálifon$4/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cal(i|e)fon(t|)s($|\.|,|:|;|"|-|‖|\]|\)|\})/$1cálifons$4/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cancel($|\.|,|:|;|"|-|‖|\]|\)|\})/$1cáncel$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)carn(é|e)(t|)(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1karné$3$4/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)carrie($|\.|,|:|;|"|-|‖|\]|\)|\})/$1kari$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cd(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1sedé$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cft($|\.|,|:|;|"|-|‖|\]|\)|\})/$1sefeté$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)chalet(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1chalét$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)charlo(t|tt|tte|th)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1chárlot$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)chv($|\.|,|:|;|"|-|‖|\]|\)|\})/$1chilevisión$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)clinic($|\.|,|:|;|"|-|‖|\]|\)|\})/$1klínik$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)closet(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1klóset$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cnn($|\.|,|:|;|"|-|‖|\]|\)|\})/$1seeneéne$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)complot(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1complót$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)confort(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1confórt$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)conicyt(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1kónisit$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)contrafagot(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1contrafagót$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)copyright(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1cópirait$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cowboy(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1cówboy$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)coñac(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1coñác$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)crush($|\.|,|:|;|"|-|‖|\]|\)|\})/$1crash$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cullen($|\.|,|:|;|"|-|‖|\]|\)|\})/$1calen$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)dastyn($|\.|,|:|;|"|-|‖|\]|\)|\})/$1dástin$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)datashow(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1dátacho$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)davis($|\.|,|:|;|"|-|‖|\]|\)|\})/$1déivis$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)dc($|\.|,|:|;|"|-|‖|\]|\)|\})/$1desé$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)debut(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1debút$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)dellanira($|\.|,|:|;|"|-|‖|\]|\)|\})/$1deyanira$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)denis(|s)e($|\.|,|:|;|"|-|‖|\]|\)|\})/$1denís$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)derek($|\.|,|:|;|"|-|‖|\]|\)|\})/$1dérek$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)dieciseis($|\.|,|:|;|"|-|‖|\]|\)|\})/$1dieciséis$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)diet($|\.|,|:|;|"|-|‖|\]|\)|\})/$1dáyet$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)digg($|\.|,|:|;|"|-|‖|\]|\)|\})/$1dig$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)dj(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1diyei$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)dreamcast(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1drímcast$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)dry($|\.|,|:|;|"|-|‖|\]|\)|\})/$1drai$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)dvd(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1devedé$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)e-mail($|\.|,|:|;|"|-|‖|\]|\)|\})/$1imeil$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)ebay($|\.|,|:|;|"|-|‖|\]|\)|\})/$1ibei$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)ebner($|\.|,|:|;|"|-|‖|\]|\)|\})/$1ébner$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)eduvigis($|\.|,|:|;|"|-|‖|\]|\)|\})/$1eduvíguis$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)email($|\.|,|:|;|"|-|‖|\]|\)|\})/$1imeil$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)emily($|\.|,|:|;|"|-|‖|\]|\)|\})/$1émili$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)empires($|\.|,|:|;|"|-|‖|\]|\)|\})/$1émpaiers$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)etc($|\.|,|:|;|"|-|‖|\]|\)|\})/$1etcétera$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)evelyn($|\.|,|:|;|"|-|‖|\]|\)|\})/$1évelin$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)face($|\.|,|:|;|"|-|‖|\]|\)|\})/$1feis$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)facebook($|\.|,|:|;|"|-|‖|\]|\)|\})/$1feisbuk$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)fagot(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1fagót$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)fahrenheit($|\.|,|:|;|"|-|‖|\]|\)|\})/$1fárenjait$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)falabella($|\.|,|:|;|"|-|‖|\]|\)|\})/$1falabela$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)firefox($|\.|,|:|;|"|-|‖|\]|\)|\})/$1faierfox$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)flickr($|\.|,|:|;|"|-|‖|\]|\)|\})/$1flíquer$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)fmr($|\.|,|:|;|"|-|‖|\]|\)|\})/$1efeemeére$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)fondecyt(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1fóndesit$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)foster($|\.|,|:|;|"|-|‖|\]|\)|\})/$1fóster$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)fpmr($|\.|,|:|;|"|-|‖|\]|\)|\})/$1efepeemeére$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)francesca($|\.|,|:|;|"|-|‖|\]|\)|\})/$1franchéska$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)freezer(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1fríser$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)frizer($|\.|,|:|;|"|-|‖|\]|\)|\})/$1fríser$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)g(é|e)iser(es|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1gu$2iser$3$4/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)game($|\.|,|:|;|"|-|‖|\]|\)|\})/$1gueim$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)gangster(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1gánster$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)gay(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1guei$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)gb($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yígabaits$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)geisha(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1gueicha$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)geocities($|\.|,|:|;|"|-|‖|\]|\)|\})/$1jeositis$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)george(t|tt|tte)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yoryet$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)gerbner($|\.|,|:|;|"|-|‖|\]|\)|\})/$1guérbner$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)gibson($|\.|,|:|;|"|-|‖|\]|\)|\})/$1guibson$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)gigoló(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yigoló$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)gin($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yin$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)giselle($|\.|,|:|;|"|-|‖|\]|\)|\})/$1ymiaisél$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)gmail($|\.|,|:|;|"|-|‖|\]|\)|\})/$1jemeil$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)goldfarb($|\.|,|:|;|"|-|‖|\]|\)|\})/$1góldfarb$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)good($|\.|,|:|;|"|-|‖|\]|\)|\})/$1gud$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)google($|\.|,|:|;|"|-|‖|\]|\)|\})/$1gúguel$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)gángster(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1gánster$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)h(a|á)mster(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1jámster$3$4/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)h(a|á)ndicap(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1jándicap$3$4/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)hall(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1jol$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)happening(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1jápening$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)hardware(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1járwer$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)hasbro($|\.|,|:|;|"|-|‖|\]|\)|\})/$1jasbro$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)hawaiana(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1jaguayana$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)hd($|\.|,|:|;|"|-|‖|\]|\)|\})/$1achedé$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)hd($|\.|,|:|;|"|-|‖|\]|\)|\})/$1achedé$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)heavy($|\.|,|:|;|"|-|‖|\]|\)|\})/$1jevi$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)hepburn($|\.|,|:|;|"|-|‖|\]|\)|\})/$1jepburn$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)hil(l|)ar(i|y)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1jílari$4/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)hiphoper(o|a)(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1jipjopér$4/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)hipp(y|ie)(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1jipi$3$4/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)hobbies($|\.|,|:|;|"|-|‖|\]|\)|\})/$1jobis$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)hobby($|\.|,|:|;|"|-|‖|\]|\)|\})/$1jobi$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)hobbys($|\.|,|:|;|"|-|‖|\]|\)|\})/$1jobis$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)hockey(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1jókei$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)hollywood(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1jóligwud$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)hotmail($|\.|,|:|;|"|-|‖|\]|\)|\})/$1jótmeil$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)ic($|\.|,|:|;|"|-|‖|\]|\)|\})/$1isé$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)iceberg(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1aisberg$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)imdb($|\.|,|:|;|"|-|‖|\]|\)|\})/$1iemedebé$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)input(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1ínput$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)ip($|\.|,|:|;|"|-|‖|\]|\)|\})/$1ipé$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)isabella($|\.|,|:|;|"|-|‖|\]|\)|\})/$1isabela$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)j(h|)o(h|)n($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yon$4/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)j(h|)o(h|)nat(h|)an($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yónatan$5/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)j(|h)ander($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yánder$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)jacqu(elín|elin|eline)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yákelin$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)jacqui($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yáki$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)jamie($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yeimi$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)jazz($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yas$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)jeans($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yins$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)jeep(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yip$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)jet(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yet$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)jockey(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yóqui$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)journal(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yórnal$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)jumper(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yámper$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)junior(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yúnior$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)k(u|ú)chen(e|)(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1k$2jen$3$4$5/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)kaiser($|\.|,|:|;|"|-|‖|\]|\)|\}|>)/$1káiser$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)kb($|\.|,|:|;|"|-|‖|\]|\)|\})/$1kílobaits$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)kinder($|\.|,|:|;|"|-|‖|\]|\)|\}|>)/$1kínder$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)kiwi(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1quigüi$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)light($|\.|,|:|;|"|-|‖|\]|\)|\})/$1lait$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)live($|\.|,|:|;|"|-|‖|\]|\)|\})/$1laib$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)livejournal($|\.|,|:|;|"|-|‖|\]|\)|\})/$1laibyórnal$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)locker($|\.|,|:|;|"|-|‖|\]|\)|\})/$1lóquer$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)lycra(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1laicra$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)ma(i|y)(c|k|ch)o(l|ll)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1máicol$5/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)macchu($|\.|,|:|;|"|-|‖|\]|\)|\})/$1machu$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)magic($|\.|,|:|;|"|-|‖|\]|\)|\})/$1máyik$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)mail($|\.|,|:|;|"|-|‖|\]|\)|\})/$1meil$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)mall(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1mol$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)mar(i|y)l(i|y|iy)n($|\.|,|:|;|"|-|‖|\]|\)|\})/$1márilin$4/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)marketing($|\.|,|:|;|"|-|‖|\]|\)|\})/$1márketin$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)marshall($|\.|,|:|;|"|-|‖|\]|\)|\})/$1márchal$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)mb($|\.|,|:|;|"|-|‖|\]|\)|\})/$1mégabaits$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)messenger(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1mésenyer$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)metempsicosis($|\.|,|:|;|"|-|‖|\]|\)|\})/$1metemsicosis$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)microsoft(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1maicrosof$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)miley($|\.|,|:|;|"|-|‖|\]|\)|\})/$1maili$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)molotov($|\.|,|:|;|"|-|‖|\]|\)|\})/$1mólotov$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)moulinex($|\.|,|:|;|"|-|‖|\]|\)|\})/$1múlinex$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)mouse($|\.|,|:|;|"|-|‖|\]|\)|\})/$1maus$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)mouses($|\.|,|:|;|"|-|‖|\]|\)|\})/$1mauses$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)mozilla(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1mosila$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)msn(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1emesene$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)myspace(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1maispes$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)nashville($|\.|,|:|;|"|-|‖|\]|\)|\})/$1náchvil$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)ni(c|k)ole($|\.|,|:|;|"|-|‖|\]|\)|\})/$1nikol$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)nietzsche($|\.|,|:|;|"|-|‖|\]|\)|\})/$1níche$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)nietzschean(a|o)(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1nichean$2$3$4/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)noam($|\.|,|:|;|"|-|‖|\]|\)|\})/$1nóam$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)nordic($|\.|,|:|;|"|-|‖|\]|\)|\})/$1nórdik$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)notebook($|\.|,|:|;|"|-|‖|\]|\)|\})/$1nótbuk$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)of($|\.|,|:|;|"|-|‖|\]|\)|\})/$1af$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)office($|\.|,|:|;|"|-|‖|\]|\)|\})/$1ófis$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)offices($|\.|,|:|;|"|-|‖|\]|\)|\})/$1ófises$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)ok($|\.|,|:|;|"|-|‖|\]|\)|\})/$1okey$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)okay($|\.|,|:|;|"|-|‖|\]|\)|\})/$1okey$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)orange($|\.|,|:|;|"|-|‖|\]|\)|\})/$1óranch$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)output($|\.|,|:|;|"|-|‖|\]|\)|\})/$1áutput$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)outsource($|\.|,|:|;|"|-|‖|\]|\)|\})/$1autsors$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)outsourcing($|\.|,|:|;|"|-|‖|\]|\)|\})/$1áutsorsin$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)panties($|\.|,|:|;|"|-|‖|\]|\)|\})/$1pántis$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)paper(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1péper$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)paypal($|\.|,|:|;|"|-|‖|\]|\)|\})/$1peipal$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)pc(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1pesé$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)pdc($|\.|,|:|;|"|-|‖|\]|\)|\})/$1pedesé$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)pdf(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1pedeéfe$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)pdi($|\.|,|:|;|"|-|‖|\]|\)|\})/$1pedeí$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)peak($|\.|,|:|;|"|-|‖|\]|\)|\})/$1pik$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)ph($|\.|,|:|;|"|-|‖|\]|\)|\})/$1peache$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)philadelphia($|\.|,|:|;|"|-|‖|\]|\)|\})/$1filadelfia$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)photoshop($|\.|,|:|;|"|-|‖|\]|\)|\})/$1fotochop$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)picchu($|\.|,|:|;|"|-|‖|\]|\)|\})/$1pichu$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)picnic($|\.|,|:|;|"|-|‖|\]|\)|\})/$1pícnik$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)play(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1plei$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)pn($|\.|,|:|;|"|-|‖|\]|\)|\})/$1peéne$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)pool(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1pul$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)power(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1páwer$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)powerpoint(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1páwerpoint$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)ppd($|\.|,|:|;|"|-|‖|\]|\)|\})/$1pepedé$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)ppt(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1pepeté$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)pq($|\.|,|:|;|"|-|‖|\]|\)|\})/$1porque$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)pr($|\.|,|:|;|"|-|‖|\]|\)|\})/$1peérre$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)pre(u|ú)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1preú$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)prsd($|\.|,|:|;|"|-|‖|\]|\)|\})/$1perresedé$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)ps($|\.|,|:|;|"|-|‖|\]|\)|\})/$1peése$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)psu($|\.|,|:|;|"|-|‖|\]|\)|\})/$1peeseú$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)pub(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1pab$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)punk(|s)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1pank$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)puzzle(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1pusle$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)python($|\.|,|:|;|"|-|‖|\]|\)|\})/$1paiton$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)quatro($|\.|,|:|;|"|-|‖|\]|\)|\})/$1cuatro$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)quix($|\.|,|:|;|"|-|‖|\]|\)|\})/$1kwix$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)rally($|\.|,|:|;|"|-|‖|\]|\)|\})/$1rali$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)rallys($|\.|,|:|;|"|-|‖|\]|\)|\})/$1ralis$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)ranger(|s)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1rányer$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)rapidshare($|\.|,|:|;|"|-|‖|\]|\)|\})/$1rápidcher$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)reader($|\.|,|:|;|"|-|‖|\]|\)|\})/$1ríderr$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)reality($|\.|,|:|;|"|-|‖|\]|\)|\})/$1reáliti$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)reggaet(o|ó)n($|\.|,|:|;|"|-|‖|\]|\)|\})/$1reguetón$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)reggaetoner(o|a|os|as)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1reguetoner$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)reggaetón($|\.|,|:|;|"|-|‖|\]|\)|\})/$1reguetón$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)ripley($|\.|,|:|;|"|-|‖|\]|\)|\})/$1ríplei$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)rn($|\.|,|:|;|"|-|‖|\]|\)|\})/$1erreéne$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)rob(b|)(y|ie)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1robi$4/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)robinson($|\.|,|:|;|"|-|‖|\]|\)|\})/$1róbinson$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)rugby(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1ragbi$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)rémington(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1réminton$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)s(a|á)n(d|)wich($|\.|,|:|;|"|-|‖|\]|\)|\})/$1sándwich$4/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)sadowsk(y|i)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1sadoski$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)scarle(t|tt|tte|th)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1skárlet$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)scooter(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1scúter$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)scotch(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1scoch$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)scott(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1eskot$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)sd($|\.|,|:|;|"|-|‖|\]|\)|\})/$1esedé$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)sevenup(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1sevenáp$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)skate(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1skeit$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)skateboard(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1skeitbord$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)skater(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1skéiter$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)skype($|\.|,|:|;|"|-|‖|\]|\)|\})/$1eskaip$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)snowboard(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1snówbord$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)socialite(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1socialité$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)software($|\.|,|:|;|"|-|‖|\]|\)|\})/$1sófwer$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)sour(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1ságüer$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)sourceforge($|\.|,|:|;|"|-|‖|\]|\)|\})/$1sórsforch$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)spaghetti($|\.|,|:|;|"|-|‖|\]|\)|\})/$1espaguéti$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)starcraft($|\.|,|:|;|"|-|‖|\]|\)|\})/$1stárcraft$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)station(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1steichon$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)subway($|\.|,|:|;|"|-|‖|\]|\)|\})/$1sábwei$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)super($|\.|,|:|;|"|-|‖|\]|\)|\})/$1súper$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)surf(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1serf$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)sydney($|\.|,|:|;|"|-|‖|\]|\)|\})/$1sídnei$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)symantec($|\.|,|:|;|"|-|‖|\]|\)|\})/$1simántek$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)tb($|\.|,|:|;|"|-|‖|\]|\)|\})/$1térabaits$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)thunderbird(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1tánderberd$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)toilette(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1twalét$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)touch($|\.|,|:|;|"|-|‖|\]|\)|\})/$1tach$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)tour($|\.|,|:|;|"|-|‖|\]|\)|\})/$1tur$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)tours($|\.|,|:|;|"|-|‖|\]|\)|\})/$1turs$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)tripod($|\.|,|:|;|"|-|‖|\]|\)|\})/$1traipod$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)tuit($|\.|,|:|;|"|-|‖|\]|\)|\})/$1twit$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)tuits($|\.|,|:|;|"|-|‖|\]|\)|\})/$1twits$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)tv(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1tevé$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)tvn($|\.|,|:|;|"|-|‖|\]|\)|\})/$1teveéne$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)tweet($|\.|,|:|;|"|-|‖|\]|\)|\})/$1twit$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)tweets($|\.|,|:|;|"|-|‖|\]|\)|\})/$1twits$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)twister($|\.|,|:|;|"|-|‖|\]|\)|\})/$1twíster$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)twitter($|\.|,|:|;|"|-|‖|\]|\)|\})/$1twíter$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)ubb($|\.|,|:|;|"|-|‖|\]|\)|\})/$1ubebé$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)udd($|\.|,|:|;|"|-|‖|\]|\)|\})/$1udedé$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)up($|\.|,|:|;|"|-|‖|\]|\)|\})/$1upé$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)upgrade($|\.|,|:|;|"|-|‖|\]|\)|\})/$1ápgreid$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)upgrades($|\.|,|:|;|"|-|‖|\]|\)|\})/$1ápgreids$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)url(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1uereéle$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)usenet($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yúsnet$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)vhs($|\.|,|:|;|"|-|‖|\]|\)|\})/$1veacheése$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)vries($|\.|,|:|;|"|-|‖|\]|\)|\})/$1vríes$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)vtr($|\.|,|:|;|"|-|‖|\]|\)|\})/$1veteérre$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)walker($|\.|,|:|;|"|-|‖|\]|\)|\})/$1wálker$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)walter($|\.|,|:|;|"|-|‖|\]|\)|\})/$1wálter$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)war(c|k)raft($|\.|,|:|;|"|-|‖|\]|\)|\})/$1wárcraft$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)washington($|\.|,|:|;|"|-|‖|\]|\)|\})/$1wáchinton$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)wc(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1doblevesé$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)wifi($|\.|,|:|;|"|-|‖|\]|\)|\}|>)/$1wáifai$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)windsurf($|\.|,|:|;|"|-|‖|\]|\)|\})/$1wínserf$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)wok(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1gwok$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)wood($|\.|,|:|;|"|-|‖|\]|\)|\})/$1wud$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)xa(b|v)ier(it|)(a|o|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1jabier$3$4$5/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)xa(v|b)i($|\.|,|:|;|"|-|‖|\]|\)|\})/$1javi$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)xbox($|\.|,|:|;|"|-|‖|\]|\)|\})/$1éxbox$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)xbox(e|)s($|\.|,|:|;|"|-|‖|\]|\)|\})/$1éxbox$2s$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)xd($|\.|,|:|;|"|-|‖|\]|\)|\})/$1equisdé$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)ximena(s|)($|\.|,|:|;|"|-|‖|\]|\)|\})/$1jimena$2$3/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)xml($|\.|,|:|;|"|-|‖|\]|\)|\})/$1equisemeéle$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)xp($|\.|,|:|;|"|-|‖|\]|\)|\})/$1equispé$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)xs($|\.|,|:|;|"|-|‖|\]|\)|\})/$1equisése$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)xy($|\.|,|:|;|"|-|‖|\]|\)|\})/$1éxboxquis i$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)yahoo($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yajú$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)youtube($|\.|,|:|;|"|-|‖|\]|\)|\})/$1yutub$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)ángstrom($|\.|,|:|;|"|-|‖|\]|\)|\})/$1angstrom$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{| )abc($|\.|,|:|;|"|-|‖|\]|\)|\}| )/$1abecé$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{| )mc($|\.|,|:|;|"|-|‖|\]|\)|\}| )/$1emesé$2/g;


     # CONVERT *COMMON* ROMAN NUMERALS TO HINDU-ARABIC ONES
     # Order: longest match first to avoid partial rewrites (e.g. "viii" before "vi").
     # I, V, VI, X are included here because normalize_structured_tokens already
     # locked in any date/math context that contains those chars; by the time we
     # reach this sub the only remaining bare I/V/VI/X are genuine roman numerals
     # or isolated letters.  The word-boundary guards (boundary chars on both sides)
     # prevent them firing inside words.

     # NOTE: Roman numerals M, MM, CM, D, L, XL, XC (50–3999) are deliberately
     # NOT converted here.  They conflict with common measurement abbreviations
     # (m=metro, mm=milímetro, cm=centímetro) and common Spanish words/letters.
     # The spec requires only I–XXI; larger ordinals in running text are rare
     # and better left as-is than mis-converted.

     # ROMAN NUMERALS II–XXVIII:
     # [REMOVED: lowercase Roman numeral rule conflicts with Spanish words]
     # [REMOVED: lowercase Roman numeral rule conflicts with Spanish words]
     # [REMOVED: lowercase/ambiguous Roman rule — pre-lc pass handles uppercase Roman numerals]
     # [REMOVED: lowercase Roman numeral rule conflicts with Spanish words]
     # [REMOVED: lowercase Roman numeral rule conflicts with Spanish words]
     # [REMOVED: lowercase Roman numeral rule conflicts with Spanish words]
     # [REMOVED: lowercase Roman numeral rule conflicts with Spanish words]
     # [REMOVED: lowercase Roman numeral rule conflicts with Spanish words]
     # [REMOVED: lowercase Roman numeral rule conflicts with Spanish words]
     # [REMOVED: lowercase/ambiguous Roman rule — pre-lc pass handles uppercase Roman numerals]
     # [REMOVED: lowercase/ambiguous Roman rule — pre-lc pass handles uppercase Roman numerals]
     # [REMOVED: lowercase/ambiguous Roman rule — pre-lc pass handles uppercase Roman numerals]
     # [REMOVED: lowercase/ambiguous Roman rule — pre-lc pass handles uppercase Roman numerals]
     # [REMOVED: lowercase/ambiguous Roman rule — pre-lc pass handles uppercase Roman numerals]
     # [REMOVED: lowercase/ambiguous Roman rule — pre-lc pass handles uppercase Roman numerals]
     # [REMOVED: lowercase/ambiguous Roman rule — pre-lc pass handles uppercase Roman numerals]
     # [REMOVED: lowercase Roman numeral rule conflicts with Spanish words]
     # [REMOVED: lowercase Roman numeral rule conflicts with Spanish words]
     # [REMOVED: lowercase Roman numeral rule conflicts with Spanish words]
     # [REMOVED: lowercase Roman numeral rule conflicts with Spanish words]
     # [REMOVED: lowercase Roman numeral rule conflicts with Spanish words]
     # [REMOVED: lowercase Roman numeral rule conflicts with Spanish words]
     # [REMOVED: lowercase/ambiguous Roman rule — pre-lc pass handles uppercase Roman numerals]
     # [REMOVED: lowercase Roman numeral rule conflicts with Spanish words]
     # [REMOVED: lowercase Roman numeral rule conflicts with Spanish words]

     # SINGLE-CHAR ROMAN NUMERALS: I, V, X MUST COME AFTER MULTI-CHAR ONES
     # so that e.g. "viii" is caught before "v" could gobble its first char.
     # These are case-insensitive (/i flag) so that uppercase "I", "V", "X"
     # (as typed in running text) are handled before spell_out_single_letters
     # would turn them into letter names.
     # [REMOVED: lowercase Roman numeral rule conflicts with Spanish words]
     # [REMOVED: lowercase/ambiguous Roman rule — pre-lc pass handles uppercase Roman numerals]
     # [REMOVED: lowercase/ambiguous Roman rule — pre-lc pass handles uppercase Roman numerals]
     # [REMOVED: lowercase/ambiguous Roman rule — pre-lc pass handles uppercase Roman numerals]

     # "XXX" IS NOT TREATED AS A ROMAN NUMERAL.
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)xxx($|\.|,|:|;|"|-|‖|\]|\)|\})/$1tripleéquis$2/g;

     # COMMON URL COMPONENTS (ALL BUT ONE ARE FILE EXTENSIONS)
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)asp($|\.|,|:|;|"|-|‖|\]|\)|\}|>)/$1aesepé$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)aspx($|\.|,|:|;|"|-|‖|\]|\)|\}|>)/$1aesepeéquis$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cfm($|\.|,|:|;|"|-|‖|\]|\)|\}|>)/$1seefeéme$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cgi($|\.|,|:|;|"|-|‖|\]|\)|\}|>)/$1segeí$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)htm($|\.|,|:|;|"|-|‖|\]|\)|\}|>)/$1acheteéme$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)html($|\.|,|:|;|"|-|‖|\]|\)|\}|>)/$1acheteemeéle$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)jsp($|\.|,|:|;|"|-|‖|\]|\)|\}|>)/$1jotaesepé$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)php($|\.|,|:|;|"|-|‖|\]|\)|\}|>)/$1peachepé$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)www($|\.|,|:|;|"|-|‖|\]|\)|\})/$1tripledoblebé$2/g;

     # PROCESS SINGLE-WORD BACKCHANNEL VOCALIZATIONS
     # These MUST be in ALL CAPS.
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)ah($|\.|,|:|;|"|-|‖|\]|\)|\}|>)/$1AH$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)ch($|\.|,|:|;|"|-|‖|\]|\)|\}|>)/$1CH$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)eh($|\.|,|:|;|"|-|‖|\]|\)|\}|>)/$1EH$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)ff($|\.|,|:|;|"|-|‖|\]|\)|\}|>)/$1FF$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)hm($|\.|,|:|;|"|-|‖|\]|\)|\}|>)/$1HM$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)mm($|\.|,|:|;|"|-|‖|\]|\)|\}|>)/$1MM$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)oh($|\.|,|:|;|"|-|‖|\]|\)|\}|>)/$1OH$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)pf($|\.|,|:|;|"|-|‖|\]|\)|\}|>)/$1PF$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)sh($|\.|,|:|;|"|-|‖|\]|\)|\}|>)/$1SH$2/g;
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)uh($|\.|,|:|;|"|-|‖|\]|\)|\}|>)/$1UH$2/g;

     return ($current_item);
}

#################################################################################
#                            SPELL OUT SINGLE LETTERS                           #
#################################################################################
sub spell_out_single_letters {

     my $current_item = $_[0];

     if ( $vrt_format == 1 ) {

          #print STDOUT "SPELL-OUT-VRT-BEF:$current_item\n"; # AD-HOC DEBUG

          $current_item =~ s/(^| |ø)b( |ø|$)/$1belárga$2/g;
          $current_item =~ s/(^| |ø)c( |ø|$)/$1se$2/g;
          $current_item =~ s/(^| |ø)d( |ø|$)/$1de$2/g;
          $current_item =~ s/(^| |ø)f( |ø|$)/$1éfe$2/g;
          $current_item =~ s/(^| |ø)g( |ø|$)/$1je$2/g;
          $current_item =~ s/(^| |ø)h( |ø|$)/$1áche$2/g;
          $current_item =~ s/(^| |ø)j( |ø|$)/$1jóta$2/g;
          $current_item =~ s/(^| |ø)k( |ø|$)/$1ca$2/g;
          $current_item =~ s/(^| |ø)l( |ø|$)/$1éle$2/g;
          $current_item =~ s/(^| |ø)m( |ø|$)/$1éme$2/g;
          $current_item =~ s/(^| |ø)n( |ø|$)/$1éne$2/g;
          $current_item =~ s/(^| |ø)p( |ø|$)/$1pe$2/g;
          $current_item =~ s/(^| |ø)q( |ø|$)/$1cu$2/g;
          $current_item =~ s/(^| |ø)r( |ø|$)/$1ére$2/g;
          $current_item =~ s/(^| |ø)s( |ø|$)/$1ése$2/g;
          $current_item =~ s/(^| |ø)t( |ø|$)/$1te$2/g;
          $current_item =~ s/(^| |ø)v( |ø|$)/$1$name_for_v$2/g;
          $current_item =~ s/(^| |ø)w( |ø|$)/$1doblebé$2/g;
          $current_item =~ s/(^| |ø)x( |ø|$)/$1équis$2/g;
          $current_item =~ s/(^| |ø)z( |ø|$)/$1séta$2/g;
          $current_item =~ s/(^| |ø)ñ( |ø|$)/$1éñe$2/g;

          #print STDOUT "SPELL-OUT-VRT-AFT:$current_item\n\n"; # AD-HOC DEBUG
     }
     else {
          $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)b($|\.|,|:|;|"|-|‖|\]|\)|\})/$1belárga$2/g;
          $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)c($|\.|,|:|;|"|-|‖|\]|\)|\})/$1se$2/g;
          $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)d($|\.|,|:|;|"|-|‖|\]|\)|\})/$1de$2/g;
          $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)f($|\.|,|:|;|"|-|‖|\]|\)|\})/$1efe$2/g;
          $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)g($|\.|,|:|;|"|-|‖|\]|\)|\})/$1je$2/g;
          $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)h($|\.|,|:|;|"|-|‖|\]|\)|\})/$1ache$2/g;
          $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)j($|\.|,|:|;|"|-|‖|\]|\)|\})/$1jota$2/g;
          $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)k($|\.|,|:|;|"|-|‖|\]|\)|\})/$1ca$2/g;
          $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)l($|\.|,|:|;|"|-|‖|\]|\)|\})/$1ele$2/g;
          $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)m($|\.|,|:|;|"|-|‖|\]|\)|\})/$1eme$2/g;
          $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)n($|\.|,|:|;|"|-|‖|\]|\)|\})/$1ene$2/g;
          $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)p($|\.|,|:|;|"|-|‖|\]|\)|\})/$1pe$2/g;
          $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)q($|\.|,|:|;|"|-|‖|\]|\)|\})/$1cu$2/g;
          $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)r($|\.|,|:|;|"|-|‖|\]|\)|\})/$1ere$2/g;
          $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)s($|\.|,|:|;|"|-|‖|\]|\)|\})/$1ese$2/g;
          $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)t($|\.|,|:|;|"|-|‖|\]|\)|\})/$1te$2/g;
          $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)v($|\.|,|:|;|"|-|‖|\]|\)|\})/$1$name_for_v$2/g;
          $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)w($|\.|,|:|;|"|-|‖|\]|\)|\})/$1doblebé$2/g;
          $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)x($|\.|,|:|;|"|-|‖|\]|\)|\})/$1equis$2/g;
          $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)z($|\.|,|:|;|"|-|‖|\]|\)|\})/$1seta$2/g;
          $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)ñ($|\.|,|:|;|"|-|‖|\]|\)|\})/$1eñe$2/g;
     }

     return ($current_item);
}

###########################################################################################
# SUBROUTINE: FIX LETTER PATTERNS                                                         #
#             This fixes letter patterns that would produce incorrect transcriptions      #
###########################################################################################
sub fix_letter_patterns {

     my $current_item = $_[0];

     ######################################################################################
     # PROTECT -UIR VERB UI HIATUS BEFORE ANY LETTER-PATTERN RULES RUN                  #
     # "deshuir" contains the grapheme sequence "sh" (morpheme boundary: "des-" + "huir")#
     # which the sh→ch rule below would convert to "ch", mangling the word before any   #
     # later protection could act.  We mark the hiatus ui/uí HERE, at the very top of   #
     # this subroutine, before anything else runs.                                       #
     #                                                                                    #
     # Two strategies, chosen so that the preceding consonant context is preserved:      #
     #                                                                                    #
     # 1. hui-family (huir, rehuir, deshuir): consume the silent h INTO the mark so     #
     #    the bare "s" left behind in "deshuir" cannot reform "sh" with anything.        #
     #                                                                                    #
     # 2. All other -uir families: capture the immediately preceding consonant as $1     #
     #    and replace only "ui"/"uí" with the mark.                                      #
     #      b+ui → atribuir, contribuir, distribuir, retribuir                           #
     #      l+ui → fluir, influir                                                        #
     #      t+ui → intuir                                                                #
     #      n+ui → insinuir                                                              #
     #     tr+ui → construir, reconstruir                                                #
     #                                                                                    #
     # Deliberately NOT protected: r+ui (ruido), c+ui (cuidar), f+ui (fuimos) — those  #
     # have genuine ui diphthongs.                                                       #
     ######################################################################################
     $current_item =~ s/huí/UIMARK_ACUTE/g;          # h consumed into mark (silent anyway)
     $current_item =~ s/hui/UIMARK_PLAIN/g;           # h consumed into mark
     $current_item =~ s/(b|l|t|n|tr)uí/$1UIMARK_ACUTE/g;
     $current_item =~ s/(b|l|t|n|tr)ui/$1UIMARK_PLAIN/g;
     # huy- forms (rehuye, rehuyó, rehuyendo, rehúyo...): the h is silent and the
     # u is nuclear (re.u.ʝe, not rew.ʝe).  Consume the h into a mark so that:
     #   (a) the eu→ew diphthong rule in process_diphthongs cannot fire, and
     #   (b) the y is left in place to be converted to {J}→ʤ normally.
     $current_item =~ s/hu(?=y)/HUYMARK/g;    # lookahead keeps "y" for normal {J}→ʤ conversion

     ######################################################################################
     # Fix ANYWHERE                                                                       #
     ######################################################################################
     $current_item =~ s/aa/a/g;                               # sAA > sA
     $current_item =~ s/cc([aou])/k$1/g;                      # staCCato > staCato
     $current_item =~ s/ck/k/g;                               # chadwiCK > chadwik
     $current_item =~ s/cq/q/g;                               # beCQueriano > bekeriano
     $current_item =~ s/ff/f/g;                               # maFFia > maFia (as in Pedro...)
     $current_item =~ s/gh/gu/g;                              # "ghetto" = [geto], not [jeto]
     $current_item =~ s/gnn/gn/g;                             # "Agnnes" = agn.nes > ag.nes
     $current_item =~ s/ll([$orthog_epenth_e_cons])/l$1/g;    # "bulldozer" = bul.do.ser, "allcu" = "al.ku"
     $current_item =~ s/mm/m/g;                               # digaMMa > digaMa, graMMy > graMy
     $current_item =~ s/pp/p/g;                               # shoPPing > shoPing
     $current_item =~ s/sch/ch/g;                             # SCHafer > CHafer, with CH being the Chilean pronunciation
     $current_item =~ s/sh/ch/g;                              # SHOpping > CHÓping, traSH > traCH, with CH being the Chilean pronunciation
     $current_item =~ s/ss/s/g;                               # es.trap.les.s > es.trap.les
     $current_item =~ s/tch/ch/g;                             # keTCHup > keCHup
     $current_item =~ s/tt/t/g;                               # maTThei > maThei
     $current_item =~ s/ym/im/g;                              # aYMara > aIMara (otherwise it would become /aʤ.ˈma.ɾa/)

     ######################################################################################
     # Fix at BEGINNING of word                                                           #
     ######################################################################################
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)mn/$1n/g;       # MNemotecnia > Nemotecnia
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)out/$1aut/g;    # MNemotecnia > Nemotecnia
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)ps/$1s/g;       # PSicología > Sicología
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)pt/$1t/g;       # PTerodáctilo > Terodáctilo
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)sh/$1ch/g;      # SHafer > CHafer
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)shw/$1chw/g;    # SHWartz: makes it /ʧu̯aɾts/, NOT /su̯aɾts/
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)st/$1est/g;     # STange > ESTange
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)mc/$1mak/g;     # MCiver > MAKiver
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)cn/$1n/g;       # CNeorácea > Neorácea
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)gn/$1n/g;       # GNomo > Nomo, GNeiss > Neiss
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)chr/$1cr/g;     # CHristopher > Cristopher

     # MYSTERY: This code is commented out, yet the -aeg flag still works.
     if ( $add_epenthetic_g == 1 ) {
          #$current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)w/$1gw/g;       # Westfaliano > GWestfaliano, Web > GWeb
          }

     # Add epenthetic /e/ before coda /s/ at beginning of word
     $current_item =~ s/(^|\.|,|:|;|"|-|‖|\[|\(|\{)s($orthog_epenth_e_cons)/es$2/g;

     ######################################################################################
     # Fix at END of word                                                             #
     ######################################################################################
     $current_item =~ s/adic($|\.|,|:|;|"|-|‖|\]|\)|\})/ádich$1/g;         # NEW IN 419 "Fadic", etc.
     $current_item =~ s/añalic($|\.|,|:|;|"|-|‖|\]|\)|\})/áñalich$1/g;     # NEW IN 419 "Mañalic", etc.
     $current_item =~ s/eaux($|\.|,|:|;|"|-|‖|\]|\)|\})/ó$1/g;             # SubercasEAUX > subercasó
     $current_item =~ s/endic($|\.|,|:|;|"|-|‖|\]|\)|\})/éndic$1/g;        # NEW IN 419 "Rendic", etc.
     $current_item =~ s/evesic($|\.|,|:|;|"|-|‖|\]|\)|\})/évesich$1/g;     # NEW IN 419 "Chevesic", etc.
     $current_item =~ s/iaux($|\.|,|:|;|"|-|‖|\]|\)|\})/ió$1/g;            # vIAUX > vió # NOTE This produces /ˈbi̯o/ (/bjo/) instead of /bi.'o/
     $current_item =~ s/inovic($|\.|,|:|;|"|-|‖|\]|\)|\})/ínovich$1/g;     # NEW IN 419 "Marinovic", etc.
     $current_item =~ s/lds($|\.|,|:|;|"|-|‖|\]|\)|\})/ld$1/g;             # mcdonaLDS > mcdonaLD
     $current_item =~ s/ll($|\.|,|:|;|"|-|‖|\]|\)|\})/l$1/g;               # lluLL > lluL
     $current_item =~ s/lls($|\.|,|:|;|"|-|‖|\]|\)|\})/ls$1/g;             # nonparells, wells, balcells
     $current_item =~ s/matic($|\.|,|:|;|"|-|‖|\]|\)|\})/mátic$1/g;        # NEW IN 419 "Drivematic", etc.
     $current_item =~ s/nds($|\.|,|:|;|"|-|‖|\]|\)|\})/ns$1/g;             # estaNDS > estaNS
     $current_item =~ s/ng($|\.|,|:|;|"|-|‖|\]|\)|\})/n$1/g;               # shoppiNG > shoppin
     $current_item =~ s/ngs($|\.|,|:|;|"|-|‖|\]|\)|\})/ns$1/g;             # liviNGS > liviNS
     $current_item =~ s/omic($|\.|,|:|;|"|-|‖|\]|\)|\})/ómich$1/g;         # NEW IN 419 "Tomic", etc.
     $current_item =~ s/omicic($|\.|,|:|;|"|-|‖|\]|\)|\})/ómicich$1/g;     # NEW IN 419 "Tomicic", etc.
     $current_item =~ s/q($|\.|,|:|;|"|-|‖|\]|\)|\})/k$1/g;                # estaNDS > estaNS
     $current_item =~ s/unovic($|\.|,|:|;|"|-|‖|\]|\)|\})/únovich$1/g;     # NEW IN 419 "Simunovic", etc.
     $current_item =~ s/ys($|\.|,|:|;|"|-|‖|\]|\)|\})/is$1/g;              # currys > [kuris] and not [kur.dzhs]

     return ($current_item);

}

############################################################################
# SUBROUTINE: CONVERT PUNCTUATION TO PAUSES                                #
############################################################################
sub convert_punctuation_to_pauses {

     my $current_item = $_[0];

     ############################################################################
     # Catch ELLIPSES                                                           #
     ############################################################################
     if ( $current_item =~ m/\.\.\./ ) {
          $ellipsis_pause = 1;

          # Temporarily change ellipsis to € to avoid counting it as a periods,
          # too. The € will later be checked to see if it occurs right before
          # a newline.
          $current_item =~ s/\.\.\./€/g;
     }

     ############################################################################
     # Catch PERIODS, EXCLAMATION MARKS AND QUESTION MARKS for later insertion
     # as "##" breaks
     ############################################################################
     if ( $current_item =~ m/[.!?]/ ) {
          $sentence_break = 1;
     }

     ############################################################################
     # Catch COMMAS for later insertion as "(..)" pauses                        #
     ############################################################################
     if ( $current_item =~ m{,} ) {
          $comma_pause = 1;
     }

     ############################################################################
     # Catch SEMICOLONS for later insertion as "(...)" pauses                   #
     ############################################################################
     if ( $current_item =~ m{;} ) {
          $semicolon_pause = 1;
     }

     ############################################################################
     # Catch COLONS for later insertion as "(..)" pauses                        #
     ############################################################################
     if ( $current_item =~ m{:} ) {
          $colon_pause = 1;
     }

     ############################################################################
     # Catch BRACKETS for later insertion as "(.)" pauses                       #
     ############################################################################

     # INITIAL BRACKETS
     if ( $current_item =~ m{\[} ) {
          $initial_bracket_pause = 1;
     }

     # FINAL BRACKETS
     if ( $current_item =~ m{\]} ) {
          $final_bracket_pause = 1;
     }

     ############################################################################
     # Catch PARENS for later insertion as "(.)" pauses
     ############################################################################

     # INITIAL PARENS
     if ( $current_item =~ m{\(} ) {
          $initial_paren_pause = 1;
     }

     # FINAL PARENS
     if ( $current_item =~ m{\)} ) {
          $final_paren_pause = 1;
     }

     ############################################################################
     #  Strip ALL white space, punctuation and other non-alphabetic chars from  #
     #  word.                                                                   #
     #                        (FOR NON-VRT FORMATS)                             #
     ############################################################################
     if ( $vrt_format == 0 ) {
          $current_item =~ s{\W}{}g;
     }
     ############################################################################
     # Strip white space, punctuation and other non-alphabetic chars from       #
     # BEGINNING and END of each word (which can actually be 1+ words in VRT    #
     # format).                                                                 #
     #                           (FOR VRT FORMAT)                               #
     ############################################################################
     if ( $vrt_format == 1 ) {
          $current_item =~ s{^\W}{}g;
          $current_item =~ s{\W$}{}g;
     }

     return ($current_item);
}

##################################################################################
# CONVERT BACKCHANNEL VOCALIZATIONS                                              #
##################################################################################

sub convert_backchannel_vocalizations {

     # NOTE:    YOU MAY CHANGE OR EXTEND THE VOCALIZATIONS PROCESSED. HOWEVER...
     # WARNING: IMPLEMENTING THE CHANGES DESCRIBED BELOW IS COMPLICATED. IF YOU DO
     #          SO, MAKE SURE TO TEST THE OUTPUT EXTENSIVELY.
     #
     # To add backchannel vocalizations several things need to be done:
     # 1a. If they're multi-word (e.g. "mm hm"), you have to go to the
     #     replace_multiword_phrases subroutine and add an entry for them,
     #     converting them into some intermediate SINGLE-WORD representation
     #     that is in ALL CAPS and that will then be used here.
     # 1b. If they're single-word, you have to do the same thing in the
     #     modify_single_ortho_words section.
     # 2. Add an entry BELOW providing the definitive PHONETIC transcription,
     #    including syllabification. This MIGHT cause problems with certain
     #    types of transcripcion (e.g. CV)... haven't tested this yet. Some things
     #    don't work quite right, either, e.g. /ch/ gets a dental diacritic placed on the t.

     my $current_item = $_[0];

      # DEBUG
      #print STDOUT "\n\$current_item = $current_item";    # NOTE: DEBUG

     # NOTE: THE USE OF PHONETIC (RATHER THAN PHONEMIC) TRANSCRIPTION HERE IS TRULY
     #       ANOMALOUS - IT OCCURS NOWHERE ELSE. BUT THERE'S REALLY NO OTHER WAY TO
     #       REPRESENT THESE VOCALIZATIONS ACCURATELY.


     $current_item =~ s/MMHM/m̩.ˈhm̩/g;
     $current_item =~ s/NNNN/ˈnt̚.n̩/g;
     $current_item =~ s/UHHUH/ʔʌ.ˈhʌ/g;
     $current_item =~ s/UHOH/ˈʔʌ.ʔo/g;
     $current_item =~ s/UHUH/ˈʔʌ.ʔʌ/g;

     $current_item =~ s/AH/aː/g;
     $current_item =~ s/EH/eː/g;
     $current_item =~ s/OH/oː/g;
     $current_item =~ s/UH/uː/g;

     $current_item =~ s/FF/f̩ː/g;
     $current_item =~ s/PF/pf̩ː/g;
     $current_item =~ s/MM/m̩ː/g;
     $current_item =~ s/HM/hm̩ː/g;
     $current_item =~ s/CH/ʧʃ ̩ː/g;
     $current_item =~ s/SH/ʃ ̩ː/g;

     return ($current_item);

}


############################################################################
# SUBROUTINE: CONVERT (MOST) GRAPHEMES TO INTERMEDIATE REPRESENTATIONS OF  #
#             PHONEMES                                                     #
############################################################################

sub convert_graphemes_to_interm_phonemes {

     my $current_item = $_[0];

     # /tr/ cluster
     $current_item =~ s/tr/\{TR\}/g;

     # <ll> cluster
     $current_item =~ s/ll/\{J\}/g;

     # Grapheme <t>
     $current_item =~ s/t/\{T\}/g;

     # Grapheme <d>
     $current_item =~ s/d/\{D\}/g;

     # Grapheme <ñ>
     $current_item =~ s/ñ/\{Ñ\}/g;

     # Grapheme <v>
     $current_item =~ s/v/\{B\}/g;

     # <ch> cluster
     $current_item =~ s/ch/\{CH\}/g;

     # <qu> cluster
     $current_item =~ s/qui(e|é)/\{K\}i$1/g;     # To avoid conflicts with expressions below, "j" is represented as "i" here.
     $current_item =~ s/qu(i|í)$/\{K\}$1/g;      # To catch words like "aquí".
     $current_item =~ s/qu(o|ó)/\{K\}\{W\}$1/g;  # For Latinisms like "quorum", etc.
     $current_item =~ s/qu/\{K\}/g;

     # Grapheme <x>
     $current_item =~ s/^x/\{S\}/g;              # <x> at beginning of word > /s/ ("xilófono").
     $current_item =~ s/x/\{KS\}/g;              # For all other words ("máximo", "aproximado", "Axolotl").

     # Grapheme <ü>
     # NOTE: This *retains* [g] in words with orthographic <g> plus <ü> (e.g. <agüero>).
     #       A distinction is thus introduced between <hu> and <gü> (e.g. <huevón> vs. <güevón>). This
     #       distinction is artificial and not reflected in language use; it exists to allow words like
     #       <agüero> to retain /g/, while not introducing the epenthetic [g] (if so desired) in words
     #       like <huevo>. Thus, it should only be a problem with variant spellings that are considered
     #       incorrect, like "güevo".
     $current_item =~ s/ü/\{W\}/g;

     # Grapheme <h>
     # Note: hui/huí in -uir verb forms (huir, rehuir, deshuir) were already
     # marked as UIMARK_PLAIN/UIMARK_ACUTE at the top of fix_letter_patterns,
     # before the sh→ch rule could fire.  The h was consumed into the mark there,
     # so by the time we reach this point the word contains no "hui" sequence —
     # only the mark followed by whatever came after.  The hu+vowel rule below
     # therefore fires only on genuine hu+vowel words (huevo, huacho, etc.).

     if ( $add_epenthetic_g == 1 ) {
          $current_item =~ s/hu$all_vowels/\{G\}\{W\}$1/g;    # "huevo" > hwebo > /gwebo/, "huacho" > /gwacho/
     }
     else {
          $current_item =~ s/hu$all_vowels/\{W\}$1/g;    # "huevo" > hwebo > /webo/, "huacho" > /wacho/
     }

     $current_item =~ s/h//g;                            # Eliminate <h> in all other cases.

     # Grapheme <g>
     $current_item =~ s/gu(i|í|e|é)/\{G\}$1/g;         # Catch words like "Guevara" and "guita" and avoid /gwe.../, /gwi.../
     $current_item =~ s/g(a|á|o|ó|u|ú)/\{G\}$1/g;
     $current_item =~ s/g(i|í|e|é)/\{X\}$1/g;

     # Grapheme <r>, in various combinations
     $current_item =~ s/rr/\{RR\}/g;
     $current_item =~ s/^r/\{RR\}/g;
     $current_item =~ s/sr/s\{RR\}/g;
     $current_item =~ s/zr/z\{RR\}/g;                    # As in "Azra"
     $current_item =~ s/lr/l\{RR\}/g;                    # Is this actually the linguistic case?
     $current_item =~ s/nr/n\{RR\}/g;                    # Is this actually the linguistic case?
     $current_item =~ s/r/\{R\}/g;

     # Grapheme <j>
     $current_item =~ s/j/\{X\}/g;

     # Grapheme <c>
     $current_item =~ s/c(i|í|e|é)/\{S\}$1/g;
     $current_item =~ s/c/\{K\}/g;

     # Grapheme <y> in general
     $current_item =~ s/y/\{J\}/g;
     #print "\n$current_item";     # DEBUG

     # Process Chilean voseo accentuation
     # If there's a word-final grapheme "y", make the last vowel acute.
     $current_item =~ s/i\{J\}$/íj/g;
     $current_item =~ s/e\{J\}$/éj/g;
     $current_item =~ s/a\{J\}$/áj/g;
     $current_item =~ s/o\{J\}$/ój/g;
     $current_item =~ s/u\{J\}$/új/g;

     #print "\n$current_item";     # AD HOC DEBUG

     # Fix the grapheme <y> when it's representing the preposition "y"
     $current_item =~ s/^\{J\}$/i/g;
     # DEBUG
     #print "\n$current_item";     # AD HOC DEBUG

     return ($current_item);

}

############################################################################
# SUBROUTINE: CHANGE INTERMEDIATE REPRESENTATIONS OF PHONEMES TO THEIR     #
#                    DEFINITIVE **ONE-CHARACTER** FORMS                    #
############################################################################
sub change_interm_phonemes_to_one_char {

     my $current_item = $_[0];

     $current_item =~ s/\{B\}/b/g;
     $current_item =~ s/\{CH\}/ʧ/g;
     $current_item =~ s/\{D\}/d/g;
     $current_item =~ s/\{G\}/g/g;
     $current_item =~ s/\{J\}/ʤ/g;
     $current_item =~ s/\{K\}/k/g;
     $current_item =~ s/\{KS\}/ks/g;
     $current_item =~ s/\{Ñ\}/ɲ/g;
     $current_item =~ s/\{R\}/ɾ/g;
     $current_item =~ s/\{RR\}/r/g;
     $current_item =~ s/\{S\}/s/g;
     $current_item =~ s/\{TR\}/ʂ/g;
     $current_item =~ s/\{T\}/t/g;
     $current_item =~ s/\{W\}/w/g;
     $current_item =~ s/\{X\}/x/g;

     return ($current_item);

}

############################################################################
# SUBROUTINE: PROCESS DIPHTHONGS                                           #
#   /j/ and /w/ are used as intermediate representations of                #
#   non-syllabic vowels                                                    #
############################################################################
sub process_diphthongs {

     my $current_item = $_[0];

     ############################################
     # -UIR VERB FORMS WITH TRUE UI HIATUS
     ############################################
     # UIMARK_ACUTE and UIMARK_PLAIN were placed in fix_letter_patterns (before
     # any letter-pattern rules ran) and have survived grapheme conversion and
     # one-char conversion unchanged (all-uppercase ASCII, invisible to those rules).
     # Detect their presence now to guard the fui block and the diphthong rules.
     my $protect_hiatus_ui = ( $current_item =~ /UIMARK_(?:ACUTE|PLAIN)/ );

     ############################################
     # SPECIAL CASE: fui
     ############################################
     # Also skip words carrying UIMARK_* — those are protected -uir verb forms.

     if (
            $current_item !~ /[áéíóú]/
         && $current_item ne "fui"
         && !$protect_hiatus_ui
     )
     {
          $current_item =~ s/ui$/új/g;
     }

     ############################################
     # LEXICAL EXCEPTIONS:
     # TRUE HIATUS FORMS
     ############################################

     # IMPORTANT: keys here are PHONEMIC forms, not orthographic ones.
     # By the time process_diphthongs() is called, the word has already passed
     # through convert_graphemes_to_interm_phonemes() and
     # change_interm_phonemes_to_one_char(), so consonants have been rewritten:
     #   c (before a/o/u) → k     v → b     g (before e/i) → x
     #   c (before e/i)   → s     r (non-initial, non-cluster) → ɾ
     #   gu (before e/i)  → g     r (initial or after n/l/s/z) → r
     # amplió is the canonical example that already worked: none of its
     # consonants are affected by these rules, so "amplió" survives unchanged.
     # Every other key below was wrong (still in orthographic form) and therefore
     # never matched, leaving all those words unprotected.
     my %HIATUS_IO = map { $_ => 1 } qw(
          río ríos
          fɾío fɾía fɾíos fɾías
          tío tía tíos tías
          lío lía líos lías
          pío pía píos pías

          rosío
          xentío
          bɾío

          kɾío kɾía kɾías
          gío gía gías
          enbío enbía enbías

          rió
          rióse

          kɾió
          gió
          enbió

          konfió
          desbió
          basió
          amplió
          baɾió
          kopió
     );

     my $protect_hiatus_io = exists $HIATUS_IO{ lc($current_item) };

     if ($protect_hiatus_io)
     {
          $current_item =~ s/ió$/IO_HIATUS_MARK_O/;    # preterite: stress on o  (e.g. confió)
          $current_item =~ s/ío$/IO_HIATUS_MARK_I/;    # present:   stress on í  (e.g. envío)

          $current_item =~ s/ías$/IAS_HIATUS_MARK/;
          $current_item =~ s/ía$/IA_HIATUS_MARK/;
     }

     ############################################
     # PRODUCTIVE -CIÓN / -SIÓN
     ############################################

     $current_item =~ s/ción$/SJON_MARK/g;
     $current_item =~ s/sión$/SJON_MARK/g;
     $current_item =~ s/xión$/XSJON_MARK/g;

     ############################################
     # PRODUCTIVE PRETERITE -IÓ
     ############################################

     if (
            !$protect_hiatus_io
         && $current_item =~ /ió$/
     )
     {
          $current_item =~ s/ió$/jó/;
     }

     ############################################
     # GENERAL DIPHTHONG RULES
     ############################################

     $current_item =~ s/i(e|a|o|u)/j$1/g;

     $current_item =~ s/éis$/éjs/g;

     $current_item =~ s/u(i|e|a|o|í|é|á|ó)/w$1/g;

     $current_item =~ s/([aeou])i/$1j/g;
     # Protect "ou" hiatus in "estadounidenses" before the diphthong rule fires.
     # In Spanish, "estado" + "unido" → the o+u sequence is at a morpheme boundary
     # and forms a hiatus (es.ta.do.u.ni.den.ses), not a diphthong (dow).
     # The general rule ([aeo])u→$1w would incorrectly produce "estadow..." → "dow".
     # We protect this by marking the hiatus before the rule and restoring after.
     $current_item =~ s/estadou/estadoOU_HIATUS/g;
     $current_item =~ s/([aeo])u/$1w/g;
     $current_item =~ s/estadoOU_HIATUS/estadou/g;

     ############################################
     # RESTORE PRODUCTIVE SUFFIXES
     ############################################

     $current_item =~ s/XSJON_MARK/ksjón/g;
     $current_item =~ s/SJON_MARK/sjón/g;

     ############################################
     # RESTORE LEXICAL HIATUS
     ############################################

     if ($protect_hiatus_io)
     {
          $current_item =~ s/IO_HIATUS_MARK_O/ió/g;    # restore ió  (stress stays on o via tilde)
          $current_item =~ s/IO_HIATUS_MARK_I/ío/g;    # restore ío  (stress stays on í via tilde)
          $current_item =~ s/IAS_HIATUS_MARK/ías/g;
          $current_item =~ s/IA_HIATUS_MARK/ía/g;
     }

     # Restore -uir verb hiatus marks placed in fix_letter_patterns.
     # Both marks restore to uí (tilde on i) because in every form of these -uir
     # verbs the stress falls on i, never on u:
     #   UIMARK_ACUTE: word already had orthographic tilde on í (e.g. construí, fluí)
     #   UIMARK_PLAIN: word had no orthographic tilde; stress_accent_routine would
     #                 need to assign it, but struggles with the u+i sequence when
     #                 the final consonant is the multi-byte ɾ.  Supplying the tilde
     #                 on í here is always correct and avoids the ambiguity entirely.
     # Exception: forms like uiéramos where a tilde already appears elsewhere — in
     # those cases stress_accent_routine takes the "tilde" branch and ignores any
     # further tilde, so the extra í tilde is harmless.
     $current_item =~ s/UIMARK_ACUTE/uí/g;
     $current_item =~ s/UIMARK_PLAIN/uí/g;
     # Restore huy-verb marks: h was silent; u is the nuclear vowel; y was already
     # converted to ʤ by the grapheme rules before process_diphthongs ran.
     # Restoring to plain "u" here (after all diphthong rules have run) means the
     # eu→ew rule could never have seen it, so "e.u" hiatus is preserved.
     # The syllabifier will split "eu" before a consonant via the new Rule 25-C.
     $current_item =~ s/HUYMARK/u/g;

     return ($current_item);
}
###############################################################################
#                                                                             #
# SUBROUTINE: STRESS ACCENT ROUTINE - PRE-PROCESSING                          #
#                                                                             #
###############################################################################
# NOTE: This section need to be *here*, before tildes are removed from vowels.#
###############################################################################
sub stress_accent_routine {

     # Like perform_syllabification, this routine relies on rindex() and
     # @character lookups that can legitimately return/reference undef as
     # part of correct, pre-existing stress-placement logic (e.g. no
     # preceding vowel found). Not a bug -- silence the warning rather
     # than change logic.
     no warnings 'uninitialized';

     my $current_item = $_[0];

     # print binmode "START-STRESS-ACCENT-RT:$current_item\n"; # AD-HOC DEBUG

     # SPLIT CURRENT WORD UP INTO CHARACTERS AND ASSIGN EACH TO AN ARRAY
     @character = split //, $current_item;

     # GET NUMBER OF ITEMS (CHARACTERS) IN THE CHARACTER ARRAY
     my $char_count = @character;

     # GET THE VOWEL CHARACTERS AND COUNT THEM.
     $vowels = $current_item;

     # HERE IS AN ODD BUT NECESSARY MIX OF PHONEMES (b,d,ɾ) AND GRAPHEMES (y,z). #
     # THIS REMOVES THEM TO LEAVE JUST VOWELS.                              #
     $vowels =~ s/(b|ʧ|d|ʤ|f|g|j|k|l|m|n|ɲ|p|ɾ|r|s|ʂ|t|w|x|y|z)//g;

     #print "\n$vowels";     # AD HOC DEBUG

     $accent         = "";    # Resets variable
     $last_vowel_pos = "";    # Resets variable

     # GET THE NUMBER OF VOWELS IN THE WORD BEING ANALYZED
     $vowel_count = length($vowels);

     #print "\nCurrent Item:\t$current_item\nVowels:\t\t$vowels ($vowel_count)\n";     # AD HOC DEBUG

     ############################################################################
     #                   STRESS ACCENT ROUTINE - LOGIC AND ACTION               #
     ############################################################################

     # IF WORD IS MONOSYLLABIC, REMOVE TILDE FROM ITS LONE VOWEL
     # NOTE:  The vowels of monosyllabic words should NOT get a tilde (or, later,
     #        a stress mark ').
     if ( $vowel_count == 1 ) {
          $accent = "monosyllabic";

          $current_item =~ s/á/a/;
          $current_item =~ s/é/e/;
          $current_item =~ s/í/i/;
          $current_item =~ s/ó/o/;
          $current_item =~ s/ú/u/;
     }

     # SKIP WORDS THAT HAVE TILDES, AS THEIR STRESS IS ALREADY INDICATED
     elsif ( $vowels =~ m/$stressed_vowels/ ) {
          $accent = "tilde";
     }

     # IF WORD ENDS IN ACUTE-IZING CONSONANT, PLACE STRESS ON LAST SYLLABLE
     elsif ( $character[-1] =~ m/[$acute_consonants_except_j]/ ) {
           $accent = "acute";

          # THIS IS A RATHER COMPLEX WAY OF FIGURING OUT THE LAST VOWEL.
          # SINCE IT'S USED TWICE, MIGHT WANT TO TURN IT INTO A SUBROUTINE.

          # First, reverse the list of vowels, split it and put it into an array.
          @rev_vowels = ( split //, reverse($vowels) );

          # The first element of the array, @rev_vowels[0], is the first vowel.
          $last_vowel_pos = rindex( $current_item, $rev_vowels[0] );

          # Now replace unaccented last vowel with accented version
          $character[$last_vowel_pos] =~ s/a/á/;
          $character[$last_vowel_pos] =~ s/e/é/;
          $character[$last_vowel_pos] =~ s/i/í/;
          $character[$last_vowel_pos] =~ s/o/ó/;
          $character[$last_vowel_pos] =~ s/u/ú/;

          # Finally, rebuild $current_item using characters in array @character
          $current_item = join( "", @character );
     }

     # IN ALL OTHER CASES, STRESS IS ON SECOND-TO-LAST SYLLABLE.
     else {
          $accent = "grave";

          # THIS IS A ROUNDABOUT WAY OF FIGURING OUT THE SECOND-TO-LAST VOWEL.

          # First, reverse the list of vowels, split it and put it into an array.
          @rev_vowels = ( split //, reverse($vowels) );

          # The first element of the array, $rev_vowels[0], is the first vowel.
          $last_vowel_pos = rindex( $current_item, $rev_vowels[0] );
          $sec_last_vowel_pos = rindex( $current_item, $rev_vowels[1], $last_vowel_pos - 1 );

          # Now replace unaccented last vowel with accented version
          $character[$sec_last_vowel_pos] =~ s/a/á/;
          $character[$sec_last_vowel_pos] =~ s/e/é/;
          $character[$sec_last_vowel_pos] =~ s/i/í/;
          $character[$sec_last_vowel_pos] =~ s/o/ó/;
          $character[$sec_last_vowel_pos] =~ s/u/ú/;

          # Rebuild $current_item using the characters in the array @character
          $current_item = join( '', @character );
     }

     #print "Accent: $accent\t$current_item\n";     # AD HOC DEBUG

     return ($current_item);

}

############################################################################
# SUBROUTINE: FINAL TOUCHES TO THE BROAD TRANSCRIPTION                     #
############################################################################
sub final_touches_broad_transcr {

     # Intentionally reads @character at positions that may be past the
     # start/end of the array as part of boundary checks; undef there is
     # treated as "no adjacent character" (falsy), which is correct,
     # pre-existing behavior -- silence the warning rather than change logic.
     no warnings 'uninitialized';

     my $current_item = $_[0];

     # Grapheme <z> becomes <s>
     $current_item =~ s/z/s/g;

     # Phoneme /j/ at end of word becomes <i>
     $current_item =~ s/([^$all_vowels])j$/$1i/g;    # <y> a final de palabra becomes <i>: "Uruguay"

     return ($current_item);

}

############################################################################
#                                                                          #
#                        SUBROUTINE: SYLLABIFICATION                       #
#                                                                          #
############################################################################
sub perform_syllabification {

     # Syllabification repeatedly probes neighboring array positions
     # ($character[$i-1], $character[$i+1], $character[$i+2], etc.) to
     # detect cluster/boundary patterns. Near the start/end of a word
     # these reads intentionally go out of range and rely on undef
     # comparing false -- correct, pre-existing behavior, not a bug.
     no warnings 'uninitialized';

     my ($current_item) = $_[0];

     if ( $debug_syllab_sub == 1 ) {
          print STDOUT "\n\$current_item = $current_item";    # NOTE: DEBUG THIS IF REQUESTED
     }

     @character = split //, $current_item;

     my $char_count = @character;

     for ( $i = 0 ; $i < $char_count ; $i++ ) {

          #################################################################################
          #                           SYLLABIFICATION RULE 1                              #
          #                                                                               #
          # AFFECTS: C + /bdfgkp/ + /ɾl/ + /j/                                            #
          # CHANGES: Inserts syllable break between consonants 1 & 2                      #
          # EXAMPLE: em.bɾja.gaɾ                                                          #
          #################################################################################
          # WARNING 2: Added "l" to the second expression below; used to be only ɾ        #
          if (    $character[$i] =~ m/[$all_consonants]/
               && $character[ $i + 1 ] =~ m/[bdfgkp]/
               && $character[ $i + 2 ] =~ m/[ɾl]/
               && $character[ $i + 3 ] eq "j" )
          {

               # Add syllable dot
               $character[ $i + 1 ] = (".$character[$i+1]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 3;
          }

          # WARNING Added "w" to "j" in final expression in 311 WARNING                   #
          #################################################################################
          #                           SYLLABIFICATION RULE 2                              #
          #                                                                               #
          # AFFECTS: /bdfgkp/ + /ɾl/ + /jw/                                               #
          # CHANGES: Inserts NO syllable break                                            #
          # EXAMPLE: gɾje.ga (crianza, briana, etc.)                                      #
          #################################################################################
          # WARNING 2: Added "l" to the second expression below; used to be only ɾ        #
          # WARNING Added "w" to "j" in third expression below in 311                     #
          elsif ( $character[$i] =~ m/[bdfgkp]/
               && $character[ $i + 1 ] =~ m/[ɾl]/
               && $character[ $i + 2 ] =~ m/[jw]/ )
          {

               # Add NO syllable dot

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 2;
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 3                              #
          #     Same as a later rule, but catches this cluster when preceded by vowel.    #
          #                                                                               #
          # AFFECTS: V + /bdfgkp/ + /ɾl/ + /jw/ -- DANGER Added /w/ to /j/ in 311         #
          # CHANGES: Inserts syllable break after vowel                                   #
          # EXAMPLE: i.gɾje.ga   (acrianza, apriori, adriático, etc.)                     #
          #################################################################################
          # WARNING 2: Added "l" to the second expression below; used to be only ɾ        #
          # WARNING: Added "w" to "j" in final expression in 311                          #

          elsif ( $character[$i] =~ m/[$all_vowels]/
               && $character[ $i + 1 ] =~ m/[bdfgkp]/
               && $character[ $i + 2 ] =~ m/[ɾl]/
               && $character[ $i + 3 ] =~ m/[jw]/ )
          {

               # Add syllable dot
               $character[ $i + 1 ] = (".$character[$i+1]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 3;
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 4                              #
          #                                                                               #
          # AFFECTS: 4 consecutive MISC CONS                                              #
          # CHANGES: Inserts syllable break between consonants 2 & 3                      #
          # EXAMPLE: in.scrito, ek.s(tr)anjero                                            #
          #################################################################################
          elsif ( $character[$i] =~ m/[$all_consonants]/
               && $character[ $i + 1 ] =~ m/[$all_consonants]/
               && $character[ $i + 2 ] =~ m/[$all_consonants]/
               && $character[ $i + 3 ] =~ m/[$all_consonants]/ )
          {

               # Add syllable dot
               $character[ $i + 2 ] = (".$character[$i+2]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 3;
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 5                              #
          #                                                                               #
          # AFFECTS: MISC CONS + /s/ + APPRX                                              #
          # CHANGES: Inserts syllable break between consonants 1 & 2                      #
          # EXAMPLE: op.sión sek.swál                                                     #
          #################################################################################
          elsif ( $character[$i] =~ m/[$all_consonants]/
               && $character[ $i + 1 ] eq "s"
               && $character[ $i + 2 ] =~ m/[jw]/ )
          {

               # Add syllable dot
               $character[ $i + 1 ] = (".$character[$i+1]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 2;
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 6                              #
          #                                                                               #
          # AFFECTS: MISC CONS + /s/ + MISC CONS (NON-APPRX)                              #
          #          ($all_consonants includes approximants, but they are processed in    #
          #          this environment by Syllabification Rule 2).                         #
          # CHANGES: Inserts syllable break between consonants 2 & 3                      #
          # EXAMPLE: ins.pirado, eks.perjensja                                            #
          #################################################################################
          elsif ( $character[$i] =~ m/[$all_consonants]/
               && $character[ $i + 1 ] eq "s"
               && $character[ $i + 2 ] =~ m/[$all_consonants]/ )
          {

               # Add syllable dot
               $character[ $i + 2 ] = (".$character[$i+2]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 2;
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 7                              #
          #                                                                               #
          # AFFECTS: /s/ + /u/ + /b/ + /lɾw/ at BEGINNING OF WORD                         #
          # CHANGES: Inserts syllable break after /b/. Basically for prefix "sub-"        #
          # EXAMPLE: SU.Brep.ti.sjo > SUB.rep.ti.sjo                                      #
          #################################################################################
          # NOTE In 317, changed the second argument from [$all_vowels] to eq "u", as it  #
          #      was syllabifying "imposible" > im.posib.le, etc.                         #
          #      Also changed it to only apply at the beginning of words (in effect, to   #
          #      prefix "sub-").                                                          #
          elsif ( $character[$i] eq "s"
               && $character[ $i + 1 ] eq "u"
               && $character[ $i + 2 ] eq "b"
               && $character[ $i + 3 ] =~ m/[lɾw]/ )
          {

               # Limit this rule to cases where /sub/ is at the beginning of the word     #
               # since the rule seems to only apply at that position                      #
               if ( $i == 0 ) {

                    # Add syllable dot
                    $character[ $i + 3 ] = (".$character[$i+3]");

                    # Increment $i so as not to repeatedly process this cluster
                    $i = $i + 3;
               }
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 8                              #
          #                                                                               #
          # AFFECTS: VOWEL + /b/ + LIQUID                                                 #
          # CHANGES: Inserts syllable break between VOWEL and /b/.                        #
          # EXAMPLE: This gets words like "pro.blema", "ha.blar" and "a.brir", but it     #
          #          DOESN'T get words like "increíble", which comes out as in.kɾe.ÍBLE   #
          #          without additional (later) measures                                  #
          #################################################################################
          elsif ( $character[$i] =~ m/[$all_vowels]/
               && $character[ $i + 1 ] eq "b"
               && $character[ $i + 2 ] =~ m/[lrɾ]/ )
          {

               # Add syllable dot
               $character[ $i + 1 ] = (".$character[$i+1]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 2;
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 9                              #
          #                                                                               #
          # AFFECTS: [s] + [t] + C                                                        #
          # CHANGES: Inserts syllable break between 2nd and 3rd.                          #
          # EXAMPLE: poSTData, poSTPalatal, poSTGuerra,                                   #
          #          ("postraumático" shouldn't be included as /tr/ is one phoneme        #
          #################################################################################
          elsif ( $character[$i] eq "s"
               && $character[ $i + 1 ] eq "t"
               && $character[ $i + 2 ] =~ m/[$all_consonants]/ )
          {

               # Add syllable dot
               $character[ $i + 2 ] = (".$character[$i+2]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 2;
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 10                             #
          #                                                                               #
          # AFFECTS: [w] + [bkdfgxp] + [ɾl]                                               #
          # CHANGES: Inserts syllable break between 2nd and 3rd syllable.                 #
          # EXAMPLE: "Louvre" = lowb.ɾe > low.bɾe                                         #
          #################################################################################
          elsif ( $character[$i] eq "w"
               && $character[ $i + 1 ] =~ m/[bkdfgxp]/
               && $character[ $i + 2 ] =~ m/[ɾl]/ )
          {

               # Add syllable dot
               $character[ $i + 1 ] = (".$character[$i+1]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 2;
          }






          #################################################################################
          #                           SYLLABIFICATION RULE 10-B                           #
          #                                                                               #
          # AFFECTS: [w] + C + [j]                                                        #
          # CHANGES: Inserts syllable break between 1st and 2nd syllable.                 #
          # EXAMPLE: Claudia > klaw.dja, NOT klawd.jazz                                   #
          #################################################################################
          elsif ( $character[$i] eq "w"
               && $character[ $i + 1 ] =~ m/[$all_consonants]/
               && $character[ $i + 2 ] =~ m/[j]/ )
          {

               # Add syllable dot
               $character[ $i + 1 ] = (".$character[$i+1]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 2;
          }





         #################################################################################
          #                           SYLLABIFICATION RULE 11                             #
          #                                                                               #
          # AFFECTS: [w] + C + C                                                          #
          # CHANGES: Inserts syllable break between 2nd and 3rd syllable.                 #
          # EXAMPLE: output, outsource                                                    #
          #################################################################################
          elsif ( $character[$i] eq "w"
               && $character[ $i + 1 ] =~ m/[$all_consonants]/
               && $character[ $i + 2 ] =~ m/[$all_consonants]/ )
          {

               # Add syllable dot
               $character[ $i + 2 ] = (".$character[$i+2]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 2;
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 12                             #
          #                                                                               #
          # AFFECTS: [ɾ]  +  [kg]  +  [s]                                                 #
          # CHANGES: Inserts syllable break between 2nd and 3rd.                          #
          # EXAMPLE: mark.sis.ta, ber.gso.nja.no > berg.so.nja.no                         #
          #################################################################################
          elsif ( $character[$i] eq "ɾ"
               && $character[ $i + 1 ] =~ /[gk]/
               && $character[ $i + 2 ] eq "s" )
          {

               # Add syllable dot
               $character[ $i + 2 ] = (".$character[$i+2]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 2;
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 13                             #
          #                      Exceptions to 12 (3 consec misc cons)                    #
          #                                                                               #
          # AFFECTS: 3 consecutive MISC CONS (SPECIFIC ONES, THOUGH)                      #
          # CHANGES: Inserts syllable break between 2nd and 3rd.                          #
          # EXAMPLE: corp.banca, gold.farb, gerb.ner, lamb.da                             #
          #################################################################################
          elsif ( $character[$i] =~ m/[lɾm]/
               && $character[ $i + 1 ] =~ m/[bpd]/
               && $character[ $i + 2 ] =~ m/[bdnf]/ )
          {

               # Add syllable dot
               $character[ $i + 2 ] = (".$character[$i+2]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 2;
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 14                             #
          #                                                                               #
          # AFFECTS: 3 consecutive MISC CONS                                              #
          # CHANGES: Inserts syllable break between 1st and 2nd.                          #
          # EXAMPLE: in.creíble, im.provisar, es.drújula.                                 #
          #################################################################################

          # FIX ME!!! 390 -- this incorrectly turns "bejntiséjs" into "bej.ntiéjejs"
          # Changed from $all_consonants in 390. EXPERIMENTAL!
          elsif ( $character[$i] =~ m/[$all_consonants_but_glides]/
               && $character[ $i + 1 ] =~ m/[$all_consonants]/
               && $character[ $i + 2 ] =~ m/[$all_consonants]/ )
          {

               # Add syllable dot
               $character[ $i + 1 ] = (".$character[$i+1]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 2;
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 15                             #
          #                                                                               #
          # AFFECTS: LIQUID + LIQUID                                                      #
          # CHANGES: Inserts syllable break between 1st and 2nd.                          #
          # EXAMPLE: ar.let (?), alrededor (?) NOTE: must verify examples                 #
          #################################################################################
          elsif ( $character[$i] =~ m/[lrɾ]/
               && $character[ $i + 1 ] =~ m/[lrɾ]/ )
          {

               # Add syllable dot
               $character[ $i + 1 ] = (".$character[$i+1]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 1;
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 16                             #
          #                                                                               #
          # AFFECTS: /j/ + LIQUID                                                         #
          # CHANGES: Inserts syllable break between them.                                 #
          # EXAMPLE: paj.lón (?)  NOTE: must verify examples                              #
          #################################################################################

          elsif ( $character[$i] eq "j"
               && $character[ $i + 1 ] =~ m/[lrɾ]/ )
          {

               # Add syllable dot
               $character[ $i + 1 ] = (".$character[$i+1]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 1;
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 17                             #
          #                                                                               #
          # AFFECTS: ACUTE CONS (NOT /n,s/) + LIQUID                                      #
          # CHANGES: This PREVENTS a syllable break from being inserted between them.     #
          # EXAMPLE: PREVENTS f.río, hab.lar (but PERMITS is.rael, en.redado).            #
          #################################################################################
          elsif ( $character[$i] =~ m/[$acute_consonants]/
               && $character[ $i + 1 ] =~ m/[lrɾ]/ )
          {

               # Do not introduce syllable break - this just skips this
               # cluster to avoid incorrect syllabification

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 1;
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 18                             #
          #                                                                               #
          # AFFECTS: MISC CONS + /j,w/                                                    #
          # CHANGES: This PREVENTS a syllable break from being inserted between them.     #
          # EXAMPLE: PREVENTS k.watro, k.jen                                              #
          #################################################################################
          elsif ( $character[$i] =~ m/[$all_consonants]/
               && $character[ $i + 1 ] =~ m/[jw]/ )
          {

               # Do not introduce syllable break

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 1;
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 19                             #
          #                                                                               #
          # AFFECTS: MISC CONS + MISC CONS                                                #
          # CHANGES: Inserts a syllable break between the two.                            #
          # EXAMPLE: ac.ción tam.bién can.sado                                            #
          #################################################################################
          # FIX ME!!! This rule turns "veintiséis" into /bej.ntiséjs/
          # WARINING - Changed variable in next line to $all_consonants_but_glides in 390!!!
          elsif ( $character[$i] =~ m/[$all_consonants_but_glides]/
               && $character[ $i + 1 ] =~ m/[$all_consonants]/ )
          {

               # Add syllable dot
               $character[ $i + 1 ] = (".$character[$i+1]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 1;
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 20                             #
          #                                                                               #
          # AFFECTS: VOWEL + /[ʧdfgkp]/ + /ɾ/                                             #
          # CHANGES: Inserts syllable break between VOWEL and /[ʧdfgkp]/                  #
          # EXAMPLE: Should make "Pedro" y "adramático" become /pe.dɾo/, /a.dɾa.../ and   #
          #          NOT /pedɾo/, /adɾa.ma.../.                                           #
          #################################################################################
          elsif (
                  $character[$i] =~ m/[$all_vowels]/
               && $character[ $i + 1 ] =~ m/[ʧdfgkp]/    # NOTE I need a different accent for [mnx] - MAKE NEW RULE MAYBE!
               && $character[ $i + 2 ] eq "ɾ"
            )    # Might be necessary to add a 4th place here - a
                 # trailing vowel - to prevent false positives
          {

               # Add syllable dot
               $character[ $i + 1 ] = (".$character[$i+1]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 2;
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 21                             #
          #                                                                               #
          # AFFECTS: VOWEL  +  /ɾ/  +  /w/                                                #
          # CHANGES: Inserts a syllable break between /ɾ/ and /w/.                        #
          # EXAMPLE: da.ɾwin > daɾ.win                                                    #
          #################################################################################
          elsif ( $character[$i] =~ m/[$all_vowels]/
               && $character[ $i + 1 ] eq "ɾ"
               && $character[ $i + 2 ] eq "w" )
          {

               # Add syllable dot
               $character[ $i + 2 ] = (".$character[$i+2]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 2;
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 22                             #
          #                                                                               #
          # AFFECTS: MISC CONS + VOWEL                                                    #
          # CHANGES: Inserts a syllable break BEFORE CONS.                                #
          # EXAMPLE: to.ma.te, a.mi.go, ca.mi.se.ta                                       #
          #################################################################################
          elsif ( $character[$i] =~ m/[$all_consonants]/
               && $character[ $i + 1 ] =~ m/[$all_vowels]/ )
          {

               # Add syllable dot
               $character[$i] = (".$character[$i]");

               # Increment $i so as not to repeatedly process this cluster
               #$i = $i + 1;
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 23                             #
          #                                                                               #
          # AFFECTS: VOWEL *without* tilde   +   VOWEL *with* tilde                       #
          # CHANGES: Inserts a syllable break between them.                               #
          # EXAMPLE: fre.ír, a.ún, pro.híbe, ba.úl, pe.ón                                 #
          #################################################################################
          elsif ( $character[$i] =~ m/[$unstressed_vowels]/
               && $character[ $i + 1 ] =~ m/[$stressed_vowels]/ )
          {

               # Add syllable dot
               $character[ $i + 1 ] = (".$character[$i+1]");

               # DON'T DO IT! This makes the script miss increíble > Í.BLE!
               # Increment $i so as not to repeatedly process this cluster
               #$i = $i + 1;    # NOTE: DOUBTFUL!!!
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 24                             #
          #                                                                               #
          # AFFECTS: VOWEL *with* tilde   +   VOWEL *without* tilde                       #
          # CHANGES: Inserts a syllable break between them.                               #
          # EXAMPLE: pí.o, trí.o, frí.o                                                   #
          #################################################################################
          elsif ( $character[$i] =~ m/[$stressed_vowels]/
               && $character[ $i + 1 ] =~ m/[$unstressed_vowels]/ )
          {

               # Add syllable dot
               $character[ $i + 1 ] = (".$character[$i+1]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 1;    # NOTE: DOUBTFUL!!!
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 25                             #
          #                                                                               #
          # AFFECTS: STRONG VOWEL  +  STRONG VOWEL                                        #
          # CHANGES: Inserts a syllable break between them.                               #
          # EXAMPLE: pa.ola, co.articular, a.edo, pe.atón                                 #
          #################################################################################
          elsif ( $character[$i] =~ m/[$strong_vowels]/
               && $character[ $i + 1 ] =~ m/[$strong_vowels]/ )
          {

               # Add syllable dot
               $character[ $i + 1 ] = (".$character[$i+1]");
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 25-B                          #
          #                                                                               #
          # AFFECTS: STRONG VOWEL (a/e/o) + WEAK VOWEL (i/u) + ANY VOWEL                #
          # CHANGES: Inserts a syllable break between the strong and weak vowel.          #
          # RATIONALE: A weak vowel followed immediately by another vowel must be         #
          # syllabically nuclear (it cannot be a glide if the next segment is also a     #
          # vowel), so the preceding strong vowel closes its own syllable.               #
          # This fixes rehuir → re.u.íɾ, deshuir → des.u.íɾ, and similar forms          #
          # without disturbing genuine diphthongs like reunión (u followed by consonant).#
          #################################################################################
          elsif ( $character[$i]     =~ m/[$strong_vowels]/
               && $character[$i + 1] =~ m/[$weak_vowels]/
               && $character[$i + 2] =~ m/[$all_vowels]/ )
          {
               # Add syllable dot before the weak vowel.
               # Do NOT increment $i here: the next iteration must land on the
               # (now-dotted) weak vowel so that a further vowel-vowel rule (e.g.
               # Rule 23: unstressed+stressed) can insert the second dot between
               # the weak vowel and the following stressed vowel.
               # Without this, "re.uíɾ" would not get the second split to "re.u.íɾ".
               $character[$i + 1] = (".$character[$i+1]");
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 25-C                          #
          #                                                                               #
          # AFFECTS: [e]  +  [u]  +  CONSONANT                                           #
          # CHANGES: Inserts a syllable break between [e] and [u].                       #
          # RATIONALE: If "u" survived as a vowel (not converted to glide "w") and is    #
          # followed by a consonant, it must be nuclear — i.e. a true hiatus.  Any       #
          # genuine "eu" diphthong would have been converted to "ew" by the diphthong    #
          # rules in process_diphthongs before reaching this point.                      #
          # This handles rehuye → re.u.ʝe and rehuyó → re.u.ˈʝo.                        #
          #################################################################################
          elsif ( $character[$i] eq "e"
               && $character[$i + 1] eq "u"
               && $character[$i + 2] =~ m/[$all_consonants]/ )
          {
               # Add syllable dot before u
               $character[$i + 1] = (".$character[$i+1]");
               # Do not increment $i — the next iteration lands on (dotted) u,
               # allowing further vowel-sequence rules to fire if needed.
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 26                             #
          #                                                                               #
          # AFFECTS: [u]  +  [u]                                                          #
          # CHANGES: Inserts a syllable break between them.                               #
          # EXAMPLE: con.ti.nuum >con.ti.nu.um                                            #
          #################################################################################
          elsif ( $character[$i] eq "u"
               && $character[ $i + 1 ] eq "u" )
          {

               # Add syllable dot
               $character[ $i + 1 ] = (".$character[$i+1]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 1;    # NOTE: DOUBTFUL!!!
          }

          #################################################################################
          #####                    Miscellaneous syllabification                     ######
          #################################################################################

          #################################################################################
          #                           SYLLABIFICATION RULE 27                             #
          #                                                                               #
          # AFFECTS: VOWEL  +  MISC CONS  +  /j,w/                                        #
          # CHANGES: Inserts a syllable break between VOWEL + CONS.                       #
          # EXAMPLE: a.kwerdo (?)                                                         #
          #################################################################################
          elsif ( $character[$i] =~ m/[$all_vowels]/
               && $character[ $i + 1 ] =~ m/[$all_consonants]/
               && $character[ $i + 2 ] =~ m/[jw]/ )
          {

               # Add syllable dot
               $character[ $i + 1 ] = (".$character[$i+1]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 2;
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 28                             #
          #                                                                               #
          # AFFECTS: VOWEL  +  /ɾ/  +  /t/ + /l/                                          #
          # CHANGES: Inserts a syllable break after /t/.                                  #
          # EXAMPLE: por.tland > port.land                                                #
          #################################################################################
          elsif ( $character[$i] =~ m/[$all_vowels]/
               && $character[ $i + 1 ] eq "ɾ"
               && $character[ $i + 2 ] eq "t"
               && $character[ $i + 3 ] eq "l" )
          {

               # Add syllable dot
               $character[ $i + 3 ] = (".$character[$i+3]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 3;
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 29                             #
          #                                                                               #
          # AFFECTS: VOWEL  +  /fgkmptx/  +  /l/                                          #
          # CHANGES: Inserts a syllable break after VOWEL.                                #
          # EXAMPLE: a.tlántico, a.clarar, a.glutina, ba.plazar                           #
          #################################################################################
          elsif ( $character[$i] =~ m/[$all_vowels]/
               && $character[ $i + 1 ] =~ m/[bfgkpt]/
               && $character[ $i + 2 ] eq "l" )
          {

               # Add syllable dot
               $character[ $i + 1 ] = (".$character[$i+1]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 2;
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 30                             #
          #                                                                               #
          # AFFECTS: VOWEL  +  /ʧdmx/  +  /l/                                             #
          # CHANGES: Inserts a syllable break after 1ST CONSONANT.                        #
          # EXAMPLE: po.si.BLe, aD.Látero, aSH.Li, aSH.Ram (aCH.Ram)                      #
          #################################################################################
          elsif ( $character[$i] =~ m/[$all_vowels]/
               && $character[ $i + 1 ] =~ m/[ʧdmx]/
               && $character[ $i + 2 ] =~ m/[l]/ )
          {

               # Add syllable dot
               $character[ $i + 2 ] = (".$character[$i+2]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 2;
          }

          #################################################################################
          #                           SYLLABIFICATION RULE 31                             #
          #                                                                               #
          # AFFECTS: VOWEL  +  /w/  +  /lrɾ/                                              #
          # CHANGES: Inserts a syllable break after /w/                                   #
          # EXAMPLE: ew.ɾo, paw.la                                                        #
          #################################################################################
          elsif ( $character[$i] =~ m/[$all_vowels]/
               && $character[ $i + 1 ] eq "w"
               && $character[ $i + 2 ] =~ m/[lrɾ]/ )
          {

               # Add syllable dot
               $character[ $i + 2 ] = (".$character[$i+2]");

               # Increment $i so as not to repeatedly process this cluster
               $i = $i + 2;
          }

     }

     $current_item = join( "", @character );    # Might have to move this up inside the previous curly brace

     # CLEAN UP DOT ADDED AT BEGINNING OF WORD
     $current_item =~ s/^\.//;

     ############################################################################
     #                                                                          #
     #                         SYSTEMATIC AD HOC FIXES                          #
     #                         (Is that an oxymoron?)                           #
     ############################################################################
     $current_item =~ s/s\.s/\.s/g;             # "es.sen.sja" > "e.sen.sja" (?)

     # TRYING TO FIX IN 390
     $current_item =~ s/ej\.s$/ejs/g;           # "seis": makes it one syllable
     $current_item =~ s/(^|f)ʧ\.n/ʧn/g;         # "Schneider": makes it /ʧnei̯.ˈdeɾ/, NOT /ʧ.nei̯.ˈdeɾ/
                                                #  at BEGINNING OF WORD, or AFTER f ("Reifschneider")
     $current_item =~ s/ɾ\.ts/ɾts/g;            # "Schwartz": rt.s > rts
     $current_item =~ s/ɾ\.tn/ɾt.n/g;           # "partner": par.tner > part.ner
     $current_item =~ s/pan\.ks/panks/g;        # "punks": pan.ks > panks


     ###############################################################################
     #              CLEAN UP WORDS IN THEIR SEMI-PHONEMIC REPRESENTATION           #
     ###############################################################################
     # Here, words are in an intermediate representation:                          #
     #  · syllable division dots ARE present                                       #
     #  · stress accents are represented with TILDES, not apostrophes              #
     #  · multi-character symbols (e.g. "t̪", "t̠͡ʃ") have NOT been inserted.        #
     ###############################################################################
     if ( $preclean_semiphonemic == 1 ) {

          #print STDOUT "\t$current_item";   # AD HOC DEBUG

          #################################################################################
          #                                                                               #
          # FIX SYSTEMATIC SYLLABIFICATION ERRORS                                         #
          #                                                                               #
          # The regular expressions here are semi-phonemic. To change incorrect Perkins   #
          # output to strings that can be processed here, remove the accent apostrophes   #
          # and put a tilde on the corresponding vowel.                                   #
          #                                                                               #
          # Note that many of these rules could probably be made unnecessary by fine-     #
          # tuning the main syllabification routine's regexes.                            #
          #################################################################################

          # [s.t#] > [st#] ("test" = tes.t > test)
          $current_item =~ s/s\.t$/st/g;

          # Remove syllable break before word-final [ts]
          # (ou.ts > outs, ko.pi.raj.ts > ko.pi.rajts)
          $current_item =~ s/\.ts$/ts/g;

          # [.nt#] > [nt#]
          $current_item =~ s/\.nt$/nt/g;

          # Fix single consonant being put in its own syllable at end of word
          # following /j/ (e.g. mei.l > meil, es.kai.p > es.kaip
          $current_item =~ s/j\.($all_consonants)$/j$1/g;

          # Fix syllabification in C + C at end of word (e.g. fór.sep.s > fór.seps,
          # or.g > org)
          $current_item =~ s/$all_consonants\.$all_consonants$/$1$2/g;    # NOTE Added "g" in "/g;"

          # [j.lt] > [jl.t] (e.g. "mailto": mai.ltu > meil.tu)
          $current_item =~ s/j\.lt/jl.t/g;

          # Fix "-áis" and "-éis"
          $current_item =~ s/([áé])\.is$/$1is/g;

          # Fix English-derived affix "-ter(s)"
          $current_item =~ s/é\.i\.teɾ/éj\.teɾ/g;

          # Change n.tx to nt.x (ro.en.txen > ro.ent.xen)
          $current_item =~ s/ro\.én\.txen/ro.ént.xen/g;

          # Change é.i to éj (ne.i.si.ko > nej.si.ko)
          $current_item =~ s/é\.i/éj/g;    # DANGER DANGER DANGER DANGER

          # Change á.i to áj ("iceberg" > a.'is.berg > ajs.berg)
          $current_item =~ s/á\.i/áj/g;    # DANGER DANGER DANGER DANGER

          # Change á.u to áu ("táutoro" > ta.u.to.ro > táu.to.ro
          $current_item =~ s/á\.u/áw/g;    # DANGER DANGER DANGER DANGER

          # w.rs > wr.s
          # ( awt.'sow.r.ses > awt.'sowr.ses )
          $current_item =~ s/w\.ɾ\.s/wɾ\.s/g;

          # Remove syllable break in ɾ.[df]s at end of word
          # skateboard: boɾ.ds > boɾds, sur.fs > surfs
          $current_item =~ s/ɾ\.(d|f)s$/ɾ$1s/g;

          # Remove syllable break in w.ɾs, j.ɾs
          # amateurs: tew.ɾs > tewɾs, hardwares:ˈxaɾ.dwej.ɾs > ˈxaɾ.dwejɾs
          $current_item =~ s/([jw])\.ɾs$/$1ɾs/g;

          # st.j > s.tj
          # (crist.jan > cris.tjan, best.ja > bes.tja
          $current_item =~ s/st\.j/s\.tj/g;

          # j.ds > jds
          # (upgrades = 'ap.grejds, not 'ap.gred.ds
          $current_item =~ s/j\.ds/jds/g;

          ############################################################################
          #                                                                          #
          #       FIX ACCENTUATION IN MISC WORDS (SEMI-PHONEMIC REPRESENTATION)      #
          #                                                                          #
          ############################################################################
          $current_item =~ s/a\.má\.tewɾs/a\.ma\.téwɾs/g;
          $current_item =~ s/a\.nó\.ɾaks/a\.no\.ɾáks/g;
          $current_item =~ s/ajs\.béɾg/ájs\.beɾg/g;
          $current_item =~ s/als\.xé\.i\.meɾ/als.xéj.meɾ/g;
          $current_item =~ s/ang\.sʂóm/ángs.ʂom/g;
          $current_item =~ s/aw\.tós\.tops/áw\.tos.tops/g;
          $current_item =~ s/blógs\.pot/blóg.spot/g;
          $current_item =~ s/bul\.do\.séɾ/búl\.do\.seɾ/g;
          $current_item =~ s/bís\.teks/bis\.téks/g;
          $current_item =~ s/bɾown\.já\.no/bɾow\.njá\.no/g;
          $current_item =~ s/dí\.ʤejs/di\.ʤéjs/g;
          $current_item =~ s/e\.lek\.ʂó.ʧoks/e\.lek\.ʂo.ʧóks/g;
          $current_item =~ s/fa\.jéɾ\.foks/fá\.jeɾ\.foks/g;
          $current_item =~ s/fejs\.búk/féjs.buk/g;
          $current_item =~ s/j\.l\.t/jl\.t/g;            # "mailto": mej.l.tu > mejl.tu
          $current_item =~ s/je\.me\.de\.bé/i.e.me.de.ˈbe/g;
          $current_item =~ s/kɾajg\.slíst/kɾéigs.list/g;
          $current_item =~ s/laj\.bxow\.ɾ\.nál/lajb.ʤóɾ.nal/g;
          $current_item =~ s/laj\.bʤóɾ\.nal/lajb\.ʤóɾ\.nal/g;
          $current_item =~ s/maj\.kɾo\.sóf/máj.kɾo.sof/g;
          $current_item =~ s/maj\.kɾó\.sofs/máj.kɾo.sofs/g;
          $current_item =~ s/májs\.pes/máj.spes/g;
          $current_item =~ s/of\.sét/óf.set/g;
          $current_item =~ s/pej\.pál/péj.pal/g;
          $current_item =~ s/poɾt\.lánd/póɾt.land/g;
          $current_item =~ s/pɾoj\.b/pɾo.i.b/g;        # "prohíben", "prohibímos", etc. (accent NOT on 1st syl)
          $current_item =~ s/pɾój\.ba/pɾo.í.ba/g;      # "prohíban", "prohibamos", etc. (accent on 1st syl)
          $current_item =~ s/pɾój\.be/pɾo.í.be/g;      # "prohíben", "prohibímos", etc. (accent on 1st syl)
          $current_item =~ s/ti\.két/tí.ket/g;
          $current_item =~ s/tung\.sté\.no/tungs.té.no/g;
          $current_item =~ s/we\.ɾe\.é\.le/u.e.re.é.le/g;
          $current_item =~ s/á\.ut\.put/áwt.put/g;
          $current_item =~ s/á\.ut\.soɾ\.sin/áwt.soɾ.sin/g;
          $current_item =~ s/és\.pɾints/es.pɾínts/g;
          $current_item =~ s/és\.tafs/es.táfs/g;
          $current_item =~ s/és\.tans/es.táns/g;
          $current_item =~ s/ʂaj\.pód/ʂáj.pod/g;

          #PLACEHOLDER 3

          if ( $debug_syllab_sub == 1 ) {
               print STDOUT "\tfinal in sub = $current_item";    # DEBUG IF CHOSEN
          }

          return ($current_item);

     }
}    # END OF SYLLABIFICATION SUBROUTINE

#################################################################################
#                                ==OPTIONAL==                                   #
# SUBROUTINE: CONVERT VOWELS WITH TILDES INTO NORMAL VOWELS WITH IPA STRESS     #
#                  SYMBOLS AT THE BEGINNING OF THEIR SYLLABLE                   #
#                             ( es.tá > es.'ta )                                #
#################################################################################
sub opt_tildes_to_ipa_apostrophes {

     my $current_item = $_[0];

     @syllable = split /\./, $current_item;

     $syl_count = @syllable;

     for ( $i = 0 ; $i < $syl_count ; $i++ ) {

          if ( $syllable[$i] =~ m/$stressed_vowels/ ) {
               $syllable[$i] =~ s/á/a/;
               $syllable[$i] =~ s/é/e/;
               $syllable[$i] =~ s/í/i/;
               $syllable[$i] =~ s/ó/o/;
               $syllable[$i] =~ s/ú/u/;
               $syllable[$i] = "ˈ$syllable[$i]";    # This is the IPA stress accent apostrophe
          }
     }
     $current_item = join( ".", @syllable );
     return ($current_item);
}

#################################################################################
# SUBROUTINE: USE MULTI-CHARACTER PHONEME SYMBOLS                               #
#                                                                               #
# Has the option to let $use_one_char_ch_symbol and $use_one_char_ye_symbol     #
# override all other /ʧ/ and /ʤ/-related options (e.g.  ʧ = t̠͡ʃ,   ʤ = d̠͡ʒ)     #
#################################################################################
sub opt_use_multichars {

     my $current_item = $_[0];

     # ADD DENTAL DIACRITICS TO T AND D
     if ( $use_dental_diacr == 1 ) {
          $current_item =~ s/t/t̪/g;
          $current_item =~ s/d/d̪/g;
     }

     # IF TR IS CONSIDERED A CONSONANT GROUP...
     if ( $tr_is_group == 1 ) {

          # CONVERT ʂ TO t̪͡ɾ (WITH DENTAL DIACRITIC AND TIEBAR)
          if ( $use_dental_diacr == 1 ) {
               $current_item =~ s/ʂ/t̪͡ɾ/g;
          }
          else {
               # CONVERT ʂ TO t͡ɾ (NO DENTAL DIACRITIC AND TIEBAR)
               $current_item =~ s/ʂ/t͡ɾ/g;
          }
     }
     # IF TR IS *NOT* CONSIDERED A CONSONANT GROUP...
     elsif ( $tr_is_group == 0 ) {

          # CONVERT ʂ TO t̪ɾ (DENTAL DIACRITIC, NO TIEBAR)
          if ( $use_dental_diacr == 1 ) {
               $current_item =~ s/ʂ/t̪ɾ/g;
          }
          # CONVERT ʂ TO tɾ (NO DENTAL DIACRITIC, NO TIEBAR)
          else {
               $current_item =~ s/ʂ/tɾ/g;
          }
     }

     # PROCESS /ʧ/ ----------

     # CONVERT ʧ TO t̠͡ʃ (RETRACTED + LIGATURE)
     if ( $use_one_char_ch_symbol == 0 ) {
          if ( $ch_dzh_retracted == 1 ) {
               if ( $use_ligatures == 1 ) {
                    $current_item =~ s/ʧ/t̠͡ʃ/g;
               }
               # CONVERT ʧ TO t̠ʃ (RETRACTED + NO LIGATURE)
               else {
                    $current_item =~ s/ʧ/t̠ʃ/g;
               }
          }
          else {
               # CONVERT ʧ TO t͡ʃ (NO RETRACTED + LIGATURE)
               if ( $use_ligatures == 1 ) {
                    $current_item =~ s/ʧ/t͡ʃ/g;
               }
               else {
                    # CONVERT ʧ TO tʃ (NO RETRACTED, NO LIGATURE)
                    $current_item =~ s/ʧ/tʃ/g;
               }
          }
     }

     # PROCESS /ʤ/
     if ( $ch_dzh_retracted == 1 && $use_one_char_ye_symbol == 0 ) {
          if ( $use_ligatures == 1 ) {
               $current_item =~ s/ʤ/d̠͡ʒ/g;
               }
          else {
               if ( $use_one_char_ye_symbol == 0 ) {
               $current_item =~ s/ʤ/d̠ʒ/g;
               }
          }
     }
     else {
          if ( $use_ligatures == 1 && $use_one_char_ye_symbol == 0 ) {
               $current_item =~ s/ʤ/d͡ʒ/g;
          }
          else {
               if ( $use_one_char_ye_symbol == 0 ) {
               $current_item =~ s/ʤ/dʒ/g;
               }
          }
     }

return ($current_item);

}

############################################################################
# SUBROUTINE: OPTIONAL PHONEME TRANSFORMATIONS                             #
############################################################################
sub opt_phoneme_transforms {

     my $current_item = $_[0];

     ############################################################################
     # CHANGE /ʂ/ TO /tɾ/ IF NO MULTI-CHARS AND TR IS NOT GROUP                 #
     # NOTE NEW IN 351                                                          #
     ############################################################################
     if ( ( $multichars == 0 ) && ( $tr_is_group == 0 ) ) {
          $current_item =~ s/ʂ/tɾ/g;
     }

     ############################################################################
     # REPRESENT NON-SYLLABIC U AS VOWEL+DIACRITIC /u̯/, NOT /w/                #
     ############################################################################
     if ( $non_syl_u_with_u_diacr == 1 ) {
          $current_item =~ s/($all_vowels)w/$1u̯/g;
          $current_item =~ s/w($all_vowels)/u̯$1/g;
     }

     ############################################################################
     # REPRESENT NON-SYLLABIC I AS VOWEL+DIACRITIC /i̯/, NOT /j/                #
     ############################################################################
     if ( $non_syl_i_with_i_diacr == 1 ) {
          $current_item =~ s/j($all_vowels)/i̯$1/g;
          $current_item =~ s/($all_vowels)j/$1i̯/g;
     }

     return ($current_item);

}

############################################################################
# SUBROUTINE: IF SELECTED, CONVERT PHONEMES TO C OR V                      #
############################################################################
sub opt_convert_phonemes_cv {

     my $current_item = $_[0];

     if ( $tr_is_group == 0 ) {
          $current_item =~ s/$tr/CC/g;
     }
     else {
          $current_item =~ s/$tr/C/g;
     }

     $current_item =~ s/$vowels_glides/V/g;
     $current_item =~ s/$all_consonants/C/g;
     $current_item =~ s/̪̠͡//g;
     return ($current_item);

}

############################################################################
# SUBROUTINE: IF SELECTED, CONVERT PHONEMES TO C, V OR G                   #
############################################################################
sub opt_convert_phonemes_cvg {

     my $current_item = $_[0];

     if ( $tr_is_group == 0 ) {
          $current_item =~ s/$tr/CC/g;
     }
     else {
          $current_item =~ s/$tr/C/g;
     }

     $current_item =~ s/$glides/G/g;
     $current_item =~ s/$all_vowels/V/g;
     $current_item =~ s/$all_consonants/C/g;
     $current_item =~ s/̪̠͡//g;

     return ($current_item);

}

############################################################################
# SUBROUTINE: IF SELECTED, CONVERT PHONEMES TO C, V, N, L, R OR G          #
############################################################################
sub opt_convert_phonemes_cvnlrg {

     my $current_item = $_[0];

     if ( $tr_is_group == 0 ) {
          $current_item =~ s/$tr/CR/g;
     }
     else {
          $current_item =~ s/$tr/C/g;
     }

     $current_item =~ s/$glides/G/g;
     $current_item =~ s/$nasals/N/g;
     $current_item =~ s/$liquids/L/g;
     $current_item =~ s/$rhotics/R/g;
     $current_item =~ s/$all_vowels/V/g;
     $current_item =~ s/$all_consonants/C/g;
     $current_item =~ s/̪̠͡//g;
     return ($current_item);

}

############################################################################
# SUBROUTINE: IF SELECTED, CONVERT PHONEMES TO MANNERS OF ARTICULATION     #
############################################################################
sub opt_convert_phonemes_manners {

     my $current_item = $_[0];

     if ( $tr_is_group == 0 ) {
          $current_item =~ s/$tr/PT/g;
     }
     else {
          $current_item =~ s/$tr/A/g;
     }

     $current_item =~ s/$plosives/P/g;
     $current_item =~ s/$nasals/N/g;
     $current_item =~ s/$trills/R/g;
     $current_item =~ s/$taps/T/g;
     $current_item =~ s/$fricatives/F/g;
     $current_item =~ s/$laterals/L/g;
     $current_item =~ s/$affricates/A/g;
     $current_item =~ s/$aproximants/X/g;
     $current_item =~ s/$all_vowels/V/g;
     $current_item =~ s/̪̠͡//g;
     return ($current_item);
}

############################################################################
# SUBROUTINE: IF SELECTED, CONVERT PHONEMES TO PLACES OF ARTICULATION      #
############################################################################
sub opt_convert_phonemes_places {

     my $current_item = $_[0];

     if ( $tr_is_group == 0 ) {
          $current_item =~ s/$tr/DA/g;
     }
     else {
          $current_item =~ s/$tr/A/g;
     }

     $current_item =~ s/$bilabials/B/g;
     $current_item =~ s/$labiodentals/L/g;
     $current_item =~ s/$dentals/D/g;
     $current_item =~ s/$alveolars/A/g;
     $current_item =~ s/$postalveolars/T/g;
     $current_item =~ s/$palatals/P/g;
     $current_item =~ s/$velars/V/g;
     $current_item =~ s/$labiovelars/W/g;
     $current_item =~ s/$all_vowels/V/g;
     $current_item =~ s/̪̠͡//g;

     return ($current_item);
}

############################################################################
# SUBROUTINE IF SELECTED, CONVERT PHONEMES TO VOICING                      #
############################################################################
sub opt_convert_phonemes_voicing {

     my $current_item = $_[0];

     if ( $tr_is_group == 0 ) {
          $current_item =~ s/$tr/AS/g;
     }
     else {
          $current_item =~ s/$tr/A/g;
     }

     $current_item =~ s/$voiced/S/g;
     $current_item =~ s/$unvoiced/A/g;
     $current_item =~ s/̪̠͡//g;
     return ($current_item);

}

############################################################################
# SUBROUTINE: IMPLEMENT PUNCTUATION > PAUSES (IPA PAUSE SYMBOLS)           #
############################################################################
sub punct_to_pauses_ipa {

     my $current_item = $_[0];

     # Insert INITIAL BRACKET pause (.) if appropriate

     if ( $add_bracket_pauses == 1 && $initial_bracket_pause == 1 ) {
          $current_item          = "| $current_item";
          $initial_bracket_pause = 0;
     }

     # Insert FINAL BRACKET pause (.) if appropriate

     if ( $add_bracket_pauses == 1 && $final_bracket_pause == 1 ) {
          $current_item        = "$current_item |";
          $final_bracket_pause = 0;
     }

     # Insert INITIAL PAREN pause (.) if appropriate

     if ( $add_paren_pauses == 1 && $initial_paren_pause == 1 ) {
          $current_item        = "| $current_item";
          $initial_paren_pause = 0;
     }

     # Insert FINAL PAREN pause (.) if appropriate

     if ( $add_paren_pauses == 1 && $final_paren_pause == 1 ) {
          $current_item      = "$current_item |";
          $final_paren_pause = 0;
     }

     # Insert ELLIPSIS pause if appropriate
     if ( $add_ellipsis_pauses == 1 && $ellipsis_pause == 1 ) {
          $current_item   = "$current_item ‖";
          $ellipsis_pause = 0;
     }

     # Insert SENTENCE end break if appropriate
     if ( $add_sentence_breaks == 1 && $sentence_break == 1 ) {
          $current_item   = "$current_item ‖";
          $sentence_break = 0;
     }

     # Insert COMMA pause (.) if appropriate
     if ( $add_comma_pauses == 1 && $comma_pause == 1 ) {
          $current_item = "$current_item |";
          $comma_pause  = 0;
     }

     # Insert SEMICOLON pause (...) if appropriate
     if ( $add_semicolon_pauses == 1 && $semicolon_pause == 1 ) {
          $current_item    = "$current_item ‖";
          $semicolon_pause = 0;
     }

     # Insert COLON pause (..) if appropriate
     if ( $add_colon_pauses == 1 && $colon_pause == 1 ) {
          $current_item = "$current_item |";
          $colon_pause  = 0;
     }

     return ($current_item);

}

############################################################################
# SUBROUTINE: IMPLEMENT PUNCTUATION > PAUSES (NON-IPA PAUSE SYMBOLS)       #
############################################################################
sub punct_to_pauses_non_ipa {

     my $current_item = $_[0];

     # Insert INITIAL BRACKET pause (.) if appropriate

     if ( $add_bracket_pauses == 1 && $initial_bracket_pause == 1 ) {
          $current_item          = "\(\.\) $current_item";
          $initial_bracket_pause = 0;
     }

     # Insert FINAL BRACKET pause (.) if appropriate

     if ( $add_bracket_pauses == 1 && $final_bracket_pause == 1 ) {
          $current_item        = "$current_item \(\.\)";
          $final_bracket_pause = 0;
     }

     # Insert  PAREN pause (.) if appropriate

     if ( $add_paren_pauses == 1 && $initial_paren_pause == 1 ) {
          $current_item        = "\(\.\) $current_item";
          $initial_paren_pause = 0;
     }

     # Insert FINAL PAREN pause (.) if appropriate

     if ( $add_paren_pauses == 1 && $final_paren_pause == 1 ) {
          $current_item      = "$current_item \(\.\)";
          $final_paren_pause = 0;
     }

     # Insert ellipsis pause if appropriate
     if ( $add_ellipsis_pauses == 1 && $ellipsis_pause == 1 ) {
          $current_item   = "$current_item \(\.\.\.\)";
          $ellipsis_pause = 0;
     }

     # Insert sentence end break if appropriate
     if ( $add_sentence_breaks == 1 && $sentence_break == 1 ) {
          $current_item   = "$current_item ##";
          $sentence_break = 0;
     }

     # Insert comma pause (.) if appropriate
     if ( $add_comma_pauses == 1 && $comma_pause == 1 ) {
          $current_item = "$current_item \(\.\)";
          $comma_pause  = 0;
     }

     # Insert semicolon pause (...) if appropriate
     if ( $add_semicolon_pauses == 1 && $semicolon_pause == 1 ) {
          $current_item    = "$current_item \(\.\.\.\)";
          $semicolon_pause = 0;
     }

     # Insert colon pause (..) if appropriate
     if ( $add_colon_pauses == 1 && $colon_pause == 1 ) {
          $current_item = "$current_item \(\.\.\)";
          $colon_pause  = 0;
     }

     return ($current_item);

}

############################################################################
# SUBROUTINE: SPLIT AT SYLLABLES: PUT A NEWLINE AT EACH SYLLABLE BOUNDARY  #
#             IF DESIRED                                                   #
############################################################################
sub opt_split_at_syllables {

     my $current_item = $_[0];

     # NEW in 373: Changed \n to \r\n in replace expression. Otherwise, the     #
     #             newline gets eaten (possibly by the clean-up routine)        #
     $current_item =~ s/\./\r\n/g;
     $current_item =~ s/ /\r\n/g;

     $current_item =~ s/#//g;
     $current_item =~ s/\(//g;
     $current_item =~ s/\)//g;

     # Remove double newlines
     $current_item =~ s/\n\n/\n/g;

     return ($current_item);

}

############################################################################
#                                                                          #
# SUBROUTINE: REPLACE .VRT PLACEHOLDERS                                    #
#                                                                          #
############################################################################
sub replace_vrt_placeholders {

     my $current_item = $_[0];

     $temp = Encode::encode_utf8($current_item);    # AD-HOC DEBUG
                                                    #print STDOUT "VRT-ELIMIN-PLCHLD-BEF:$current_item:\n";    # AD-HOC DEBUG

     # Placeholders to replace with other things
     $current_item =~ s/þ//g;                      # Period
     $current_item =~ s/¢//g;                      # Comma
     $current_item =~ s/ĸ//g;                      # Colon
     $current_item =~ s/ð//g;                      # Dash
     $current_item =~ s/ł//g;                      # Quotation mark
     $current_item =~ s/ŋ//g;                      # Opening question mark
     $current_item =~ s/µµ//g;                    # Paragraph marker
     $current_item =~ s/µ//g;                      # Sentence marker
     $current_item =~ s/ø/ /g;
     $current_item =~ s/½/ /g;

     # Placeholders to kill #NEW IN 402
     $current_item =~ s/ø$//g;
     $current_item =~ s/^ø//g;

     # Kill leading or trailing spaces that may crop up
     $current_item =~ s/^ //g;
     $current_item =~ s/ $//g;

     $temp = Encode::encode_utf8($current_item);    # AD-HOC DEBUG
                                                    #print STDOUT "VRT-ELIMIN-PLCHLD-AFT:$current_item:\n";    # AD-HOC DEBUG

     return ($current_item);
}    # END OF SUBROUTINE: REPLACE .VRT PLACEHOLDERS

#################################################################################
#                        SUBROUTINE: Clean up pauses                            #
#################################################################################
sub clean_up_pauses {

     my $current_item = $_[0];

     # Merge a period break followed by a paragraph break.                 #
     $current_item =~ s/## ###/###/g;

     # Merge a final bracket or comma break followed by a paragraph break. #
     $current_item =~ s/\(\.\) ###/###/g;

     # Merge a final bracket or comma break followed by a paragraph break. #
     $current_item =~ s/###$/\n/g;

     # Merge an IPA MINOR group and MAJOR group when the occur together,   #
     # leaving only the major group symbol.                                #
     $current_item =~ s/\| ‖/‖/g;

     # Merge an IPA MAJOR group and MINOR group when the occur together,   #
     # leaving only the major group symbol.                                #
     $current_item =~ s/‖ \|/‖/g;

     # Merge two IPA minor groups when the occur together.                 #
     $current_item =~ s/\| \|/\|/g;

     # Eliminate IPA major group symbol at end of line                     #
     $current_item =~ s/ ‖ $//g;

     # Eliminate IPA minor group symbol at end of line                     #
     $current_item =~ s/ \| $//g;

     # Eliminate whitespace at END of line.                                #
     # Note that at least one whitespace character must remain, or else    #
     # all lines are merged into one.                                      #
     $current_item =~ s/\s(\s)+$/$1/g;

     # Eliminate SPACES (but NOT other whitespace) at BEGINNING of line    #
     $current_item =~ s/^( )+//g;

     # print "\n>>>$input_line<<<";     # DEBUG


     # Replace IPA long pause double bar with two IPA short pause single bars
     # NEW in 0456
     if ( $ipa_long_pause_two_singles == 1 ) {
          $current_item =~ s/‖/||/g;
     }

     return ($current_item);

}    # END SUB: CLEAN UP PAUSES

#################################################################################
#    SUBROUTINE: PERFORM WORD-LEVEL SYLLABIFICATION ADJUSTMENTS, IF DESIRED     #
#################################################################################
sub word_level_syllab_adjust {

     my $current_item = $_[0];

     $current_item =~ s/($all_consonants_diacr){1,1}( )(ˈ|'|)($all_vowels_diacr)/.$4$1$5/g;

     # Convert spaces to syllable dots                                #

     # NOTE: NEW IN 379 [DISABLED FOR TESTING]
     #$current_item =~ s/([^ˈ'|‖]) ([^|‖])/$1.$2/g;

     # NOTE: NEW IN 380 [TESTING] ñññññ
     $current_item =~ s/([^‖|]) ([^|‖])/$1.$2/g;
     $current_item =~ s/([^‖|]) ([^|‖])/$1.$2/g;

     #print STDOUT "\t > $input_line"; # AD HOC DEBUG

     return ($current_item);
}    # END SUBROUTINE: PERFORM WORD-LEVEL SYLLABIFICATION ADJUSTMENTS

###############################################################################
#                     SUBROUTINE: PRINT INFO HEADER                           #
###############################################################################
sub print_info_header {

     binmode STDOUT, ":utf8";

     if ( $lang eq "es" ) {

          print STDOUT "\n+-----------------------------------------------------------------------+";
          print STDOUT "\n|                          Perkins v$version                               |";
          print STDOUT "\n|             Tu suche para el trabajo sucio de la fonética.            |";
          print STDOUT "\n|                   Copyright (c) 2026 Scott Sadowsky                   |";
          print STDOUT "\n|          https://sadowsky.cl - ssadowsky arro ba gma il pu nto com    |";
          print STDOUT "\n|                                                                       |";
          print STDOUT "\n| Este programa se distribuye bajo la licencia GNU AGPL v3. Se          |";
          print STDOUT "\n| distribuye SIN GARANTÍA ALGUNA. Véase LICENSE.txt para más detalles.  |";
          print STDOUT "\n|    El código fuente se encuentra en el archivo perkins-n.n.n.pl       |";
          print STDOUT "\n|                                                                       |";
          print STDOUT "\n|-----------------------------------------------------------------------|";
          print STDOUT "\n| PARA EJECUTARLO: $progname [OPCIONES] -i=input.txt [-o=output.txt]   |";
          print STDOUT "\n| AYUDA:           $progname -h                                        |";
          print STDOUT "\n| INFO SOBRE USO:  $progname -u                                        |";
          print STDOUT "\n| USE IN ENGLISH:  $progname -en -i=input.txt [OPCIONES]               |";
          print STDOUT "\n|                                                                       |";
          print STDOUT "\n| (Si usas el binario de GNU/Linux, elimina la extensión \'.pl\').        |";
          print STDOUT "\n|                                                                       |";
          print STDOUT "\n| - El archivo a procesar DEBE ser texto plano codificado en UTF-8.     |";
          print STDOUT "\n| - El archivo de salida será texto en UTF-8 (Unicode).                 |";
          print STDOUT "\n| - Se puede configurar Perkins mediante las opciones de la línea de    |";
          print STDOUT "\n|   comandos, o bien editando el archivo de configuración (perkins.ini).|";
     }
     else {

          print STDOUT "\n+-----------------------------------------------------------------------+";
          print STDOUT "\n|                           Perkins v$version                              |";
          print STDOUT "\n|                       The Phonetician's Assistant.                    |";
          print STDOUT "\n|                   Copyright (c) 2026 Scott Sadowsky                   |";
          print STDOUT "\n|          https://sadowsky.cl - ssadowsky a t gma il d ot com          |";
          print STDOUT "\n|                                                                       |";
          print STDOUT "\n| This program is free software distributed under the GNU AGPL v3. It   |";
          print STDOUT "\n| comes with ABSOLUTELY NO WARRANTY. See LICENSE.txt for details.       |";
          print STDOUT "\n|            The source code is in the file perkins-n.n.n.pl            |";
          print STDOUT "\n|                                                                       |";
          print STDOUT "\n|-----------------------------------------------------------------------|";
          print STDOUT "\n| TO RUN:          $progname [OPTIONS] -i=input.txt [-o=output.txt]    |";
          print STDOUT "\n| FOR HELP:        $progname -h                                        |";
          print STDOUT "\n| USAGE INFO:      $progname -u                                        |";
          print STDOUT "\n| USAR EN ESPAÑOL: $progname -es -i=input.txt [OPTIONS]                |";
          print STDOUT "\n|                                                                       |";
          print STDOUT "\n| (If you're using the GNU/Linux binary, remove the \'.pl\' extension)    |";
          print STDOUT "\n|                                                                       |";
          print STDOUT "\n| - The file to be processed MUST be plain text encoded UTF-8 (Unicode).|";
          print STDOUT "\n| - The output file will be encoded in UTF-8 (Unicode).                 |";
          print STDOUT "\n| - Perkins can be configured with command-line options, or by editing  |";
          print STDOUT "\n|   its configuration file (perkins.ini).                               |";

     }
     if ( $^O eq "MSWin32" ) {

          if ( $lang eq "es" ) {
               print STDOUT "\n|                                                                       |";
               print STDOUT "\n| NOTA: Debido a que el terminal de Windows (cmd.exe) no es capaz de    |";
               print STDOUT "\n|       mostrar caracteres Unicode, la ayuda y la información sobre el  |";
               print STDOUT "\n|       uso no se muestran correctamente. Sin embargo, LOS ARCHIVOS DE  |";
               print STDOUT "\n|       QUE PRODUCE PERKINS *NO* SE VEN AFECTADOS POR ESTA SITUACIÓN.   |";
          }
          else {

               print STDOUT "\n|                                                                       |";
               print STDOUT "\n| NOTE: Because the Windows command line (cmd.exe) cannot correctly     |";
               print STDOUT "\n|       show Unicode characters, help and usage information will not    |";
               print STDOUT "\n|       be shown correctly here. However, PERKINS' OUTPUT IS IN NO WAY  |";
               print STDOUT "\n|       AFFECTED BY THIS.                                               |";

          }
     }
     print STDOUT "\n+-----------------------------------------------------------------------+\n";
     print STDOUT "\n";
}

#################################################################################
# (assign_lang_str removed -- see note near its former call site, ~line 627.    #
# It was an empty stub that did nothing.)                                       #
#################################################################################

#################################################################################
#                                                                               #
#                                  TODO LIST                                    #
#                                                                               #
# TODO: Seems all the cover term transformations (>CV, CVG, etc.) are not       #
#       affected by the option to syllabify at sentence level!! Thus, while     #
#       "los hombres" is phonemically transcribed as "lo.som.bres", the same    #
#       phrase as CV is incorrectly given as "CVC.VC.CCVC"!!!.   MUST FIX!!!    #
#                                                                               #
# TODO: Add the ability to chunk utterances (thus producing one utterance per   #
#       line). The easiest way to go about this may be to open the input file,  #
#       chunk it, close it, and then sic Perkins on it as before. For           #
#       CC-Unified IMS-based corpus.                                            #
#                                                                               #
# TODO: Give Perkins the ability to perform multiple types (formats) of         #
#       transcriptions in a single pass. For purposes of the CC-Unified IMS-    #
#       based corpus, the first implementation should tack each successive      #
#       transcription format on to the end of the previous one, using \t as a   #
#       separator. In the future, multiple output files may also be an option.  #
#                                                                               #
# TODO: Review and update usage section examples. Change most of them to        #
#       phrase-level transcriptions, rather than word-level ones.               #
#                                                                               #
# TODO: Figure out why "tr" is transcribed WITHOUT the dental diacritic. Was    #
#       this intentional?                                                       #
#       0433: I think I fixed this.                                             #
#################################################################################
