From: David ‘Bombe’ Roden Date: Thu, 10 Nov 2016 20:10:11 +0000 (+0100) Subject: Replace parser filter with separate filters X-Git-Tag: 0.9.7^2~455 X-Git-Url: https://git.pterodactylus.net/?p=Sone.git;a=commitdiff_plain;h=f6bb52d378645bf821febd6696bc69f250a92280 Replace parser filter with separate filters --- diff --git a/src/main/java/net/pterodactylus/sone/template/ParserFilter.java b/src/main/java/net/pterodactylus/sone/template/ParserFilter.java deleted file mode 100644 index e2afb9c..0000000 --- a/src/main/java/net/pterodactylus/sone/template/ParserFilter.java +++ /dev/null @@ -1,313 +0,0 @@ -/* - * Sone - ParserFilter.java - Copyright © 2011–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.template; - -import static java.lang.String.valueOf; -import static net.pterodactylus.sone.utils.NumberParsers.parseInt; - -import java.io.StringReader; -import java.io.StringWriter; -import java.io.UnsupportedEncodingException; -import java.io.Writer; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import javax.annotation.Nonnull; - -import net.pterodactylus.sone.core.Core; -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.text.FreemailPart; -import net.pterodactylus.sone.text.FreenetLinkPart; -import net.pterodactylus.sone.text.LinkPart; -import net.pterodactylus.sone.text.Part; -import net.pterodactylus.sone.text.PlainTextPart; -import net.pterodactylus.sone.text.PostPart; -import net.pterodactylus.sone.text.SonePart; -import net.pterodactylus.sone.text.SoneTextParser; -import net.pterodactylus.sone.text.SoneTextParserContext; -import net.pterodactylus.util.template.Filter; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; -import net.pterodactylus.util.template.TemplateContextFactory; -import net.pterodactylus.util.template.TemplateParser; - -import com.google.common.base.Function; -import com.google.common.base.Optional; - -/** - * Filter that filters a given text through a {@link SoneTextParser}. - * - * @author David ‘Bombe’ Roden - */ -public class ParserFilter implements Filter { - - /** The core. */ - private final Core core; - - /** The link parser. */ - private final SoneTextParser soneTextParser; - - /** The template context factory. */ - private final TemplateContextFactory templateContextFactory; - - /** The template for {@link PlainTextPart}s. */ - private static final Template plainTextTemplate = TemplateParser.parse(new StringReader("<%text|html>")); - - /** The template for {@link FreenetLinkPart}s. */ - private static final Template linkTemplate = TemplateParser.parse(new StringReader("\" href=\"<%link|html>\" title=\"<%title|html>\"><%text|html>")); - - /** - * Creates a new filter that runs its input through a {@link SoneTextParser} - * . - * - * @param core - * The core - * @param templateContextFactory - * The context factory for rendering the parts - * @param soneTextParser - * The Sone text parser - */ - public ParserFilter(Core core, TemplateContextFactory templateContextFactory, SoneTextParser soneTextParser) { - this.core = core; - this.templateContextFactory = templateContextFactory; - this.soneTextParser = soneTextParser; - } - - /** - * {@inheritDoc} - */ - @Override - public Object format(TemplateContext templateContext, Object data, Map parameters) { - String text = valueOf(data); - int length = parseInt(valueOf(parameters.get("length")), -1); - int cutOffLength = parseInt(valueOf(parameters.get("cut-off-length")), length); - Object sone = parameters.get("sone"); - if (sone instanceof String) { - sone = core.getSone((String) sone).orNull(); - } - SoneTextParserContext context = new SoneTextParserContext((Sone) sone); - StringWriter parsedTextWriter = new StringWriter(); - Iterable parts = soneTextParser.parse(text, context); - if (length > -1) { - int allPartsLength = 0; - List shortenedParts = new ArrayList(); - for (Part part : parts) { - if (part instanceof PlainTextPart) { - String longText = part.getText(); - if (allPartsLength < cutOffLength) { - if ((allPartsLength + longText.length()) > cutOffLength) { - shortenedParts.add(new PlainTextPart(longText.substring(0, cutOffLength - allPartsLength) + "…")); - } else { - shortenedParts.add(part); - } - } - allPartsLength += longText.length(); - } else if (part instanceof LinkPart) { - if (allPartsLength < cutOffLength) { - shortenedParts.add(part); - } - allPartsLength += part.getText().length(); - } else { - if (allPartsLength < cutOffLength) { - shortenedParts.add(part); - } - } - } - if (allPartsLength >= length) { - parts = shortenedParts; - } - } - render(parsedTextWriter, parts); - return parsedTextWriter.toString(); - } - - // - // PRIVATE METHODS - // - - /** - * Renders the given parts. - * - * @param writer - * The writer to render the parts to - * @param parts - * The parts to render - */ - private void render(Writer writer, Iterable parts) { - for (Part part : parts) { - render(writer, part); - } - } - - /** - * Renders the given part. - * - * @param writer - * The writer to render the part to - * @param part - * The part to render - */ - @SuppressWarnings("unchecked") - private void render(Writer writer, Part part) { - if (part instanceof PlainTextPart) { - render(writer, (PlainTextPart) part); - } else if (part instanceof FreenetLinkPart) { - render(writer, (FreenetLinkPart) part); - } else if (part instanceof LinkPart) { - render(writer, (LinkPart) part); - } else if (part instanceof SonePart) { - render(writer, (SonePart) part); - } else if (part instanceof PostPart) { - render(writer, (PostPart) part); - } else if (part instanceof FreemailPart) { - render(writer, (FreemailPart) part); - } else if (part instanceof Iterable) { - render(writer, (Iterable) part); - } - } - - /** - * Renders the given plain-text part. - * - * @param writer - * The writer to render the part to - * @param plainTextPart - * The part to render - */ - private void render(Writer writer, PlainTextPart plainTextPart) { - TemplateContext templateContext = templateContextFactory.createTemplateContext(); - templateContext.set("text", plainTextPart.getText()); - plainTextTemplate.render(templateContext, writer); - } - - /** - * Renders the given freenet link part. - * - * @param writer - * The writer to render the part to - * @param freenetLinkPart - * The part to render - */ - private void render(Writer writer, FreenetLinkPart freenetLinkPart) { - renderLink(writer, "/" + freenetLinkPart.getLink(), freenetLinkPart.getText(), freenetLinkPart.getTitle(), freenetLinkPart.isTrusted() ? "freenet-trusted" : "freenet"); - } - - /** - * Renders the given link part. - * - * @param writer - * The writer to render the part to - * @param linkPart - * The part to render - */ - private void render(Writer writer, LinkPart linkPart) { - try { - renderLink(writer, "/external-link/?_CHECKED_HTTP_=" + URLEncoder.encode(linkPart.getLink(), "UTF-8"), linkPart.getText(), linkPart.getTitle(), "internet"); - } catch (UnsupportedEncodingException uee1) { - /* not possible for UTF-8. */ - throw new RuntimeException("The JVM does not support UTF-8 encoding!", uee1); - } - } - - /** - * Renders the given Sone part. - * - * @param writer - * The writer to render the part to - * @param sonePart - * The part to render - */ - private void render(Writer writer, SonePart sonePart) { - if ((sonePart.getSone() != null) && (sonePart.getSone().getName() != null)) { - renderLink(writer, "viewSone.html?sone=" + sonePart.getSone().getId(), SoneAccessor.getNiceName(sonePart.getSone()), SoneAccessor.getNiceName(sonePart.getSone()), "in-sone"); - } else { - renderLink(writer, "/WebOfTrust/ShowIdentity?id=" + sonePart.getSone().getId(), sonePart.getSone().getId(), sonePart.getSone().getId(), "in-sone"); - } - } - - /** - * Renders the given post part. - * - * @param writer - * The writer to render the part to - * @param postPart - * The part to render - */ - private void render(Writer writer, PostPart postPart) { - SoneTextParser parser = new SoneTextParser(core, core); - SoneTextParserContext parserContext = new SoneTextParserContext(postPart.getPost().getSone()); - Iterable parts = parser.parse(postPart.getPost().getText(), parserContext); - StringBuilder excerpt = new StringBuilder(); - for (Part part : parts) { - excerpt.append(part.getText()); - if (excerpt.length() > 20) { - int lastSpace = excerpt.lastIndexOf(" ", 20); - if (lastSpace > -1) { - excerpt.setLength(lastSpace); - } else { - excerpt.setLength(20); - } - excerpt.append("…"); - break; - } - } - renderLink(writer, "viewPost.html?post=" + postPart.getPost().getId(), excerpt.toString(), SoneAccessor.getNiceName(postPart.getPost().getSone()), "in-sone"); - } - - private void render(@Nonnull Writer writer, @Nonnull FreemailPart freemailPart) { - Optional sone = core.getSone(freemailPart.getIdentityId()); - String soneName = sone.transform(new Function() { - @Nonnull - @Override - public String apply(Sone input) { - return SoneAccessor.getNiceName(input); - } - }).or(freemailPart.getIdentityId()); - renderLink(writer, - "/Freemail/NewMessage?to=" + freemailPart.getIdentityId(), - String.format("%s@%s.freemail", freemailPart.getEmailLocalPart(), soneName), - String.format("%s\n%s@%s.freemail", soneName, freemailPart.getEmailLocalPart(), freemailPart.getFreemailId()), - "in-sone"); - } - - /** - * Renders the given link. - * - * @param writer - * The writer to render the link to - * @param link - * The link to render - * @param text - * The text of the link - * @param title - * The title of the link - * @param cssClass - * The CSS class of the link - */ - private void renderLink(Writer writer, String link, String text, String title, String cssClass) { - TemplateContext templateContext = templateContextFactory.createTemplateContext(); - templateContext.set("cssClass", cssClass); - templateContext.set("link", link); - templateContext.set("text", text); - templateContext.set("title", title); - linkTemplate.render(templateContext, writer); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/WebInterface.java b/src/main/java/net/pterodactylus/sone/web/WebInterface.java index b7e3287..bbb3396 100644 --- a/src/main/java/net/pterodactylus/sone/web/WebInterface.java +++ b/src/main/java/net/pterodactylus/sone/web/WebInterface.java @@ -87,6 +87,7 @@ import net.pterodactylus.sone.template.JavascriptFilter; import net.pterodactylus.sone.template.ParserFilter; import net.pterodactylus.sone.template.PostAccessor; import net.pterodactylus.sone.template.ProfileAccessor; +import net.pterodactylus.sone.template.RenderFilter; import net.pterodactylus.sone.template.ReplyAccessor; import net.pterodactylus.sone.template.ReplyGroupFilter; import net.pterodactylus.sone.template.RequestChangeFilter; @@ -198,6 +199,7 @@ public class WebInterface { /** The parser filter. */ private final ParserFilter parserFilter; + private final RenderFilter renderFilter; private final ListNotificationFilter listNotificationFilter; private final PostVisibilityFilter postVisibilityFilter; @@ -284,7 +286,8 @@ public class WebInterface { templateContextFactory.addFilter("match", new MatchFilter()); templateContextFactory.addFilter("css", new CssClassNameFilter()); templateContextFactory.addFilter("js", new JavascriptFilter()); - templateContextFactory.addFilter("parse", parserFilter = new ParserFilter(getCore(), templateContextFactory, soneTextParser)); + templateContextFactory.addFilter("parse", parserFilter = new ParserFilter(getCore(), soneTextParser)); + templateContextFactory.addFilter("render", renderFilter = new RenderFilter(getCore(), templateContextFactory)); templateContextFactory.addFilter("reparse", new ReparseFilter()); templateContextFactory.addFilter("unknown", new UnknownDateFilter(getL10n(), "View.Sone.Text.UnknownDate")); templateContextFactory.addFilter("format", new FormatFilter()); @@ -745,7 +748,7 @@ public class WebInterface { pageToadlets.add(pageToadletFactory.createPageToadlet(new FollowSoneAjaxPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new UnfollowSoneAjaxPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new EditAlbumAjaxPage(this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new EditImageAjaxPage(this, parserFilter))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new EditImageAjaxPage(this, parserFilter, renderFilter))); pageToadlets.add(pageToadletFactory.createPageToadlet(new TrustAjaxPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new DistrustAjaxPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new UntrustAjaxPage(this))); diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/EditImageAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/EditImageAjaxPage.java index 6f67ccb..f606e50 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/EditImageAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/EditImageAjaxPage.java @@ -19,6 +19,8 @@ package net.pterodactylus.sone.web.ajax; import net.pterodactylus.sone.data.Image; import net.pterodactylus.sone.template.ParserFilter; +import net.pterodactylus.sone.template.RenderFilter; +import net.pterodactylus.sone.text.Part; import net.pterodactylus.sone.text.TextFilter; import net.pterodactylus.sone.web.WebInterface; import net.pterodactylus.sone.web.page.FreenetRequest; @@ -33,8 +35,8 @@ import com.google.common.collect.ImmutableMap; */ public class EditImageAjaxPage extends JsonPage { - /** Parser for image descriptions. */ private final ParserFilter parserFilter; + private final RenderFilter renderFilter; /** * Creates a new edit image AJAX page. @@ -44,9 +46,10 @@ public class EditImageAjaxPage extends JsonPage { * @param parserFilter * The parser filter for image descriptions */ - public EditImageAjaxPage(WebInterface webInterface, ParserFilter parserFilter) { + public EditImageAjaxPage(WebInterface webInterface, ParserFilter parserFilter, RenderFilter renderFilter) { super("editImage.ajax", webInterface); this.parserFilter = parserFilter; + this.renderFilter = renderFilter; } // @@ -83,7 +86,14 @@ public class EditImageAjaxPage extends JsonPage { String description = request.getHttpRequest().getParam("description").trim(); image.modify().setTitle(title).setDescription(TextFilter.filter(request.getHttpRequest().getHeader("host"), description)).update(); webInterface.getCore().touchConfiguration(); - return createSuccessJsonObject().put("imageId", image.getId()).put("title", image.getTitle()).put("description", image.getDescription()).put("parsedDescription", (String) parserFilter.format(new TemplateContext(), image.getDescription(), ImmutableMap.builder().put("sone", image.getSone()).build())); + return createSuccessJsonObject().put("imageId", image.getId()).put("title", image.getTitle()).put("description", image.getDescription()).put("parsedDescription", renderImageDescription(image)); + } + + private String renderImageDescription(Image image) { + TemplateContext templateContext = new TemplateContext(); + ImmutableMap parameters = ImmutableMap.builder().put("sone", image.getSone()).build(); + Object parts = parserFilter.format(templateContext, image.getDescription(), parameters); + return (String) renderFilter.format(templateContext, parts, parameters); } } diff --git a/src/main/kotlin/net/pterodactylus/sone/template/ParserFilter.kt b/src/main/kotlin/net/pterodactylus/sone/template/ParserFilter.kt new file mode 100644 index 0000000..4d2f9c1 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/template/ParserFilter.kt @@ -0,0 +1,28 @@ +package net.pterodactylus.sone.template + +import net.pterodactylus.sone.core.Core +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.text.Part +import net.pterodactylus.sone.text.SoneTextParser +import net.pterodactylus.sone.text.SoneTextParserContext +import net.pterodactylus.util.template.Filter +import net.pterodactylus.util.template.TemplateContext + +/** + * Parses a [String] into a number of [Part]s. + */ +class ParserFilter(private val core: Core, private val soneTextParser: SoneTextParser) : Filter { + + override fun format(templateContext: TemplateContext?, data: Any?, parameters: MutableMap?): Any? { + val text = data?.toString() ?: return listOf() + val soneParameter = parameters?.get("sone") + val sone = when (soneParameter) { + is String -> core.getSone(soneParameter).orNull() + is Sone -> soneParameter + else -> null + } + val context = SoneTextParserContext(sone as? Sone) + return soneTextParser.parse(text, context) + } + +} diff --git a/src/main/resources/templates/imageBrowser.html b/src/main/resources/templates/imageBrowser.html index 644848a..7e4c824 100644 --- a/src/main/resources/templates/imageBrowser.html +++ b/src/main/resources/templates/imageBrowser.html @@ -341,7 +341,7 @@ <%/foreach> -

<% album.description|parse sone=album.sone>

+

<% album.description|parse sone=album.sone|render>

<%if album.sone.local> @@ -389,7 +389,7 @@
<% image.title|html>
-
<% image.description|parse sone=image.sone>
+
<% image.description|parse sone=image.sone|render>
<%if album.sone.local>
@@ -501,7 +501,7 @@ <%/if> -

<%image.description|parse sone=image.sone>

+

<%image.description|parse sone=image.sone|render>

<%if image.sone.local> @@ -619,7 +619,7 @@
<% album.title|html> (<%= View.Sone.Stats.Images|l10n 0=album.images.size>)
-
<% album.description|parse sone=album.sone>
+
<% album.description|parse sone=album.sone|render>
<%= false|store key==endRow> diff --git a/src/main/resources/templates/include/browseAlbums.html b/src/main/resources/templates/include/browseAlbums.html index 9aacad3..7c8c490 100644 --- a/src/main/resources/templates/include/browseAlbums.html +++ b/src/main/resources/templates/include/browseAlbums.html @@ -14,7 +14,7 @@
<% album.title|html> (<%= View.Sone.Stats.Images|l10n 0=album.images.size>)
-
<% album.description|parse sone=album.sone>
+
<% album.description|parse sone=album.sone|render>
<%if album.sone.local> diff --git a/src/main/resources/templates/include/viewPost.html b/src/main/resources/templates/include/viewPost.html index 1c53d42..24ce420 100644 --- a/src/main/resources/templates/include/viewPost.html +++ b/src/main/resources/templates/include/viewPost.html @@ -27,8 +27,8 @@ <%/if> <%/if> <% post.text|html|store key==originalText text==true> - <% post.text|parse sone=post.sone|store key==parsedText text==true> - <% post.text|parse sone=post.sone length=core.preferences.charactersPerPost cut-off-length=core.preferences.postCutOffLength|store key==shortText text==true> + <% post.text|parse sone=post.sone|render|store key==parsedText text==true> + <% post.text|parse sone=post.sone length=core.preferences.charactersPerPost cut-off-length=core.preferences.postCutOffLength|render|store key==shortText text==true>
<% originalText>
<% parsedText>
<% shortText>
diff --git a/src/main/resources/templates/include/viewReply.html b/src/main/resources/templates/include/viewReply.html index 5e2ed7d..1ef4d99 100644 --- a/src/main/resources/templates/include/viewReply.html +++ b/src/main/resources/templates/include/viewReply.html @@ -15,8 +15,8 @@
<% reply.text|html|store key==originalText text==true> - <% reply.text|parse sone=reply.sone|store key==parsedText text==true> - <% reply.text|parse sone=reply.sone length=core.preferences.charactersPerPost cut-off-length=core.preferences.postCutOffLength|store key==shortText text==true> + <% reply.text|parse sone=reply.sone|render|store key==parsedText text==true> + <% reply.text|parse sone=reply.sone length=core.preferences.charactersPerPost cut-off-length=core.preferences.postCutOffLength|render|store key==shortText text==true>
<% originalText>
<% parsedText>
<% shortText>
diff --git a/src/main/resources/templates/invalid.html b/src/main/resources/templates/invalid.html index 3ac37cf..922829c 100644 --- a/src/main/resources/templates/invalid.html +++ b/src/main/resources/templates/invalid.html @@ -4,9 +4,9 @@ <%foreach messages message> <%if message|substring start==0 length==1|match value=='!'> -

<% message|substring start==1|parse>

+

<% message|substring start==1|parse|render>

<%else> -

<% message|parse>

+

<% message|parse|render>

<%/if> <%foreachelse>

<%= Page.Invalid.Text|l10n|html|replace needle=="{link}" replacement==''|replace needle=="{/link}" replacement==''>

diff --git a/src/main/resources/templates/notify/newVersionNotification.html b/src/main/resources/templates/notify/newVersionNotification.html index f35ecaf..fa9c53e 100644 --- a/src/main/resources/templates/notify/newVersionNotification.html +++ b/src/main/resources/templates/notify/newVersionNotification.html @@ -1,4 +1,4 @@ -
<%= Notification.NewVersion.Text|l10n|replace needle=="{version}" replacement=latestVersion|replace needle=="{edition}" replacement=latestEdition|parse sone=="nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI">
+
<%= Notification.NewVersion.Text|l10n|replace needle=="{version}" replacement=latestVersion|replace needle=="{edition}" replacement=latestEdition|parse sone=="nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI"|render>
<%if disruptive>
<%= Notification.NewVersion.Disruptive.Text|l10n|html|replace needle=="{em}" replacement==""|replace needle=="{/em}" replacement=="">
<%/if> diff --git a/src/main/resources/templates/notify/soneInsertNotification.html b/src/main/resources/templates/notify/soneInsertNotification.html index c15864f..8965e36 100644 --- a/src/main/resources/templates/notify/soneInsertNotification.html +++ b/src/main/resources/templates/notify/soneInsertNotification.html @@ -1,7 +1,7 @@ <%if soneStatus|match value=="inserting"> - <%= Notification.SoneIsInserting.Text|l10n 0=insertSone.id|parse> + <%= Notification.SoneIsInserting.Text|l10n 0=insertSone.id|parse|render> <%elseif soneStatus|match value=="inserted"> - <%= Notification.SoneIsInserted.Text|l10n 0=insertSone.id 1=insertDuration|parse> + <%= Notification.SoneIsInserted.Text|l10n 0=insertSone.id 1=insertDuration|parse|render> <%elseif soneStatus|match value=="insert-aborted"> - <%= Notification.SoneInsertAborted.Text|l10n 0=insertSone.id|parse> + <%= Notification.SoneInsertAborted.Text|l10n 0=insertSone.id|parse|render> <%/if> diff --git a/src/main/resources/templates/viewSone.html b/src/main/resources/templates/viewSone.html index 4b88474..dd994ea 100644 --- a/src/main/resources/templates/viewSone.html +++ b/src/main/resources/templates/viewSone.html @@ -52,7 +52,7 @@ <%foreach sone.profile.fields field>
<% field.name|html>
-
<% field.value|parse sone=sone>
+
<% field.value|parse sone=sone|render>
<%/foreach> diff --git a/src/test/java/net/pterodactylus/sone/template/ParserFilterTest.java b/src/test/java/net/pterodactylus/sone/template/ParserFilterTest.java deleted file mode 100644 index 5820652..0000000 --- a/src/test/java/net/pterodactylus/sone/template/ParserFilterTest.java +++ /dev/null @@ -1,293 +0,0 @@ -package net.pterodactylus.sone.template; - -import static java.util.Arrays.asList; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.is; -import static org.mockito.ArgumentCaptor.forClass; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.net.URLEncoder; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import net.pterodactylus.sone.core.Core; -import net.pterodactylus.sone.data.Post; -import net.pterodactylus.sone.data.Profile; -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.text.FreemailPart; -import net.pterodactylus.sone.text.FreenetLinkPart; -import net.pterodactylus.sone.text.LinkPart; -import net.pterodactylus.sone.text.Part; -import net.pterodactylus.sone.text.PartContainer; -import net.pterodactylus.sone.text.PlainTextPart; -import net.pterodactylus.sone.text.PostPart; -import net.pterodactylus.sone.text.SonePart; -import net.pterodactylus.sone.text.SoneTextParser; -import net.pterodactylus.sone.text.SoneTextParserContext; -import net.pterodactylus.util.template.HtmlFilter; -import net.pterodactylus.util.template.TemplateContext; -import net.pterodactylus.util.template.TemplateContextFactory; - -import com.google.common.base.Optional; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Attribute; -import org.jsoup.nodes.Element; -import org.jsoup.nodes.TextNode; -import org.junit.Test; -import org.mockito.ArgumentCaptor; - -/** - * Unit test for {@link ParserFilter}. - * - * @author David ‘Bombe’ Roden - */ -public class ParserFilterTest { - - private static final String FREEMAIL_ID = "t4dlzfdww3xvsnsc6j6gtliox6zaoak7ymkobbmcmdw527ubuqra"; - private static final String SONE_FREEMAIL = "sone@" + FREEMAIL_ID + ".freemail"; - private static final String SONE_IDENTITY = "nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI"; - private static final String POST_ID = "37a06250-6775-4b94-86ff-257ba690953c"; - - private final Core core = mock(Core.class); - private final TemplateContextFactory templateContextFactory = new TemplateContextFactory(); - private final TemplateContext templateContext; - private final SoneTextParser soneTextParser = mock(SoneTextParser.class); - private final ParserFilter filter = new ParserFilter(core, templateContextFactory, soneTextParser); - private final Sone sone = setupSone(SONE_IDENTITY, "Sone", "First"); - private final Map parameters = new HashMap<>(); - - public ParserFilterTest() { - templateContextFactory.addFilter("html", new HtmlFilter()); - templateContext = templateContextFactory.createTemplateContext(); - } - - @Test - public void givenSoneIsUsedInParseContext() { - setupSoneAndVerifyItIsUsedInContext(sone, sone); - } - - private void setupSoneAndVerifyItIsUsedInContext(Object soneOrSoneId, Sone sone) { - setupParser("text", new PlainTextPart("text")); - parameters.put("sone", sone); - filter.format(templateContext, "text", parameters); - ArgumentCaptor context = forClass(SoneTextParserContext.class); - verify(soneTextParser).parse(eq("text"), context.capture()); - assertThat(context.getValue().getPostingSone(), is(sone)); - } - - @Test - public void soneWithGivenSoneIdIsUsedInParseContext() { - setupSoneAndVerifyItIsUsedInContext(SONE_IDENTITY, sone); - } - - @Test - public void plainTextIsRenderedCorrectly() { - setupParser("plain text", new PlainTextPart("plain text")); - String result = (String) filter.format(templateContext, "plain text", Collections.emptyMap()); - assertThat(result, is("plain text")); - } - - private void setupParser(String text, Part... parsedParts) { - when(soneTextParser.parse(eq(text), any(SoneTextParserContext.class))).thenReturn(asList(parsedParts)); - } - - @Test - public void plainTextPartIsShortenedIfLengthExceedsMaxLength() { - setupParser("text", new PlainTextPart("This is a long text.")); - setLengthAndCutOffLength(15, 10); - String output = (String) filter.format(templateContext, "text", parameters); - assertThat(output, is("This is a …")); - } - - @Test - public void plainTextPartIsNotShortenedIfLengthDoesNotExceedMaxLength() { - setupParser("text", new PlainTextPart("This is a long text.")); - setLengthAndCutOffLength(20, 10); - String output = (String) filter.format(templateContext, "text", parameters); - assertThat(output, is("This is a …")); - } - - @Test - public void shortPartsAreNotShortened() { - setupParser("text", new PlainTextPart("This.")); - setLengthAndCutOffLength(15, 10); - String output = (String) filter.format(templateContext, "text", parameters); - assertThat(output, is("This.")); - } - - @Test - public void multiplePlainTextPartsAreShortened() { - setupParser("text", new PlainTextPart("This "), new PlainTextPart("is a long text.")); - setLengthAndCutOffLength(15, 10); - String output = (String) filter.format(templateContext, "text", parameters); - assertThat(output, is("This is a …")); - } - - @Test - public void partsAfterLengthHasBeenReachedAreIgnored() { - setupParser("text", new PlainTextPart("This is a long text."), new PlainTextPart(" And even more.")); - setLengthAndCutOffLength(15, 10); - String output = (String) filter.format(templateContext, "text", parameters); - assertThat(output, is("This is a …")); - } - - @Test - public void linkPartsAreNotShortened() { - setupParser("text", new FreenetLinkPart("KSK@gpl.txt", "This is a long text.", false)); - setLengthAndCutOffLength(15, 10); - String output = (String) filter.format(templateContext, "text", parameters); - Element linkNode = Jsoup.parseBodyFragment(output).body().child(0); - verifyLink(linkNode, "/KSK@gpl.txt", "freenet", "KSK@gpl.txt", "This is a long text."); - } - - @Test - public void additionalLinkPartsAreIgnored() { - setupParser("text", new PlainTextPart("This is a long text."), new FreenetLinkPart("KSK@gpl.txt", "This is a long text.", false)); - setLengthAndCutOffLength(15, 10); - String output = (String) filter.format(templateContext, "text", parameters); - assertThat(output, is("This is a …")); - } - - private void setLengthAndCutOffLength(int length, int cutOffLength) { - parameters.put("length", length); - parameters.put("cut-off-length", cutOffLength); - } - - @Test - public void sonePartsAreAddedButTheirLengthIsIgnored() { - setupParser("text", new SonePart(sone), new PlainTextPart("This is a long text.")); - setLengthAndCutOffLength(15, 10); - String output = (String) filter.format(templateContext, "text", parameters); - Element body = Jsoup.parseBodyFragment(output).body(); - Element linkNode = (Element) body.childNode(0); - System.out.println(linkNode); - verifyLink(linkNode, "viewSone.html?sone=" + SONE_IDENTITY, "in-sone", "First", "First"); - assertThat(((TextNode) body.childNode(1)).text(), is("This is a …")); - } - - @Test - public void additionalSonePartsAreIgnored() { - setupParser("text", new PlainTextPart("This is a long text."), new SonePart(sone)); - setLengthAndCutOffLength(15, 10); - String output = (String) filter.format(templateContext, "text", parameters); - assertThat(output, is("This is a …")); - } - - @Test - public void freenetLinkIsRenderedCorrectly() { - setupParser("KSK@gpl.txt", new FreenetLinkPart("KSK@gpl.txt", "gpl.txt", false)); - Element linkNode = filterText("KSK@gpl.txt"); - verifyLink(linkNode, "/KSK@gpl.txt", "freenet", "KSK@gpl.txt", "gpl.txt"); - } - - private void verifyLink(Element linkNode, String url, String cssClass, String tooltip, String text) { - assertThat(linkNode.nodeName(), is("a")); - assertThat(linkNode.attributes().asList(), containsInAnyOrder( - new Attribute("href", url), - new Attribute("class", cssClass), - new Attribute("title", tooltip) - )); - assertThat(linkNode.text(), is(text)); - } - - @Test - public void trustedFreenetLinkIsRenderedWithCorrectCssClass() { - setupParser("KSK@gpl.txt", new FreenetLinkPart("KSK@gpl.txt", "gpl.txt", true)); - Element linkNode = filterText("KSK@gpl.txt"); - verifyLink(linkNode, "/KSK@gpl.txt", "freenet-trusted", "KSK@gpl.txt", "gpl.txt"); - } - - private Element filterText(String text) { - String output = (String) filter.format(templateContext, text, Collections.emptyMap()); - return Jsoup.parseBodyFragment(output).body().child(0); - } - - @Test - public void internetLinkIsRenderedCorrectly() throws Exception { - setupParser("http://test.com/test.html", new LinkPart("http://test.com/test.html", "test.com/test.html")); - Element linkNode = filterText("http://test.com/test.html"); - verifyLink(linkNode, "/external-link/?_CHECKED_HTTP_=" + URLEncoder.encode("http://test.com/test.html", "UTF-8"), "internet", - "http://test.com/test.html", "test.com/test.html"); - } - - @Test - public void sonePartsAreRenderedCorrectly() { - setupParser("sone://" + SONE_IDENTITY, new SonePart(sone)); - Element linkNode = filterText("sone://" + SONE_IDENTITY); - verifyLink(linkNode, "viewSone.html?sone=" + SONE_IDENTITY, "in-sone", "First", "First"); - } - - private Sone setupSone(String identity, String name, String firstName) { - Sone sone = mock(Sone.class); - when(sone.getId()).thenReturn(identity); - when(sone.getProfile()).thenReturn(new Profile(sone)); - when(sone.getName()).thenReturn(name); - sone.getProfile().setFirstName(firstName); - when(core.getSone(identity)).thenReturn(Optional.of(sone)); - return sone; - } - - @Test - public void sonePartsWithUnknownSoneIsRenderedAsLinkToWebOfTrust() { - Sone sone = setupSone(SONE_IDENTITY, null, "First"); - setupParser("sone://" + SONE_IDENTITY, new SonePart(sone)); - Element linkNode = filterText("sone://" + SONE_IDENTITY); - verifyLink(linkNode, "/WebOfTrust/ShowIdentity?id=" + SONE_IDENTITY, "in-sone", SONE_IDENTITY, SONE_IDENTITY); - } - - @Test - public void postPartIsCutOffCorrectlyWhenThereAreSpaces() { - Post post = setupPost(sone, "1234 678901 345 789012 45678 01."); - setupParser("post://" + POST_ID, new PostPart(post)); - Element linkNode = filterText("post://" + POST_ID); - verifyLink(linkNode, "viewPost.html?post=" + POST_ID, "in-sone", "First", "1234 678901 345…"); - } - - private Post setupPost(Sone sone, String value) { - Post post = mock(Post.class); - when(post.getId()).thenReturn(POST_ID); - when(post.getSone()).thenReturn(sone); - when(post.getText()).thenReturn(value); - return post; - } - - @Test - public void postPartIsCutOffCorrectlyWhenThereAreNoSpaces() { - Post post = setupPost(sone, "1234567890123456789012345678901."); - setupParser("post://" + POST_ID, new PostPart(post)); - Element linkNode = filterText("post://" + POST_ID); - verifyLink(linkNode, "viewPost.html?post=" + POST_ID, "in-sone", "First", "12345678901234567890…"); - } - - @Test - public void postPartShorterThan21CharsIsNotCutOff() { - Post post = setupPost(sone, "12345678901234567890"); - setupParser("post://" + POST_ID, new PostPart(post)); - Element linkNode = filterText("post://" + POST_ID); - verifyLink(linkNode, "viewPost.html?post=" + POST_ID, "in-sone", "First", "12345678901234567890"); - } - - @Test - public void multiplePartsAreRenderedCorrectly() { - PartContainer parts = new PartContainer(); - parts.add(new PlainTextPart("te")); - parts.add(new PlainTextPart("xt")); - setupParser("text", parts); - String result = (String) filter.format(templateContext, "text", Collections.emptyMap()); - assertThat(result, is("text")); - } - - @Test - public void freemailAddressIsDisplayedCorrectly() { - setupParser(SONE_FREEMAIL, new FreemailPart("sone", FREEMAIL_ID, SONE_IDENTITY)); - Element linkNode = filterText(SONE_FREEMAIL); - verifyLink(linkNode, "/Freemail/NewMessage?to=" + SONE_IDENTITY, "in-sone", "First\n" + SONE_FREEMAIL, "sone@First.freemail"); - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/template/ParserFilterTest.kt b/src/test/kotlin/net/pterodactylus/sone/template/ParserFilterTest.kt new file mode 100644 index 0000000..3c735c2 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/template/ParserFilterTest.kt @@ -0,0 +1,65 @@ +package net.pterodactylus.sone.template + +import com.google.common.base.Optional.of +import net.pterodactylus.sone.core.Core +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.text.SoneTextParser +import net.pterodactylus.sone.text.SoneTextParserContext +import net.pterodactylus.util.template.TemplateContext +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.`is` +import org.hamcrest.Matchers.emptyIterable +import org.junit.Test +import org.mockito.ArgumentCaptor.forClass +import org.mockito.Mockito.`when` +import org.mockito.Mockito.eq +import org.mockito.Mockito.verify + +/** + * Unit test for [ParserFilter]. + */ +class ParserFilterTest { + + companion object { + private const val SONE_IDENTITY = "nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI" + } + + private val core = mock() + private val sone = setupSone(SONE_IDENTITY) + private val soneTextParser = mock() + private val templateContext = TemplateContext() + private val parameters = mutableMapOf() + private val filter = ParserFilter(core, soneTextParser) + + private fun setupSone(identity: String): Sone { + val sone = mock() + `when`(sone.id).thenReturn(identity) + `when`(core.getSone(identity)).thenReturn(of(sone)) + return sone + } + + @Test + fun `parsing null returns an empty iterable`() { + assertThat(filter.format(templateContext, null, mutableMapOf()) as Iterable<*>, emptyIterable()) + } + + @Test + fun `given sone is used to create parser context`() { + setupSoneAndVerifyItIsUsedInContext(sone, sone) + } + + @Test + fun `sone with given sone ID is used to create parser context`() { + setupSoneAndVerifyItIsUsedInContext(SONE_IDENTITY, sone) + } + + private fun setupSoneAndVerifyItIsUsedInContext(soneOrSoneId: Any, sone: Sone) { + parameters.put("sone", soneOrSoneId) + filter.format(templateContext, "text", parameters) + val context = forClass(SoneTextParserContext::class.java) + verify(soneTextParser).parse(eq("text"), context.capture()) + assertThat(context.value.postingSone, `is`(sone)) + } + +}