Mercurial > cgi-bin > hgweb.cgi > ClipMan
comparison src/name/blackcap/clipman/RtfToHtml.kt @ 0:be282c48010a
Incomplete; checking it in as a backup.
author | David Barts <n5jrn@me.com> |
---|---|
date | Tue, 14 Jan 2020 14:07:19 -0800 |
parents | |
children | 8aa2dfac27eb |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:be282c48010a |
---|---|
1 /* | |
2 * Because Java (and by implication Kotlin) sucks at processing RTF data, | |
3 * we deal with such data by invoking an external program to convert it | |
4 * to HTML. | |
5 */ | |
6 package name.blackcap.clipman | |
7 | |
8 import java.io.BufferedInputStream | |
9 import java.io.BufferedOutputStream | |
10 import java.io.ByteArrayOutputStream | |
11 import java.io.IOException | |
12 import java.io.InputStream | |
13 import java.io.OutputStream | |
14 import java.io.UnsupportedEncodingException | |
15 | |
16 private val RTF_CHARSET_NAME = "UTF-8" | |
17 private val LANG = "en_US." + RTF_CHARSET_NAME | |
18 private val UNRTF = System.getenv("UNRTF") | |
19 | |
20 private class Consumer(val source: InputStream): Thread() { | |
21 val target = ByteArrayOutputStream() | |
22 | |
23 override fun run() { | |
24 source.use { it.copyTo(target) } | |
25 } | |
26 | |
27 val output: String | |
28 @Synchronized get() { | |
29 if (isAlive()) { | |
30 throw IllegalThreadStateException("consumer not finished!") | |
31 } else { | |
32 return target.toString(RTF_CHARSET_NAME) | |
33 } | |
34 } | |
35 } | |
36 | |
37 /** | |
38 * Convert an InputStream of RTF bytes to a String containing an HTML | |
39 * document. | |
40 * @param rtfStream stream containing the RTF document | |
41 * @return a Pair. On success, the first element contains the HTML and | |
42 * the second is null. On failure, the first is null and the | |
43 * second contains an error message. | |
44 */ | |
45 public fun rtfToHtml(rtfStream: InputStream): Pair<String?, String?> { | |
46 if (OS.type == OS.MAC && UNRTF == null) { | |
47 return _rtfToHtml(rtfStream, ProcessBuilder("textutil", "-format", | |
48 "rtf", "-convert", "html", "-stdin", "-stdout")) | |
49 } else { | |
50 return _rtfToHtml(rtfStream, ProcessBuilder( | |
51 if (UNRTF == null) { "unrtf" } else { UNRTF }, | |
52 "--html", "--nopict")) | |
53 } | |
54 } | |
55 | |
56 private fun _rtfToHtml(rtfStream: InputStream, pb: ProcessBuilder): Pair<String?, String?> { | |
57 var job: Process? = null | |
58 try { | |
59 /* set the Posix locale to force UTF-8 I/O */ | |
60 pb.environment().run { | |
61 put("LANG", LANG) | |
62 put("LC_ALL", LANG) | |
63 } | |
64 | |
65 /* start the process */ | |
66 job = pb.start() | |
67 | |
68 /* start consuming its output and error streams */ | |
69 val outputConsumer = Consumer(job.inputStream).apply { start() } | |
70 val errorConsumer = Consumer(job.errorStream).apply { start() } | |
71 | |
72 /* feed it input */ | |
73 job.outputStream.use { rtfStream.copyTo(it) } | |
74 | |
75 /* wait for it to exit */ | |
76 val exitStatus = job.waitFor() | |
77 | |
78 /* after it exits, wait for our data consumers to exit */ | |
79 outputConsumer.join(); | |
80 errorConsumer.join(); | |
81 | |
82 /* if it barfed, return an error, else return the HTML */ | |
83 if (exitStatus != 0) { | |
84 val errors = errorConsumer.output | |
85 if (errors.isEmpty()) { | |
86 return Pair(null, "converter exited with status " + exitStatus) | |
87 } else { | |
88 return Pair(null, errors) | |
89 } | |
90 } | |
91 return Pair(outputConsumer.output, null) | |
92 } catch (e: IOException) { | |
93 return barfed(e) | |
94 } catch (e: InterruptedException) { | |
95 if (job != null && job.isAlive()) { | |
96 job.destroy() | |
97 job.waitFor() | |
98 } | |
99 return barfed(e) | |
100 } | |
101 } | |
102 | |
103 private fun barfed(e: Exception): Pair<String?, String?> { | |
104 val sb = StringBuilder(e::class.simpleName) | |
105 val message = e.message ?: "" | |
106 if (!message.isEmpty()) { | |
107 sb.append(": ") | |
108 sb.append(e.message) | |
109 } | |
110 return Pair(null, sb.toString()) | |
111 } |