preg_match_all 远程内容
preg_match_all on remote content
我尝试解析 iCal:
//open file
$calendar = file_get_contents('http://app.kigo.net/public/ics.php?c-7ca2eb67c1a7fa8b87b2434ed1096076-422-9871b35967bb29f999cd11ac72943011');
//debug purpose
echo $calendar;
//parse string
preg_match_all('#^BEGIN\:VEVENT.*?END\:VEVENT$#sm',$calendar,$results,PREG_SET_ORDER);
//output: empty!
print_r($results);
它returns一个空数组。
无论如何,如果我 copy/paste 另一个变量上的“$calendar”内容,并使用相同的正则表达式解析它,它就可以正常工作。
为什么当我直接从 file_get_contents 在 相同的字符串 上调用 preg_match_all 时,它会出错?
远程文件使用序列 CR LF 作为换行符,这就是锚 $
不匹配的原因。当您 copy/paste 将文件内容传送到(或从)一个默认仅使用 LF 作为换行符的应用程序时,序列 CR LF 可能会被 LF 悄悄替换,并且您的模式有效。
解决问题的几种方法:
1) 在你的模式中明确写下回车 return:
#^BEGIN:VEVENT.*?END:VEVENT\r$#sm
如果您不希望在匹配结束时出现回车 return,请使用 trim
或将其置于先行断言中:#^BEGIN:VEVENT.*?END:VEVENT(?=\r$)#sm
。
您还可以删除 $
并使用匹配 \r
、\r\n
和 \n
.
的 \R
别名
2) 允许 $
使用指令 (*ANYCRLF)
匹配任何换行序列
#(*ANYCRLF)^BEGIN:VEVENT.*?END:VEVENT$#sm
3) 根本不要使用模式 (毕竟你只是在固定行之间寻找块,如果你的文件可能有点长,它会更优雅并节省内存逐行读取文件并使用生成器来 return 块):
$filePath = 'http://app.kigo.net/public/ics.php?c-7ca2eb67c1a7fa8b87b2434ed1096076-422-9871b35967bb29f999cd11ac72943011';
try {
if ( false === $fp = fopen($filePath, 'rb') )
throw new Exception('Could not open the file!');
} catch (Exception $e) {
echo 'Error (File: ' . $e->getFile() . ', line ' . $e->getLine() . '): ' . $e->getMessage();
}
foreach (genBlocks($fp, "BEGIN:VEVENT\r\n", "END:VEVENT\r\n") as $block) {
echo $block . PHP_EOL;
}
fclose($fp);
function genBlocks($fp, $start, $end, $buffer = 1024) {
$block = false;
while ( false !== $line = fgets($fp, $buffer) ) {
if ( $line === $start ) {
$block = $line;
} elseif ( $block !== false ) {
$block .= $line;
if ( $line === $end ) {
yield $block;
$block = false;
}
}
}
}
注意:您也可以使用 stream_get_line
而不是 fgets
因为这个可以 return 没有换行序列的行。
我尝试解析 iCal:
//open file $calendar = file_get_contents('http://app.kigo.net/public/ics.php?c-7ca2eb67c1a7fa8b87b2434ed1096076-422-9871b35967bb29f999cd11ac72943011'); //debug purpose echo $calendar; //parse string preg_match_all('#^BEGIN\:VEVENT.*?END\:VEVENT$#sm',$calendar,$results,PREG_SET_ORDER); //output: empty! print_r($results);
它returns一个空数组。
无论如何,如果我 copy/paste 另一个变量上的“$calendar”内容,并使用相同的正则表达式解析它,它就可以正常工作。
为什么当我直接从 file_get_contents 在 相同的字符串 上调用 preg_match_all 时,它会出错?
远程文件使用序列 CR LF 作为换行符,这就是锚 $
不匹配的原因。当您 copy/paste 将文件内容传送到(或从)一个默认仅使用 LF 作为换行符的应用程序时,序列 CR LF 可能会被 LF 悄悄替换,并且您的模式有效。
解决问题的几种方法:
1) 在你的模式中明确写下回车 return:
#^BEGIN:VEVENT.*?END:VEVENT\r$#sm
如果您不希望在匹配结束时出现回车 return,请使用 trim
或将其置于先行断言中:#^BEGIN:VEVENT.*?END:VEVENT(?=\r$)#sm
。
您还可以删除 $
并使用匹配 \r
、\r\n
和 \n
.
\R
别名
2) 允许 $
使用指令 (*ANYCRLF)
#(*ANYCRLF)^BEGIN:VEVENT.*?END:VEVENT$#sm
3) 根本不要使用模式 (毕竟你只是在固定行之间寻找块,如果你的文件可能有点长,它会更优雅并节省内存逐行读取文件并使用生成器来 return 块):
$filePath = 'http://app.kigo.net/public/ics.php?c-7ca2eb67c1a7fa8b87b2434ed1096076-422-9871b35967bb29f999cd11ac72943011';
try {
if ( false === $fp = fopen($filePath, 'rb') )
throw new Exception('Could not open the file!');
} catch (Exception $e) {
echo 'Error (File: ' . $e->getFile() . ', line ' . $e->getLine() . '): ' . $e->getMessage();
}
foreach (genBlocks($fp, "BEGIN:VEVENT\r\n", "END:VEVENT\r\n") as $block) {
echo $block . PHP_EOL;
}
fclose($fp);
function genBlocks($fp, $start, $end, $buffer = 1024) {
$block = false;
while ( false !== $line = fgets($fp, $buffer) ) {
if ( $line === $start ) {
$block = $line;
} elseif ( $block !== false ) {
$block .= $line;
if ( $line === $end ) {
yield $block;
$block = false;
}
}
}
}
注意:您也可以使用 stream_get_line
而不是 fgets
因为这个可以 return 没有换行序列的行。