2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4 * SugarCRM Community Edition is a customer relationship management program developed by
5 * SugarCRM, Inc. Copyright (C) 2004-2012 SugarCRM Inc.
7 * This program is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU Affero General Public License version 3 as published by the
9 * Free Software Foundation with the addition of the following permission added
10 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
14 * This program is distributed in the hope that it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
19 * You should have received a copy of the GNU Affero General Public License along with
20 * this program; if not, see http://www.gnu.org/licenses or write to the Free
21 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
27 * The interactive user interfaces in modified source and object code versions
28 * of this program must display Appropriate Legal Notices, as required under
29 * Section 5 of the GNU Affero General Public License version 3.
31 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32 * these Appropriate Legal Notices must retain the display of the "Powered by
33 * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34 * technical reasons, the Appropriate Legal Notices must display the words
35 * "Powered by SugarCRM".
36 ********************************************************************************/
39 /*********************************************************************************
40 * Description: Class to detect csv file settings (delimiter, enclosure, etc)
41 * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc.
42 * All Rights Reserved.
43 ********************************************************************************/
47 $auto = new CsvAutoDetect('/tmp/_books.csv', 10);
49 $delimiter = $enclosure = $heading = false;
51 $ret = $auto->getCsvSettings($delimiter, $enclosure);
53 echo "found delimiter = ".$delimiter."<br>";
54 echo "found enclosure = ".$enclosure."<br>";
56 echo "couldn't find settings<br>";
59 $ret = $auto->hasHeader($heading);
61 $header = $heading?'true':'false';
62 echo "found heading = ".$header."<br>";
64 echo "couldn't determine header info<br>";
67 $date_format = $auto->getDateFormat();
69 echo "found date format=".$date_format."<br>";
71 echo "couldn't find date format<br>";
74 $time_format = $auto->getTimeFormat();
76 echo "found time format=".$time_format."<br>";
78 echo "couldn't find time format<br>";
83 require_once('include/parsecsv.lib.php');
87 protected $_parser = null;
89 protected $_csv_file = null;
91 protected $_max_depth = 15;
93 protected $_parsed = false;
95 static protected $_date_formats = array(
96 'm/d/Y' => "/^(0?[1-9]|1[012])\/(0?[1-9]|[12][0-9]|3[01])\/\d\d\d\d/", // 12/23/2010 or 3/23/2010
97 'd/m/Y' => "/^(0?[1-9]|[12][0-9]|3[01])\/(0?[1-9]|1[012])\/\d\d\d\d/", // 23/12/2010 or 23/3/2010
98 'Y/m/d' => "/^\d\d\d\d\/(0?[1-9]|1[012])\/(0?[1-9]|[12][0-9]|3[01])/", // 2010/12/23 or 2010/3/23
99 'm-d-Y' => "/^(0?[1-9]|1[012])-(0?[1-9]|[12][0-9]|3[01])-\d\d\d\d/", // 12-23-2010 or 3-23-2010
100 'd-m-Y' => "/^(0?[1-9]|[12][0-9]|3[01])-(0?[1-9]|1[012])-\d\d\d\d/", // 23-12-2010 or 23-3-2010
101 'Y-m-d' => "/^\d\d\d\d-(0?[1-9]|1[012])-(0?[1-9]|[12][0-9]|3[01])/", // 2010-12-23 or 2010-3-23
102 'm.d.Y' => "/^(0?[1-9]|1[012])\.(0?[1-9]|[12][0-9]|3[01])\.\d\d\d\d/", // 12.23.2010 or 3.23.2010
103 'd.m.Y' => "/^(0?[1-9]|[12][0-9]|3[01])\.(0?[1-9]|1[012])\.\d\d\d\d/", // 23.12.2010 or 23.3.2010
104 'Y.m.d' => "/^\d\d\d\d\.(0?[1-9]|1[012])\.(0?[1-9]|[12][0-9]|3[01])/", // 2010.12.23 or 2010.3.23
107 static protected $_time_formats = array(
108 'h:ia' => "/(^| )(0?[0-9]|1[0-2]):(0?[0-9]|[1-5][0-9])(:0?[0-9]|[1-5][0-9])?[am|pm]/", // 11:00pm or 11:00:00pm or 9:3pm
109 'h:iA' => "/(^| )(0?[0-9]|1[0-2]):(0?[0-9]|[1-5][0-9])(:0?[0-9]|[1-5][0-9])?[AM|PM]/", // 11:00PM or 11:00:00PM or 9:3PM
110 'h:i a' => "/(^| )(0?[0-9]|1[0-2]):(0?[0-9]|[1-5][0-9])(:0?[0-9]|[1-5][0-9])? [am|pm]/", // 11:00 pm or 11:00:00 pm or 9:3 pm
111 'h:i A' => "/(^| )(0?[0-9]|1[0-2]):(0?[0-9]|[1-5][0-9])(:0?[0-9]|[1-5][0-9])? [AM|PM]/", // 11:00 PM or 11:00:00 PM or 9:3 PM
112 'H:i' => "/(^| )(0?[0-9]|1[0-9]|2[0-4]):(0?[0-9]|[1-5][0-9])(:0?[0-9]|[1-5][0-9])?/", // 23:00 or 23:00:00 or 9:3
113 'h.ia' => "/(^| )(0?[0-9]|1[0-2])\.(0?[0-9]|[1-5][0-9])(\.0?[0-9]|[1-5][0-9])?[am|pm]/", // 11.00pm or 11.00.00pm or 9.3pm
114 'h.iA' => "/(^| )(0?[0-9]|1[0-2])\.(0?[0-9]|[1-5][0-9])(\.0?[0-9]|[1-5][0-9])?[AM|PM]/", // 11.00PM or 11.00.00PM or 9.3PM
115 'h.i a' => "/(^| )(0?[0-9]|1[0-2])\.(0?[0-9]|[1-5][0-9])(\.0?[0-9]|[1-5][0-9])? [am|pm]/", // 11.00 pm or 11.00.00 pm or 9.3 pm
116 'h.i A' => "/(^| )(0?[0-9]|1[0-2])\.(0?[0-9]|[1-5][0-9])(\.0?[0-9]|[1-5][0-9])? [AM|PM]/", // 11.00 PM or 11.00.00 PM or 9.3 PM
117 'H.i' => "/(^| )(0?[0-9]|1[0-9]|2[0-4])\.(0?[0-9]|[1-5][0-9])(\.0?[0-9]|[1-5][0-9])?/", // 23.00 or 23.00.00 or 9.3
124 * @param string $csv_filename
125 * @param int $max_depth
127 public function __construct($csv_filename, $max_depth = 2) {
128 $this->_csv_file = $csv_filename;
130 $this->_parser = new parseCSV();
132 $this->_parser->auto_depth = $max_depth;
134 $this->_max_depth = $max_depth;
140 * To get the possible csv settings (delimiter, enclosure).
141 * This function causes CSV to be parsed.
142 * So call this function before calling others.
144 * @param string $delimiter
145 * @param string $enclosure
146 * @return bool true if settings are found, false otherwise
148 public function getCsvSettings(&$delimiter, &$enclosure) {
149 // try parsing the file to find possible delimiter and enclosure
150 $this->_parser->heading = false;
152 $found_setting = false;
154 $singleQuoteParsedOK = $doubleQuoteParsedOK = false;
155 $beginEndWithSingle = $beginEndWithDouble = false;
157 // check double quotes first
160 $delimiter1 = $this->_parser->auto($this->_csv_file, true, null, null, $enclosure);
161 if (strlen($delimiter1) == 1) { // this means parsing ok
162 $doubleQuoteParsedOK = true;
163 // sometimes it parses ok with either single quote or double quote as enclosure
164 // so we need to make sure the data do not begin and end with the other enclosure
165 foreach ($this->_parser->data as &$row) {
166 foreach ($row as &$data) {
167 $len = strlen($data);
168 // check if it begins and ends with single quotes
169 // if it does, then it double quotes may not be the enclosure
170 if ($len>=2 && $data[0] == "'" && $data[$len-1] == "'") {
171 $beginEndWithSingle = true;
175 if ($beginEndWithSingle) {
179 if ($depth > $this->_max_depth) {
183 if (!$beginEndWithSingle) {
184 $delimiter = $delimiter1;
185 $found_setting = true;
189 // check single quotes
190 if (!$found_setting) {
193 $delimiter2 = $this->_parser->auto($this->_csv_file, true, null, null, $enclosure);
194 if (strlen($delimiter2) == 1) { // this means parsing ok
195 $singleQuoteParsedOK = true;
196 foreach ($this->_parser->data as &$row) {
197 foreach ($row as &$data) {
198 $len = strlen($data);
199 // check if it begins and ends with double quotes
200 // if it does, then it single quotes may not be the enclosure
201 if ($len>=2 && $data[0] == "\"" && $data[$len-1] == "\"") {
202 $beginEndWithDouble = true;
206 if ($beginEndWithDouble) {
210 if ($depth > $this->_max_depth) {
214 if (!$beginEndWithDouble) {
215 $delimiter = $delimiter2;
216 $found_setting = true;
221 if (!$found_setting) {
222 // we don't seem to have a perfect enclosure candidate
223 // let's pick one of the possible candidates
224 if ($doubleQuoteParsedOK) {
225 // if double quotes parsed ok, let's take that
226 $delimiter = $delimiter1;
228 $found_setting = true;
229 } else if ($singleQuoteParsedOK) {
230 // otherwise, if single quote parsed ok, let's use it
231 $delimiter = $delimiter2;
233 $found_setting = true;
237 if (!$found_setting) {
241 $this->_parsed = true;
247 * To check CSV heading
249 * @param bool $heading true of it has header, false if not
250 * @return bool true if header is found, false if error
252 public function hasHeader(&$heading, $module) {
254 if (!$this->_parsed) {
258 $total_count = count($this->_parser->data[0]);
259 if ($total_count == 0) {
263 if (!isset($GLOBALS['beanList'][$module])) {
267 $bean = new $GLOBALS['beanList'][$module]();
271 $mod_strings = return_module_language($GLOBALS['current_language'], $module);
273 // process only the first row
274 foreach ($this->_parser->data[0] as $val) {
275 foreach ($bean->field_defs as $field_name=>$defs) {
277 // check if the CSV item matches field name
278 if (!strcasecmp($val, $field_name)) {
282 // check if the CSV item is part of the label or vice versa
283 else if (isset($defs['vname']) && isset($mod_strings[$defs['vname']])) {
284 if (stripos(trim($mod_strings[$defs['vname']],':'), $val) !== false || stripos($val, trim($mod_strings[$defs['vname']],':')) !== false) {
289 else if (isset($defs['vname']) && isset($GLOBALS['app_strings'][$defs['vname']])) {
290 if (stripos(trim($GLOBALS['app_strings'][$defs['vname']],':'), $val) !== false || stripos($val, trim($GLOBALS['app_strings'][$defs['vname']],':')) !== false) {
298 // if more than 50% matched, consider it a header
299 if ($match_count/$total_count >= 0.5) {
310 * To get the possible format (for date or time)
312 * @param array $formats
313 * @return mixed possible format if found, false otherwise
315 protected function getFormat(&$formats) {
317 if (!$this->_parsed) {
323 foreach ($this->_parser->data as $row) {
325 foreach ($row as $val) {
327 foreach ($formats as $format=>$regex) {
329 $ret = preg_match($regex, $val);
336 // give up if reaching max depth
338 if ($depth > $this->_max_depth) {
348 * To get the possible date format used in the csv file
350 * @return mixed possible date format if found, false otherwise
352 public function getDateFormat() {
354 $format = $this->getFormat(self::$_date_formats);
361 * To get the possible time format used in the csv file
363 * @return mixed possible time format if found, false otherwise
365 public function getTimeFormat() {
367 $format = $this->getFormat(self::$_time_formats);