skarim / vobject

A full-featured Python package for parsing and creating iCalendar and vCard files
https://vobject.sameenkarim.com
258 stars 93 forks source link

j.add('org') creates a list instead of a string #64

Closed hubearth closed 7 years ago

hubearth commented 7 years ago

When creating a 'title' entry, for exemple, j.add('title') creates an object defined as this:

j.title -> ContentLine: <TITLE{}>
j.title.value -> str: ''

But, when creating a 'org' entry, j.add('org') creates an array like this:

j.org -> ContentLine: <ORG{}['']>
j.org.value -> <class 'list'>: ['']

So, this means that doing this: j.org.value = "My organization" you will get this: j.org.value -> <class 'list'>: ['M', 'y', ' ', 'o', 'r', 'g', 'a', 'n', 'i', 'z', 'a', 't', 'i', 'o', 'n']

I'm using python 3.4 vobject 0.9.4.1 IDE: LiClipse 3.3 running over Eclipse 4.6.1 OS: Debian 8.0

hubearth commented 7 years ago

Although I don't understand this part of the code, this behaviour is rooted in base.py in this function:

    def add(self, objOrName, group=None):

        if isinstance(objOrName, VBase):
            obj = objOrName
            if self.behavior:
                obj.parentBehavior = self.behavior
                obj.autoBehavior(True)
        else:
            name = objOrName.upper()
            try:
                id = self.behavior.knownChildren[name][2]
                behavior = getBehavior(name, id)
                if behavior.isComponent:
                    obj = Component(name)
                else:
                    obj = ContentLine(name, [], '', group)
                obj.parentBehavior = self.behavior
                obj.behavior = behavior
                obj = obj.transformToNative()
            except (KeyError, AttributeError):
                obj = ContentLine(objOrName, [], '', group)
            if obj.behavior is None and self.behavior is not None:
                if isinstance(obj, ContentLine):
                    obj.behavior = self.behavior.defaultBehavior
        self.contents.setdefault(obj.name.lower(), []).append(obj)
        return obj

With j.add('title'), it runs into try: and breaks on the first line id = self.behavior.knownChildren[name][2] so it jumps on the except statement.

With j.add('org'), it continues in the try without error.

hubearth commented 7 years ago

I did a pull request thinking that it would solve the problem, but it seems that I have a more complexe issue, maybe related to my IDE. I need to re-evaluate j.serialize() before running the line. Any idea if it's a problem with my setting or the library?

cecilphillip commented 7 years ago

I'm seeing this issue too

wpercy commented 7 years ago

Well, the cardinality of ORG is *, so I think that the max parameter is correctly None. I'll look into it.

wpercy commented 7 years ago

Yeah, after looking at the specs and some of the examples that Jeffery wrote, it looks like because ORG can have zero or more values (up to an infinite number) you want to always make it a list. To assign a single value to this list I would suggest using something like

j.org.value = ["My organization"]

where you give the org the value of your organization name as a single-element list.

wpercy commented 7 years ago

Okay, so after digging in the spec for a little while, I finally figured out why it behaves like this. When you add an ORG ContentLine, it is supposed to be a list, because each element of the list is supposed to be a more specific distinction within the organization.

From RFC 6350 Section 6.6.4:

Example: A property value consisting of an organizational name, organizational unit #1 name, and organizational unit #2 name.

            ORG:ABC\, Inc.;North American Division;Marketing

So I could create a vCard for myself using the following organizational distinctions:

>>> c = vobject.vCard()
>>> c.add('fn').value = "Will"
>>> c.add('org').value = ['Eventable', 'Engineering Team', 'Backend']
>>> print c.serialize()
BEGIN:VCARD
VERSION:3.0
FN:Will
ORG:Eventable;Engineering Team;Backend
END:VCARD

This is mildly inconvenient, but I believe it's necessary in order to match the spec.

It's also worth noting that if you have someone who is a member of multiple organizations, you should create multiple content lines according to RFC 5545 Section 3.1.2:

>>> c = vobject.vCard()
>>> c.add('fn').value = "Will"
>>> c.add('org').value = ['Eventable', 'Engineering Team', 'Backend']
>>> c.add('org').value = ['CalConnect']
>>> print c.serialize()
BEGIN:VCARD
VERSION:3.0
FN:Will
ORG:Eventable;Engineering Team;Backend
ORG:CalConnect
END:VCARD
Forflood commented 6 years ago

Little Tip: that kind of script is pretty good if you are working with constant : c.add('org').value = ['Eventable', 'Engineering Team', 'Backend'] If you want to set your first 'org' with variables you can do like this : c.add('org').value[0] = myVar

Hope this helps.