+ public function addFlag($flag)
+ {
+ $this->flags->addFlag($flag);
+ }
+
+ /** Auxilary function for formatting postal addresses.
+ * If the needle is found in the haystack, it notifies the substitution's
+ * success, modifies the length accordingly and returns either the matching
+ * substitution or the needle.
+ */
+ private function substitute($needle, $haystack, &$length, &$success, $trim = false)
+ {
+ if (array_key_exists($needle, $haystack)) {
+ $success = true;
+ $length -= (strlen($needle) - strlen($haystack[$needle]));
+ return $haystack[$needle];
+ } elseif ($trim) {
+ $success = true;
+ if (strlen($needle) > 4) {
+ $length -= (strlen($needle) - 4);
+ $needle = $needle{4};
+ }
+ }
+ return $needle;
+ }
+
+ /** Checks if the line corresponds to a French street line.
+ * A line is considered a French street line if it starts by between 1 and 4 numbers.
+ */
+ private function isStreetFR($line)
+ {
+ return preg_match('/^\d{1,4}\D/', $line);
+ }
+
+ /** Retrieves a French street number and slit the rest of the line into an array.
+ * @param $words: array containing the rest of the line (a word per cell).
+ * @param $line: line to consider.
+ * Returns the street number.
+ */
+ private function getStreetNumberFR(&$line)
+ {
+ // First we define numbers and separators.
+ $numberReq = '(\d{1,4})\s*(BIS|TER|QUATER|[A-Z])?';
+ $separatorReq = '\s*(?:\\|-|&|A|ET)?\s*';
+
+ // Then we retrieve the number(s) and the rest of the line.
+ // $matches contains:
+ // -0: the full patern, here the given line,
+ // -1: the number,
+ // -2: its optionnal quantifier,
+ // -3: an optionnal second number,
+ // -4: the second number's optionnal quantifier,
+ // -5: the rest of the line.
+ preg_match('/^' . $numberReq . '(?:' . $separatorReq . $numberReq . ')?\s+(.*)/', $line, $matches);
+ $number = $matches[1];
+ $line = $matches[5];
+
+ // If there is a precision on the address, we concatenate it to the number.
+ if ($matches[2] != '') {
+ $number .= $matches[2]{0};
+ } elseif ($matches[4] != '') {
+ $number .= $matches[4]{0};
+ }
+
+ return $number;
+ }
+
+ /** Checks if the line corresponds to a French locality line.
+ * A line is considered a French locality line if it starts by exactly a
+ * postal code of exactly 5 numbers.
+ */
+ private function isLocalityFR($line)
+ {
+ return preg_match('/^\d{5}\D/', $line);
+ }
+
+ /** Retrieves a French postal code and slit the rest of the line into an array.
+ * @param $words: array containing the rest of the line (a word per cell).
+ * @param $line: line to consider.
+ * Returns the postal code, and cuts it out from the line.
+ */
+ private function getPostalCodeFR(&$line)
+ {
+ $number = substr($line, 0, 5);
+ $line = trim(substr($line, 5));
+ return $number;
+ }
+
+ /** Returns the address formated for French postal use (cf AFNOR XPZ 10-011).
+ * A postal addresse containts at most 6 lines of at most 38 characters each:
+ * - addressee's identification ("MONSIEUR JEAN DURAND", "DURAND SA"…),
+ * - delivery point identification ("CHEZ TOTO APPARTEMENT 2", "SERVICE ACHAT"…),
+ * - building localisation complement ("ENTREE A BATIMENT DES JONQUILLES", "ZONE INDUSTRIELLE OUEST"…),
+ * - N° and street name ("25 RUE DES FLEURS", "LES VIGNES"…),
+ * - delivery service, street localisation complement ("BP 40122", "BP 40112 AREYRES"…),
+ * - postal code and locality or cedex code and cedex ("33500 LIBOURNE", "33506 LIBOURNE CEDEX"…).
+ * Punctuation must be removed, all leters must be uppercased.
+ * Both locality and street name must not take more than 32 characters.
+ *
+ * @param $arrayText: array containing the address to be formated, one
+ * address line per array line.
+ * @param $count: array size.
+ */
+ private function formatPostalAddressFR($arrayText)
+ {
+ // First removes country if any.
+ $count = count($arrayText);
+ if ($arrayText[$count - 1] == 'FRANCE') {
+ unset($arrayText[$count - 1]);
+ --$count;
+ }
+
+ // All the lines must have less than 38 characters but street and
+ // locality lines whose limit is 32 characters.
+ foreach ($arrayText as $lineNumber => $line) {
+ if ($isStreetLine = $this->isStreetFR($line)) {
+ $formattedLine = $this->getStreetNumberFR($line) . ' ';
+ $limit = 32;
+ } elseif ($this->isLocalityFR($line)) {
+ $formattedLine = $this->getPostalCodeFR($line) . ' ';
+ $limit = 32;
+ } else {
+ $formattedLine = '';
+ $limit = 38;
+ }
+
+ $words = explode(' ', $line);
+ $count = count($words);
+ $length = $count - 1;
+ foreach ($words as $word) {
+ $length += strlen($word);
+ }
+
+ // Checks is length is ok. Otherwise, we try to shorten words and
+ // update the length of the current line accordingly.
+ for ($i = 0; $i < $count && $length > $limit; ++$i) {
+ $success = false;
+ if ($isStreetLine) {
+ $sub = $this->substitute($words[$i], Address::$streetAbbreviations, $length, $success, ($i == 0));
+ }
+ // Entreprises' substitution are only suitable for the first two lines.
+ if ($lineNumber <= 2 && !$success) {
+ $sub = $this->substitute($words[$i], Address::$entrepriseAbbreviations, $length, $success);
+ }
+ if (!$success) {
+ $sub = $this->substitute($words[$i], Address::$otherAbbreviations, $length, $success);
+ }
+
+ $formattedLine .= $sub . ' ';
+ }
+ for (; $i < $count; ++$i) {
+ $formattedLine .= $words[$i] . ' ';
+ }
+ $arrayText[$lineNumber] = trim($formattedLine);
+ }
+
+ return implode("\n", $arrayText);
+ }
+
+ // Formats postal addresses.
+ // First erases punctuation, accents… Then uppercase the address and finally
+ // calls the country's dedicated formatting function.
+ public function formatPostalAddress()