diff --git a/src/main/java/ezvcard/io/chain/ChainingTextWriter.java b/src/main/java/ezvcard/io/chain/ChainingTextWriter.java index e8b44d38c..6535b0ac9 100644 --- a/src/main/java/ezvcard/io/chain/ChainingTextWriter.java +++ b/src/main/java/ezvcard/io/chain/ChainingTextWriter.java @@ -48,6 +48,7 @@ public class ChainingTextWriter extends ChainingWriter { private VCardVersion version; private boolean caretEncoding = false; + private boolean outlook = false; /** * @param vcards the vCards to write @@ -88,6 +89,30 @@ public ChainingTextWriter caretEncoding(boolean enable) { return this; } + /** + *

+ * Sets whether the vCards should be fully compatible with Microsoft Outlook + * mail clients. This setting is disabled by default. + *

+ *

+ * Enabling this setting may make the vCards incompatible with other vCard + * consumers. + *

+ *

+ * Enabling this setting adds an empty line after all base64-encoded + * property values for vCards with versions 2.1 and 3.0. This setting has no + * effect on 4.0 vCards, or on vCards that do not have any properties with + * base64-encoded values. + *

+ * @param enable true to enable, false to disable (defaults to false). + * @return this + * @see VCardWriter#setOutlookCompatibility(boolean) + */ + public ChainingTextWriter outlook(boolean enable) { + this.outlook = enable; + return this; + } + @Override public ChainingTextWriter prodId(boolean include) { return super.prodId(include); @@ -165,6 +190,7 @@ private void go(VCardWriter writer) throws IOException { writer.setAddProdId(prodId); writer.setCaretEncodingEnabled(caretEncoding); writer.setVersionStrict(versionStrict); + writer.setOutlookCompatibility(outlook); if (index != null) { writer.setScribeIndex(index); } diff --git a/src/main/java/ezvcard/io/text/VCardWriter.java b/src/main/java/ezvcard/io/text/VCardWriter.java index 1bfc533ba..edcef7c3c 100644 --- a/src/main/java/ezvcard/io/text/VCardWriter.java +++ b/src/main/java/ezvcard/io/text/VCardWriter.java @@ -105,6 +105,7 @@ public class VCardWriter extends StreamWriter implements Flushable { private final VCardRawWriter writer; private final LinkedList prodIdStack = new LinkedList(); + private boolean outlookCompatibility = false; /** * @param out the output stream to write to @@ -208,6 +209,42 @@ public void setCaretEncodingEnabled(boolean enable) { writer.setCaretEncodingEnabled(enable); } + /** + *

+ * Gets whether vCards generated by this writer will be compatible with + * Microsoft Outlook mail clients. This setting is disabled by default. + *

+ *

+ * Enabling this setting adds an empty line after all base64-encoded + * property values for vCard versions 2.1 and 3.0. + *

+ * @return true if enabled, false if disabled (defaults to false). + */ + public boolean isOutlookCompatibility() { + return outlookCompatibility; + } + + /** + *

+ * Sets whether vCards generated by this writer should be fully compatible + * with Microsoft Outlook mail clients. This setting is disabled by default. + *

+ *

+ * Enabling this setting may make the vCards incompatible with other vCard + * consumers. + *

+ *

+ * Enabling this setting adds an empty line after all base64-encoded + * property values for vCards with versions 2.1 and 3.0. This setting has no + * effect on 4.0 vCards, or on vCards that do not have any properties with + * base64-encoded values. + *

+ * @param enable true to enable, false to disable (defaults to false). + */ + public void setOutlookCompatibility(boolean enable) { + outlookCompatibility = enable; + } + @Override @SuppressWarnings({ "rawtypes", "unchecked" }) protected void _write(VCard vcard, List propertiesToAdd) throws IOException { @@ -283,22 +320,44 @@ protected void _write(VCard vcard, List propertiesToAdd) throws I //write the property writer.writeProperty(property.getGroup(), scribe.getPropertyName(), parameters, value); - /* - * Outlook 2010 requires an empty line after base64 values (at - * least, some of the time). - * See: https://code.google.com/p/ez-vcard/issues/detail?id=21 - */ - if (targetVersion != VCardVersion.V4_0 && property instanceof BinaryProperty) { - BinaryProperty binaryProperty = (BinaryProperty) property; - if (binaryProperty.getData() != null) { - writer.getFoldedLineWriter().writeln(""); - } - } + insertEmptyLineForOutlook(targetVersion, property); } writer.writeEndComponent("VCARD"); } + /** + * Outlook 2010 requires an empty line after base64 values (at + * least, some of the time). + * @param targetVersion the vCard version + * @param property the property being written + * @see https://github.com/mangstadt/ez-vcard/issues/21 + */ + private void insertEmptyLineForOutlook(VCardVersion targetVersion, VCardProperty property) throws IOException { + if (!outlookCompatibility) { + //setting not enabled + return; + } + + if (targetVersion == VCardVersion.V4_0) { + //only do this for 2.1 and 3.0 vCards + return; + } + + if (!(property instanceof BinaryProperty)) { + //property does not have binary data + return; + } + + BinaryProperty binaryProperty = (BinaryProperty) property; + if (binaryProperty.getData() == null) { + //property value is not base64-encoded + return; + } + + writer.getFoldedLineWriter().writeln(""); + } + /** * Determines if the given default data type is "date-and-or-time" and the * given data type is time-based. Properties that meet this criteria should diff --git a/src/test/java/ezvcard/EzvcardTest.java b/src/test/java/ezvcard/EzvcardTest.java index 0f13d35a6..a872cc347 100644 --- a/src/test/java/ezvcard/EzvcardTest.java +++ b/src/test/java/ezvcard/EzvcardTest.java @@ -26,7 +26,9 @@ import ezvcard.io.LuckyNumType; import ezvcard.io.LuckyNumType.LuckyNumScribe; import ezvcard.io.xml.XCardNamespaceContext; +import ezvcard.parameter.ImageType; import ezvcard.property.FormattedName; +import ezvcard.property.Photo; import ezvcard.util.XCardBuilder; import ezvcard.util.XmlUtils; @@ -456,6 +458,59 @@ public void write_versionStrict() throws Exception { assertTrue(actual.contains("\r\nMAILER:")); } + @Test + public void write_outlook() throws Exception { + byte data[] = "data".getBytes(); + VCard vcard = new VCard(); + vcard.addPhoto(new Photo(data, ImageType.JPEG)); + + //default + { + String actual = Ezvcard.write(vcard).prodId(false).version(VCardVersion.V2_1).go(); + + //@formatter:off + String expected = + "BEGIN:VCARD\r\n" + + "VERSION:2.1\r\n" + + "PHOTO;ENCODING=base64;JPEG:ZGF0YQ==\r\n" + + "END:VCARD\r\n"; + //@formatter:on + + assertEquals(expected, actual); + } + + //true + { + String actual = Ezvcard.write(vcard).prodId(false).version(VCardVersion.V2_1).outlook(true).go(); + + //@formatter:off + String expected = + "BEGIN:VCARD\r\n" + + "VERSION:2.1\r\n" + + "PHOTO;ENCODING=base64;JPEG:ZGF0YQ==\r\n" + + "\r\n" + + "END:VCARD\r\n"; + //@formatter:on + + assertEquals(expected, actual); + } + + //false + { + String actual = Ezvcard.write(vcard).prodId(false).version(VCardVersion.V2_1).outlook(false).go(); + + //@formatter:off + String expected = + "BEGIN:VCARD\r\n" + + "VERSION:2.1\r\n" + + "PHOTO;ENCODING=base64;JPEG:ZGF0YQ==\r\n" + + "END:VCARD\r\n"; + //@formatter:on + + assertEquals(expected, actual); + } + } + @Test public void writeXml_go() throws Exception { VCard vcard = new VCard(); diff --git a/src/test/java/ezvcard/io/text/VCardWriterTest.java b/src/test/java/ezvcard/io/text/VCardWriterTest.java index 23d7fe959..74a61ac55 100644 --- a/src/test/java/ezvcard/io/text/VCardWriterTest.java +++ b/src/test/java/ezvcard/io/text/VCardWriterTest.java @@ -412,23 +412,33 @@ class DateAndOrTimeProperty extends VCardProperty { } @Test - public void newline_after_base64() throws Throwable { + public void outlookCompatibility() throws Throwable { VCard vcard = new VCard(); byte data[] = "foobar".getBytes(); vcard.addKey(new Key(data, KeyType.X509)); vcard.addPhoto(new Photo(data, ImageType.JPEG)); vcard.addLogo(new Logo("http://www.company.com/logo.png", ImageType.PNG)); + vcard.addNote("note"); { StringWriter sw = new StringWriter(); - VCardWriter vcw = new VCardWriter(sw, VCardVersion.V2_1); - vcw.setAddProdId(false); - vcw.write(vcard); + VCardWriter writer = new VCardWriter(sw, VCardVersion.V2_1); + writer.setAddProdId(false); + writer.write(vcard); + writer.setOutlookCompatibility(true); + writer.write(vcard); String actual = sw.toString(); //@formatter:off String expected = + "BEGIN:VCARD\r\n" + + "VERSION:2.1\r\n" + + "KEY;ENCODING=base64;X509:Zm9vYmFy\r\n" + + "PHOTO;ENCODING=base64;JPEG:Zm9vYmFy\r\n" + + "LOGO;PNG;VALUE=url:http://www.company.com/logo.png\r\n" + + "NOTE:note\r\n" + + "END:VCARD\r\n" + "BEGIN:VCARD\r\n" + "VERSION:2.1\r\n" + "KEY;ENCODING=base64;X509:Zm9vYmFy\r\n" + @@ -436,6 +446,7 @@ public void newline_after_base64() throws Throwable { "PHOTO;ENCODING=base64;JPEG:Zm9vYmFy\r\n" + "\r\n" + "LOGO;PNG;VALUE=url:http://www.company.com/logo.png\r\n" + + "NOTE:note\r\n" + "END:VCARD\r\n"; //@formatter:on @@ -444,14 +455,23 @@ public void newline_after_base64() throws Throwable { { StringWriter sw = new StringWriter(); - VCardWriter vcw = new VCardWriter(sw, VCardVersion.V3_0); - vcw.setAddProdId(false); - vcw.write(vcard); + VCardWriter writer = new VCardWriter(sw, VCardVersion.V3_0); + writer.setAddProdId(false); + writer.write(vcard); + writer.setOutlookCompatibility(true); + writer.write(vcard); String actual = sw.toString(); //@formatter:off String expected = + "BEGIN:VCARD\r\n" + + "VERSION:3.0\r\n" + + "KEY;ENCODING=b;TYPE=x509:Zm9vYmFy\r\n" + + "PHOTO;ENCODING=b;TYPE=jpeg:Zm9vYmFy\r\n" + + "LOGO;TYPE=png;VALUE=uri:http://www.company.com/logo.png\r\n" + + "NOTE:note\r\n" + + "END:VCARD\r\n" + "BEGIN:VCARD\r\n" + "VERSION:3.0\r\n" + "KEY;ENCODING=b;TYPE=x509:Zm9vYmFy\r\n" + @@ -459,6 +479,7 @@ public void newline_after_base64() throws Throwable { "PHOTO;ENCODING=b;TYPE=jpeg:Zm9vYmFy\r\n" + "\r\n" + "LOGO;TYPE=png;VALUE=uri:http://www.company.com/logo.png\r\n" + + "NOTE:note\r\n" + "END:VCARD\r\n"; //@formatter:on @@ -467,9 +488,11 @@ public void newline_after_base64() throws Throwable { { StringWriter sw = new StringWriter(); - VCardWriter vcw = new VCardWriter(sw, VCardVersion.V4_0); - vcw.setAddProdId(false); - vcw.write(vcard); + VCardWriter writer = new VCardWriter(sw, VCardVersion.V4_0); + writer.setAddProdId(false); + writer.write(vcard); + writer.setOutlookCompatibility(true); + writer.write(vcard); String actual = sw.toString(); @@ -480,6 +503,14 @@ public void newline_after_base64() throws Throwable { "KEY:data:application/x509;base64,Zm9vYmFy\r\n" + "PHOTO:data:image/jpeg;base64,Zm9vYmFy\r\n" + "LOGO;MEDIATYPE=image/png:http://www.company.com/logo.png\r\n" + + "NOTE:note\r\n" + + "END:VCARD\r\n" + + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "KEY:data:application/x509;base64,Zm9vYmFy\r\n" + + "PHOTO:data:image/jpeg;base64,Zm9vYmFy\r\n" + + "LOGO;MEDIATYPE=image/png:http://www.company.com/logo.png\r\n" + + "NOTE:note\r\n" + "END:VCARD\r\n"; //@formatter:on