/**
  * parse component unparsed data into properties
  *
  * @author Kjell-Inge Gustafsson, kigkonsult <*****@*****.**>
  * @since 2.20.3 - 2015-03-05
  * @param mixed $unparsedtext   strict rfc2445 formatted, single property string or array of strings
  * @uses calendarComponent::getConfig()
  * @uses iCalUtilityFunctions::convEolChar()
  * @uses calendarComponent::$unparsed
  * @uses calendarComponent::$components
  * @uses calendarComponent::copy()
  * @uses iCalUtilityFunctions::_splitContent()
  * @uses calendarComponent::setProperty()
  * @uses iCalUtilityFunctions::_strunrep()
  * @uses calendarComponent::parse()
  * @return bool FALSE if error occurs during parsing
  */
 function parse($unparsedtext = null)
 {
     $nl = $this->getConfig('nl');
     if (!empty($unparsedtext)) {
         if (is_array($unparsedtext)) {
             $unparsedtext = implode('\\n' . $nl, $unparsedtext);
         }
         $unparsedtext = iCalUtilityFunctions::convEolChar($unparsedtext, $nl);
     } elseif (!isset($this->unparsed)) {
         $unparsedtext = array();
     } else {
         $unparsedtext = $this->unparsed;
     }
     /* skip leading (empty/invalid) lines */
     foreach ($unparsedtext as $lix => $line) {
         if (FALSE !== ($pos = stripos($line, 'BEGIN:'))) {
             $unparsedtext[$lix] = substr($unparsedtext[$lix], $pos);
             break;
         }
         $tst = trim($line);
         if ('\\n' == $tst || empty($tst)) {
             unset($unparsedtext[$lix]);
         }
     }
     $this->unparsed = array();
     $comp =& $this;
     $config = $this->getConfig();
     $compsync = $subsync = 0;
     foreach ($unparsedtext as $lix => $line) {
         if ('END:VALARM' == strtoupper(substr($line, 0, 10))) {
             if (1 != $subsync) {
                 return FALSE;
             }
             $this->components[] = $comp->copy();
             $subsync--;
         } elseif ('END:DAYLIGHT' == strtoupper(substr($line, 0, 12))) {
             if (1 != $subsync) {
                 return FALSE;
             }
             $this->components[] = $comp->copy();
             $subsync--;
         } elseif ('END:STANDARD' == strtoupper(substr($line, 0, 12))) {
             if (1 != $subsync) {
                 return FALSE;
             }
             array_unshift($this->components, $comp->copy());
             $subsync--;
         } elseif ('END:' == strtoupper(substr($line, 0, 4))) {
             // end:<component>
             if (1 != $compsync) {
                 return FALSE;
             }
             if (0 < $subsync) {
                 $this->components[] = $comp->copy();
             }
             $compsync--;
             break;
             /* skip trailing empty lines */
         } elseif ('BEGIN:VALARM' == strtoupper(substr($line, 0, 12))) {
             $comp = new valarm($config);
             $subsync++;
         } elseif ('BEGIN:STANDARD' == strtoupper(substr($line, 0, 14))) {
             $comp = new vtimezone('standard', $config);
             $subsync++;
         } elseif ('BEGIN:DAYLIGHT' == strtoupper(substr($line, 0, 14))) {
             $comp = new vtimezone('daylight', $config);
             $subsync++;
         } elseif ('BEGIN:' == strtoupper(substr($line, 0, 6))) {
             // begin:<component>
             $compsync++;
         } else {
             $comp->unparsed[] = $line;
         }
     }
     if (0 < $subsync) {
         $this->components[] = $comp->copy();
     }
     unset($config);
     /* concatenate property values spread over several lines */
     $lastix = -1;
     $propnames = array('action', 'attach', 'attendee', 'categories', 'comment', 'completed', 'contact', 'class', 'created', 'description', 'dtend', 'dtstart', 'dtstamp', 'due', 'duration', 'exdate', 'exrule', 'freebusy', 'geo', 'last-modified', 'location', 'organizer', 'percent-complete', 'priority', 'rdate', 'recurrence-id', 'related-to', 'repeat', 'request-status', 'resources', 'rrule', 'sequence', 'status', 'summary', 'transp', 'trigger', 'tzid', 'tzname', 'tzoffsetfrom', 'tzoffsetto', 'tzurl', 'uid', 'url', 'x-');
     $proprows = array();
     for ($i = 0; $i < count($this->unparsed); $i++) {
         // concatenate lines
         $line = rtrim($this->unparsed[$i], $nl);
         while (isset($this->unparsed[$i + 1]) && !empty($this->unparsed[$i + 1]) && ' ' == $this->unparsed[$i + 1][0]) {
             $line .= rtrim(substr($this->unparsed[++$i], 1), $nl);
         }
         $proprows[] = $line;
     }
     /* parse each property 'line' */
     foreach ($proprows as $line) {
         if ('\\n' == substr($line, -2)) {
             $line = substr($line, 0, -2);
         }
         /* get propname */
         $propname = null;
         $cix = 0;
         while (isset($line[$cix])) {
             if (in_array($line[$cix], array(':', ';'))) {
                 break;
             } else {
                 $propname .= $line[$cix];
             }
             $cix++;
         }
         if ('x-' == substr($propname, 0, 2) || 'X-' == substr($propname, 0, 2)) {
             $propname2 = $propname;
             $propname = 'X-';
         }
         if (!in_array(strtolower($propname), $propnames)) {
             // skip non standard property names
             continue;
         }
         /* rest of the line is opt.params and value */
         $line = substr($line, $cix);
         /* separate attributes from value */
         iCalUtilityFunctions::_splitContent($line, $propAttr);
         /* call setProperty( $propname.. . */
         switch (strtoupper($propname)) {
             case 'ATTENDEE':
                 foreach ($propAttr as $pix => $attr) {
                     if (!in_array(strtoupper($pix), array('MEMBER', 'DELEGATED-TO', 'DELEGATED-FROM'))) {
                         continue;
                     }
                     $attr2 = explode(',', $attr);
                     if (1 < count($attr2)) {
                         $propAttr[$pix] = $attr2;
                     }
                 }
                 $this->setProperty($propname, $line, $propAttr);
                 break;
             case 'CATEGORIES':
             case 'RESOURCES':
                 if (FALSE !== strpos($line, ',')) {
                     $content = array(0 => '');
                     $cix = $lix = 0;
                     while (FALSE !== substr($line, $lix, 1)) {
                         if (',' == $line[$lix] && "\\" != $line[$lix - 1]) {
                             $cix++;
                             $content[$cix] = '';
                         } else {
                             $content[$cix] .= $line[$lix];
                         }
                         $lix++;
                     }
                     if (1 < count($content)) {
                         $content = array_values($content);
                         foreach ($content as $cix => $contentPart) {
                             $content[$cix] = iCalUtilityFunctions::_strunrep($contentPart);
                         }
                         $this->setProperty($propname, $content, $propAttr);
                         break;
                     } else {
                         $line = reset($content);
                     }
                 }
             case 'COMMENT':
             case 'CONTACT':
             case 'DESCRIPTION':
             case 'LOCATION':
             case 'SUMMARY':
                 if (empty($line)) {
                     $propAttr = null;
                 }
                 $this->setProperty($propname, iCalUtilityFunctions::_strunrep($line), $propAttr);
                 break;
             case 'REQUEST-STATUS':
                 $values = explode(';', $line, 3);
                 $values[1] = !isset($values[1]) ? null : iCalUtilityFunctions::_strunrep($values[1]);
                 $values[2] = !isset($values[2]) ? null : iCalUtilityFunctions::_strunrep($values[2]);
                 $this->setProperty($propname, $values[0], $values[1], $values[2], $propAttr);
                 break;
             case 'FREEBUSY':
                 $fbtype = isset($propAttr['FBTYPE']) ? $propAttr['FBTYPE'] : '';
                 // force setting default, if missing
                 unset($propAttr['FBTYPE']);
                 $values = explode(',', $line);
                 foreach ($values as $vix => $value) {
                     $value2 = explode('/', $value);
                     if (1 < count($value2)) {
                         $values[$vix] = $value2;
                     }
                 }
                 $this->setProperty($propname, $fbtype, $values, $propAttr);
                 break;
             case 'GEO':
                 $value = explode(';', $line, 2);
                 if (2 > count($value)) {
                     $value[1] = null;
                 }
                 $this->setProperty($propname, $value[0], $value[1], $propAttr);
                 break;
             case 'EXDATE':
                 $values = !empty($line) ? explode(',', $line) : null;
                 $this->setProperty($propname, $values, $propAttr);
                 break;
             case 'RDATE':
                 if (empty($line)) {
                     $this->setProperty($propname, $line, $propAttr);
                     break;
                 }
                 $values = explode(',', $line);
                 foreach ($values as $vix => $value) {
                     $value2 = explode('/', $value);
                     if (1 < count($value2)) {
                         $values[$vix] = $value2;
                     }
                 }
                 $this->setProperty($propname, $values, $propAttr);
                 break;
             case 'EXRULE':
             case 'RRULE':
                 $values = explode(';', $line);
                 $recur = array();
                 foreach ($values as $value2) {
                     if (empty($value2)) {
                         continue;
                     }
                     // ;-char in ending position ???
                     $value3 = explode('=', $value2, 2);
                     $rulelabel = strtoupper($value3[0]);
                     switch ($rulelabel) {
                         case 'BYDAY':
                             $value4 = explode(',', $value3[1]);
                             if (1 < count($value4)) {
                                 foreach ($value4 as $v5ix => $value5) {
                                     $value6 = array();
                                     $dayno = $dayname = null;
                                     $value5 = trim((string) $value5);
                                     if (ctype_alpha(substr($value5, -1)) && ctype_alpha(substr($value5, -2, 1))) {
                                         $dayname = substr($value5, -2, 2);
                                         if (2 < strlen($value5)) {
                                             $dayno = substr($value5, 0, strlen($value5) - 2);
                                         }
                                     }
                                     if ($dayno) {
                                         $value6[] = $dayno;
                                     }
                                     if ($dayname) {
                                         $value6['DAY'] = $dayname;
                                     }
                                     $value4[$v5ix] = $value6;
                                 }
                             } else {
                                 $value4 = array();
                                 $dayno = $dayname = null;
                                 $value5 = trim((string) $value3[1]);
                                 if (ctype_alpha(substr($value5, -1)) && ctype_alpha(substr($value5, -2, 1))) {
                                     $dayname = substr($value5, -2, 2);
                                     if (2 < strlen($value5)) {
                                         $dayno = substr($value5, 0, strlen($value5) - 2);
                                     }
                                 }
                                 if ($dayno) {
                                     $value4[] = $dayno;
                                 }
                                 if ($dayname) {
                                     $value4['DAY'] = $dayname;
                                 }
                             }
                             $recur[$rulelabel] = $value4;
                             break;
                         default:
                             $value4 = explode(',', $value3[1]);
                             if (1 < count($value4)) {
                                 $value3[1] = $value4;
                             }
                             $recur[$rulelabel] = $value3[1];
                             break;
                     }
                     // end - switch $rulelabel
                 }
                 // end - foreach( $values.. .
                 $this->setProperty($propname, $recur, $propAttr);
                 break;
             case 'X-':
                 $propname = isset($propname2) ? $propname2 : $propname;
                 unset($propname2);
             case 'ACTION':
             case 'CLASSIFICATION':
             case 'STATUS':
             case 'TRANSP':
             case 'UID':
             case 'TZID':
             case 'RELATED-TO':
             case 'TZNAME':
                 $line = iCalUtilityFunctions::_strunrep($line);
             default:
                 $this->setProperty($propname, $line, $propAttr);
                 break;
         }
         // end  switch( $propname.. .
     }
     // end - foreach( $proprows.. .
     unset($unparsedtext, $this->unparsed, $proprows);
     if (isset($this->components) && is_array($this->components) && 0 < count($this->components)) {
         $ckeys = array_keys($this->components);
         foreach ($ckeys as $ckey) {
             if (!empty($this->components[$ckey]) && !empty($this->components[$ckey]->unparsed)) {
                 $this->components[$ckey]->parse();
             }
         }
     }
     return TRUE;
 }