1 // This file is part of Desktop App Toolkit,
2 // a set of libraries for developing nice desktop applications.
3 //
4 // For license and copyright information please follow this link:
5 // https://github.com/desktop-app/legal/blob/master/LEGAL
6 //
7 #pragma once
8 
9 #include "base/basic_types.h"
10 #include "base/algorithm.h"
11 
12 #include <QtCore/QList>
13 #include <QtCore/QVector>
14 #include <QtGui/QClipboard>
15 
16 enum class EntityType : uchar {
17 	Invalid = 0,
18 
19 	Url,
20 	CustomUrl,
21 	Email,
22 	Hashtag,
23 	Cashtag,
24 	Mention,
25 	MentionName,
26 	BotCommand,
27 	MediaTimestamp,
28 
29 	Bold,
30 	Semibold,
31 	Italic,
32 	Underline,
33 	StrikeOut,
34 	Code, // inline
35 	Pre,  // block
36 };
37 
38 enum class EntityLinkShown : uchar {
39 	Full,
40 	Partial,
41 };
42 
43 struct EntityLinkData {
44 	QString text;
45 	QString data;
46 	EntityType type = EntityType::Invalid;
47 	EntityLinkShown shown = EntityLinkShown::Full;
48 };
49 
50 class EntityInText;
51 using EntitiesInText = QList<EntityInText>;
52 
53 class EntityInText {
54 public:
55 	EntityInText(
56 		EntityType type,
57 		int offset,
58 		int length,
59 		const QString &data = QString());
60 
type()61 	EntityType type() const {
62 		return _type;
63 	}
offset()64 	int offset() const {
65 		return _offset;
66 	}
length()67 	int length() const {
68 		return _length;
69 	}
data()70 	QString data() const {
71 		return _data;
72 	}
73 
extendToLeft(int extent)74 	void extendToLeft(int extent) {
75 		_offset -= extent;
76 		_length += extent;
77 	}
shrinkFromRight(int shrink)78 	void shrinkFromRight(int shrink) {
79 		_length -= shrink;
80 	}
shiftLeft(int shift)81 	void shiftLeft(int shift) {
82 		_offset -= shift;
83 		if (_offset < 0) {
84 			_length += _offset;
85 			_offset = 0;
86 			if (_length < 0) {
87 				_length = 0;
88 			}
89 		}
90 	}
shiftRight(int shift)91 	void shiftRight(int shift) {
92 		_offset += shift;
93 	}
updateTextEnd(int textEnd)94 	void updateTextEnd(int textEnd) {
95 		if (_offset > textEnd) {
96 			_offset = textEnd;
97 			_length = 0;
98 		} else if (_offset + _length > textEnd) {
99 			_length = textEnd - _offset;
100 		}
101 	}
102 
103 	static int FirstMonospaceOffset(
104 		const EntitiesInText &entities,
105 		int textLength);
106 
107 	explicit operator bool() const {
108 		return type() != EntityType::Invalid;
109 	}
110 
111 private:
112 	EntityType _type = EntityType::Invalid;
113 	int _offset = 0;
114 	int _length = 0;
115 	QString _data;
116 
117 };
118 
119 inline bool operator==(const EntityInText &a, const EntityInText &b) {
120 	return (a.type() == b.type())
121 		&& (a.offset() == b.offset())
122 		&& (a.length() == b.length())
123 		&& (a.data() == b.data());
124 }
125 
126 inline bool operator!=(const EntityInText &a, const EntityInText &b) {
127 	return !(a == b);
128 }
129 
130 struct TextWithEntities {
131 	QString text;
132 	EntitiesInText entities;
133 
emptyTextWithEntities134 	bool empty() const {
135 		return text.isEmpty();
136 	}
137 
138 	void reserve(int size, int entitiesCount = 0) {
139 		text.reserve(size);
140 		entities.reserve(entitiesCount);
141 	}
142 
appendTextWithEntities143 	TextWithEntities &append(TextWithEntities &&other) {
144 		const auto shift = text.size();
145 		for (auto &entity : other.entities) {
146 			entity.shiftRight(shift);
147 		}
148 		text.append(other.text);
149 		entities.append(other.entities);
150 		return *this;
151 	}
appendTextWithEntities152 	TextWithEntities &append(const QString &other) {
153 		text.append(other);
154 		return *this;
155 	}
appendTextWithEntities156 	TextWithEntities &append(QLatin1String other) {
157 		text.append(other);
158 		return *this;
159 	}
appendTextWithEntities160 	TextWithEntities &append(QChar other) {
161 		text.append(other);
162 		return *this;
163 	}
164 
SimpleTextWithEntities165 	static TextWithEntities Simple(const QString &simple) {
166 		auto result = TextWithEntities();
167 		result.text = simple;
168 		return result;
169 	}
170 };
171 
172 inline bool operator==(
173 		const TextWithEntities &a,
174 		const TextWithEntities &b) {
175 	return (a.text == b.text) && (a.entities == b.entities);
176 }
177 
178 inline bool operator!=(
179 		const TextWithEntities &a,
180 		const TextWithEntities &b) {
181 	return !(a == b);
182 }
183 
184 struct TextForMimeData {
185 	QString expanded;
186 	TextWithEntities rich;
187 
emptyTextForMimeData188 	bool empty() const {
189 		return expanded.isEmpty();
190 	}
191 
192 	void reserve(int size, int entitiesCount = 0) {
193 		expanded.reserve(size);
194 		rich.reserve(size, entitiesCount);
195 	}
appendTextForMimeData196 	TextForMimeData &append(TextForMimeData &&other) {
197 		expanded.append(other.expanded);
198 		rich.append(std::move(other.rich));
199 		return *this;
200 	}
appendTextForMimeData201 	TextForMimeData &append(TextWithEntities &&other) {
202 		expanded.append(other.text);
203 		rich.append(std::move(other));
204 		return *this;
205 	}
appendTextForMimeData206 	TextForMimeData &append(const QString &other) {
207 		expanded.append(other);
208 		rich.append(other);
209 		return *this;
210 	}
appendTextForMimeData211 	TextForMimeData &append(QLatin1String other) {
212 		expanded.append(other);
213 		rich.append(other);
214 		return *this;
215 	}
appendTextForMimeData216 	TextForMimeData &append(QChar other) {
217 		expanded.append(other);
218 		rich.append(other);
219 		return *this;
220 	}
221 
RichTextForMimeData222 	static TextForMimeData Rich(TextWithEntities &&rich) {
223 		auto result = TextForMimeData();
224 		result.expanded = rich.text;
225 		result.rich = std::move(rich);
226 		return result;
227 	}
SimpleTextForMimeData228 	static TextForMimeData Simple(const QString &simple) {
229 		auto result = TextForMimeData();
230 		result.expanded = result.rich.text = simple;
231 		return result;
232 	}
233 };
234 
235 enum {
236 	TextParseMultiline = 0x001,
237 	TextParseLinks = 0x002,
238 	TextParseRichText = 0x004,
239 	TextParseMentions = 0x008,
240 	TextParseHashtags = 0x010,
241 	TextParseBotCommands = 0x020,
242 	TextParseMarkdown = 0x040,
243 };
244 
245 struct TextWithTags {
246 	struct Tag {
247 		int offset = 0;
248 		int length = 0;
249 		QString id;
250 	};
251 	using Tags = QVector<Tag>;
252 
253 	QString text;
254 	Tags tags;
255 };
256 
257 inline bool operator==(const TextWithTags::Tag &a, const TextWithTags::Tag &b) {
258 	return (a.offset == b.offset) && (a.length == b.length) && (a.id == b.id);
259 }
260 inline bool operator!=(const TextWithTags::Tag &a, const TextWithTags::Tag &b) {
261 	return !(a == b);
262 }
263 
264 inline bool operator==(const TextWithTags &a, const TextWithTags &b) {
265 	return (a.text == b.text) && (a.tags == b.tags);
266 }
267 inline bool operator!=(const TextWithTags &a, const TextWithTags &b) {
268 	return !(a == b);
269 }
270 
271 // Parsing helpers.
272 
273 namespace TextUtilities {
274 
275 bool IsValidProtocol(const QString &protocol);
276 bool IsValidTopDomain(const QString &domain);
277 
278 const QRegularExpression &RegExpMailNameAtEnd();
279 const QRegularExpression &RegExpHashtag();
280 const QRegularExpression &RegExpHashtagExclude();
281 const QRegularExpression &RegExpMention();
282 const QRegularExpression &RegExpBotCommand();
283 QString MarkdownBoldGoodBefore();
284 QString MarkdownBoldBadAfter();
285 QString MarkdownItalicGoodBefore();
286 QString MarkdownItalicBadAfter();
287 QString MarkdownStrikeOutGoodBefore();
288 QString MarkdownStrikeOutBadAfter();
289 QString MarkdownCodeGoodBefore();
290 QString MarkdownCodeBadAfter();
291 QString MarkdownPreGoodBefore();
292 QString MarkdownPreBadAfter();
293 
294 // Text preprocess.
295 QString Clean(const QString &text);
296 QString EscapeForRichParsing(const QString &text);
297 QString SingleLine(const QString &text);
298 TextWithEntities SingleLine(const TextWithEntities &text);
299 QString RemoveAccents(const QString &text);
300 QString RemoveEmoji(const QString &text);
301 QStringList PrepareSearchWords(const QString &query, const QRegularExpression *SplitterOverride = nullptr);
302 bool CutPart(TextWithEntities &sending, TextWithEntities &left, int limit);
303 
304 struct MentionNameFields {
305 	MentionNameFields(uint64 userId = 0, uint64 accessHash = 0)
userIdMentionNameFields306 	: userId(userId), accessHash(accessHash) {
307 	}
308 	uint64 userId = 0;
309 	uint64 accessHash = 0;
310 };
311 
MentionNameDataToFields(const QString & data)312 inline MentionNameFields MentionNameDataToFields(const QString &data) {
313 	auto components = data.split('.');
314 	if (!components.isEmpty()) {
315 		return {
316 			components.at(0).toULongLong(),
317 			(components.size() > 1) ? components.at(1).toULongLong() : 0
318 		};
319 	}
320 	return MentionNameFields{};
321 }
322 
MentionNameDataFromFields(const MentionNameFields & fields)323 inline QString MentionNameDataFromFields(const MentionNameFields &fields) {
324 	auto result = QString::number(fields.userId);
325 	if (fields.accessHash) {
326 		result += '.' + QString::number(fields.accessHash);
327 	}
328 	return result;
329 }
330 
331 // New entities are added to the ones that are already in result.
332 // Changes text if (flags & TextParseMarkdown).
333 TextWithEntities ParseEntities(const QString &text, int32 flags);
334 void ParseEntities(TextWithEntities &result, int32 flags, bool rich = false);
335 
336 void PrepareForSending(TextWithEntities &result, int32 flags);
337 void Trim(TextWithEntities &result);
338 
339 enum class PrepareTextOption {
340 	IgnoreLinks,
341 	CheckLinks,
342 };
343 inline QString PrepareForSending(const QString &text, PrepareTextOption option = PrepareTextOption::IgnoreLinks) {
344 	auto result = TextWithEntities { text };
345 	auto prepareFlags = (option == PrepareTextOption::CheckLinks) ? (TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands) : 0;
346 	PrepareForSending(result, prepareFlags);
347 	return result.text;
348 }
349 
350 // Replace bad symbols with space and remove '\r'.
351 void ApplyServerCleaning(TextWithEntities &result);
352 
353 [[nodiscard]] int SerializeTagsSize(const TextWithTags::Tags &tags);
354 [[nodiscard]] QByteArray SerializeTags(const TextWithTags::Tags &tags);
355 [[nodiscard]] TextWithTags::Tags DeserializeTags(
356 	QByteArray data,
357 	int textLength);
358 [[nodiscard]] QString TagsMimeType();
359 [[nodiscard]] QString TagsTextMimeType();
360 
361 inline const auto kMentionTagStart = qstr("mention://user.");
362 
363 [[nodiscard]] bool IsMentionLink(QStringView link);
364 [[nodiscard]] bool IsSeparateTag(QStringView tag);
365 [[nodiscard]] QString JoinTag(const QList<QStringView> &list);
366 [[nodiscard]] QString TagWithRemoved(
367 	const QString &tag,
368 	const QString &removed);
369 [[nodiscard]] QString TagWithAdded(const QString &tag, const QString &added);
370 
371 EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags);
372 TextWithTags::Tags ConvertEntitiesToTextTags(
373 	const EntitiesInText &entities);
374 std::unique_ptr<QMimeData> MimeDataFromText(const TextForMimeData &text);
375 std::unique_ptr<QMimeData> MimeDataFromText(TextWithTags &&text);
376 void SetClipboardText(
377 	const TextForMimeData &text,
378 	QClipboard::Mode mode = QClipboard::Clipboard);
379 
380 } // namespace TextUtilities
381