diff options
author | Ricki Hirner <hirner@bitfire.at> | 2021-10-21 09:13:57 +0300 |
---|---|---|
committer | Ricki Hirner <hirner@bitfire.at> | 2021-10-21 09:23:52 +0300 |
commit | 959570a724766327f0ab56427d6b92cd7f334b1d (patch) | |
tree | cb4e621f05efba54c07314796cf2add214685237 | |
parent | 51b33ebb26e4e7df8b94d0b1c010e39aad9dee43 (diff) |
Support jCard
7 files changed, 96 insertions, 37 deletions
diff --git a/build.gradle b/build.gradle index b94553f..f53aace 100644 --- a/build.gradle +++ b/build.gradle @@ -87,8 +87,6 @@ dependencies { // hCard functionality not needed exclude group: 'org.jsoup' exclude group: 'org.freemarker' - // jCard functionality not needed - exclude group: 'com.fasterxml.jackson.core' } androidTestImplementation 'androidx.test:runner:1.4.0' diff --git a/src/androidTest/java/at/bitfire/vcard4android/AndroidContactTest.kt b/src/androidTest/java/at/bitfire/vcard4android/AndroidContactTest.kt index 63df5b1..61b173e 100644 --- a/src/androidTest/java/at/bitfire/vcard4android/AndroidContactTest.kt +++ b/src/androidTest/java/at/bitfire/vcard4android/AndroidContactTest.kt @@ -110,7 +110,7 @@ class AndroidContactTest { "TEL;CELL=;PREF=:+12345\r\n" + "EMAIL;PREF=invalid:test@example.com\r\n" + "END:VCARD\r\n" - val contacts = Contact.fromReader(StringReader(vCard), null) + val contacts = Contact.fromReader(StringReader(vCard), false, null) val dbContact = AndroidContact(addressBook, contacts.first(), null, null) dbContact.add() diff --git a/src/main/java/at/bitfire/vcard4android/Contact.kt b/src/main/java/at/bitfire/vcard4android/Contact.kt index 98618f8..cd48a6a 100644 --- a/src/main/java/at/bitfire/vcard4android/Contact.kt +++ b/src/main/java/at/bitfire/vcard4android/Contact.kt @@ -11,6 +11,7 @@ package at.bitfire.vcard4android import at.bitfire.vcard4android.property.CustomScribes.registerCustomScribes import at.bitfire.vcard4android.property.XAbDate import ezvcard.VCardVersion +import ezvcard.io.json.JCardReader import ezvcard.io.text.VCardReader import ezvcard.property.* import org.apache.commons.lang3.builder.HashCodeBuilder @@ -19,7 +20,6 @@ import java.io.IOException import java.io.OutputStream import java.io.Reader import java.util.* -import kotlin.collections.HashSet /** * Data class for a contact; between vCards and the Android contacts provider. @@ -84,8 +84,6 @@ class Contact { // You may set this statically from the calling application. var productID: String? = null - const val LABEL_GROUP_PREFIX = "item" - const val DATE_PARAMETER_OMIT_YEAR = "X-APPLE-OMIT-YEAR" const val DATE_PARAMETER_OMIT_YEAR_DEFAULT = 1604 @@ -94,15 +92,24 @@ class Contact { * * @param reader reader for the input stream containing the vCard (pay attention to the charset) * @param downloader will be used to download external resources like contact photos (may be null) + * @param jCard *true*: content is jCard; *false*: content is vCard + * * @return list of filled Event data objects (may have size 0) – doesn't return null + * * @throws IOException on I/O errors when reading the stream * @throws ezvcard.io.CannotParseException when the vCard can't be parsed */ - fun fromReader(reader: Reader, downloader: Downloader?): List<Contact> { - // create new vCard reader and add custom scribes - val vCards = VCardReader(reader, VCardVersion.V3_0) // CardDAV requires vCard 3 or newer - .registerCustomScribes() - .readAll() + fun fromReader(reader: Reader, jCard: Boolean, downloader: Downloader?): List<Contact> { + // create new reader and add custom scribes + val vCards = + if (jCard) + JCardReader(reader) + .registerCustomScribes() + .readAll() + else + VCardReader(reader, VCardVersion.V3_0) // CardDAV requires vCard 3 or newer + .registerCustomScribes() + .readAll() return vCards.map { vCard -> // convert every vCard to a Contact data object @@ -114,9 +121,15 @@ class Contact { @Throws(IOException::class) + fun writeJCard(os: OutputStream) { + val generator = ContactWriter.fromContact(this, VCardVersion.V4_0) + generator.writeCard(os, true) + } + + @Throws(IOException::class) fun writeVCard(vCardVersion: VCardVersion, os: OutputStream) { val generator = ContactWriter.fromContact(this, vCardVersion) - generator.writeVCard(os) + generator.writeCard(os, false) } diff --git a/src/main/java/at/bitfire/vcard4android/ContactWriter.kt b/src/main/java/at/bitfire/vcard4android/ContactWriter.kt index c12a7e3..aa967e6 100644 --- a/src/main/java/at/bitfire/vcard4android/ContactWriter.kt +++ b/src/main/java/at/bitfire/vcard4android/ContactWriter.kt @@ -5,6 +5,7 @@ import at.bitfire.vcard4android.property.CustomScribes.registerCustomScribes import ezvcard.Ezvcard import ezvcard.VCard import ezvcard.VCardVersion +import ezvcard.io.json.JCardWriter import ezvcard.io.text.VCardWriter import ezvcard.parameter.ImageType import ezvcard.parameter.RelatedType @@ -313,7 +314,44 @@ class ContactWriter private constructor(val contact: Contact, val version: VCard } - fun writeVCard(stream: OutputStream) { + /** + * Validates and writes the vCard to an output stream. + * + * @param stream target output stream + * @param jCard *true*: write as jCard; *false*: write as vCard + */ + fun writeCard(stream: OutputStream, jCard: Boolean) { + validate() + + val writer = + if (jCard) + JCardWriter(stream).apply { + isAddProdId = Contact.productID == null + registerCustomScribes() + + // allow properties that are not defined in this vCard version + isVersionStrict = false + } + else + VCardWriter(stream, version).apply { + isAddProdId = Contact.productID == null + registerCustomScribes() + + // include trailing semicolons for maximum compatibility + isIncludeTrailingSemicolons = true + + // use caret encoding for parameter values (RFC 6868) + isCaretEncodingEnabled = true + + // allow properties that are not defined in this vCard version + isVersionStrict = false + } + + writer.write(vCard) + writer.flush() + } + + private fun validate() { // validate vCard and log results val validation = vCard.validate(version) if (!validation.isEmpty) { @@ -322,21 +360,6 @@ class ContactWriter private constructor(val contact: Contact, val version: VCard msgs += " * " + key?.javaClass?.simpleName + " - " + warnings?.joinToString(" | ") Constants.log.log(Level.WARNING, "vCard validation warnings", msgs.joinToString(",")) } - - val writer = VCardWriter(stream, version).apply { - isAddProdId = Contact.productID == null - registerCustomScribes() - - // include trailing semicolons for maximum compatibility - isIncludeTrailingSemicolons = true - - // use caret encoding for parameter values (RFC 6868) - isCaretEncodingEnabled = true - - isVersionStrict = false - } - writer.write(vCard) - writer.flush() } }
\ No newline at end of file diff --git a/src/main/java/at/bitfire/vcard4android/property/CustomScribes.kt b/src/main/java/at/bitfire/vcard4android/property/CustomScribes.kt index 640e657..0fda8a6 100644 --- a/src/main/java/at/bitfire/vcard4android/property/CustomScribes.kt +++ b/src/main/java/at/bitfire/vcard4android/property/CustomScribes.kt @@ -1,6 +1,8 @@ package at.bitfire.vcard4android.property import ezvcard.io.chain.ChainingTextWriter +import ezvcard.io.json.JCardReader +import ezvcard.io.json.JCardWriter import ezvcard.io.scribe.ScribeIndex import ezvcard.io.text.VCardReader import ezvcard.io.text.VCardWriter @@ -27,15 +29,25 @@ object CustomScribes { return this } - fun VCardReader.registerCustomScribes(): VCardReader { + fun ScribeIndex.registerCustomScribes() { for (scribe in customScribes) - scribeIndex.register(scribe) + register(scribe) + } + + fun JCardReader.registerCustomScribes(): JCardReader { + scribeIndex.registerCustomScribes() return this } - fun VCardWriter.registerCustomScribes() { - for (scribe in customScribes) - scribeIndex.register(scribe) + fun JCardWriter.registerCustomScribes() = + scribeIndex.registerCustomScribes() + + fun VCardReader.registerCustomScribes(): VCardReader { + scribeIndex.registerCustomScribes() + return this } + fun VCardWriter.registerCustomScribes() = + scribeIndex.registerCustomScribes() + } diff --git a/src/test/java/at/bitfire/vcard4android/ContactTest.kt b/src/test/java/at/bitfire/vcard4android/ContactTest.kt index 786ad30..0218808 100644 --- a/src/test/java/at/bitfire/vcard4android/ContactTest.kt +++ b/src/test/java/at/bitfire/vcard4android/ContactTest.kt @@ -28,13 +28,13 @@ class ContactTest { private fun parseContact(fname: String, charset: Charset = Charsets.UTF_8) = javaClass.classLoader!!.getResourceAsStream(fname).use { stream -> - Contact.fromReader(InputStreamReader(stream, charset), null).first() + Contact.fromReader(InputStreamReader(stream, charset), false, null).first() } private fun regenerate(c: Contact, vCardVersion: VCardVersion): Contact { val os = ByteArrayOutputStream() c.writeVCard(vCardVersion, os) - return Contact.fromReader(InputStreamReader(ByteArrayInputStream(os.toByteArray()), Charsets.UTF_8), null).first() + return Contact.fromReader(InputStreamReader(ByteArrayInputStream(os.toByteArray()), Charsets.UTF_8), false,null).first() } private fun toString(c: Contact, groupMethod: GroupMethod, vCardVersion: VCardVersion): String { diff --git a/src/test/java/at/bitfire/vcard4android/ContactWriterTest.kt b/src/test/java/at/bitfire/vcard4android/ContactWriterTest.kt index 0b0a19e..c242eb2 100644 --- a/src/test/java/at/bitfire/vcard4android/ContactWriterTest.kt +++ b/src/test/java/at/bitfire/vcard4android/ContactWriterTest.kt @@ -538,6 +538,19 @@ class ContactWriterTest { @Test + fun testWriteJCard() { + val generator = ContactWriter.fromContact(Contact(), VCardVersion.V4_0) + generator.vCard.revision = Revision(Calendar.getInstance(TimeZone.getTimeZone(ZoneOffset.UTC.id)).apply { + set(2021, 6, 30, 1, 2, 3) + }) + + val stream = ByteArrayOutputStream() + generator.writeCard(stream, true) + assertEquals("[\"vcard\",[[\"version\",{},\"text\",\"4.0\"],[\"prodid\",{},\"text\",\"ez-vcard 0.11.3\"],[\"rev\",{},\"timestamp\",\"2021-07-30T01:02:03Z\"]]]", stream.toString()) + } + + + @Test fun testWriteVCard() { val generator = ContactWriter.fromContact(Contact(), VCardVersion.V4_0) generator.vCard.revision = Revision(Calendar.getInstance(TimeZone.getTimeZone(ZoneOffset.UTC.id)).apply { @@ -545,7 +558,7 @@ class ContactWriterTest { }) val stream = ByteArrayOutputStream() - generator.writeVCard(stream) + generator.writeCard(stream, false) assertEquals("BEGIN:VCARD\r\n" + "VERSION:4.0\r\n" + "PRODID:ez-vcard 0.11.3\r\n" + @@ -565,7 +578,7 @@ class ContactWriterTest { } ContactWriter .fromContact(contact, VCardVersion.V4_0) - .writeVCard(stream) + .writeCard(stream, false) assertTrue(stream.toString().contains("ADR;LABEL=\"Li^^ne 1,1 - ^' -\":;;Line1;;;;Line2")) } |