Split text parsing and rendering into two filters
[Sone.git] / src / main / java / net / pterodactylus / sone / template / RenderFilter.java
1 package net.pterodactylus.sone.template;
2
3 import static java.lang.String.valueOf;
4 import static net.pterodactylus.sone.utils.NumberParsers.parseInt;
5
6 import java.io.IOException;
7 import java.io.StringReader;
8 import java.io.StringWriter;
9 import java.io.UnsupportedEncodingException;
10 import java.io.Writer;
11 import java.net.URLEncoder;
12 import java.util.ArrayList;
13 import java.util.List;
14 import java.util.Map;
15
16 import net.pterodactylus.sone.core.Core;
17 import net.pterodactylus.sone.data.Album;
18 import net.pterodactylus.sone.data.Sone;
19 import net.pterodactylus.sone.text.AlbumPart;
20 import net.pterodactylus.sone.text.FreenetLinkPart;
21 import net.pterodactylus.sone.text.LinkPart;
22 import net.pterodactylus.sone.text.Part;
23 import net.pterodactylus.sone.text.PartContainer;
24 import net.pterodactylus.sone.text.PlainTextPart;
25 import net.pterodactylus.sone.text.PostPart;
26 import net.pterodactylus.sone.text.SonePart;
27 import net.pterodactylus.sone.text.SoneTextParser;
28 import net.pterodactylus.sone.text.SoneTextParserContext;
29 import net.pterodactylus.util.template.Filter;
30 import net.pterodactylus.util.template.Template;
31 import net.pterodactylus.util.template.TemplateContext;
32 import net.pterodactylus.util.template.TemplateContextFactory;
33 import net.pterodactylus.util.template.TemplateParser;
34
35 /**
36  * {@link Filter} implementation that renders an {@link Iterable} (such as a {@link PartContainer}) of {@link Part}s to HTML.
37  *
38  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
39  */
40 public class RenderFilter implements Filter {
41
42         private static final Template plainTextTemplate = TemplateParser.parse(new StringReader("<%text|html>"));
43         private static final Template linkTemplate =
44                         TemplateParser.parse(new StringReader("<a class=\"<%cssClass|html>\" href=\"<%link|html>\" title=\"<%title|html>\"><%text|html></a>"));
45         private final Core core;
46         private final TemplateContextFactory templateContextFactory;
47
48         public RenderFilter(Core core, TemplateContextFactory templateContextFactory) {
49                 this.core = core;
50                 this.templateContextFactory = templateContextFactory;
51         }
52
53         @Override
54         public Object format(TemplateContext templateContext, Object data, Map<String, Object> parameters) {
55                 if (!(data instanceof Iterable<?>)) {
56                         return null;
57                 }
58                 Iterable<Part> parts = (Iterable<Part>) data;
59                 int length = parseInt(valueOf(parameters.get("length")), -1);
60                 int cutOffLength = parseInt(valueOf(parameters.get("cut-off-length")), length);
61                 StringWriter parsedTextWriter = new StringWriter();
62                 if (length > -1) {
63                         int allPartsLength = 0;
64                         List<Part> shortenedParts = new ArrayList<Part>();
65                         for (Part part : parts) {
66                                 if (part instanceof PlainTextPart) {
67                                         String longText = part.getText();
68                                         if (allPartsLength < cutOffLength) {
69                                                 if ((allPartsLength + longText.length()) > cutOffLength) {
70                                                         shortenedParts.add(new PlainTextPart(longText.substring(0, cutOffLength - allPartsLength) + "…"));
71                                                 } else {
72                                                         shortenedParts.add(part);
73                                                 }
74                                         }
75                                         allPartsLength += longText.length();
76                                 } else if (part instanceof LinkPart) {
77                                         if (allPartsLength < cutOffLength) {
78                                                 shortenedParts.add(part);
79                                         }
80                                         allPartsLength += part.getText().length();
81                                 } else {
82                                         if (allPartsLength < cutOffLength) {
83                                                 shortenedParts.add(part);
84                                         }
85                                 }
86                         }
87                         if (allPartsLength >= length) {
88                                 parts = shortenedParts;
89                         }
90                 }
91                 render(parsedTextWriter, parts);
92                 return parsedTextWriter.toString();
93         }
94
95         private void render(Writer writer, Iterable<Part> parts) {
96                 for (Part part : parts) {
97                         render(writer, part);
98                 }
99         }
100
101         private void render(Writer writer, Part part) {
102                 if (part instanceof PlainTextPart) {
103                         render(writer, (PlainTextPart) part);
104                 } else if (part instanceof FreenetLinkPart) {
105                         render(writer, (FreenetLinkPart) part);
106                 } else if (part instanceof LinkPart) {
107                         render(writer, (LinkPart) part);
108                 } else if (part instanceof SonePart) {
109                         render(writer, (SonePart) part);
110                 } else if (part instanceof PostPart) {
111                         render(writer, (PostPart) part);
112                 } else if (part instanceof AlbumPart) {
113                         render(writer, (AlbumPart) part);
114                 } else if (part instanceof Iterable<?>) {
115                         render(writer, (Iterable<Part>) part);
116                 }
117         }
118
119         private void render(Writer writer, PlainTextPart plainTextPart) {
120                 TemplateContext templateContext = templateContextFactory.createTemplateContext();
121                 templateContext.set("text", plainTextPart.getText());
122                 plainTextTemplate.render(templateContext, writer);
123         }
124
125         private void render(Writer writer, FreenetLinkPart freenetLinkPart) {
126                 renderLink(writer, "/" + freenetLinkPart.getLink(), freenetLinkPart.getText(), freenetLinkPart.getTitle(),
127                                 freenetLinkPart.isTrusted() ? "freenet-trusted" : "freenet");
128         }
129
130         private void render(Writer writer, LinkPart linkPart) {
131                 try {
132                         renderLink(writer, "/external-link/?_CHECKED_HTTP_=" + URLEncoder.encode(linkPart.getLink(), "UTF-8"), linkPart.getText(),
133                                         linkPart.getTitle(), "internet");
134                 } catch (UnsupportedEncodingException uee1) {
135                         /* not possible for UTF-8. */
136                         throw new RuntimeException("The JVM does not support UTF-8 encoding!", uee1);
137                 }
138         }
139
140         private void render(Writer writer, SonePart sonePart) {
141                 Sone sone = sonePart.getSone();
142                 if ((sone != null) && (sone.getName() != null)) {
143                         String niceName = SoneAccessor.getNiceName(sone);
144                         renderLink(writer, "viewSone.html?sone=" + sone.getId(), niceName, niceName, "in-sone");
145                 } else {
146                         renderLink(writer, "/WebOfTrust/ShowIdentity?id=" + sone.getId(), sone.getId(), sone.getId(), "in-sone");
147                 }
148         }
149
150         private void render(Writer writer, PostPart postPart) {
151                 SoneTextParser parser = new SoneTextParser(core, core, core);
152                 SoneTextParserContext parserContext = new SoneTextParserContext(null, postPart.getPost().getSone());
153                 try {
154                         Iterable<Part> parts = parser.parse(parserContext, new StringReader(postPart.getPost().getText()));
155                         StringBuilder excerpt = new StringBuilder();
156                         for (Part part : parts) {
157                                 excerpt.append(part.getText());
158                                 if (excerpt.length() > 20) {
159                                         int lastSpace = excerpt.lastIndexOf(" ", 20);
160                                         if (lastSpace > -1) {
161                                                 excerpt.setLength(lastSpace);
162                                         } else {
163                                                 excerpt.setLength(20);
164                                         }
165                                         excerpt.append("…");
166                                         break;
167                                 }
168                         }
169                         renderLink(writer, "viewPost.html?post=" + postPart.getPost().getId(), excerpt.toString(),
170                                         SoneAccessor.getNiceName(postPart.getPost().getSone()), postPart.usesDeprecatedLink() ? "internet" : "in-sone");
171                 } catch (IOException ioe1) {
172                         /* StringReader shouldn’t throw. */
173                 }
174         }
175
176         private void render(Writer writer, AlbumPart albumPart) {
177                 Album album = albumPart.getAlbum();
178                 renderLink(writer, String.format("imageBrowser.html?album=%s", album.getId()), album.getTitle(), album.getDescription(), "in-sone");
179         }
180
181         private void renderLink(Writer writer, String link, String text, String title, String cssClass) {
182                 TemplateContext templateContext = templateContextFactory.createTemplateContext();
183                 templateContext.set("cssClass", cssClass);
184                 templateContext.set("link", link);
185                 templateContext.set("text", text);
186                 templateContext.set("title", title);
187                 linkTemplate.render(templateContext, writer);
188         }
189
190 }