From 2edce9207e8e56612a310a0b8f56d26a4c3a8814 Mon Sep 17 00:00:00 2001 From: shreeshd-tn Date: Tue, 24 Mar 2026 00:35:43 +0530 Subject: [PATCH 1/2] Electronic: ready for PR Signed-off-by: shreeshd-tn --- .../hi/data/electronic/__init__.py | 14 ++ .../hi/data/electronic/common_words.tsv | 163 ++++++++++++ .../hi/data/electronic/domain.tsv | 39 +++ .../hi/data/electronic/file_extensions.tsv | 51 ++++ .../hi/data/electronic/govt_edu_suffixes.tsv | 7 + .../hi/data/electronic/latin_to_hindi.tsv | 52 ++++ .../hi/data/electronic/server_name.tsv | 64 +++++ .../hi/data/electronic/subscript_digit.tsv | 11 + .../hi/data/electronic/symbols.tsv | 33 +++ .../hi/taggers/electronic.py | 234 ++++++++++++++++++ .../hi/taggers/tokenize_and_classify.py | 5 + .../hi/verbalizers/electronic.py | 214 ++++++++++++++++ .../hi/verbalizers/verbalize.py | 5 + .../test_cases_electronic.txt | 54 ++++ .../hi/test_electronic.py | 34 +++ tools/text_processing_deployment/Dockerfile | 2 +- 16 files changed, 981 insertions(+), 1 deletion(-) create mode 100644 nemo_text_processing/text_normalization/hi/data/electronic/__init__.py create mode 100644 nemo_text_processing/text_normalization/hi/data/electronic/common_words.tsv create mode 100644 nemo_text_processing/text_normalization/hi/data/electronic/domain.tsv create mode 100644 nemo_text_processing/text_normalization/hi/data/electronic/file_extensions.tsv create mode 100644 nemo_text_processing/text_normalization/hi/data/electronic/govt_edu_suffixes.tsv create mode 100644 nemo_text_processing/text_normalization/hi/data/electronic/latin_to_hindi.tsv create mode 100644 nemo_text_processing/text_normalization/hi/data/electronic/server_name.tsv create mode 100644 nemo_text_processing/text_normalization/hi/data/electronic/subscript_digit.tsv create mode 100644 nemo_text_processing/text_normalization/hi/data/electronic/symbols.tsv create mode 100644 nemo_text_processing/text_normalization/hi/taggers/electronic.py create mode 100644 nemo_text_processing/text_normalization/hi/verbalizers/electronic.py create mode 100644 tests/nemo_text_processing/hi/data_text_normalization/test_cases_electronic.txt create mode 100644 tests/nemo_text_processing/hi/test_electronic.py diff --git a/nemo_text_processing/text_normalization/hi/data/electronic/__init__.py b/nemo_text_processing/text_normalization/hi/data/electronic/__init__.py new file mode 100644 index 000000000..f6e3c3795 --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/data/electronic/__init__.py @@ -0,0 +1,14 @@ +# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/nemo_text_processing/text_normalization/hi/data/electronic/common_words.tsv b/nemo_text_processing/text_normalization/hi/data/electronic/common_words.tsv new file mode 100644 index 000000000..3231209a2 --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/data/electronic/common_words.tsv @@ -0,0 +1,163 @@ +about अबाउट +blog ब्लॉग +home होम +index इंडेक्स +login लॉगिन +register रजिस्टर +search सर्च +tags टैग्स +category केटेगरी +categories केटेगरीज़ +post पोस्ट +posts पोस्ट्स +page पेज +pages पेजेस +user यूज़र +users यूज़र्स +Users यूज़र्स +User यूज़र +Desktop डेस्कटॉप +Documents डॉक्युमेंट्स +Downloads डाउनलोड्स +Music म्यूज़िक +Pictures पिक्चर्स +Videos वीडियोज़ +admin एडमिन +app ऐप +faq एफ ए क्यू +help हेल्प +terms टर्म्स +privacy प्राइवेसी +contact कॉन्टैक्ट +main मेन +explore एक्सप्लोर +wiki विकी +docs डॉक्स +download डाउनलोड +downloads डाउनलोड्स +upload अपलोड +uploads अपलोड्स +photos फ़ोटोज़ +images इमेजेज़ +music म्यूज़िक +video वीडियो +videos वीडियोज़ +desktop डेस्कटॉप +documents डॉक्युमेंट्स +master मास्टर +blob ब्लॉब +tree ट्री +tests टेस्ट्स +test टेस्ट +config कॉन्फ़िग +settings सेटिंग्स +profile प्रोफ़ाइल +account अकाउंट +web वेब +email ई मेल +laptop लैपटॉप +mobile मोबाइल +phone फोन +phones फोन्स +online ऑनलाइन +courses कोर्सेज़ +learn लर्न +learning लर्निंग +university यूनिवर्सिटी +academy अकेडमी +domain डोमेन +domains डोमेन्स +analysis अनैलिसिस +play प्ले +maps मैप्स +drive ड्राइव +cloud क्लाउड +services सर्विसेज़ +india इंडिया +screenshot स्क्रीनशॉट +zoom ज़ूम +audacity ऑडेसिटी +coursera कोर्सेरा +apache अपाची +kernel कर्नल +bin बिन +var वार +home होम +activate एक्टिवेट +sites साइट्स +available अवेलेबल +enabled इनेबल्ड +backups बैकअप्स +temp टेम्प +secure सेक्योर +real रियल +data डेटा +work वर्क +survey सर्वे +files फाइल्स +file फ़ाइल +chapter चैप्टर +python पाइथन +audio ऑडियो +sample सैंपल +templates टेम्पलेट्स +impress इंप्रेस +office ऑफिस +libreoffice लिब्रे ऑफिस +express एक्सप्रेस +scribe स्क्राइब +transcription ट्रांसक्रिप्शन +software सॉफ्टवेयर +teams टीम्स +school स्कूल +space स्पेस +green ग्रीन +brown ब्राउन +white व्हाइट +black ब्लैक +homepage होमपेज +content कंटेन्ट +default डिफ़ॉल्ट +foodhealth फूड हेल्थ +workflows वर्कफ्लोज़ +world वर्ल्ड +list लिस्ट +and एंड +LICENSE लाइसेंस +license लाइसेंस +tag टैग +blogs ब्लॉग्स +bath बाथ +ward वार्ड +banks बैंक्स +Audio ऑडियो +dean डीन +rice राइस +honda होंडा +ford फोर्ड +house हाउस +bharat भरत +rich रिच +cook कुक +lane लेन +knight नाइट +moody मूडी +wise वाइज़ +shields शील्ड्स +puppy पप्पी +recipe रेसिपी +hall हॉल +mason मेसन +king किंग +fry फ्राई +flowers फ्लावर्स +assam आसाम +grace ग्रेस +bishop बिशप +woods वुड्स +brewer ब्रूअर +cannon कैनन +saute सौटे +pope पोप +robin रॉबिन +price प्राइस diff --git a/nemo_text_processing/text_normalization/hi/data/electronic/domain.tsv b/nemo_text_processing/text_normalization/hi/data/electronic/domain.tsv new file mode 100644 index 000000000..0689a1295 --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/data/electronic/domain.tsv @@ -0,0 +1,39 @@ +com कॉम +org ऑर्ग +net नेट +edu ई डी यू +gov जी ओ वी +biz बिज़ +info इन्फो +in इन +co सी ओ +io आई ओ +ai ए आई +uk यू के +us यू एस +ru आर यू +de डी ई +fr एफ आर +jp जे पी +cn सी एन +au ए यू +ca सी ए +br बी आर +ac ए सी +res आर ई एस +nic एन आई सी +ernet ई आर नेट +mil एम आई एल +int आई एन टी +tv टी वी +me एम ई +tech टेक +dev डी ई वी +app ऐप +xyz एक्स वाई ज़ेड +online ऑनलाइन +store स्टोर +blog ब्लॉग +site साइट +pro प्रो + diff --git a/nemo_text_processing/text_normalization/hi/data/electronic/file_extensions.tsv b/nemo_text_processing/text_normalization/hi/data/electronic/file_extensions.tsv new file mode 100644 index 000000000..badc638fc --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/data/electronic/file_extensions.tsv @@ -0,0 +1,51 @@ +.jpg डॉट जे पी जी +.jpeg डॉट जे पी ई जी +.png डॉट पी एन जी +.gif डॉट जी आई एफ +.pdf डॉट पी डी एफ +.doc डॉट डी ओ सी +.docx डॉट डी ओ सी एक्स +.xls डॉट एक्स एल एस +.xlsx डॉट एक्स एल एस एक्स +.ppt डॉट पी पी टी +.pptx डॉट पी पी टी एक्स +.csv डॉट सी एस वी +.txt डॉट टी एक्स टी +.html डॉट एच टी एम एल +.htm डॉट एच टी एम +.xml डॉट एक्स एम एल +.json डॉट जे एस ओ एन +.css डॉट सी एस एस +.js डॉट जे एस +.py डॉट पी वाई +.java डॉट जावा +.cpp डॉट सी पी पी +.zip डॉट ज़िप +.rar डॉट आर ए आर +.tar डॉट टी ए आर +.mp3 डॉट एम पी तीन +.mp4 डॉट एम पी चार +.avi डॉट ए वी आई +.mkv डॉट एम के वी +.mov डॉट एम ओ वी +.wav डॉट डब्ल्यू ए वी +.svg डॉट एस वी जी +.ico डॉट आई सी ओ +.apk डॉट ए पी के +.exe डॉट ई एक्स ई +.dmg डॉट डी एम जी +.iso डॉट आई एस ओ +.sql डॉट एस क्यू एल +.log डॉट एल ओ जी +.bak डॉट बी ए के +.JPG डॉट जे पी जी +.JPEG डॉट जे पी ई जी +.PNG डॉट पी एन जी +.PDF डॉट पी डी एफ +.DOC डॉट डी ओ सी +.DOCX डॉट डी ओ सी एक्स +.CSV डॉट सी एस वी +.TXT डॉट टी एक्स टी +.HTML डॉट एच टी एम एल +.MP3 डॉट एम पी तीन +.MP4 डॉट एम पी चार diff --git a/nemo_text_processing/text_normalization/hi/data/electronic/govt_edu_suffixes.tsv b/nemo_text_processing/text_normalization/hi/data/electronic/govt_edu_suffixes.tsv new file mode 100644 index 000000000..fd17513d5 --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/data/electronic/govt_edu_suffixes.tsv @@ -0,0 +1,7 @@ +.nic.in डॉट एन आई सी डॉट इन +.gov.in डॉट जी ओ वी डॉट इन +.ac.in डॉट ए सी डॉट इन +.org.in डॉट ऑर्ग डॉट इन +.edu.in डॉट ई डी यू डॉट इन +.res.in डॉट आर ई एस डॉट इन +.ernet.in डॉट ई आर नेट डॉट इन diff --git a/nemo_text_processing/text_normalization/hi/data/electronic/latin_to_hindi.tsv b/nemo_text_processing/text_normalization/hi/data/electronic/latin_to_hindi.tsv new file mode 100644 index 000000000..4ad6b3308 --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/data/electronic/latin_to_hindi.tsv @@ -0,0 +1,52 @@ +a ए +b बी +c सी +d डी +e ई +f एफ +g जी +h एच +i आई +j जे +k के +l एल +m एम +n एन +o ओ +p पी +q क्यू +r आर +s एस +t टी +u यू +v वी +w डब्ल्यू +x एक्स +y वाई +z ज़ेड +A ए +B बी +C सी +D डी +E ई +F एफ +G जी +H एच +I आई +J जे +K के +L एल +M एम +N एन +O ओ +P पी +Q क्यू +R आर +S एस +T टी +U यू +V वी +W डब्ल्यू +X एक्स +Y वाई +Z ज़ेड diff --git a/nemo_text_processing/text_normalization/hi/data/electronic/server_name.tsv b/nemo_text_processing/text_normalization/hi/data/electronic/server_name.tsv new file mode 100644 index 000000000..0ff6674d2 --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/data/electronic/server_name.tsv @@ -0,0 +1,64 @@ +gmail जीमेल +yahoo याहू +hotmail हॉटमेल +outlook आउटलुक +live लाइव +google गूगल +microsoft माइक्रोसॉफ्ट +facebook फ़ेसबुक +twitter ट्विटर +instagram इंस्टाग्राम +linkedin लिंक्डइन +youtube यूट्यूब +amazon अमेज़ोन +wikipedia विकिपीडिया +github गिटहब +reddit रेडिट +netflix नेटफ्लिक्स +spotify स्पॉटिफाई +apple एप्पल +samsung सैमसंग +nvidia एनविडिया +intel इंटेल +adobe अडोब +wordpress वर्डप्रेस +blogger ब्लॉगर +mentalfloss मेंटल फ्लॉस +placekitten प्लेस किटन +dummyimage डमी इमेज +reliablesoft रिलाएबल सॉफ्ट +ebay ई बे +moz मोज़ +mozilla मॉज़िला +genius जीनियस +groupon ग्रुप ऑन +gutenberg गुटेनबर्ग +recipepuppy रेसिपी पप्पी +buyagift बाय अ गिफ्ट +webmd वेब एम डी +researchgate रिसर्च गेट +afternic आफ्टर निक +hipolabs हिपो लैब्स +licindia एल आई सी इंडिया +placeimg प्लेस आई एम जी +codecademy कोड कैडेमी +skillshop स्किलशॉप +skillshare स्किल शेयर +udemy यूडेमी +masterclass मास्टरक्लास +amity एमिटी +sharda शारदा +universities यूनिवर्सिटीज़ +mcdonald मैक्डॉनल्ड +southmountaincc साउथ माउन्टेन सी सी +academyart अकेडमी आर्ट +bryanuniversity ब्रायन यूनिवर्सिटी +centralaz सेंट्रल ए ज़ेड +alaska अलास्का +phoenix फीनिक्स +phoenixcollege फीनिक्स कॉलेज +maricopa मैरीकोपा +prescott प्रेसकॉट +azwestern ए ज़ेड वेस्टर्न +poetry पोएट्री +harvard हावर्ड diff --git a/nemo_text_processing/text_normalization/hi/data/electronic/subscript_digit.tsv b/nemo_text_processing/text_normalization/hi/data/electronic/subscript_digit.tsv new file mode 100644 index 000000000..bb6259598 --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/data/electronic/subscript_digit.tsv @@ -0,0 +1,11 @@ +₀ शून्य +₁ एक +₂ दो +₃ तीन +₄ चार +₅ पाँच +₆ छह +₇ सात +₈ आठ +₉ नौ + diff --git a/nemo_text_processing/text_normalization/hi/data/electronic/symbols.tsv b/nemo_text_processing/text_normalization/hi/data/electronic/symbols.tsv new file mode 100644 index 000000000..9c6af5ba1 --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/data/electronic/symbols.tsv @@ -0,0 +1,33 @@ +. डॉट +- हाइफ़न +_ अंडर स्कोर +@ एट +/ फॉरवर्ड स्लैश +\\ बैकवर्ड स्लैश +: कोलन +\# हैशटैग +$ डॉलर +% प्रतिशत +& एंड ++ प्लस += इक्वल +? क्वेश्चन मार्क +* स्टार +! एक्सक्लेमेशन मार्क +~ टिल्डा +^ कैरेट +| पाइप +` बैकटिक +; सेमीकोलन +, कॉमा +( ओपन ब्रेकेट +) क्लोज़ ब्रेकेट + स्पेस +' सिंगल कोट +" डबल कोट +< लेस दैन +> ग्रेटर दैन +\[ ओपन स्क्वेर ब्रेकेट +\] क्लोज़ स्क्वेर ब्रेकेट +{ ओपन कर्ली ब्रेकेट +} क्लोज़ कर्ली ब्रेकेट diff --git a/nemo_text_processing/text_normalization/hi/taggers/electronic.py b/nemo_text_processing/text_normalization/hi/taggers/electronic.py new file mode 100644 index 000000000..2a2a4f9a0 --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/taggers/electronic.py @@ -0,0 +1,234 @@ +# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.hi.graph_utils import ( + NEMO_ALPHA, + NEMO_DIGIT, + NEMO_HI_DIGIT, + GraphFst, +) +from nemo_text_processing.text_normalization.hi.utils import get_abs_path + + +class ElectronicFst(GraphFst): + """ + Finite state transducer for classifying electronic: as URLs, email addresses, file paths, + IP addresses, domains, chemical formulas, and alphanumeric codes. + e.g. kumar@gmail.com -> tokens { electronic { username: "kumar" domain: "gmail.com" } } + e.g. https://google.com/ -> tokens { electronic { protocol: "https" domain: "google.com/" } } + e.g. C:\\Users\\HP\\Desktop -> tokens { electronic { path: "C:\\Users\\HP\\Desktop" } } + e.g. 192.168.1.1 -> tokens { electronic { ip: "192.168.1.1" } } + + """ + + def __init__(self, deterministic: bool = True): + super().__init__(name="electronic", kind="classify", deterministic=deterministic) + + subscript_digit = pynini.project( + pynini.string_file(get_abs_path("data/electronic/subscript_digit.tsv")), "input" + ) + + alphanumeric = NEMO_ALPHA | NEMO_DIGIT | NEMO_HI_DIGIT | subscript_digit + + # email + username_chars = NEMO_ALPHA | NEMO_DIGIT | pynini.accep(".") | pynini.accep("-") | pynini.accep("_") + username = ( + pynutil.insert("username: \"") + + pynini.closure(username_chars, 1) + + pynutil.insert("\"") + ) + + domain_chars = NEMO_ALPHA | NEMO_DIGIT | pynini.accep(".") | pynini.accep("-") + domain = ( + pynutil.insert(" domain: \"") + + pynini.closure(domain_chars, 1) + + pynutil.insert("\"") + ) + + email_graph = username + pynini.cross("@", "") + domain + + # url: protocol handling for https://, http://, www., and combined forms + protocol_start = pynini.cross("https://", "https") | pynini.cross("http://", "http") + protocol_end = pynini.cross("www.", "www") + protocol = ( + pynutil.insert("protocol: \"") + + ( + pynutil.add_weight(protocol_start + protocol_end, 1.0) + | pynutil.add_weight(protocol_start, 1.01) + | pynutil.add_weight(protocol_end, 1.02) + ) + + pynutil.insert("\"") + ) + + url_path_chars = alphanumeric | pynini.union( + pynini.accep("."), + pynini.accep("-"), + pynini.accep("_"), + pynini.accep("/"), + pynini.accep("#"), + pynini.accep("?"), + pynini.accep("&"), + pynini.accep("="), + pynini.accep("%"), + pynini.accep("+"), + pynini.accep(":"), + ) + url_path = pynini.closure(url_path_chars, 1) + + url_domain = ( + pynutil.insert(" domain: \"") + + url_path + + pynutil.insert("\"") + ) + + url_graph = protocol + url_domain + + # file paths: Windows (C:\...), Unix (/...), and backslash-prefixed (\...) + drive_letter = NEMO_ALPHA + windows_path_chars = alphanumeric | pynini.union( + pynini.accep("\\"), + pynini.accep("."), + pynini.accep("-"), + pynini.accep("_"), + pynini.accep(" "), + pynini.accep("("), + pynini.accep(")"), + ) + windows_path = ( + pynutil.insert("path: \"") + + drive_letter + + pynini.accep(":") + + pynini.accep("\\") + + pynini.closure(windows_path_chars, 1) + + pynutil.insert("\"") + ) + + unix_path_chars = alphanumeric | pynini.union( + pynini.accep("/"), + pynini.accep("."), + pynini.accep("-"), + pynini.accep("_"), + pynini.accep("$"), + ) + unix_path = ( + pynutil.insert("path: \"") + + pynini.accep("/") + + pynini.closure(unix_path_chars, 1) + + pynutil.insert("\"") + ) + + backslash_path_chars = alphanumeric | pynini.union( + pynini.accep("\\"), + pynini.accep("."), + pynini.accep("-"), + pynini.accep("_"), + pynini.accep(" "), + ) + backslash_path = ( + pynutil.insert("path: \"") + + pynini.accep("\\") + + pynini.closure(backslash_path_chars, 1) + + pynutil.insert("\"") + ) + + # ip addresses: exactly 4 dot-separated octets + ip_octet = pynini.closure(NEMO_DIGIT, 1, 3) + dot_octet = pynini.accep(".") + ip_octet + ip_address = ( + pynutil.insert("ip: \"") + + ip_octet + pynini.closure(dot_octet, 3, 3) + + pynutil.insert("\"") + ) + + # domains: simple TLD-based (abc.com) and government/education suffixes (.gov.in, .ac.in) + domain_segment_chars = NEMO_ALPHA | NEMO_DIGIT | pynini.accep("-") + domain_segment = pynini.closure(domain_segment_chars, 1) + + tld = pynini.project( + pynini.string_file(get_abs_path("data/electronic/domain.tsv")), "input" + ) + + govt_edu_suffix = pynini.project( + pynini.string_file(get_abs_path("data/electronic/govt_edu_suffixes.tsv")), "input" + ) + + simple_domain_body = pynini.closure(domain_segment + pynini.accep("."), 1) + tld + govt_domain_body = domain_segment + govt_edu_suffix + + combined_domain = ( + pynutil.insert("domain: \"") + + (simple_domain_body | govt_domain_body) + + pynini.closure(pynini.accep("/"), 0, 1) + + pynutil.insert("\"") + ) + + # file extensions: e.g. report.pdf, data.csv + known_extensions = pynini.project( + pynini.string_file(get_abs_path("data/electronic/file_extensions.tsv")), "input" + ) + filename_stem_chars = NEMO_ALPHA | NEMO_DIGIT | pynini.accep("-") | pynini.accep("_") + filename_stem = pynini.closure(filename_stem_chars, 1) + file_with_extension = ( + pynutil.insert("domain: \"") + + filename_stem + + known_extensions + + pynutil.insert("\"") + ) + + # chemical formulas with subscript digits: e.g. H₂O, CO₂ + chemical_chars = NEMO_ALPHA | subscript_digit + chemical_formula = ( + pynutil.insert("domain: \"") + + NEMO_ALPHA + + pynini.closure(chemical_chars, 1) + + pynutil.insert("\"") + ) + + # alphanumeric codes: strings containing both letters and digits, + # optionally separated by hyphens, e.g. IELF004, N95, GSAT-18, F-35B + alnum_seg = pynini.closure(NEMO_ALPHA | NEMO_DIGIT, 1) + alphanumeric_pattern = alnum_seg + pynini.closure(pynini.accep("-") + alnum_seg) + + alnum_hyp_sigma = pynini.closure(NEMO_ALPHA | NEMO_DIGIT | pynini.accep("-")) + contains_alpha = alnum_hyp_sigma + NEMO_ALPHA + alnum_hyp_sigma + contains_digit = alnum_hyp_sigma + NEMO_DIGIT + alnum_hyp_sigma + alphanumeric_code_fst = pynini.intersect( + pynini.intersect(alphanumeric_pattern, contains_alpha), contains_digit + ).optimize() + + alphanumeric_code = ( + pynutil.insert("domain: \"") + + alphanumeric_code_fst + + pynutil.insert("\"") + ) + + # Weights use 3 tiers: structurally unambiguous (1.0), moderately general (1.1), greedy (1.2) + graph = ( + pynutil.add_weight(url_graph, 1.0) + | pynutil.add_weight(email_graph, 1.0) + | pynutil.add_weight(windows_path, 1.0) + | pynutil.add_weight(unix_path, 1.0) + | pynutil.add_weight(backslash_path, 1.0) + | pynutil.add_weight(ip_address, 1.0) + | pynutil.add_weight(combined_domain, 1.1) + | pynutil.add_weight(file_with_extension, 1.1) + | pynutil.add_weight(chemical_formula, 1.2) + | pynutil.add_weight(alphanumeric_code, 1.2) + ) + + self.graph = graph.optimize() + self.fst = self.add_tokens(graph).optimize() diff --git a/nemo_text_processing/text_normalization/hi/taggers/tokenize_and_classify.py b/nemo_text_processing/text_normalization/hi/taggers/tokenize_and_classify.py index cb03ebce6..25c6116de 100644 --- a/nemo_text_processing/text_normalization/hi/taggers/tokenize_and_classify.py +++ b/nemo_text_processing/text_normalization/hi/taggers/tokenize_and_classify.py @@ -29,6 +29,7 @@ from nemo_text_processing.text_normalization.hi.taggers.cardinal import CardinalFst from nemo_text_processing.text_normalization.hi.taggers.date import DateFst from nemo_text_processing.text_normalization.hi.taggers.decimal import DecimalFst +from nemo_text_processing.text_normalization.hi.taggers.electronic import ElectronicFst from nemo_text_processing.text_normalization.hi.taggers.fraction import FractionFst from nemo_text_processing.text_normalization.hi.taggers.measure import MeasureFst from nemo_text_processing.text_normalization.hi.taggers.money import MoneyFst @@ -113,6 +114,9 @@ def __init__( telephone = TelephoneFst() telephone_graph = telephone.fst + electronic = ElectronicFst(deterministic=deterministic) + electronic_graph = electronic.fst + classify = ( pynutil.add_weight(whitelist_graph, 1.01) | pynutil.add_weight(cardinal_graph, 1.1) @@ -124,6 +128,7 @@ def __init__( | pynutil.add_weight(money_graph, 1.1) | pynutil.add_weight(telephone_graph, 1.1) | pynutil.add_weight(ordinal_graph, 1.1) + | pynutil.add_weight(electronic_graph, 1.05) ) word_graph = WordFst(punctuation=punctuation, deterministic=deterministic).fst diff --git a/nemo_text_processing/text_normalization/hi/verbalizers/electronic.py b/nemo_text_processing/text_normalization/hi/verbalizers/electronic.py new file mode 100644 index 000000000..57f8b5809 --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/verbalizers/electronic.py @@ -0,0 +1,214 @@ +# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.hi.graph_utils import ( + NEMO_ALPHA, + NEMO_DIGIT, + NEMO_NOT_QUOTE, + GraphFst, + delete_space, + insert_space, +) +from nemo_text_processing.text_normalization.hi.utils import get_abs_path + + +class ElectronicFst(GraphFst): + """ + Finite state transducer for verbalizing electronic addresses. + Uses a phonetic-first approach with letter-by-letter fallback. + + Examples: + electronic { username: "kumar" domain: "gmail.com" } -> "कुमार एट जीमेल डॉट कॉम" + electronic { protocol: "https" domain: "google.com/" } -> "एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश गूगल डॉट कॉम फॉरवर्ड स्लैश" + electronic { path: "C:\\Users\\HP" } -> "सी कोलन बैकवर्ड स्लैश यूज़र्स बैकवर्ड स्लैश एच पी" + electronic { ip: "192.168.1.1" } -> "एक नौ दो डॉट एक छह आठ डॉट एक डॉट एक" + + Args: + deterministic: if True will provide a single transduction option, + for False multiple transductions are generated (used for audio-based normalization) + """ + + def __init__(self, deterministic: bool = True): + super().__init__(name="electronic", kind="verbalize", deterministic=deterministic) + + # Load data files + symbols_graph = pynini.string_file(get_abs_path("data/electronic/symbols.tsv")).optimize() + domain_graph = pynini.string_file(get_abs_path("data/electronic/domain.tsv")).optimize() + server_name_graph = pynini.string_file(get_abs_path("data/electronic/server_name.tsv")).optimize() + common_words_graph = pynini.string_file(get_abs_path("data/electronic/common_words.tsv")).optimize() + latin_to_hindi_graph = pynini.string_file(get_abs_path("data/electronic/latin_to_hindi.tsv")).optimize() + + # Digit mappings - use telephone number mappings for ASCII digits + ascii_digit_graph = pynini.string_file(get_abs_path("data/telephone/number.tsv")).optimize() + hindi_digit_graph = pynini.string_file(get_abs_path("data/numbers/digit.tsv")).optimize() + hindi_zero_graph = pynini.string_file(get_abs_path("data/numbers/zero.tsv")).optimize() + subscript_digit_graph = pynini.string_file(get_abs_path("data/electronic/subscript_digit.tsv")).optimize() + digit_verbalization = ascii_digit_graph | hindi_digit_graph | hindi_zero_graph | subscript_digit_graph + + # Combined phonetic word graph: server names + common words + phonetic_word = server_name_graph | common_words_graph + + # ============ CHARACTER VERBALIZATION ============ + # Single character to Hindi verbalization with space insertion + char_to_hindi = ( + pynutil.add_weight(latin_to_hindi_graph, 1.0) # Letter mapping + | pynutil.add_weight(digit_verbalization, 1.0) # Digit mapping + ) + char_with_space = char_to_hindi + insert_space + + # ============ SYMBOL VERBALIZATION ============ + symbol_to_hindi = symbols_graph + insert_space + + # ============ WORD SEGMENTATION & VERBALIZATION ============ + # Try to match complete words first, then fall back to letter-by-letter + word_char = NEMO_ALPHA | NEMO_DIGIT + + # For a sequence of word characters, try phonetic first, else letter-by-letter + word_segment = pynini.closure(word_char, 1) + + # Phonetic word verbalization (higher priority) + phonetic_verbalization = phonetic_word + insert_space + + # Letter-by-letter verbalization (fallback) + letter_by_letter = pynini.closure(char_with_space, 1) + + # Combined: try phonetic first, fall back to letter-by-letter + # This is done by using weights - phonetic has lower weight (higher priority) + word_verbalization = ( + pynutil.add_weight(phonetic_verbalization, 0.9) + | pynutil.add_weight(letter_by_letter, 1.1) + ) + + # ============ DOMAIN VERBALIZATION ============ + # Domain extension verbalization (.com -> डॉट कॉम) + domain_ext_verbalization = ( + pynini.cross(".", "डॉट ") + + domain_graph + + insert_space + ) + + # ============ PROTOCOL VERBALIZATION ============ + # https -> एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश + https_verb = pynini.cross("https", "एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश ") + http_verb = pynini.cross("http", "एच टी टी पी कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश ") + www_verb = pynini.cross("www", "डब्ल्यू डब्ल्यू डब्ल्यू डॉट ") + httpswww_verb = pynini.cross("httpswww", "एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश डब्ल्यू डब्ल्यू डब्ल्यू डॉट ") + httpwww_verb = pynini.cross("httpwww", "एच टी टी पी कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश डब्ल्यू डब्ल्यू डब्ल्यू डॉट ") + protocol_verbalization = https_verb | http_verb | www_verb | httpswww_verb | httpwww_verb + + # ============ CONTENT VERBALIZATION ============ + # General content: mix of words, symbols, and characters + # Process character by character with symbol handling + content_char = ( + pynutil.add_weight(symbol_to_hindi, 1.0) # Symbol + | pynutil.add_weight(char_with_space, 1.1) # Single char + ) + + # Full content verbalization + content_verbalization = pynini.closure(content_char, 1) + + # ============ FIELD EXTRACTION ============ + # Extract username field + delete_username_tag = pynutil.delete("username: \"") + delete_domain_tag = pynutil.delete("domain: \"") + delete_protocol_tag = pynutil.delete("protocol: \"") + delete_path_tag = pynutil.delete("path: \"") + delete_ip_tag = pynutil.delete("ip: \"") + delete_quote = pynutil.delete("\"") + + # Username verbalization: letter-by-letter with symbol handling + username_content = pynini.closure( + pynutil.add_weight(phonetic_word + insert_space, 0.9) + | pynutil.add_weight(symbol_to_hindi, 1.0) + | pynutil.add_weight(char_with_space, 1.1) + , 1) + + username_graph = ( + delete_username_tag + + username_content + + delete_quote + + delete_space + + pynutil.insert("एट ") # @ symbol + ) + + # Domain verbalization + domain_content = pynini.closure( + pynutil.add_weight(phonetic_word + insert_space, 0.9) + | pynutil.add_weight(domain_ext_verbalization, 0.95) + | pynutil.add_weight(symbol_to_hindi, 1.0) + | pynutil.add_weight(char_with_space, 1.1) + , 1) + + domain_only_graph = ( + delete_domain_tag + + domain_content + + delete_quote + ) + + # Protocol verbalization + protocol_only_graph = ( + delete_protocol_tag + + protocol_verbalization + + delete_quote + + delete_space + ) + + # Path verbalization (Windows/Unix file paths) + path_content = pynini.closure( + pynutil.add_weight(common_words_graph + insert_space, 0.9) + | pynutil.add_weight(symbol_to_hindi, 1.0) + | pynutil.add_weight(char_with_space, 1.1) + , 1) + + path_graph = ( + delete_path_tag + + path_content + + delete_quote + ) + + # IP address verbalization (digit by digit) + ip_char = ( + pynutil.add_weight(symbols_graph + insert_space, 1.0) + | pynutil.add_weight(digit_verbalization + insert_space, 1.0) + ) + ip_content = pynini.closure(ip_char, 1) + + ip_graph = ( + delete_ip_tag + + ip_content + + delete_quote + ) + + # ============ COMBINED GRAPH ============ + # Email: username + domain + email_full = username_graph + domain_only_graph + + # URL with protocol: protocol + domain + url_full = protocol_only_graph + domain_only_graph + + # Combined final graph + graph = ( + pynutil.add_weight(url_full, 1.0) + | pynutil.add_weight(email_full, 1.01) + | pynutil.add_weight(path_graph, 1.02) + | pynutil.add_weight(ip_graph, 1.03) + | pynutil.add_weight(domain_only_graph, 1.04) + ) + + delete_tokens = self.delete_tokens(graph) + self.fst = delete_tokens.optimize() + diff --git a/nemo_text_processing/text_normalization/hi/verbalizers/verbalize.py b/nemo_text_processing/text_normalization/hi/verbalizers/verbalize.py index 30d076c93..e0fb8d8b5 100644 --- a/nemo_text_processing/text_normalization/hi/verbalizers/verbalize.py +++ b/nemo_text_processing/text_normalization/hi/verbalizers/verbalize.py @@ -16,6 +16,7 @@ from nemo_text_processing.text_normalization.hi.verbalizers.cardinal import CardinalFst from nemo_text_processing.text_normalization.hi.verbalizers.date import DateFst from nemo_text_processing.text_normalization.hi.verbalizers.decimal import DecimalFst +from nemo_text_processing.text_normalization.hi.verbalizers.electronic import ElectronicFst from nemo_text_processing.text_normalization.hi.verbalizers.fraction import FractionFst from nemo_text_processing.text_normalization.hi.verbalizers.measure import MeasureFst from nemo_text_processing.text_normalization.hi.verbalizers.money import MoneyFst @@ -66,6 +67,9 @@ def __init__(self, deterministic: bool = True): telephone = TelephoneFst() telephone_graph = telephone.fst + electronic = ElectronicFst(deterministic=deterministic) + electronic_graph = electronic.fst + whitelist_graph = WhiteListFst(deterministic=deterministic).fst graph = ( @@ -79,6 +83,7 @@ def __init__(self, deterministic: bool = True): | ordinal_graph | whitelist_graph | telephone_graph + | electronic_graph ) self.fst = graph diff --git a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_electronic.txt b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_electronic.txt new file mode 100644 index 000000000..34660f943 --- /dev/null +++ b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_electronic.txt @@ -0,0 +1,54 @@ +gmail.com~जीमेल डॉट कॉम +yahoo.com~याहू डॉट कॉम +hotmail.com~हॉटमेल डॉट कॉम +google.com~गूगल डॉट कॉम +kumaar.org~के यू एम ए ए आर डॉट ऑर्ग +kumaar.info~के यू एम ए ए आर डॉट इन्फो +kumar@gmail.com~के यू एम ए आर एट जीमेल डॉट कॉम +robin@hotmail.com~रॉबिन एट हॉटमेल डॉट कॉम +kapil@live.com~के ए पी आई एल एट लाइव डॉट कॉम +sneha@live.com~एस एन ई एच ए एट लाइव डॉट कॉम +mayank@google.com~एम ए वाई ए एन के एट गूगल डॉट कॉम +charu@yahoo.com~सी एच ए आर यू एट याहू डॉट कॉम +john20@yahoo.com~जे ओ एच एन दो शून्य एट याहू डॉट कॉम +vivaan62@gmail.com~वी आई वी ए ए एन छह दो एट जीमेल डॉट कॉम +viaan15@kumaar.com~वी आई ए ए एन एक पाँच एट के यू एम ए ए आर डॉट कॉम +ltaa12@gmail.com~एल टी ए ए एक दो एट जीमेल डॉट कॉम +kristen11@hotmail.com~के आर आई एस टी ई एन एक एक एट हॉटमेल डॉट कॉम +dsmith@yahoo.com~डी एस एम आई टी एच एट याहू डॉट कॉम +hgarza@gmail.com~एच जी ए आर ज़ेड ए एट जीमेल डॉट कॉम +qhill@yahoo.com~क्यू एच आई एल एल एट याहू डॉट कॉम +green-turner.org~ग्रीन हाइफ़न टी यू आर एन ई आर डॉट ऑर्ग +sharma-badami.com~एस एच ए आर एम ए हाइफ़न बी ए डी ए एम आई डॉट कॉम +osborne-gross.com~ओ एस बी ओ आर एन ई हाइफ़न जी आर ओ एस एस डॉट कॉम +lucero-stevenson.net~एल यू सी ई आर ओ हाइफ़न एस टी ई वी ई एन एस ओ एन डॉट नेट +https://google.com/~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश गूगल डॉट कॉम फॉरवर्ड स्लैश +https://github.com/~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश गिटहब डॉट कॉम फॉरवर्ड स्लैश +https://wikipedia.org/~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश विकिपीडिया डॉट ऑर्ग फॉरवर्ड स्लैश +https://amazon.com/~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश अमेज़ोन डॉट कॉम फॉरवर्ड स्लैश +www.google.com~डब्ल्यू डब्ल्यू डब्ल्यू डॉट गूगल डॉट कॉम +https://www.ndtv.com~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश डब्ल्यू डब्ल्यू डब्ल्यू डॉट एन डी टी वी डॉट कॉम +https://www.rbi.org.in/~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश डब्ल्यू डब्ल्यू डब्ल्यू डॉट आर बी आई डॉट ऑर्ग डॉट इन फॉरवर्ड स्लैश +https://www.amity.edu~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश डब्ल्यू डब्ल्यू डब्ल्यू डॉट एमिटी डॉट ई डी यू +https://example.com/blog/~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश ई एक्स ए एम पी एल ई डॉट कॉम फॉरवर्ड स्लैश ब्लॉग फॉरवर्ड स्लैश +https://example.com/about.html~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश ई एक्स ए एम पी एल ई डॉट कॉम फॉरवर्ड स्लैश अबाउट डॉट एच टी एम एल +https://example.com/search.php~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश ई एक्स ए एम पी एल ई डॉट कॉम फॉरवर्ड स्लैश सर्च डॉट पी एच पी +http://ati.edu~एच टी टी पी कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश ए टी आई डॉट ई डी यू +http://gcu.edu~एच टी टी पी कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश जी सी यू डॉट ई डी यू +http://pima.edu~एच टी टी पी कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश पी आई एम ए डॉट ई डी यू +bamu.nic.in/~बी ए एम यू डॉट एन आई सी डॉट इन फॉरवर्ड स्लैश +bieap.gov.in/~बी आई ई ए पी डॉट जी ओ वी डॉट इन फॉरवर्ड स्लैश +www.sharda.ac.in~डब्ल्यू डब्ल्यू डब्ल्यू डॉट शारदा डॉट ए सी डॉट इन +C:\Users\HP\Desktop\~सी कोलन बैकवर्ड स्लैश यूज़र्स बैकवर्ड स्लैश एच पी बैकवर्ड स्लैश डेस्कटॉप बैकवर्ड स्लैश +C:\Users\HP\Downloads\~सी कोलन बैकवर्ड स्लैश यूज़र्स बैकवर्ड स्लैश एच पी बैकवर्ड स्लैश डाउनलोड्स बैकवर्ड स्लैश +C:\Users\HP\Documents\Zoom~सी कोलन बैकवर्ड स्लैश यूज़र्स बैकवर्ड स्लैश एच पी बैकवर्ड स्लैश डॉक्युमेंट्स बैकवर्ड स्लैश ज़ेड ओ ओ एम +/home/desktop~फॉरवर्ड स्लैश होम फॉरवर्ड स्लैश डेस्कटॉप +/etc/apache~फॉरवर्ड स्लैश ई टी सी फॉरवर्ड स्लैश अपाची +/var/www~फॉरवर्ड स्लैश वार फॉरवर्ड स्लैश डब्ल्यू डब्ल्यू डब्ल्यू +192.168.1.1~एक नौ दो डॉट एक छह आठ डॉट एक डॉट एक +10.0.0.1~एक शून्य डॉट शून्य डॉट शून्य डॉट एक +83.54.245.61~आठ तीन डॉट पाँच चार डॉट दो चार पाँच डॉट छह एक +85.189.50.24~आठ पाँच डॉट एक आठ नौ डॉट पाँच शून्य डॉट दो चार +report.pdf~आर ई पी ओ आर टी डॉट पी डी एफ +photo.jpg~पी एच ओ टी ओ डॉट जे पी जी +data.csv~डेटा डॉट सी एस वी diff --git a/tests/nemo_text_processing/hi/test_electronic.py b/tests/nemo_text_processing/hi/test_electronic.py new file mode 100644 index 000000000..024bcf180 --- /dev/null +++ b/tests/nemo_text_processing/hi/test_electronic.py @@ -0,0 +1,34 @@ +# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from parameterized import parameterized + +from nemo_text_processing.text_normalization.normalize import Normalizer + +from ..utils import CACHE_DIR, parse_test_case_file + + +class TestElectronic: + normalizer = Normalizer( + input_case='cased', lang='hi', cache_dir=CACHE_DIR, overwrite_cache=False, post_process=True + ) + + @parameterized.expand(parse_test_case_file('hi/data_text_normalization/test_cases_electronic.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_norm(self, test_input, expected): + pred = self.normalizer.normalize(test_input, verbose=False, punct_post_process=True) + assert pred == expected + diff --git a/tools/text_processing_deployment/Dockerfile b/tools/text_processing_deployment/Dockerfile index be6fedcda..972bab75e 100644 --- a/tools/text_processing_deployment/Dockerfile +++ b/tools/text_processing_deployment/Dockerfile @@ -16,7 +16,7 @@ # Dockerfile for C++ (inverse) text normalization backend Sparrowhawk https://github.com/google/sparrowhawk # set base image (host OS) -FROM continuumio/miniconda3 +FROM continuumio/miniconda3:25.3.1-1 # set the working directory in the container From 423f2907e74db44c5ff4c88ff939a31dc38c0695 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 19:10:05 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../hi/data/electronic/__init__.py | 1 - .../hi/taggers/electronic.py | 58 +++--------- .../hi/verbalizers/electronic.py | 89 +++++++------------ .../hi/test_electronic.py | 1 - 4 files changed, 41 insertions(+), 108 deletions(-) diff --git a/nemo_text_processing/text_normalization/hi/data/electronic/__init__.py b/nemo_text_processing/text_normalization/hi/data/electronic/__init__.py index f6e3c3795..341a77c5b 100644 --- a/nemo_text_processing/text_normalization/hi/data/electronic/__init__.py +++ b/nemo_text_processing/text_normalization/hi/data/electronic/__init__.py @@ -11,4 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - diff --git a/nemo_text_processing/text_normalization/hi/taggers/electronic.py b/nemo_text_processing/text_normalization/hi/taggers/electronic.py index 2a2a4f9a0..edb0b9831 100644 --- a/nemo_text_processing/text_normalization/hi/taggers/electronic.py +++ b/nemo_text_processing/text_normalization/hi/taggers/electronic.py @@ -15,12 +15,7 @@ import pynini from pynini.lib import pynutil -from nemo_text_processing.text_normalization.hi.graph_utils import ( - NEMO_ALPHA, - NEMO_DIGIT, - NEMO_HI_DIGIT, - GraphFst, -) +from nemo_text_processing.text_normalization.hi.graph_utils import NEMO_ALPHA, NEMO_DIGIT, NEMO_HI_DIGIT, GraphFst from nemo_text_processing.text_normalization.hi.utils import get_abs_path @@ -46,18 +41,10 @@ def __init__(self, deterministic: bool = True): # email username_chars = NEMO_ALPHA | NEMO_DIGIT | pynini.accep(".") | pynini.accep("-") | pynini.accep("_") - username = ( - pynutil.insert("username: \"") - + pynini.closure(username_chars, 1) - + pynutil.insert("\"") - ) + username = pynutil.insert("username: \"") + pynini.closure(username_chars, 1) + pynutil.insert("\"") domain_chars = NEMO_ALPHA | NEMO_DIGIT | pynini.accep(".") | pynini.accep("-") - domain = ( - pynutil.insert(" domain: \"") - + pynini.closure(domain_chars, 1) - + pynutil.insert("\"") - ) + domain = pynutil.insert(" domain: \"") + pynini.closure(domain_chars, 1) + pynutil.insert("\"") email_graph = username + pynini.cross("@", "") + domain @@ -89,11 +76,7 @@ def __init__(self, deterministic: bool = True): ) url_path = pynini.closure(url_path_chars, 1) - url_domain = ( - pynutil.insert(" domain: \"") - + url_path - + pynutil.insert("\"") - ) + url_domain = pynutil.insert(" domain: \"") + url_path + pynutil.insert("\"") url_graph = protocol + url_domain @@ -125,10 +108,7 @@ def __init__(self, deterministic: bool = True): pynini.accep("$"), ) unix_path = ( - pynutil.insert("path: \"") - + pynini.accep("/") - + pynini.closure(unix_path_chars, 1) - + pynutil.insert("\"") + pynutil.insert("path: \"") + pynini.accep("/") + pynini.closure(unix_path_chars, 1) + pynutil.insert("\"") ) backslash_path_chars = alphanumeric | pynini.union( @@ -148,19 +128,13 @@ def __init__(self, deterministic: bool = True): # ip addresses: exactly 4 dot-separated octets ip_octet = pynini.closure(NEMO_DIGIT, 1, 3) dot_octet = pynini.accep(".") + ip_octet - ip_address = ( - pynutil.insert("ip: \"") - + ip_octet + pynini.closure(dot_octet, 3, 3) - + pynutil.insert("\"") - ) + ip_address = pynutil.insert("ip: \"") + ip_octet + pynini.closure(dot_octet, 3, 3) + pynutil.insert("\"") # domains: simple TLD-based (abc.com) and government/education suffixes (.gov.in, .ac.in) domain_segment_chars = NEMO_ALPHA | NEMO_DIGIT | pynini.accep("-") domain_segment = pynini.closure(domain_segment_chars, 1) - tld = pynini.project( - pynini.string_file(get_abs_path("data/electronic/domain.tsv")), "input" - ) + tld = pynini.project(pynini.string_file(get_abs_path("data/electronic/domain.tsv")), "input") govt_edu_suffix = pynini.project( pynini.string_file(get_abs_path("data/electronic/govt_edu_suffixes.tsv")), "input" @@ -182,20 +156,12 @@ def __init__(self, deterministic: bool = True): ) filename_stem_chars = NEMO_ALPHA | NEMO_DIGIT | pynini.accep("-") | pynini.accep("_") filename_stem = pynini.closure(filename_stem_chars, 1) - file_with_extension = ( - pynutil.insert("domain: \"") - + filename_stem - + known_extensions - + pynutil.insert("\"") - ) + file_with_extension = pynutil.insert("domain: \"") + filename_stem + known_extensions + pynutil.insert("\"") # chemical formulas with subscript digits: e.g. H₂O, CO₂ chemical_chars = NEMO_ALPHA | subscript_digit chemical_formula = ( - pynutil.insert("domain: \"") - + NEMO_ALPHA - + pynini.closure(chemical_chars, 1) - + pynutil.insert("\"") + pynutil.insert("domain: \"") + NEMO_ALPHA + pynini.closure(chemical_chars, 1) + pynutil.insert("\"") ) # alphanumeric codes: strings containing both letters and digits, @@ -210,11 +176,7 @@ def __init__(self, deterministic: bool = True): pynini.intersect(alphanumeric_pattern, contains_alpha), contains_digit ).optimize() - alphanumeric_code = ( - pynutil.insert("domain: \"") - + alphanumeric_code_fst - + pynutil.insert("\"") - ) + alphanumeric_code = pynutil.insert("domain: \"") + alphanumeric_code_fst + pynutil.insert("\"") # Weights use 3 tiers: structurally unambiguous (1.0), moderately general (1.1), greedy (1.2) graph = ( diff --git a/nemo_text_processing/text_normalization/hi/verbalizers/electronic.py b/nemo_text_processing/text_normalization/hi/verbalizers/electronic.py index 57f8b5809..9f448affb 100644 --- a/nemo_text_processing/text_normalization/hi/verbalizers/electronic.py +++ b/nemo_text_processing/text_normalization/hi/verbalizers/electronic.py @@ -64,10 +64,9 @@ def __init__(self, deterministic: bool = True): # ============ CHARACTER VERBALIZATION ============ # Single character to Hindi verbalization with space insertion - char_to_hindi = ( - pynutil.add_weight(latin_to_hindi_graph, 1.0) # Letter mapping - | pynutil.add_weight(digit_verbalization, 1.0) # Digit mapping - ) + char_to_hindi = pynutil.add_weight(latin_to_hindi_graph, 1.0) | pynutil.add_weight( # Letter mapping + digit_verbalization, 1.0 + ) # Digit mapping char_with_space = char_to_hindi + insert_space # ============ SYMBOL VERBALIZATION ============ @@ -76,30 +75,25 @@ def __init__(self, deterministic: bool = True): # ============ WORD SEGMENTATION & VERBALIZATION ============ # Try to match complete words first, then fall back to letter-by-letter word_char = NEMO_ALPHA | NEMO_DIGIT - + # For a sequence of word characters, try phonetic first, else letter-by-letter word_segment = pynini.closure(word_char, 1) - + # Phonetic word verbalization (higher priority) phonetic_verbalization = phonetic_word + insert_space - + # Letter-by-letter verbalization (fallback) letter_by_letter = pynini.closure(char_with_space, 1) # Combined: try phonetic first, fall back to letter-by-letter # This is done by using weights - phonetic has lower weight (higher priority) - word_verbalization = ( - pynutil.add_weight(phonetic_verbalization, 0.9) - | pynutil.add_weight(letter_by_letter, 1.1) + word_verbalization = pynutil.add_weight(phonetic_verbalization, 0.9) | pynutil.add_weight( + letter_by_letter, 1.1 ) # ============ DOMAIN VERBALIZATION ============ # Domain extension verbalization (.com -> डॉट कॉम) - domain_ext_verbalization = ( - pynini.cross(".", "डॉट ") - + domain_graph - + insert_space - ) + domain_ext_verbalization = pynini.cross(".", "डॉट ") + domain_graph + insert_space # ============ PROTOCOL VERBALIZATION ============ # https -> एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश @@ -113,11 +107,10 @@ def __init__(self, deterministic: bool = True): # ============ CONTENT VERBALIZATION ============ # General content: mix of words, symbols, and characters # Process character by character with symbol handling - content_char = ( - pynutil.add_weight(symbol_to_hindi, 1.0) # Symbol - | pynutil.add_weight(char_with_space, 1.1) # Single char - ) - + content_char = pynutil.add_weight(symbol_to_hindi, 1.0) | pynutil.add_weight( # Symbol + char_with_space, 1.1 + ) # Single char + # Full content verbalization content_verbalization = pynini.closure(content_char, 1) @@ -134,15 +127,12 @@ def __init__(self, deterministic: bool = True): username_content = pynini.closure( pynutil.add_weight(phonetic_word + insert_space, 0.9) | pynutil.add_weight(symbol_to_hindi, 1.0) - | pynutil.add_weight(char_with_space, 1.1) - , 1) - + | pynutil.add_weight(char_with_space, 1.1), + 1, + ) + username_graph = ( - delete_username_tag - + username_content - + delete_quote - + delete_space - + pynutil.insert("एट ") # @ symbol + delete_username_tag + username_content + delete_quote + delete_space + pynutil.insert("एट ") # @ symbol ) # Domain verbalization @@ -150,48 +140,32 @@ def __init__(self, deterministic: bool = True): pynutil.add_weight(phonetic_word + insert_space, 0.9) | pynutil.add_weight(domain_ext_verbalization, 0.95) | pynutil.add_weight(symbol_to_hindi, 1.0) - | pynutil.add_weight(char_with_space, 1.1) - , 1) - - domain_only_graph = ( - delete_domain_tag - + domain_content - + delete_quote + | pynutil.add_weight(char_with_space, 1.1), + 1, ) + domain_only_graph = delete_domain_tag + domain_content + delete_quote + # Protocol verbalization - protocol_only_graph = ( - delete_protocol_tag - + protocol_verbalization - + delete_quote - + delete_space - ) + protocol_only_graph = delete_protocol_tag + protocol_verbalization + delete_quote + delete_space # Path verbalization (Windows/Unix file paths) path_content = pynini.closure( pynutil.add_weight(common_words_graph + insert_space, 0.9) | pynutil.add_weight(symbol_to_hindi, 1.0) - | pynutil.add_weight(char_with_space, 1.1) - , 1) - - path_graph = ( - delete_path_tag - + path_content - + delete_quote + | pynutil.add_weight(char_with_space, 1.1), + 1, ) + path_graph = delete_path_tag + path_content + delete_quote + # IP address verbalization (digit by digit) - ip_char = ( - pynutil.add_weight(symbols_graph + insert_space, 1.0) - | pynutil.add_weight(digit_verbalization + insert_space, 1.0) + ip_char = pynutil.add_weight(symbols_graph + insert_space, 1.0) | pynutil.add_weight( + digit_verbalization + insert_space, 1.0 ) ip_content = pynini.closure(ip_char, 1) - - ip_graph = ( - delete_ip_tag - + ip_content - + delete_quote - ) + + ip_graph = delete_ip_tag + ip_content + delete_quote # ============ COMBINED GRAPH ============ # Email: username + domain @@ -211,4 +185,3 @@ def __init__(self, deterministic: bool = True): delete_tokens = self.delete_tokens(graph) self.fst = delete_tokens.optimize() - diff --git a/tests/nemo_text_processing/hi/test_electronic.py b/tests/nemo_text_processing/hi/test_electronic.py index 024bcf180..b351d0ce9 100644 --- a/tests/nemo_text_processing/hi/test_electronic.py +++ b/tests/nemo_text_processing/hi/test_electronic.py @@ -31,4 +31,3 @@ class TestElectronic: def test_norm(self, test_input, expected): pred = self.normalizer.normalize(test_input, verbose=False, punct_post_process=True) assert pred == expected -