Location: PHPKode > projects > PhpiCalLib > contentline.php
<?php
/**
 * contentline.php: iCalendar contentline class
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * @copyright Copyright (C) 2008 Nigel Swinson, hide@address.com 
 * @author  Nigel Swinson
 * @version 1.0
 */

require_once 'parameterfactory.php';

$iPHPICALLIB_PropertyIndex = 1;
define('PHPICALLIB_PROPERTY_BEGIN', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_END', $iPHPICALLIB_PropertyIndex++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.7
//3.7. Calendar Properties
define('PHPICALLIB_PROPERTY_CALSCALE', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_METHOD', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_PRODID', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_VERSION', $iPHPICALLIB_PropertyIndex++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.1
//3.8.1. Descriptive Component Properties
define('PHPICALLIB_PROPERTY_ATTACH', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_CATEGORIES', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_CLASS', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_COMMENT', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_DESCRIPTION', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_GEO', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_LOCATION', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_PERCENT_COMPLETE', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_PRIORITY', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_RESOURCES', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_STATUS', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_SUMMARY', $iPHPICALLIB_PropertyIndex++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.2
//3.8.2. Date and Time Component Properties
define('PHPICALLIB_PROPERTY_COMPLETED', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_DTEND', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_DUE', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_DTSTART', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_DURATION', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_FREEBUSY', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_TRANSP', $iPHPICALLIB_PropertyIndex++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.3
//3.8.3. Time Zone Component Properties
define('PHPICALLIB_PROPERTY_TZID', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_TZNAME', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_TZOFFSETFROM', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_TZOFFSETTO', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_TZURL', $iPHPICALLIB_PropertyIndex++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.4
//3.8.4. Relationship Component Properties
define('PHPICALLIB_PROPERTY_ATTENDEE', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_CONTACT', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_ORGANIZER', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_RECURRENCE_ID', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_RELATED_TO', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_URL', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_UID', $iPHPICALLIB_PropertyIndex++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.5
//3.8.5. Recurrence Component Properties
define('PHPICALLIB_PROPERTY_EXDATE', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_RDATE', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_RRULE', $iPHPICALLIB_PropertyIndex++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.6
//3.8.6. Alarm Component Properties
define('PHPICALLIB_PROPERTY_ACTION', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_REPEAT', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_TRIGGER', $iPHPICALLIB_PropertyIndex++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.7
//3.8.7. Change Management Component Properties
define('PHPICALLIB_PROPERTY_CREATED', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_DTSTAMP', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_LASTMODIFIED', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_SEQUENCE', $iPHPICALLIB_PropertyIndex++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.8
//3.8.8. Miscellaneous Component Properties
define('PHPICALLIB_PROPERTY_IANAPROP', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_XPROP', $iPHPICALLIB_PropertyIndex++);
define('PHPICALLIB_PROPERTY_REQUEST_STATUS', $iPHPICALLIB_PropertyIndex++);
// If you add another, then be sure to amend the PhpiCalLib_ContentLine::TypeMap as well
//define('PHPICALLIB_PROPERTY_', $iPHPICALLIB_PropertyIndex++);


/**
 * Represents a contentline token.  
 * 
 * ContentLines in iCalendar mostly represent properties, but sometimes indicate the start/end
 * of a component.
 */
class PhpiCalLib_ContentLine {
	/**
	 * The "name" token
	 * @var string
	 */
	protected $Name = '';
	/**
	 * An enumerated version of $Name
	 * @var PHPICALLIB_PROPERTY_? 
	 */
	protected $Type = 0;
	/**
	 * The "param" tokens
	 * @var array 
	 */
	protected $aParameters = null;
	/**
	 * The "value" token
	 * @var string 
	 */
	protected $EncodedValue = '';
	/**
	 * Info about which parameters are permitted for this property
	 *
	 * Empty array means all parameters are permitted.  Each entry is keyed
	 * by id.  The values indicate the min and max number of instances.  For
	 * example, the spec says this for ATTENDEE.
	 * {@link http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.4.1}
	 * <pre>
	 *        attparam   = *(
	 *
	 *                   ; the following are OPTIONAL,
	 *                   ; but MUST NOT occur more than once
	 *                   (";" cutypeparam) / (";" memberparam) /
	 *                   (";" roleparam) / (";" partstatparam) /
	 *                   (";" rsvpparam) / (";" deltoparam) /
	 *                   (";" delfromparam) / (";" sentbyparam) /
	 *                   (";" cnparam) / (";" dirparam) /
	 *                   (";" languageparam) /
	 *
	 *                   ; the following is OPTIONAL,
	 *                   ; and MAY occur more than once
	 *                   (";" other-param)
	 *
	 *                   )
	 * </pre>
	 * 
	 * This would be implemented as:
	 * 
	 * <code>
	 * $this->aPermittedParameters = array(
	 *				//                   ; the following are OPTIONAL,
	 *				//                   ; but MUST NOT occur more than once
	 *				//                   (";" cutypeparam) / (";" memberparam) /
	 *				//                   (";" roleparam) / (";" partstatparam) /
	 *				//                   (";" rsvpparam) / (";" deltoparam) /
	 *				//                   (";" delfromparam) / (";" sentbyparam) /
	 *				//                   (";" cnparam) / (";" dirparam) /
	 *				//                   (";" languageparam) /
	 *				PHPICALLIB_PARAMETER_CUTYPE => array(0,1),
	 *				PHPICALLIB_PARAMETER_MEMBER => array(0,1),
	 *				PHPICALLIB_PARAMETER_ROLE => array(0,1),
	 *				PHPICALLIB_PARAMETER_PARTSTAT => array(0,1),
	 *				PHPICALLIB_PARAMETER_RSVP => array(0,1),
	 *				PHPICALLIB_PARAMETER_DELEGATED_TO => array(0,1),
	 *				PHPICALLIB_PARAMETER_DELEGATED_FROM => array(0,1),
	 *				PHPICALLIB_PARAMETER_SENT_BY => array(0,1),
	 *				PHPICALLIB_PARAMETER_CN => array(0,1),
	 *				PHPICALLIB_PARAMETER_DIR => array(0,1),
	 *				PHPICALLIB_PARAMETER_LANGUAGE => array(0,1),
	 *				//                   ; the following is OPTIONAL,
	 *				//                   ; and MAY occur more than once
	 *				//                   (";" other-param)
	 *				PHPICALLIB_PARAMETER_IANAPARAM => array(0,-1),
	 *				PHPICALLIB_PARAMETER_XPARAM => array(0,-1),
	 * </code>
	 * 
	 * Note that this mechanism isn't sufficient to ensure the property only has valid
	 * parameters, for example two parameters may be mutually exclusive.  Where that is the
	 * case the user should override (@link AddParameterPermitted()}, {@link RemoveParameterPermitted} or
	 * {@link SetParameterPermitted} to make sure that any remaining rules have been followed.
	 * 
	 * @var array Keyed by PHPICALLIB_PARAMETER_* ids, value is an array(mininstances, maxinstances)
	 */
	protected $aPermittedParameters = array();
	
	/**
	 * Regex to capture validate an iana-token
	 * @link http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.1
	 *      iana-token    = 1*(ALPHA / DIGIT / "-")
	 * 
	 * @var string
	 */
	public static $IanaTokenRegex = '[a-zA-Z0-9-]+';
	
	/**
	 * Regex to validate a name token
	 * A quick regular expression to validate the name token
	 * ==>
	 *      name          = iana-token / x-name
	 *      iana-token    = 1*(ALPHA / DIGIT / "-")
	 *      x-name        = "X-" [vendorid "-"] 1*(ALPHA / DIGIT / "-")
	 *      vendorid      = 3*(ALPHA / DIGIT)
	 * ==>
	 *      name          = 1*(ALPHA / DIGIT / "-") 
	 *                    / "X-" [3*(ALPHA / DIGIT) "-"] 1*(ALPHA / DIGIT / "-")
	 * ==>
	 *      name          = 1*(ALPHA / DIGIT / "-") 
	 * @var string
	 */
	public static $NameSplitRegex = '[a-zA-Z0-9-]+';

	/**
	 * Map a property type to it's string property name.
	 *
	 * @var array Keyed by PHPICALLIB_PROPERTY_?, value is a string
	 */
	private static $TypeMap = array(
			PHPICALLIB_PROPERTY_BEGIN => 'BEGIN',
			PHPICALLIB_PROPERTY_END => 'END',
			//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.7
			//3.7. Calendar Properties
			PHPICALLIB_PROPERTY_CALSCALE => 'CALSCALE',
			PHPICALLIB_PROPERTY_METHOD => 'METHOD',
			PHPICALLIB_PROPERTY_PRODID => 'PRODID',
			PHPICALLIB_PROPERTY_VERSION => 'VERSION',
			//http => //tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.1
			//3.8.1. Descriptive Component Properties
			PHPICALLIB_PROPERTY_ATTACH => 'ATTACH',
			PHPICALLIB_PROPERTY_CATEGORIES => 'CATEGORIES',
			PHPICALLIB_PROPERTY_CLASS => 'CLASS',
			PHPICALLIB_PROPERTY_COMMENT => 'COMMENT',
			PHPICALLIB_PROPERTY_DESCRIPTION => 'DESCRIPTION',
			PHPICALLIB_PROPERTY_GEO => 'GEO',
			PHPICALLIB_PROPERTY_LOCATION => 'LOCATION',
			PHPICALLIB_PROPERTY_PERCENT_COMPLETE => 'PERCENT-COMPLETE',
			PHPICALLIB_PROPERTY_PRIORITY => 'PRIORITY',
			PHPICALLIB_PROPERTY_RESOURCES => 'RESOURCES',
			PHPICALLIB_PROPERTY_STATUS => 'STATUS',
			PHPICALLIB_PROPERTY_SUMMARY => 'SUMMARY',
			//http => //tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.2
			//3.8.2. Date and Time Component Properties
			PHPICALLIB_PROPERTY_COMPLETED => 'COMPLETED',
			PHPICALLIB_PROPERTY_DTEND => 'DTEND',
			PHPICALLIB_PROPERTY_DUE => 'DUE',
			PHPICALLIB_PROPERTY_DTSTART => 'DTSTART',
			PHPICALLIB_PROPERTY_DURATION => 'DURATION',
			PHPICALLIB_PROPERTY_FREEBUSY => 'FREEBUSY',
			PHPICALLIB_PROPERTY_TRANSP => 'TRANSP',
			//http => //tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.3
			//3.8.3. Time Zone Component Properties
			PHPICALLIB_PROPERTY_TZID => 'TZID',
			PHPICALLIB_PROPERTY_TZNAME => 'TZNAME',
			PHPICALLIB_PROPERTY_TZOFFSETFROM => 'TZOFFSETFROM',
			PHPICALLIB_PROPERTY_TZOFFSETTO => 'TZOFFSETTO',
			PHPICALLIB_PROPERTY_TZURL => 'TZURL',
			//http => //tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.4
			//3.8.4. Relationship Component Properties
			PHPICALLIB_PROPERTY_ATTENDEE => 'ATTENDEE',
			PHPICALLIB_PROPERTY_CONTACT => 'CONTACT',
			PHPICALLIB_PROPERTY_ORGANIZER => 'ORGANIZER',
			PHPICALLIB_PROPERTY_RECURRENCE_ID => 'RECURRENCE-ID',
			PHPICALLIB_PROPERTY_RELATED_TO => 'RELATED-TO',
			PHPICALLIB_PROPERTY_URL => 'URL',
			PHPICALLIB_PROPERTY_UID => 'UID',
			//http => //tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.5
			//3.8.5. Recurrence Component Properties
			PHPICALLIB_PROPERTY_EXDATE => 'EXDATE',
			PHPICALLIB_PROPERTY_RDATE => 'RDATE',
			PHPICALLIB_PROPERTY_RRULE => 'RRULE',
			//http => //tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.6
			//3.8.6. Alarm Component Properties
			PHPICALLIB_PROPERTY_ACTION => 'ACTION',
			PHPICALLIB_PROPERTY_REPEAT => 'REPEAT',
			PHPICALLIB_PROPERTY_TRIGGER => 'TRIGGER',
			//http => //tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.7
			//3.8.7. Change Management Component Properties
			PHPICALLIB_PROPERTY_CREATED => 'CREATED',
			PHPICALLIB_PROPERTY_DTSTAMP => 'DTSTAMP',
			PHPICALLIB_PROPERTY_LASTMODIFIED => 'LASTMODIFIED',
			PHPICALLIB_PROPERTY_SEQUENCE => 'SEQUENCE',
			PHPICALLIB_PROPERTY_REQUEST_STATUS => 'REQUEST-STATUS');
	// array_flip() on $TypeMap, built on first use.
	private static $ReverseTypeMap = null;
	
	/**
	 * The parameter factory
	 *
	 * @var PhpiCalLib_ParameterFactory
	 */
	private $ParameterFactory = null;
	
	/**
	 * Constructor
	 *
	 */
	public function __construct() {
		$this->ParameterFactory = new PhpiCalLib_ParameterFactory();
	}

	/**
	 * Copy from the other object
	 *
	 * @param PhpiCalLib_ContentLine $ContentLine
	 */
	public function Copy($Other) {
		$this->SetName($Other->Name);
		$this->aParameters = null;
		if (!empty($Other->aParameters)) {
			$this->aParameters = $Other->aParameters;
		}
		$this->SetEncodedValue($Other->EncodedValue);
	}

	/**
	 * Convert a string to a list of content lines
	 *
	 * @param string $String A string of iCalendar data that represents a set of content lines
	 * @return array An ordered list of content lines
	 */
	public function ToContentLines($iCalString) {
		//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.1
		//      contentline   = name *(";" param ) ":" value CRLF
		//      ; This ABNF is just a general definition for an initial parsing
		//      ; of the content line into its property name, parameter list,
		//      ; and value string
		//
		//      ; When parsing a content line, folded lines MUST first
		//      ; be unfolded according to the unfolding procedure
		//      ; described above. When generating a content line, lines
		//      ; longer than 75 octets SHOULD be folded according to
		//      ; the folding procedure described above.
		if (empty($iCalString)) return array();
		if (!preg_match("/\r\n$/D", $iCalString)) {
			throw new PhpiCalLib_ParseException(sprintf('Last line doesn\'t end with \r\n:%s', substr($iCalString, -100, 100)));
		}
		$aFoldedContentLines = preg_split("/\r\n/", $iCalString);
		$aUnfoldedContentLines = array();
		$iLineNumber = 0;
		foreach ($aFoldedContentLines as $FoldedContentLine) {
			if (empty($FoldedContentLine)) {
				// Technically not allowed, so we treat as the EOF
				//throw new PhpiCalLib_ParseException(sprintf('Line %d: Empty content lines are not allowed', $iLineNumber));
				break;
			}
			
			switch ($FoldedContentLine[0]) {
				case ' ':
				case "\t":
					// It's a continuation line.  Check we have a line to continue
					if (empty($aUnfoldedContentLines)) {
						throw new PhpiCalLib_ParseException(sprintf('Line %d: Invalid continution line, no previous line to continue', $iLineNumber));
					}
					$aUnfoldedContentLines[] = array_pop($aUnfoldedContentLines) . substr($FoldedContentLine, 1);
					break;
				default:
					// Append to the existing lines
					$aUnfoldedContentLines[] = $FoldedContentLine;
			}
			$iLineNumber++;
		}
		
		// Now convert unfolded lines to content lines (properties)
		$aContentLines = array();
		$iContentLineNumber = 0;
		foreach ($aUnfoldedContentLines as $UnfoldedContentLine) {
			try {
				$aContentLine = $this->Create($UnfoldedContentLine);
			} catch (PhpiCalLib_Exception $Exception) {
				$Exception->SetUnfoldedContentLine($iContentLineNumber);
				throw $Exception;
			}
			$aContentLines[] = $aContentLine;
			$iContentLineNumber++;
		}
		
		return $aContentLines;
	}

	/**
	 * Convert a property name to it's defined type
	 *
	 * @param string $Name a "name" RFC2445 token
	 * @return int one of the PHPICALLIB_PROPERTY_* properties
	 */
	static public function ToPropertyType($Name) {
		// Build the type map on first access
		if (!self::$ReverseTypeMap) {
			self::$ReverseTypeMap = array_flip(self::$TypeMap);
		}
		// If we have a key for this name, return the unique type
		if (isset(self::$ReverseTypeMap[$Name])) {
			return self::$ReverseTypeMap[$Name];
		}
		// It must be an iana defined extension or x-prop that we don't really recognize.
		if (preg_match('/^X-.*$/', $Name)) { 
			return PHPICALLIB_PROPERTY_XPROP;
		}
		return PHPICALLIB_PROPERTY_IANAPROP;
	}
	
	/**
	 * Convert a property type to it's defined name
	 *
	 * @param integer $Type one of the PHPICALLIB_PROPERTY_* properties
	 * @return string A "name" RFC2445 token
	 */
	static public function ToPropertyName($Type) {
		if (empty(self::$TypeMap[$Type])) return '';
		return self::$TypeMap[$Type];
	}
	
	/**
	 * Get the name of the property
	 */
	public function GetName() {
		if (empty($this->Name)) {
			throw new PhpiCalLib_Exception('No name assigned to this parameter yet');
		}
		return $this->Name;
	}

	/**
	 * Set the new name for the property
	 *
	 * @param string $Name
	 */
	public function SetName($Name) {
		// Further parse the name before storage.
		//      name          = iana-token / x-name
		//      iana-token    = 1*(ALPHA / DIGIT / "-")
		//      x-name        = "X-" [vendorid "-"] 1*(ALPHA / DIGIT / "-")
		$XNameRegex = 'X-([a-zA-Z0-9]{3}-)?([a-zA-Z0-9-]+)';
		//      vendorid      = 3*(ALPHA / DIGIT)
		// Note an invalid x-name becomes an iana-token, so we just check for valid iana-tokens
		// iana-token
		$Pattern = sprintf("/^%s$/D", self::$IanaTokenRegex);
		if (!preg_match($Pattern, $Name)) {
			throw new PhpiCalLib_ParameterException(sprintf("Invalid iana-token (%s) in property name: %s", $Pattern, $Name));
		}
		
		if (strlen($Name) > 2 && substr($Name, 0, 2) == "X-") {
			// x-name
			$this->Type = PHPICALLIB_PROPERTY_XPROP;
			$this->Name = $Name;
		} else {
			// iana-token
			$this->Name = $Name;
			$this->Type = self::ToPropertyType($Name);
		}
	}

	/**
	 * Access the property type
	 * 
	 * Note this will return PHPICALLIB_PROPERTY_IANAPROP or PHPICALLIB_PROPERTY_XPROP for properties 
	 * that it doesn't recognize.
	 *
	 * @return PHPICALLIB_PROPERTY_*
	 */
	public function GetType() {
		return $this->Type;
	}

	/**
	 * Set the type of the content line
	 *
	 * @param integer $Type
	 */
	public function SetType($Type) {
		// Check they aren't trying to set this to an x-prop or an iana-prop
		switch ($Type) {
			//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.8.8
			//3.8.8. Miscellaneous Component Properties
			case PHPICALLIB_PROPERTY_IANAPROP:
			case PHPICALLIB_PROPERTY_XPROP:
				throw new PhpiCalLib_Exception('Can\'t set contentline type to iana-prop or x-prop.  Use SetName instead');
		}
		// Pull out the name, note if we ever add a PHPICALLIB_PROPERTY_? and forget to ament $TypeMap, then
		// this can start to fail
		$this->Name = self::$TypeMap[$Type];
		// Store the type.
		$this->Type = $Type;
	}

	/**
	 * Access the parameters
	 *
	 * @param string $Name If not null, specifies the name of the properties to return.  If empty, all parameters
	 * 					will be returned
	 * @return array of PhpiCalLib_Parameter objects
	 */
	public function GetParameters($Type = null, $Name = null) {
		if (empty($Type)) {
			return empty($this->aParameters) ? array() : $this->aParameters;
		}

		$aParameters = array();
		foreach ($this->aParameters as $Parameter) {
			// Test the type
			if ($Parameter->GetType() != $Type) continue;
			// Also test the name if necessary
			switch ($Type) {
				case PHPICALLIB_PARAMETER_IANAPARAM:
				case PHPICALLIB_PARAMETER_XPARAM:
					// Should we filter by name too?
					if (empty($Name)) {
						$aParameters[] = $Parameter;
						break;
					} 
					// Must search by name
					if ($Parameter->GetName() == $Name) {
						$aParameters[] = $Parameter;
						break;
					}
					break;
				default:
					$aParameters[] = $Parameter;
			}
		}
		return $aParameters;
	}
	
	/**
	 * Get the parameter of the given name, returning a single PhpiCalLib object
	 * 
	 * If there are multiple parameters of the given name, it will just return the first
	 *
	 * @param string $Name The name of the parameter to return
	 * @return string PhpiCalLib_Parameter object, or null if the parameter value isn't specified
	 */
	public function GetParameter($Type, $Name = null) {
		if (empty($this->aParameters)) return null;
		
		foreach ($this->aParameters as $Parameter) {
			// Test the type
			if ($Parameter->GetType() != $Type) continue;
			// Also test the name if necessary
			switch ($Type) {
				case PHPICALLIB_PARAMETER_IANAPARAM:
				case PHPICALLIB_PARAMETER_XPARAM:
					// Should we filter by name too?
					if (empty($Name)) {
						return $Parameter;
					} 
					// Must search by name
					if ($Parameter->GetName() == $Name) {
						return $Parameter;
					}
					break;
				default:
					return $Parameter;
			}
		}
		// Not found.
		return null;
	}
	
	/**
	 * Set the parameters
	 *
	 * @param array $aParameters
	 */
	public function SetParameters($aParameters) {
		// Forget all the existing parameters
		$this->aParameters = array();
		// Call AddParameter to make sure each parameter is validated before addition
		foreach ($aParameters as $aParameter) {
			$this->AddParameter($aParameter);
		}
		// Check that we have all the parameters that were REQUIRED
		if (!empty($this->aPermittedParameters)) {
			foreach ($this->aPermittedParameters as $Type => $aTypeInfo) {
				// If there's no lower limit, or a lower limit of 0, then no min threshold to check.
				if ($aTypeInfo[0] <= 0) continue;
				// Get all the properties of the give type
				$aParameters = $this->GetParameters($Type);
				// Check we have at least enough instances of the property
				if (count($aParameters) < $aTypeInfo[0]) {
					throw new PhpiCalLib_ParameterException(
								sprintf("The %s parameters must occur %d times in the %s property and it only occurs %d times",
									self::$TypeMap[$Type],
									$aTypeInfo[0],
									$this->Name,
									count($aParameter)));
				} 
			}
		}
	}
	
	/**
	 * Removes all existing parameters of the given name, and adds the given parameter
	 *
	 * @param PhpiCalLib_Parameter $Parameter
	 */
	public function SetParameter($Parameter) {
		// Check we are allowed to set this property
		$this->SetParameterPermitted($Parameter);
		// Remove it's current values, calling the private version to avoid
		// failing if this property has to have at least one value.
		$this->_RemoveParameter($Parameter->GetType(), $Parameter->GetName());
		// Make sure we have a parameters collection
		if (!$this->aParameters)
			$this->aParameters = array();
		// Add the new value
		$this->aParameters[] = $Parameter;
	}
	
	/**
	 * Add a new parameter
	 *
	 * @param PhpiCalLib_Parameter $Parameter
	 */
	public function AddParameter($Parameter) {
		// Check we are allowed to add this parameter
		$this->AddParameterPermitted($Parameter);
		// Make sure we have a parameters collection
		if (!$this->aParameters)
			$this->aParameters = array();
		// Add the new value
		$this->aParameters[] = $Parameter;
	}
	
	/**
	 * Removes all parameters of the given name
	 *
	 * @param string $Name
	 */
	public function RemoveParameter($Type, $Name = null) {
		// If there's no parameters yet, this is easy
		if (!$this->aParameters) return;
		// Check we are allowed to remove this property
		$this->RemoveParameterPermitted($Type, $Name);
		$this->_RemoveParameter($Type,$Name);
	}

	/**
	 * Internal version of {@link RemoveParameter()} that doesn't validate if removal of the parameter is permitted
	 *
	 * @param integer $Type
	 * @param string $Name
	 */
	private function _RemoveParameter($Type, $Name) {
		// Iterate through all parameters
		for ($iIndex = count($this->aParameters) - 1; $iIndex >= 0; $iIndex--) {
			$Parameter = $this->aParameters[$iIndex];
			// Test the type
			if ($Parameter->GetType() != $Type) continue;
			// Also test the name if necessary
			switch ($Type) {
				case PHPICALLIB_PARAMETER_IANAPARAM:
				case PHPICALLIB_PARAMETER_XPARAM:
					// Should we filter by name too?
					if (empty($Name)) {
						array_splice($this->aParameters, $iIndex, 1);
						break;
					} 
					if ($Parameter->GetName() == $Name) {
						array_splice($this->aParameters, $iIndex, 1);
						break;
					}
					break;
				default:
					array_splice($this->aParameters, $iIndex, 1);
			}
		}
	}

	/**
	 * Access the property value as an encoded value
	 *
	 * @return string
	 */
	public function GetEncodedValue() {
		return $this->EncodedValue;
	}

	/**
	 * Sets the property value by specifying the encoded value
	 *
	 * @param string $Value
	 */
	public function SetEncodedValue($Value) {
		// Further parse the main value
		
		//      value         = *VALUE-CHAR
		//      VALUE-CHAR    = WSP / %x21-7E / NON-US-ASCII
		// ==>
		//      value         = *VALUE-CHAR
		//      VALUE-CHAR    = %x09 / %x20-7E / NON-US-ASCII
		$Pattern = '/^[\x09\x20-\x7E]*$/D';
		if (!preg_match($Pattern, $Value)) {
			$aTokens = preg_split('/[^\x09\x20-\x7E]/', $Value);
			throw new PhpiCalLib_ParameterException(sprintf("Invalid value (%s) between these tokens '%s' in contentline: %s", $Pattern, implode("','", $aTokens), $Value));
		}
		$this->EncodedValue = $Value;
	}

	/**
	 * Create a new ContentLine object
	 *
	 * A component would override this class, providing a new version of this function
	 * in order to choose the right kind of class to create.  The alternative is to create
	 * a generic ContentLine object, then us Copy() to establish the specialised version, but
	 * it's better to create the right object up front.
	 * 
	 * @param string $Name The name of the contentline
	 * @return PhpiCalLib_ContentLine derived class
	 */
	protected function CreateContentLine($Name) {
		$Result = new PhpiCalLib_ContentLine();
		$Result->SetName($Name);
		return $Result;
	}

	/**
	 * Parse a contentline from the unfolded string, returning the new object
	 *
	 * @param string $UnfoldedContentLine The unfolded line that we think represents a contentline
	 * @return A PhpiCalLib_ContentLine derived class, according to what CreateContentLine returned
	 */
	public function Create($UnfoldedContentLine) {
		//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.1
		//      contentline   = name *(";" param ) ":" value CRLF
		//      ; This ABNF is just a general definition for an initial parsing
		//      ; of the content line into its property name, parameter list,
		//      ; and value string
		//
		//      name          = iana-token / x-name
		//      iana-token    = 1*(ALPHA / DIGIT / "-")
		//      ; iCalendar identifier registered with IANA
		//      x-name        = "X-" [vendorid "-"] 1*(ALPHA / DIGIT / "-")
		//      ; Reserved for experimental use.
		//      vendorid      = 3*(ALPHA / DIGIT)
		//      ; Vendor identification
		//      param         = param-name "=" param-value *("," param-value)
		//      ; Each property defines the specific ABNF for the parameters
		//      ; allowed on the property. Refer to specific properties for
		//      ; precise parameter ABNF.
		//      param-name    = iana-token / x-name
		//      param-value   = paramtext / quoted-string
		//      paramtext     = *SAFE-CHAR
		//      quoted-string = DQUOTE *QSAFE-CHAR DQUOTE
		//      SAFE-CHAR     = WSP / %x21 / %x23-2B / %x2D-39 / %x3C-7E
		//                    / NON-US-ASCII
		//      ; Any character except CONTROL, DQUOTE, ";", ":", ","
		// http://tools.ietf.org/html/rfc2234#section-6.1
		//        ALPHA          =  %x41-5A / %x61-7A   ; A-Z / a
		//        DIGIT          =  %x30-39
		//                               ; 0-9
		//        DQUOTE         =  %x22
		//                               ; " (Double Quote)		
		//        HTAB           =  %x09
		//                               ; horizontal tab
		//        LF             =  %x0A
		//                               ; linefeed
		//        LWSP           =  *(WSP / CRLF WSP)
		//                               ; linear white space (past newline)
		//        OCTET          =  %x00-FF
		//                               ; 8 bits of data
		//        SP             =  %x20
		//                               ; space
		//        VCHAR          =  %x21-7E
		//                               ; visible (printing) characters
		//        WSP            =  SP / HTAB
		//                               ; white space
		
		// We'll break up the name, parameters and value in one go.
		
		$ContentLineSplitPattern = sprintf('/^(%s)((;%s)*):(.*)$/D', self::$NameSplitRegex, PhpiCalLib_Parameter::GetParamRegex());
		$aMatches = array();
		if (!preg_match($ContentLineSplitPattern, $UnfoldedContentLine, $aMatches)) {
			throw new PhpiCalLib_ParseException(sprintf("Doesn't match standard contentline pattern '%s': %s", $ContentLineSplitPattern, $UnfoldedContentLine));
		}
		
		// Extract the three components
		$Name = $aMatches[1];
		$Parameters = $aMatches[2];
		$Value = $aMatches[count($aMatches)-1];
		
		// Create the right kind of content line property to populate, assigning it's name
		$ContentLine = $this->CreateContentLine($Name);
		
		// Further parse the parameters before storage
		if (!empty($Parameters)) {
			// If we had parameters, then they must start with a ;
			if ($Parameters[0] != ';') {
				throw new PhpiCalLib_ParseException(sprintf("Expected to find a ';' to start the parameters: %s", $Parameters));
			}
			
			// While it would be nice to do a simple explode(';') it wouldn't be valid, as there can be ';' inside
			// quoted-string arguments.  So instead use a regex to peel off one parameter at a time.
			$Pattern = sprintf('/^;(%s)((;.*)*)$/D',PhpiCalLib_Parameter::GetParamRegex());
			$aMatches = array();
			while (preg_match($Pattern, $Parameters, $aMatches)) {
				$Parameters = $aMatches[count($aMatches) - 1];
				$Parameter = $this->ParameterFactory->Create($aMatches[1]);
				$ContentLine->AddParameter($Parameter);
			}
			
			// We should have no parameter text left
			if (!empty($Parameters)) {
				throw new PhpiCalLib_ParseException(sprintf("Failed to extract parameters (%s) from: %s", $Pattern, $Parameters));
			}
		}
		
		$ContentLine->SetEncodedValue($Value);
		
		// We're done :o)
		return $ContentLine;
	}

	/**
	 * Convert the content line to a string
	 * 
	 * @return A string representation in the default code page
	 */
	public function ToString() {
		if (empty($this->Name)) {
			throw new PhpiCalLib_Exception(sprintf('No name assigned to this parameter yet.  It has value: %s',$this->EncodedValue));
		}
		//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.1
		//      contentline   = name *(";" param ) ":" value CRLF
		$aParameters = array();
		if ($this->aParameters) {
			foreach ($this->aParameters as $Parameter) {
				$aParameters[] = ";";
				$aParameters[] = $Parameter->ToString();
			}
		}
		
		return sprintf("%s%s:%s\r\n", $this->Name, implode('', $aParameters), $this->EncodedValue);
	}

	/**
	 * Determine if the given parameter can be changed
	 *
	 * @param PhpiCalLib_Parameter $ContentLine
	 */
	protected function SetParameterPermitted($Parameter) {
		// If there's no permitted parameter info, then we can't validate, so assume it's ok
		if (empty($this->aPermittedParameters)) return;
		
		// Get the parameter info
		$TypeId = $Parameter->GetType();
		if (empty($this->aPermittedParameters[$TypeId])) {
			throw new PhpiCalLib_ParameterException(
							sprintf("The %s parameter is not permitted within the %s property", 
								$Parameter->GetName(), 
								$this->GetName()));
		}
	}
	
	/**
	 * Determine if the given parameter is permitted for addition
	 *
	 * @param PhpiCalLib_Parameter $Parameter
	 */
	protected function AddParameterPermitted($Parameter) {
		// If there's no permitted parameter info, then we can't validate, so assume it's ok
		if (empty($this->aPermittedParameters)) return;
		
		// Get the parameter info
		$TypeId = $Parameter->GetType();
		if (empty($this->aPermittedParameters[$TypeId])) {
			throw new PhpiCalLib_ParameterException(
							sprintf("The %s parameter is not permitted within the %s property", 
								$Parameter->GetName(), 
								$this->GetName()));
		}
		
		// Check the max instances is respected
		$aParameterInfo = $this->aPermittedParameters[$TypeId];
		if ($aParameterInfo[1] == 1) {
			// If we are only allowed one of these parameters, and we already have one, then fail
			$Parameter = $this->GetParameter($TypeId);
			if (isset($Parameter)) {
				throw new PhpiCalLib_ParameterException(
						sprintf("The %s parameter is only permitted once within the %s property and already has value %s", 
							$Parameter->GetName(), 
							$this->GetName(), 
							$Parameter->GetEncodedValue()));
			}
		} else if ($aParameterInfo[1] > 1) {
			// If there's a max limit, then check we aren't at our max limit already
			$aParameters = $this->GetParameters($TypeId);
			if (count($aParameters) >= $aParameterInfo[1]) {
				throw new PhpiCalLib_ParameterException(
						sprintf("The %s parameter is only permitted %d times within the %s property and already has %d values", 
							$Parameter->GetName(), 
							$aParameterInfo[1],
							$this->GetName(), 
							count($aParameters)));
			}
		}
	}
	
	/**
	 * Determine if we are allowed to remove the given parameter
	 *
	 * @param integer $Type
	 * @param string $Name
	 */
	protected function RemoveParameterPermitted($Type, $Name) {
		// If there's no permitted parameter info, then we can't validate, so assume it's ok
		if (empty($this->aPermittedParameters)) return;
		
		// Get the parameter info
		if (empty($this->aPermittedParameters[$Type])) {
			// They'd never have been allowed to add the parameter, so they are welcome to remove it.
			return;
		}
		$aParameterInfo = $this->aPermittedParameters[$Type];
		// If removing this parameter would drop us below our lower limit, then fail
		if ($aParameterInfo[0] == 1) {
			throw new PhpiCalLib_ParameterException(
						sprintf("The %s (%s) parameter is REQUIRED for the %s property.  Call SetParameter to change it's value.", 
							self::$TypeMap[$Type],
							$Name, 
							$this->Name));
		} else if ($aParameterInfo[0] > 0) {
			// There's a min number of instances, check we aren't at that lower limit already
			$aParameters = $this->GetParameters($Type);
			if (count($aParameters) <= $aParameterInfo[0]) {
				throw new PhpiCalLib_ParameterException(
							sprintf("The %s (%s) parameter is REQUIRED at least %d times for the %s property.  Call SetParameter/SetParameters/AddParameter to change/add values.", 
								self::$TypeMap[$Type],
								$Name, 
								$aParameterInfo[0],
								$this->Name));
			}
		}
	}
}

Return current item: PhpiCalLib