1package eu.siacs.conversations.ui.util;
2
3import eu.siacs.conversations.Config;
4import eu.siacs.conversations.utils.UIHelper;
5
6public class QuoteHelper {
7
8
9 public static final char QUOTE_CHAR = '>';
10 public static final char QUOTE_END_CHAR = '<'; // used for one check, not for actual quoting
11 public static final char QUOTE_ALT_CHAR = '»';
12 public static final char QUOTE_ALT_END_CHAR = '«';
13
14 public static boolean isPositionQuoteCharacter(CharSequence body, int pos) {
15 // second part of logical check actually goes against the logic indicated in the method name, since it also checks for context
16 // but it's very useful
17 return body.charAt(pos) == QUOTE_CHAR || isPositionAltQuoteStart(body, pos);
18 }
19
20 public static boolean isPositionQuoteEndCharacter(CharSequence body, int pos) {
21 return body.charAt(pos) == QUOTE_END_CHAR;
22 }
23
24 public static boolean isPositionAltQuoteCharacter(CharSequence body, int pos) {
25 return body.charAt(pos) == QUOTE_ALT_CHAR;
26 }
27
28 public static boolean isPositionAltQuoteEndCharacter(CharSequence body, int pos) {
29 return body.charAt(pos) == QUOTE_ALT_END_CHAR;
30 }
31
32 public static boolean isPositionAltQuoteStart(CharSequence body, int pos) {
33 return isPositionAltQuoteCharacter(body, pos)
34 && isPositionPrecededByPreQuote(body, pos)
35 && !isPositionFollowedByAltQuoteEnd(body, pos);
36 }
37
38 public static boolean isPositionFollowedByQuoteChar(CharSequence body, int pos) {
39 return body.length() > pos + 1 && isPositionQuoteCharacter(body, pos + 1);
40 }
41
42 /**
43 * 'Prequote' means anything we require or can accept in front of a QuoteChar.
44 */
45 public static boolean isPositionPrecededByPreQuote(CharSequence body, int pos) {
46 return UIHelper.isPositionPrecededByLineStart(body, pos);
47 }
48
49 public static boolean isPositionQuoteStart(CharSequence body, int pos) {
50 return (isPositionQuoteCharacter(body, pos)
51 && isPositionPrecededByPreQuote(body, pos)
52 && (UIHelper.isPositionFollowedByQuoteableCharacter(body, pos)
53 || isPositionFollowedByQuoteChar(body, pos)));
54 }
55
56 public static boolean bodyContainsQuoteStart(CharSequence body) {
57 for (int i = 0; i < body.length(); i++) {
58 if (isPositionQuoteStart(body, i)) {
59 return true;
60 }
61 }
62 return false;
63 }
64
65 public static boolean isPositionFollowedByAltQuoteEnd(CharSequence body, int pos) {
66 if (body.length() <= pos + 1 || Character.isWhitespace(body.charAt(pos + 1))) {
67 return false;
68 }
69 boolean previousWasWhitespace = false;
70 for (int i = pos + 1; i < body.length(); i++) {
71 char c = body.charAt(i);
72 if (c == '\n' || isPositionAltQuoteCharacter(body, i)) {
73 return false;
74 } else if (isPositionAltQuoteEndCharacter(body, i) && !previousWasWhitespace) {
75 return true;
76 } else {
77 previousWasWhitespace = Character.isWhitespace(c);
78 }
79 }
80 return false;
81 }
82
83 public static boolean isNestedTooDeeply(CharSequence line) {
84 if (isPositionQuoteStart(line, 0)) {
85 int nestingDepth = 1;
86 for (int i = 1; i < line.length(); i++) {
87 if (isPositionQuoteCharacter(line, i)) {
88 nestingDepth++;
89 } else if (line.charAt(i) != ' ') {
90 break;
91 }
92 }
93 return nestingDepth >= (Config.QUOTING_MAX_DEPTH);
94 }
95 return false;
96 }
97
98 public static String replaceAltQuoteCharsInText(String text) {
99 for (int i = 0; i < text.length(); i++) {
100 if (isPositionAltQuoteStart(text, i)) {
101 text = text.substring(0, i) + QUOTE_CHAR + text.substring(i + 1);
102 }
103 }
104 return text;
105 }
106
107 public static String quote(String text) {
108 text = replaceAltQuoteCharsInText(text);
109 return text
110 // first replace all '>' at the beginning of the line with nice and tidy '>>'
111 // for nested quoting
112 .replaceAll("(^|\n)(" + QUOTE_CHAR + ")", "$1$2$2")
113 // then find all other lines and have them start with a '> '
114 .replaceAll("(^|\n)(?!" + QUOTE_CHAR + ")(.*)", "$1> $2")
115 ;
116 }
117}