From 8b7982fe410510eca2219794ee011c6cfa74306a Mon Sep 17 00:00:00 2001 From: Floens Date: Thu, 8 Feb 2018 00:06:17 +0100 Subject: [PATCH] move to the builder style comment parser. --- .../chan/core/site/parser/CommentParser.java | 137 +++++----- .../chan/core/site/parser/StyleRule.java | 248 ++++++++++++++++++ .../sites/vichan/ViChanCommentParser.java | 21 +- 3 files changed, 320 insertions(+), 86 deletions(-) create mode 100644 Clover/app/src/main/java/org/floens/chan/core/site/parser/StyleRule.java diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/parser/CommentParser.java b/Clover/app/src/main/java/org/floens/chan/core/site/parser/CommentParser.java index b992e9df..435f9a06 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/site/parser/CommentParser.java +++ b/Clover/app/src/main/java/org/floens/chan/core/site/parser/CommentParser.java @@ -21,9 +21,7 @@ import android.graphics.Typeface; import android.support.annotation.AnyThread; import android.text.SpannableString; import android.text.TextUtils; -import android.text.style.StrikethroughSpan; import android.text.style.StyleSpan; -import android.text.style.TypefaceSpan; import android.text.style.UnderlineSpan; import org.floens.chan.core.model.Post; @@ -35,11 +33,14 @@ import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static org.floens.chan.core.site.parser.StyleRule.tagRule; import static org.floens.chan.utils.AndroidUtils.sp; @AnyThread @@ -52,6 +53,38 @@ public class CommentParser { private Pattern quotePattern = Pattern.compile(".*#p(\\d+)"); private Pattern colorPattern = Pattern.compile("color:#([0-9a-fA-F]*)"); + private Map> rules = new HashMap<>(); + + public CommentParser() { + rule(tagRule("br").just("\n")); + + rule(tagRule("a").action(this::handleAnchor)); + + rule(tagRule("span").cssClass("deadlink").color(StyleRule.Color.QUOTE).strikeThrough()); + rule(tagRule("span").cssClass("spoiler").link(PostLinkable.Type.SPOILER)); + rule(tagRule("span").cssClass("fortune").action(this::handleFortune)); + rule(tagRule("span").cssClass("abbr").nullify()); + rule(tagRule("span").color(StyleRule.Color.INLINE_QUOTE).linkify()); + + rule(tagRule("table").action(this::handleTable)); + + rule(tagRule("s").link(PostLinkable.Type.SPOILER)); + + rule(tagRule("strong").color(StyleRule.Color.QUOTE).bold()); + + rule(tagRule("pre").cssClass("prettyprint").monospace().size(sp(12f))); + } + + public void rule(StyleRule rule) { + List list = rules.get(rule.tag()); + if (list == null) { + list = new ArrayList<>(3); + rules.put(rule.tag(), list); + } + + list.add(rule); + } + public void setQuotePattern(Pattern quotePattern) { this.quotePattern = quotePattern; } @@ -66,24 +99,23 @@ public class CommentParser { String tag, CharSequence text, Element element) { + + List rules = this.rules.get(tag); + if (rules != null) { + for (int i = 0; i < 2; i++) { + boolean highPriority = i == 0; + for (StyleRule rule : rules) { + if (rule.highPriority() == highPriority && rule.applies(element)) { + return rule.apply(theme, callback, post, text, element); + } + } + } + } + switch (tag) { - case "br": - return "\n"; - case "span": - return handleSpan(theme, post, text, element); case "p": return appendBreakIfNotLastSibling( handleParagraph(theme, post, text, element), element); - case "table": - return handleTable(theme, post, text, element); - case "strong": - return handleStrong(theme, post, text, element); - case "a": - return handleAnchor(theme, post, text, element, callback); - case "s": - return handleStrike(theme, post, text, element); - case "pre": - return handlePre(theme, post, text, element); default: // Unknown tag, return the text; return text; @@ -99,10 +131,10 @@ public class CommentParser { } private CharSequence handleAnchor(Theme theme, + PostParser.Callback callback, Post.Builder post, CharSequence text, - Element anchor, - PostParser.Callback callback) { + Element anchor) { CommentParser.Link handlerLink = matchAnchor(post, text, anchor, callback); if (handlerLink != null) { @@ -136,19 +168,14 @@ public class CommentParser { } } - public CharSequence handleSpan(Theme theme, Post.Builder post, CharSequence text, Element span) { - SpannableString quote; - + private CharSequence handleFortune(Theme theme, + PostParser.Callback callback, + Post.Builder builder, + CharSequence text, + Element span) { Set classes = span.classNames(); - if (classes.contains("deadlink")) { - quote = new SpannableString(span.text()); - quote.setSpan(new ForegroundColorSpanHashed(theme.quoteColor), 0, quote.length(), 0); - quote.setSpan(new StrikethroughSpan(), 0, quote.length(), 0); - } else if (classes.contains("fortune")) { + if (classes.contains("fortune")) { // html looks like

Your fortune: - // manually add these
- quote = new SpannableString("\n\n" + span.text()); - String style = span.attr("style"); if (!TextUtils.isEmpty(style)) { style = style.replace(" ", ""); @@ -167,30 +194,27 @@ public class CommentParser { } if (hexColor >= 0 && hexColor <= 0xffffff) { - quote.setSpan(new ForegroundColorSpanHashed(0xff000000 + hexColor), 0, quote.length(), 0); - quote.setSpan(new StyleSpan(Typeface.BOLD), 0, quote.length(), 0); + text = span(text, new ForegroundColorSpanHashed(0xff000000 + hexColor), + new StyleSpan(Typeface.BOLD)); } } - } else if (classes.contains("spoiler")) { - PostLinkable pl = new PostLinkable(theme, span.text(), span.text(), PostLinkable.Type.SPOILER); - post.addLinkable(pl); - return span(span.text(), pl); - } else if (classes.contains("abbr")) { - return null; - } else { - quote = new SpannableString(span.text()); - quote.setSpan(new ForegroundColorSpanHashed(theme.inlineQuoteColor), 0, quote.length(), 0); - CommentParserHelper.detectLinks(theme, post, span.text(), quote); } - return quote; + return text; } - public CharSequence handleParagraph(Theme theme, Post.Builder post, CharSequence text, Element span) { + public CharSequence handleParagraph(Theme theme, + Post.Builder post, + CharSequence text, + Element span) { return text; } - public CharSequence handleTable(Theme theme, Post.Builder post, CharSequence text, Element table) { + public CharSequence handleTable(Theme theme, + PostParser.Callback callback, + Post.Builder builder, + CharSequence text, + Element table) { List parts = new ArrayList<>(); Elements tableRows = table.getElementsByTag("tr"); for (int i = 0; i < tableRows.size(); i++) { @@ -221,31 +245,6 @@ public class CommentParser { new AbsoluteSizeSpanHashed(sp(12f))); } - public CharSequence handleStrong(Theme theme, Post.Builder post, CharSequence text, Element strong) { - return span(text, - new ForegroundColorSpanHashed(theme.quoteColor), - new StyleSpan(Typeface.BOLD)); - } - - public CharSequence handlePre(Theme theme, Post.Builder post, CharSequence text, Element pre) { - Set classes = pre.classNames(); - if (classes.contains("prettyprint")) { -// String linebreakText = CommentParserHelper.getNodeTextPreservingLineBreaks(pre); - return span(text, - new TypefaceSpan("monospace"), - new AbsoluteSizeSpanHashed(sp(12f))); - } else { - return pre.text(); - } - } - - public CharSequence handleStrike(Theme theme, Post.Builder post, CharSequence text, Element strike) { - PostLinkable pl = new PostLinkable(theme, text.toString(), text, PostLinkable.Type.SPOILER); - post.addLinkable(pl); - - return span(text, pl); - } - public Link matchAnchor(Post.Builder post, CharSequence text, Element anchor, PostParser.Callback callback) { String href = anchor.attr("href"); diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/parser/StyleRule.java b/Clover/app/src/main/java/org/floens/chan/core/site/parser/StyleRule.java new file mode 100644 index 00000000..57f38322 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/site/parser/StyleRule.java @@ -0,0 +1,248 @@ +/* + * Clover - 4chan browser https://github.com/Floens/Clover/ + * Copyright (C) 2014 Floens + * + * 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 org.floens.chan.core.site.parser; + +import android.graphics.Typeface; +import android.text.SpannableString; +import android.text.style.StrikethroughSpan; +import android.text.style.StyleSpan; +import android.text.style.TypefaceSpan; + +import org.floens.chan.core.model.Post; +import org.floens.chan.core.model.PostLinkable; +import org.floens.chan.ui.span.AbsoluteSizeSpanHashed; +import org.floens.chan.ui.span.ForegroundColorSpanHashed; +import org.floens.chan.ui.theme.Theme; +import org.jsoup.nodes.Element; + +import java.util.ArrayList; +import java.util.List; + +public class StyleRule { + public enum Color { + INLINE_QUOTE, + QUOTE + } + + public static StyleRule tagRule(String tag) { + return new StyleRule().tag(tag); + } + + private String tag; + private List classes; + + private List actions = new ArrayList<>(); + + private Color color = null; + private boolean strikeThrough = false; + private boolean bold = false; + private boolean monospace = false; + private int size = 0; + + private PostLinkable.Type link = null; + + private boolean nullify = false; + private boolean linkify = false; + + private String justText = null; + + public StyleRule tag(String tag) { + this.tag = tag; + + return this; + } + + public String tag() { + return tag; + } + + public StyleRule cssClass(String cssClass) { + if (classes == null) { + classes = new ArrayList<>(4); + } + classes.add(cssClass); + + return this; + } + + public StyleRule action(Action action) { + actions.add(action); + + return this; + } + + public StyleRule color(Color color) { + this.color = color; + + return this; + } + + public StyleRule link(PostLinkable.Type link) { + this.link = link; + + return this; + } + + public StyleRule strikeThrough() { + strikeThrough = true; + + return this; + } + + public StyleRule bold() { + bold = true; + + return this; + } + + public StyleRule monospace() { + monospace = true; + + return this; + } + + public StyleRule size(int size) { + this.size = size; + + return this; + } + + public StyleRule nullify() { + nullify = true; + + return this; + } + + public StyleRule linkify() { + linkify = true; + + return this; + } + + public StyleRule just(String justText) { + this.justText = justText; + + return this; + } + + public boolean highPriority() { + return classes != null && !classes.isEmpty(); + } + + public boolean applies(Element element) { + if (classes == null || classes.isEmpty()) { + return true; + } + + for (String c : classes) { + if (element.hasClass(c)) { + return true; + } + } + + return false; + } + + public CharSequence apply(Theme theme, + PostParser.Callback callback, + Post.Builder post, + CharSequence text, + Element element) { + if (nullify) { + return null; + } + + if (justText != null) { + return justText; + } + + CharSequence result = text; + for (Action action : actions) { + result = action.execute(theme, callback, post, text, element); + } + + List spansToApply = new ArrayList<>(2); + + if (color != null) { + spansToApply.add(new ForegroundColorSpanHashed(getColor(theme, color))); + } + + if (strikeThrough) { + spansToApply.add(new StrikethroughSpan()); + } + + if (bold) { + spansToApply.add(new StyleSpan(Typeface.BOLD)); + } + + if (monospace) { + spansToApply.add(new TypefaceSpan("monospace")); + } + + if (size != 0) { + spansToApply.add(new AbsoluteSizeSpanHashed(size)); + } + + if (link != null) { + PostLinkable pl = new PostLinkable(theme, result, result, link); + post.addLinkable(pl); + spansToApply.add(pl); + } + + if (!spansToApply.isEmpty()) { + result = applySpan(result, spansToApply); + } + + if (linkify) { + CommentParserHelper.detectLinks(theme, post, result.toString(), new SpannableString(result)); + } + + return result; + } + + private int getColor(Theme theme, Color color) { + switch (color) { + case INLINE_QUOTE: + return theme.inlineQuoteColor; + case QUOTE: + return theme.quoteColor; + } + return 0; + } + + private SpannableString applySpan(CharSequence text, List spans) { + SpannableString result = new SpannableString(text); + int l = result.length(); + + for (Object span : spans) { + if (span != null) { + result.setSpan(span, 0, l, 0); + } + } + + return result; + } + + public interface Action { + CharSequence execute(Theme theme, + PostParser.Callback callback, + Post.Builder post, + CharSequence text, + Element element); + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/sites/vichan/ViChanCommentParser.java b/Clover/app/src/main/java/org/floens/chan/core/site/sites/vichan/ViChanCommentParser.java index 2ef49c78..b78758e3 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/site/sites/vichan/ViChanCommentParser.java +++ b/Clover/app/src/main/java/org/floens/chan/core/site/sites/vichan/ViChanCommentParser.java @@ -17,31 +17,18 @@ */ package org.floens.chan.core.site.sites.vichan; -import android.text.SpannableString; - -import org.floens.chan.core.model.Post; import org.floens.chan.core.site.parser.CommentParser; -import org.floens.chan.core.site.parser.CommentParserHelper; -import org.floens.chan.ui.span.ForegroundColorSpanHashed; -import org.floens.chan.ui.theme.Theme; -import org.jsoup.nodes.Element; +import org.floens.chan.core.site.parser.StyleRule; import java.util.regex.Pattern; +import static org.floens.chan.core.site.parser.StyleRule.tagRule; + public class ViChanCommentParser extends CommentParser { public ViChanCommentParser() { setQuotePattern(Pattern.compile(".*#(\\d+)")); setFullQuotePattern(Pattern.compile("/(\\w+)/\\w+/(\\d+)\\.html#(\\d+)")); - } - @Override - public CharSequence handleParagraph(Theme theme, Post.Builder post, CharSequence text, Element element) { - if (element.hasClass("quote")) { - SpannableString res = span(text, new ForegroundColorSpanHashed(theme.inlineQuoteColor)); - CommentParserHelper.detectLinks(theme, post, res.toString(), res); - return res; - } else { - return text; - } + rule(tagRule("p").cssClass("quote").color(StyleRule.Color.INLINE_QUOTE).linkify()); } }