2c38e58d0cf2f47af4bf375994dfc327832ee0e5
[Sone.git] / src / main / kotlin / net / pterodactylus / sone / template / RenderFilter.kt
1 package net.pterodactylus.sone.template
2
3 import net.pterodactylus.sone.core.Core
4 import net.pterodactylus.sone.text.FreemailPart
5 import net.pterodactylus.sone.text.FreenetLinkPart
6 import net.pterodactylus.sone.text.LinkPart
7 import net.pterodactylus.sone.text.Part
8 import net.pterodactylus.sone.text.PlainTextPart
9 import net.pterodactylus.sone.text.PostPart
10 import net.pterodactylus.sone.text.SonePart
11 import net.pterodactylus.sone.text.SoneTextParser
12 import net.pterodactylus.sone.text.SoneTextParserContext
13 import net.pterodactylus.util.template.Filter
14 import net.pterodactylus.util.template.TemplateContext
15 import net.pterodactylus.util.template.TemplateContextFactory
16 import net.pterodactylus.util.template.TemplateParser
17 import java.io.StringReader
18 import java.io.StringWriter
19 import java.io.Writer
20 import java.net.URLEncoder
21 import java.util.ArrayList
22
23 /**
24  * Renders a number of pre-parsed [Part] into a [String].
25  *
26  * @author [David ‘Bombe’ Roden](mailto:bombe@pterodactylus.net)
27  */
28 class RenderFilter(private val core: Core, private val templateContextFactory: TemplateContextFactory) : Filter {
29
30         companion object {
31                 private val plainTextTemplate = TemplateParser.parse(StringReader("<%text|html>"))
32                 private val linkTemplate = TemplateParser.parse(StringReader("<a class=\"<%cssClass|html>\" href=\"<%link|html>\" title=\"<%title|html>\"><%text|html></a>"))
33         }
34
35         override fun format(templateContext: TemplateContext?, data: Any?, parameters: MutableMap<String, Any?>?): Any? {
36                 @Suppress("UNCHECKED_CAST")
37                 val parts = getPartsToRender(parameters, data as? Iterable<Part> ?: return null)
38                 val parsedTextWriter = StringWriter()
39                 render(parsedTextWriter, parts)
40                 return parsedTextWriter.toString()
41         }
42
43         private fun Map<String, Any?>.parseInt(key: String) = this[key]?.toString()?.toInt()
44
45         private fun getPartsToRender(parameters: MutableMap<String, Any?>?, parts: Iterable<Part>): Iterable<Part> {
46                 val length = parameters?.parseInt("length") ?: -1
47                 val cutOffLength = parameters?.parseInt("cut-off-length") ?: length
48                 if (length > -1) {
49                         var allPartsLength = 0
50                         val shortenedParts = ArrayList<Part>()
51                         for (part in parts) {
52                                 if (part is PlainTextPart) {
53                                         val longText = part.text
54                                         if (allPartsLength < cutOffLength) {
55                                                 if (allPartsLength + longText.length > cutOffLength) {
56                                                         shortenedParts.add(PlainTextPart(longText.substring(0, cutOffLength - allPartsLength) + "…"))
57                                                 } else {
58                                                         shortenedParts.add(part)
59                                                 }
60                                         }
61                                         allPartsLength += longText.length
62                                 } else if (part is LinkPart) {
63                                         if (allPartsLength < cutOffLength) {
64                                                 shortenedParts.add(part)
65                                         }
66                                         allPartsLength += part.text.length
67                                 } else {
68                                         if (allPartsLength < cutOffLength) {
69                                                 shortenedParts.add(part)
70                                         }
71                                 }
72                         }
73                         if (allPartsLength >= length) {
74                                 return shortenedParts
75                         }
76                 }
77                 return parts
78         }
79
80         private fun render(writer: Writer, parts: Iterable<Part>) {
81                 parts.forEach { render(writer, it) }
82         }
83
84         private fun render(writer: Writer, part: Part) {
85                 @Suppress("UNCHECKED_CAST")
86                 when (part) {
87                         is PlainTextPart -> render(writer, part)
88                         is FreenetLinkPart -> render(writer, part)
89                         is LinkPart -> render(writer, part)
90                         is SonePart -> render(writer, part)
91                         is PostPart -> render(writer, part)
92                         is FreemailPart -> render(writer, part)
93                         is Iterable<*> -> render(writer, part as Iterable<Part>)
94                 }
95         }
96
97         private fun render(writer: Writer, plainTextPart: PlainTextPart) {
98                 val templateContext = templateContextFactory.createTemplateContext()
99                 templateContext.set("text", plainTextPart.text)
100                 plainTextTemplate.render(templateContext, writer)
101         }
102
103         private fun render(writer: Writer, freenetLinkPart: FreenetLinkPart) {
104                 renderLink(writer, "/${freenetLinkPart.link}", freenetLinkPart.text, freenetLinkPart.title, if (freenetLinkPart.isTrusted) "freenet-trusted" else "freenet")
105         }
106
107         private fun render(writer: Writer, linkPart: LinkPart) {
108                 renderLink(writer, "/external-link/?_CHECKED_HTTP_=${linkPart.link.urlEncode()}", linkPart.text, linkPart.title, "internet")
109         }
110
111         private fun String.urlEncode() = URLEncoder.encode(this, "UTF-8")!!
112
113         private fun render(writer: Writer, sonePart: SonePart) {
114                 if (sonePart.sone.name != null) {
115                         renderLink(writer, "viewSone.html?sone=${sonePart.sone.id}", SoneAccessor.getNiceName(sonePart.sone), SoneAccessor.getNiceName(sonePart.sone), "in-sone")
116                 } else {
117                         renderLink(writer, "/WebOfTrust/ShowIdentity?id=${sonePart.sone.id}", sonePart.sone.id, sonePart.sone.id, "in-sone")
118                 }
119         }
120
121         private fun render(writer: Writer, postPart: PostPart) {
122                 val parser = SoneTextParser(core, core)
123                 val parserContext = SoneTextParserContext(postPart.post.sone)
124                 val parts = parser.parse(postPart.post.text, parserContext)
125                 val excerpt = StringBuilder()
126                 for (part in parts) {
127                         excerpt.append(part.text)
128                         if (excerpt.length > 20) {
129                                 val lastSpace = excerpt.lastIndexOf(" ", 20)
130                                 if (lastSpace > -1) {
131                                         excerpt.setLength(lastSpace)
132                                 } else {
133                                         excerpt.setLength(20)
134                                 }
135                                 excerpt.append("…")
136                                 break
137                         }
138                 }
139                 renderLink(writer, "viewPost.html?post=${postPart.post.id}", excerpt.toString(), SoneAccessor.getNiceName(postPart.post.sone), "in-sone")
140         }
141
142         private fun render(writer: Writer, freemailPart: FreemailPart) {
143                 val sone = core.getSone(freemailPart.identityId)
144                 val soneName = sone.transform(SoneAccessor::getNiceName).or(freemailPart.identityId)
145                 renderLink(writer,
146                                 "/Freemail/NewMessage?to=${freemailPart.identityId}",
147                                 "${freemailPart.emailLocalPart}@$soneName.freemail",
148                                 "$soneName\n${freemailPart.emailLocalPart}@${freemailPart.freemailId}.freemail",
149                                 "in-sone")
150         }
151
152         private fun renderLink(writer: Writer, link: String, text: String, title: String, cssClass: String) {
153                 val templateContext = templateContextFactory.createTemplateContext()
154                 templateContext["cssClass"] = cssClass
155                 templateContext["link"] = link
156                 templateContext["text"] = text
157                 templateContext["title"] = title
158                 linkTemplate.render(templateContext, writer)
159         }
160
161 }