+ * The transformation is sensitive to the source and target string encodings.
+ *
+ *
+ *
+ * This code is based on a class of the same name originally implemented in alfresco-repository.
+ *
+ *
+ *
+ * @author Derek Hulley
+ * @author eknizat
+ */
+public class StringExtractingContentTransformer implements SelectableTransformer
+{
+
+ private static final Log logger = LogFactory.getLog(StringExtractingContentTransformer.class);
+
+ @Override
+ public boolean isTransformable(String sourceMimetype, String targetMimetype, Map parameters)
+ {
+ boolean transformable = (sourceMimetype.startsWith("text/")
+ || MIMETYPE_JAVASCRIPT.equals(sourceMimetype)
+ || MIMETYPE_DITA.equals(sourceMimetype))
+ && MIMETYPE_TEXT_PLAIN.equals(targetMimetype);
+ return transformable;
+ }
+
+ /**
+ * Text to text conversions are done directly using the content reader and writer string
+ * manipulation methods.
+ *
+ * Extraction of text from binary content attempts to take the possible character
+ * encoding into account. The text produced from this will, if the encoding was correct,
+ * be unformatted but valid.
+ */
+ @Override
+ public void transform(File sourceFile, File targetFile, Map parameters) throws Exception
+ {
+
+ String sourceEncoding = parameters.get(SOURCE_ENCODING);
+ String targetEncoding = parameters.get(TARGET_ENCODING);
+
+ if(logger.isDebugEnabled())
+ {
+ logger.debug("Performing text to text transform with sourceEncoding=" + sourceEncoding
+ + " targetEncoding=" + targetEncoding);
+ }
+
+ Reader charReader = null;
+ Writer charWriter = null;
+ try
+ {
+ // Build reader
+ if (sourceEncoding == null)
+ {
+ charReader = new BufferedReader(new InputStreamReader(new FileInputStream(sourceFile)));
+ }
+ else
+ {
+ checkEncodingParameter(sourceEncoding, SOURCE_ENCODING);
+ charReader = new BufferedReader(new InputStreamReader(new FileInputStream(sourceFile), sourceEncoding));
+ }
+
+ // Build writer
+ if (targetEncoding == null)
+ {
+ charWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(targetFile)));
+ }
+ else
+ {
+ checkEncodingParameter( targetEncoding, TARGET_ENCODING);
+ charWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(targetFile), targetEncoding));
+ }
+
+ // copy from the one to the other
+ char[] buffer = new char[8192];
+ int readCount = 0;
+ while (readCount > -1)
+ {
+ // write the last read count number of bytes
+ charWriter.write(buffer, 0, readCount);
+ // fill the buffer again
+ readCount = charReader.read(buffer);
+ }
+ }
+ finally
+ {
+ if (charReader != null)
+ {
+ try { charReader.close(); } catch (Throwable e) { logger.error(e); }
+ }
+ if (charWriter != null)
+ {
+ try { charWriter.close(); } catch (Throwable e) { logger.error(e); }
+ }
+ }
+ // done
+ }
+
+ private void checkEncodingParameter(String encoding, String paramterName)
+ {
+ try
+ {
+ if (!Charset.isSupported(encoding))
+ {
+ throw new IllegalArgumentException(paramterName + "=" + encoding + " is not supported by the JVM.");
+ }
+ }
+ catch (IllegalCharsetNameException e)
+ {
+ throw new IllegalArgumentException(paramterName + "=" + encoding + " is not a valid encoding.");
+ }
+ }
+}
diff --git a/alfresco-docker-transform-misc/src/main/java/org/alfresco/transformer/transformers/TextToPdfContentTransformer.java b/alfresco-docker-transform-misc/src/main/java/org/alfresco/transformer/transformers/TextToPdfContentTransformer.java
new file mode 100644
index 00000000..530f61e1
--- /dev/null
+++ b/alfresco-docker-transform-misc/src/main/java/org/alfresco/transformer/transformers/TextToPdfContentTransformer.java
@@ -0,0 +1,332 @@
+/*
+ * #%L
+ * Alfresco Transform Core
+ * %%
+ * Copyright (C) 2005 - 2019 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * -
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ * -
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * -
+ * Alfresco 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 Lesser General Public License for more details.
+ * -
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.transformer.transformers;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.font.PDType1Font;
+import org.apache.pdfbox.tools.TextToPDF;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_DITA;
+import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_PDF;
+import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_TEXT_CSV;
+import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_TEXT_PLAIN;
+import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_XML;
+
+/**
+ *
+ *
+ * This code is based on a class of the same name originally implemented in alfresco-repository.
+ *
+ *
+ * Makes use of the PDFBox library's TextToPDF
utility.
+ *
+ * @author Derek Hulley
+ * @author eknizat
+*/
+public class TextToPdfContentTransformer implements SelectableTransformer
+{
+ private static final Logger logger = LoggerFactory.getLogger(TextToPdfContentTransformer.class);
+
+ public static final String PAGE_LIMIT = "pageLimit";
+
+ private PagedTextToPDF transformer;
+
+ public TextToPdfContentTransformer()
+ {
+ transformer = new PagedTextToPDF();
+ }
+
+ public void setStandardFont(String fontName)
+ {
+ try
+ {
+ transformer.setFont(transformer.getStandardFont(fontName));
+ }
+ catch (Throwable e)
+ {
+ throw new AlfrescoRuntimeException("Unable to set Standard Font for PDF generation: " + fontName, e);
+ }
+ }
+
+ public void setFontSize(int fontSize)
+ {
+ try
+ {
+ transformer.setFontSize(fontSize);
+ }
+ catch (Throwable e)
+ {
+ throw new AlfrescoRuntimeException("Unable to set Font Size for PDF generation: " + fontSize);
+ }
+ }
+
+ @Override
+ public boolean isTransformable(String sourceMimetype, String targetMimetype, Map parameters)
+ {
+ boolean transformable = ( (MIMETYPE_TEXT_PLAIN.equals(sourceMimetype)
+ || MIMETYPE_TEXT_CSV.equals(sourceMimetype)
+ || MIMETYPE_DITA.equals(sourceMimetype)
+ || MIMETYPE_XML.equals(sourceMimetype) )
+ && MIMETYPE_PDF.equals(targetMimetype));
+
+ return transformable;
+ }
+
+ @Override
+ public void transform(File sourceFile, File targetFile, Map parameters) throws Exception
+ {
+ String sourceEncoding = parameters.get(SOURCE_ENCODING);
+ String stringPageLimit = parameters.get(PAGE_LIMIT);
+ int pageLimit = -1;
+ if ( stringPageLimit != null)
+ {
+ pageLimit = parseInt(stringPageLimit, PAGE_LIMIT);
+ }
+
+ PDDocument pdf = null;
+ try (InputStream is = new FileInputStream(sourceFile);
+ Reader ir = new BufferedReader(buildReader(is, sourceEncoding));
+ OutputStream os = new BufferedOutputStream(new FileOutputStream(targetFile)))
+ {
+ //TransformationOptionLimits limits = getLimits(reader, writer, options);
+ //TransformationOptionPair pageLimits = limits.getPagesPair();
+ pdf = transformer.createPDFFromText(ir, pageLimit);
+ pdf.save(os);
+ }
+ finally
+ {
+ if (pdf != null)
+ {
+ try { pdf.close(); } catch (Throwable e) {e.printStackTrace(); }
+ }
+ }
+ }
+
+ protected InputStreamReader buildReader(InputStream is, String encoding)
+ {
+ // If they gave an encoding, try to use it
+ if(encoding != null)
+ {
+ Charset charset = null;
+ try
+ {
+ charset = Charset.forName(encoding);
+ } catch(Exception e)
+ {
+ logger.warn("JVM doesn't understand encoding '" + encoding +
+ "' when transforming text to pdf");
+ }
+ if(charset != null)
+ {
+ logger.debug("Processing plain text in encoding " + charset.displayName());
+ return new InputStreamReader(is, charset);
+ }
+ }
+
+ // Fall back on the system default
+ logger.debug("Processing plain text using system default encoding");
+ return new InputStreamReader(is);
+ }
+
+ private static class PagedTextToPDF extends TextToPDF
+ {
+ // REPO-1066: duplicating the following lines from org.apache.pdfbox.tools.TextToPDF because they made them private
+ // before the upgrade to pdfbox 2.0.8, in pdfbox 1.8, this piece of code was public in org.apache.pdfbox.pdmodel.font.PDType1Font
+ static PDType1Font getStandardFont(String name) {
+ return (PDType1Font)STANDARD_14.get(name);
+ }
+ private static final Map STANDARD_14 = new HashMap();
+ static
+ {
+ STANDARD_14.put(PDType1Font.TIMES_ROMAN.getBaseFont(), PDType1Font.TIMES_ROMAN);
+ STANDARD_14.put(PDType1Font.TIMES_BOLD.getBaseFont(), PDType1Font.TIMES_BOLD);
+ STANDARD_14.put(PDType1Font.TIMES_ITALIC.getBaseFont(), PDType1Font.TIMES_ITALIC);
+ STANDARD_14.put(PDType1Font.TIMES_BOLD_ITALIC.getBaseFont(), PDType1Font.TIMES_BOLD_ITALIC);
+ STANDARD_14.put(PDType1Font.HELVETICA.getBaseFont(), PDType1Font.HELVETICA);
+ STANDARD_14.put(PDType1Font.HELVETICA_BOLD.getBaseFont(), PDType1Font.HELVETICA_BOLD);
+ STANDARD_14.put(PDType1Font.HELVETICA_OBLIQUE.getBaseFont(), PDType1Font.HELVETICA_OBLIQUE);
+ STANDARD_14.put(PDType1Font.HELVETICA_BOLD_OBLIQUE.getBaseFont(), PDType1Font.HELVETICA_BOLD_OBLIQUE);
+ STANDARD_14.put(PDType1Font.COURIER.getBaseFont(), PDType1Font.COURIER);
+ STANDARD_14.put(PDType1Font.COURIER_BOLD.getBaseFont(), PDType1Font.COURIER_BOLD);
+ STANDARD_14.put(PDType1Font.COURIER_OBLIQUE.getBaseFont(), PDType1Font.COURIER_OBLIQUE);
+ STANDARD_14.put(PDType1Font.COURIER_BOLD_OBLIQUE.getBaseFont(), PDType1Font.COURIER_BOLD_OBLIQUE);
+ STANDARD_14.put(PDType1Font.SYMBOL.getBaseFont(), PDType1Font.SYMBOL);
+ STANDARD_14.put(PDType1Font.ZAPF_DINGBATS.getBaseFont(), PDType1Font.ZAPF_DINGBATS);
+ }
+ //duplicating until here
+
+ // The following code is based on the code in TextToPDF with the addition of
+ // checks for page limits.
+ // The calling code must close the PDDocument once finished with it.
+ public PDDocument createPDFFromText(Reader text, int pageLimit)
+ throws IOException
+ {
+ //int pageLimit = (int)pageLimits.getValue();
+ PDDocument doc = null;
+ int pageCount = 0;
+ try
+ {
+ final int margin = 40;
+ float height = getFont().getFontDescriptor().getFontBoundingBox().getHeight()/1000;
+
+ //calculate font height and increase by 5 percent.
+ height = height*getFontSize()*1.05f;
+ doc = new PDDocument();
+ BufferedReader data = new BufferedReader( text );
+ String nextLine = null;
+ PDPage page = new PDPage();
+ PDPageContentStream contentStream = null;
+ float y = -1;
+ float maxStringLength = page.getMediaBox().getWidth() - 2*margin;
+
+ // There is a special case of creating a PDF document from an empty string.
+ boolean textIsEmpty = true;
+
+ outer:
+ while( (nextLine = data.readLine()) != null )
+ {
+
+ // The input text is nonEmpty. New pages will be created and added
+ // to the PDF document as they are needed, depending on the length of
+ // the text.
+ textIsEmpty = false;
+
+ String[] lineWords = nextLine.trim().split( " " );
+ int lineIndex = 0;
+ while( lineIndex < lineWords.length )
+ {
+ StringBuffer nextLineToDraw = new StringBuffer();
+ float lengthIfUsingNextWord = 0;
+ do
+ {
+ nextLineToDraw.append( lineWords[lineIndex] );
+ nextLineToDraw.append( " " );
+ lineIndex++;
+ if( lineIndex < lineWords.length )
+ {
+ String lineWithNextWord = nextLineToDraw.toString() + lineWords[lineIndex];
+ lengthIfUsingNextWord =
+ (getFont().getStringWidth( lineWithNextWord )/1000) * getFontSize();
+ }
+ }
+ while( lineIndex < lineWords.length &&
+ lengthIfUsingNextWord < maxStringLength );
+ if( y < margin )
+ {
+ int test = pageCount +1;
+ if (pageLimit > 0 && (pageCount++ >= pageLimit))
+ {
+// pageLimits.getAction().throwIOExceptionIfRequired("Page limit ("+pageLimit+
+// ") reached.", transformerDebug);
+ break outer;
+ }
+
+ // We have crossed the end-of-page boundary and need to extend the
+ // document by another page.
+ page = new PDPage();
+ doc.addPage( page );
+ if( contentStream != null )
+ {
+ contentStream.endText();
+ contentStream.close();
+ }
+ contentStream = new PDPageContentStream(doc, page);
+ contentStream.setFont(getFont(), getFontSize());
+ contentStream.beginText();
+ y = page.getMediaBox().getHeight() - margin + height;
+ contentStream.moveTextPositionByAmount(
+ margin, y );
+ }
+ //System.out.println( "Drawing string at " + x + "," + y );
+
+ if( contentStream == null )
+ {
+ throw new IOException( "Error:Expected non-null content stream." );
+ }
+ contentStream.moveTextPositionByAmount( 0, -height);
+ y -= height;
+ contentStream.drawString( nextLineToDraw.toString() );
+ }
+ }
+
+ // If the input text was the empty string, then the above while loop will have short-circuited
+ // and we will not have added any PDPages to the document.
+ // So in order to make the resultant PDF document readable by Adobe Reader etc, we'll add an empty page.
+ if (textIsEmpty)
+ {
+ doc.addPage(page);
+ }
+
+ if( contentStream != null )
+ {
+ contentStream.endText();
+ contentStream.close();
+ }
+ }
+ catch( IOException io )
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ throw io;
+ }
+ return doc;
+ }
+ }
+
+ private int parseInt(String s, String paramName)
+ {
+ try
+ {
+ return Integer.valueOf(s);
+ }
+ catch (NumberFormatException e)
+ {
+ throw new IllegalArgumentException(paramName + " parameter must be an integer.");
+ }
+ }
+}
diff --git a/alfresco-docker-transform-misc/src/main/resources/application-default.yaml b/alfresco-docker-transform-misc/src/main/resources/application-default.yaml
new file mode 100644
index 00000000..c0a793ba
--- /dev/null
+++ b/alfresco-docker-transform-misc/src/main/resources/application-default.yaml
@@ -0,0 +1,2 @@
+queue:
+ engineRequestQueue: ${TRANSFORM_ENGINE_REQUEST_QUEUE:org.alfresco.transform.engine.misc.acs}
\ No newline at end of file
diff --git a/alfresco-docker-transform-misc/src/main/resources/engine_config.json b/alfresco-docker-transform-misc/src/main/resources/engine_config.json
new file mode 100644
index 00000000..423d6a6a
--- /dev/null
+++ b/alfresco-docker-transform-misc/src/main/resources/engine_config.json
@@ -0,0 +1,65 @@
+{
+ "transformOptions": {
+ "textToPdfOptions": [
+ {"value": {"name": "pageLimit"}},
+ {"value": {"name": "sourceEncoding"}}
+ ],
+ "stringOptions": [
+ {"value": {"name": "sourceEncoding"}},
+ {"value": {"name": "targetEncoding"}}
+ ],
+ "htmlOptions": [
+ {"value": {"name": "sourceEncoding"}}
+ ]
+ },
+ "transformers": [
+ {
+ "transformerName": "html",
+ "supportedSourceAndTargetList": [
+ {"sourceMediaType": "text/html", "targetMediaType": "text/plain"}
+ ],
+ "transformOptions": [
+ "htmlOptions"
+ ]
+ },
+ {
+ "transformerName": "string",
+ "supportedSourceAndTargetList": [
+ {"sourceMediaType": "text/plain", "targetMediaType": "text/plain"},
+ {"sourceMediaType": "text/mediawiki", "targetMediaType": "text/plain"},
+ {"sourceMediaType": "text/css", "targetMediaType": "text/plain"},
+ {"sourceMediaType": "text/csv", "targetMediaType": "text/plain"},
+ {"sourceMediaType": "text/javascript", "targetMediaType": "text/plain"},
+ {"sourceMediaType": "text/xml", "targetMediaType": "text/plain"},
+ {"sourceMediaType": "text/html", "targetMediaType": "text/plain"},
+ {"sourceMediaType": "application/x-javascript", "targetMediaType": "text/plain"},
+ {"sourceMediaType": "application/dita+xml", "targetMediaType": "text/plain"}
+ ],
+ "transformOptions": [
+ "stringOptions"
+ ]
+ },
+ {
+ "transformerName": "appleIWorks",
+ "supportedSourceAndTargetList": [
+ {"sourceMediaType": "application/vnd.apple.keynote", "targetMediaType": "image/jpeg"},
+ {"sourceMediaType": "application/vnd.apple.numbers", "targetMediaType": "image/jpeg"},
+ {"sourceMediaType": "application/vnd.apple.pages", "targetMediaType": "image/jpeg"}
+ ],
+ "transformOptions": [
+ ]
+ },
+ {
+ "transformerName": "textToPdf",
+ "supportedSourceAndTargetList": [
+ {"sourceMediaType": "text/plain", "targetMediaType": "application/pdf"},
+ {"sourceMediaType": "text/csv", "targetMediaType": "application/pdf"},
+ {"sourceMediaType": "application/dita+xml", "targetMediaType": "application/pdf"},
+ {"sourceMediaType": "text/xml", "targetMediaType": "application/pdf"}
+ ],
+ "transformOptions": [
+ "textToPdfOptions"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/alfresco-docker-transform-misc/src/main/resources/quick.html b/alfresco-docker-transform-misc/src/main/resources/quick.html
new file mode 100644
index 00000000..76c633d7
--- /dev/null
+++ b/alfresco-docker-transform-misc/src/main/resources/quick.html
@@ -0,0 +1,17 @@
+
+
+
+
+ The quick brown fox jumps over the lazy dog
+
+
+
+
+
+
+
+The quick brown fox jumps over the lazy dog
+
+
+
+
diff --git a/alfresco-docker-transform-misc/src/main/resources/templates/transformForm.html b/alfresco-docker-transform-misc/src/main/resources/templates/transformForm.html
new file mode 100644
index 00000000..59097697
--- /dev/null
+++ b/alfresco-docker-transform-misc/src/main/resources/templates/transformForm.html
@@ -0,0 +1,29 @@
+
+
+
+
+
Miscellaneous Transformers Test Transformation
+
+
+
+
+
+
+
diff --git a/alfresco-docker-transform-misc/src/test/java/org/alfresco/transformer/MiscControllerTest.java b/alfresco-docker-transform-misc/src/test/java/org/alfresco/transformer/MiscControllerTest.java
new file mode 100644
index 00000000..66d63619
--- /dev/null
+++ b/alfresco-docker-transform-misc/src/test/java/org/alfresco/transformer/MiscControllerTest.java
@@ -0,0 +1,294 @@
+/*
+ * #%L
+ * Alfresco Transform Core
+ * %%
+ * Copyright (C) 2005 - 2019 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * -
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ * -
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * -
+ * Alfresco 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 Lesser General Public License for more details.
+ * -
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.transformer;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.transform.client.model.TransformRequest;
+import org.alfresco.transformer.transformers.AppleIWorksContentTransformer;
+import org.alfresco.transformer.transformers.HtmlParserContentTransformer;
+import org.alfresco.transformer.transformers.SelectingTransformer;
+import org.alfresco.transformer.transformers.StringExtractingContentTransformer;
+import org.alfresco.transformer.transformers.TextToPdfContentTransformer;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.text.PDFTextStripper;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.context.annotation.Import;
+import org.springframework.mock.web.MockMultipartFile;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+
+import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_HTML;
+import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_IMAGE_JPEG;
+import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_IWORK_KEYNOTE;
+import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_IWORK_NUMBERS;
+import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_IWORK_PAGES;
+import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENXML_WORDPROCESSING;
+import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_PDF;
+import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_TEXT_PLAIN;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.springframework.http.HttpStatus.OK;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@RunWith(SpringRunner.class)
+@WebMvcTest(MiscController.class)
+@Import({SelectingTransformer.class})
+public class MiscControllerTest extends AbstractTransformerControllerTest
+{
+
+ @Autowired
+ private MiscController controller;
+
+ private String sourceEncoding = "UTF-8";
+ private String targetEncoding = "UTF-8";
+ private String targetMimetype = MIMETYPE_TEXT_PLAIN;
+
+ @Before
+ public void before() throws Exception
+ {
+ sourceMimetype = MIMETYPE_HTML;
+ sourceExtension = "html";
+ targetExtension = "txt";
+ expectedOptions = null;
+ expectedSourceSuffix = null;
+ expectedSourceFileBytes = readTestFile(sourceExtension);
+ expectedTargetFileBytes = readTestFile(targetExtension);
+ //expectedTargetFileBytes = null;
+ sourceFile = new MockMultipartFile("file", "quick." + sourceExtension, sourceMimetype, expectedSourceFileBytes);
+
+ }
+
+ @Override
+ protected void mockTransformCommand(String sourceExtension, String targetExtension, String sourceMimetype, boolean readTargetFileBytes) throws IOException
+ {
+ }
+
+ @Override
+ protected AbstractTransformerController getController()
+ {
+ return controller;
+ }
+
+ @Override
+ protected void updateTransformRequestWithSpecificOptions(TransformRequest transformRequest)
+ {
+ }
+
+ @Override
+ // Add extra required parameters to the request.
+ protected MockHttpServletRequestBuilder mockMvcRequest(String url, MockMultipartFile sourceFile, String... params)
+ {
+ return super.mockMvcRequest(url, sourceFile, params)
+ .param("targetEncoding", targetEncoding)
+ .param("sourceEncoding", sourceEncoding)
+ .param("targetMimetype", targetMimetype)
+ .param("sourceMimetype", sourceMimetype);
+ }
+
+ @Test
+ @Override
+ public void noTargetFileTest()
+ {
+ // Ignore the test in super class as the Misc transforms are real rather than mocked up.
+ // It is the mock that returns a zero length file for other transformers, when we supply an invalid targetExtension.
+ }
+
+ @Test
+ public void testHTMLtoString() throws Exception
+ {
+ final String NEWLINE = System.getProperty ("line.separator");
+ final String TITLE = "Testing!";
+ final String TEXT_P1 = "This is some text in English";
+ final String TEXT_P2 = "This is more text in English";
+ final String TEXT_P3 = "C'est en Fran\u00e7ais et Espa\u00f1ol";
+ String partA = "" + TITLE + "" + NEWLINE;
+ String partB = "" + TEXT_P1 + "
" + NEWLINE +
+ "" + TEXT_P2 + "
" + NEWLINE +
+ "" + TEXT_P3 + "
" + NEWLINE;
+ String partC = "";
+ final String expected = TITLE + NEWLINE + TEXT_P1 + NEWLINE + TEXT_P2 + NEWLINE + TEXT_P3 + NEWLINE;
+
+ MvcResult result = sendText("html",
+ "UTF-8",
+ MIMETYPE_HTML,
+ "txt",
+ MIMETYPE_TEXT_PLAIN,
+ "UTF-8",
+ expected.getBytes());
+
+ String contentResult = new String(result.getResponse().getContentAsByteArray(), targetEncoding);
+ assertTrue("The content did not include \""+expected, contentResult.contains(expected));
+ }
+
+ @Test
+ public void testStringtoString() throws Exception
+ {
+ String expected = null;
+ byte[] content = null;
+ try
+ {
+ content = "azAz10!�$%^&*()\t\r\n".getBytes("UTF-8");
+ expected = new String(content, "MacDingbat");
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ throw new AlfrescoRuntimeException("Encoding not recognised", e);
+ }
+
+ MvcResult result = sendText("txt",
+ "MacDingbat",
+ MIMETYPE_TEXT_PLAIN,
+ "txt",
+ MIMETYPE_TEXT_PLAIN,
+ "UTF-8",
+ content);
+
+ String contentResult = new String(result.getResponse().getContentAsByteArray(), targetEncoding);
+ assertTrue("The content did not include \""+expected, contentResult.contains(expected));
+ }
+
+ @Test
+ public void textToPdf() throws Exception
+ {
+ StringBuilder sb = new StringBuilder();
+ String expected = null;
+ for (int i=1; i<=5; i++)
+ {
+ sb.append(i);
+ sb.append(" I must not talk in class or feed my homework to my cat.\n");
+ }
+ sb.append("\nBart\n");
+ expected = sb.toString();
+
+ MvcResult result = sendText("txt",
+ "UTF-8",
+ MIMETYPE_TEXT_PLAIN,
+ "pdf",
+ MIMETYPE_PDF,
+ "UTF-8",
+ expected.getBytes());
+
+ // Read back in the PDF and check it
+ PDDocument doc = PDDocument.load(result.getResponse().getContentAsByteArray());
+ PDFTextStripper textStripper = new PDFTextStripper();
+ StringWriter textWriter = new StringWriter();
+ textStripper.writeText(doc, textWriter);
+ doc.close();
+
+ expected = clean(expected);
+ String actual = clean(textWriter.toString());
+
+ assertEquals("The content did not match.", expected, actual);
+ }
+
+ @Test
+ public void testAppleIWorksPages() throws Exception
+ {
+ imageBasedTransform("pages", MIMETYPE_IWORK_PAGES, MIMETYPE_IMAGE_JPEG, "jpeg");
+ }
+
+ @Test
+ public void testAppleIWorksNumbers() throws Exception
+ {
+ imageBasedTransform("numbers", MIMETYPE_IWORK_NUMBERS, MIMETYPE_IMAGE_JPEG, "jpeg");
+ }
+
+ @Test
+ public void testAppleIWorksKey() throws Exception
+ {
+ imageBasedTransform("key", MIMETYPE_IWORK_KEYNOTE, MIMETYPE_IMAGE_JPEG, "jpeg");
+ }
+
+ // TODO Doesn't wotk with java 11, enable when fixed
+// @Test
+ public void testOOXML() throws Exception
+ {
+ imageBasedTransform("docx", MIMETYPE_OPENXML_WORDPROCESSING, MIMETYPE_IMAGE_JPEG, "jpeg");
+ }
+
+ private void imageBasedTransform(String sourceExtension, String sourceMimetype, String targetMimetype, String targetExtension) throws Exception
+ {
+ MockMultipartFile sourceFilex = new MockMultipartFile("file", "test_file." + sourceExtension, sourceMimetype, readTestFile(sourceExtension));
+
+ MockHttpServletRequestBuilder requestBuilder = super.mockMvcRequest("/transform", sourceFilex)
+ .param("targetExtension", "jpeg")
+ .param("targetMimetype", targetMimetype)
+ .param("sourceMimetype", sourceMimetype);
+
+ MvcResult result = mockMvc.perform(requestBuilder)
+ .andExpect(status().is(OK.value()))
+ .andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''test_file." + targetExtension))
+ .andReturn();
+ assertTrue("Expected image content but content is empty.",result.getResponse().getContentLengthLong() > 0L);
+ }
+
+
+ private MvcResult sendText(String sourceExtension,
+ String sourceEncoding,
+ String sourceMimetype,
+ String targetExtension,
+ String targetMimetype,
+ String targetEncoding,
+ byte[] content) throws Exception
+ {
+ MockMultipartFile sourceFilex = new MockMultipartFile("file", "test_file." + sourceExtension, sourceMimetype, content);
+
+ MockHttpServletRequestBuilder requestBuilder = super.mockMvcRequest("/transform", sourceFilex)
+ .param("targetExtension", targetExtension)
+ .param("targetEncoding", targetEncoding)
+ .param("targetMimetype", targetMimetype)
+ .param("sourceEncoding", sourceEncoding)
+ .param("sourceMimetype", sourceMimetype);
+
+
+ MvcResult result = mockMvc.perform(requestBuilder)
+ .andExpect(status().is(OK.value()))
+ .andExpect(header().string("Content-Disposition", "attachment; filename*= "+targetEncoding+"''test_file." + targetExtension)).
+ andReturn();
+ return result;
+ }
+
+ private String clean(String text)
+ {
+ text = text.replaceAll("\\s+\\r", "");
+ text = text.replaceAll("\\s+\\n", "");
+ text = text.replaceAll("\\r", "");
+ text = text.replaceAll("\\n", "");
+ return text;
+ }
+
+}
\ No newline at end of file
diff --git a/alfresco-docker-transform-misc/src/test/java/org/alfresco/transformer/MiscQueueTransformServiceIT.java b/alfresco-docker-transform-misc/src/test/java/org/alfresco/transformer/MiscQueueTransformServiceIT.java
new file mode 100644
index 00000000..c78c5817
--- /dev/null
+++ b/alfresco-docker-transform-misc/src/test/java/org/alfresco/transformer/MiscQueueTransformServiceIT.java
@@ -0,0 +1,56 @@
+/*
+ * #%L
+ * Alfresco Transform Core
+ * %%
+ * Copyright (C) 2005 - 2019 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * -
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ * -
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * -
+ * Alfresco 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 Lesser General Public License for more details.
+ * -
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.transformer;
+
+import org.alfresco.transform.client.model.TransformRequest;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.util.UUID;
+
+import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_HTML;
+import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_TEXT_PLAIN;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+public class MiscQueueTransformServiceIT extends AbstractQueueTransformServiceIT
+{
+ @Override
+ protected TransformRequest buildRequest()
+ {
+ return TransformRequest.builder()
+ .withRequestId(UUID.randomUUID().toString())
+ .withSourceMediaType(MIMETYPE_HTML)
+ .withTargetMediaType(MIMETYPE_TEXT_PLAIN)
+ .withTargetExtension("txt")
+ .withSchema(1)
+ .withClientData("ACS")
+ .withSourceReference(UUID.randomUUID().toString())
+ .withSourceSize(32L).build();
+ }
+}
diff --git a/alfresco-docker-transform-misc/src/test/java/org/alfresco/transformer/MiscTransformerHttpRequestTest.java b/alfresco-docker-transform-misc/src/test/java/org/alfresco/transformer/MiscTransformerHttpRequestTest.java
new file mode 100644
index 00000000..3d18f2ca
--- /dev/null
+++ b/alfresco-docker-transform-misc/src/test/java/org/alfresco/transformer/MiscTransformerHttpRequestTest.java
@@ -0,0 +1,48 @@
+/*
+ * #%L
+ * Alfresco Transform Core
+ * %%
+ * Copyright (C) 2005 - 2019 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * -
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ * -
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * -
+ * Alfresco 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 Lesser General Public License for more details.
+ * -
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.transformer;
+
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+public class MiscTransformerHttpRequestTest extends AbstractHttpRequestTest
+{
+ @Override
+ protected String getTransformerName()
+ {
+ return "Miscellaneous Transformers";
+ }
+
+ @Override
+ protected String getSourceExtension()
+ {
+ return "html";
+ }
+}
diff --git a/alfresco-docker-transform-misc/src/test/java/org/alfresco/transformer/transformers/HtmlParserContentTransformerTest.java b/alfresco-docker-transform-misc/src/test/java/org/alfresco/transformer/transformers/HtmlParserContentTransformerTest.java
new file mode 100644
index 00000000..0bab9bf8
--- /dev/null
+++ b/alfresco-docker-transform-misc/src/test/java/org/alfresco/transformer/transformers/HtmlParserContentTransformerTest.java
@@ -0,0 +1,173 @@
+/*
+ * #%L
+ * Alfresco Transform Core
+ * %%
+ * Copyright (C) 2005 - 2019 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * -
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ * -
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * -
+ * Alfresco 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 Lesser General Public License for more details.
+ * -
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.transformer.transformers;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.nio.file.Files;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.alfresco.transformer.transformers.StringExtractingContentTransformer.SOURCE_ENCODING;
+import static org.alfresco.transformer.transformers.StringExtractingContentTransformer.TARGET_ENCODING;
+import static org.junit.Assert.*;
+
+@RunWith(SpringRunner.class)
+@Import(HtmlParserContentTransformer.class)
+public class HtmlParserContentTransformerTest
+{
+ @Autowired
+ HtmlParserContentTransformer transformer;
+
+ /**
+ * Checks that we correctly handle text in different encodings,
+ * no matter if the encoding is specified on the Content Property
+ * or in a meta tag within the HTML itself. (ALF-10466)
+ *
+ * On Windows, org.htmlparser.beans.StringBean.carriageReturn() appends a new system dependent new line
+ * so we must be careful when checking the returned text
+ */
+ @Test
+ public void testEncodingHandling() throws Exception
+ {
+ final String NEWLINE = System.getProperty ("line.separator");
+ final String TITLE = "Testing!";
+ final String TEXT_P1 = "This is some text in English";
+ final String TEXT_P2 = "This is more text in English";
+ final String TEXT_P3 = "C'est en Fran\u00e7ais et Espa\u00f1ol";
+ String partA = "" + TITLE + "" + NEWLINE;
+ String partB = "" + TEXT_P1 + "
" + NEWLINE +
+ "" + TEXT_P2 + "
" + NEWLINE +
+ "" + TEXT_P3 + "
" + NEWLINE;
+ String partC = "";
+ final String expected = TITLE + NEWLINE + TEXT_P1 + NEWLINE + TEXT_P2 + NEWLINE + TEXT_P3 + NEWLINE;
+
+ File tmpS = null;
+ File tmpD = null;
+
+ try
+ {
+ // Content set to ISO 8859-1
+ tmpS = File.createTempFile("AlfrescoTestSource_", ".html");
+ writeToFile(tmpS, partA+partB+partC, "ISO-8859-1");
+
+ tmpD = File.createTempFile("AlfrescoTestTarget_", ".txt");
+
+ Map parameters = new HashMap<>();
+ parameters.put(SOURCE_ENCODING, "ISO-8859-1");
+ transformer.transform(tmpS, tmpD, parameters);
+
+ assertEquals(expected, readFromFile(tmpD, "UTF-8"));
+ tmpS.delete();
+ tmpD.delete();
+
+ // Content set to UTF-8
+ tmpS = File.createTempFile("AlfrescoTestSource_", ".html");
+ writeToFile(tmpS, partA+partB+partC, "UTF-8");
+
+ tmpD = File.createTempFile("AlfrescoTestTarget_", ".txt");
+ parameters = new HashMap<>();
+ parameters.put(SOURCE_ENCODING, "UTF-8");
+ transformer.transform(tmpS, tmpD, parameters);
+ assertEquals(expected, readFromFile(tmpD, "UTF-8"));
+ tmpS.delete();
+ tmpD.delete();
+
+
+ // Content set to UTF-16
+ tmpS = File.createTempFile("AlfrescoTestSource_", ".html");
+ writeToFile(tmpS, partA+partB+partC, "UTF-16");
+
+ tmpD = File.createTempFile("AlfrescoTestTarget_", ".txt");
+ parameters = new HashMap<>();
+ parameters.put(SOURCE_ENCODING, "UTF-16");
+ transformer.transform(tmpS, tmpD, parameters);
+ assertEquals(expected, readFromFile(tmpD, "UTF-8"));
+ tmpS.delete();
+ tmpD.delete();
+
+ // Note - since HTML Parser 2.0 META tags specifying the
+ // document encoding will ONLY be respected if the original
+ // content type was set to ISO-8859-1.
+ //
+ // This means there is now only one test which we can perform
+ // to ensure that this now-limited overriding of the encoding
+ // takes effect.
+
+ // Content set to ISO 8859-1, meta set to UTF-8
+ tmpS = File.createTempFile("AlfrescoTestSource_", ".html");
+ String str = partA+
+ "" +
+ partB+partC;
+
+ writeToFile(tmpS, str, "UTF-8");
+
+ tmpD = File.createTempFile("AlfrescoTestTarget_", ".txt");
+
+ parameters = new HashMap<>();
+ parameters.put(SOURCE_ENCODING, "ISO-8859-1");
+ transformer.transform(tmpS, tmpD, parameters);
+ assertEquals(expected, readFromFile(tmpD, "UTF-8"));
+ tmpS.delete();
+ tmpD.delete();
+
+
+ // Note - we can't test UTF-16 with only a meta encoding,
+ // because without that the parser won't know about the
+ // 2 byte format so won't be able to identify the meta tag
+ }
+ finally
+ {
+ if (tmpS != null && tmpS.exists()) tmpS.delete();
+ if (tmpD != null && tmpD.exists()) tmpD.delete();
+ }
+ }
+
+ private void writeToFile(File file, String content, String encoding) throws Exception
+ {
+ try (OutputStreamWriter ow = new OutputStreamWriter(new FileOutputStream(file), encoding))
+ {
+ ow.append(content);
+ }
+ }
+
+ private String readFromFile(File file, String encoding) throws Exception
+ {
+ String content = "wrong content";
+ content = new String(Files.readAllBytes(file.toPath()), encoding);
+ return content;
+ }
+}
\ No newline at end of file
diff --git a/alfresco-docker-transform-misc/src/test/java/org/alfresco/transformer/transformers/TextToPdfContentTransformerTest.java b/alfresco-docker-transform-misc/src/test/java/org/alfresco/transformer/transformers/TextToPdfContentTransformerTest.java
new file mode 100644
index 00000000..3623628c
--- /dev/null
+++ b/alfresco-docker-transform-misc/src/test/java/org/alfresco/transformer/transformers/TextToPdfContentTransformerTest.java
@@ -0,0 +1,157 @@
+/*
+ * #%L
+ * Alfresco Transform Core
+ * %%
+ * Copyright (C) 2005 - 2019 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * -
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ * -
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * -
+ * Alfresco 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 Lesser General Public License for more details.
+ * -
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.transformer.transformers;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.text.PDFTextStripper;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.alfresco.transformer.transformers.TextToPdfContentTransformer.PAGE_LIMIT;
+import static org.junit.Assert.*;
+
+@RunWith(SpringRunner.class)
+@Import(TextToPdfContentTransformer.class)
+public class TextToPdfContentTransformerTest
+{
+ @Autowired
+ TextToPdfContentTransformer transformer;
+
+ @Before
+ public void setUp()
+ {
+ transformer.setStandardFont("Times-Roman");
+ transformer.setFontSize(20);
+ }
+
+
+ @Test
+ public void testUnlimitedPages() throws Exception
+ {
+ transformTextAndCheckPageLength(-1);
+ }
+
+ @Test
+ public void testLimitedTo1Page() throws Exception
+ {
+ transformTextAndCheckPageLength(1);
+ }
+
+ @Test
+ public void testLimitedTo2Pages() throws Exception
+ {
+ transformTextAndCheckPageLength(2);
+ }
+
+ @Test
+ public void testLimitedTo50Pages() throws Exception
+ {
+ transformTextAndCheckPageLength(50);
+ }
+
+ private void transformTextAndCheckPageLength(int pageLimit) throws Exception
+ {
+ int pageLength = 32;
+ int lines = (pageLength+10) * ((pageLimit > 0) ? pageLimit : 1);
+ StringBuilder sb = new StringBuilder();
+ String checkText = null;
+ int cutoff = pageLimit * pageLength;
+ for (int i=1; i<=lines; i++)
+ {
+ sb.append(i);
+ sb.append(" I must not talk in class or feed my homework to my cat.\n");
+ if (i == cutoff)
+ checkText = sb.toString();
+ }
+ sb.append("\nBart\n");
+ String text = sb.toString();
+ checkText = (checkText == null) ? clean(text) : clean(checkText);
+ transformTextAndCheck(text, "UTF-8", checkText, String.valueOf(pageLimit));
+ }
+
+ private void transformTextAndCheck(String text, String encoding, String checkText, String pageLimit) throws Exception
+ {
+ // Get a reader for the text
+ File sourceFile = File.createTempFile("AlfrescoTestSource_", ".txt");
+ writeToFile(sourceFile,text, encoding);
+
+
+ // And a temp writer
+ File targetFile = File.createTempFile("AlfrescoTestTarget_", ".pdf");
+
+ // Transform to PDF
+ Map parameters = new HashMap<>();
+ parameters.put(PAGE_LIMIT, pageLimit);
+ transformer.transform(sourceFile, targetFile, parameters);
+
+ // Read back in the PDF and check it
+ PDDocument doc = PDDocument.load(targetFile);
+ PDFTextStripper textStripper = new PDFTextStripper();
+ StringWriter textWriter = new StringWriter();
+ textStripper.writeText(doc, textWriter);
+ doc.close();
+
+ String roundTrip = clean(textWriter.toString());
+
+ assertEquals(
+ "Incorrect text in PDF when starting from text in " + encoding,
+ checkText, roundTrip
+ );
+
+ sourceFile.delete();
+ targetFile.delete();
+ }
+
+ private String clean(String text)
+ {
+ text = text.replaceAll("\\s+\\r", "");
+ text = text.replaceAll("\\s+\\n", "");
+ text = text.replaceAll("\\r", "");
+ text = text.replaceAll("\\n", "");
+ return text;
+ }
+
+ private void writeToFile(File file, String content, String encoding) throws Exception
+ {
+ try (OutputStreamWriter ow = new OutputStreamWriter(new FileOutputStream(file), encoding))
+ {
+ ow.append(content);
+ }
+ }
+}
diff --git a/alfresco-docker-transform-misc/src/test/resources/quick.docx b/alfresco-docker-transform-misc/src/test/resources/quick.docx
new file mode 100644
index 0000000000000000000000000000000000000000..cd28e0a1a7bd9f448b7784a42f83418d6a0593b1
GIT binary patch
literal 237227
zcmeEtby!qixb7YX7`mieKtL&JkX9)X2?+%W1*E&XL24)gX`~SZX;8XDx};-38fM4=
zhT+cd+xDVJzbE$m<0e}t;
z0NexcuneC$I=ERlxS8m9J6X6I@q5|ZvF2lAapwV8nEU^u{X1TPy3fN7p^wQFFH^2b
z2Nnsk+_ZFcRjB=W7gf~dI;tTXvjX#@`^Qr9A>X(`WvXH-k6n*tz+Otpae9LUehIUi
z=3j_aup$iatEVSd%^O~;B$1_)zkCapoPWxij5r*zYH%l`(8)|lR*XH0n(Uy-dpl48
z4o&Iu8DU$wPI}T8_y+$h;IUWUQU)GparFr+Y=V%|uI$6f&(3~g{2|qor88YE$@ilV
z2jBJxegAe=IvPsA)RjUGaodIRKC@W9M?D$-YYKmrWGEE7tbAe*%SION?(>i}U5I5h
zy$|_I9l5glKG)XJM7FkFI
z^VjGBscqvh(cf}aN{Idzn!=7Hx=fxXX(iibLUP3PQ&*;(5v@fx#Sh!lhBQuu$iUcE
zxyqC$y}?XK?q7F5#mK*p<&b_;XO(9hEIxn5c!#mCyE`zT`QMpme-~HF9Y*I?F{6YS
z^E7d>uyYmQ|L6K2X8G^f7XOXt6-gs6z=C8!$05gLe^*8tqCbf^AL^RggaBoLb)b
z8)^RSaFIcAI^2T%z!@#uruwl}vPq#udq~U+XZRbgf-%I*zhtet)$m>DKGeiHi}51f9cVfxZ?U$`nvaFmWu!&>ABndnV8sB)T;Q~NgiqPuDJ7gxN;7q7Hq
zgLB5R8?vN=TDuiY(E6Y4WoiH6H34R%0So|)03OKG(ZyWgKO2p?qnW!sCbIn#(*D(e
zK$y6Ox%=ODt4JPx)%}lOS3!k}`fe1;hihg#kZCuP$#$r7LhlC*Ff(8I^5#E%^rDAt
zxOsNT_oK*)o!rZ@mB-HyD}vaDgTH<^D)>$T*I71M0%ADq*?ldwI)ca>wyx&?f_zoW
z9(pV&r@phJ94Egr95q}1nkeYXZ0}=j^f*bIj`Ff5Ad%qe(b$~yOPM_B6Z2n6@_$6r
zI=*$n0$Yq0_vnw`-P(*#M&Y5MH_99@x_ixeLZ4DRCa>MZLx~K`B(W|OK0vY3+SM`k
zviV+=qC=XDDe~P@A2lCyv93-oan7ne)!fSl{89?u{1&8C6M9bXll89W%~)?0b=;kM
z5@ai=BR;_J3jcqlOInLDv2oY{pic|{}q1|07CuK;c=TV
zDM6C%`>_uf&9a0yLLNV+emASqty$Uetdd~@{4!X?Xfk>)yYuIAx1CY|``g(MXBX13
z8o1as>PGI#SH7-H6Bjey18OQy(yTLG)C3X=vAVL#lA@oj$j0FI)quUl*9uG<*2(%ryv9H=-
z<8K~h=4C-y_=$v@w?=65Q#nuLQT@wc=f@h>xf{}xUScr?zsv&^3SkoT@gqI8Duyu@
z-UO@h+Y=Ip{dW+QEaYUqjW#efi^gMypqk3dK~wYuf1i(;m_*@4Je6YhBd0Q_75hk#
zmicGh&*TCnFWMKnXm_0j{Fl`tN*%>DB53)W>n5b3Q}e7j^|(o|Y0Tvay1i)WR@g!Z
zMiir0mC&rJ1v}JEaEDDURk=e_qwx27T4WWL4h|MH!gO#M>)c@n0X$bxIkm9^=)mO?
zzfbWS@g%RjfvB!EZ4up>&|gedjsc%^_oD(Ac{wN^^t$2)a58WkGFi8|wCEi^e4cAH
zTd61{`|vBbc?EWuxz)m1dpo+moLNCVQOTHnOll$U8Gf3IJow=~UjtU7xIQsW&U>|V
z@{bnSB3K@N*xn0DSl_bo$T()(l1lrgm`G@lj+LRum`#-
z#YgU?tKaJG>bVS}d6=QyaQ=V;b=>vBw6NU@@iw(dWBSoaj+YS=92qG4mi1DRAv-Z0
zb)3UTfx9USob#dqjj)6D#%=Z=VLuy-Xq#Ax^dBHRRWwX3_@erX$O%8y&p?!MGrv6J
z|E+>wy5MpXx2^ej+R7mrp*OJ55}bGWk>{uFZqr$d=UHgzuY?x@cvUj&@|D4f;mQo3
zy44ShX+>cXX-0(~xS~6(c>}wfsMa0rmHRin4n|9DKQ$N~-E8jircNxOyjR*`CzqE)1SqbZg3YE$sD
zpdySJ?>n1i=_~2&BM)7^=o8evV12p7SRTRrGhuqEhI7G^;5>q1e^$4Ua3cDMr_;RP
zHZK4P`ES09;J*a2`@ris#IPv4DR%^}qTqdkb^x_X5`T?=39^`E8smEbso@tpQ{&
zlvI@f5C{aQV1B^eBJd2r!^MT*;^09b5PW<*0wPLcB0@qUI*NOwluYz2%uMu*jI12|
zT&!%o?2L@u;tzQr3yO${uy9GrNC-*u3yTQ-GYJSEAD@Vjh?ba`R_H$CeWCy7cGm%r
z<6#rw0ALUsfJF`hlY{Ph02TlM;b7kOpBMfw4TJ^8#=(W);S&&IZm1^%us~oi7B(0M
z2OHzQLH?L=0Gk|#;=Z5)?meyd5H@E@q2R=?cA^GIlb44X(71bB7b#(Rg-@G+2Gq?C?X=QEW>gMj@>E-Pc8WtWA`6((o
zDfx3sYTB3djQoPaqT-U$vTrrDb@dI6P0c^Mx_f&2`UeJwCa0!nX6OFQFRX9CHn;w6
z@9gd&PEOCxFD{W+*Z;@`!pQlr?LQ0lKgdOnkqZkO8;lM4M=lVSC#HkRv2pGT;!-GR
zLEbyxV-pI-qkNk9wYn3ZU054V^}%I=fSN;O{Q=@1(f&oU|2x4#{=X#quY&zIxfTE-
zFbK1GU~)hXxV+}h4E-Z|y
zKiOkFI4N)(@RPuP%tVlry+56o?!X62cW!u2B*$z!h?d)Bv4QtS6n|5DyL4!V?v7=d
z=JPNit~WW1kNhb!A?Y4E6yQBXQ6l&5W6(oCO+?XHh_oc}RN>4Q&MWN?-n%y}2&aqZ)$uo~t?C})tG%myhH_Splhpt+2
zUF(D3x&1%4q3v}=(|L8#D{~*d2Gdf19F!a;?_en{H=(aLIT(v*oCu9KxFlj(E9O5Z>Tbd|HL%`uz6+_Xpngk;m>cRcXf6L$U0xVcKOSS
z&96N4>M^OEevAHy=H9FU%^{9w+1@?8@Ag|wmafNyL6Cq~km)&fU&P+#z~pWYb%yN&
zuFI2O{PNci!KZnoj`>X*0P5wGpYz|vLQ?BMiep<(`+4K^*HB-NW23q$Wk>LcaD{Vz
zs-;;Fa4b(TVJ!LG&$B*W(^r6G=X9^vMS8qfx4BOTP-RI`Utfiac`jtUvHhv@^JRrc
zq~!yh;8~P6*NP)Yfs3An%Ig@pSzsZif+=AyLt
zF?e8^fjL*xyYu>56QZF=6}8c7<4EG=aI6>cd-*oyS)IZ~Qgf#a>C0_+9`R2eC^8UM
zD(zmlWYlkGUl?M-w=U2G1wr!m>{e#|7>w2yhD
zI9amtb^nTx3AUALS$*O!QtlX2|1SeFaDSd>T?R;mT=;?eSH}xyw~8*d#fJ6h^NzC-
zz(GeGJI4D(7Fk$`lpZ7wr7g|0J}ByH_AmBK;1V+D{@^?wpnD)m)qxU%
zi(ZE0&&*zXJ@8!&Pa)#S;vRbkZ5CBFME+y
zgt>Z!_K9Uk)6+p0HXM0hoicTpv8E6PPZe8%|CEln8Vewblh;P1jckMjJ;ze8y#zRA
zgcn!qy`zvfr9Z?4kx%ui>27yZ+J&ByNgT>EZ!5aU;v>+>Fkf+BK7`S;P|>S!ca8uSc<%_xdRFPj7DANM)VaN`$cW{uG}UGgyx6g3aps^T5S;{*;0WN8GH+3v{yCwOHslGE^aX-6
zI0FN4k;!jkv}dZS*r8-v{0_|WC4pMa@p3ON32QUyuI_+TdsKOcoMqJ&ef{e7CCfEk
z9!V~2wEEl5wQRz*ae=4#v2+Z(;j0O1TBa9?IfXFMRWHsB-*)VsIhuRYQ>r#`97%#7
z&7g%zs&aAOjfXUM0ILiQx{c!w7}xuGDDXC|dlhmZYGpUFUiSm46ec#Q18NEl9e16|
z%Og37-as=WuSuD$=k&=J7*y>G@g`_U6TBgLm#Ff*xpOZ!CafFXEC=W;XR;NVeVnvs
zEkFcRrs#ABkox&@LGyk!(Z8*YSo(G>bhOl;_;DQ1Nr5~#)i8Wu13p4)rMU~
z%YW9mnf3?jS>g#FpimnR6xv>3OjVVw$?S+Sw
zU)NSh@{|)R8PA-yVj|T?A*cQUu&YELUpk4+CGpS5o+k%0_=I_6d&Z$pkTtcyp{WA03q>-!OC~FB^XC^zh(_
z&o2W*ea0b8`q_S#(^Y3xkyGz>=hCRtur?k)XiOM-tv#PaL{A42RY+53L6RhccR%H}z^*5X|Y6j-MR6&Z(tdNKA
zdaPtS#3w6mox5Q0y`ji_*7;2&Xjc7@hsS=Qfa&VEkB49zcDvV}4GDiBCb?m4VXAWA
zr47PFJGcC1QZVxJ6{0mPX`!Igl|*{qQ@YYOzxOA-Nl+16Z}UYYVT^`y>DiNr-Gc23+Afwp#B;bZC6UY0L87Ace>
zg5{U>Z@)1ilYQ_9Io0`e>LB^e-s7cmnU*X>2nh+Ub3Zm1~fBepl7*t3A`mPug$}Xj`q%P2;NSgl_=02J#eBV
zQ5A({%=I^Ym^PkLm@v`Ol{hsTitIPad&uFN0UWh)8XLXwdot@#QOViZ3}w}RlU49=
zDQ_(VPsVpn4z8v%?d}L!dS~M~C!S1Lto5L0gxw4rTP<7MOiGS2u0wEzxMwqHPH|11
z)4gXlkmRlOe8@@xg3z(EGwN%=3DM7ebFXh4H@U$xr6ZB>`_X_
z&vqbJR9jVuea*xLUpc%HzNBrTbOwoY3Cu2x0KRdYiq6Kk$dp3lhB$QDVBuP@`LIY*
zVoaC4^j5h@8!^_?y%uLPISPAW{k@V*Zh`upS(Z5M9t4A;8@S(9`m7i;UE{vEK{U?X?56u6^%ram)GtyiFWSyz*yv`-@Gb;Tbb=}k3dT;-S?FGwajI{Kkm
z)4y0p06t8&bM2e^1L;#u~Bx%C+>i@nzEL
zLFvmENf7HyCy+9uPm%`hchbmJYEDG|r>!XlWkZQn+-#0})Lb}9cnc7z+C%8A6?ob<
z7};5ByqimMbX0fKeCR;^=IJnYu{etalLC`%Ta^@Yeyp3}*!rs+5}AkJMrN{VV?ui9
z;7-_lR7r0QO7$kJ?smo~jpxkic4)xET{J123Ytxo^)sS)Qf1}2H%LK}PJ%0O6c8;l
zLtMey-?--z4_n`3X-lFQB6X=KkT?#NXI@p*o^od}GSmpU9f4f$bNQ=9=zPBc
zaCpx2boIqlrNGgb3hEv6gwjF4S5H4e`hB5S8~)!(PHzfag01l~>=0#>BJbl~(od(m
zwKiy4BLeTl!!G}7)dlfVyW6Rb(I;~`C^2NXc&Nw?GJnEyT6Ti1+UieG6Six(+@>aE
z`m?SfLwx-o!=*33KU&RPFqbE0=_bn)D%t*a&yBh8VSzq?-);q~+;@K~AiV-CHf1**
z<{ETNw@HuINLkC$HZ|93J3I4G5lNoCjvoz@X^1`*!1wz&j#6-Ra>qgcZfg8AG{_#I
zr!{8{<0`?n%n^Qf@fET6G3Qx<*9YHU&+WWVg!$$7CsgPzB{5#wA-@II5lHQJOuKRi
zSgP5~(g$k~-jh8~X3Q>u%44quue9P?7c_>b7P~{ar)#AcallFHfI0?cR3j#r1s2A-
zfAaNJ?EhoY4n9F3iR+E8lW6HaQOY_5b*OB55-35?zFFo)3WOya@Rjx7DK*XD$@JO|
zo>eK-+}ZOo^~To!T-7fl%jBy4qkYO4uOdf>f;TueHZBNj%`^)Wd@C`yf*#gGzknVu
zd1+*Tw7vuE;sS>FS%x~}T*NGC_ZO+!*T!PCk-z%LcVLRt<1b=|1;5lf2?>e8rrMc{
zn+bETBwgwGqGMN{IZ^f9WsLOuA}=ZR8;3GMhNw4RU(zI8c3hnAj(B?`
z&&yQQdmFD}Ct1Fb;I1iSo5xShHU&muMoXZpM)3X%#=pXJF^56fEkUlAJ{Z)v
z+eU}@n6j}?=O~hHtnwjw*kwm2H1_iQ!d2jUNsl**sbKl7;@7c&2bsTxlzMPrv~mx(
z+hHc^om|=2F7}J9&rB}{b}?D6A|;^pzwe;mDEY}dph23jd;td&qb>PEC({1147J=`
z1k7UR(9G2Bz5!c0ZxiK2?}uJdav`eBQbkj_;ySp8bR$bePx=ze2G<me7;*;EAR!xXLd{PWy(r0Abqa8QCaO@_d^yX0E4)6=T(#bfv1G*S)5C
z|G-KUDE)3pW&cpk_z_IV)%uZK9yMiZei=-j&PRQdd)$x4dAnMN`*|#H*XMxnW|1eJ
z?+f_SB=Jfp_}m5aobod4>SMVt69S)Hn-Tz5nKvX;JtlRG3-)k6`v;rQJ&7d>
zp*72f7xCSqXJLAJKMcRO=pi^B{n8VarzCx;AGE`A;wpgAI~tbj`};dSz*WC;hz#LH
zf|%F~wnp1Xqfd{qS`>dkOi4(j0G&}3;2?%9QWzq6)AEfNHlsj2RA%(_DN
z!z1hD@ngaLjA7)U>$<&bH`YbBR!!RLhC~nAEE;oZGUvg5t=(wP2w4&@@g78SOyYiyd^3DuYW$QZEw?cwi*wlt{Of
z;no(eCDA-PB#+TaN*@|9wt6CA8+d*Yc=ll&(qdwrnh}vJn$p{%K~$=hP|6O8h6e^i
zX9LOHW3bIOGc6f>DU!IcZ-P`%wmUXgu(f_vijfJrb^8vOD3@n>HeS@JLpGsIL|Agy-Glm5dNr)K8vvpvB8!s2g+`Wk8do!
zM83LqZ4+Co_3Dy?BwHQuY3x3{FU>{9Jmj%NSumUf63$Iv>P)MQAhc?faAU{#$sx?!vp!J-#l3xp)Eg|KR
z_vS-vXbWhzEukt4&HZ>5Eo$xK|8<#H3GYp4cE!8u-5tC!m^@Qe|N0$3$;gGynYyCi
z37`lZ1(+zKC}n-m}KWPH+m`Eg#1rg|eKe
zAIa@+2;34>pfKrc(%Zs2pj8eH^(jZR1jARHG&nbSN&CM0(Xk+GMRGlI?*L^Pz_W?!
zR6nL}K(s1ey?AMRpy_9bq!`>IMN|H4fara-)BG2}Z
z<2()cf1WDZx_Yvu#f3Ed#V*EQ!*Y7!DHjB3T|`!OHZ+vmI+j}}c0^N|>1*bBY_!N3
zv`zVmBMLO8QBv-N^Xk&-Y>Pi0fwOD4`)OH|q_7kYjiY4&q=eb!&k{5?#@fkW#jNRv
zt~EtJ&(uC84=oiZ9E8X%ZMx-=Y8ihHi~~zgphZBZGU#O6O7!^U6N^3ShSalkN=64l
zT06@h68+eW%=vB5`l*VFe3e6c)?hNf7{>QcZy)n)-^g7_Vl=#}5u@dtZPC4e-%Mbj
z8B8PYUbYLS5Spo)^Wd!^o>^=Hz=3EA(Wun0Gh0mVZ6i^0w##Vv!b;dUO!7;cdt`gr-k-p{YW0jPgUY$GY7ls1Ux~0yOD?ALUT)o##PItLsVbi58)Jo
z6stj%-yh2}F((GD-2qs;V%HKg2W(a}6v7fDhs&9an;9n>aX(8UMPo*o?|`SR^*IOL
zQBD`P9;hN=D2!M54w$W+!srW5i-?<=+TlmZdWPXIsrmxKj~>3y0tzjsl3ZMzgri|-
zQ7^e}GkN9#$v~S8!VeF$Pa7PptSUZP`GdRBWjlU%t$KC$#xuaG{=VL`r__j)j
zIVn-YBWT=2FfHrN2TTx+xdRfIF&7hxz#SgrxHZx%GB<(iQwfp7-Una>uAw|$^(Lb29!3tL-I<6Le?KHFB=ggW4LY{;zk;I
zZ6{Q`7!yV#44!k1UAqYmJ|FR@n*oRb!9N7%mKfHsg|Es=&-01V
zZFXM)<&Ed_07u@?1b-#x*vsA7R=M?}>Bd}=I@OOK#x6{Y;$Mdb=XU6)`@M(DMYIU7
zGx&aISBK7SRi+Ac{z)o&lNh41ADVF%7%q46^6c3i@G|hiOr9wlLv)e%ht(CtUj*jGX|wpN-x2*kusvJVs{Jv>fGe!4IT;MO_6b
zmF0nplbR%9|6h%DdD>NMckg-u(D%CEZ!3x(moo>vGbizvEXLqn5)lnTWHGGw$!J}p
zw#qSEOs}qvhvdNEW^jUxu&yj`r~{_W^NxwlH(HTeA39
zF@h5aUk+CsU?qJWqS4?2$RukaN`l=9A67WJe-|_K)+y*aPEbCRdxa@U0ZXbhvF^4i
zn|uxzSN)nEKQP85Tfd7yvBG)D!k;|)p*1XhLJ`m|>TS}Ys|7n$*CY;E4uKYcf@sh4
z2v_MVD@E@BW=x_I@gX6em+T27u{wD_yno+#wP2@#o;n>j_q{#i&uWBz8|%!0Xdtno
zqoZF~Oa$Ln1Un8x@U)BYl0VKHqC`08d>BSe-Q`K*`uUiFN5uBPzs+&1#j
zJf6@H;E~2(EAZ=$<<&6Mv$e&j@AXpnqEgy5hII~vjlaPHObT=o=UDY#{Jh3g-(W(b@*l}yrz4QvHKS{Sik$MWg{^3vy
z0fNLOv&oiw7|)MN8F9QFj=!a^KjQ=Z4&gO|g?=_OXPGWt3vzW`<;er8kDJdsm~L%f
zUxdw;#JR;$)7*csEFDcEZhENcU!%c1lfA|szd&PnnF9~peD>-#@31hk=gEUBZ8Je3
zb2pqfi~;Odnvg0lfv!|zYWQ+dEcFi8$xP*gj%AK87uB;d4V%+DAl`sKM_SId{6Wow
zy-_wH)=qQQEJhM1Wx7q*BhpIiOD*Hwi#fdmX1e^hhbuf+*UFs`Ls7Ga?Zt%L>Us3qMPN=5i|4SkW`e%Y|&&3Zo>~1t#iWg@K5_c
zutwr5fou9|xNFy}k8;R&QQTnq8%>iZs&8F7HJ0C9(9&JciXW0%^0wWZa+sz_Ft)ez
zSSm@z?D(8LeDs{4qqs3;n_bL8rPz9PaX2x(Sn4t=jRcn8?qW+QR#05+MiTmQxrN>GQ_
z=rt)d%1T}Qb?Y0EOv&f`qc=Kiabsg2ddPFNc^3Ilx@*H~Zw~FxjfAA!jLPVrgo7EK
zcsi=wm}DRF1Z5;Af5eaLz{LaSv!WL_n!Ub
zU4*G;zRLcF^w^`W$#}D(+i0reAZ4xveXOKW{K}ta0xfC9r2`i>>B%gaDx`3d)=JXyV8w?3A8(prns<&Ags|WW7SyBL7lBUYc85z%Y?woPy-$TU2ym^RsZY728fpeA(6TBj7-UNhm)|~y?Q9(3I6t0*Qiur0w_8tbr+f4^9{{x-MEib}r
zQ~2Fvo<9_{Ph#_@CF{nZnt^f1SCl9`J2#?*G3lD0DJTpvT>b9
z-M2HXu?&m`HKSVZ?QGGTCuly*K>yJF+1)oMAm6~2lSGB$=JT!*a06(z3u?5
zNHsWhv#S(PJgPFsRo?Wd0KuUNVS%$4Wxj^n&5Bdd_~OS}y-D&QPmm+A4Ng|AoxKCB
z%IpL-C>B*c4iFdi)zrXsh>p1@9n*fCk<@_iUdR8YO|t$RopETY(1
ztUqI9eP<9P8$|6f3@k2?cu;
z2QDQLQ#y9U##f}FK$tY&I)h%O?o?liN4{;EH|`eSgybe5n7E(3Qi~BpN|Hr&;wVV=
zolA|Z6a}taVj;Y-a}^gE-%amP(MTU7(?c+6*Q81P3JN@Oj=*-8n+;+?MDKSkT!yPJ
zrxsZ7m)Pxn5R_OM^0JWFlS1oGqqt`N&dSmnNFR-fHYLq}$x0$y4@4{%?D6oTaNsUg
z!ZxO$L2<%sRLUQuYuAEjC`wK&v#9RS;qBkwqjWVL-~licVgtbA!{|wJtNi5K>0=3{
zJwBZlz5?mluPZ4W2x&~a_EVi@>5VJIbK=1|I{xH2o5=ZL-vNo3<&`_zy92I8d3K@!
zo-tW57G#4s`t+$B0%06!?1^YZtHxd4dPLp<&nQ7@a4Gqzb$H}OdqTXgD^J0T{iJUt
zAccNrbE
z^DU*>MOri69gvo;fqp6)c;Qx`z~r!ReKM9pD^nvB*j2hK_v^NpADn>S{28?WC5k1N
z5_G2McjW`;a-x?*Yh>F5KWheqNva2)|b=E?+2b
zLVfhJ&|@#+3v3M&HI6q9(Zl)$$V>Ed8Ao(0bIbMxX1WAfOu}dc!x)}HkW3zt6SvGm
zYtk3MRYA|J*mcPv{sm#3fTPUjL$>RX@~pEiX5dI>>k4A%RKIG;th>cXKF_7A5s@ebts1CH|8dk~)d4
zARr5b7VSrw&G~U{un@9aU#Db`77^{42{N`#C3NpjwJN~p)<4E?7!e4NBcs(aua45w
zKL`tz()GQG|B_QuRrCf~zD7#u<=Ls>Uu+#^@4@w4pF(nNn;kpB3S5Jfa^)NAHj*0o
zu<}}!KKXXM9}FLb@TGQ(S}mYh3)DqtC}eFkUj;
z)-wL;PyiinYz=Qmt2KPb?4f|%*%J&dV34x4P{3)?3ZvEhgbT^T@E_$XhP&-`I&*0*
z6?*VxrK1BcO}5v`Xn&ra>nqQGdXY9Z9zI@
zS_C;LQjV`)9B-!A*5$`@&K5{7
zGwn2*6q(d*IKi0&(~!_UaUWxtKm>#r^P>eff!>G|j`
zJbh(Fz)PT$6x;TRa+H9(jP1*&I&wpAP469@)Q{MS{SrWY|4Qp#GA*V8LOkIoP^EAq
zFIz2^=JwMuSMo+RHjK4vPqP21%JE9;P&7BVYq4wL7$w3z8(4GHUK=pUaA^GY1hEN|
zs-BYe_o~uISVnx8e!IRO$9Zt@R!LizPOxdGdyb{jSMQJ#bXu9_h!9wJJ2f!)L#(WB
z$&2+hiIXd+BZMG1co%tI?h{dlc(%b9IG!OszU0m?yu^M)~GtPZmiNn2U+n_m&bA
zm2Z8|3Qg5G#o3y1?Zx_MYAC?c&UYKy;o=10I5pOSYjj;7MQ&spz;vegzu!;ADm_)w
z5nLgGFxf7Z=3%u6tZRgyA98KTmiU+MdWy}~C_GlRi2YVA^blZl9Y<3gebxjiQfX+n
zN2*QnJ06L9i%p1B)tMh^B?m99X+oF~zKSt#ke;Pd_HXUve@1-?4rx{1D2kAAekXo#
znwJv)dT#4=^FTv*{G}4zCzJwfg6#PuV4%8i|MFGTgM
z|HXv{4$|9CU|ous2mCuq_#qQvj$sZecQWV+k$V+6V!PYk_a#CQVHu+4|21ovmmKEP
zyf=i`4NgwElu>{!?&Oi;CT}SB5|*2pK_Zf*x_)w^_Dhx6(%qe5iLE!QKvWt3me
z81}4i+g}$PS#5k=85o)Qa<&}eb(m|+^M^Z+#Ic{5&V(ANeX6=bp$}U37^PO+a*aO`
zHh!>v_5w;i+-~n@K(aqWf|LZi|QP>wW1KrHiWeo^+Xh+GRw|DFXx7WC3CNwu%7N
zxv-19;WwESAr{-qz>>!|7vi}Mtd1=56RB=UQ9d
zXry|OZ;{V22^;UL2CK<4D^;2(U}?pDZlleFYNX)Vy7a-d=(jqF%
zCggquOl<};syo{p3_t*^3L&6RIA^(>r#AqCjM>05ovZitcBtG+=u36xH=yj4d?cai
zO}n4dl9*d!l}BRRRc%xiRKfbQA(gPM?qr3IhKQEPjQRX;Ahw@irIj(;EZ2)-o|nk>!5_55*^)O
zRRPA@GEiFUCQ$(bQw#>Pe4Azr*r6Ixk2@GcI&qyKbX**WX`;CwDE@}FYGh#Xyp340
zfssO+ShU5$k{~Wk`}6P=coL9EnAzi7=7{g`u_
zKG_&&c@$-j5MCcmhy3BC(VXH`U^g75GXv-3mEQjx3JnX*N#D&bH$*8TivsO!Jxelq
zxHloVVo=(_wdy6+4pL&d7KXj6Ggs;n$7Xp7I%k|~*OF(1^kBY;
zCS};gztDekF7>XCuMhIb+N(?*OmPacYB
z6)z5-sB>6NY%=Hlx)(gh3TUu%75N;8o-tIBczu|9`KvAI!Yl1>%3z0VOpXV1zA^$c
z!*Mv8cFwIT>SrtZYq62x!i(BZf0Km1^$g$vuzjR{(*$Of97FvCD(C&2X3lND3;pGj
zeS0+YPTHIuH{r0eU-Eh`-|s2h+*lO8_~+=&(T1bYg5D?ja_##HL<-|MsVbgO_?$BZ7?~c^GF<
znxrsEW3nwFKS)v_qJhT8vU{0%=o+8z=tER!FWXw#N{_ko9?y5o2NV4Wv0&ScK++X!
zn!vrvdk$-oN_%*h5URBWa~5(m)$*8yg{h+*hfAklEypV#2W&E(93deob4LA15lzV0
zlk>)&)lWZVx{d!3GRZCzmD^HU5O+hPDU*H&lz^*@Temyy8D5X0?mOFmn;)~5G7=6;
zC&+GMf@~Y7V0a__UXzG>Uf)#evnjvXzyW%pMZYykh|CwC%Zthn{r#BorihgL)NzaC
z#b5zQ+d9vGlLYiYh4{RKYp&IIixRXEKt1jkxM4!KfK2OYyl36eQ1TGl^1X9z(>>{j
zM;Z=No4!VTl%O>ex)yN7p7FyNo$VRmNp`gtw|nwC!F
z&sqJn$YD<&_ZY3@UPU2!4nDS7a)XCkLSz=Iv41;n*Cl&HE%347kEG>9|*&jrR
zx^EoD5)gj!$ntXe2C*Eb2%ZR#de=e|>ow}P4)Zo^yv;DYF$iT?h|-&K6g5b>nz#Y=CHz(S#=E~46@Qab`{QGSk@}-;%xN2tZkeao
zeZ$Yhc_e{~gxUhC0X^+P=5*9;)3r@9CFr$!>W072tZfsjJe^H11?Zb)r4T%@ncr2z
zcuKL7bt4~>_#d|9yZY+I-#1@5JhwCjDZO$&KQ=y9#1NPPyzqurPej|8Uc=;(`P8JZ
zo;V>LH&)8%t0zu@166JqX9kb|gr=H17l<+VSYTFUq+!XhNRuHksJpP1gSxoOVZk4c*GKQkqA~i7bJ4ZTB<^
z?q7pHoqK(Te9F!@CDDmE=q`~_E|%Q3mt{q1O!CcXw8B>xN@8)|76o^U&slfM{wMC<
zJRHjZ{~sMjhO(qo2&2f-YO8Ex(Ayfu)+V8N$0Yj_V;Ng1G`6TTX2>!qwAiy2h8Dw&
z5VABfhJ+Z)7}I&F&-eHH{{B4YI_GztbN-m?V&=Z@*YeyR&+T=y<}U(Abnik+f?n7+
zxa8$EV%tCf&
z=LVn?GJanFK!kSIl6iHHa7&g!$OmZsPiY^!0(UVS2{zP}Ru$%6d3)+gdZlYUNH?)R
zCxV8G{~JEV9l<#87g{qAuA@|Y5#p))5@wRM1od_DSfEs
zzqfDxKl%5^D?2Q#P#fk!8~%~$W1hb{IBBtg%ZioLpk!Y8fA>dkJ4CrWgx6kqnzuTsH)9`7WhbHIi-q1R$;%&
ziYG6LU9mnFEw|~(YjGs2pL<~K!n=WE-X&hDhu*RhRjI1_@yc498&`IaL5lk5sCDME
zGkR;kAr68235@M6yK!r|BI${iiEk4+^x<^_-M2+bcF)8lK5^Y2V$da{)*}TOeVTFb
zMe~Fji1Tn1kc03cf9#E)Usi6O`auuTaU&r-p_s&v{lQ-~-mSRY{Om*RA9;aNYn4~3
zf%Ko7Zxh`0?uqFqev>(V40X14Y3|V1
zmn)fFFj!o-A^$5D<*w|$h3~C3eXP6Ja{M2qO3DOwcG~!p_2^ZQpydl1c^rdkgS2O?B<_nivKn+2;CRY2njTG)3Pw42;?_NpDimp&|sXE#C*UwNPjW&DbCcB@Q8|pIzh){pl%OUZkx|SASOiatCi1o5r
z_VJR%QsMJnoki4L-xQY~$vkm@cANMKAnK-~5j$F(H5Y%r`L@Hes%-n&$m5=0I7^<>
z`A~VGNK}O*tNv|I#O1uu$3<86+u;`TAe}qKDy3ukM9*BV1OdB%?6Ec%!L_LGWh~(v
zd^zUA;;!%Co7xz~#ev4g^2Dji6S8Y=ZLim*&R6i!-1P1F?^AkI`RGISpp*K!cVHX)
z#GSlL!I36Aj3eem#x&e|P0Tslu3d~czi__LuX@4jMM3hQY>=-6G6@3v<_WEQ`sjIi
z%!62<$a-kgJh~S$I7Vv?qY|HS2~WS2jpnK>k|JdXlHFC79I9et5YtB43RBcV6<^h
z4V=_{)NuA`*S|Mj%qN;wh1RIax$e@Y>$8|=3f-o;hkC1qRVsqsto`(!P%uBp%bTh(
zg0a^;v3FYkPI7+i$nqN=`OD}Ar*V4mQ9aQ>8w?R-;&9eD1-7RVLVUr=hBL$yb#)%W^5KX&!b
zp0xP1y(1xbp~;Pb(hJ?q{2)(RGULMP_T3b^(CYxGX$OK>mCuyk^c~Nuu0b^_JQo+e
zN#OSJgd|QRw9oTakux^yA2S@$T^3iKJa0PbJf(K|944&jJOWbj$^r5{})%^-MqSpTV6{=d}L4?Pwz!
zk;&)J#P`7dHNHCSe7r}`@_A6C9cUuTGCt%adW8g&T`Q}HI3C&Ow0QpZL~SPPzV!}+
zv0$h4Ps_IH*u!L6luD?^`TEOV-acX)r#@dwqw}qNkA~j~G?2Mhadcs|ZS(AfwS51H
zOE-_XRV7|ZwY*(3!6Wy}KO?2T(QK!QVJE91CqfuxrNlrTl*>LWdxs6I_kn|k)t-hE
zN2!NRqCtoN0t>w8h`isRT`~6Ln34nT=eSBhTD~+-s{%b{C|XD7|4EDVzw_B=wqWHx
za?Y;HGPleZjdKS--7R!Q-WPOtn@>(z6Ank-VO?ZVM9^%!$C1G8bt{P0Rhaz3^OU=6
z0not8I$~@iT5v7W*H&pa*He?^qQ!1zeoa;`8k0V%Njp^RVH
zr7aEd_4BuTdldurh%a|*A2mH``VISb_zKJn91Gy_vC2zb)-$tfNS*&X!bDJVg;5pm
zoM^wX1Eogy_>w=IV)gWOgHJCgCtG=xa^lBsC}G`5jg>2uay#nC!*+u3ohfFVv%%;;
zKalsL$2ZO77b}h|EFu@EG@RPXJ#O<%cD?AALG#V~&!yofD3-EcK$>ypxv1#k##zwH
z<)Q)VW{N=5hUpm>U0&=&v08^dNXAqONA5zFMf^ZcPGjdl+Qm}}GRH-DaCeGNV#(BC
zw$!A)BuAxk@pQs5RwHIA?7#zc$Kj5(?TH@+wPjBGXo0|J(HqeemtS(E%}CO(ldl)T
zx#s5+xIdcNC#QK&>@tMEquhSD=y~Me4$tAPqhH*Qw`CB$I~wN4S7S7hd(ao^J8G@w
zI_uQdk52Nmcz^0P*G6net$Jb|`o^>j`B
zz3U3!`&>os8BF@1=8qwf05DnX|KxcbmT67OSFV
zwhAe2G`K#Q9u8$XXFQA8w@^5hd8qOJlS^Ou^qpoVbH?VNt&;7b1MezC*Ir+iN?T|u
zKPqH`)C+6h!3Y_RW=LG6SZ)jiH1V#z;(;$@#%H
z^1J=YbS=+E;0(z(rSdlnu$^5`u>*A=Jw5Bv0%YRs#F$Kb#f_Sf*9R`~J;z-de3hz^
zMp2NhO8f8EPD2OhHTR2(tvQcb_f4n|>-oQyyQXd+G1U~&xKN!T+EE*!lhuYU3rKsE
zw9D+Upd_V4u_=p&o!keiDV^*=1@xYAM1Io&~d0XZ#YD`q)%T(n3U3CX8OP;q2`(bL|
zccF6a@}&%b9G*P0cvF+S~|byf9e$qmGF%onU1CmOep&YxSf8ZFyRd10)HGpZIG
zfvQP%m_qdiDkIeB6V)I~>iiqh*8{t^CU2GW4b@g=9m1IcM*
z$;s)_qiZK8Ha=~~{y3}QQEYNMZ;){_85tiN&Q5kKb69j~b!hxN*&lJTxH>2Fr@PuO
zakZ{P7O599xU!iPry1m4&bA#+qctl<#C^x-?itAZG7!=ykuZ?CCecf4`IbFCm1Dac
zt+o$$7WEeTwdKJM3n)b!a29(m6kdQYY>>}4Y4;Y?C(3X8{D!YK8b7C`AT}c+IvIK7
zMXnG^EcCMZqE+Q7c2zS``uG+8Z>oc2Rs})t&_txX!`i#>yeq?!N9Dubnn=g;GJNG$
z+9QoYpb5sc7Pb8iDSznlu?P0*OQ?&*LQTTSBUcp1x)Lvb{d#tHMh$4M2aQg8zaeO^
zM&xJHp789|EB0K2)n?Yb5;xZCZL%x~r-}8rIJJMjq`lXY98ZPN`#f@=FHOuR5k!-#bBe1A(Gmd6E9{aMiH1o+
z2c>43_D7ua-7_Ql^u|go?YvEU$mG!{Q46h;s*+kOAkUGk59`q+3XFMWWf`9s{l3Gz
z0sqkVNeQ_OE$=bC=(S=zRT}mcDs>$5Fynh`8&8=&+6>PuG~gAqpL{+@`Bwn@h%^
z025|Af`XPXY!1YH@v_z8`KqU_MWH?i-5yhhMl5&ENxY4<_6zHj@ps@HS@3#SAC<&A
zd;V->u@3*EH#x=R^irN_9gk-Eg(j5=k*X-4SnH@~aj3t;D?S%P=>NoVj)E1TA%`Q;dhps=HU=k!IH)lF7AF@cx
zq}UbgR1NI#8as*IX6QTELWTQ|;$Pkjt
zyFJX!VrVB97MkGDOg#~F<($$9SBF-G_njnXF7h)g*@d8WTlo41(_Q=DJn71_l?^y|Pe@RPUn65MI9Fth+;p-pN^Ul3
zLR2uz%QVCNRS`DI)G4hO<`P93``oZ&JPFP>{j?wQ>dJellat#+Uz982NIM5w>J)K&
z@q2el#V}dwig%*<`^ntya}QX`f!$iunB)sof%ntT5+Tq-$m*4Yo5Kv+(wU+I$;^=f70A
zgIu8mVu$fxMnvynWzMrlB!kMd9iHv~z5$3CQXBFfz2iLeo%5cW+9z{2pB;IiTQDh;2-8yFHU9w^_u<8b
zgoLUudSC5LUe`V3Vl-WmJueutUrIDnggh}2&?R%g6e-K|J>sXm3%V
zSX{u+%?~zwNdM;ABtP;UVNGJsr2eVPV>}TaDQ#odJzi&{v_mIa?aJ$z_+4Q?je&)z
z5>|hAzVcN4ZTvpJ(~8=uI*b8Wb0Z|IokC98PQFGRE}R^Y=uG&qgHopix$r0yWz+Pz
zNq6vu;{*QDshNpF&&1SomX;2#(f&0OO2v%E@D<|tiS6izX9Ll>gJkv8a~};8@sncs
z@st}>9T8lxT1n2~wi(FemHk0Wh8_A`4H~Xd8JtbT0@c;q1CaDNolW7J99atIaE@qd
z)TJ=cbW6G7&3$~eba<0!`?9(rLAT+}-7I5DxrCVYt5;RDvR(b}hmLrKF1=EA6G-Z|
zhKvVhto6V&7UfMp2NPi<#!sC_42%Ey=jHmYR~q!vN!b9M@NW%cAp6?BrioCVZaTW*
zT3;P{MC&AqY2P1mSIQhUm!Hvy
zo6qj2MjV)!+m1SI+53DBr~pFA&f|zUS*%e1cSuAZ>EfEkB7gY9Ds*Ld<9i9wtc#%I
zZ(0)N)~zu()hLU?5q4fiYRk?Qb^N1$UL;ZjzkS*3&5_F+9yJZscfRq%H3Lm#F3*Va
z*9|sFhn_kTqZ1nNE>wFxzmu{-0_E?jQTx|EwCSrh_4!l;bTHDn*
z9T`X#{);T}DQl}pwq!*_qVmK&w|jdvZFozCAP7jQ^oa#DY
zI{_AeTYMeg5PUfm|Tuh4_?L?%0RN
zzrGv$Q=cT!#qNRKj<8sZ38sf@4WMY>6O5}K9@D@%8wp6t$B+#~e^p#ri-fO`-U^=@
zk{mjn{&`}TNt?L$M|HJMxq
za5J7`-!Hd;0-2H)2*}-XcQ01IUKnc|U|&cY;!cg)LBg&SxDS~LJ-N5dtlKY)UR(aA
zjkrHTl`S(OqCt7K@U{~76xKthTk|f){r@(_FZF!X%eTK+k>9g)BJN~e)b7w(`h-{qqk3~0Mw23kW?7CakpM@20yy&E7LGig{}ip$
znzDg9`>N^J((AcX%;O`hj`6Jn&jPfl%2cf$g&UA_w$ZsE&=j^14rvH30hH)MB&?%Lzcmxy-O
zXoBWP=Kk&}#q*siU2>`n2(AUE;bQ2p?|k)Y^}KH|pZ#09uypYs8I9JP%+&+kM{B6j!ZW5qY}oP*W?%Yep>!j=
zfJAkRXS{>#i!^try>$)?E&h=%P!UoLAsg#az&Af-I!p
z;9ZYEUaL9qEnmxa$nblv~i>mavZf&Ulg^sAD*g85Y
zk+Gt`A=loX)GE(!#;KO4<^CH2m4qcsNUfgGVb^sgs+}PYu*t-Tu@7pLv@7MMjvd5+R-mIVQDJ*20wT?00M`vY(Y87!7(938%%V)e+xH%$lKg&QyvAMcR3tbFe#RlP
zifa>jIZ~dQV*Y_xb@Ake@I8VOT?~ca5J#3U%7k|EnLj`2s`ssZHm^hPMNo?G7IIU8ZeUh|lQOZ!TaH)QjBHmw4i`6EJBcY;PhmFJyy+~FfPsnYvwKZ{`R
zx^f}x3opODO1V?H8@saf6y33eeY}$LiuCx+#_&u0@>KN6;{Ou|e*VAtfK(L$x_Bk%
z+YViw__qef^72}*GA|R_xqfJu>O}OlFWsSf{i|^edkWT+EWHrwr%uT8XNRde+v)7`
zQ-!(exUXs>j)_D5mos7icZOO2zdV+ThDo5o4r53!Ncui51Q}6F>eS!Ub-^#5Je~!g
zjzPrjt|#~K_8KcpP4+~y2kE$Rw_tjTY@G1&$;~Pm%q#TGos}GxM^$kTRC9^
z#Jz|GZ84iSx}Bs6VdBM*
zZ}DkX$L~B`sc;zqK^w2%kTTPi3iyonZwRnF6b^Rg0HOtE&7_B
zs0t#4Z10oAbsShqf}bgCK)*o
zt}FRayi4$x|J<7jro$aI+&Z^%0*|$%q6Mmhd6C~!$s1#vH}uCkA}ao@eEimqd5k2eN;0zTx!;KvyjXt{=ER3ApR&%uBZCsZXyf$XGda+;p^YQq_C_~5)}U%
zLd5u(g!iNi?|5gU$7TGTp&iy!78^+0tl(DJ@wsmzt3LxVDUbwLZ025oBs2%RKlkEZwMJ&h4u93KKsu`?=-a}Alb`8;TyK@f(PeU
z?!ef8H_qZDuSrCCGvLtD7$y!weFovjO6}^|bz-v_{_DekHes`NX`=#ULCPEk|HZQV
z?plv{H10K6sXun#ATH&|-;3;T|1@Pc5w`fdWQ6$Lyw)8T2@_Yiy)W_u(Hpt+VQe1o
z(mpcr>lUk5FwYrE&iJ2A)fnJpdmkLSvV(t(u780G7_>isu1#p&
z_uxmYXfude$EOZm5#$aT{Dz#HUj;X2Wj?
zoq@*sYlt3ovlRO^mvEyAOZP%=knfYGhpV;pY
z|Bx!z8ULT*K3{YtMu4S5Bq%;(WqZ$&VBUxLB=qW`S-^BIpQ(}h{NnT8xegH#}gD65s?(1=FJBP0r3S8gERyskO=g|a@Cbo{o;-l#8s
zC((S9vJlmXwJ1^z&{(II^KgDca<=Qt?7Mt$@C|Z_@*i>8PZiNCM}W!pmyY8@fcS$Z
z1J_U{0eiUIK@+v|E`=4bwW1x}8sAg?2>Q7N!1zf)BIGf9`#eXD;%Xlj47Z;2I?
z%x?ZGda3?Q+ETux1S2i`$$LCr-gEE6gpfggy@MY&TS~t-SOtFL?~FMTH;k(b_~v5dm~VeJ2CH-QuJ^-jAmB}i
zz&8F7VL*xh(8{)RmK#<`hBK&12S_8YS12pUt+~K&GJgeYZRyOiSVO&QG5DBa5F9s7
zGvC`Z5O<#lSP+bZ$3ByN1Rmu0tQvG+;Ffbtsqv`_NxZAbU;lZ$-RA2QmKzP|meSlw
zg6P2q$;sh8S-Dx+S?yD%BXG-JFdh%*MiGLt{z;QAV{N4?tZ-#tnB*XG9?1HE2F9k#
zmOzyb!l2pCka?pNf3PDr0V$bFTUx{~GDO%KB|-$l=%EeA(td3-#85*UxeXP}I^@{T${XP}hCw
z|4d)}%lupGi~$U3$;EGR`C8yyP_JtKJ{l$lz==v`eV$>)#BFd7Kz`j-?w`2
zfZqgc5{U$5P_uTCHQdL4Rc|z1l0`@6oKBXl6OV0A3n%Ek{0kcPHUAF=uw24NDnsMI
zW>ugvcNk4HLf8IsfVVV<_(#o`jE_&Uu{sb5M4*wGlrg1RWctrk;5U~+@(N&((jp!S
z%6CKi6yGxG+_HdyVSnP+O?{u{Lf++!l%ql1E#56Xh0<@JCBQiB-fP+Cdump?k)K)M
zhgo|oX7_)lRa&PPiNH$v@?~T@3MyRMxV5QH-y(mm3$Tc|UEhFhy1=)L;06{xlkkUNrFC5^?V548vHN>vGLw?9z^V`b!8N0X
zQ^)@*%zwt={#?z35=L5o(k`cc9Be7zI}|mg29GUe{1L|z+@su`g7VsA@k>0OE_iwu
zV-v{NiQ!K8Of5p+ms@pCfuVtUb|z{QQ>#42z-te^piKBPYza
zY=8XqI}Z(qjnPmILtj4c~ODucGlk;%e7$&jN*1(G}#5%5`{=n@x@N&C@%g$z^Ub;RMjxqI+9oh
z4rAj0V4^{WY>Q0p7x3DrDf97gszxz!@vZwRCD@IrVb
zQs>qW%_^B#5$%
z98y@nG{+)&K#M2+ff-9#Z`X?KC1c+goTON?L$CQid%`9GtQ+uzt}Hm=b5TaJwIBT{Aj43Q0$G4&%PX2$QK9
zd?`!qdMfg{-J#-QF~f_w*RmjP^6>7|)3Q@fS{N_LO9ifH7qny_+{k+}i
zQGrHr+;CbSF0?52AP;5Yla`1MuDL%gd_j#zd&vLmh`PzhwJ^va*sWfaMohm
z2+s!4ckYi3#E);HnTqBxsEq$we`IwTIw;CSvKuM=d4b;qnFKVFahp{w+%ou+R<)sY
zzzvOL?t^E2w*I6Z9xV=1CqxjIvXp%)JpcxAO|ifnkY}-bva;_V@_L25i6wZ#aQPgx
zwJA*ot7Awh6mW#m2bL#$$2jcZ!ovZ;KhxHv3Lr4ct9c50&-dJlpu@TKmi{<(v;hKb
zv=C{$_xuiBVb&(17;Za1Z@nWmG~Y*yOkbxOq>lOzEVA-})k(P#qE#RdtI=~?EPb(l
zrA<2J9VaZm*%|Ol3_H?u9(WO|UT0Nd_(g7@={nnvjt^g)@6XVS5|2-RBQ1&?Sj_i_
z{1T-#J3_$wFrXAD>3jbpg=krQVj*9wKNpj2DVAuR&tYftC}?@W2!WJl@jCs=?lAfo
zvt67&?ulg4Dm$vOnV}h10T}a4>-aLg@qiu=0h@Vn8ToK2KW|gNlKMk2>)ktl9D{>y
z;gAUai9Vs+=KFHYWpUjfZB$tuH%_9ZyPG_b+o%}Y#o0t(z(;CuW_Gz3lxv`+{Vj6i
zIyt5{GNe}u=97Z~>wWgVt{ZH`)5@*Zh%irf)mf3c`wu&9>rEVQ=P>;15
zA@jg=C_AcZzq`LxUfIu%v<%`>KVe@AO%qR7e6UMU0zPpzQ>rz1=j3)zU&DE81=m;|
z8n4Wk63k8QW10#=0`h$xvnD3!H)e=!%92{-npvaBu3=lrxD&1hoD#_7CRWErRX@K`
zZk$;0{fzt9(I0*jW}QVD^JG#XUw9x;VY$mnqZzI}r3sQ0*#~nZLRg{wZHkXJ2OS8E
zt_7OcXq#3yw%hY?AaQAw(2%k;YigPDfmQmQZ|o|5Riecb-O+w(Y+7F+XGWs~`>^8Vv0$06@8o4n*KBK(fqfWYl$~CnuP<7_Bq>VGbn^q#
zu%kMQVBPru$>DQ7;EYBUvM;JTo9~B)g!Y`7HEezA0iYMgPAt`QT|BYAv0VlwPf#(1
z2>ns`&}9Ph6To`?vmvXyrE{noIId)+#7VdkjX2=Rz=x
z{W0%Fvw*{RN=VQ-iB5ssNGq8)
zf#-S(z4;-h#lef<`Q!QY#YV4FRhGU^VX8X76L<|g@`R+#EY3$jYW!#
zK6pGwT*|iZ%$MF^b2YPjs6&DTwKUpN(WIN?R89@v6(C%+baqyy@EPggd_F=WW`l}r
zM0QGdCkugpmgzbZyzkMXC@FdCpWJlu0Ae`yUOG4Y`4qCvw|hN@>e1Ql|E7N2q4>
zZE)&aku_GDTY1;l59-O$u@g&6`4v{!h1js=8gkIF81T9^lBM0ARZ?-Mp2!1mkjYf)
z!_@W;oz;0$TRivRgG3uuB#)KwvEcp0CFUm?KMk4;T6?8XCUBYKIiv7~pGN50Hpzwe
z=itJFXPE}GG!F2J(Q38GO6HHL!4^293~7`qsD)(or$(CusEDXX-qv%K
zW1evoOx^*E8{*Re{+wE&n@bh0`4U*&RcR{PCyR-te!k|9pzQ5s&(Jpm)W&%1ef
z5F=|QqcT2zOT5jwVTB|{Bl;HV$BP^T9dgW1iW4)DuMH(&MAYmjMRhd-BGPkO*i&~0_H20qrKL%Blk
ze%NmGY#+dN+YNI4iA$ffcm)|1vo;yKMs%dZSBg%%kU8itt+Qrk3cvI#6(DyO>j1ANG}!q7f8GD4`OMUm7SWY=v#1
z`9~KGbhwqW3}VMalKz7P33*QTKUL-V4(_aZ>(=2ln|R((X$o(W^{3ku|5QSr1-FMY$uu_YjA^ne6Hi<$NsX>UBv`!v>?{8|K0V5#lY-&vFS4$Arm8qJ
zlEc7hlW+Wsi(#}_eaL7g1g4^gtO*`?r&{3*JmKBR-e~bhZx?iNeX(D=t-TLaGW*)B
z1zS0@`ISen8AU=UO~MZSlGu>akvj~T^=Db`n>kvO$qboL&5Pz`NrvXyoT$p&!1>{&
zLOZkB6ecy(ppyY8;GE8L?ymg+t?J^h+?5$duTnyDU2It8@pff*8Fqav>3VcL=<;D$Y
z%7!sGo;41rQ`OgQQ8ee8osAW>p##kfJ9j8Iv@n-CqpI{V3^_@whUgGo7}ikyqz90J
zGeWU}1MvQA$16g~#SlUyGdB*f0*i`|HnA3*N>zq#InCA|4GBmWD$waJi-p_O`T#P3
z5*o0_(VA+?bfvQ6+J(c>k=UGbpQCM>U6!(~8iMrf2f+IYR;O?s@neRhqI^sgwm(_O
z{zc#b4Vj}^B41&{$Z9$|u>u3`8ld&x(0fBc?B?WM^^^g575jm~yopVa+4Ks8X!Ir>PaK0iwN6htx5N
z{(8Z{@fs+wJH?Ee}CeCU7XG!!kL8X(3HeTVvZjXwgeq
zD2nSwb`?+u12UwgclY$xdgReLXpL^Qq3EzV7Kl`^uBN}M@TqhIwJs9S1*QQkgNxG4
zK6Y!jG_XC8mVgQj`z+pQ{bM!_{p`C$tj96q>$?Mu1s=^IR+(2s?!F+kR;~8)&qcOtp
zSg?dP0xZN^PK;u4PRpkukjyySp*Ew6V3J0n;$YZ{8&O&u9hwD867FK*H||EOVc7N8
zxHh!#;7G{#50sC9_YP0T
zTRb>PyW64%glc+cgDb0Dm5Ze2;K6w*kGT)1rj|i->pe33nZmOmnCN&vL3@sx8MPXP
zb@cBx)$>@=e{d-G$8$vywOOW#AJzf}qourm*rb&!ZWBH3mVM=GSOHmSof{M_Ek;<@
z6h0m+3*O*2E{n2o@@Ew&xNd>4_$t?|tTTbBS_ovBau-L`p2;V~
zb6sV(=|Zsb>$Yhr@3JF}z4^U6H9sbc+2Ntdq9lf;oX41qvTN4uGyMxUoG!fr$;
z&hgM^sRH)>Zqt53t&6h=($Td;EslFqSx$t|=ag2sAkfORzP~igG{bjkJVd}mczbyXN&!1@x*=-5&Ve#3tWXSW$F88!y
zfN1x&$RH2jdVhh$FtR+Z;m;>}!%vzHXm7$F7-ucPpMAF2ctuET(BA~Jfb1$f8klV7
zPl0b@jH}}$JC|pz{2bhHZ8Wl^)|}-{LZ7;nK|bGtx|L?K=whC^_V}g>%Gy}5WAHIU
zc5M?~x3a}lg?t_)-5L?zBYt<7GMh>9||GY6B@2M$?jA9Q1Ruo32=V
zU%XJd0Av|{Uv>Ocq^`ZeN{?YTrYE1!%*YmJfXJPc4M~z4q^P`};D-qPhSxtoNAYzu
zsMQ89R4(u{2d$puM&11L^#@IF3XRhxJ2_rS14ZdeHfH>0Fg{#t{S~7hXKqfW3pk^w
zFN3@cgE0d_>Lk)q!I+UV`w1rE;C_i7S&UT8e(AcFmhr`*g0~0b=G{HGb}5ui7Bbx>N_v#t9{V+P*tdyX@&33ohX+y0EXf
z%rvbsjSyD_m7E{op!cT{HaD64JX$i$<1PK)a0$$zhqde;9T3vTI6;~7s9dF75mRRb
zR>w|<5N^ZIj}>K{9nf1YxtyNCLxxjkJoO*IK}v9-0N9N{t43JSM;j58bts6V1P&j}
z&{m2w&<7D~tJt%-xqfK0E$F6k>@p4Xc20&}td@AWg-+Mp=3b2&@H9Cq*W-ptA#M
zy_~Q-6Uj_`Pha~DL7p+y))U2CH2DZN&eVO-#jeairmRuL
zeqcM&eb4W>8rU^E!0xS0O9pm-mWHyQW7`sR;EuiC*H2DqiTImn>w{p?{q2Tst%c*u
z@Dv6rclpKbP$g)8N`y_@xad2knK>Dgb-;{~Y#XaZCsaPxqA<(^m6~*I2(fTVZmulR
zf`>0?(l#xwmD!)U0rm7#S}0JMo7R#noJ*TEPjyI!-;g+U_uV)b*-zN7AS8=^1Oh1R
z`n?dA=g-X&3k;-Q9M?WS-0~2l6-2H7caPi^xcU$HRE(M9l
zhOXpGhs>JkESZE+W=#zbl|Q97%ihV5xam(3Z$$Hl(aRLyqa8cdqA^hjrh(R6^0y4U
zSRC_gM%O^HZcU45lA(w*a{L2&4`K14j|daOsm9c>%Suv
z>M9{Nv5rUWuCxP~r92#s)i6+!!LpZk`2j!p#$E$c8MFXh=8fG77yz8jJGhMjFuzy|
zZ6Oas0Iq?+SAJN1!GOS^7TL`aCDLVuL!!_8XX7HjW!|mJ&BUtvd
zQt{SCW?v+LcvMAyd~|4sZeXH;NMH=t3bIIE|CaKu(eCmHs_WV(sQGS{KUC|zwJ`@x{Gm#a_ixGjkA%o^$eFL$0;G4V;bsp!67Z8QcL
zfq_E3lycOsf);H!qgitz8cESigMyST@?K#V8H;gNqft}E;>fh&ME-|1BmTqU@sm6;
zto#97bA_gC^8nkPk8eCkb{U{F{Yo_%zep9ezD-r-`WLkGVmMT_mR13b55J7;PY#~N
zU&9G9$riUnAASFD+(ZWiDu}2X|0t+PZ>rpq1#~{8_)h;T@csf!4U>a&19*Nz;o|e0fZnlzT`+i8v;sJ``4*&tx-L0&4g{&IBhqh;=J)k|*Us286dRILxLFEH3
z5=nHFqSDQeJ@PnsdnPiJ!Z6Wc_uzN4xRp=aglV~*n!Pj*oBC4Mbh$v1++TJ)ug7_P
z6{v785l&DclUN}hkEcstD?w=TEh`@pdCWvHc+(7EB=Mh*^{%}%D{8ZYbcY9*h)44<
zI4XB}K#=EGiP5+SVRX##jpy}8{gO&0z|xDmu9BNEAz|&%So9^4uly;E1pj%K=2W+u
zdFwS51IgK;^z+xw+QOo+Y!0;oYY9}5QXrdJ4s0%gWj_7kO;y4+zKv^yJzr6GbkC=91t
zAlhGOY$In4oi{f;vAU-WAIIknR@#W#R1ydP~bKbtBR}bIga|-B6MF_8ayMzDo
zr6V)#`zoi)<(BvZlj-8`&l>gF9WgodHPysV*Wt+-Mf$)C)}AENl~?fBst^@2V%GD7
zLPm>ZZdSdpGh9NeFs=X4SAt+?*Xl_VhC|}#rhron$?^*>ix`vJZmtF}iTSSFy$sjY
z-k4NV!_h~R?y=+1D(vZ<&zr8#;oMR+i;hb*fUx5n
zrKI_H?9pCjOdlc7MYuc}%r*-7xj*LTksjbyN?zQ5E?J8&tuHxI(6?^zju8*#6O(xf
zQ<*o|Au1z$R;1#3k7W8kdO-?-7kg(^ugq*aEz)rPvEU7Fw4Z>hFz)g|^T}HWaOwfT
zRLR*&-zabuS~QgQNGaP!@{Ye2xIE7^A8ua%q$?-n%8>D=X7l%8Z2L}x3`XN+!|fm7
zfiBqY$~Q^t5pvT!R>_xuiyH))DMJb5hk-)5@$I74Rp2o3Nfv!<1`Qh}SHP{6wK>Yp
z5+K2PTX0A2X1-E6FaXg)_FXV}dE#ObuJVVb9E35vTqq!*4`dvazRLFHrm8WG3{;DG
zvj@A?L**Apr_8#eYKsWXck_$u?}h~*LyoeCpOUrav8Ywc0l?eRtX&C9V;Z?ac8Q3BFbM-RhaFxZQIjLJ
zH5DOsvW-AiYnSJZcw$w6J=%Ue+8U6N;Q1BDv2zci)8O~zF}6Yb#AU%VzkfO&26CiR
zsbaW%O=zq(NTc=!@s4AO7EZ~8umS)P`tzY7sP(DL9uW$R^DSkT-5V1}&B5tcI`K2M
zl8>4Le4->KN`icInN4$_3{Tt{v_A`0VZZ4qWIjnVpY12)Xmo_rL}EQ)71qmjj0#8l
zXtQ={w?fEhtZYJdh+iW}u4uquWm+{KlB0rm@pYU!ErMDXuWebLH!%t9z-IC-{)klB
zGp(9IXbk5D<$CD(n;CY8F6S%3h#20tHLykII?Y6F4QmlDO~-L)*td@oRJ!ztzdWZ!
zK_DdF9sVI(OLEfYP8MAhs<%WLC{z>?oa5Ayb_r~%AQKknStre4Jh!`KALQPGmK{zN
z7f}gN_0zTA7a(WYMFA$mmYNv`lP@~~O-|&g2r`!ARgWUxhrc8K0)ja=Yhn6fxKPwv
z5I5;gsFSbKl7)a!@8z=7#b8zmXUuhZdPG|OKr@!E@hIPY%FVf2I>HTt$$M1qa%Q>
z2u2uh*Y_o10{D)ri`F=LPQ1+Mf8YVb2N&4>9~@nKAk*vrACpm{q7${DI7Fv1R&8uX
z7k8UWbvlw1Czoz!h-r*fPR^QZCCxT5qjTgqDY^(1qfWLqp$IiKvm|YES+no&@%yho
z#P+#7&+GlVJ;emyP=DK?EHr$E>Ff%#6SIj3@0w<4?d`CLnw9bJI6hb%NQ%G`KQu!Hfo_Bx5hmw6??`2!=C%%$;}J&a|f3hn3%m_xph0X$^3k6KKKHO%K6Dy
z;q>_uAF|-=r(hZG8<1HFNfLQj8RUmxfuiO`nW?MCXEC#tM2n$)ktGP=JAGGbLYfBh
z2t&3IrZ(u~)keDIObu(qYqH94PnudZQi~V^crsCaJ2RJ(Z#*QSxl_Ui?jZXLWWgau
zf>Y=Cxq8Aj)Xib@zDTK-N&!>N62>eo5)hT;LCUbQAyJ5%Z~-eZvchJoC->oeQP296
z^2NkLnjS|s|dP}7U;vPRLc)cA84+X_BmNX9iH)c>Af1wjgyZv8=rF_0GI(m+K-vZFH^*DdxNSs^JtKV;1A-^|v;3EeD&m27T
zCa;Ekh`sTC+#kU?7j0Q)^%1Wflwa<$e}!6)sEPs~)Q0-^zy|X7wA&dlV%}AB{OmvB
z1#6?My?a5%uDS86iyFP&>RRf8j}J-QG*$E4tq@Z(lPXtwl~mcO5N?V?#^)9}8-MNI
zhc%b98Z>j+Kj+xzl)`u6JO=D)(nA~KPPCloM`wON8KC&>@cW?WMz)Mtc>nt=)x5PK
ztNZAR;soO}F|R%xao_mw=A2Uy(x8;ne+{2E`>)Br%nSi9b(WGm^oeJ<+Y9~G)cO1A
z9yA=TH%#7xI$0-^oX6aI7hv1#x-uqe-f(uuRy{ko)bpw$$O{9labhC6yeu*BeALg?
z=?9*#pDi|8_QKS~TR`c#KcL={`P#m+RNr`PKotIN=#>@o(1GT20*XwHH;g#aHD%%L
zkI1{HAH@66u;JavmOUQHTod;5!Yz%LK4ZR#{yEl1qmZJu|DEph!|%HA{%@uPqo5*M
zrlbkK?udMD(-ztbX!JFCPEl`s(Du5i$)^qi70CSiY-DmT
zc>!>exSIu(t`kL?WD}o@O|AilLZG&7F@@eRAHG+OD0ZcSNo*MtFSLk_CHoBu!aKt#
zCa(T?8S2j>mM*&jC}gizsPR`I9)OrZ^8~P*{*rs!_sokYZQI*b0u^e^!nE{h7I=C;
zNm=1umR9mS|
zSoYj`CmX*$h^42|rF{O8$8>7VQ)v?)F4w?b(Ir}^2TS{#GLY?FzQwZ&eqroKnDWt*
z!c~4@v#ogi44l2sfZ+&SAR~-#KVNA%w~~-%dXg-i0m|3){8e&mWO1>Y{n&|uL-^*OT$;I&$UXRaS+G_4IjL_rj^tF
z0|O27VL`{b0bTpbg$^{=h;#!68N7Q@Ra<(jc6eRv5FFJtqdth{gz{kYG-u{>X-eIr
z3257eg6|33Lh>k@Hwm02MvltLohuVa$^P&kRHs4E0WBOUd(f?zA{xZC9aZcbEME1E
zUi8W#GgCPgK*|rwJa_Ah!MKuLQ42Jh@+dkVob|bji=&!u4mzcW0drIJBt?_$octXx
ztal1!>iW7TcRAt!)O5njX82Q<|AUy-JB&PDqHIJ+Ln?7Tj6r8`x(e0P`GmyGhfh6jb&E{_0Whtrxx$&_G-d
zqa{<(QgKNoYK#EGrgDQPpcrtI)M7ea6BY-+Vzrc$7<2^o6I$!!3j9Gs1LfW{H?Iy@
zeNd6#F>Gdv@B{Ja!_2fADP6G8xfz?yi73sV+z3}K-ROW;+#)Vs3KqV?ggvI&{KR#~
z>gja~Nh^fJL~3iN8Nl)aW%KzK#B{8h1y9tbpc9Wu5tq_G?eq*=`DKcHjDWNGVRCe-TFmL>SPZ87>yH!UrdQB
z5#oEVSMU}6G*cp?QJlL$