From 55bc3a1a245d9efc9e5f5801b705d656c5100383 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Sun, 13 Oct 2019 00:38:44 +0300 Subject: [PATCH] #9 Implemented RTF to HTML conversion according to RTF spec --- .../rtf/SimpleRTF2HTMLConverter.java | 271 +++--- .../rtf/util/CharsetHelper.java | 4 +- .../HighoverEmailsTest.java | 10 +- .../outlookmessageparser/TestUtils.java | 37 + .../rtf/SimpleRTF2HTMLConverterTest.java | 37 + .../test-messages/rtf-to-html-input.rtf | 746 +++++++++++++++ .../test-messages/rtf-to-html-output.html | 874 ++++++++++++++++++ 7 files changed, 1834 insertions(+), 145 deletions(-) create mode 100644 src/test/java/org/simplejavamail/outlookmessageparser/TestUtils.java create mode 100644 src/test/java/org/simplejavamail/outlookmessageparser/rtf/SimpleRTF2HTMLConverterTest.java create mode 100644 src/test/resources/test-messages/rtf-to-html-input.rtf create mode 100644 src/test/resources/test-messages/rtf-to-html-output.html diff --git a/src/main/java/org/simplejavamail/outlookmessageparser/rtf/SimpleRTF2HTMLConverter.java b/src/main/java/org/simplejavamail/outlookmessageparser/rtf/SimpleRTF2HTMLConverter.java index be1a13a..70539ff 100644 --- a/src/main/java/org/simplejavamail/outlookmessageparser/rtf/SimpleRTF2HTMLConverter.java +++ b/src/main/java/org/simplejavamail/outlookmessageparser/rtf/SimpleRTF2HTMLConverter.java @@ -18,9 +18,10 @@ import org.simplejavamail.outlookmessageparser.rtf.util.CharsetHelper; import java.nio.charset.Charset; +import java.util.LinkedList; import java.util.regex.Matcher; +import java.util.regex.Pattern; -import static java.util.regex.Pattern.compile; import static org.simplejavamail.outlookmessageparser.rtf.util.ByteUtil.hexToString; import static org.simplejavamail.outlookmessageparser.rtf.util.CharsetHelper.WINDOWS_CHARSET; @@ -28,140 +29,138 @@ * This class is intended to be used for certain RTF related operations such as extraction of plain HTML from an RTF text. */ public class SimpleRTF2HTMLConverter implements RTF2HTMLConverter { + private static Pattern CONTROL_WORD = Pattern.compile("\\\\(([^a-zA-Z])|(([a-zA-Z]+)(-?[\\d]*) ?))"); + private static Pattern ENCODED_CHARACTER = Pattern.compile("\\\\'([0-9a-fA-F]{2})"); - private static final String[] HTML_START_TAGS = { "", "", "" }; - - public String rtf2html(final String rtf) { - if (rtf != null) { - final Charset charset = extractCodepage(rtf); - String plain = fetchHtmlSection(rtf); - plain = replaceSpecialSequences(plain); // first step, remove known control words or else we'll match single escape hex values in the next step - plain = replaceHexSequences(plain, "(?:\\\\f\\d(?:\\\\'..)+)", WINDOWS_CHARSET); // match all header control values with default charset - plain = replaceHexSequences(plain, "(?:\\\\'..)+", charset); // match all remaining escaped hex values as encoded text (which might be DBCS like CP936) - plain = cleanupRemainingSequences(plain); - plain = replaceLineBreaks(plain); - return plain; - } - return null; - } - - private String cleanupRemainingSequences(String plain) { - return plain - .replaceAll("(\\\\f\\d.+?;)+", "") // clear all \f sequences including fontnames like Courier new - .replaceAll("\\\\\\S+", "") // filtering all remaining \ like e.g.: \htmlrtf - .replaceAll("BM__MailAutoSig((?s).*?(?-s))BM__MailAutoSig", "$1"); - } - - private Charset extractCodepage(String rtf) { - Matcher codePageMatcher = compile("(?:\\\\ansicpg(?.+?)\\\\)+").matcher(rtf); - if (codePageMatcher.find()) { - return CharsetHelper.findCharset(codePageMatcher.group("codePage")); - } else { - return WINDOWS_CHARSET; // fallback - } - } - - /** - * @return The text with removed newlines as they are only part of the RTF document and should not be inside the HTML. - */ - private String replaceLineBreaks(final String text) { - return text - .replaceAll("(
(
)+)", "
") - .replaceAll("\\r\\n", "\n") - .replaceAll("[\\r\\u0000]", ""); - } - - /** - * @return The text with replaced special characters that denote hex codes for strings using Windows CP1252 encoding. - */ - private String replaceHexSequences(final String text, String sequencesToMatch, final Charset charset) { - final StringBuilder res = new StringBuilder(); - int lastPosition = 0; - - final Matcher escapedHexGroupMatcher = compile(sequencesToMatch).matcher(text); - while (escapedHexGroupMatcher.find()) { - res.append(text, lastPosition, escapedHexGroupMatcher.start()); - - StringBuilder hexText = new StringBuilder(); - - String escapedHexGroup = escapedHexGroupMatcher.group(0); - final Matcher unescapedHexCharacterMatcher = compile("\\\\'(..)").matcher(escapedHexGroup); - while (unescapedHexCharacterMatcher.find()) { - hexText.append(unescapedHexCharacterMatcher.group(1)); - } - - res.append(hexToString(hexText.toString(), charset)); - - lastPosition = escapedHexGroupMatcher.end(); - } - - if (res.length() == 0) { - res.append(text); - } else { - res.append(text, lastPosition, text.length()); - } - - return res.toString(); - } - - /** - * @return The actual HTML block / section only but still with RTF code inside (still needs to be cleaned). - */ - private String fetchHtmlSection(final String text) { - int htmlStart = -1; - int htmlEnd = -1; - - //determine html tags - for (int i = 0; i < HTML_START_TAGS.length && htmlStart < 0; i++) { - htmlStart = text.indexOf(HTML_START_TAGS[i]); - } - for (int i = 0; i < HTML_END_TAGS.length && htmlEnd < 0; i++) { - htmlEnd = text.indexOf(HTML_END_TAGS[i]); - if (htmlEnd > 0) { - htmlEnd = htmlEnd + HTML_END_TAGS[i].length(); - } - } - - if (htmlStart > -1 && htmlEnd > -1) { - //trim rtf code - return text.substring(htmlStart, htmlEnd + 1); - } else { - //embed code within html tags - String html = "" + text + ""; - //replace linebreaks with html breaks - html = html.replaceAll("[\\n\\r]+", " "); - //create hyperlinks - html = html.replaceAll("(http://\\S+)", "$1"); - return html.replaceAll("mailto:(\\S+@\\S+)", "$1"); - } - } - - /** - * @return The text with special sequences replaced by equivalent representations. - */ - private String replaceSpecialSequences(final String text) { - String replacedText = text; - //filtering whatever color control sequence, e.g. {\sp{\sn fillColor}{\sv 14935011}}{\sp{\sn fFilled}{\sv 1}} - replacedText = replacedText.replaceAll("\\{\\\\S+ [^\\s\\\\}]*\\}", ""); - //filtering hyperlink sequences like {HYPERLINK "http://xyz.com/print.jpg"} - replacedText = replacedText.replaceAll("\\{HYPERLINK[^\\}]*\\}", ""); - //filtering plain replacedText sequences like {\pntext *\tab} - replacedText = replacedText.replaceAll("\\{\\\\pntext[^\\}]*\\}", ""); - //filtering embedded tags like {\*\htmltag84 +} - replacedText = replacedText.replaceAll("\\{\\\\\\*\\\\htmltag\\d+ (&[#\\w]+;)}\\\\htmlrtf.*\\\\htmlrtf0 ", "$1"); - //filtering curly braces that are NOT escaped with backslash }, thus marking the end of an RTF sequence - replacedText = replacedText.replaceAll("([^\\\\])" + "\\}+", "$1"); - replacedText = replacedText.replaceAll("([^\\\\])" + "\\{+", "$1"); - //filtering curly braces that are escaped with backslash \}, thus representing an actual brace - replacedText = replacedText.replaceAll("\\\\\\}", "}"); - replacedText = replacedText.replaceAll("\\\\\\{", "{"); - //filtering \par sequences - replacedText = replacedText.replaceAll("\\\\pard*", "\n"); - //filtering \tab sequences - replacedText = replacedText.replaceAll("\\\\tab", "\t"); - //filtering \*\ like e.g.: \*\fldinst - replacedText = replacedText.replaceAll("\\\\\\*\\\\\\S+", ""); - return replacedText; - } + public String rtf2html(String rtf) { + Charset charset = WINDOWS_CHARSET; + + // RTF processing requires stack holding current settings, each group adds new settings to stack + LinkedList groupStack = new LinkedList<>(); + groupStack.add(new Group()); + + Matcher controlWordMatcher = CONTROL_WORD.matcher(rtf); + Matcher encodedCharMatcher = ENCODED_CHARACTER.matcher(rtf); + StringBuilder result = new StringBuilder(); + int length = rtf.length(); + int charIndex = 0; + + while (charIndex < length) { + char c = rtf.charAt(charIndex); + Group currentGroup = groupStack.getFirst(); + if (c == '\r' || c == '\n') { + charIndex++; + } else if (c == '{') { //entering group + groupStack.addFirst(currentGroup.copy()); + charIndex++; + } else if (c == '}') { //exiting group + groupStack.removeFirst(); + //Not outputting anything after last closing brace matching opening brace. + if (groupStack.size() == 1) { + break; + } + charIndex++; + } else if (c == '\\') { + + // matching ansi-encoded sequences like \'f5\'93 + encodedCharMatcher.region(charIndex, length); + if (encodedCharMatcher.lookingAt()) { + StringBuilder encodedSequence = new StringBuilder(); + while (encodedCharMatcher.lookingAt()) { + encodedSequence.append(encodedCharMatcher.group(1)); + charIndex += 4; + encodedCharMatcher.region(charIndex, length); + } + String decoded = hexToString(encodedSequence.toString(), charset); + append(result, decoded, currentGroup); + continue; + } + + // set matcher to current char position and match from it + controlWordMatcher.region(charIndex, length); + if (!controlWordMatcher.lookingAt()) { + throw new IllegalStateException("RTF file has invalid structure. Failed to match character '" + + c + "' at [" + charIndex + "/" + length + "] to a control symbol or word."); + } + + //checking for control symbol or control word + //control word can have optional number following it and the option space as well + Integer controlNumber = null; + String controlWord = controlWordMatcher.group(2); // group(2) matches control symbol + if (controlWord == null) { + controlWord = controlWordMatcher.group(4); // group(2) matches control word + String controlNumberString = controlWordMatcher.group(5); + if (!"".equals(controlNumberString)) { + controlNumber = Integer.valueOf(controlNumberString); + } + } + charIndex += controlWordMatcher.end() - controlWordMatcher.start(); + + switch (controlWord) { + case "par": + append(result, "\n", currentGroup); + break; + case "tab": + append(result, "\t", currentGroup); + break; + case "htmlrtf": + //htmlrtf starts ignored text area, htmlrtf0 ends it + //Though technically this is not a group, it's easier to treat it as such to ignore everything in between + currentGroup.htmlRtf = controlNumber == null; + break; + case "ansicpg": + //charset definition is important for decoding ansi encoded values + charset = CharsetHelper.findCharset(controlNumber); + break; + case "fonttbl": // skipping these groups contents - these are font and color settings + case "colortbl": + currentGroup.ignore = true; + break; + case "uc": // This denotes a number of characters to skip after unicode symbols + currentGroup.unicodeCharLength = controlNumber == null ? 1 : controlNumber; + break; + case "u": // Unicode symbols + if (controlNumber != null) { + char unicodeSymbol = (char) controlNumber.intValue(); + append(result, Character.toString(unicodeSymbol), currentGroup); + charIndex += currentGroup.unicodeCharLength; + } + break; + case "{": // Escaped characters + case "}": + case "\\": + append(result, controlWord, currentGroup); + break; + default: + } + + } else { + append(result, c + "", currentGroup); + charIndex++; + } + } + return result.toString(); + } + + + private void append(StringBuilder result, String symbol, Group group) { + if (group.ignore || group.htmlRtf) { + return; + } + result.append(symbol); + } + + private static class Group { + boolean ignore = false; + int unicodeCharLength = 1; + boolean htmlRtf = false; + + Group copy() { + Group newGroup = new Group(); + newGroup.ignore = this.ignore; + newGroup.unicodeCharLength = this.unicodeCharLength; + newGroup.htmlRtf = this.htmlRtf; + return newGroup; + } + } } \ No newline at end of file diff --git a/src/main/java/org/simplejavamail/outlookmessageparser/rtf/util/CharsetHelper.java b/src/main/java/org/simplejavamail/outlookmessageparser/rtf/util/CharsetHelper.java index 2338e0a..cd45b3d 100644 --- a/src/main/java/org/simplejavamail/outlookmessageparser/rtf/util/CharsetHelper.java +++ b/src/main/java/org/simplejavamail/outlookmessageparser/rtf/util/CharsetHelper.java @@ -23,7 +23,7 @@ public class CharsetHelper { public static final Charset WINDOWS_CHARSET = Charset.forName("CP1252"); - public static Charset findCharset(String rtfCodePage) { + public static Charset findCharset(Integer rtfCodePage) { for (String prefix : CHARSET_PREFIXES) { try { return Charset.forName(prefix + rtfCodePage); @@ -31,6 +31,6 @@ public static Charset findCharset(String rtfCodePage) { // ignore } } - throw new UnsupportedCharsetException(rtfCodePage); + throw new UnsupportedCharsetException("" + rtfCodePage); } } \ No newline at end of file diff --git a/src/test/java/org/simplejavamail/outlookmessageparser/HighoverEmailsTest.java b/src/test/java/org/simplejavamail/outlookmessageparser/HighoverEmailsTest.java index f4a75ef..5aca9ab 100644 --- a/src/test/java/org/simplejavamail/outlookmessageparser/HighoverEmailsTest.java +++ b/src/test/java/org/simplejavamail/outlookmessageparser/HighoverEmailsTest.java @@ -28,10 +28,11 @@ import java.io.IOException; import java.io.InputStream; import java.util.List; -import java.util.Scanner; import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; +import static org.simplejavamail.outlookmessageparser.TestUtils.classpathFileToString; +import static org.simplejavamail.outlookmessageparser.TestUtils.normalizeText; public class HighoverEmailsTest { @@ -492,8 +493,7 @@ public void testChineseMessage() "酒店研发部\n" + " \n"); - InputStream resourceAsStream = OutlookMessageParser.class.getClassLoader().getResourceAsStream("test-messages/chinese message.html"); - String expectedHtml = new Scanner(resourceAsStream, UTF_8.name()).useDelimiter("\\A").next(); + String expectedHtml = classpathFileToString("/test-messages/chinese message.html", UTF_8); assertThat(normalizeText(msg.getConvertedBodyHTML())).isEqualTo(normalizeText(expectedHtml)); } @@ -803,10 +803,6 @@ private static OutlookRecipient createRecipient(String toName, String toEmail) { return recipient; } - private static String normalizeText(String text) { - return text.replaceAll("\\r\\n", "\n").replaceAll("\\r", "\n"); - } - private static OutlookMessage parseMsgFile(String msgPath) throws IOException { InputStream resourceAsStream = OutlookMessageParser.class.getClassLoader().getResourceAsStream(msgPath); diff --git a/src/test/java/org/simplejavamail/outlookmessageparser/TestUtils.java b/src/test/java/org/simplejavamail/outlookmessageparser/TestUtils.java new file mode 100644 index 0000000..3722a4f --- /dev/null +++ b/src/test/java/org/simplejavamail/outlookmessageparser/TestUtils.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) ${project.inceptionYear} Benny Bottema (benny@bennybottema.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.simplejavamail.outlookmessageparser; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class TestUtils { + public static String classpathFileToString(String classPathFile, Charset charset) { + try { + return new String(Files.readAllBytes(Paths.get(TestUtils.class.getResource(classPathFile).toURI()))); + } catch (IOException | URISyntaxException e) { + throw new RuntimeException(e); + } + } + + + public static String normalizeText(String text) { + return text.replaceAll("\\r\\n", "\n").replaceAll("\\r", "\n"); + } +} diff --git a/src/test/java/org/simplejavamail/outlookmessageparser/rtf/SimpleRTF2HTMLConverterTest.java b/src/test/java/org/simplejavamail/outlookmessageparser/rtf/SimpleRTF2HTMLConverterTest.java new file mode 100644 index 0000000..e57253b --- /dev/null +++ b/src/test/java/org/simplejavamail/outlookmessageparser/rtf/SimpleRTF2HTMLConverterTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) ${project.inceptionYear} Benny Bottema (benny@bennybottema.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.simplejavamail.outlookmessageparser.rtf; + +import org.junit.Test; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertEquals; +import static org.simplejavamail.outlookmessageparser.TestUtils.classpathFileToString; +import static org.simplejavamail.outlookmessageparser.TestUtils.normalizeText; + +public class SimpleRTF2HTMLConverterTest { + + @Test + public void testConversion() { + SimpleRTF2HTMLConverter converter = new SimpleRTF2HTMLConverter(); + String rtf = classpathFileToString("/test-messages/rtf-to-html-input.rtf", UTF_8); + String expectedHtml = classpathFileToString("/test-messages/rtf-to-html-output.html", UTF_8); + + String html = converter.rtf2html(rtf); + assertEquals(normalizeText(expectedHtml), normalizeText(html)); + } + +} \ No newline at end of file diff --git a/src/test/resources/test-messages/rtf-to-html-input.rtf b/src/test/resources/test-messages/rtf-to-html-input.rtf new file mode 100644 index 0000000..f0bbe3a --- /dev/null +++ b/src/test/resources/test-messages/rtf-to-html-input.rtf @@ -0,0 +1,746 @@ +{\rtf1\ansi\ansicpg1251\fromhtml1 \fbidis \deff0{\fonttbl + +{\f0\fswiss\fcharset204 Arial;} + +{\f1\fmodern Courier New;} + +{\f2\fnil\fcharset2 Symbol;} + +{\f3\fmodern\fcharset0 Courier New;}} + +{\colortbl\red0\green0\blue0;\red0\green0\blue255;} + +\uc1\pard\plain\deftab360 \f0\fs24 + +{\*\htmltag243 } + +{\*\htmltag3 \par } + +{\*\htmltag19 }\htmlrtf \lang9 \htmlrtf0 + +{\*\htmltag2 \par } + +{\*\htmltag34 } + +{\*\htmltag1 \par } + +{\*\htmltag241 } + +{\*\htmltag1 \par } + +{\*\htmltag241 } + +{\*\htmltag241 } +{\*\htmltag1 \par } +{\*\htmltag41 } +{\*\htmltag2 \par } +{\*\htmltag50 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96 }\htmlrtf {\pard\plain \f0\fs24 \htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag64 }\htmlrtf {\htmlrtf0 +{\*\htmltag4 \par } +{\*\htmltag84 } +{\*\htmltag148 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag72 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag104 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag104
}\htmlrtf {\htmlrtf0 +{\*\htmltag4 \par } +{\*\htmltag84 }\htmlrtf }\htmlrtf0 \htmlrtf}\htmlrtf0 + +{\*\htmltag96
}\htmlrtf {\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96 }\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 }{\*\htmltag64}\htmlrtf {\htmlrtf0 \'c7\'e4\'f0\'e0\'e2\'f1\'f2\'e2\'f3\'e9\'f2\'e5 +{\*\htmltag84 }\htmlrtf {\b \htmlrtf0 ! +{\*\htmltag92 }\htmlrtf }\htmlrtf0 +{\*\htmltag4 \par }\htmlrtf \htmlrtf0 +{\*\htmltag84 } +{\*\htmltag4 \par } +{\*\htmltag84 }{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag0 \par } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96 }\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 {\*\htmltag64}\htmlrtf {\htmlrtf0 \'cf\'ee\'eb\'fc\'e7\'f3\'e9\'f2\'e5\'f1\'fc \'cf\'ee\'f7\'f2\'ee\'e9 \'f1 \'ec\'ee\'e1\'e8\'eb\'fc\'ed\'ee\'e3\'ee{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 {\*\htmltag64}\htmlrtf {\htmlrtf0 \'cf\'ee\'f7\'f2\'e0 \'e2\'f1\'e5\'e3\'e4\'e0 \'f0\'ff\'e4\'ee\'ec, \'e4\'e0\'e6\'e5 \'e5\'f1\'eb\'e8 \'e2\'fb \'e4\'e0\'eb\'e5\'ea\'ee \'ee\'f2 \'ea\'ee\'ec\'ef\'fc\'fe\'f2\'e5\'f0\'e0. \'d7\'e8\'f2\'e0\'e9\'f2\'e5 \'e8 \'ee\'f2\'ef\'f0\'e0\'e2\'eb\'ff\'e9\'f2\'e5 \'ef\'e8\'f1\'fc\'ec\'e0, \'ef\'f0\'e8\'ea\'f0\'e5\'ef\'eb\'ff\'e9\'f2\'e5 \'e8 \'ef\'f0\'ee\'f1\'ec\'e0\'f2\'f0\'e8\'e2\'e0\'e9\'f2\'e5 \'f4\'e0\'e9\'eb\'fb, \'ed\'e0\'f5\'ee\'e4\'ff\'f1\'fc \'e2 \'eb\'fe\'e1\'ee\'e9 \'f2\'ee\'f7\'ea\'e5 \'c7\'e5\'ec\'eb\'e8.{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 }{\*\htmltag64}\htmlrtf {\htmlrtf0 +{\*\htmltag84 }\htmlrtf {\field{\*\fldinst{HYPERLINK "https://mailer.mail.ru/media/letter/static/2019/4/12/FMOsCMR4gfUXmge/img/head.png"}}{\fldrslt\cf1\ul }}\htmlrtf0 +{\*\htmltag4 \par } +{\*\htmltag84 } +{\*\htmltag244 } +{\*\htmltag4 \par } +{\*\htmltag84 } +{\*\htmltag244 } +{\*\htmltag4 \par } +{\*\htmltag84 }{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 {\*\htmltag64}\htmlrtf {\htmlrtf0 \'d3\'f1\'f2\'e0\'ed\'ee\'e2\'e8\'f2\'e5 \'ef\'f0\'e8\'eb\'ee\'e6\'e5\'ed\'e8\'e5{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96 }\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96 }\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 }{\*\htmltag64}\htmlrtf {\htmlrtf0 +{\*\htmltag84 }\htmlrtf {\field{\*\fldinst{HYPERLINK "https://mailer.mail.ru/media/letter/static/2019/4/12/FMOsCMR4gfUXmge/img/006.png"}}{\fldrslt\cf1\ul }}\htmlrtf0 +{\*\htmltag4 \par } +{\*\htmltag84 } +{\*\htmltag244 } +{\*\htmltag4 \par } +{\*\htmltag84 } +{\*\htmltag244 } +{\*\htmltag4 \par } +{\*\htmltag84 }{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 {\*\htmltag64}\htmlrtf {\htmlrtf0 \'cc\'e3\'ed\'ee\'e2\'e5\'ed\'ed\'fb\'e5 \'f3\'e2\'e5\'e4\'ee\'ec\'eb\'e5\'ed\'e8\'ff{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 {\*\htmltag64}\htmlrtf {\htmlrtf0 \'d3\'e7\'ed\'e0\'e2\'e0\'e9\'f2\'e5 \'ee \'ed\'ee\'e2\'fb\'f5 \'ef\'e8\'f1\'fc\'ec\'e0\'f5 \'ec\'e3\'ed\'ee\'e2\'e5\'ed\'ed\'ee. \'cf\'f0\'ee\'e2\'e5\'f0\'ff\'f2\'fc \'ef\'ee\'f7\'f2\'f3 \'e2\'f0\'f3\'f7\'ed\'f3\'fe \'ed\'e5 \'ed\'f3\'e6\'ed\'ee.{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 }{\*\htmltag64}\htmlrtf {\htmlrtf0 +{\*\htmltag84 }\htmlrtf {\field{\*\fldinst{HYPERLINK "https://mailer.mail.ru/media/letter/static/2019/4/12/FMOsCMR4gfUXmge/img/005.png"}}{\fldrslt\cf1\ul }}\htmlrtf0 +{\*\htmltag4 \par } +{\*\htmltag84 } +{\*\htmltag244 } +{\*\htmltag4 \par } +{\*\htmltag84 } +{\*\htmltag244 } +{\*\htmltag4 \par } +{\*\htmltag84 }{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 {\*\htmltag64}\htmlrtf {\htmlrtf0 \'cf\'f0\'ee\'f1\'f2\'ee\'e9 \'e8 \'f3\'e4\'ee\'e1\'ed\'fb\'e9 \'e8\'ed\'f2\'e5\'f0\'f4\'e5\'e9\'f1{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 {\*\htmltag64}\htmlrtf {\htmlrtf0 \'d7\'e8\'f2\'e0\'e9\'f2\'e5 \'ef\'ee\'f7\'f2\'f3, \'ef\'e8\'f8\'e8\'f2\'e5 \'ef\'e8\'f1\'fc\'ec\'e0, \'ed\'e0\'f5\'ee\'e4\'e8\'f2\'e5 \'f1\'e0\'ec\'ee\'e5 \'e2\'e0\'e6\'ed\'ee\'e5 \'e8 \'e1\'fb\'f1\'f2\'f0\'ee \'f0\'e5\'f8\'e0\'e9\'f2\'e5 \'f0\'e0\'e7\'ed\'fb\'e5 \'e7\'e0\'e4\'e0\'f7\'e8.{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 }{\*\htmltag64}\htmlrtf {\htmlrtf0 +{\*\htmltag84 }\htmlrtf {\field{\*\fldinst{HYPERLINK "https://mailer.mail.ru/media/letter/static/2019/4/12/FMOsCMR4gfUXmge/img/002.png"}}{\fldrslt\cf1\ul }}\htmlrtf0 +{\*\htmltag4 \par } +{\*\htmltag84 } +{\*\htmltag244 } +{\*\htmltag4 \par } +{\*\htmltag84 } +{\*\htmltag244 } +{\*\htmltag4 \par } +{\*\htmltag84 }{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 {\*\htmltag64}\htmlrtf {\htmlrtf0 \'c1\'fb\'f1\'f2\'f0\'fb\'e9 \'ef\'ee\'e8\'f1\'ea{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 {\*\htmltag64}\htmlrtf {\htmlrtf0 \'cd\'e0\'f5\'ee\'e4\'e8\'f2\'e5 \'ef\'e8\'f1\'fc\'ec\'e0 \'e7\'e0 \'e2\'f1\'fe \'e8\'f1\'f2\'ee\'f0\'e8\'fe \'ef\'e5\'f0\'e5\'ef\'e8\'f1\'ea\'e8. \'cf\'ee\'eb\'fc\'e7\'f3\'e9\'f2\'e5\'f1\'fc \'ef\'ee\'e8\'f1\'ea\'ee\'e2\'fb\'ec\'e8 \'ef\'ee\'e4\'f1\'ea\'e0\'e7\'ea\'e0\'ec\'e8.{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 }{\*\htmltag64}\htmlrtf {\htmlrtf0 +{\*\htmltag84 }\htmlrtf {\field{\*\fldinst{HYPERLINK "https://mailer.mail.ru/media/letter/static/2019/4/12/FMOsCMR4gfUXmge/img/004.png"}}{\fldrslt\cf1\ul }}\htmlrtf0 +{\*\htmltag4 \par } +{\*\htmltag84 } +{\*\htmltag244 } +{\*\htmltag4 \par } +{\*\htmltag84 } +{\*\htmltag244 } +{\*\htmltag4 \par } +{\*\htmltag84 }{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 {\*\htmltag64}\htmlrtf {\htmlrtf0 \'cc\'f3\'eb\'fc\'f2\'e8\'e0\'ea\'ea\'e0\'f3\'ed\'f2{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 {\*\htmltag64}\htmlrtf {\htmlrtf0 \'d1\'ee\'e1\'e5\'f0\'e8\'f2\'e5 \'e2\'f1\'e5 \'ef\'ee\'f7\'f2\'ee\'e2\'fb\'e5 \'ff\'f9\'e8\'ea\'e8 \'e2 \'fd\'f2\'ee\'ec \'ef\'f0\'e8\'eb\'ee\'e6\'e5\'ed\'e8\'e8. \'cf\'ee\'eb\'fc\'e7\'f3\'e9\'f2\'e5\'f1\'fc \'e8\'ec\'e8 \'ee\'e4\'ed\'ee\'e2\'f0\'e5\'ec\'e5\'ed\'ed\'ee, \'ef\'e5\'f0\'e5\'ea\'eb\'fe\'f7\'e0\'ff\'f1\'fc \'e2 \'ee\'e4\'e8\'ed \'ea\'eb\'e8\'ea.{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 }{\*\htmltag64}\htmlrtf {\htmlrtf0 +{\*\htmltag84 }\htmlrtf {\field{\*\fldinst{HYPERLINK "https://mailer.mail.ru/media/letter/static/2019/4/12/FMOsCMR4gfUXmge/img/003.png"}}{\fldrslt\cf1\ul }}\htmlrtf0 +{\*\htmltag4 \par } +{\*\htmltag84 } +{\*\htmltag244 } +{\*\htmltag4 \par } +{\*\htmltag84 } +{\*\htmltag244 } +{\*\htmltag4 \par } +{\*\htmltag84 }{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 {\*\htmltag64}\htmlrtf {\htmlrtf0 \'cd\'e0\'f1\'f2\'f0\'ee\'e9\'ea\'e8 \'f3\'e2\'e5\'e4\'ee\'ec\'eb\'e5\'ed\'e8\'e9 \'ee \'ef\'e8\'f1\'fc\'ec\'e0\'f5{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 {\*\htmltag64}\htmlrtf {\htmlrtf0 \'cd\'e0\'f1\'f2\'f0\'ee\'e9\'f2\'e5 \'f3\'e2\'e5\'e4\'ee\'ec\'eb\'e5\'ed\'e8\'ff \'ee \'ed\'ee\'e2\'fb\'f5 \'ef\'e8\'f1\'fc\'ec\'e0\'f5. \'c1\'eb\'e0\'e3\'ee\'e4\'e0\'f0\'ff \'e3\'e8\'e1\'ea\'ee\'e9 \'f1\'e8\'f1\'f2\'e5\'ec\'e5 \'ed\'e0\'f1\'f2\'f0\'ee\'e5\'ea \'ec\'ee\'e6\'ed\'ee \'ef\'ee\'eb\'f3\'f7\'e0\'f2\'fc \'f3\'e2\'e5\'e4\'ee\'ec\'eb\'e5\'ed\'e8\'ff \'ee \'ef\'e8\'f1\'fc\'ec\'e0\'f5 \'e8\'e7 \'ee\'ef\'f0\'e5\'e4\'e5\'eb\'e5\'ed\'ed\'ee\'e9 \'ef\'e0\'ef\'ea\'e8 \'e8\'eb\'e8 \'e2 \'ee\'ef\'f0\'e5\'e4\'e5\'eb\'e5\'ed\'ed\'ee\'e5 \'e2\'f0\'e5\'ec\'ff.{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 }{\*\htmltag64}\htmlrtf {\htmlrtf0 +{\*\htmltag84 }\htmlrtf {\field{\*\fldinst{HYPERLINK "https://mailer.mail.ru/media/letter/static/2019/4/12/FMOsCMR4gfUXmge/img/001.png"}}{\fldrslt\cf1\ul }}\htmlrtf0 +{\*\htmltag4 \par } +{\*\htmltag84 } +{\*\htmltag244 } +{\*\htmltag4 \par } +{\*\htmltag84 } +{\*\htmltag244 } +{\*\htmltag4 \par } +{\*\htmltag84 }{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 {\*\htmltag64}\htmlrtf {\htmlrtf0 \'d7\'f2\'e5\'ed\'e8\'e5 \'ef\'e8\'f1\'e5\'ec \'e1\'e5\'e7 \'ef\'ee\'e4\'ea\'eb\'fe\'f7\'e5\'ed\'e8\'ff \'ea \'e8\'ed\'f2\'e5\'f0\'ed\'e5\'f2\'f3{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 {\*\htmltag64}\htmlrtf {\htmlrtf0 \'cf\'f0\'ee\'f1\'ec\'e0\'f2\'f0\'e8\'e2\'e0\'e9\'f2\'e5 \'ef\'e8\'f1\'fc\'ec\'e0 \'e1\'e5\'e7 \'ef\'ee\'e4\'ea\'eb\'fe\'f7\'e5\'ed\'e8\'ff \'ea \'e8\'ed\'f2\'e5\'f0\'ed\'e5\'f2\'f3, \'ed\'e0\'ef\'f0\'e8\'ec\'e5\'f0, \'ed\'e0\'f5\'ee\'e4\'ff\'f1\'fc \'ed\'e0 \'e1\'ee\'f0\'f2\'f3 \'f1\'e0\'ec\'ee\'eb\'b8\'f2\'e0 \'e8\'eb\'e8 \'f2\'e0\'ec, \'e3\'e4\'e5 \'ef\'eb\'ee\'f5\'ee \'f0\'e0\'e1\'ee\'f2\'e0\'e5\'f2 \'f1\'ee\'f2\'ee\'e2\'e0\'ff \'f1\'e2\'ff\'e7\'fc.{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 {\*\htmltag64}\htmlrtf {\htmlrtf0 \'d1\'e8\'ed\'f5\'f0\'ee\'ed\'e8\'e7\'e0\'f6\'e8\'ff \'ef\'ee\'f7\'f2\'fb \'ed\'e0 \'e2\'f1\'e5\'f5 \'f3\'f1\'f2\'f0\'ee\'e9\'f1\'f2\'e2\'e0\'f5{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 }{\*\htmltag64}\htmlrtf {\htmlrtf0 +{\*\htmltag84 }\htmlrtf {\field{\*\fldinst{HYPERLINK "https://mailer.mail.ru/media/letter/static/2019/4/12/FMOsCMR4gfUXmge/img/bitmap.png"}}{\fldrslt\cf1\ul }}\htmlrtf0 {\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag240 } +{\*\htmltag0 \par } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 {\*\htmltag64}\htmlrtf {\htmlrtf0 \'c2 \'ef\'f0\'e8\'eb\'ee\'e6\'e5\'ed\'e8\'e8 \'e5\'f1\'f2\'fc \'e2\'f1\'b8 \'ed\'e5\'ee\'e1\'f5\'ee\'e4\'e8\'ec\'ee\'e5 \'e4\'eb\'ff \'f0\'e0\'e1\'ee\'f2\'fb \'f1 \'ef\'ee\'f7\'f2\'ee\'e9: \'f1\'ef\'e8\'f1\'ee\'ea \'ef\'e8\'f1\'e5\'ec \'f1 \'e0\'e2\'e0\'f2\'e0\'f0\'ea\'e0\'ec\'e8, \'e2\'ee\'e7\'ec\'ee\'e6\'ed\'ee\'f1\'f2\'fc \'ee\'f2\'ef\'f0\'e0\'e2\'eb\'ff\'f2\'fc \'e8 \'ef\'ee\'eb\'f3\'f7\'e0\'f2\'fc \'e2\'eb\'ee\'e6\'e5\'ed\'e8\'ff, \'ef\'e5\'f0\'f1\'ee\'ed\'e0\'eb\'fc\'ed\'fb\'e9 \'f1\'ef\'e0\'ec-\'f4\'e8\'eb\'fc\'f2\'f0, \'f6\'e5\'ef\'ee\'f7\'ea\'e8 \'ef\'e8\'f1\'e5\'ec \'e8 \'ec\'ed\'ee\'e3\'ee\'e5 \'e4\'f0\'f3\'e3\'ee\'e5.{\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag96 }\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag248
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag240 } +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag0 \par } +{\*\htmltag96
}\htmlrtf {\htmlrtf0 {\*\htmltag64}\htmlrtf {\htmlrtf0 +{\*\htmltag84 .}\htmlrtf {\field{\*\fldinst{HYPERLINK "https://mailer.mail.ru/pub/mailer/pixel/3001/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsZXR0ZXJfaWQiOjMwMDEsImVtYWlsIjoiYmF0bWFuMTExNTU1QG1haWwucnUiLCJ0ZXN0aW5nIjowLCJwb3N0X3R5cGUiOiJDIiwicG9zdF9pZCI6NDIsInNlbmRfdXVpZCI6ImVhNzMzNzU0LTM0MWQtNGQ0Yi04ZDJjLTcwYWMwYzBiOWJmNCIsImxhbmd1YWdlIjoicnUifQ.kBxrr6UFII4HGPuEInkOKX_MpZKSCLhZk6lGnVCJyC8"}}{\fldrslt\cf1\ul {\chbrdr\brdrsh .}}}\htmlrtf0 {\*\htmltag72}\htmlrtf\par}\htmlrtf0 + +{\*\htmltag104
}\htmlrtf }\htmlrtf0 +{\*\htmltag58 } +{\*\htmltag2 \par } +{\*\htmltag27 }}Jf \ No newline at end of file diff --git a/src/test/resources/test-messages/rtf-to-html-output.html b/src/test/resources/test-messages/rtf-to-html-output.html new file mode 100644 index 0000000..c348462 --- /dev/null +++ b/src/test/resources/test-messages/rtf-to-html-output.html @@ -0,0 +1,874 @@ + + + + + + + +
+ + + + + + + +
+
+ + + +
+ + Здравствуйте ! + +
+ + + +
Пользуйтесь Почтой с мобильного
+
Почта всегда рядом, даже если вы далеко от компьютера. Читайте и отправляйте письма, прикрепляйте и просматривайте файлы, находясь в любой точке Земли.
+ +
+ +
+ + + + +
+ +
+ +
Установите приложение
+ + + + + +
+
+
+ +
+ +
+ + + + +
+ +
Мгновенные уведомления
+ +
Узнавайте о новых письмах мгновенно. Проверять почту вручную не нужно.
+ +
+ +
+ +
+ + + + +
+ +
Простой и удобный интерфейс
+ +
Читайте почту, пишите письма, находите самое важное и быстро решайте разные задачи.
+ +
+ +
+
+ +
+ +
+ + + + +
+ +
Быстрый поиск
+ +
Находите письма за всю историю переписки. Пользуйтесь поисковыми подсказками.
+ +
+ +
+ +
+ + + + +
+ +
Мультиаккаунт
+ +
Соберите все почтовые ящики в этом приложении. Пользуйтесь ими одновременно, переключаясь в один клик.
+ +
+ +
+
+ +
+ +
+ + + + +
+ +
Настройки уведомлений о письмах
+ +
Настройте уведомления о новых письмах. Благодаря гибкой системе настроек можно получать уведомления о письмах из определенной папки или в определенное время.
+ +
+ +
+ +
+ + + + +
+ +
Чтение писем без подключения к интернету
+ +
Просматривайте письма без подключения к интернету, например, находясь на борту самолёта или там, где плохо работает сотовая связь.
+ +
+ +
+
+ +
+
Синхронизация почты на всех устройствах
+
+ +
+ + + +
В приложении есть всё необходимое для работы с почтой: список писем с аватарками, возможность отправлять и получать вложения, персональный спам-фильтр, цепочки писем и многое другое.
+
+ + +
+
+
.
+ \ No newline at end of file