docs: function(input, callback, context) {
              var vcard = null;
  
              if(! context) {
                  context = this;
              }
  
              this.lex(input, function(key, value, attrs) {
                  function setAttr(val) {
                      if(vcard) {
                          vcard.addAttribute(key.toLowerCase(), val);
                      }
                  }
                  if(key == 'BEGIN') {
                      vcard = new VCard();
                  } else if(key == 'END') {
                      if(vcard) {
                          callback.apply(context, [vcard]);
                          vcard = null;
                      }
  
                  } else if(this.simpleKeys.indexOf(key) != -1) {
                      setAttr(value);
  
                  } else if(this.csvKeys.indexOf(key) != -1) {
                      setAttr(value.split(','));
  
                  } else if(this.dateAndOrTimeKeys.indexOf(key) != -1) {
                      if(attrs.VALUE == 'text') {
                          // times can be expressed as "text" as well,
                          // e.g. "ca 1800", "next week", ...
                          setAttr(value);
                      } else if(attrs.CALSCALE && attrs.CALSCALE != 'gregorian') {
                          // gregorian calendar is the only calscale mentioned
                          // in RFC 6350. I do not intend to support anything else
                          // (yet).
                      } else {
                          // FIXME: handle TZ attribute.
                          setAttr(this.parseDateAndOrTime(value));
                      }
  
                  } else if(key == 'N') { // 6.2.2
                      setAttr(this.parseName(value));
  
                  } else if(key == 'GENDER') { // 6.2.7
                      setAttr(this.parseGender(value));
  
                  } else if(key == 'TEL') { // 6.4.1
                      setAttr({
                          type: (attrs.TYPE || 'voice'),
                          pref: attrs.PREF,
                          value: value
                      });
  
                  } else if(key == 'EMAIL') { // 6.4.2
                      setAttr({
                          type: attrs.TYPE,
                          pref: attrs.PREF,
                          value: value
                      });
  
                  } else if(key == 'IMPP') { // 6.4.3
                      // RFC 6350 doesn't define TYPEs for IMPP addresses.
                      // It just seems odd to me to have multiple email addresses and phone numbers,
                      // but not multiple IMPP addresses.
                      setAttr({ value: value });
  
                  } else if(key == 'LANG') { // 6.4.4
                      setAttr({
                          type: attrs.TYPE,
                          pref: attrs.PREF,
                          value: value
                      });
  
                  } else if(key == 'TZ') { // 6.5.1
                      // neither hCard nor jCard mention anything about the TZ
                      // property, except that it's singular (which it is *not* in
                      // RFC 6350).
                      // using compound representation.
                      if(attrs.VALUE == 'utc-offset') {
                          setAttr({ 'utc-offset': this.parseTimezone(value) });
                      } else {
                          setAttr({ name: value });
                      }
  
                  } else if(key == 'ORG') { // 6.6.4
                      var parts = value.split(';');
                      setAttr({
                          'organization-name': parts[0],
                          'organization-unit': parts[1]
                      });
  
                  } else if(key == 'RELATED') { // 6.6.6
                      setAttr({
                          type: attrs.TYPE,
                          pref: attrs.PREF,
                          value: attrs.VALUE
                      });
  
                  } else if(key =='ADR'){
                      setAttr({
                          type: attrs.TYPE,
                          pref: attrs.PREF,
                          value: value
                      });
                      //TODO: Handle 'LABEL' field.
                  } else {
                      console.log('WARNING: unhandled key: ', key);
                  }
              });
          }