diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 0bdaaa05..b65cd53d 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -23,8 +23,8 @@ jobs: - name: Prepare Graph run: | mkdir graph - cp tn/zh_tn_normalizer.far graph - cp itn/zh_itn_normalizer.far graph + cp tn/*.fst graph + cp itn/*.fst graph - name: Upload Graph uses: actions/upload-artifact@v3 diff --git a/itn/chinese/inverse_normalizer.py b/itn/chinese/inverse_normalizer.py index 52a03084..3d5e5038 100644 --- a/itn/chinese/inverse_normalizer.py +++ b/itn/chinese/inverse_normalizer.py @@ -12,8 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os - from tn.processor import Processor from itn.chinese.rules.cardinal import Cardinal from itn.chinese.rules.char import Char @@ -26,36 +24,19 @@ from itn.chinese.rules.time import Time from itn.chinese.rules.preprocessor import PreProcessor -from pynini import Far from pynini.lib.pynutil import add_weight, delete from importlib_resources import files class InverseNormalizer(Processor): - def __init__(self, cache_dir=None, overwrite_cache=False, + def __init__(self, cache_dir='itn', overwrite_cache=False, enable_standalone_number=True, enable_0_to_9=True): super().__init__(name='inverse_normalizer', ordertype='itn') - self.cache_dir = cache_dir - self.overwrite_cache = overwrite_cache self.convert_number = enable_standalone_number self.enable_0_to_9 = enable_0_to_9 - - far_file = files('itn').joinpath('zh_itn_normalizer.far') - if self.cache_dir: - os.makedirs(self.cache_dir, exist_ok=True) - far_file = os.path.join(self.cache_dir, 'zh_itn_normalizer.far') - - if far_file and os.path.exists(far_file) and not overwrite_cache: - self.tagger = Far(far_file)['tagger'] - self.verbalizer = Far(far_file)['verbalizer'] - else: - self.build_tagger() - self.build_verbalizer() - - if self.cache_dir and self.overwrite_cache: - self.export(far_file) + self.build_fst('zh_itn', cache_dir, overwrite_cache) def build_tagger(self): tagger = (add_weight(Date().tagger, 1.02) diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index eda6dedd..463ffd43 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -1,14 +1,12 @@ cmake_minimum_required(VERSION 3.14 FATAL_ERROR) -project(text_processing VERSION 0.1) +project(wetextprocessing VERSION 0.1) set(CMAKE_CXX_STANDARD 14) set(CMAKE_VERBOSE_MAKEFILE OFF) -option(BUILD_TESTING "whether to build unit test" ON) -option(FST_HAVE_BIN "whether to build fst binaries" OFF) +option(BUILD_TESTING "whether to build unit test" OFF) include(FetchContent) -include(ExternalProject) set(FETCHCONTENT_QUIET OFF) get_filename_component(fc_base "fc_base" REALPATH BASE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(FETCHCONTENT_BASE_DIR ${fc_base}) @@ -21,9 +19,8 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") endif() include(openfst) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${PROJECT_SOURCE_DIR}) add_subdirectory(utils) -add_dependencies(utils openfst) add_subdirectory(processor) add_subdirectory(bin) diff --git a/runtime/bin/processor_main.cc b/runtime/bin/processor_main.cc index 45ccfc49..2b50d5af 100644 --- a/runtime/bin/processor_main.cc +++ b/runtime/bin/processor_main.cc @@ -17,26 +17,26 @@ #include #include "processor/processor.h" -#include "processor/token_parser.h" #include "utils/flags.h" DEFINE_string(text, "", "input string"); DEFINE_string(file, "", "input file"); -DEFINE_string(far, "", "FST archives"); +DEFINE_string(tagger, "", "tagger fst path"); +DEFINE_string(verbalizer, "", "verbalizer fst path"); int main(int argc, char* argv[]) { gflags::ParseCommandLineFlags(&argc, &argv, false); google::InitGoogleLogging(argv[0]); - if (FLAGS_far.empty()) { - LOG(FATAL) << "Please provide the FST archives."; + if (FLAGS_tagger.empty() || FLAGS_verbalizer.empty()) { + LOG(FATAL) << "Please provide the tagger and verbalizer fst files."; } - wenet::Processor processor(FLAGS_far); + wenet::Processor processor(FLAGS_tagger, FLAGS_verbalizer); if (!FLAGS_text.empty()) { std::string tagged_text = processor.tag(FLAGS_text); std::cout << tagged_text << std::endl; - std::string normalized_text = processor.normalize(FLAGS_text); + std::string normalized_text = processor.verbalize(tagged_text); std::cout << normalized_text << std::endl; } @@ -46,7 +46,7 @@ int main(int argc, char* argv[]) { while (getline(file, line)) { std::string tagged_text = processor.tag(line); std::cout << tagged_text << std::endl; - std::string normalized_text = processor.normalize(line); + std::string normalized_text = processor.verbalize(tagged_text); std::cout << normalized_text << std::endl; } } diff --git a/runtime/cmake/openfst.cmake b/runtime/cmake/openfst.cmake index 35f57e63..335a1744 100644 --- a/runtime/cmake/openfst.cmake +++ b/runtime/cmake/openfst.cmake @@ -1,28 +1,36 @@ include(gflags) +# We can't build glog with gflags, unless gflags is pre-installed. +# If build glog with pre-installed gflags, there will be conflict. set(WITH_GFLAGS OFF CACHE BOOL "whether build glog with gflags" FORCE) include(glog) -set(CONFIG_FLAGS "") -if(NOT FST_HAVE_BIN) - set(CONFIG_FLAGS "--disable-bin") +set(HAVE_BIN OFF CACHE BOOL "Build the fst binaries" FORCE) +set(HAVE_SCRIPT OFF CACHE BOOL "Build the fstscript" FORCE) +set(HAVE_COMPACT OFF CACHE BOOL "Build compact" FORCE) +set(HAVE_CONST OFF CACHE BOOL "Build const" FORCE) +set(HAVE_GRM OFF CACHE BOOL "Build grm" FORCE) +set(HAVE_FAR OFF CACHE BOOL "Build far" FORCE) +set(HAVE_PDT OFF CACHE BOOL "Build pdt" FORCE) +set(HAVE_MPDT OFF CACHE BOOL "Build mpdt" FORCE) +set(HAVE_LINEAR OFF CACHE BOOL "Build linear" FORCE) +set(HAVE_LOOKAHEAD OFF CACHE BOOL "Build lookahead" FORCE) +set(HAVE_NGRAM OFF CACHE BOOL "Build ngram" FORCE) +set(HAVE_SPECIAL OFF CACHE BOOL "Build special" FORCE) + +if(MSVC) + add_compile_options(/W0 /wd4244 /wd4267) endif() +# "OpenFST port for Windows" builds openfst with cmake for multiple platforms. # Openfst is compiled with glog/gflags to avoid log and flag conflicts with log and flags in wenet/libtorch. # To build openfst with gflags and glog, we comment out some vars of {flags, log}.h and flags.cc. set(openfst_SOURCE_DIR ${fc_base}/openfst-src CACHE PATH "OpenFST source directory") -set(openfst_PREFIX_DIR ${fc_base}/openfst-subbuild/openfst-populate-prefix CACHE PATH "OpenFST prefix directory") -ExternalProject_Add(openfst - URL https://github.com/mjansche/openfst/archive/1.7.2.zip - URL_HASH MD5=96656fee440ee2d71006a4900ef9ac00 - PREFIX ${openfst_PREFIX_DIR} - SOURCE_DIR ${openfst_SOURCE_DIR} - CONFIGURE_COMMAND ${openfst_SOURCE_DIR}/configure ${CONFIG_FLAGS} --enable-far --prefix=${openfst_PREFIX_DIR} - "CPPFLAGS=-I${gflags_BINARY_DIR}/include -I${glog_SOURCE_DIR}/src -I${glog_BINARY_DIR}" - "LDFLAGS=-L${gflags_BINARY_DIR} -L${glog_BINARY_DIR}" - "LIBS=-lgflags_nothreads -lglog -lpthread" - COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/patch/openfst ${openfst_SOURCE_DIR} - BUILD_COMMAND make -j$(nproc) + FetchContent_Declare(openfst + URL https://github.com/kkm000/openfst/archive/refs/tags/win/1.7.2.1.tar.gz + URL_HASH SHA256=e04e1dabcecf3a687ace699ccb43a8a27da385777a56e69da6e103344cc66bca + PATCH_COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/patch/openfst ${openfst_SOURCE_DIR} ) -add_dependencies(openfst gflags glog) -link_directories(${openfst_PREFIX_DIR}/lib) +FetchContent_MakeAvailable(openfst) +add_dependencies(fst gflags glog) +target_link_libraries(fst PUBLIC gflags_nothreads_static glog) include_directories(${openfst_SOURCE_DIR}/src/include) diff --git a/runtime/patch/openfst/src/CMakeLists.txt b/runtime/patch/openfst/src/CMakeLists.txt index 83bfd7d5..04fb1c15 100644 --- a/runtime/patch/openfst/src/CMakeLists.txt +++ b/runtime/patch/openfst/src/CMakeLists.txt @@ -6,7 +6,10 @@ install(DIRECTORY include/ DESTINATION include/ FILES_MATCHING PATTERN "*.h") add_subdirectory(lib) -add_subdirectory(script) + +if(HAVE_SCRIPT) + add_subdirectory(script) +endif(HAVE_SCRIPT) if(HAVE_BIN) add_subdirectory(bin) diff --git a/runtime/patch/openfst/src/bin/CMakeLists.txt b/runtime/patch/openfst/src/bin/CMakeLists.txt new file mode 100644 index 00000000..e69de29b diff --git a/runtime/patch/openfst/src/extensions/special/CMakeLists.txt b/runtime/patch/openfst/src/extensions/special/CMakeLists.txt index bad401e1..9c71b750 100644 --- a/runtime/patch/openfst/src/extensions/special/CMakeLists.txt +++ b/runtime/patch/openfst/src/extensions/special/CMakeLists.txt @@ -10,7 +10,7 @@ if(HAVE_BIN) sigma-fst.cc ) - set_target_properties(fstspecial-bin PROPERTIE + set_target_properties(fstspecial-bin PROPERTIES FOLDER special/bin OUTPUT_NAME fstspecial ) diff --git a/runtime/patch/openfst/src/test/CMakeLists.txt b/runtime/patch/openfst/src/test/CMakeLists.txt deleted file mode 100644 index 9230ce56..00000000 --- a/runtime/patch/openfst/src/test/CMakeLists.txt +++ /dev/null @@ -1,54 +0,0 @@ -add_executable(fst_test - fst_test.cc - ../include/fst/test/fst_test.h - ) -target_link_libraries(fst_test fst ${CMAKE_DL_LIBS}) -set_target_properties(fst_test PROPERTIES FOLDER test) -add_test(NAME fst_test-test COMMAND fst_test) - -add_executable(weight_test - weight_test.cc - ../include/fst/test/weight-tester.h - ) -target_link_libraries(weight_test fst ${CMAKE_DL_LIBS}) -set_target_properties(weight_test PROPERTIES FOLDER test) -add_test(NAME weight_test-test COMMAND weight_test) - -add_executable(algo_test_log algo_test.cc ../include/fst/test/algo_test.h ../include/fst/test/rand-fst.h) -target_link_libraries(algo_test_log fst ${CMAKE_DL_LIBS}) -target_compile_definitions(algo_test_log - PRIVATE TEST_LOG=1) -set_target_properties(algo_test_log PROPERTIES FOLDER test) -add_test(NAME algo_test_log-test COMMAND algo_test_log) - - -add_executable(algo_test_tropical algo_test.cc ../include/fst/test/algo_test.h ../include/fst/test/rand-fst.h) -target_link_libraries(algo_test_tropical fst ${CMAKE_DL_LIBS}) -target_compile_definitions(algo_test_tropical - PRIVATE TEST_TROPICAL=1) -set_target_properties(algo_test_tropical PROPERTIES FOLDER test) -add_test(NAME algo_test_tropical-test COMMAND algo_test_tropical) - - -add_executable(algo_test_minmax algo_test.cc ../include/fst/test/algo_test.h ../include/fst/test/rand-fst.h) -target_link_libraries(algo_test_minmax fst ${CMAKE_DL_LIBS}) -target_compile_definitions(algo_test_minmax - PRIVATE TEST_MINMAX=1) -set_target_properties(algo_test_minmax PROPERTIES FOLDER test) -add_test(NAME algo_test_minmax-test COMMAND algo_test_minmax) - - -add_executable(algo_test_lexicographic algo_test.cc ../include/fst/test/algo_test.h ../include/fst/test/rand-fst.h) -target_link_libraries(algo_test_lexicographic fst ${CMAKE_DL_LIBS}) -target_compile_definitions(algo_test_lexicographic - PRIVATE TEST_LEXICOGRAPHIC=1) -set_target_properties(algo_test_lexicographic PROPERTIES FOLDER test) -add_test(NAME algo_test_lexicographic-test COMMAND algo_test_lexicographic) - - -add_executable(algo_test_power algo_test.cc ../include/fst/test/algo_test.h ../include/fst/test/rand-fst.h) -target_link_libraries(algo_test_power fst ${CMAKE_DL_LIBS}) -target_compile_definitions(algo_test_power - PRIVATE TEST_POWER=1) -set_target_properties(algo_test_power PROPERTIES FOLDER test) -add_test(NAME algo_test_power-test COMMAND algo_test_power) diff --git a/runtime/processor/CMakeLists.txt b/runtime/processor/CMakeLists.txt index f349ac9e..77ec09f8 100644 --- a/runtime/processor/CMakeLists.txt +++ b/runtime/processor/CMakeLists.txt @@ -2,4 +2,4 @@ add_library(processor STATIC processor.cc token_parser.cc ) -target_link_libraries(processor PUBLIC fstfar utils) +target_link_libraries(processor PUBLIC utils) diff --git a/runtime/processor/processor.cc b/runtime/processor/processor.cc index 1c4eb313..8f5808ec 100644 --- a/runtime/processor/processor.cc +++ b/runtime/processor/processor.cc @@ -14,47 +14,30 @@ #include "processor/processor.h" -#include "fst/fstlib.h" - #include "utils/utils.h" -using fst::FarReader; -using fst::StdVectorFst; using fst::StringTokenType; namespace wenet { -Processor::Processor(const std::string& far_path) { - FarReader* reader = FarReader::Open(far_path); - CHECK_NOTNULL(reader); - - CHECK_GT(reader->Find("tagger"), 0) << "Tagger is missing."; - tagger_ = reader->GetFst()->Copy(); - - CHECK_GT(reader->Find("verbalizer"), 0) << "Verbalizer is missing."; - verbalizer_ = reader->GetFst()->Copy(); - - delete reader; - +Processor::Processor(const std::string& tagger_path, + const std::string& verbalizer_path) { + tagger_.reset(StdVectorFst::Read(tagger_path)); + verbalizer_.reset(StdVectorFst::Read(verbalizer_path)); compiler_ = std::make_shared>(StringTokenType::BYTE); - if (far_path.find("_tn_") != far_path.npos) { + if (tagger_path.find("_tn_") != tagger_path.npos) { parse_type_ = ParseType::kTN; - } else if (far_path.find("_itn_") != far_path.npos) { + } else if (tagger_path.find("_itn_") != tagger_path.npos) { parse_type_ = ParseType::kITN; } else { - LOG(FATAL) << "Invalid far prefix, prefix should contain" + LOG(FATAL) << "Invalid fst prefix, prefix should contain" << " either \"_tn_\" or \"_itn_\"."; } } -Processor::~Processor() { - delete tagger_; - delete verbalizer_; -} - std::string Processor::compose(const std::string& input, - const Fst* fst) { + const StdVectorFst* fst) { StdVectorFst input_fst; compiler_->operator()(input, &input_fst); @@ -64,21 +47,20 @@ std::string Processor::compose(const std::string& input, } std::string Processor::tag(const std::string& input) { - return compose(input, tagger_); + return compose(input, tagger_.get()); } std::string Processor::verbalize(const std::string& input) { - return compose(input, verbalizer_); -} - -std::string Processor::normalize(const std::string& input) { - std::string output = tag(input); - if (output.empty()) { + if (input.empty()) { return ""; } TokenParser parser(parse_type_); - output = parser.reorder(output); - return verbalize(output); + std::string output = parser.reorder(input); + return compose(output, verbalizer_.get()); +} + +std::string Processor::normalize(const std::string& input) { + return verbalize(tag(input)); } } // namespace wenet diff --git a/runtime/processor/processor.h b/runtime/processor/processor.h index 4954b1fa..84e305da 100644 --- a/runtime/processor/processor.h +++ b/runtime/processor/processor.h @@ -15,30 +15,29 @@ #ifndef PROCESSOR_PROCESSOR_H_ #define PROCESSOR_PROCESSOR_H_ -#include "fst/extensions/far/farlib.h" +#include "fst/fstlib.h" #include "processor/token_parser.h" -using fst::Fst; using fst::StdArc; +using fst::StdVectorFst; using fst::StringCompiler; namespace wenet { class Processor { public: - Processor(const std::string& far_path); - ~Processor(); + Processor(const std::string& tagger_path, const std::string& verbalizer_path); std::string tag(const std::string& input); std::string verbalize(const std::string& input); std::string normalize(const std::string& input); private: - std::string compose(const std::string& input, const Fst* fst); + std::string compose(const std::string& input, const StdVectorFst* fst); ParseType parse_type_; - Fst* tagger_ = nullptr; - Fst* verbalizer_ = nullptr; + std::shared_ptr tagger_ = nullptr; + std::shared_ptr verbalizer_ = nullptr; std::shared_ptr> compiler_ = nullptr; }; diff --git a/runtime/processor/token_parser.cc b/runtime/processor/token_parser.cc index 80e36e6a..5fdce2d4 100644 --- a/runtime/processor/token_parser.cc +++ b/runtime/processor/token_parser.cc @@ -148,7 +148,6 @@ std::string TokenParser::reorder(const std::string& input) { for (auto& token : tokens) { output += token.string(orders) + " "; } - tokens.clear(); return trim(output); } diff --git a/runtime/test/processor_test.cc b/runtime/test/processor_test.cc index b61297b3..55ab0965 100644 --- a/runtime/test/processor_test.cc +++ b/runtime/test/processor_test.cc @@ -52,7 +52,9 @@ class ProcessorTest std::string spoken; virtual void SetUp() { - processor = new wenet::Processor("../data/zh_tn_normalizer.far"); + std::string tagger_path = "../tn/zh_tn_tagger.fst"; + std::string verbalizer_path = "../tn/zh_tn_verbalizer.fst"; + processor = new wenet::Processor(tagger_path, verbalizer_path); written = GetParam().first; spoken = GetParam().second; }; @@ -65,6 +67,6 @@ TEST_P(ProcessorTest, NormalizeTest) { } std::vector> test_cases = - parse_test_case("../test/data/normalizer.txt"); + parse_test_case("../tn/chinese/test/data/normalizer.txt"); INSTANTIATE_TEST_SUITE_P(NormalizeTest, ProcessorTest, testing::ValuesIn(test_cases)); diff --git a/setup.py b/setup.py index 797c87c3..ea21a549 100644 --- a/setup.py +++ b/setup.py @@ -32,8 +32,8 @@ url="https://github.com/wenet-e2e/WeTextProcessing", packages=find_packages(), package_data={ - "tn": ["*.far"], - "itn": ["*.far"], + "tn": ["*.fst"], + "itn": ["*.fst"], }, install_requires=['pynini', 'importlib_resources'], tests_require=['pytest'], diff --git a/tn/chinese/normalizer.py b/tn/chinese/normalizer.py index f2a3c005..5e00e087 100644 --- a/tn/chinese/normalizer.py +++ b/tn/chinese/normalizer.py @@ -12,8 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os - from tn.processor import Processor from tn.chinese.rules.cardinal import Cardinal from tn.chinese.rules.char import Char @@ -28,7 +26,6 @@ from tn.chinese.rules.time import Time from tn.chinese.rules.whitelist import Whitelist -from pynini import Far from pynini.lib.pynutil import add_weight, delete, insert from importlib_resources import files @@ -36,7 +33,7 @@ class Normalizer(Processor): def __init__(self, - cache_dir=None, + cache_dir='tn', overwrite_cache=False, remove_interjections=True, traditional_to_simple=True, @@ -44,28 +41,12 @@ def __init__(self, full_to_half=True, tag_oov=False): super().__init__(name='normalizer') - self.cache_dir = cache_dir - self.overwrite_cache = overwrite_cache self.remove_interjections = remove_interjections self.traditional_to_simple = traditional_to_simple self.remove_puncts = remove_puncts self.full_to_half = full_to_half self.tag_oov = tag_oov - - far_file = files('tn').joinpath('zh_tn_normalizer.far') - if self.cache_dir: - os.makedirs(self.cache_dir, exist_ok=True) - far_file = os.path.join(self.cache_dir, 'zh_tn_normalizer.far') - - if far_file and os.path.exists(far_file) and not overwrite_cache: - self.tagger = Far(far_file)['tagger'] - self.verbalizer = Far(far_file)['verbalizer'] - else: - self.build_tagger() - self.build_verbalizer() - - if self.cache_dir and self.overwrite_cache: - self.export(far_file) + self.build_fst('zh_tn', cache_dir, overwrite_cache) def build_tagger(self): processor = PreProcessor( diff --git a/tn/processor.py b/tn/processor.py index 52e39a15..b70a9292 100644 --- a/tn/processor.py +++ b/tn/processor.py @@ -12,10 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os + from tn.token_parser import TokenParser -from pynini import cdrewrite, cross, difference, escape, shortestpath, union -from pynini.export import export +from pynini import cdrewrite, cross, difference, escape, Fst, shortestpath, union from pynini.lib import byte, utf8 from pynini.lib.pynutil import delete, insert @@ -35,7 +36,7 @@ def __init__(self, name, ordertype="tn"): self.SIGMA = (CHAR | cross('\\\\\\', '\\') | cross('\\"', '"')).star self.name = name - self.parser = TokenParser(ordertype) + self.ordertype = ordertype self.tagger = None self.verbalizer = None @@ -56,11 +57,24 @@ def build_verbalizer(self): verbalizer = delete('value: "') + self.SIGMA + delete('"') self.verbalizer = self.delete_tokens(verbalizer) - def export(self, file_name): - exporter = export.Exporter(file_name) - exporter['tagger'] = self.tagger.optimize() - exporter['verbalizer'] = self.verbalizer.optimize() - exporter.close() + def build_fst(self, prefix, cache_dir, overwrite_cache): + os.makedirs(cache_dir, exist_ok=True) + tagger_name = '{}_tagger.fst'.format(prefix) + verbalizer_name = '{}_verbalizer.fst'.format(prefix) + + tagger_path = os.path.join(cache_dir, tagger_name) + verbalizer_path = os.path.join(cache_dir, verbalizer_name) + + exists = os.path.exists(tagger_path) and os.path.exists( + verbalizer_path) + if exists and not overwrite_cache: + self.tagger = Fst.read(tagger_path).optimize() + self.verbalizer = Fst.read(verbalizer_path).optimize() + else: + self.build_tagger() + self.build_verbalizer() + self.tagger.optimize().write(tagger_path) + self.verbalizer.optimize().write(verbalizer_path) def tag(self, input): input = escape(input) @@ -68,14 +82,13 @@ def tag(self, input): return shortestpath(lattice, nshortest=1, unique=True).string() def verbalize(self, input): - lattice = input @ self.verbalizer + # Only words from the blacklist are contained. + if len(input) == 0: + return '' + output = TokenParser(self.ordertype).reorder(input) + # We need escape for pynini to build the fst from string. + lattice = escape(output) @ self.verbalizer return shortestpath(lattice, nshortest=1, unique=True).string() def normalize(self, input): - output = self.tag(input) - # Only words from the blacklist are contained. - if len(output) == 0: - return '' - output = self.parser.reorder(output) - output = escape(output) - return self.verbalize(output) + return self.verbalize(self.tag(input))