From 9a4373bf8e9fe7ebae7d378455f319dc4decd3f0 Mon Sep 17 00:00:00 2001 From: bogdankostic Date: Mon, 25 Sep 2023 11:47:21 +0200 Subject: [PATCH] feat: Add `TikaDocumentConverter` (2.0) (#5847) * Add TikaFileToDocument component * Add tests * Add tika service to CI * Add release note * Change name * PR feedback * Fix naming in tests * Fix tika version in CI * Update tests --------- Co-authored-by: ZanSara --- .github/workflows/tests.yml | 9 +- .../components/file_converters/__init__.py | 3 +- .../components/file_converters/tika.py | 85 +++++++++++++++ ...dd-tikaconverter-2.0-ef637f93114e6c96.yaml | 4 + .../test_tika_doc_converter.py | 102 ++++++++++++++++++ test/preview/test_files/docx/sample_docx.docx | Bin 0 -> 13232 bytes test/preview/test_files/pdf/sample_pdf_2.pdf | Bin 0 -> 26093 bytes 7 files changed, 200 insertions(+), 3 deletions(-) create mode 100644 haystack/preview/components/file_converters/tika.py create mode 100644 releasenotes/notes/add-tikaconverter-2.0-ef637f93114e6c96.yaml create mode 100644 test/preview/components/file_converters/test_tika_doc_converter.py create mode 100644 test/preview/test_files/docx/sample_docx.docx create mode 100644 test/preview/test_files/pdf/sample_pdf_2.pdf diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2667cae4d..99bbe037f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -831,8 +831,13 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest] + os: [ubuntu-latest] runs-on: ${{ matrix.os }} + services: + tika: + image: apache/tika:2.9.0.0 + ports: + - 9998:9998 steps: - uses: actions/checkout@v4 @@ -848,7 +853,7 @@ jobs: - name: Install Haystack # FIXME Use haystack-ai dependency list - run: pip install .[dev,inference] langdetect + run: pip install .[dev,inference,file-conversion] langdetect - name: Run tests run: | diff --git a/haystack/preview/components/file_converters/__init__.py b/haystack/preview/components/file_converters/__init__.py index 55ef63c1d..c5ca1f7c2 100644 --- a/haystack/preview/components/file_converters/__init__.py +++ b/haystack/preview/components/file_converters/__init__.py @@ -1,3 +1,4 @@ from haystack.preview.components.file_converters.txt import TextFileToDocument +from haystack.preview.components.file_converters.tika import TikaDocumentConverter -__all__ = ["TextFileToDocument"] +__all__ = ["TextFileToDocument", "TikaDocumentConverter"] diff --git a/haystack/preview/components/file_converters/tika.py b/haystack/preview/components/file_converters/tika.py new file mode 100644 index 000000000..fe1cb31fa --- /dev/null +++ b/haystack/preview/components/file_converters/tika.py @@ -0,0 +1,85 @@ +import logging +from pathlib import Path +from typing import Optional, List, Union, Dict, Any + +from haystack.preview.lazy_imports import LazyImport +from haystack.preview import component, Document, default_to_dict, default_from_dict + + +with LazyImport("Run 'pip install farm-haystack[file-conversion]' or 'pip install tika'") as tika_import: + from tika import parser as tika_parser + +logger = logging.getLogger(__name__) + + +@component +class TikaDocumentConverter: + """ + A component for converting files of different types (pdf, docx, html, etc.) to Documents. + This component uses [Apache Tika](https://tika.apache.org/) for parsing the files and, therefore, + requires a running Tika server. + """ + + def __init__(self, tika_url: str = "http://localhost:9998/tika", id_hash_keys: Optional[List[str]] = None): + """ + Create a TikaDocumentConverter component. + + :param tika_url: URL of the Tika server. Default: `"http://localhost:9998/tika"` + :param id_hash_keys: Generate the Document ID from a custom list of strings that refer to the Document's + attributes. If you want to ensure you don't have duplicate Documents in your DocumentStore but texts are not + unique, you can pass the name of the metadata to use when building the document ID (like + `["text", "category"]`) to this field. In this case, the ID will be generated by using the text and the content of the + `category` field. Default: `None` + """ + tika_import.check() + self.tika_url = tika_url + self.id_hash_keys = id_hash_keys or [] + + @component.output_types(documents=List[Document]) + def run(self, paths: List[Union[str, Path]], id_hash_keys: Optional[List[str]] = None): + """ + Convert files to Documents. + + :param paths: A list of paths to the files to convert. + :param id_hash_keys: Generate the Document ID from a custom list of strings that refer to the Document's + attributes. If you want to ensure you don't have duplicate Documents in your DocumentStore but texts are not + unique, you can pass the name of the metadata to use when building the document ID (like + `["text", "category"]`) to this field. In this case, the ID will be generated by using the text and the + content of the `category` field. + If not set, the id_hash_keys passed to the constructor will be used. + Default: `None` + + """ + id_hash_keys = id_hash_keys or self.id_hash_keys + + documents = [] + for path in paths: + path = Path(path) + try: + parsed_file = tika_parser.from_file(path.as_posix(), self.tika_url) + extracted_text = parsed_file["content"] + if not extracted_text: + logger.warning("Skipping file at '%s' as Tika was not able to extract any content.", str(path)) + continue + if id_hash_keys: + document = Document(text=extracted_text, id_hash_keys=id_hash_keys) + else: + document = Document(text=extracted_text) + documents.append(document) + except Exception as e: + logger.error("Could not convert file at '%s' to Document. Error: %s", str(path), e) + + return {"documents": documents} + + def to_dict(self) -> Dict[str, Any]: + """ + Serialize this component to a dictionary. + """ + return default_to_dict(self, tika_url=self.tika_url, id_hash_keys=self.id_hash_keys) + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "TikaDocumentConverter": + """ + Deserialize this component from a dictionary. + """ + return default_from_dict(cls, data) diff --git a/releasenotes/notes/add-tikaconverter-2.0-ef637f93114e6c96.yaml b/releasenotes/notes/add-tikaconverter-2.0-ef637f93114e6c96.yaml new file mode 100644 index 000000000..e1ec43515 --- /dev/null +++ b/releasenotes/notes/add-tikaconverter-2.0-ef637f93114e6c96.yaml @@ -0,0 +1,4 @@ +--- +preview: + - | + Add TikaDocumentConverter component to convert files of different types to Documents. diff --git a/test/preview/components/file_converters/test_tika_doc_converter.py b/test/preview/components/file_converters/test_tika_doc_converter.py new file mode 100644 index 000000000..b1e176bd0 --- /dev/null +++ b/test/preview/components/file_converters/test_tika_doc_converter.py @@ -0,0 +1,102 @@ +from unittest.mock import patch + +import pytest + +from haystack.preview.components.file_converters.tika import TikaDocumentConverter + + +class TestTikaDocumentConverter: + @pytest.mark.unit + def test_to_dict(self): + component = TikaDocumentConverter() + data = component.to_dict() + assert data == { + "type": "TikaDocumentConverter", + "init_parameters": {"tika_url": "http://localhost:9998/tika", "id_hash_keys": []}, + } + + @pytest.mark.unit + def test_to_dict_with_custom_init_parameters(self): + component = TikaDocumentConverter(tika_url="http://localhost:1234/tika", id_hash_keys=["text", "category"]) + data = component.to_dict() + assert data == { + "type": "TikaDocumentConverter", + "init_parameters": {"tika_url": "http://localhost:1234/tika", "id_hash_keys": ["text", "category"]}, + } + + @pytest.mark.unit + def test_from_dict(self): + data = { + "type": "TikaDocumentConverter", + "init_parameters": {"tika_url": "http://localhost:9998/tika", "id_hash_keys": ["text", "category"]}, + } + component = TikaDocumentConverter.from_dict(data) + assert component.tika_url == "http://localhost:9998/tika" + assert component.id_hash_keys == ["text", "category"] + + @pytest.mark.unit + def test_run(self): + component = TikaDocumentConverter() + with patch("haystack.preview.components.file_converters.tika.tika_parser.from_file") as mock_tika_parser: + mock_tika_parser.return_value = {"content": "Content of mock_file.pdf"} + documents = component.run(paths=["mock_file.pdf"])["documents"] + + assert len(documents) == 1 + assert documents[0].text == "Content of mock_file.pdf" + + @pytest.mark.unit + def test_run_logs_warning_if_content_empty(self, caplog): + component = TikaDocumentConverter() + with patch("haystack.preview.components.file_converters.tika.tika_parser.from_file") as mock_tika_parser: + mock_tika_parser.return_value = {"content": ""} + with caplog.at_level("WARNING"): + component.run(paths=["mock_file.pdf"]) + assert "Skipping file at 'mock_file.pdf' as Tika was not able to extract any content." in caplog.text + + @pytest.mark.unit + def test_run_logs_error(self, caplog): + component = TikaDocumentConverter() + with patch("haystack.preview.components.file_converters.tika.tika_parser.from_file") as mock_tika_parser: + mock_tika_parser.side_effect = Exception("Some error") + with caplog.at_level("ERROR"): + component.run(paths=["mock_file.pdf"]) + assert "Could not convert file at 'mock_file.pdf' to Document. Error: Some error" in caplog.text + + @pytest.mark.integration + def test_run_with_txt_files(self, preview_samples_path): + component = TikaDocumentConverter() + output = component.run( + paths=[preview_samples_path / "txt" / "doc_1.txt", preview_samples_path / "txt" / "doc_2.txt"] + ) + documents = output["documents"] + assert len(documents) == 2 + assert "Some text for testing.\nTwo lines in here." in documents[0].text + assert "This is a test line.\n123 456 789\n987 654 321" in documents[1].text + + @pytest.mark.integration + def test_run_with_pdf_file(self, preview_samples_path): + component = TikaDocumentConverter() + output = component.run( + paths=[preview_samples_path / "pdf" / "sample_pdf_1.pdf", preview_samples_path / "pdf" / "sample_pdf_2.pdf"] + ) + documents = output["documents"] + assert len(documents) == 2 + assert "A sample PDF file" in documents[0].text + assert "Page 2 of Sample PDF" in documents[0].text + assert "Page 4 of Sample PDF" in documents[0].text + assert "First Page" in documents[1].text + assert ( + "Wiki engines usually allow content to be written using a simplified markup language" in documents[1].text + ) + assert "This section needs additional citations for verification." in documents[1].text + assert "This would make it easier for other users to find the article." in documents[1].text + + @pytest.mark.integration + def test_run_with_docx_file(self, preview_samples_path): + component = TikaDocumentConverter() + output = component.run(paths=[preview_samples_path / "docx" / "sample_docx.docx"]) + documents = output["documents"] + assert len(documents) == 1 + assert "Sample Docx File" in documents[0].text + assert "Now we are in Page 2" in documents[0].text + assert "Page 3 was empty this is page 4" in documents[0].text diff --git a/test/preview/test_files/docx/sample_docx.docx b/test/preview/test_files/docx/sample_docx.docx new file mode 100644 index 0000000000000000000000000000000000000000..3a740ac96876f71e624fbc80fd2cb2408024e34f GIT binary patch literal 13232 zcmeHuRdgK5vTaMUB#Uf;1r{?igT>5XF*9R}*CR z_kB;T)zPc6YDaZsM`rBIh?0|pc=G`O1$YYp00;oJcOmd9Qo!}FzZN^5m zIKM;VZTOLNGmd_Ze~|TyC39Q*M=4e?dZPN?n9(+e;Ir2E1$&B+z zl#2o```P1ML||g@CdsKpK%Y3^IWWsp4fGKfIO`RMReC6Dx;9tkQOhi2Z`>&5lhi#e z(FoCjMUSndD4J9vUB_s*6 z>63`XDonH~M7HbQ_};=&T4>&w-3^#o?ly1VTBT+AnArw^;c(hpD^+aHWE}d|DvJ+i zU~l=^>Hz`(`1un8Aoq_Ui5rdCcn0}V+Xppm6L9qn)OzeDl=@c8?i>18q9 z=DqareCK{Iev|F;OWhc`GIRzL%NUE$Z#BfFP?i_W=U-nwEi8a*9qNe=Pff>8xY?(P zIBvvgU0^0CA%wQW%sp!NYCN~O0OAAM@)=y_Y`5T3_wI~ehKR%|hWx_RP(#Ntp`ssx zlSaExb}5DKb_?Om2r0&A42WxUGwhV+?~}ZkvXU8Q=gh@4+@VXjg*yUqTE;$l!5Z^c zCo<7QMfR-KXnj*Wifv;2YEE%WjcG|wN0Jt;Uj~bL(KdPHmfKnQJtTk{CL1P%?%Bgw z6IuOrKXmUSFY7shTAj|O4OW^K|1~Hu{rmW+{_>t11{wfB#s&b8K%L@j12m*Fv@vkB z0>!Q00@jGyvh}hEq9@&ix9UYcT|{$RtiXwHOFhL>PjLF4t8fMJ9C6};mV|O=N3EoM z4)Jn!@*6STq#f;dCSz&IIGpF+o|M44Y)!+mHij|_OMG=w>d~Ruua}2FHTbRM=#Sdq zix6MB1eaUDI&!iEh4S5jvHlxG_F!J&RIwC)NBfz3P{k+&nh`OMB!XaTDj zEOesJFX!)`;}3IP@0TAHL~{;-Sc_FRCkifsLfY6_O6pEHwc$ z13Q5O@Md$vd4(aJF^udaWy6vkCu7pcpr8iC;)K+@%~jhQZ~}Zg5PB?55nFS%9aHT3Lq!&Bb2!F7#ZnMfdBvMaAl6_N#f2%;?3B zk4_NnZ3a*o-(9d5YUxrp`#ZL=i6&E>Ua6&e_j*%uWbaL--!M`VzLAdh&kjY7;-Zol zZzR`Ci#LqDRL}B%OIb02QwS(h-=L7%rEuGDlqcZl15PmJhE6j}=7|s?!zjzmN=Z^q zB+OQ2d{(N`V?`;_&DYeq56wk|YB+xXb=#RVs(b#;Rr&_30eoKka5#Ebm%mlbO*$&e zikaJu1hh^-L!<(15T{&;sg6l03o3Fq1=O(aT&^I=H(c+N>@(u78rm3X~?5gd*s070bN0kqo}xqlH{~+=iu_~5o#0&d=x0%4(YF|&qufMLbSJ;UP#8{ z#}SYqwW4;AxQ!`H!@A%#a?~|HE?MDUVRpZ(BZ=x6@}xv6ee~h0zh64whit$ZkFeam zc^@D1RbNcq(1?_Hp1Vm#p-3tf-T|6W(^wWiyps%1bsAT&E&<57zH>#$DO!#yCDtsmPQl0-CRyS|2}h1X zchT8Q8M5Hm%Y`eg@}{F&WSpB`xUH|MG#(+wo|=Qg9i7Z%v@2=2vqOIC><(e4SL5`M z?d-|}Kox#;0rSCk)834C3)X$sWr_SsAFhb}w2nUE?i&7dNK30~qn%kdTzYikqoY_n zHz>*uD)devvskBg_^MJ+vW%(;mRmjByLq}gyT@v1#Kh_yrt!>9eaeO^pPZoYcX89g_XL| z)<-3CrjF!O8EolZU8lK#C%X@ewZ)W8ewHep_hO2~U~q~V3LV*fr_erukGYsp8Lv%x z8=OoYb8c=O=+Z>1QSZXOc;GI4js-inV9bl zsBQxHW-X#;s_nDd(Sy&`R$e#ucdJa&?Td;w|N{p7AYm~`@~!n_#I?z+W) z7hL3CNw|EnmqJo8vY=}7`hqqVM)mN)Eq1Y`$R?H7H@W}kNb3vUe~O!PhPCoHpm@mu zVx8E4w_v}=&3}>0zemqMi6$6m%?FCV|F@5_m|+mn0SX2$ttw z0K&MuMbt2(H=CW6V&caWx-A#LiX{8-FRaVs-J#CmGwy|113<)s@Jjc2N|$2X$}@gM z3l)oF97>F6T@7t*%Zv$g8OfPjAE6v}g$ahB`Zz;QY-S^#EJ?brk$!v3eacW)+Kyqs z_EWS3CI(J+LJrI!?n z+80)q44uFWGhi%~qb}*n{a)Oqmr>fORg*DI``UolcV3lb-=sE`eHlU3-iQ*Jc|cAe z%j5P4+5-4@w$*TBk~9Sc01V;-0H`2m_}yz9OpUCJ=zf8HOVk6QN#}DE4;ww zVYkr(y)u?5u`7#t*0p-S$aV#bg7OTr$8a~AMaAO6k&Ix@n&7Bc!niWMoxsu)ks_)gu0q%&IjxCzPdu8bMd-u7aj^Enj7Vcd zM*GvgA2x`H;J1+)u9wJ{k3bUFB;#i&YG+7}LUMW+C*f?8@4>gJLl}UGA>UD{XdozH z&_7-BgbarycZN-{WT%(Tz{S&bpMNYzOf2QF68dUC%!NK=K|l#-Xb%*8X^hi=4KHM& z>F@2W&)R>~&TXBm1XHx$h8pEJm{2{Q1~6lG%Y3FS7&E51NLauow^DbwPknKl?b^SY zZ@kdb@q}-K%9pl*7Cxsm!q*Zr!LX`ov*v4^WHxmb&m4+ zk6TSDGkM)_U*^4EetZKz2RbAL;YDG0sN2SQci02f#@uOwxEA@Ki41q4{sF6tyU#Sz?l_~Oxc*8xi7eaG zj%R$^n!v$OgKOd9N|(p4iHtR#-yb1`jiL!VfW3Q4-rh2_TKI&PAW-XmAegC}bgEf0 z4VO?k7CTCiD1|Y5m>8;)+VV~JdZ5+a89TR#)zS0AfKbmO-%J7W=iczd3C)~^A&l?L zkt@QCHPI_N7twmQ1l`jF1tTBBs}Wg~u{`u9+M&$)&hEHdf~AQQ4b2752>Y%Ecv7K| zLQRy%h(oRxsPth8Leo;E)Wzwq{fl@Wz(@klIs~gW&l)pNm!kqtz;cZDR-B0GnZ~z$ zWe^)E&aW24sH+b_^N(hIH zhP81>dPFd_IlE2%<{PoA1!SH4^8;Sxoe%8x?0Z?7h3R7Fldf1rPT@hr+75~yD`gh2 zZG)>rbQVu$S!?|!QR<1>l)bWo$Mtz;;r++57>-iIG0Kmv1ENKSHCw{G;Og85lj>VN zg*+$%haH-7ZB!16L>niOLG(pj>ZUJIW@!BO&1pVx98*?7OplP!lV1MDRZ>coh@tsM za_mf`S+-NX%B$wSW82)UGNPrkv-Z-6e2iE@Jr*qQj8ddTu|`k8*3{e20-pGfL%+m$ z;vqMGmqK=g3)wTjpdr2)lL-)B?#aoYL9dz6k*w_6-NC;VTe(*dL9|AkGT1Ft^x9qh zeD4s=FlseVy=*~MQMhg9Cq%mX4D1h$$v;OI8eMFXD~mTm$-JMDaw;L=R-l^)JwmR{+S%I zVA=jEUu?KW{hXrv523+&s?3bt8h>ceod@?-ja=7t;|tD4HRrW=TXHnEcF+YP)Ekoc zr5PIJhDD^!3sn(!5;TI0CvohS8oP~WO5pUBRF2l>!mONJ-V=7dpFK*G$S&@wM2qm9 zr5a>LIha&(YNku)Aj7%m|9seMV1J~Vu* zB(s9sAMe?r+QX)@zj1A~zf|FQCHT8h!SO~7+T8hBY5kV-{X4MXPo^`0r*5tDiJF+2k}o|vlj90AUbDpc^uM-5bj!TyDZ?<^F>(RN@(Zi95gQF7pRmvwi`FH zT(rDTYzR!R>`>X8ZF744k3sO$89g|{8vq~*?r&QT_C^j4X4WS5zXiZL<#iiO4n!~B z!&m6KaZz2L07E4ONCnzr0e8x@v&GgsU(}y5rvVewFXwD`OVnm1p>;!WF8isE#-DJ< zA6B2q#yGih5aRVy^3Y%M1cfLVP*csW?%gKar8xTteUM^(rD7o%2A_}Dz1 zP&IADY2l3?+H1g!l zLbNFq!Iw2*OG(1qRJ~%H-i`7LoA}E?8d79k&Wpeo50cOS`o~9eJx@79{-|;S zX`7vq%x}K(^?B-&o^PH_yA+miS3G?`CC-Y?63uyoO^j90Yx%&vLz!y$ zB7A|@cC|8E*y*6zJau}}ryt>xO>soM`sSG9s{rpl7O+ol)k2@ZDfx1!@LQHVp$6 z))i>iXF-D(HC#^r1XlH#|*YzZ4qVfsc*~ zU=TOupNf3Ef&22_$J$rl4@2ipUo!6r8gca}R7%c(j+dvz)`m}L_~nfQqkAncj49?- zaK3Y4EM8;g{hEv~HJO=^e#X@FAk!#mr`N+iua~Pw=I%JdC^(E*?Y+1Qr`rDG-it#@ zfJI8w3Bvk>;+dvhbd+WXs`Ipe@Uwm``jw#|u)&aFII|G?`T1Hr07;PCqh6ojWuTWZ z+pOZW(6=vUVA^t&RZZF*TflvXaOo}X6m=|g`Zp3f0%|MzS|WgiD!EIa$|II5y;3Bz zM9TQxZ7J~@c!b>-2tZQM4NSj_21CKr)K1mg{$w_6w2#=0V;En;w-Easd29BCaO9CY zI$I>0@rPAo@*=@GMtq=3J8K$`GQB8xU-_Xdzx75s2#2zd&uG{Yl{C(By__@2UQ@ex z$ks$vYb?^i$R=6tUr~=P2%TjU505j7a5`7ahK+ISC19nFt9>K=Jga|~xbn!iH9gqP z-@Yqwn^jLkdZ^X@wZL6PXEftLnitJ{S^+3aCUinG7Pnc)S#{NE*OZ!g?$C>c%((ha zZCBCUj&)sSB8lP<54*E;-Ggo>DVA_wi3n5f)u!q*{gy(GLRA=}%|N@uZ9vdHv-%FO z|4^urgzdGd1t}aZ8B2!j+#waX?Mr$RBa9^d^?iW_+GM6h%Ikn>-RkSJN%^_C;6RvS z2hXSEWAfnIgXzy=Y?K-U?G*Ank0tQ>w}-w~So3>7%k^r9P_*tWBov3>jp>kDp2@%Y z)C`2@o*JXkMQC}$&Q5GvusGbPBj9u@3trEgAMDg)x;?8Sn2oG2B1#fHDZ(PLLKL;K zqp{?0pjBscpwWOn#aSF^Bv~8@e4_T$cHw)fJs-LUT}{ITh!)<{Oslp|M*D-QoBo~J9HJ**Ep9;W*Qpv8UaD< zgLsB$R+cLPHPIIGv~NXGmYJ9*R0D5O$s4~qu>@Rwd%?4|YEw*C+>Vq35(;K8n&bU? zWSDEj85kHyymc0tw%s5)YAjx5SmZQxJ1Oq!jwcv~QA`~y(~*ZMK7&3LC`_gfdU}sN zi(tx>!srsy45+dkSIkC69?!2D!q?2!sH4+ON_CgpPQzEKg)a893|L-gXM}r4BR>zk zs6knU9dO+r4xiJR5Uhjooqa9md^o9Azp_MrTpr_y%<$8|vsV|qs;`(Q01`x|Dcy99QR%%Vt_oz+)}IF^m!X*^dL1PTSC4QBmx}udwc^{!)0*~|e%;w7vZVSl zE@il8IPvusv0G;rsN6g)y0?4>^yqIWyR30bj&F0S6mUB*8tHZCHG8;n6%3#rJ`;`Q zxn|#sWWEyB-0@AUHkNqhYc-78;U!6B8BD5(R}bF&?CCkmXDiK``~HBGl=^nNQPv$s z&u+s8($3ZWg`{a0OUbyz=Mv)6zUvr@&B&3|MyJ=*Zs&``g%n!%VQpb9_qBtYSTAi- zkaz*{rCKdVg-oD*TGnsBwwTZg?z&Xtt?XP5qY3)IMd&9cyib-)~-4UsBDI|4yu81bkF zZuOq)C*9F&&o*2^k?T)s!qaU(-=ewN2L?V~`3F7%{XpM7fv%2S4`Jr;3lCHlATewq zkyJ?X1?(EtM7z*iR%Zl*UdOILuVYK9*W-I~;A3cQZzPyj`3~X2`UeAi&+lnIH*Ax6 z9MY_sh?gfYj}!&Y-+DPlW1NT{BwluW3wIsoaifmvfG2jlu(~l{zu57LX?3lN! z;>2-y)48}xwBBlsT�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 literal 0 HcmV?d00001 diff --git a/test/preview/test_files/pdf/sample_pdf_2.pdf b/test/preview/test_files/pdf/sample_pdf_2.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6384246e891c59abf174e7225ed7f793e814ed69 GIT binary patch literal 26093 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 S4>n+N3&DvwIj^b75&sVaB5i5_ literal 0 HcmV?d00001