<commit_before><?php
/**
 * vCard class for parsing a vCard and/or creating one
 *
 * @link https://github.com/nuovo/vCard-parser
 * @author <NAME>, <NAME>
 * @see RFC 2426, RFC 2425, RFC 6350
 * @version 0.4.8
 * Modified by <NAME> November 2014
*/
	class vCard implements Countable, Iterator
	{
		const MODE_ERROR = 'error';
		const MODE_SINGLE = 'single';
		const MODE_MULTIPLE = 'multiple';

		const endl = "\n";

		/**
		 * @var string Current object mode - error, single or multiple (for a single vCard within a file and multiple combined vCards)
		 */
		private $Mode;  //single, multiple, error

		private $Path = '';
		private $RawData = '';

		/**
		 * @var array Internal options container. Options:
		 *	bool Collapse: If true, elements that can have multiple values but have only a single value are returned as that value instead of an array
		 *		If false, an array is returned even if it has only one value.
		 */
		private $Options = array(
			'Collapse' => false
		);

		/**
		 * @var array Internal data container. Contains vCard objects for multiple vCards and just the data for single vCards.
		 */
		private $Data = array();

		/**
		 * @static Parts of structured elements according to the spec.
		 */
		private static $Spec_StructuredElements = array(
			'n' => array('LastName', 'FirstName', 'AdditionalNames', 'Prefixes', 'Suffixes'),
			'adr' => array('POBox', 'ExtendedAddress', 'StreetAddress', 'Locality', 'Region', 'PostalCode', 'Country'),
			'geo' => array('Latitude', 'Longitude'),
			'org' => array('Name', 'Unit1', 'Unit2')
		);
		private static $Spec_MultipleValueElements = array('nickname', 'categories');

		private static $Spec_ElementTypes = array(
			'email' => array('internet', 'x400', 'pref'),
			'adr' => array('dom', 'intl', 'postal', 'parcel', 'home', 'work', 'pref'),
			'label' => array('dom', 'intl', 'postal', 'parcel', 'home', 'work', 'pref'),
			'tel' => array('home', 'msg', 'work', 'pref', 'voice', 'fax', 'cell', 'video', 'pager', 'bbs', 'modem', 'car', 'isdn', 'pcs'),
			'impp' => array('personal', 'business', 'home', 'work', 'mobile', 'pref'),
			'note' => array('home', 'work'),
			'url'  => array('home', 'work')
		);

		private static $Spec_FileElements = array('photo', 'logo', 'sound');
		/**
                 * @static Elements with only one value according to spec
                 */
		private static $Spec_SingleElements = array('fn', 'kind', 'bday', 'anniversary', 'prodid', 'rev');

		/**
		 * vCard constructor
		 *
		 * @param string Path to file, optional.
		 * @param string Raw data, optional.
		 * @param array Additional options, optional. Currently supported options:
		 *	bool Collapse: If true, elements that can have multiple values but have only a single value are returned as that value instead of an array
		 *		If false, an array is returned even if it has only one value.
		 *
		 * One of these parameters must be provided, otherwise an exception is thrown.
		 */
		public function __construct($Path = false, $RawData = false, array $Options = null)
		{
			// Checking preconditions for the parser.
			// If path is given, the file should be accessible.
			// If raw data is given, it is taken as it is.
			// In both cases the real content is put in $this -> RawData
			if ($Path)
			{
				if (!is_readable($Path))
				{
					throw new Exception('vCard: Path not accessible ('.$Path.')');
				}

				$this -> Path = $Path;
				$this -> RawData = file_get_contents($this -> Path);
			}
			elseif ($RawData)
			{
				$this -> RawData = $RawData;
			}
			else
			{
				//throw new Exception('vCard: No content provided');
				// Not necessary anymore as possibility to create vCards is added
			}

			if (!$this -> Path && !$this -> RawData)
			{
				return true;
			}

			if ($Options)
			{
				$this -> Options = array_merge($this -> Options, $Options);
			}

			// Counting the begin/end separators. If there aren't any or the count doesn't match, there is a problem with the file.
			// If there is only one, this is a single vCard, if more, multiple vCards are combined.
			$Matches = array();
			$vCardBeginCount = preg_match_all('{^BEGIN\:VCARD}miS', $this -> RawData, $Matches);
			$vCardEndCount = preg_match_all('{^END\:VCARD}miS', $this -> RawData, $Matches);

			if (($vCardBeginCount != $vCardEndCount) || !$vCardBeginCount)
			{
				$this -> Mode = vCard::MODE_ERROR;
				throw new Exception('vCard: invalid vCard');
			}

			$this -> Mode = $vCardBeginCount == 1 ? vCard::MODE_SINGLE : vCard::MODE_MULTIPLE;

			// Removing/changing inappropriate newlines, i.e., all CRs or multiple newlines are changed to a single newline
			$this -> RawData = str_replace("\r", "\n", $this -> RawData);
			$this -> RawData = preg_replace('{(\n+)}', "\n", $this -> RawData);

			// In multiple card mode the raw text is split at card beginning markers and each
			//	fragment is parsed in a separate vCard object.
			if ($this -> Mode == self::MODE_MULTIPLE)
			{
				$this -> RawData = explode('BEGIN:VCARD', $this -> RawData);
				$this -> RawData = array_filter($this -> RawData);

				foreach ($this -> RawData as $SinglevCardRawData)
				{
					// Prepending "BEGIN:VCARD" to the raw string because we exploded on that one.
					// If there won't be the BEGIN marker in the new object, it will fail.
					$SinglevCardRawData = 'BEGIN:VCARD'."\n".$SinglevCardRawData;

					$ClassName = get_class($this);
					$this -> Data[] = new $ClassName(false, $SinglevCardRawData);
				}
			}
			else
			{
				// Protect the BASE64 final = sign (detected by the line beginning with whitespace), otherwise the next replace will get rid of it
				$this -> RawData = preg_replace('{(\n\s.+)=(\n)}', '$1-base64=-$2', $this -> RawData);

				// Joining multiple lines that are split with a hard wrap and indicated by an equals sign at the end of line
				// (quoted-printable-encoded values in v2.1 vCards)
				$this -> RawData = str_replace("=\n", '', $this -> RawData);

				// Joining multiple lines that are split with a soft wrap (space or tab on the beginning of the next line
				$this -> RawData = str_replace(array("\n ", "\n\t"), '-wrap-', $this -> RawData);

				// Restoring the BASE64 final equals sign (see a few lines above)
				$this -> RawData = str_replace("-base64=-\n", "=\n", $this -> RawData);

				$Lines = explode("\n", $this -> RawData);

				foreach ($Lines as $Line)
				{
					// Lines without colons are skipped because, most likely, they contain no data.
					if (strpos($Line, ':') === false)
					{
						continue;
					}
					
					// Each line is split into two parts. The key contains the element name and additional parameters, if present,
					//	value is just the value
					list($Key, $Value) = explode(':', $Line, 2);
					if (empty($Value)) continue;

					// Key is transformed to lowercase because, even though the element and parameter names are written in uppercase,
					//	it is quite possible that they will be in lower- or mixed case.
					// The key is trimmed to allow for non-significant WSP characters as allowed by v2.1
					$Key = strtolower(trim(self::Unescape($Key)));

					// These two lines can be skipped as they aren't necessary at all.
					if ($Key == 'begin' || $Key == 'end')
					{
						continue;
					}

					if ((strpos($Key, 'agent') === 0) && (stripos($Value, 'begin:vcard') !== false))
					{
						$ClassName = get_class($this);
						$Value = new $ClassName(false, str_replace('-wrap-', "\n", $Value));
						if (!isset($this -> Data[$Key]))
						{
							$this -> Data[$Key] = array();
						}
						$this -> Data[$Key][] = $Value;
						continue;
					}
					else
					{
						$Value = str_replace('-wrap-', '', $Value);
					}

					$Value = trim(self::Unescape($Value));
					$Type = array();

					// Here additional parameters are parsed
					$KeyParts = explode(';', $Key);
					$Key = $KeyParts[0];
					$Encoding = false;

					if (strpos($Key, 'item') === 0)
					{
						$TmpKey = explode('.', $Key, 2);
						$Key = $TmpKey[1];
						$ItemIndex = (int)str_ireplace('item', '', $TmpKey[0]);
					}

					if (count($KeyParts) > 1)
					{
						$Parameters = self::ParseParameters($Key, array_slice($KeyParts, 1));

						foreach ($Parameters as $ParamKey => $ParamValue)
						{
							switch ($ParamKey)
							{
								case 'encoding':
									$Encoding = $ParamValue;
									if (in_array($ParamValue, array('b', 'base64')))
									{
										//$Value = base64_decode($Value);
									}
									elseif ($ParamValue == 'quoted-printable') // v2.1
									{
										$Value = quoted_printable_decode($Value);
									}
									break;
								case 'charset': // v2.1
									if ($ParamValue != 'utf-8' && $ParamValue != 'utf8')
									{
										$Value = mb_convert_encoding($Value, 'UTF-8', $ParamValue);
									}
									break;
								case 'type':
									$Type = $ParamValue;
									break;
							}
						}
					}

					// Checking files for colon-separated additional parameters (Apple's Address Book does this), for example, "X-ABCROP-RECTANGLE" for photos
					if (in_array($Key, self::$Spec_FileElements) && isset($Parameters['encoding']) && in_array($Parameters['encoding'], array('b', 'base64')))
					{
						// If colon is present in the value, it must contain Address Book parameters
						//	(colon is an invalid character for base64 so it shouldn't appear in valid files)
						if (strpos($Value, ':') !== false)
						{
							$Value = explode(':', $Value);
							$Value = array_pop($Value);
						}
					}

					// Values are parsed according to their type
					if (isset(self::$Spec_StructuredElements[$Key]))
					{
						$Value = self::ParseStructuredValue($Value, $Key);
						if ($Type)
						{
							$Value['Type'] = $Type;
						}
					}
					else
					{
						if (in_array($Key, self::$Spec_MultipleValueElements))
						{
							$Value = self::ParseMultipleTextValue($Value, $Key);
						}

						if ($Type)
						{
							$Value = array(
								'Value' => $Value,
								'Type' => $Type
							);
						}
					}

					if (is_array($Value) && $Encoding)
					{
						$Value['Encoding'] = $Encoding;
					}


					if (in_array($Key, self::$Spec_SingleElements))
					{
						$this -> Data[$Key] = $Value;
					} else {
						if (!isset($this -> Data[$Key]))
						{
						    $this -> Data[$Key] = array();
						}
// FIXME: Horribly convoluted and opaque
						if (in_array($Key, self::$Spec_MultipleValueElements))
							$this->Data[$Key] = array_merge($this -> Data[$Key], $Value);
						else
							$this -> Data[$Key][] = $Value;
					}
				}
			}
		}

		/**
		 * Magic method to get the various vCard values as object members, e.g.
		 *	a call to $vCard -> N gets the "N" value<commit_msg>Refactored --construct. Broke it into multiple functions to reduce nesting, localize and eliminate variables, and make flow clearer. Some of the resulting functions need similar refactoring, but some overall improvement has been gained. Eliminated $this->RawData parameter as handling of raw text is now handled in method arguments and there is no need to store it.<commit_after><?php
/**
 * vCard class for parsing a vCard and/or creating one
 *
 * @link https://github.com/nuovo/vCard-parser
 * @author <NAME>, <NAME>
 * @see RFC 2426, RFC 2425, RFC 6350
 * @version 0.4.8
 * Modified by <NAME> November 2014
*/
	class vCard implements Countable, Iterator
	{
		const MODE_ERROR = 'error';
		const MODE_SINGLE = 'single';
		const MODE_MULTIPLE = 'multiple';

		const endl = "\n";

		/**
		 * @var string Current object mode - error, single or multiple (for a single vCard within a file and multiple combined vCards)
		 */
		private $Mode;  //single, multiple, error

		private $Path = '';
		private $RawData = '';

		/**
		 * @var array Internal options container. Options:
		 *	bool Collapse: If true, elements that can have multiple values but have only a single value are returned as that value instead of an array
		 *		If false, an array is returned even if it has only one value.
		 */
		private $Options = array(
			'Collapse' => false
		);

		/**
		 * @var array Internal data container. Contains vCard objects for multiple vCards and just the data for single vCards.
		 */
		private $Data = array();

		/**
		 * @static Parts of structured elements according to the spec.
		 */
		private static $Spec_StructuredElements = array(
			'n' => array('LastName', 'FirstName', 'AdditionalNames', 'Prefixes', 'Suffixes'),
			'adr' => array('POBox', 'ExtendedAddress', 'StreetAddress', 'Locality', 'Region', 'PostalCode', 'Country'),
			'geo' => array('Latitude', 'Longitude'),
			'org' => array('Name', 'Unit1', 'Unit2')
		);
		private static $Spec_MultipleValueElements = array('nickname', 'categories');

		private static $Spec_ElementTypes = array(
			'email' => array('internet', 'x400', 'pref'),
			'adr' => array('dom', 'intl', 'postal', 'parcel', 'home', 'work', 'pref'),
			'label' => array('dom', 'intl', 'postal', 'parcel', 'home', 'work', 'pref'),
			'tel' => array('home', 'msg', 'work', 'pref', 'voice', 'fax', 'cell', 'video', 'pager', 'bbs', 'modem', 'car', 'isdn', 'pcs'),
			'impp' => array('personal', 'business', 'home', 'work', 'mobile', 'pref'),
			'note' => array('home', 'work'),
			'url'  => array('home', 'work')
		);

		private static $Spec_FileElements = array('photo', 'logo', 'sound');
		/**
                 * @static Elements with only one value according to spec
                 */
		private static $Spec_SingleElements = array('fn', 'kind', 'bday', 'anniversary', 'prodid', 'rev');

		/**
		 * vCard constructor
		 *
		 * @param string Path to file, optional.
		 * @param string Raw data, optional.
		 * @param array Additional options, optional. Currently supported options:
		 *	bool Collapse: If true, elements that can have multiple values but have only a single value are returned as that value instead of an array
		 *		If false, an array is returned even if it has only one value.
		 *
		 * One of these parameters must be provided, otherwise an exception is thrown.
		 */
		public function __construct($Path = false, $RawData = false, array $Options = null)
		{
		    // Checking preconditions for the parser.
		    // If path is given, the file should be accessible.
		    // If raw data is given, it is taken as it is.
		    // In both cases the real content is put in $this -> RawData
		    if ($Path)
		    {
			if (!is_readable($Path))
			    throw new Exception( 'vCard: Path not accessible ('
                                .    $Path.')' );

			$this -> Path = $Path;
			$RawData = file_get_contents($this -> Path);
		     }

		     if (!$RawData)
			return true;

		     if ($Options)
			$this -> Options
                            = array_merge($this -> Options, $Options);

		     $RawData
                         = $this->checkRawDataAndSetMode($RawData);

		     // In multiple card mode the raw text is split at card 
                     // beginning markers and each
		     // fragment is parsed in a separate vCard object.
		     if ($this -> Mode == self::MODE_MULTIPLE)
		         $this->processMultipleRawCards($RawData);
		     else
                         $this->processSingleRawCard($RawData);
		} // --construct()

		/**
                 * Checks the raw VCard data for major errors (such as BEGIN and
		 * END markers) and throws an exception if malformed.
		 * Sets mode to single or multiple.
		 */
		protected function checkRawDataAndSetMode($rawData)
		{
		    // Counting the begin/end separators.
                    // If there aren't any or the count doesn't match,
                    // there is a problem with the file.
		    // If there is only one, this is a single vCard, if more,
                    // multiple vCards are combined.
		    $Matches = array();
		    $vCardBeginCount = preg_match_all( '{^BEGIN\:VCARD}miS',
                            $rawData, $Matches );
		    $vCardEndCount = preg_match_all( '{^END\:VCARD}miS',
                            $rawData, $Matches );

		    if (($vCardBeginCount != $vCardEndCount) || !$vCardBeginCount)
		    {
			$this -> Mode = vCard::MODE_ERROR;
			throw new Exception('vCard: invalid vCard');
		    }

		    $this->Mode = $vCardBeginCount == 1 ? vCard::MODE_SINGLE : vCard::MODE_MULTIPLE;

		    // Removing/changing inappropriate newlines, i.e., all CRs or multiple newlines are changed to a single newline
		    $rawData = str_replace("\r", "\n", $rawData);
		    $rawData = preg_replace('{(\n+)}', "\n", $rawData);
		    return $rawData;
		} // checkRawDataAndSetMode()

		/**
                 * If there are multiple VCards in the raw input, process them
		 * recursively by construction new VCard objects.
		 */
		protected function processMultipleRawCards($rawData)
		{
		    $rawData = explode('BEGIN:VCARD', $rawData);
		    $rawData = array_filter($rawData);

		    foreach ($rawData as $SinglevCardRawData)
		    {
		        // Prepending "BEGIN:VCARD" to the raw string because
                        // we exploded on that one.
			// If there won't be the BEGIN marker in the new object, 
                        // it will fail.
			$SinglevCardRawData = 'BEGIN:VCARD' . "\n"
                                              . $SinglevCardRawData;

			$ClassName = get_class($this);
			$this->Data[] = new $ClassName( false,
                                                        $SinglevCardRawData );
		    }
		} // processMultipleRawCards()

                protected function processSingleRawCard($rawData)
                {
		    // Protect the BASE64 final = sign (detected by the line 
                    // beginning with whitespace), otherwise the next replace 
                    // will get rid of it
		    $rawData = preg_replace( '{(\n\s.+)=(\n)}',
                            '$1-base64=-$2', $rawData );

		    // Joining multiple lines that are split with a hard wrap
                    // and indicated by an equals sign at the end of line
		    // (quoted-printable-encoded values in v2.1 vCards)
		    $rawData = str_replace("=\n", '', $rawData);

		    // Joining multiple lines that are split with a soft
                    // wrap (space or tab on the beginning of the next line
		    $rawData = str_replace( array("\n ", "\n\t"),
                            '-wrap-', $rawData );

		    // Restoring the BASE64 final equals sign (see a few
                    // lines above)
		    $rawData = str_replace( "-base64=-\n", "=\n", $rawData );

		    $Lines = explode("\n", $rawData);

		    foreach ($Lines as $Line)
		    {
		        // Lines without colons are skipped because, most
                        // likely, they contain no data.
			if (strpos($Line, ':') === false)
			{
			    continue;
			}
					
			// Each line is split into two parts.
                        // The key contains the element name and additional 
                        // parameters, if present; value is just the value
			list($Key, $Value) = explode(':', $Line, 2);
			if (empty($Value)) continue;

			// Key is transformed to lowercase because, even
                        // though the element and parameter names are written
                        // in uppercase, it is quite possible that they will
                        // be in lower- or mixed case.
			// The key is trimmed to allow for non-significant
                        // WSP characters as allowed by v2.1
			$Key = strtolower(trim(self::Unescape($Key)));

			// These two lines can be skipped as they aren't 
                        // necessary at all.
			if ($Key == 'begin' || $Key == 'end')
			{
			    continue;
			}

			if ( (strpos($Key, 'agent') === 0)
                             && (stripos($Value, 'begin:vcard') !== false) )
			{
			    $ClassName = get_class($this);
			    $Value = new $ClassName( false,
                                    str_replace('-wrap-', "\n", $Value) );
			    if (!isset($this -> Data[$Key]))
			    {
			        $this -> Data[$Key] = array();
			    }
			    $this -> Data[$Key][] = $Value;
			    continue;
			} else {
			    $Value = str_replace('-wrap-', '', $Value);
			}

			$Value = trim(self::Unescape($Value));
			$Type = array();

			// Here additional parameters are parsed
			$KeyParts = explode(';', $Key);
			$Key = $KeyParts[0];
			$Encoding = false;

			if (strpos($Key, 'item') === 0)
			{
			    $TmpKey = explode('.', $Key, 2);
			    $Key = $TmpKey[1];
			    $ItemIndex = (int)str_ireplace( 'item', '',
                                    $TmpKey[0] );
			}

			if (count($KeyParts) > 1)
			{
			    $Parameters = self::ParseParameters( $Key,
                                    array_slice($KeyParts, 1) );

			    foreach ($Parameters as $ParamKey => $ParamValue)
			    {
			        switch ($ParamKey)
			        {
			            case 'encoding':
				        $Encoding = $ParamValue;
				        if (in_array($ParamValue, array('b', 'base64')))
				        {
				            //$Value = base64_decode($Value);
				        } elseif ($ParamValue == 'quoted-printable') {
                                            // v2.1
				            $Value = quoted_printable_decode($Value);
				        }
					break;
				    case 'charset': // v2.1
				        if ( $ParamValue != 'utf-8'
                                             && $ParamValue != 'utf8' )
                                        {
				            $Value = mb_convert_encoding($Value, 'UTF-8', $ParamValue);
				        }
				        break;
				    case 'type':
				        $Type = $ParamValue;
				        break;
			        } //switch
	                    } //foreach $Parameters
		        } // if

		        // Checking files for colon-separated additional parameters 
                        // (Apple's Address Book does this), for example,
                        // "X-ABCROP-RECTANGLE" for photos
		        if ( in_array($Key, self::$Spec_FileElements)
                             && isset($Parameters['encoding'])
                             && in_array( $Parameters['encoding'],
                                          array('b', 'base64') ) )
		        {
		            // If colon is present in the value, it must contain 
                            // Address Book parameters
			    // (colon is an invalid character for base64 so it 
                            // shouldn't appear in valid files)
			    if (strpos($Value, ':') !== false)
			    {
			        $Value = explode(':', $Value);
			        $Value = array_pop($Value);
			    }
		        }

		        // Values are parsed according to their type
		        if (isset(self::$Spec_StructuredElements[$Key]))
		        {
		            $Value = self::ParseStructuredValue($Value, $Key);
			    if ($Type)
                            {
			        $Value['Type'] = $Type;
			    }
		        } else {
		            if (in_array($Key, self::$Spec_MultipleValueElements))
			    {
			        $Value = self::ParseMultipleTextValue($Value, $Key);
			    }

			    if ($Type)
			    {
			        $Value = array(
				 	       'Value' => $Value,
					       'Type' => $Type
					   );
			    }
		        }

		        if (is_array($Value) && $Encoding)
		        {
		            $Value['Encoding'] = $Encoding;
		        }


		        if (in_array($Key, self::$Spec_SingleElements))
		        {
		            $this -> Data[$Key] = $Value;
		        } else {
		            if (!isset($this -> Data[$Key]))
			    {
			        $this -> Data[$Key] = array();
		            }

                            // FIXME: Horribly convoluted and opaque
			    if (in_array($Key, self::$Spec_MultipleValueElements))
			        $this->Data[$Key]
                                    = array_merge($this -> Data[$Key], $Value);
			    else
			        $this -> Data[$Key][] = $Value;
                         }
                    } // foreach $Lines
                } // processSingleRawCard()

		/**
		 * Magic method to get the various vCard values as object members, e.g.
		 *	a call to $vCard -> N gets the "N" value
{'fraction_non_alphanumeric': 0.14081564432558916, 'fraction_numerical': 0.005264917265585827, 'mean_word_length': 2.12482047264656, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 107, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 17, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4752484', 'n_tokens_mistral': 8551, 'n_tokens_neox': 6796, 'n_words': 2230}