@@ -323,7 +323,7 @@ def handle_word(self, word: str, words: Words) -> Generator[str]:
323323 variants .add (lowercase_word )
324324
325325 # Russian on Kindle must provide a lowercase variant for uppercase-only words (see #2623)
326- elif is_russian and isinstance (self , DictFileFormatForMobi ) and current_word .isupper ():
326+ elif is_russian and isinstance (self , MobiFormat ) and current_word .isupper ():
327327 variants .add (current_word .lower ())
328328
329329 yield self .render_word (
@@ -503,18 +503,11 @@ def process(self) -> None:
503503 self .summary (file )
504504
505505
506- class DictFileFormatForMobi (DictFileFormat ):
507- """Save the data into a *.df* DictFile."""
508-
509- output_file = f"altered-{ DictFileFormat .output_file } "
510-
511-
512506class ConverterFromDictFile (DictFileFormat ):
513507 target_format = ""
514508 target_suffix = ""
515509 final_file = ""
516510 zip_glob_files = "dict-data.*"
517- dictfile_format_cls = DictFileFormat
518511 glossary_options : dict [str , str | bool ] = {}
519512
520513 def _patch_gc (self ) -> None :
@@ -569,10 +562,21 @@ def get_bookname(cls) -> str: # type: ignore[no-untyped-def]
569562 glos .sourceLangName = self .effective_lang_src ()
570563 glos .targetLangName = self .effective_lang_dst ()
571564
565+ if isinstance (self , MobiFormat ):
566+ # Alter the generated word title to fix this Kindling warning:
567+ # [warning R6.1] section 6.1 (p.22): Content is not well-formed XHTML. Kindle requires well-formed HTML documents for reliable conversion. Parse error: ill-formed document: expected `</br>`, but `</idx:orth>` was found (g000002.xhtml)
568+ wordTitleStr_original = glos .wordTitleStr
569+
570+ def wordTitleStr (word : str , ** kwargs : str ) -> str :
571+ # Do not end with `<br>` but `<br/>`
572+ return str (wordTitleStr_original (word , ** kwargs )).replace ("<br>" , "<br/>" )
573+
574+ glos .wordTitleStr = wordTitleStr
575+
572576 self .output_dir_tmp .mkdir ()
573577 glos .convert (
574578 ConvertArgs (
575- inputFilename = str (self .dictionary_file (self . dictfile_format_cls .output_file )),
579+ inputFilename = str (self .dictionary_file (DictFileFormat .output_file )),
576580 outputFilename = str (self .output_dir_tmp / f"dict-data.{ self .target_suffix } " ),
577581 writeOptions = self .glossary_options ,
578582 )
@@ -619,33 +623,16 @@ class DictOrgFormat(ConverterFromDictFile):
619623
620624
621625class MobiFormat (ConverterFromDictFile ):
622- """Save the data into a Mobi file.
623-
624- Incompatibility issues:
625-
626- 1) No support for multiple HTML tags, they will be ignored:
627-
628- Warning(inputpreprocessor):W29007: Rejected unknown tag: <bdi>
629-
630- 2) Most locales are not fully supported:
631-
632- Warning(index build):W15008: language not supported. Using default phonetics for spellchecker: english.
633-
634- 3) Greek (EL), and Russian (RU), locales might be incorrectly displayed:
635-
636- Error(core):E1008: Failed conversion to unicode. The resulting string may contain wrong characters.
637-
638- """
626+ """Save the data into a MobiPocket file."""
639627
640628 target_format = "mobi"
641629 target_suffix = "mobi"
642630 final_file = "dict-{lang_src}-{lang_dst}{etym_suffix}.mobi.zip"
643631 zip_glob_files = "" # Will be set in `_compress()`
644- dictfile_format_cls = DictFileFormatForMobi
645632 glossary_options = {
646633 "cover_path" : str (constants .COVER_FILE ),
647634 "keep" : True ,
648- "kindlegen_path" : str (constants .KINDLEGEN_FILE ),
635+ "kindlegen_path" : str (constants .MOBIPOCKET_TOOL ),
649636 }
650637
651638 def _compress (self ) -> Path :
@@ -913,16 +900,18 @@ def summary(self, file: Path) -> None:
913900
914901
915902PRIMARY_FORMATTERS = {KoboFormat , DictFileFormat , JSONVolumeFormat }
916- SECONDARY_FORMATTERS = {BZ2DictFileFormat , DictOrgFormat , StarDictFormat }
903+ SECONDARY_FORMATTERS = {BZ2DictFileFormat , DictOrgFormat , MobiFormat , StarDictFormat }
917904FORMATTERS : dict [str , tuple [type [BaseFormat ], type [BaseFormat ] | None ]] = {
918905 # "format": (primary formatter class, secondary formatter class)
919906 "dictfile" : (DictFileFormat , BZ2DictFileFormat ),
907+ "dicthtml" : (KoboFormat , None ),
920908 "dictorg" : (DictFileFormat , DictOrgFormat ),
921909 "jsonvolume" : (JSONVolumeFormat , None ),
922- "dicthtml " : (KoboFormat , None ),
910+ "mobi " : (DictFileFormat , MobiFormat ),
923911 "stardict" : (DictFileFormat , StarDictFormat ),
924912}
925913FORMATTERS ["df" ] = FORMATTERS ["dictfile" ]
914+ FORMATTERS ["kindle" ] = FORMATTERS ["mobi" ]
926915FORMATTERS ["kobo" ] = FORMATTERS ["dicthtml" ]
927916
928917
@@ -935,93 +924,6 @@ def get_secondary_formatters() -> set[type[BaseFormat]]:
935924 return SECONDARY_FORMATTERS
936925
937926
938- def run_mobi_formatter (
939- output_dir : Path ,
940- snapshot : str ,
941- locale : str ,
942- words : Words ,
943- variants : Variants ,
944- * ,
945- include_etymology : bool = True ,
946- ) -> None :
947- """Mobi formatter.
948-
949- For multiple languages, we need to delete words if the total number of unique unicode characters is greater than 256.
950- To do this, we delete words using the least-used characters until we meet this condition.
951- """
952-
953- if locale in constants .MOBI_SKIP :
954- log .info ("[Mobi %s] Skipping as the final file size would be > 650 MiB" , locale .upper ())
955- return
956-
957- def all_chars (word : str , details : Word ) -> set [str ]:
958- chars = set (word )
959- if definitions := details .definitions :
960- if isinstance (definitions , str ):
961- chars .update (definitions )
962- elif isinstance (definitions , tuple ):
963- chars .update (utils .flatten (definitions ))
964- if etymology := details .etymology :
965- if isinstance (etymology , str ):
966- chars .update (etymology )
967- elif isinstance (etymology , tuple ):
968- chars .update (utils .flatten (etymology ))
969- return chars
970-
971- stats = defaultdict (list )
972- for word , details in words .copy ().items ():
973- if len (word ) > 127 :
974- log .info ("[Mobi %s] Truncated word too long: %r" , locale .upper (), word )
975- truncated = word [:127 ]
976- words [truncated ] = words .pop (word )
977- word = truncated
978- for char in all_chars (word , details ):
979- stats [char ].append (word )
980-
981- if locale in constants .MOBI_CLEANUP and len (stats ) > 256 :
982- new_words = words .copy ()
983- threshold = 1
984- while len (stats ) > 256 :
985- log .info (
986- "[Mobi %s] Removing words with unique characters count at %d (total is %d)" ,
987- locale .upper (),
988- threshold ,
989- len (stats ),
990- )
991- for char , related_words in sorted (stats .copy ().items (), key = lambda v : (char , len (v [1 ]))):
992- if len (related_words ) == threshold :
993- for w in related_words :
994- new_words .pop (w , None )
995- stats .pop (char )
996- if len (stats ) <= 256 :
997- break
998- threshold += 1
999-
1000- log .info (
1001- "[Mobi %s] Removed %s words from .mobi (total words count is %s, unique characters count is %d)" ,
1002- locale .upper (),
1003- f"{ len (words ) - len (new_words ):,} " ,
1004- f"{ len (new_words ):,} " ,
1005- len (stats ),
1006- )
1007- words = new_words
1008- variants = make_variants (words )
1009- else :
1010- log .info (
1011- "[Mobi %s] Untouched words for .mobi (total words count is %s, unique characters count is %d)" ,
1012- locale .upper (),
1013- f"{ len (words ):,} " ,
1014- len (stats ),
1015- )
1016-
1017- args = (locale , output_dir , words , variants , snapshot )
1018- run_formatter (DictFileFormatForMobi , * args , include_etymology = include_etymology )
1019- try :
1020- run_formatter (MobiFormat , * args , include_etymology = include_etymology )
1021- except Exception :
1022- log .exception ("[Mobi %s] Error with the Mobi conversion" , locale .upper ())
1023-
1024-
1025927def run_formatter (
1026928 cls : type [BaseFormat ],
1027929 locale : str ,
@@ -1097,33 +999,28 @@ def get_latest_json_file(source_dir: Path) -> Path | None:
1097999 return sorted (files )[- 1 ] if files else None
10981000
10991001
1100- def get_formatters (formats : str ) -> tuple [set [type [BaseFormat ]], set [type [BaseFormat ]], bool ]:
1002+ def get_formatters (formats : str ) -> tuple [set [type [BaseFormat ]], set [type [BaseFormat ]]]:
11011003 primary_formatters : set [type [BaseFormat ]] = set ()
11021004 secondary_formatters : set [type [BaseFormat ]] = set ()
1103- mobi_run = False
11041005 for fmt in (formats or "all" ).split ("," ):
11051006 match fmt :
11061007 case _ if fmt in FORMATTERS :
11071008 primary , secondary = FORMATTERS [fmt ]
11081009 primary_formatters .add (primary )
11091010 if secondary :
11101011 secondary_formatters .add (secondary )
1111- case "kindle" | "mobi" :
1112- mobi_run = True
11131012 case "all" :
11141013 primary_formatters = get_primary_formatters ()
11151014 secondary_formatters = get_secondary_formatters ()
1116- mobi_run = True
11171015 break
11181016 case _:
11191017 print (f"Unknown format: { fmt !r} " )
1120- return primary_formatters , secondary_formatters , mobi_run
1018+ return primary_formatters , secondary_formatters
11211019
11221020
11231021def convert (
11241022 primary_formatters : set [type [BaseFormat ]],
11251023 secondary_formatters : set [type [BaseFormat ]],
1126- mobi_run : bool ,
11271024 output_dir : Path ,
11281025 snapshot : str ,
11291026 locale : str ,
@@ -1137,8 +1034,6 @@ def convert(
11371034 for include_etymology in include_etymologies :
11381035 distribute_workload (primary_formatters , * args , include_etymology = include_etymology )
11391036 distribute_workload (secondary_formatters , * args , include_etymology = include_etymology )
1140- if mobi_run :
1141- run_mobi_formatter (* args , include_etymology = include_etymology )
11421037
11431038
11441039def main (locale : str , format : str = "all" , with_etym_only : bool = False ) -> int :
@@ -1159,12 +1054,11 @@ def main(locale: str, format: str = "all", with_etym_only: bool = False) -> int:
11591054 output_dir = source_dir / "output"
11601055 output_dir .mkdir (exist_ok = True , parents = True )
11611056
1162- primary_formatters , secondary_formatters , mobi_run = get_formatters (format )
1057+ primary_formatters , secondary_formatters = get_formatters (format )
11631058 start = monotonic ()
11641059 convert (
11651060 primary_formatters ,
11661061 secondary_formatters ,
1167- mobi_run ,
11681062 output_dir ,
11691063 input_file .stem .split ("-" )[- 1 ],
11701064 locale ,
0 commit comments