1 /*============================================================================ 2 epub2txt v2 3 xhtml.c 4 Copyright (c)2020 Kevin Boone, GPL v3.0 5 ============================================================================*/ 6 7 #define _GNU_SOURCE 8 #include 9 #include 10 #include 11 #include 12 #include 13 #include 14 #include 15 #ifndef __APPLE__ 16 #include 17 #endif 18 #include "epub2txt.h" 19 #include "log.h" 20 #include "custom_string.h" 21 #include "wstring.h" 22 #include "wrap.h" 23 #include "xhtml.h" 24 25 /*============================================================================ 26 Format definition stuff 27 ============================================================================*/ 28 29 typedef enum { FORMAT_NONE, 30 FORMAT_BOLD_ON, FORMAT_BOLD_OFF, 31 FORMAT_ITALIC_ON, FORMAT_ITALIC_OFF, 32 FORMAT_H1_ON, FORMAT_H1_OFF, 33 FORMAT_H2_ON, FORMAT_H2_OFF, 34 FORMAT_H3_ON, FORMAT_H3_OFF, 35 FORMAT_H4_ON, FORMAT_H4_OFF, 36 FORMAT_H5_ON, FORMAT_H5_OFF } Format; 37 38 /* bitmasks for ANSI highlighting */ 39 enum { FMT_BOLD = 1 << 0, 40 FMT_ITAL = 1 << 1 }; 41 42 /*============================================================================ 43 xhtml_is_start_format_tag 44 ============================================================================*/ 45 BOOL xhtml_is_start_format_tag (const char *tag, Format *format) 46 { 47 if (strcasecmp (tag, "b") == 0) 48 { 49 *format = FORMAT_BOLD_ON; 50 return TRUE; 51 } 52 if (strcasecmp (tag, "i") == 0) 53 { 54 *format = FORMAT_ITALIC_ON; 55 return TRUE; 56 } 57 return FALSE; 58 } 59 60 61 /*============================================================================ 62 xhtml_is_end_breaking_tag 63 ============================================================================*/ 64 BOOL xhtml_is_end_breaking_tag (const char *tag, Format *format) 65 { 66 if (strcasecmp (tag, "/h1") == 0) 67 { 68 *format = FORMAT_BOLD_OFF; 69 return TRUE; 70 } 71 if (strcasecmp (tag, "/h2") == 0) 72 { 73 *format = FORMAT_BOLD_OFF; 74 return TRUE; 75 } 76 if (strcasecmp (tag, "/h3") == 0) 77 { 78 *format = FORMAT_BOLD_OFF; 79 return TRUE; 80 } 81 if (strcasecmp (tag, "/h4") == 0) 82 { 83 *format = FORMAT_BOLD_OFF; 84 return TRUE; 85 } 86 if (strcasecmp (tag, "/h5") == 0) 87 { 88 *format = FORMAT_BOLD_OFF; 89 return TRUE; 90 } 91 if (strcasecmp (tag, "/div") == 0) 92 { 93 *format = FORMAT_NONE; 94 return TRUE; 95 } 96 if (strcasecmp (tag, "/blockquote") == 0) 97 { 98 *format = FORMAT_NONE; 99 return TRUE; 100 } 101 return FALSE; 102 } 103 104 105 /*============================================================================ 106 xhtml_is_end_format_tag 107 ============================================================================*/ 108 BOOL xhtml_is_end_format_tag (const char *tag, Format *format) 109 { 110 if (strcasecmp (tag, "/b") == 0) 111 { 112 *format = FORMAT_BOLD_OFF; 113 return TRUE; 114 } 115 if (strcasecmp (tag, "/i") == 0) 116 { 117 *format = FORMAT_ITALIC_OFF; 118 return TRUE; 119 } 120 return FALSE; 121 } 122 123 124 /*============================================================================ 125 xhtml_is_start_breaking_tag 126 ============================================================================*/ 127 BOOL xhtml_is_start_breaking_tag (const char *tag, Format *format) 128 { 129 if (strcasecmp (tag, "h1") == 0) 130 { 131 *format = FORMAT_BOLD_ON; 132 return TRUE; 133 } 134 if (strcasecmp (tag, "h2") == 0) 135 { 136 *format = FORMAT_BOLD_ON; 137 return TRUE; 138 } 139 if (strcasecmp (tag, "h3") == 0) 140 { 141 *format = FORMAT_BOLD_ON; 142 return TRUE; 143 } 144 if (strcasecmp (tag, "h4") == 0) 145 { 146 *format = FORMAT_BOLD_ON; 147 return TRUE; 148 } 149 if (strcasecmp (tag, "h5") == 0) 150 { 151 *format = FORMAT_BOLD_ON; 152 return TRUE; 153 } 154 if (strcasecmp (tag, "div") == 0) 155 { 156 *format = FORMAT_NONE; 157 return TRUE; 158 } 159 if (strcasecmp (tag, "blockquote") == 0) 160 { 161 *format = FORMAT_NONE; 162 return TRUE; 163 } 164 return FALSE; 165 } 166 167 168 169 /*============================================================================ 170 xhtml_emit_format 171 ============================================================================*/ 172 void xhtml_emit_format (const Epub2TxtOptions *options, Format format) 173 { 174 IN 175 176 if (options->ansi && !options->raw) 177 { 178 switch (format) 179 { 180 case FORMAT_BOLD_ON: 181 printf ("\x1B[1m"); break; 182 183 case FORMAT_BOLD_OFF: 184 printf ("\x1B[0m"); break; 185 186 case FORMAT_ITALIC_ON: 187 printf ("\x1B[3m"); break; 188 189 case FORMAT_ITALIC_OFF: 190 printf ("\x1B[0m"); break; 191 192 case FORMAT_NONE: 193 break; 194 195 case FORMAT_H1_ON: 196 case FORMAT_H2_ON: 197 case FORMAT_H3_ON: 198 case FORMAT_H4_ON: 199 case FORMAT_H5_ON: 200 printf ("\x1B[1m"); break; 201 202 case FORMAT_H1_OFF: 203 case FORMAT_H2_OFF: 204 case FORMAT_H3_OFF: 205 case FORMAT_H4_OFF: 206 case FORMAT_H5_OFF: 207 printf ("\x1B[0m"); break; 208 209 } 210 } 211 OUT 212 } 213 214 /*============================================================================ 215 xhtml_emit_fmt_eol_pre 216 ============================================================================*/ 217 void xhtml_emit_fmt_eol_pre (WrapTextContext *context) 218 { 219 IN 220 221 unsigned int fmt = wraptext_context_get_fmt (context); 222 const Epub2TxtOptions *options = (Epub2TxtOptions *) wraptext_context_get_app_opts (context); 223 224 if (options->ansi && !options->raw && fmt) 225 { 226 /* reset ANSI escape-sequence at EOL. */ 227 xhtml_emit_format (options, FORMAT_BOLD_OFF); 228 } 229 OUT 230 } 231 232 /*============================================================================ 233 xhtml_emit_fmt_eol_post 234 ============================================================================*/ 235 void xhtml_emit_fmt_eol_post (WrapTextContext *context) 236 { 237 IN 238 239 unsigned int fmt = wraptext_context_get_fmt (context); 240 const Epub2TxtOptions *options = (Epub2TxtOptions *) wraptext_context_get_app_opts (context); 241 242 if (options->ansi && !options->raw && fmt) 243 { 244 /* turn those set, back on at BOL. */ 245 if (fmt & FMT_BOLD) 246 xhtml_emit_format (options, FORMAT_BOLD_ON); 247 if (fmt & FMT_ITAL) 248 { 249 xhtml_emit_format (options, FORMAT_ITALIC_ON); 250 } 251 } 252 OUT 253 } 254 255 /*============================================================================ 256 xhtml_set_format 257 ============================================================================*/ 258 void xhtml_set_format (const Epub2TxtOptions *options, Format format, WrapTextContext *context) 259 { 260 IN 261 262 if (options->ansi && !options->raw) 263 { 264 switch (format) 265 { 266 case FORMAT_BOLD_ON: 267 wraptext_context_set_fmt (context, FMT_BOLD); 268 break; 269 270 case FORMAT_BOLD_OFF: 271 wraptext_context_reset_fmt (context, FMT_BOLD); 272 break; 273 274 case FORMAT_ITALIC_ON: 275 wraptext_context_set_fmt (context, FMT_ITAL); 276 break; 277 278 case FORMAT_ITALIC_OFF: 279 wraptext_context_reset_fmt (context, FMT_ITAL); 280 break; 281 282 case FORMAT_NONE: 283 wraptext_context_zero_fmt (context); 284 break; 285 286 case FORMAT_H1_ON: 287 case FORMAT_H2_ON: 288 case FORMAT_H3_ON: 289 case FORMAT_H4_ON: 290 case FORMAT_H5_ON: 291 wraptext_context_set_fmt (context, FMT_BOLD); 292 break; 293 294 case FORMAT_H1_OFF: 295 case FORMAT_H2_OFF: 296 case FORMAT_H3_OFF: 297 case FORMAT_H4_OFF: 298 case FORMAT_H5_OFF: 299 wraptext_context_reset_fmt (context, FMT_BOLD); 300 break; 301 302 } 303 } 304 OUT 305 } 306 307 308 309 310 /*============================================================================ 311 xhtml_transform_char 312 ============================================================================*/ 313 WString *xhtml_transform_char (uint32_t c, BOOL to_ascii) 314 { 315 WString *ret = wstring_create_empty(); 316 if (to_ascii && c > 127) // No ASCII chars will need transforming 317 { 318 if (c == 0x00B4) return wstring_create_from_utf8 ("\'"); 319 if (c == 0x0304) return wstring_create_from_utf8 ("-"); 320 if (c == 0x2010) return wstring_create_from_utf8 ("-"); 321 if (c == 0x2013) return wstring_create_from_utf8 ("-"); 322 if (c == 0x2014) return wstring_create_from_utf8 ("-"); 323 if (c == 0x2018) return wstring_create_from_utf8 ("'"); 324 if (c == 0x2019) return wstring_create_from_utf8 ("\'"); 325 if (c == 0x201C) return wstring_create_from_utf8 ("\""); 326 if (c == 0x201D) return wstring_create_from_utf8 ("\""); 327 if (c == 0xC2A0) return wstring_create_from_utf8 ("(c)"); // copyright 328 if (c == 0x00A9) return wstring_create_from_utf8 ("(c)"); // ditto 329 if (c == 0xC2A9) return wstring_create_from_utf8 (" "); // nbsp 330 if (c == 0x00A0) return wstring_create_from_utf8 (" "); // nbsp 331 if (c == 0x2026) return wstring_create_from_utf8 (",,,"); // elipsis 332 if (c == 0x2022) return wstring_create_from_utf8 ("."); // dot 333 if (c == 0x00B5) return wstring_create_from_utf8 ("u"); // mu 334 if (c == 0x00C0) return wstring_create_from_utf8 ("A"); // accented A 335 if (c == 0x00C1) return wstring_create_from_utf8 ("A"); // accented A 336 if (c == 0x00C2) return wstring_create_from_utf8 ("A"); // accented A 337 if (c == 0x00C3) return wstring_create_from_utf8 ("A"); // accented A 338 if (c == 0x00C4) return wstring_create_from_utf8 ("A"); // accented A 339 if (c == 0x00C5) return wstring_create_from_utf8 ("A"); // accented A 340 if (c == 0x00C6) return wstring_create_from_utf8 ("AE"); // accented A 341 if (c == 0x00C7) return wstring_create_from_utf8 ("C"); // cedilla 342 if (c == 0x00C8) return wstring_create_from_utf8 ("E"); // accented E 343 if (c == 0x00C9) return wstring_create_from_utf8 ("E"); // accented E 344 if (c == 0x00CA) return wstring_create_from_utf8 ("E"); // accented E 345 if (c == 0x00CB) return wstring_create_from_utf8 ("E"); // accented E 346 if (c == 0x00CC) return wstring_create_from_utf8 ("I"); // accented I 347 if (c == 0x00CD) return wstring_create_from_utf8 ("I"); // accented I 348 if (c == 0x00CE) return wstring_create_from_utf8 ("I"); // accented I 349 if (c == 0x00CF) return wstring_create_from_utf8 ("I"); // accented I 350 if (c == 0x00D0) return wstring_create_from_utf8 ("D"); // accented D 351 if (c == 0x00D1) return wstring_create_from_utf8 ("N"); // accented N 352 if (c == 0x00D2) return wstring_create_from_utf8 ("O"); // accented O 353 if (c == 0x00D3) return wstring_create_from_utf8 ("O"); // accented O 354 if (c == 0x00D4) return wstring_create_from_utf8 ("O"); // accented O 355 if (c == 0x00D5) return wstring_create_from_utf8 ("O"); // accented O 356 if (c == 0x00D6) return wstring_create_from_utf8 ("O"); // accented O 357 if (c == 0x00D7) return wstring_create_from_utf8 ("x"); // Multiply 358 if (c == 0x00D8) return wstring_create_from_utf8 ("O"); // accented O 359 if (c == 0x00D9) return wstring_create_from_utf8 ("U"); // accented U 360 if (c == 0x00DA) return wstring_create_from_utf8 ("U"); // accented U 361 if (c == 0x00DB) return wstring_create_from_utf8 ("U"); // accented U 362 if (c == 0x00DC) return wstring_create_from_utf8 ("U"); // accented U 363 if (c == 0x00DD) return wstring_create_from_utf8 ("Y"); // accented Y 364 if (c == 0x00DE) return wstring_create_from_utf8 ("Y"); // thorn 365 if (c == 0x00DF) return wstring_create_from_utf8 ("sz"); // esszet 366 if (c == 0x00E0) return wstring_create_from_utf8 ("a"); // accepted a 367 if (c == 0x00E1) return wstring_create_from_utf8 ("a"); // accepted a 368 if (c == 0x00E2) return wstring_create_from_utf8 ("a"); // accepted a 369 if (c == 0x00E3) return wstring_create_from_utf8 ("a"); // accepted a 370 if (c == 0x00E4) return wstring_create_from_utf8 ("a"); // accepted a 371 if (c == 0x00E5) return wstring_create_from_utf8 ("a"); // accepted a 372 if (c == 0x00E6) return wstring_create_from_utf8 ("ae"); // ae 373 if (c == 0x00E7) return wstring_create_from_utf8 ("c"); // cedilla 374 if (c == 0x00E8) return wstring_create_from_utf8 ("e"); //a ceepnted e 375 if (c == 0x00E9) return wstring_create_from_utf8 ("e"); //a ceepnted e 376 if (c == 0x00EA) return wstring_create_from_utf8 ("e"); //a ceepnted e 377 if (c == 0x00EB) return wstring_create_from_utf8 ("e"); //a ceepnted e 378 if (c == 0x00EC) return wstring_create_from_utf8 ("i"); //a ceepnted i 379 if (c == 0x00ED) return wstring_create_from_utf8 ("i"); //a ceepnted i 380 if (c == 0x00EE) return wstring_create_from_utf8 ("i"); //a ceepnted i 381 if (c == 0x00EF) return wstring_create_from_utf8 ("i"); //a ceepnted i 382 if (c == 0x00F0) return wstring_create_from_utf8 ("o"); //a ceepnted o 383 if (c == 0x00F1) return wstring_create_from_utf8 ("n"); //a ceepnted n 384 if (c == 0x00F2) return wstring_create_from_utf8 ("o"); //a ceepnted o 385 if (c == 0x00F3) return wstring_create_from_utf8 ("o"); //a ceepnted o 386 if (c == 0x00F4) return wstring_create_from_utf8 ("o"); //a ceepnted o 387 if (c == 0x00F5) return wstring_create_from_utf8 ("o"); //a ceepnted o 388 if (c == 0x00F6) return wstring_create_from_utf8 ("o"); //a ceepnted o 389 if (c == 0x00F7) return wstring_create_from_utf8 ("/"); // divide 390 if (c == 0x00F8) return wstring_create_from_utf8 ("o"); //a ceepnted o 391 if (c == 0x00F9) return wstring_create_from_utf8 ("u"); //a ceepnted u 392 if (c == 0x00FA) return wstring_create_from_utf8 ("u"); //a ceepnted u 393 if (c == 0x00FB) return wstring_create_from_utf8 ("u"); //a ceepnted u 394 if (c == 0x00FC) return wstring_create_from_utf8 ("u"); //a ceepnted u 395 if (c == 0x00FD) return wstring_create_from_utf8 ("y"); //a ceepnted y 396 if (c == 0x00FE) return wstring_create_from_utf8 ("y"); //a thorn 397 if (c == 0x00FF) return wstring_create_from_utf8 ("y"); //a ceepnted y 398 if (c == 0x0100) return wstring_create_from_utf8 ("A"); //a ceepnted A 399 if (c == 0x0101) return wstring_create_from_utf8 ("a"); //a ceepnted a 400 if (c == 0x0102) return wstring_create_from_utf8 ("A"); //a ceepnted A 401 if (c == 0x0103) return wstring_create_from_utf8 ("a"); //a ceepnted a 402 if (c == 0x0104) return wstring_create_from_utf8 ("A"); //a ceepnted A 403 if (c == 0x0105) return wstring_create_from_utf8 ("a"); //a ceepnted a 404 if (c == 0x0106) return wstring_create_from_utf8 ("C"); //a ceepnted C 405 if (c == 0x0107) return wstring_create_from_utf8 ("c"); //a ceepnted c 406 if (c == 0x0108) return wstring_create_from_utf8 ("C"); //a ceepnted C 407 if (c == 0x0109) return wstring_create_from_utf8 ("c"); //a ceepnted c 408 if (c == 0x010A) return wstring_create_from_utf8 ("C"); //a ceepnted C 409 if (c == 0x010B) return wstring_create_from_utf8 ("c"); //a ceepnted c 410 if (c == 0x010C) return wstring_create_from_utf8 ("C"); //a ceepnted C 411 if (c == 0x010D) return wstring_create_from_utf8 ("c"); //a ceepnted c 412 if (c == 0x010E) return wstring_create_from_utf8 ("D"); //a ceepnted D 413 if (c == 0x010F) return wstring_create_from_utf8 ("d"); //a ceepnted d 414 if (c == 0x0110) return wstring_create_from_utf8 ("D"); //a ceepnted D 415 if (c == 0x0111) return wstring_create_from_utf8 ("d"); //a ceepnted d 416 if (c == 0x0112) return wstring_create_from_utf8 ("E"); //a ceepnted E 417 if (c == 0x0113) return wstring_create_from_utf8 ("e"); //a ceepnted e 418 if (c == 0x0114) return wstring_create_from_utf8 ("E"); //a ceepnted E 419 if (c == 0x0115) return wstring_create_from_utf8 ("e"); //a ceepnted e 420 if (c == 0x0116) return wstring_create_from_utf8 ("E"); //a ceepnted E 421 if (c == 0x0117) return wstring_create_from_utf8 ("e"); //a ceepnted e 422 if (c == 0x0118) return wstring_create_from_utf8 ("E"); //a ceepnted E 423 if (c == 0x0119) return wstring_create_from_utf8 ("e"); //a ceepnted e 424 if (c == 0x011A) return wstring_create_from_utf8 ("E"); //a ceepnted E 425 if (c == 0x011B) return wstring_create_from_utf8 ("e"); //a ceepnted e 426 if (c == 0x011C) return wstring_create_from_utf8 ("G"); //a ceepnted G 427 if (c == 0x011D) return wstring_create_from_utf8 ("g"); //a ceepnted g 428 if (c == 0x011E) return wstring_create_from_utf8 ("G"); //a ceepnted G 429 if (c == 0x011F) return wstring_create_from_utf8 ("g"); //a ceepnted g 430 if (c == 0x0120) return wstring_create_from_utf8 ("G"); //a ceepnted G 431 if (c == 0x0121) return wstring_create_from_utf8 ("g"); //a ceepnted g 432 if (c == 0x0122) return wstring_create_from_utf8 ("G"); //a ceepnted G 433 if (c == 0x0123) return wstring_create_from_utf8 ("g"); //a ceepnted g 434 if (c == 0x0124) return wstring_create_from_utf8 ("H"); //a ceepnted H 435 if (c == 0x0125) return wstring_create_from_utf8 ("h"); //a ceepnted h 436 if (c == 0x0126) return wstring_create_from_utf8 ("H"); //a ceepnted H 437 if (c == 0x0127) return wstring_create_from_utf8 ("h"); //a ceepnted h 438 if (c == 0x0128) return wstring_create_from_utf8 ("I"); //a ceepnted I 439 if (c == 0x0129) return wstring_create_from_utf8 ("i"); //a ceepnted i 440 if (c == 0x012A) return wstring_create_from_utf8 ("I"); //a ceepnted I 441 if (c == 0x012B) return wstring_create_from_utf8 ("i"); //a ceepnted i 442 if (c == 0x012C) return wstring_create_from_utf8 ("I"); //a ceepnted I 443 if (c == 0x012D) return wstring_create_from_utf8 ("i"); //a ceepnted i 444 if (c == 0x012E) return wstring_create_from_utf8 ("I"); //a ceepnted I 445 if (c == 0x012F) return wstring_create_from_utf8 ("i"); //a ceepnted i 446 if (c == 0x0130) return wstring_create_from_utf8 ("I"); //a ceepnted I 447 if (c == 0x0131) return wstring_create_from_utf8 ("i"); //a ceepnted i 448 if (c == 0x0132) return wstring_create_from_utf8 ("IJ"); 449 if (c == 0x0133) return wstring_create_from_utf8 ("ij"); 450 if (c == 0x0134) return wstring_create_from_utf8 ("J"); //a ceepnted J 451 if (c == 0x0135) return wstring_create_from_utf8 ("j"); //a ceepnted j 452 if (c == 0x0136) return wstring_create_from_utf8 ("K"); //a ceepnted K 453 if (c == 0x0138) return wstring_create_from_utf8 ("K"); //a ceepnted K 454 if (c == 0x0138) return wstring_create_from_utf8 ("k"); //a ceepnted k 455 if (c == 0x0139) return wstring_create_from_utf8 ("L"); //a ceepnted L 456 if (c == 0x013A) return wstring_create_from_utf8 ("l"); //a ceepnted l 457 if (c == 0x013B) return wstring_create_from_utf8 ("L"); //a ceepnted L 458 if (c == 0x013C) return wstring_create_from_utf8 ("l"); //a ceepnted l 459 if (c == 0x013D) return wstring_create_from_utf8 ("L"); //a ceepnted L 460 if (c == 0x013E) return wstring_create_from_utf8 ("l"); //a ceepnted l 461 if (c == 0x013F) return wstring_create_from_utf8 ("L"); //a ceepnted L 462 if (c == 0x0140) return wstring_create_from_utf8 ("l"); //a ceepnted l 463 if (c == 0x0141) return wstring_create_from_utf8 ("L"); //a ceepnted L 464 if (c == 0x0142) return wstring_create_from_utf8 ("l"); //a ceepnted l 465 if (c == 0x0143) return wstring_create_from_utf8 ("N"); //a ceepnted N 466 if (c == 0x0144) return wstring_create_from_utf8 ("n"); //a ceepnted N 467 if (c == 0x0145) return wstring_create_from_utf8 ("N"); //a ceepnted N 468 if (c == 0x0146) return wstring_create_from_utf8 ("n"); //a ceepnted N 469 if (c == 0x0147) return wstring_create_from_utf8 ("N"); //a ceepnted N 470 if (c == 0x0148) return wstring_create_from_utf8 ("n"); //a ceepnted N 471 if (c == 0x0149) return wstring_create_from_utf8 ("N"); //a ceepnted N 472 if (c == 0x014A) return wstring_create_from_utf8 ("n"); //a ceepnted N 473 if (c == 0x014B) return wstring_create_from_utf8 ("n"); //a ceepnted n 474 if (c == 0x014C) return wstring_create_from_utf8 ("O"); //a ceepnted O 475 if (c == 0x014D) return wstring_create_from_utf8 ("o"); //a ceepnted o 476 if (c == 0x014E) return wstring_create_from_utf8 ("O"); //a ceepnted O 477 if (c == 0x014F) return wstring_create_from_utf8 ("o"); //a ceepnted o 478 if (c == 0x0150) return wstring_create_from_utf8 ("O"); //a ceepnted O 479 if (c == 0x0151) return wstring_create_from_utf8 ("o"); //a ceepnted o 480 if (c == 0x0152) return wstring_create_from_utf8 ("OE"); 481 if (c == 0x0153) return wstring_create_from_utf8 ("oe"); 482 if (c == 0x0154) return wstring_create_from_utf8 ("R"); // accepted R 483 if (c == 0x0155) return wstring_create_from_utf8 ("r"); // accepted r 484 if (c == 0x0156) return wstring_create_from_utf8 ("R"); // accepted R 485 if (c == 0x0157) return wstring_create_from_utf8 ("r"); // accepted r 486 if (c == 0x0158) return wstring_create_from_utf8 ("R"); // accepted R 487 if (c == 0x0159) return wstring_create_from_utf8 ("r"); // accepted r 488 if (c == 0x015A) return wstring_create_from_utf8 ("S"); // accepted S 489 if (c == 0x015B) return wstring_create_from_utf8 ("s"); // accepted s 490 if (c == 0x015C) return wstring_create_from_utf8 ("S"); // accepted S 491 if (c == 0x015D) return wstring_create_from_utf8 ("s"); // accepted s 492 if (c == 0x015E) return wstring_create_from_utf8 ("S"); // accepted S 493 if (c == 0x015F) return wstring_create_from_utf8 ("s"); // accepted s 494 if (c == 0x0160) return wstring_create_from_utf8 ("S"); // accepted S 495 if (c == 0x0161) return wstring_create_from_utf8 ("s"); // accepted s 496 if (c == 0x0162) return wstring_create_from_utf8 ("T"); // accepted T 497 if (c == 0x0163) return wstring_create_from_utf8 ("t"); // accepted t 498 if (c == 0x0164) return wstring_create_from_utf8 ("T"); // accepted T 499 if (c == 0x0165) return wstring_create_from_utf8 ("t"); // accepted t 500 if (c == 0x0166) return wstring_create_from_utf8 ("T"); // accepted T 501 if (c == 0x0167) return wstring_create_from_utf8 ("t"); // accepted t 502 if (c == 0x0168) return wstring_create_from_utf8 ("U"); // accepted U 503 if (c == 0x0169) return wstring_create_from_utf8 ("u"); // accepted u 504 if (c == 0x016A) return wstring_create_from_utf8 ("U"); // accepted U 505 if (c == 0x016B) return wstring_create_from_utf8 ("u"); // accepted u 506 if (c == 0x016C) return wstring_create_from_utf8 ("U"); // accepted U 507 if (c == 0x016D) return wstring_create_from_utf8 ("u"); // accepted u 508 if (c == 0x016E) return wstring_create_from_utf8 ("U"); // accepted U 509 if (c == 0x016F) return wstring_create_from_utf8 ("u"); // accepted u 510 if (c == 0x0170) return wstring_create_from_utf8 ("U"); // accepted U 511 if (c == 0x0171) return wstring_create_from_utf8 ("u"); // accepted u 512 if (c == 0x0172) return wstring_create_from_utf8 ("U"); // accepted U 513 if (c == 0x0173) return wstring_create_from_utf8 ("u"); // accepted u 514 if (c == 0x0174) return wstring_create_from_utf8 ("W"); // accepted W 515 if (c == 0x0175) return wstring_create_from_utf8 ("w"); // accepted w 516 if (c == 0x0176) return wstring_create_from_utf8 ("Y"); // accepted Y 517 if (c == 0x0177) return wstring_create_from_utf8 ("y"); // accepted y 518 if (c == 0x0178) return wstring_create_from_utf8 ("Y"); // accepted Y 519 if (c == 0x00) return wstring_create_from_utf8 (""); // 520 wstring_append_c (ret, c); 521 } 522 else 523 wstring_append_c (ret, c); 524 return ret; 525 } 526 527 528 /*============================================================================ 529 xhtml_translate_entity 530 ============================================================================*/ 531 WString *xhtml_translate_entity (const WString *entity) 532 { 533 /* Program flow in this function is very ugly, and prone to memory 534 leaks when modified. The whole thing needs to be rewritten */ 535 char out[20]; 536 IN 537 char *in = wstring_to_utf8 (entity); 538 if (strcasecmp (in, "amp") == 0) 539 strcpy (out, "&"); 540 else if (strcasecmp (in, "nbsp") == 0) 541 strcpy (out, " "); 542 else if (strcasecmp (in, "lt") == 0) 543 strcpy (out, "<"); 544 else if (strcasecmp (in, "gt") == 0) 545 strcpy (out, ">"); 546 else if (strcasecmp (in, "cent") == 0) 547 strcpy (out, "¢"); 548 else if (strcasecmp (in, "pound") == 0) 549 strcpy (out, "£"); 550 else if (strcasecmp (in, "yen") == 0) 551 strcpy (out, "£"); 552 else if (strcasecmp (in, "euro") == 0) 553 strcpy (out, "€"); 554 else if (strcasecmp (in, "sect") == 0) 555 strcpy (out, "§"); 556 else if (strcasecmp (in, "copy") == 0) 557 strcpy (out, "©"); 558 else if (strcasecmp (in, "reg") == 0) 559 strcpy (out, "®"); 560 else if (strcasecmp (in, "trade") == 0) 561 strcpy (out, "™"); 562 else if (strcasecmp (in, "quot") == 0) 563 strcpy (out, "\""); 564 else if (in[0] == '#') 565 { 566 char *s = strdup (in); 567 s[0] = '0'; 568 int v = 0; 569 if (sscanf (s, "%d", &v) == 1) 570 { 571 WString *ret = wstring_create_empty(); 572 wstring_append_c (ret, (uint32_t)v); 573 OUT 574 free (s); 575 free (in); 576 return ret; 577 } 578 free (s); 579 } 580 else 581 { 582 strncpy (out, in, sizeof (out) - 1); 583 out[sizeof (out) - 1] = 0; 584 } 585 free (in); 586 OUT 587 return wstring_create_from_utf8 (out); 588 } 589 590 591 /*============================================================================ 592 xhtml_flush_line 593 ============================================================================*/ 594 void xhtml_flush_line (const WString *para, const Epub2TxtOptions *options, 595 WrapTextContext *context) 596 { 597 IN 598 599 if (options->raw) 600 { 601 char *s = wstring_to_utf8 (para); 602 fputs (s, stdout); 603 free (s); 604 } 605 else 606 { 607 wraptext_wrap_utf32 (context, wstring_wstr (para)); 608 wraptext_eof (context); 609 } 610 611 OUT 612 } 613 614 615 /*============================================================================ 616 xhtml_flush_para 617 ============================================================================*/ 618 void xhtml_flush_para (const WString *para, const Epub2TxtOptions *options, 619 WrapTextContext *context) 620 { 621 IN 622 623 xhtml_flush_line (para, options, context); 624 625 OUT 626 } 627 628 629 /*============================================================================ 630 xhtml_line_break 631 ============================================================================*/ 632 void xhtml_line_break (WrapTextContext *context) 633 { 634 IN 635 //static uint32_t s[2] = { '\n', 0 }; 636 static uint32_t s[2] = { WT_HARD_LINE_BREAK, 0 }; 637 wraptext_wrap_utf32 (context, s); 638 wraptext_eof (context); 639 OUT 640 } 641 642 643 /*============================================================================ 644 xhtml_para_break 645 ============================================================================*/ 646 void xhtml_para_break (WrapTextContext *context, 647 const Epub2TxtOptions *options) 648 { 649 IN 650 static uint32_t s[3] = { '\n', '\n', 0 }; 651 if (options->raw) 652 { 653 printf ("\n\n"); 654 } 655 else 656 { 657 wraptext_wrap_utf32 (context, s); 658 } 659 OUT 660 } 661 662 663 /*============================================================================ 664 xhtml_all_white 665 Note that, for the purpses of application logic, an empty string 666 is considered to be whitespace 667 ============================================================================*/ 668 BOOL xhtml_all_white (WString *s) 669 { 670 if (wstring_length (s) == 0) return TRUE; 671 return wstring_is_whitespace (s); 672 } 673 674 675 /*============================================================================ 676 xhtml_utf8_to_stdout 677 ============================================================================*/ 678 void xhtml_utf8_to_stdout (const char *s, const Epub2TxtOptions *options, 679 char **error) 680 { 681 IN 682 char *ss; 683 // This is all a bit ugly. The entity translation is in 684 // xhtml_to_stdout, which expects something that looks like a viable 685 // XHTML file. There's no guarantee that the input to this function 686 // will actually be a full XHTML file, so we must wrap it in a body 687 // to fool xhtml_to_stdout. Ugh. 688 asprintf (&ss, "%s", s); 689 WString *sw = wstring_create_from_utf8 (ss); 690 xhtml_to_stdout (sw, options, error); 691 wstring_destroy (sw); 692 free (ss); 693 OUT 694 } 695 696 /*============================================================================ 697 xhtml_file_to_stdout 698 ============================================================================*/ 699 void xhtml_file_to_stdout (const char *filename, const Epub2TxtOptions *options, 700 char **error) 701 { 702 IN 703 log_debug ("Process XHTML file %s", filename); 704 705 WString *s; 706 wstring_create_from_utf8_file (filename, &s, error); 707 if (*error == NULL) 708 { 709 xhtml_to_stdout (s, options, error); 710 wstring_destroy (s); 711 } 712 713 OUT 714 } 715 716 /*============================================================================ 717 xhtml_to_stdout 718 ============================================================================*/ 719 void xhtml_to_stdout (const WString *s, const Epub2TxtOptions *options, 720 char **error) 721 { 722 IN 723 log_debug ("Process XHTML string"); 724 725 typedef enum {MODE_ANY=0, MODE_INTAG = 1, MODE_ENTITY = 2} Mode; 726 727 if (TRUE) 728 { 729 int width; 730 if (options->width <= 0) 731 width = INT_MAX; 732 else 733 width = options->width - 1; 734 735 WrapTextContext *context = wraptext_context_new(); 736 wraptext_context_set_width (context, width); 737 wraptext_context_set_app_opts (context, (void *)options); 738 739 Mode mode = MODE_ANY; 740 BOOL inbody = FALSE; 741 BOOL can_newline = FALSE; 742 WString *tag = wstring_create_empty(); 743 WString *entity = wstring_create_empty(); 744 WString *para = wstring_create_empty(); 745 WString *ruby = wstring_create_empty(); 746 BOOL inruby = FALSE; 747 int i, l = wstring_length (s); 748 uint32_t last_c = 0; 749 int taglen = 0; 750 751 const uint32_t *text = wstring_wstr (s); 752 for (i = 0; i < l; i++) 753 { 754 uint32_t c = text[i]; 755 if (c == 13) // DOS EOL 756 continue; 757 758 if (c == 9) // Tab 759 c = ' '; 760 761 //printf ("c=%c %04x\n", (char)c, c); 762 if (mode == MODE_ANY && c == '<') 763 { 764 taglen = 0; 765 mode = MODE_INTAG; 766 } 767 else if (mode == MODE_ANY && c == '\n') 768 { 769 if (inbody) 770 { 771 if (last_c != ' ') 772 { 773 wstring_append_c (para, ' '); 774 } 775 } 776 } 777 else if (mode == MODE_ANY && c == '&') 778 { 779 mode = MODE_ENTITY; 780 } 781 else if (mode == MODE_ANY) 782 { 783 if (inbody) 784 { 785 if (c == ' ' && last_c == ' ') 786 { 787 } 788 else 789 { 790 WString *s = xhtml_transform_char (c, options->ascii); 791 wstring_append (inruby ? ruby : para, s); 792 wstring_destroy (s); 793 } 794 } 795 } 796 else if (mode == MODE_ENTITY && c == ';') 797 { 798 if (inbody) 799 { 800 WString *trans = xhtml_translate_entity (entity); 801 wstring_append (inruby ? ruby : para, trans); 802 wstring_destroy (trans); 803 } 804 wstring_clear (entity); 805 mode = MODE_ANY; 806 } 807 else if (mode == MODE_ENTITY) 808 { 809 wstring_append_c (entity, c); 810 } 811 else if (mode == MODE_INTAG && c == '>') 812 { 813 taglen = 0; 814 Format format = FORMAT_NONE; 815 char *ss_tag = wstring_to_utf8 (tag); 816 char *p = strchr (ss_tag, ' '); 817 if (p) *p = 0; 818 if (strcasecmp (ss_tag, "body") == 0) 819 { 820 inbody = TRUE; 821 } 822 else if (strcasecmp (ss_tag, "/body") == 0) 823 { 824 if (xhtml_all_white (para)) 825 can_newline = FALSE; 826 else 827 can_newline = TRUE; 828 xhtml_flush_para (para, options, context); 829 wstring_clear (para); 830 if (can_newline) 831 { 832 xhtml_para_break (context, options); 833 can_newline = FALSE; 834 } 835 inbody = FALSE; 836 } 837 else if ((strcasecmp (ss_tag, "p/") == 0) 838 || (strcasecmp (ss_tag, "/p") == 0)) 839 { 840 if (inbody) 841 { 842 if (xhtml_all_white (para)) 843 can_newline = FALSE; 844 else 845 { 846 can_newline = TRUE; 847 } 848 xhtml_flush_para (para, options, context); 849 wstring_clear (para); 850 if (can_newline) 851 { 852 xhtml_para_break (context, options); 853 can_newline = FALSE; 854 } 855 } 856 } 857 else if ((strcasecmp (ss_tag, "br/") == 0) 858 || (strcasecmp (ss_tag, "br") == 0) 859 || (strcasecmp (ss_tag, "br /") == 0)) 860 { 861 if (inbody) 862 { 863 if (xhtml_all_white (para)) 864 can_newline = FALSE; 865 else 866 can_newline = TRUE; 867 xhtml_flush_para (para, options, context); 868 wstring_clear (para); 869 if (can_newline) 870 { 871 xhtml_line_break (context); 872 can_newline = FALSE; 873 } 874 } 875 } 876 else if (xhtml_is_start_format_tag (ss_tag, &format)) 877 { 878 if (inbody) 879 { 880 xhtml_flush_line (para, options, context); 881 wstring_clear (para); 882 xhtml_emit_format (options, format); 883 xhtml_set_format (options, format, context); 884 } 885 } 886 else if (xhtml_is_end_format_tag (ss_tag, &format)) 887 { 888 if (inbody) 889 { 890 xhtml_flush_line (para, options, context); 891 xhtml_emit_format (options, format); 892 xhtml_set_format (options, format, context); 893 wstring_clear (para); 894 } 895 } 896 else if (xhtml_is_end_breaking_tag (ss_tag, &format)) 897 { 898 xhtml_flush_line (para, options, context); 899 xhtml_emit_format (options, format); 900 xhtml_set_format (options, format, context); 901 wstring_clear (para); 902 xhtml_para_break (context, options); 903 } 904 905 else if (xhtml_is_start_breaking_tag (ss_tag, &format)) 906 { 907 xhtml_flush_line (para, options, context); 908 wstring_clear (para); 909 xhtml_emit_format (options, format); 910 xhtml_set_format (options, format, context); 911 } 912 913 else if (strcasecmp(ss_tag, "ruby") == 0) 914 { 915 wstring_clear (ruby); 916 } 917 else if (strcasecmp(ss_tag, "/ruby") == 0) 918 { 919 // Append concatenated ruby annotations 920 wstring_append_c (para, '('); 921 wstring_append (para, ruby); 922 wstring_append_c (para, ')'); 923 wstring_clear (ruby); 924 } 925 else if (strcasecmp(ss_tag, "rt") == 0) 926 { 927 // Start accumulating ruby annotations 928 inruby = TRUE; 929 } 930 else if (strcasecmp(ss_tag, "/rt") == 0) 931 { 932 inruby = FALSE; 933 } 934 935 free (ss_tag); 936 wstring_clear (tag); 937 mode = MODE_ANY; 938 } 939 else if (mode == MODE_INTAG) 940 { 941 taglen++; 942 // Bug #5 -- Added support to abort tag reading if tag > 1000 943 // characters. This is an arbitrary number, but it's larger than 944 // any tag that we can handle. 945 if (taglen > 1000) 946 { 947 while (i < l) 948 { 949 uint32_t c = text[i]; 950 if (c == (uint32_t)'>') 951 { 952 wstring_clear (tag); 953 } 954 i++; 955 } 956 } 957 wstring_append_c (tag, c); 958 } 959 else 960 log_error ("Unexpected character %d in mode %d", c, mode); 961 last_c = c; 962 } 963 if (wstring_length (para) > 0) 964 xhtml_flush_para (para, options, context); 965 966 wstring_destroy (tag); 967 wstring_destroy (entity); 968 wstring_destroy (para); 969 wstring_destroy (ruby); 970 971 wraptext_eof (context); 972 wraptext_context_free (context); 973 } 974 OUT 975 } 976 977 978 979 980 981 982