package org.jabref.logic.xmp; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintStream; import java.io.StringReader; import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.TimeZone; import javax.xml.transform.TransformerException; import org.jabref.cli.XMPUtilMain; import org.jabref.logic.bibtex.BibEntryWriter; import org.jabref.logic.bibtex.LatexFieldFormatter; import org.jabref.logic.bibtex.LatexFieldFormatterPreferences; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.importer.fileformat.BibtexParser; import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.AuthorList; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibtexEntryTypes; import com.google.common.io.CharStreams; import org.apache.jempbox.xmp.XMPMetadata; import org.apache.jempbox.xmp.XMPSchema; import org.apache.jempbox.xmp.XMPSchemaBasic; import org.apache.jempbox.xmp.XMPSchemaDublinCore; import org.apache.jempbox.xmp.XMPSchemaMediaManagement; import org.apache.pdfbox.exceptions.COSVisitorException; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocumentCatalog; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.common.PDMetadata; import org.apache.pdfbox.util.XMLUtil; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.mockito.Answers; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * Limitations: The test suite only handles UTF8. Not UTF16. */ public class XMPUtilTest { @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); /** * The PDF file that basically all operations are done upon. */ private File pdfFile; private XMPPreferences xmpPreferences; private ImportFormatPreferences importFormatPreferences; /** * Wrap bibtex-data (<bibtex:author>...) into an rdf:Description. * * @param bibtex * @return */ public static String bibtexDescription(String bibtex) { return " <rdf:Description rdf:about='' xmlns:bibtex='http://jabref.sourceforge.net/bibteXMP/'>\n" + bibtex + "\n" + " </rdf:Description>\n"; } /** * Wrap bibtex-descriptions (rdf:Description) into the xpacket header. * * @param bibtexDescriptions * @return */ public static String bibtexXPacket(String bibtexDescriptions) { StringBuilder xmp = new StringBuilder(); xmp.append("<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>\n" + " <x:xmpmeta xmlns:x='adobe:ns:meta/'>\n" + " <rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:iX='http://ns.adobe.com/iX/1.0/' xmlns:rdfs='http://www.w3.org/2000/01/rdf-schema#'>\n") .append(bibtexDescriptions).append(" </rdf:RDF>\n" + " </x:xmpmeta>\n" + "<?xpacket end='r'?>"); return xmp.toString(); } /** * Write a manually constructed xmp-string to file * * @param xmpString * @throws IOException * @throws COSVisitorException */ public void writeManually(File tempFile, String xmpString) throws IOException, COSVisitorException { try (PDDocument document = PDDocument.load(tempFile.getAbsoluteFile())) { if (document.isEncrypted()) { Assert.fail("Cannot add metadata to encrypted document."); } PDDocumentCatalog catalog = document.getDocumentCatalog(); // Convert to UTF8 and make available for metadata. ByteArrayOutputStream bs = new ByteArrayOutputStream(); try (OutputStreamWriter os = new OutputStreamWriter(bs, StandardCharsets.UTF_8)) { os.write(xmpString); } ByteArrayInputStream in = new ByteArrayInputStream(bs.toByteArray()); PDMetadata metadataStream = new PDMetadata(document, in, false); catalog.setMetadata(metadataStream); document.save(tempFile.getAbsolutePath()); } } private static BibEntry bibtexString2BibtexEntry(String s, ImportFormatPreferences importFormatPreferences) throws IOException { ParserResult result = new BibtexParser(importFormatPreferences).parse(new StringReader(s)); Collection<BibEntry> c = result.getDatabase().getEntries(); Assert.assertEquals(1, c.size()); return c.iterator().next(); } private static String bibtexEntry2BibtexString(BibEntry e) throws IOException { StringWriter sw = new StringWriter(); new BibEntryWriter(new LatexFieldFormatter(mock(LatexFieldFormatterPreferences.class, Answers.RETURNS_DEEP_STUBS)), false).write(e, sw, BibDatabaseMode.BIBTEX); return sw.getBuffer().toString(); } private String t1BibtexString() { return "@article{canh05,\n" + " author = {Crowston, K. and Annabi, H. and Howison, J. and Masango, C.},\n" + " title = {Effective work practices for floss development: A model and propositions},\n" + " booktitle = {Hawaii International Conference On System Sciences (HICSS)},\n" + " year = {2005},\n" + " owner = {oezbek},\n" + " timestamp = {2006.05.29},\n" + " url = {http://james.howison.name/publications.html}}\n"; } private BibEntry t1BibtexEntry() throws IOException { return XMPUtilTest.bibtexString2BibtexEntry(t1BibtexString(), importFormatPreferences); } private String t2XMP() { return "<rdf:Description rdf:about='' xmlns:bibtex='http://jabref.sourceforge.net/bibteXMP/' " + "bibtex:title='�pt�mz�t��n' " + "bibtex:bibtexkey='OezbekC06' " + "bibtex:entrytype='INCOLLECTION' " + "bibtex:year='2003' " + "bibtex:booktitle='Proceedings of the of the 25th International Conference on \n Software-Engineering (Portland, Oregon)' " + ">\n" + "<bibtex:pdf>YeKis03 - Towards.pdf</bibtex:pdf>\n" + "</rdf:Description>\n"; } private String t2BibtexString() throws IOException { return XMPUtilTest.bibtexEntry2BibtexString(t2BibtexEntry()); } private BibEntry t2BibtexEntry() { BibEntry e = new BibEntry(BibtexEntryTypes.INCOLLECTION.getName()); e.setField("title", "�pt�mz�t��n"); e.setField("bibtexkey", "OezbekC06"); e.setField("year", "2003"); e.setField("booktitle", "Proceedings of the of the 25th International Conference on Software-Engineering (Portland, Oregon)"); e.setField("pdf", "YeKis03 - Towards.pdf"); return e; } private BibEntry t3BibtexEntry() { BibEntry e = new BibEntry(); e.setType(BibtexEntryTypes.INPROCEEDINGS); e.setField("title", "Hypersonic ultra-sound"); e.setField("bibtexkey", "Clarkson06"); e.setField("author", "Kelly Clarkson and Ozzy Osbourne"); e.setField("journal", "International Journal of High Fidelity"); e.setField("booktitle", "Catch-22"); e.setField("editor", "Huey Duck and Dewey Duck and Louie Duck"); e.setField("pdf", "YeKis03 - Towards.pdf"); e.setField("keywords", "peanut, butter, jelly"); e.setField("year", "1982"); e.setField("month", "#jul#"); e.setField("abstract", "The success of the Linux operating system has demonstrated the viability of an alternative form of software development—open source software—that challenges traditional assumptions about software markets. Understanding what drives open source developers to participate in open source projects is crucial for assessing the impact of open source software. This article identifies two broad types of motivations that account for their participation in open source projects. The first category includes internal factors such as intrinsic motivation and altruism, and the second category focuses on external rewards such as expected future returns and personal needs. This article also reports the results of a survey administered to open source programmers."); return e; } public String t3BibtexString() throws IOException { return XMPUtilTest.bibtexEntry2BibtexString(t3BibtexEntry()); } public String t3XMP() { return XMPUtilTest.bibtexDescription("<bibtex:title>Hypersonic ultra-sound</bibtex:title>\n" + "<bibtex:author><rdf:Seq>\n" + " <rdf:li>Kelly Clarkson</rdf:li>" + " <rdf:li>Ozzy Osbourne</rdf:li>" + "</rdf:Seq></bibtex:author>" + "<bibtex:editor><rdf:Seq>" + " <rdf:li>Huey Duck</rdf:li>" + " <rdf:li>Dewey Duck</rdf:li>" + " <rdf:li>Louie Duck</rdf:li>" + "</rdf:Seq></bibtex:editor>" + "<bibtex:bibtexkey>Clarkson06</bibtex:bibtexkey>" + "<bibtex:journal>International Journal of High Fidelity</bibtex:journal>" + "<bibtex:booktitle>Catch-22</bibtex:booktitle>" + "<bibtex:pdf>YeKis03 - Towards.pdf</bibtex:pdf>" + "<bibtex:keywords>peanut, butter, jelly</bibtex:keywords>" + "<bibtex:entrytype>Inproceedings</bibtex:entrytype>" + "<bibtex:year>1982</bibtex:year>" + "<bibtex:month>#jul#</bibtex:month>" + "<bibtex:abstract>The success of the Linux operating system has demonstrated the viability of an alternative form of software development—open source software—that challenges traditional assumptions about software markets. Understanding what drives open source developers to participate in open source projects is crucial for assessing the impact of open source software. This article identifies two broad types of motivations that account for their participation in open source projects. The first category includes internal factors such as intrinsic motivation and altruism, and the second category focuses on external rewards such as expected future returns and personal needs. This article also reports the results of a survey administered to open source programmers.</bibtex:abstract>"); } /** * Create a temporary PDF-file with a single empty page. */ @Before public void setUp() throws IOException, COSVisitorException { pdfFile = tempFolder.newFile("JabRef.pdf"); try (PDDocument pdf = new PDDocument()) { pdf.addPage(new PDPage()); // Need page to open in Acrobat pdf.save(pdfFile.getAbsolutePath()); } importFormatPreferences = mock(ImportFormatPreferences.class, Answers.RETURNS_DEEP_STUBS); when(importFormatPreferences.getEncoding()).thenReturn(StandardCharsets.UTF_8); xmpPreferences = mock(XMPPreferences.class); // The code assumes privacy filters to be off when(xmpPreferences.isUseXMPPrivacyFilter()).thenReturn(false); when(xmpPreferences.getKeywordSeparator()).thenReturn(','); } /** * Most basic test for reading. * @throws IOException * @throws COSVisitorException */ @Test public void testReadXMPSimple() throws COSVisitorException, IOException { String bibtex = "<bibtex:year>2003</bibtex:year>\n" + "<bibtex:title>Beach sand convolution by surf-wave optimzation</bibtex:title>\n" + "<bibtex:bibtexkey>OezbekC06</bibtex:bibtexkey>\n"; writeManually(pdfFile, XMPUtilTest.bibtexXPacket(XMPUtilTest.bibtexDescription(bibtex))); List<BibEntry> l = XMPUtil.readXMP(pdfFile.getAbsoluteFile(), xmpPreferences); Assert.assertEquals(1, l.size()); BibEntry e = l.get(0); Assert.assertNotNull(e); Assert.assertEquals(Optional.of("OezbekC06"), e.getCiteKeyOptional()); Assert.assertEquals(Optional.of("2003"), e.getField("year")); Assert.assertEquals(Optional.of("Beach sand convolution by surf-wave optimzation"), e.getField("title")); Assert.assertEquals("misc", e.getType()); } /** * Is UTF8 handling working? This is because Java by default uses the platform encoding or a special UTF-kind. * @throws IOException * @throws COSVisitorException */ @Test public void testReadXMPUTF8() throws COSVisitorException, IOException { String bibtex = "<bibtex:year>2003</bibtex:year>\n" + "<bibtex:title>�pt�mz�t��n</bibtex:title>\n" + "<bibtex:bibtexkey>OezbekC06</bibtex:bibtexkey>\n"; writeManually(pdfFile, XMPUtilTest.bibtexXPacket(XMPUtilTest.bibtexDescription(bibtex))); List<BibEntry> l = XMPUtil.readXMP(pdfFile.getAbsoluteFile(), xmpPreferences); Assert.assertEquals(1, l.size()); BibEntry e = l.get(0); Assert.assertNotNull(e); Assert.assertEquals(Optional.of("OezbekC06"), e.getCiteKeyOptional()); Assert.assertEquals(Optional.of("2003"), e.getField("year")); Assert.assertEquals(Optional.of("�pt�mz�t��n"), e.getField("title")); Assert.assertEquals("misc", e.getType()); } /** * Make sure that the privacy filter works. * * @throws IOException Should not happen. * @throws TransformerException Should not happen. */ @Test public void testPrivacyFilter() throws IOException, TransformerException { when(xmpPreferences.isUseXMPPrivacyFilter()).thenReturn(true); when(xmpPreferences.getXmpPrivacyFilter()).thenReturn(Arrays.asList("author;title;note;booktitle;year;owner;timestamp")); BibEntry e = t1BibtexEntry(); XMPUtil.writeXMP(pdfFile, e, null, xmpPreferences); List<BibEntry> l = XMPUtil.readXMP(pdfFile.getAbsoluteFile(), xmpPreferences); Assert.assertEquals(1, l.size()); BibEntry x = l.get(0); Set<String> ts = x.getFieldNames(); Assert.assertEquals(8, ts.size()); Assert.assertFalse(ts.contains("bibtextype")); Assert.assertTrue(ts.contains("bibtexkey")); Assert.assertTrue(ts.contains("year")); Assert.assertTrue(ts.contains("url")); } @Test public void testPrivacyFilter2() throws Exception { BibEntry e = t1BibtexEntry(); when(xmpPreferences.isUseXMPPrivacyFilter()).thenReturn(true); when(xmpPreferences.getXmpPrivacyFilter()).thenReturn(Arrays.asList("author", "title", "note")); XMPUtil.writeXMP(pdfFile, e, null, xmpPreferences); List<BibEntry> l = XMPUtil.readXMP(pdfFile.getAbsoluteFile(), xmpPreferences); Assert.assertEquals(1, l.size()); BibEntry x = l.get(0); Set<String> expectedFields = new HashSet<>( Arrays.asList("bibtexkey", "booktitle", "owner", "timestamp", "url", "year")); Assert.assertEquals(expectedFields, x.getFieldNames()); } /** * Are authors and editors correctly read? * @throws IOException * @throws COSVisitorException */ @Test public void testReadXMPSeq() throws COSVisitorException, IOException { String bibtex = "<bibtex:author><rdf:Seq>\n" + " <rdf:li>Kelly Clarkson</rdf:li>" + " <rdf:li>Ozzy Osbourne</rdf:li>" + "</rdf:Seq></bibtex:author>" + "<bibtex:editor><rdf:Seq>" + " <rdf:li>Huey Duck</rdf:li>" + " <rdf:li>Dewey Duck</rdf:li>" + " <rdf:li>Louie Duck</rdf:li>" + "</rdf:Seq></bibtex:editor>" + "<bibtex:bibtexkey>Clarkson06</bibtex:bibtexkey>"; writeManually(pdfFile, XMPUtilTest.bibtexXPacket(XMPUtilTest.bibtexDescription(bibtex))); List<BibEntry> l = XMPUtil.readXMP(pdfFile.getAbsoluteFile(), xmpPreferences); Assert.assertEquals(1, l.size()); BibEntry e = l.get(0); Assert.assertNotNull(e); Assert.assertEquals(Optional.of("Clarkson06"), e.getCiteKeyOptional()); Assert.assertEquals("Kelly Clarkson and Ozzy Osbourne", e.getField("author").get()); Assert.assertEquals("Huey Duck and Dewey Duck and Louie Duck", e.getField("editor").get()); Assert.assertEquals("misc", e.getType()); } /** * Is the XMPEntryType correctly set? * @throws IOException * @throws COSVisitorException * */ @Test public void testReadXMPEntryType() throws COSVisitorException, IOException { String bibtex = "<bibtex:entrytype>ARticle</bibtex:entrytype>"; writeManually(pdfFile, XMPUtilTest.bibtexXPacket(XMPUtilTest.bibtexDescription(bibtex))); List<BibEntry> l = XMPUtil.readXMP(pdfFile.getAbsoluteFile(), xmpPreferences); Assert.assertEquals(1, l.size()); BibEntry e = l.get(0); Assert.assertNotNull(e); Assert.assertEquals("article", e.getType()); } public static String readManually(File tempFile) throws IOException { try (PDDocument document = PDDocument.load(tempFile.getAbsoluteFile())) { if (document.isEncrypted()) { Assert.fail("Cannot add metadata to encrypted document."); } PDDocumentCatalog catalog = document.getDocumentCatalog(); PDMetadata meta = catalog.getMetadata(); if (meta == null) { return null; } else { try (InputStream is = meta.createInputStream(); InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) { // trim() for killing padding end-newline return CharStreams.toString(reader).trim(); } } } } /** * Test whether the helper function work correctly. * @throws IOException * @throws COSVisitorException */ @Test public void testWriteReadManually() throws COSVisitorException, IOException { String bibtex = "<bibtex:year>2003</bibtex:year>\n" + "<bibtex:title>�pt�mz�t��n</bibtex:title>\n" + "<bibtex:bibtexkey>OezbekC06</bibtex:bibtexkey>\n"; writeManually(pdfFile, XMPUtilTest.bibtexXPacket(XMPUtilTest.bibtexDescription(bibtex))); Assert.assertEquals(XMPUtilTest.bibtexXPacket(XMPUtilTest.bibtexDescription(bibtex)), XMPUtilTest.readManually(pdfFile)); bibtex = "<bibtex:author><rdf:Seq>\n" + " <rdf:li>Kelly Clarkson</rdf:li>" + " <rdf:li>Ozzy Osbourne</rdf:li>" + "</rdf:Seq></bibtex:author>" + "<bibtex:editor><rdf:Seq>" + " <rdf:li>Huey Duck</rdf:li>" + " <rdf:li>Dewey Duck</rdf:li>" + " <rdf:li>Louie Duck</rdf:li>" + "</rdf:Seq></bibtex:editor>" + "<bibtex:bibtexkey>Clarkson06</bibtex:bibtexkey>"; writeManually(pdfFile, XMPUtilTest.bibtexXPacket(XMPUtilTest.bibtexDescription(bibtex))); Assert.assertEquals(XMPUtilTest.bibtexXPacket(XMPUtilTest.bibtexDescription(bibtex)), XMPUtilTest.readManually(pdfFile)); } /** * Test that readXMP and writeXMP work together. * @throws IOException * @throws TransformerException * */ @Test public void testReadWriteXMP() throws IOException, TransformerException { ParserResult result = BibtexParser.parse(new StringReader( "@article{canh05," + " author = {Crowston, K. and Annabi, H. and Howison, J. and Masango, C.}," + "\n" + " title = {Effective work practices for floss development: A model and propositions}," + "\n" + " booktitle = {Hawaii International Conference On System Sciences (HICSS)}," + "\n" + " year = {2005}," + "\n" + " owner = {oezbek}," + "\n" + " timestamp = {2006.05.29}," + "\n" + " url = {http://james.howison.name/publications.html}" + "\n" + "}"), importFormatPreferences); Collection<BibEntry> c = result.getDatabase().getEntries(); Assert.assertEquals(1, c.size()); BibEntry e = c.iterator().next(); XMPUtil.writeXMP(pdfFile, e, null, xmpPreferences); List<BibEntry> l = XMPUtil.readXMP(pdfFile.getAbsoluteFile(), xmpPreferences); Assert.assertEquals(1, l.size()); BibEntry x = l.get(0); assertEqualsBibtexEntry(e, x); } /** * Are newlines in the XML processed correctly? * @throws IOException * @throws COSVisitorException * */ @Test public void testNewlineHandling() throws COSVisitorException, IOException { String bibtex = "<bibtex:title>\nHallo\nWorld \nthis \n is\n\nnot \n\nan \n\n exercise \n \n.\n \n\n</bibtex:title>\n" + "<bibtex:tabs>\nHallo\tWorld \tthis \t is\t\tnot \t\tan \t\n exercise \t \n.\t \n\t</bibtex:tabs>\n" + "<bibtex:abstract>\n\nAbstract preserve\n\t Whitespace\n\n</bibtex:abstract>"; writeManually(pdfFile, XMPUtilTest.bibtexXPacket(XMPUtilTest.bibtexDescription(bibtex))); List<BibEntry> l = XMPUtil.readXMP(pdfFile.getAbsoluteFile(), xmpPreferences); Assert.assertEquals(1, l.size()); BibEntry e = l.get(0); Assert.assertNotNull(e); Assert.assertEquals("Hallo World this is not an exercise .", e.getField("title").get()); Assert.assertEquals("Hallo World this is not an exercise .", e.getField("tabs").get()); Assert.assertEquals("\n\nAbstract preserve\n\t Whitespace\n\n", e.getField("abstract").get()); } /** * Test whether XMP.readFile can deal with text-properties that are not element-nodes, but attribute-nodes * @throws IOException * @throws COSVisitorException * */ @Test public void testAttributeRead() throws COSVisitorException, IOException { // test 1 has attributes String bibtex = t2XMP(); writeManually(pdfFile, XMPUtilTest.bibtexXPacket(bibtex)); List<BibEntry> l = XMPUtil.readXMP(pdfFile.getAbsoluteFile(), xmpPreferences); Assert.assertEquals(1, l.size()); BibEntry e = l.get(0); assertEqualsBibtexEntry(t2BibtexEntry(), e); } @Test public void testEmpty() throws Exception { Assert.assertEquals(Collections.emptyList(), XMPUtil.readXMP(pdfFile, xmpPreferences)); } /** * Tests whether writing BibTex.xmp will preserve existing XMP-descriptions. * * @throws Exception (indicating an failure) */ @Test public void testSimpleUpdate() throws Exception { String s = " <rdf:Description rdf:about=''" + " xmlns:xmp='http://ns.adobe.com/xap/1.0/'>" + " <xmp:CreatorTool>Acrobat PDFMaker 7.0.7</xmp:CreatorTool>" + " <xmp:ModifyDate>2006-08-07T18:50:24+02:00</xmp:ModifyDate>" + " <xmp:CreateDate>2006-08-07T14:44:24+02:00</xmp:CreateDate>" + " <xmp:MetadataDate>2006-08-07T18:50:24+02:00</xmp:MetadataDate>" + " </rdf:Description>" + " <rdf:Description rdf:about=''" + " xmlns:xapMM='http://ns.adobe.com/xap/1.0/mm/'>" + " <xapMM:DocumentID>uuid:843cd67d-495e-4c1e-a4cd-64178f6b3299</xapMM:DocumentID>" + " <xapMM:InstanceID>uuid:1e56b4c0-6782-440d-ba76-d2b3d87547d1</xapMM:InstanceID>" + " <xapMM:VersionID>" + " <rdf:Seq>" + " <rdf:li>17</rdf:li>" + " </rdf:Seq>" + " </xapMM:VersionID>" + " </rdf:Description>" + " <rdf:Description rdf:about=''" + " xmlns:dc='http://purl.org/dc/elements/1.1/'>" + " <dc:format>application/pdf</dc:format>" + "</rdf:Description>"; writeManually(pdfFile, XMPUtilTest.bibtexXPacket(s)); // Nothing there yet, but should not crash Assert.assertEquals(Collections.emptyList(), XMPUtil.readXMP(pdfFile, xmpPreferences)); s = " <rdf:Description rdf:about=''" + " xmlns:xmp='http://ns.adobe.com/xap/1.0/'>" + " <xmp:CreatorTool>Acrobat PDFMaker 7.0.7</xmp:CreatorTool>" + " <xmp:ModifyDate>2006-08-07T18:50:24+02:00</xmp:ModifyDate>" + " <xmp:CreateDate>2006-08-07T14:44:24+02:00</xmp:CreateDate>" + " <xmp:MetadataDate>2006-08-07T18:50:24+02:00</xmp:MetadataDate>" + " </rdf:Description>" + " <rdf:Description rdf:about=''" + " xmlns:xapMM='http://ns.adobe.com/xap/1.0/mm/'>" + " <xapMM:DocumentID>uuid:843cd67d-495e-4c1e-a4cd-64178f6b3299</xapMM:DocumentID>" + " <xapMM:InstanceID>uuid:1e56b4c0-6782-440d-ba76-d2b3d87547d1</xapMM:InstanceID>" + " <xapMM:VersionID>" + " <rdf:Seq>" + " <rdf:li>17</rdf:li>" + " </rdf:Seq>" + " </xapMM:VersionID>" + " </rdf:Description>" + " <rdf:Description rdf:about=''" + " xmlns:dc='http://purl.org/dc/elements/1.1/'>" + " <dc:format>application/pdf</dc:format>" + " <dc:title>" + " <rdf:Alt>" + " <rdf:li xml:lang='x-default'>Questionnaire.pdf</rdf:li>" + " </rdf:Alt>" + " </dc:title>" + "</rdf:Description>"; writeManually(pdfFile, XMPUtilTest.bibtexXPacket(s)); // Title is Questionnaire.pdf so the DublinCore fallback should hit // in... Assert.assertEquals(1, XMPUtil.readXMP(pdfFile, xmpPreferences).size()); { // Now write new packet and check if it was correctly written XMPUtil.writeXMP(pdfFile, t1BibtexEntry(), null, xmpPreferences); List<BibEntry> l = XMPUtil.readXMP(pdfFile.getAbsoluteFile(), xmpPreferences); Assert.assertEquals(1, l.size()); BibEntry e = l.get(0); assertEqualsBibtexEntry(t1BibtexEntry(), e); // This is what we really want to test: Is the rest of the // descriptions still there? try (PDDocument document = PDDocument.load(pdfFile.getAbsoluteFile())) { if (document.isEncrypted()) { throw new IOException("Error: Cannot read metadata from encrypted document."); } PDDocumentCatalog catalog = document.getDocumentCatalog(); PDMetadata metaRaw = catalog.getMetadata(); XMPMetadata meta; if (metaRaw == null) { meta = new XMPMetadata(); } else { meta = new XMPMetadata(XMLUtil.parse(metaRaw.createInputStream())); } meta.addXMLNSMapping(XMPSchemaBibtex.NAMESPACE, XMPSchemaBibtex.class); List<XMPSchema> schemas = meta.getSchemas(); Assert.assertEquals(4, schemas.size()); schemas = meta.getSchemasByNamespaceURI(XMPSchemaBibtex.NAMESPACE); Assert.assertEquals(1, schemas.size()); schemas = meta.getSchemasByNamespaceURI(XMPSchemaDublinCore.NAMESPACE); Assert.assertEquals(1, schemas.size()); XMPSchemaDublinCore dc = (XMPSchemaDublinCore) schemas.get(0); Assert.assertEquals("application/pdf", dc.getFormat()); schemas = meta.getSchemasByNamespaceURI(XMPSchemaBasic.NAMESPACE); Assert.assertEquals(1, schemas.size()); XMPSchemaBasic bs = (XMPSchemaBasic) schemas.get(0); Assert.assertEquals("Acrobat PDFMaker 7.0.7", bs.getCreatorTool()); Calendar c = Calendar.getInstance(); c.clear(); c.set(Calendar.YEAR, 2006); c.set(Calendar.MONTH, Calendar.AUGUST); c.set(Calendar.DATE, 7); c.set(Calendar.HOUR, 14); c.set(Calendar.MINUTE, 44); c.set(Calendar.SECOND, 24); c.setTimeZone(TimeZone.getTimeZone("GMT+2")); Calendar other = bs.getCreateDate(); Assert.assertEquals(c.get(Calendar.YEAR), other.get(Calendar.YEAR)); Assert.assertEquals(c.get(Calendar.MONTH), other.get(Calendar.MONTH)); Assert.assertEquals(c.get(Calendar.DATE), other.get(Calendar.DATE)); Assert.assertEquals(c.get(Calendar.HOUR), other.get(Calendar.HOUR)); Assert.assertEquals(c.get(Calendar.MINUTE), other.get(Calendar.MINUTE)); Assert.assertEquals(c.get(Calendar.SECOND), other.get(Calendar.SECOND)); Assert.assertTrue(c.getTimeZone().hasSameRules(other.getTimeZone())); schemas = meta.getSchemasByNamespaceURI(XMPSchemaMediaManagement.NAMESPACE); Assert.assertEquals(1, schemas.size()); XMPSchemaMediaManagement mm = (XMPSchemaMediaManagement) schemas.get(0); Assert.assertEquals("17", mm.getSequenceList("xapMM:VersionID").get(0)); } } // Now alter the Bibtex entry, write it and do all the checks again BibEntry toSet = t1BibtexEntry(); toSet.setField("author", "Pokemon!"); XMPUtil.writeXMP(pdfFile, toSet, null, xmpPreferences); List<BibEntry> l = XMPUtil.readXMP(pdfFile.getAbsoluteFile(), xmpPreferences); Assert.assertEquals(1, l.size()); BibEntry e = l.get(0); assertEqualsBibtexEntry(toSet, e); // This is what we really want to test: Is the rest of the // descriptions still there? try (PDDocument document = PDDocument.load(pdfFile.getAbsoluteFile())) { if (document.isEncrypted()) { throw new IOException("Error: Cannot read metadata from encrypted document."); } PDDocumentCatalog catalog = document.getDocumentCatalog(); PDMetadata metaRaw = catalog.getMetadata(); XMPMetadata meta; if (metaRaw == null) { meta = new XMPMetadata(); } else { meta = new XMPMetadata(XMLUtil.parse(metaRaw.createInputStream())); } meta.addXMLNSMapping(XMPSchemaBibtex.NAMESPACE, XMPSchemaBibtex.class); List<XMPSchema> schemas = meta.getSchemas(); Assert.assertEquals(4, schemas.size()); schemas = meta.getSchemasByNamespaceURI(XMPSchemaBibtex.NAMESPACE); Assert.assertEquals(1, schemas.size()); schemas = meta.getSchemasByNamespaceURI(XMPSchemaDublinCore.NAMESPACE); Assert.assertEquals(1, schemas.size()); XMPSchemaDublinCore dc = (XMPSchemaDublinCore) schemas.get(0); Assert.assertEquals("application/pdf", dc.getFormat()); schemas = meta.getSchemasByNamespaceURI(XMPSchemaBasic.NAMESPACE); Assert.assertEquals(1, schemas.size()); XMPSchemaBasic bs = (XMPSchemaBasic) schemas.get(0); Assert.assertEquals("Acrobat PDFMaker 7.0.7", bs.getCreatorTool()); Calendar c = Calendar.getInstance(); c.clear(); c.set(Calendar.YEAR, 2006); c.set(Calendar.MONTH, 7); c.set(Calendar.DATE, 7); c.set(Calendar.HOUR, 14); c.set(Calendar.MINUTE, 44); c.set(Calendar.SECOND, 24); c.setTimeZone(TimeZone.getTimeZone("GMT+2")); Calendar other = bs.getCreateDate(); Assert.assertEquals(c.get(Calendar.YEAR), other.get(Calendar.YEAR)); Assert.assertEquals(c.get(Calendar.MONTH), other.get(Calendar.MONTH)); Assert.assertEquals(c.get(Calendar.DATE), other.get(Calendar.DATE)); Assert.assertEquals(c.get(Calendar.HOUR), other.get(Calendar.HOUR)); Assert.assertEquals(c.get(Calendar.MINUTE), other.get(Calendar.MINUTE)); Assert.assertEquals(c.get(Calendar.SECOND), other.get(Calendar.SECOND)); Assert.assertTrue(c.getTimeZone().hasSameRules(other.getTimeZone())); schemas = meta.getSchemasByNamespaceURI(XMPSchemaMediaManagement.NAMESPACE); Assert.assertEquals(1, schemas.size()); XMPSchemaMediaManagement mm = (XMPSchemaMediaManagement) schemas.get(0); Assert.assertEquals("17", mm.getSequenceList("xapMM:VersionID").get(0)); } } /** * Is XML in text properties properly escaped? * * @throws Exception */ @Test public void testXMLEscape() throws Exception { ParserResult result = BibtexParser.parse(new StringReader( "@article{canh05," + " author = {Crowston, K. and Annabi, H. and Howison, J. and Masango, C.}," + "\n" + " title = {</bibtex:title> \" bla \" '' '' && & for floss development: A model and propositions}," + "\n" + " booktitle = {<randomXML>}," + "\n" + " year = {2005}," + "\n" + " owner = {oezbek}," + "\n" + " timestamp = {2006.05.29}," + "\n" + " url = {http://james.howison.name/publications.html}" + "\n" + "}"), importFormatPreferences); Collection<BibEntry> c = result.getDatabase().getEntries(); Assert.assertEquals(1, c.size()); BibEntry e = c.iterator().next(); XMPUtil.writeXMP(pdfFile, e, null, xmpPreferences); List<BibEntry> l = XMPUtil.readXMP(pdfFile.getAbsoluteFile(), xmpPreferences); Assert.assertEquals(1, l.size()); BibEntry x = l.get(0); assertEqualsBibtexEntry(e, x); } public void assertEqualsBibtexEntry(BibEntry expected, BibEntry actual) { Assert.assertNotNull(expected); Assert.assertNotNull(actual); Assert.assertEquals(expected.getCiteKeyOptional(), actual.getCiteKeyOptional()); Assert.assertEquals(expected.getType(), actual.getType()); for (String field : expected.getFieldNames()) { if ("author".equalsIgnoreCase(field) || "editor".equalsIgnoreCase(field)) { AuthorList expectedAuthors = AuthorList.parse(expected.getField(field).get()); AuthorList actualAuthors = AuthorList.parse(actual.getField(field).get()); Assert.assertEquals(expectedAuthors, actualAuthors); } else { Assert.assertEquals("comparing " + field, expected.getField(field), actual.getField(field)); } } Assert.assertEquals(expected.getFieldNames().size(), actual.getFieldNames().size()); } /** * @depends XMPUtilTest.testReadMultiple() */ @Test public void testXMPreadString() throws Exception { ParserResult result = BibtexParser .parse(new StringReader("@article{canh05," + " author = {Crowston, K. and Annabi, H.},\n" + " title = {Title A}}\n" + "@inProceedings{foo," + " author={Norton Bar}}"), importFormatPreferences); Collection<BibEntry> c = result.getDatabase().getEntries(); Assert.assertEquals(2, c.size()); String xmp = XMPUtil.toXMP(c, null, xmpPreferences); /* Test minimal syntaxical completeness */ Assert.assertTrue(xmp.indexOf("xpacket") > 0); Assert.assertTrue(xmp.indexOf("adobe:ns:meta") > 0); Assert.assertTrue((xmp.indexOf("<bibtex:bibtexkey>canh05</bibtex:bibtexkey>") > 0) || (xmp.indexOf("bibtex:bibtexkey=") > 0)); Assert.assertTrue(xmp.indexOf("<rdf:li>Norton Bar</rdf:li>") > 0); Assert.assertTrue((xmp.indexOf("id='W5M0MpCehiHzreSzNTczkc9d'?>") > 0) || (xmp.indexOf("id=\"W5M0MpCehiHzreSzNTczkc9d\"?>") > 0)); Assert.assertTrue((xmp.indexOf("xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'") > 0) || (xmp.indexOf("xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"") > 0)); Assert.assertTrue(xmp.indexOf("<rdf:Description") > 0); Assert.assertTrue((xmp.indexOf("<?xpacket end='w'?>") > 0) || (xmp.indexOf("<?xpacket end=\"w\"?>") > 0)); /* Test contents of string */ writeManually(pdfFile, xmp); List<BibEntry> l = XMPUtil.readXMP(pdfFile, xmpPreferences); Assert.assertEquals(2, l.size()); BibEntry a = l.get(0); BibEntry b = l.get(1); if (a.getCiteKeyOptional().get().equals("foo")) { BibEntry tmp = a; a = b; b = tmp; } Assert.assertEquals(Optional.of("canh05"), a.getCiteKeyOptional()); Assert.assertEquals("K. Crowston and H. Annabi", a.getField("author").get()); Assert.assertEquals("Title A", a.getField("title").get()); Assert.assertEquals("article", a.getType()); Assert.assertEquals(Optional.of("foo"), b.getCiteKeyOptional()); Assert.assertEquals("Norton Bar", b.getField("author").get()); Assert.assertEquals("inproceedings", b.getType()); } /** * Tests whether it is possible to read several BibtexEntries * * @throws Exception */ @Test public void testReadMultiple() throws Exception { String bibtex = t2XMP() + t3XMP(); writeManually(pdfFile, XMPUtilTest.bibtexXPacket(bibtex)); // Read from file List<BibEntry> l = XMPUtil.readXMP(pdfFile, xmpPreferences); Assert.assertEquals(2, l.size()); BibEntry a = l.get(0); BibEntry b = l.get(1); if (a.getCiteKeyOptional().get().equals("Clarkson06")) { BibEntry tmp = a; a = b; b = tmp; } assertEqualsBibtexEntry(t2BibtexEntry(), a); assertEqualsBibtexEntry(t3BibtexEntry(), b); } /** * Tests whether it is possible to write several Bibtexentries * * @throws TransformerException * @throws IOException */ @Test public void testWriteMultiple() throws IOException, TransformerException { List<BibEntry> l = new LinkedList<>(); l.add(t2BibtexEntry()); l.add(t3BibtexEntry()); XMPUtil.writeXMP(pdfFile, l, null, false, xmpPreferences); l = XMPUtil.readXMP(pdfFile, xmpPreferences); Assert.assertEquals(2, l.size()); BibEntry a = l.get(0); BibEntry b = l.get(1); if (a.getCiteKeyOptional().get().equals("Clarkson06")) { BibEntry tmp = a; a = b; b = tmp; } assertEqualsBibtexEntry(t2BibtexEntry(), a); assertEqualsBibtexEntry(t3BibtexEntry(), b); } /** * Tests whether a edit-protected PDF can be read */ @Test public void testReadProtectedPDFHasMetaData() throws Exception { try (InputStream is = XMPUtilTest.class.getResourceAsStream("/pdfs/write-protected.pdf")) { Assert.assertTrue(XMPUtil.hasMetadata(is, xmpPreferences)); } } /** * Tests whether a edit-protected PDF can be read */ @Test public void testReadProtectedPDFHasCorrectMetaData() throws Exception { try (InputStream is = XMPUtilTest.class.getResourceAsStream("/pdfs/write-protected.pdf")) { List<BibEntry> readEntries = XMPUtil.readXMP(is, xmpPreferences); BibEntry entry = new BibEntry(); entry.setType("misc"); entry.setField("author", "Firstname Lastname"); List<BibEntry> expected = Collections.singletonList(entry); Assert.assertEquals(expected, readEntries); } } @Test public void testReadWriteDC() throws IOException, TransformerException { List<BibEntry> l = new LinkedList<>(); l.add(t3BibtexEntry()); XMPUtil.writeXMP(pdfFile, l, null, true, xmpPreferences); try (PDDocument document = PDDocument.load(pdfFile.getAbsoluteFile())) { if (document.isEncrypted()) { Assert.fail("Cannot add metadata to encrypted document."); } Assert.assertEquals("Kelly Clarkson and Ozzy Osbourne", document.getDocumentInformation().getAuthor()); Assert.assertEquals("Hypersonic ultra-sound", document.getDocumentInformation().getTitle()); Assert.assertEquals("Huey Duck and Dewey Duck and Louie Duck", document.getDocumentInformation().getCustomMetadataValue("bibtex/editor")); Assert.assertEquals("Clarkson06", document.getDocumentInformation().getCustomMetadataValue("bibtex/bibtexkey")); Assert.assertEquals("peanut, butter, jelly", document.getDocumentInformation().getKeywords()); assertEqualsBibtexEntry(t3BibtexEntry(), XMPUtil.getBibtexEntryFromDocumentInformation(document.getDocumentInformation()).get()); PDDocumentCatalog catalog = document.getDocumentCatalog(); PDMetadata metaRaw = catalog.getMetadata(); if (metaRaw == null) { Assert.fail(); return; } XMPMetadata meta = new XMPMetadata(XMLUtil.parse(metaRaw.createInputStream())); meta.addXMLNSMapping(XMPSchemaBibtex.NAMESPACE, XMPSchemaBibtex.class); // Check Dublin Core List<XMPSchema> schemas = meta.getSchemasByNamespaceURI("http://purl.org/dc/elements/1.1/"); Assert.assertEquals(1, schemas.size()); XMPSchemaDublinCore dcSchema = (XMPSchemaDublinCore) schemas.iterator().next(); Assert.assertNotNull(dcSchema); Assert.assertEquals("Hypersonic ultra-sound", dcSchema.getTitle()); Assert.assertEquals("1982-07", dcSchema.getSequenceList("dc:date").get(0)); Assert.assertEquals("Kelly Clarkson", dcSchema.getCreators().get(0)); Assert.assertEquals("Ozzy Osbourne", dcSchema.getCreators().get(1)); Assert.assertEquals("Huey Duck", dcSchema.getContributors().get(0)); Assert.assertEquals("Dewey Duck", dcSchema.getContributors().get(1)); Assert.assertEquals("Louie Duck", dcSchema.getContributors().get(2)); Assert.assertEquals("InProceedings".toLowerCase(), dcSchema.getTypes().get(0).toLowerCase()); Assert.assertTrue(dcSchema.getRelationships().contains("bibtex/bibtexkey/Clarkson06")); Assert.assertEquals("peanut", dcSchema.getSubjects().get(0)); Assert.assertEquals("butter", dcSchema.getSubjects().get(1)); Assert.assertEquals("jelly", dcSchema.getSubjects().get(2)); /** * Bibtexkey, Journal, pdf, booktitle */ Assert.assertEquals(4, dcSchema.getRelationships().size()); assertEqualsBibtexEntry(t3BibtexEntry(), XMPUtil.getBibtexEntryFromDublinCore(dcSchema, xmpPreferences).get()); } } @Test public void testWriteSingleUpdatesDCAndInfo() throws IOException, TransformerException { List<BibEntry> l = new LinkedList<>(); l.add(t3BibtexEntry()); XMPUtil.writeXMP(pdfFile, l, null, true, xmpPreferences); try (PDDocument document = PDDocument.load(pdfFile.getAbsoluteFile())) { if (document.isEncrypted()) { Assert.fail("Cannot add metadata to encrypted document."); } Assert.assertEquals("Kelly Clarkson and Ozzy Osbourne", document.getDocumentInformation().getAuthor()); Assert.assertEquals("Hypersonic ultra-sound", document.getDocumentInformation().getTitle()); Assert.assertEquals("Huey Duck and Dewey Duck and Louie Duck", document.getDocumentInformation().getCustomMetadataValue("bibtex/editor")); Assert.assertEquals("Clarkson06", document.getDocumentInformation().getCustomMetadataValue("bibtex/bibtexkey")); Assert.assertEquals("peanut, butter, jelly", document.getDocumentInformation().getKeywords()); assertEqualsBibtexEntry(t3BibtexEntry(), XMPUtil.getBibtexEntryFromDocumentInformation(document.getDocumentInformation()).get()); PDDocumentCatalog catalog = document.getDocumentCatalog(); PDMetadata metaRaw = catalog.getMetadata(); if (metaRaw == null) { Assert.fail(); return; } XMPMetadata meta = new XMPMetadata(XMLUtil.parse(metaRaw.createInputStream())); meta.addXMLNSMapping(XMPSchemaBibtex.NAMESPACE, XMPSchemaBibtex.class); // Check Dublin Core List<XMPSchema> schemas = meta.getSchemasByNamespaceURI("http://purl.org/dc/elements/1.1/"); Assert.assertEquals(1, schemas.size()); XMPSchemaDublinCore dcSchema = (XMPSchemaDublinCore) schemas.iterator().next(); Assert.assertNotNull(dcSchema); Assert.assertEquals("Hypersonic ultra-sound", dcSchema.getTitle()); Assert.assertEquals("1982-07", dcSchema.getSequenceList("dc:date").get(0)); Assert.assertEquals("Kelly Clarkson", dcSchema.getCreators().get(0)); Assert.assertEquals("Ozzy Osbourne", dcSchema.getCreators().get(1)); Assert.assertEquals("Huey Duck", dcSchema.getContributors().get(0)); Assert.assertEquals("Dewey Duck", dcSchema.getContributors().get(1)); Assert.assertEquals("Louie Duck", dcSchema.getContributors().get(2)); Assert.assertEquals("InProceedings".toLowerCase(), dcSchema.getTypes().get(0).toLowerCase()); Assert.assertTrue(dcSchema.getRelationships().contains("bibtex/bibtexkey/Clarkson06")); Assert.assertEquals("peanut", dcSchema.getSubjects().get(0)); Assert.assertEquals("butter", dcSchema.getSubjects().get(1)); Assert.assertEquals("jelly", dcSchema.getSubjects().get(2)); /** * Bibtexkey, Journal, pdf, booktitle */ Assert.assertEquals(4, dcSchema.getRelationships().size()); assertEqualsBibtexEntry(t3BibtexEntry(), XMPUtil.getBibtexEntryFromDublinCore(dcSchema, xmpPreferences).get()); } } @Test public void testReadRawXMP() throws IOException, TransformerException { ParserResult result = BibtexParser.parse(new StringReader( "@article{canh05," + " author = {Crowston, K. and Annabi, H. and Howison, J. and Masango, C.},\n" + " title = {Effective work practices for floss development: A model and propositions},\n" + " booktitle = {Hawaii International Conference On System Sciences (HICSS)},\n" + " year = {2005},\n" + " owner = {oezbek},\n" + " timestamp = {2006.05.29},\n" + " url = {http://james.howison.name/publications.html}}"), importFormatPreferences); Collection<BibEntry> c = result.getDatabase().getEntries(); Assert.assertEquals(1, c.size()); BibEntry e = c.iterator().next(); XMPUtil.writeXMP(pdfFile, e, null, xmpPreferences); Optional<XMPMetadata> metadata = XMPUtil.readRawXMP(pdfFile); Assert.assertTrue(metadata.isPresent()); List<XMPSchema> schemas = metadata.get().getSchemas(); Assert.assertEquals(2, schemas.size()); schemas = metadata.get().getSchemasByNamespaceURI(XMPSchemaBibtex.NAMESPACE); Assert.assertEquals(1, schemas.size()); XMPSchemaBibtex bib = (XMPSchemaBibtex) schemas.get(0); List<String> authors = bib.getSequenceList("author"); Assert.assertEquals(4, authors.size()); Assert.assertEquals("K. Crowston", authors.get(0)); Assert.assertEquals("H. Annabi", authors.get(1)); Assert.assertEquals("J. Howison", authors.get(2)); Assert.assertEquals("C. Masango", authors.get(3)); Assert.assertEquals("article", bib.getTextProperty("entrytype")); Assert.assertEquals("Effective work practices for floss development: A model and propositions", bib.getTextProperty("title")); Assert.assertEquals("Hawaii International Conference On System Sciences (HICSS)", bib.getTextProperty("booktitle")); Assert.assertEquals("2005", bib.getTextProperty("year")); Assert.assertEquals("oezbek", bib.getTextProperty("owner")); Assert.assertEquals("http://james.howison.name/publications.html", bib.getTextProperty("url")); } /** * Test whether the command-line client works correctly with writing a single entry * @throws IOException * @throws TransformerException * @throws COSVisitorException * */ @Test public void testCommandLineSingleBib() throws IOException, TransformerException, COSVisitorException { // First check conversion from .bib to .xmp File tempBib = tempFolder.newFile("JabRef.bib"); try (BufferedWriter fileWriter = Files.newBufferedWriter(tempBib.toPath(), StandardCharsets.UTF_8)) { fileWriter.write(t1BibtexString()); fileWriter.close(); try (ByteArrayOutputStream s = new ByteArrayOutputStream()) { PrintStream oldOut = System.out; System.setOut(new PrintStream(s)); XMPUtilMain.main(new String[] {tempBib.getAbsolutePath()}); System.setOut(oldOut); String xmp = s.toString(); writeManually(pdfFile, xmp); } List<BibEntry> l = XMPUtil.readXMP(pdfFile, xmpPreferences); Assert.assertEquals(1, l.size()); assertEqualsBibtexEntry(t1BibtexEntry(), l.get(0)); } } /** * @throws IOException * @throws TransformerException * @throws COSVisitorException * @depends XMPUtil.writeXMP */ @Test public void testCommandLineSinglePdf() throws IOException, TransformerException, COSVisitorException { { // Write XMP to file BibEntry e = t1BibtexEntry(); XMPUtil.writeXMP(pdfFile, e, null, xmpPreferences); try (ByteArrayOutputStream s = new ByteArrayOutputStream()) { PrintStream oldOut = System.out; System.setOut(new PrintStream(s)); XMPUtilMain.main(new String[] {pdfFile.getAbsolutePath()}); System.setOut(oldOut); String bibtex = s.toString(); ParserResult result = new BibtexParser(importFormatPreferences).parse(new StringReader(bibtex)); Collection<BibEntry> c = result.getDatabase().getEntries(); Assert.assertEquals(1, c.size()); BibEntry x = c.iterator().next(); assertEqualsBibtexEntry(e, x); } } // Write XMP to file BibEntry e = t1BibtexEntry(); XMPUtil.writeXMP(pdfFile, e, null, xmpPreferences); try (ByteArrayOutputStream s = new ByteArrayOutputStream()) { PrintStream oldOut = System.out; System.setOut(new PrintStream(s)); XMPUtilMain.main(new String[] {"-x", pdfFile.getAbsolutePath()}); System.setOut(oldOut); s.close(); String xmp = s.toString(); /* Test minimal syntaxical completeness */ Assert.assertTrue(xmp.indexOf("xpacket") > 0); Assert.assertTrue(xmp.indexOf("adobe:ns:meta") > 0); Assert.assertTrue((xmp.indexOf("<bibtex:bibtexkey>canh05</bibtex:bibtexkey>") > 0) || (xmp.indexOf("bibtex:bibtexkey=") > 0)); Assert.assertTrue(xmp.indexOf("<rdf:li>K. Crowston</rdf:li>") > 0); Assert.assertTrue((xmp.indexOf("id='W5M0MpCehiHzreSzNTczkc9d'?>") > 0) || (xmp.indexOf("id=\"W5M0MpCehiHzreSzNTczkc9d\"?>") > 0)); Assert.assertTrue((xmp.indexOf("xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'") > 0) || (xmp.indexOf("xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"") > 0)); Assert.assertTrue(xmp.indexOf("<rdf:Description") > 0); Assert.assertTrue((xmp.indexOf("<?xpacket end='w'?>") > 0) || (xmp.indexOf("<?xpacket end=\"w\"?>") > 0)); /* Test contents of string */ writeManually(pdfFile, xmp); List<BibEntry> l = XMPUtil.readXMP(pdfFile, xmpPreferences); Assert.assertEquals(1, l.size()); assertEqualsBibtexEntry(t1BibtexEntry(), l.get(0)); } } /** * Test whether the command-line client can pick one of several entries from a bibtex file * @throws IOException * @throws TransformerException */ @Test public void testCommandLineByKey() throws IOException, TransformerException { File tempBib = tempFolder.newFile("JabRef.bib"); try (BufferedWriter fileWriter = Files.newBufferedWriter(tempBib.toPath(), StandardCharsets.UTF_8)) { fileWriter.write(t1BibtexString()); fileWriter.write(t2BibtexString()); } PrintStream sysOut = System.out; // First try canh05 try (ByteArrayOutputStream s = new ByteArrayOutputStream()) { System.setOut(new PrintStream(s)); XMPUtilMain.main(new String[]{"canh05", tempBib.getAbsolutePath(), pdfFile.getAbsolutePath()}); } finally { System.setOut(sysOut); } // PDF should be annotated: List<BibEntry> l = XMPUtil.readXMP(pdfFile, xmpPreferences); Assert.assertEquals(1, l.size()); assertEqualsBibtexEntry(t1BibtexEntry(), l.get(0)); // Now try OezbekC06 try (ByteArrayOutputStream s = new ByteArrayOutputStream()) { System.setOut(new PrintStream(s)); try { XMPUtilMain.main(new String[]{"OezbekC06", tempBib.getAbsolutePath(), pdfFile.getAbsolutePath()}); } finally { System.setOut(sysOut); } } // PDF should be annotated: l = XMPUtil.readXMP(pdfFile, xmpPreferences); Assert.assertEquals(1, l.size()); assertEqualsBibtexEntry(t2BibtexEntry(), l.get(0)); } /** * Test whether the command-line client can deal with several bibtex entries. * @throws IOException * @throws TransformerException */ @Test public void testCommandLineSeveral() throws IOException, TransformerException { File tempBib = tempFolder.newFile("JabRef.bib"); try (BufferedWriter fileWriter = Files.newBufferedWriter(tempBib.toPath(), StandardCharsets.UTF_8)) { fileWriter.write(t1BibtexString()); fileWriter.write(t3BibtexString()); fileWriter.close(); try (ByteArrayOutputStream s = new ByteArrayOutputStream()) { PrintStream oldOut = System.out; System.setOut(new PrintStream(s)); XMPUtilMain.main(new String[] {tempBib.getAbsolutePath(), pdfFile.getAbsolutePath()}); System.setOut(oldOut); } List<BibEntry> l = XMPUtil.readXMP(pdfFile, xmpPreferences); Assert.assertEquals(2, l.size()); BibEntry a = l.get(0); BibEntry b = l.get(1); if (a.getCiteKeyOptional().get().equals("Clarkson06")) { BibEntry tmp = a; a = b; b = tmp; } BibEntry t1 = t1BibtexEntry(); BibEntry t3 = t3BibtexEntry(); // Writing and reading will resolve strings! t3.setField("month", "July"); assertEqualsBibtexEntry(t1, a); assertEqualsBibtexEntry(t3, b); } } /** * Test that readXMP and writeXMP work together. * @throws IOException * @throws TransformerException * * @throws Exception */ @Test public void testResolveStrings() throws IOException, TransformerException { ParserResult original = BibtexParser .parse(new StringReader("@string{ crow = \"Crowston, K.\"}\n" + "@string{ anna = \"Annabi, H.\"}\n" + "@string{ howi = \"Howison, J.\"}\n" + "@string{ masa = \"Masango, C.\"}\n" + "@article{canh05," + " author = {#crow# and #anna# and #howi# and #masa#}," + "\n" + " title = {Effective work practices for floss development: A model and propositions}," + "\n" + " booktitle = {Hawaii International Conference On System Sciences (HICSS)}," + "\n" + " year = {2005}," + "\n" + " owner = {oezbek}," + "\n" + " timestamp = {2006.05.29}," + "\n" + " url = {http://james.howison.name/publications.html}" + "\n" + "}"), importFormatPreferences); Collection<BibEntry> c = original.getDatabase().getEntries(); Assert.assertEquals(1, c.size()); BibEntry e = c.iterator().next(); XMPUtil.writeXMP(pdfFile, e, original.getDatabase(), xmpPreferences); List<BibEntry> l = XMPUtil.readXMP(pdfFile.getAbsoluteFile(), xmpPreferences); Assert.assertEquals(1, l.size()); BibEntry x = l.get(0); Assert.assertEquals(AuthorList.parse("Crowston, K. and Annabi, H. and Howison, J. and Masango, C."), AuthorList.parse(x.getField("author").get())); } @Test(expected = EncryptedPdfsNotSupportedException.class) public void expectedEncryptionNotSupportedExceptionAtRead() throws IOException { try (InputStream is = XMPUtilTest.class.getResourceAsStream("/pdfs/encrypted.pdf")) { XMPUtil.readXMP(is, xmpPreferences); } } @Test(expected = EncryptedPdfsNotSupportedException.class) public void expectedEncryptionNotSupportedExceptionAtWrite() throws IOException, TransformerException { XMPUtil.writeXMP("src/test/resources/pdfs/encrypted.pdf", t1BibtexEntry(), null, xmpPreferences); } /** * A better testcase for resolveStrings. Makes sure that also the document information and dublin core are written * correctly. * <p/> * Data was contributed by Philip K.F. Hölzenspies (p.k.f.holzenspies [at] utwente.nl). * * @throws IOException * @throws TransformerException */ @Test public void testResolveStrings2() throws IOException, TransformerException { try (BufferedReader fr = Files.newBufferedReader(Paths.get("src/test/resources/org/jabref/util/twente.bib"), StandardCharsets.UTF_8)) { ParserResult result = new BibtexParser(importFormatPreferences).parse(fr); Assert.assertEquals("Arvind", result.getDatabase().resolveForStrings("#Arvind#")); AuthorList originalAuthors = AuthorList.parse( "Patterson, David and Arvind and Asanov\\'\\i{}c, Krste and Chiou, Derek and Hoe, James and Kozyrakis, Christos and Lu, S{hih-Lien} and Oskin, Mark and Rabaey, Jan and Wawrzynek, John"); try { XMPUtil.writeXMP(pdfFile, result.getDatabase().getEntryByKey("Patterson06").get(), result.getDatabase(), xmpPreferences); // Test whether we the main function can load the bibtex correctly BibEntry b = XMPUtil.readXMP(pdfFile, xmpPreferences).get(0); Assert.assertNotNull(b); Assert.assertEquals(originalAuthors, AuthorList.parse(b.getField("author").get())); // Next check from Document Information try (PDDocument document = PDDocument.load(pdfFile.getAbsoluteFile())) { Assert.assertEquals(originalAuthors, AuthorList.parse(document.getDocumentInformation().getAuthor())); b = XMPUtil.getBibtexEntryFromDocumentInformation(document.getDocumentInformation()).get(); Assert.assertEquals(originalAuthors, AuthorList.parse(b.getField("author").get())); // Now check from Dublin Core PDDocumentCatalog catalog = document.getDocumentCatalog(); PDMetadata metaRaw = catalog.getMetadata(); if (metaRaw == null) { Assert.fail(); return; // To avoid warnings } XMPMetadata meta = new XMPMetadata(XMLUtil.parse(metaRaw.createInputStream())); meta.addXMLNSMapping(XMPSchemaBibtex.NAMESPACE, XMPSchemaBibtex.class); List<XMPSchema> schemas = meta.getSchemasByNamespaceURI("http://purl.org/dc/elements/1.1/"); Assert.assertEquals(1, schemas.size()); XMPSchemaDublinCore dcSchema = (XMPSchemaDublinCore) schemas.iterator().next(); Assert.assertNotNull(dcSchema); Assert.assertEquals("David Patterson", dcSchema.getCreators().get(0)); Assert.assertEquals("Arvind", dcSchema.getCreators().get(1)); Assert.assertEquals("Krste Asanov\\'\\i{}c", dcSchema.getCreators().get(2)); b = XMPUtil.getBibtexEntryFromDublinCore(dcSchema, xmpPreferences).get(); Assert.assertNotNull(b); Assert.assertEquals(originalAuthors, AuthorList.parse(b.getField("author").get())); } } finally { if (!pdfFile.delete()) { System.err.println("Cannot delete temporary file"); } } } } }