处理CSV文件从Excel到MySQL生成“不正确的string值”错误

我已经通过博客,Google和堆栈溢出进行了大量search。 我还没有find我的问题的工作解决scheme。

在我的PHP应用程序中,它允许用户下载一个csv模板(包含标题),以便将数据导入到系统中。 除非在CSV文件中使用特殊/外部字符(Umlaut,Acute,Grave)来导入其中一行,否则一切都会很好。

用户正在下载CSV,然后在Excel中打开它(在安装了Office的大多数系统上是默认的)。 从我看到和理解的时候,他们添加到他们想要导入的文件的一切,并单击保存在Excel中,它不正确的编码。 一旦他们上传更改后的文件,PHP将迭代CSV插入数据到MySQL数据库中,则会失败,如第1行“无法更新logging1366:不正确的string值:'\ x9Arn's …'列'rawContents' 。

我不是在寻找像“不要使用Excel”的解决scheme,因为这不是一个选项。 我正在寻找一个解决scheme来获取上传的文件,并确保编码设置为UTF-8,以便正确读入数据库。 目前我正在捕捉exception,如果它包含错误“不正确的string值”我输出一个友好的消息,用户有无效的数据,检查编码,然后再试一次。 我希望能够处理他们的CSV无论和无效数据行(如果我不能读取它)将被忽略和存储为我所谓的“错误行”(任何行包含一个错误(用户input无效通过validation的列),他们可以看到什么行,为什么,并导出另一个只包含错误的行的CSV)

我希望这不是太混乱或不清楚。 我find了一种方法来检测一个非UTF8字符的行,使用以下内容:

function utf8_clean($str, $ignore = true) { return iconv('UTF-8', 'UTF-8//' . (($ignore) ? 'IGNORE' : 'TRANSLIT'), $str); } function contains_non_utf8($str) { return (serialize($str) != serialize(utf8_clean($str))); } 

如果有一些方法来修复编码,并获得正确的字符编码来存储它,那就太棒了。 我想要做的第二个select是我提到的“错误行”,所以如果我不能以正确的编码得到它,我想存储它导出“错误行”CSV文件来解决这些错误。 但我不知道如何存储该行的“原始”包含,以允许将其导出为CSV中的错误行。

请随时向我提出想法,我可以做些什么。 我想过的一个select是支持Excel文件导入,因为它似乎保留UTF-8编码保存,如果设置在模板文件,但我真的很想看到仍然支持CSV的方式。


我试图用“macroman”来获取数据,看起来非常有效,但是也遇到了这个问题。

现在我有一个try / catch语句类似于:

 try { $this->saveImportRow($array) } catch (Exception $e) { if ($e->getCode() === 1366) { $dbClass->execute('SET NAMES \'macroman\''); $this->saveImportRow($array) $dbClass->execute('SET NAMES \'utf8\' COLLATE \'utf8_unicode_ci\''); } } 

这样做是试图将CSV数据保存到数据库,如果它失败,错误代码1366然后它将再次尝试,但使用“macroman”保存之前。 这似乎工作正常,并允许同时导入在Excel中打开并保存但包含特殊字符(即ö)和Excel的CSV文件并不保存与适当的编码。 它还允许编码为UTF-8并包含特殊字符(即ö)的CSV文件。

现在的问题是拉出数据并使用它(处理导入)。

当数据被保存时,它被放入数组中进行映射(键是数据库所属的数据列),这个数组被序列化并作为parsedData保存在数据库中。 这个问题是反序列化这些数据。 当使用utf-8插入行时,不会像以往那样对数据进行反序列化,但是,如果CSV文件中的编码不正确,则会发生新的更改以将特殊字符导入数据库,这意味着它使用“ macroman一致“。

如果在select行之前执行“SET NAMES'macroman'”,那么使用“macroman”插入的行将不可序列化,但UTF-8插入的行不是不可序列化的。 非常令人沮丧。 有任何想法吗?

我知道我的目标只是让用户知道编码是不正确的,但我认为这是有趣的,我可以让他们进入数据库,出口和使用macroman正确导入,只是不一致,如果有一个合适的编码CSV上传。 也许我需要进行自我导入,以确定它是否是“macroman”,因为我可以假设它是否必须在CSV文件中插入1行作为macroman,那么整个文件的编码是错误的。 或者我想我的目标是有点满足,因为我知道我可以将行标记为无效编码的特殊字符,并让他们知道修复它们的编码。 但是我确定所有人都更喜欢更多的手动方式。

也许导入过程需要一个完整的反思/刷新,但我不知道。 更多评论/解决scheme/想法将不胜感激。


经过努力与转换和更多的研究,我遇到了一些推理检测是否是Mac罗马或Windows-1252编码(当在Excel中打开CSV文件,进行更改和保存时的默认编码)。

这是我想到的逻辑:*如果string包含以下字节之一,则假定MacRoman:0x8E,0x8F,0x9A,0xA1,0xA5,0xA8,0xD0,0xD1,0xD5,0xE1 *如果string包含下列之一然后假定Windows-1252:0x92,0x95,0x96,0x97,0xAE,0xB0,0xB7,0xE8,0xE9,0xF6

所以使用contains_non_utf8函数来检测一个string中第一个字符是不是utf-8编码的,然后是一个函数来检测它是MacRoman还是Windows-1252。 之后,我可以运行iconv('MACROMAN','UTF-8',$ str)或iconv('Windows-1252','UTF-8',$ str)来接收UTF-8有效string。

这是我为探测字节提出的两个新function

 function isMacRomanEncoded($str) { $testBytes = array(0x8E, 0x8F, 0x9A, 0xA1, 0xA5, 0xA8, 0xD0, 0xD1, 0xD5, 0xE1); foreach ($testBytes as $testByte) { if (mb_strpos($str, chr($testByte)) !== false) { return true; } } return false; } function isWindows1252Encoded($str) { $testBytes = array(0x92, 0x95, 0x96, 0x97, 0xAE, 0xB0, 0xB7, 0xE8, 0xE9, 0xF6); foreach ($testBytes as $testByte) { if (mb_strpos($str, chr($testByte)) !== false) { return true; } } return false; } 

还有一个想法是,如果contains_non_utf8是真的,那么也首先做一个“mb_detect_encoding”,然后尝试进一步MacRoman / Windows-1252检测。

根据我的经验,这里发生的事情是Excel默认为默认编码,因为您的模板csv文件不包含BOM(字节顺序标记)。

由于csv文件是文本,如果你在php中创build模板(你确定文件truley的内容是utf-8),你可以强制在Excel中正确打开该文件(至less对于Windows)如下所示:

 $filecontents = chr(239) . chr(187) . chr(191) . $filecontents; 

现在假设你在Windows上,记事本++也会为你添加一个BOM到一个文本文件,所以你也可以这样编辑模板。

另一个有用的东西(testing导入的潜在编码问题 – 后Excel)是首先打开记事本中的文件,并保存为utf-8,然后导入,看看是否可以解决这个问题。

现在提供用户不会改变编码时,他们保存Excel应该默认为UTF-8,现在应该读取到PHP罚款。

我将继续前进,用我最终解决的解决scheme回答自己的问题。

正如你将在上面的问题中读到的,我添加的最后一个更新几乎是最终的解决scheme。

从CSV读取数据时,我使用is_non_utf8检查string,如果为true,则运行以下逻辑:*如果string包含以下字节之一,则假定MacRoman:0x8E,0x8F,0x9A,0xA1,0xA5,0xA8,0xD0 ,0xD1,0xD5,0xE1 *如果string包含以下字节之一,则假定Windows-1252:0x92,0x95,0x96,0x97,0xAE,0xB0,0xB7,0xE8,0xE9,0xF6

如果以上其中一个是真实/假设,那么我使用iconv将string转换为UTF-8。 如果不是的话,我对这个string什么都不做,继续照常。

所以使用contains_non_utf8函数来检测一个string中第一个字符是不是utf-8编码的,然后是一个函数来检测它是MacRoman还是Windows-1252。 之后,我可以运行iconv('MACROMAN','UTF-8',$ str)或iconv('Windows-1252','UTF-8',$ str)来接收UTF-8有效string。

在插入我已经包装插入查询到一个try / catch语句,在catch中我寻找错误代码“1366”,如果这是真的,我更新数据行以排除数据,但标记为错误的行logging一条错误消息。 虽然这永远不允许我提供导出给用户的数据,我无法导入它确实提供了他们的行号,以便他们可以看看上传文件,他们用来确定logging失败import。

所以你有它。 这就是我如何实现用户下载模板CSV,在Excel(Mac或Windows)中打开它,添加包含变音符号(或UTF-8中提供的另一个外部字符)的数据的function,点击保存,select文件文件的htmlinput,提交,并成功/正确导入。 它会在下个月内使用,所以如果有其他事情出现,我一定会更新这张票的细节。

以下是我正在使用的function:

 function isMacRomanEncoded($str) { $testBytes = array(0x8E, 0x8F, 0x9A, 0xA1, 0xA5, 0xA8, 0xD0, 0xD1, 0xD5, 0xE1); foreach ($testBytes as $testByte) { if (mb_strpos($str, chr($testByte)) !== false) { return true; } } return false; } function isWindows1252Encoded($str) { $testBytes = array(0x92, 0x95, 0x96, 0x97, 0xAE, 0xB0, 0xB7, 0xE8, 0xE9, 0xF6); foreach ($testBytes as $testByte) { if (mb_strpos($str, chr($testByte)) !== false) { return true; } } return false; } 

下面是一个catch语句的例子:

 try { return $this->saveImportRow($array) } catch (Exception $e) { if ($e->getCode() === 1366) { $array['dataColumn'] = null; $array['status'] = '2'; // 2 = Error $array['msg'] = 'Row contained invalid characters'; return $this->saveImportRow($array) } throw $e; } 

如果您有任何问题(或进一步的input)让我知道。

谢谢!