PHP - 按顺序填充数组,直到达到最大长度

PHP - Fill the arrays in a sequential manner until reaching a maximum length

我需要从以这种方式格式化的文本文件中提取数据,使用 PHP:

BEGIN
#1 
#2 
#3 
#4 
#5 
#6 
1       2015-05-31  2001-11-24  'Name Surname'      ID_1        0 
2       2011-04-01  ?           ?                   ID_2        1 
2       2013-02-24  ?           ?                   ID_3        1 
2       2014-02-28  ?           'Name Surname'      ID_4        2 
END

信息按照数组的逻辑进行组织,如下所示:

Array ( [#1] => 1 [#2] => 2015-05-31 [#3] => 2001-11-24 [#4] => 'Name Surname' [#5] => ID_1 [#6] => 0 )
Array ( [#1] => 2 [#2] => 2011-04-01 [#3] => ?           [#4] => ?             [#5] => ID_2 [#6] => 1 )
Array ( [#1] => 2 [#2] => 2013-02-24 [#3] => ?           [#4] => ?             [#5] => ID_3 [#6] => 1 )
Array ( [#1] => 2 [#2] => 2014-02-28 [#3] => ?           [#4] => 'Name Surname' [#5] => ID_4 [#6] => 2 )

我正在寻找获得该输出的方法。我正在使用此代码:

<?php 
    //ini_set('max_execution_time', 300); //300 seconds = 5 minutes

    function startsWith($str, $char){
        return $str[0] === $char;
    }

    $txt_path = "./test.txt";
    $txt_data = @file_get_contents($txt_path) or die("Could not access file: $txt_path");
    //echo $txt_data;

    $loop_pattern = "/BEGIN(.*?)END/s";
    preg_match_all($loop_pattern, $txt_data, $matches);
    $loops = $matches[0];
    //print_r($loops);
    $loops_count = count($loops);
    //echo $loops_count; // number of loops into the file
    foreach ($loops as $key => $value) {
        $value = trim($value);
        $pattern = array("/[[:blank:]]+/", "/BEGIN(.*)/", "/END(.*)/");
        $replacement = array(" ", "", "");
        $value = preg_replace($pattern, $replacement, $value);
        //print_r($value);
        //echo "<br><br>";
        $value_array = explode("\n", $value);
        $value_array_clean = array_filter($value_array, 'strlen');
        $value_array_clean_reindex = array_values($value_array_clean);
        //print_r($value_array_clean_reindex);
        //echo "<br><br>";
        $keys = array();
        $values = array();
        foreach ($value_array_clean_reindex as $key => $value) {
            $value = trim($value);
            if ( startsWith($value, "#") ) {
                array_push($keys, $value);
                $keys_count = count($keys);
            } else {
                array_push($values, $value);
                $values_count = count($values);

                $loop_dic = array();
                foreach ($values as $key => $value) {
                    $value = trim($value);
                    preg_match_all("/'(?:.|[^'])*'|\S+/", $value, $matches);
                    //print_r($matches[0]);
                    $loop_dic = array_combine($keys, $matches[0]);
                }

                print_r($loop_dic);
                echo "<br><br>";
            }
        }
    }
?>

它给了我想要的输出:

Array ( [#1] => 1 [#2] => 2015-05-31 [#3] => 2001-11-24 [#4] => 'Name Surname' [#5] => ID_1 [#6] => 0 )
Array ( [#1] => 2 [#2] => 2011-04-01 [#3] => ? [#4] => ? [#5] => ID_2 [#6] => 1 )
Array ( [#1] => 2 [#2] => 2013-02-24 [#3] => ? [#4] => ? [#5] => ID_3 [#6] => 1 )
Array ( [#1] => 2 [#2] => 2014-02-28 [#3] => ? [#4] => 'Name Surname' [#5] => ID_4 [#6] => 2 )

但有时会在命令级别出现问题:

$loop_dic = array_combine($keys, $matches[0]);

我了解到,在原始文本文件中,有很长的行,这些被打断,生成一个新行;而不是:

2       2014-02-28  ?           'Name Surname'      ID_4        2 

线断成这样:

2       2014-02-28  ?           'Name Surname'      
ID_4        2 

所以,当我用 \n 分解字符串时,我合并的两个数组的长度出现错误。

我会问你一个替代方案来解决这个问题,获得等长的数组,如果原始文件中出现中断也是如此。

在网上搜索,我找到了array_fill;也许,如果我知道(通过count)每个循环([#1],...,[#6])的数组中的键数,就可以循环并为值填充数组,按顺序添加它们,直到值的每个数组的最大长度。

感谢您的关注和帮助。

编辑#1

感谢@fusion3k 的解决方案! 检查一些输入文件的行为,它显示了另外两个问题:

1) 分析一些错误,发现有时候输入文件使用双引号(而不是单引号引号),并且在分号之间也有多行文本块,如下所示:

;This is some text
in multiline with "double 
quotes" too
;

需要将其视为给定键的单个值,该值需要内联,就像@fusion3k 代码所做的那样,将 \n 替换为 </code>(一个space)。我正在尝试将 @fusion3k 的工作代码与为解决此行为而精心设计的代码合并。文件结构可能是这样的:</p> <pre><code>BEGIN #1 #2 #3 #4 #5 #6 1 2015-05-31 2001-11-24 "Name Surname" ID_1 0 2 2011-04-01 ? ? ID_2 1 2 2013-02-24 ? ? ID_3 1 2 2014-02-28 ? "Name Surname" ID_4 2 ;This is some text in multiline with "double quotes" too ; 2016-01-22 ? "Name Surname" ID_5 2 END

应该会生成类似于上面的工作代码,但考虑到存在不同的文本块分隔符,例如 semicolon (;), 单引号 (') 或像在某些其他文件中一样,双引号 ("),以分隔必须被认为是一个键的单个值,就像在这个数组中相对于上面的文本文件内容:

Array ( [#1] => Array ( [0] => 1 [1] => 2 [2] => 2 [3] => 2 [4] => This is some text in multiline with "double quotes" too ) [#2] => Array ( [0] => 2015-05-31 [1] => 2011-04-01 [2] => 2013-02-24 [3] => 2014-02-28 [4] => 2016-01-22 ) [#3] => Array ( [0] => 2001-11-24 [1] => ? [2] => ? [3] => ? [4] => ? ) [#4] => Array ( [0] => Name Surname [1] => ? [2] => ? [3] => Name Surname [4] => Name Surname ) [#5] => Array ( [0] => ID_1 [1] => ID_2 [2] => ID_3 [3] => ID_4 [4] => ID_5 ) [#6] => Array ( [0] => 0 [1] => 1 [2] => 1 [3] => 2 [4] => 2 ) )

我处理了一个简单的字符串,以找到考虑 (分号) 和 (单引号 的 "working" 正则表达式] 或 双引号 )。现在我还没有找到使用所有三个定界符来定界文本块的文件,但似乎可以找到 semicolon+single_quotes 分号+double_quotes 或仅 single_quotes或仅 double_quotes;最好在同一个文本文件中找到所有三种类型的定界符的解决方案...:

$string = 'something here 
;and there
;
oh, "that\'s all!"';
$string = str_replace( "\n", " ", $string );
$origin = array("/[[:blank:]]+/", "/\"/", "/;/");
$replacement = array(" ", "\" ", "; ");
$string = preg_replace($origin, $replacement, $string);
$pattern = '/([;"])\s+/';
print_r(array_filter(preg_split( $pattern, $string ), 'strlen'));

这是输出(根据需要):

Array ( [0] => something here [1] => and there [2] => oh, [3] => that's all! )

注意 分号 之间的文本块:它总是在新行中开始,开头有一个 分号,然后结束在新行中使用 分号,然后开始另一个新行。

我不知道它是否可以用更好更快的方式编写...然后我尝试将它与@fusion3k 的代码合并,处理上述文本文件内容,但没有成功。我尝试了一个像这样的 if/elseif/else 构造:

if ( preg_match('/;(.*?);|\'(.*?)\'/', $value, $matches) ) {// semicolon with single quotes in the $value string
    $value = str_replace( "\n", " ", $value );
    $origin = array("/[[:blank:]]+/", "/'/", "/;/");
    $replacement = array(" ", "' ", "; ");
    $value = preg_replace($origin, $replacement, $value);
    $pattern = '/'.str_repeat( "([;'])\s+", count( $keys ) ).'/';
    print_r(array_filter(preg_split( $pattern, $value ), 'strlen')); // I would have an array of values of the same length of the array for the keys
    echo "<br><br>";
} elseif ( preg_match('/;(.*?);|"(.*?)"/', $value, $matches) ) {// semicolon with double quotes in the $value string
    $value = str_replace( "\n", " ", $value );
    $origin = array("/[[:blank:]]+/", "/\"/", "/;/");
    $replacement = array(" ", "\" ", "; ");
    $value = preg_replace($origin, $replacement, $value);
    $pattern = '/'.str_repeat( "([;\"])\s+", count( $keys ) ).'/';
    print_r(array_filter(preg_split( $pattern, $value ), 'strlen')); // I would have an array of values of the same length of the array for the keys
    echo "<br><br>";
} else {// neither single quotes (or double quotes) nor semicolon in the $value string
    $pattern = '/'.str_repeat( "(\S+)\s+", count( $keys ) ).'/';
    preg_match_all( $pattern, $value, $matches );
    //print_r($matches);
    //echo "<br><br>";
    $loop_dic = array_combine( $keys, array_slice( $matches, 1 ) );
    print_r( $loop_dic ); // this is good...maybe in a better way?
    echo "<br><br>";
}

唯一有效的代码是最后一个 else,它使用了 @fusion3k 的代码。

2) 第二个行为(可能已经解决)发生在文件非常大的时候。命令:

$loop_pattern = "/BEGIN(.*?)END/s";
preg_match_all($loop_pattern, $txt_data, $matches);
$loops = $matches[0];
//print_r($loops);
$loops_count = count($loops);
//echo $loops_count; // number of loops into the file

不采用文件(大文件)中的所有循环。 我认为可能答案是 here。所以,设置:

ini_set('max_execution_time', 300); // 300 seconds = 5 minutes
ini_set("pcre.backtrack_limit", "100000000"); // default 100k = "100000"

似乎可以解决,但我不知道这是否是唯一的方法:确实,如果文件很大(17MB 或以上),浏览器会有一点不响应时间(我正在测试在 Firefox 上是最新的),在页面加载完成之前......最好分块解析整个文件直到它的完整大小,也许,但如何去做?

非常感谢您的关注和帮助

要解决您的问题,常见的方法是对检索到的匹配项进行计数,如果匹配项少于键值,则继续循环而不重新初始化 $loop_dic

我向您推荐一种 inverted 方法:不是逐行分解字符串,而是在检索值之前用空格替换换行符:您的字符串结构足够坚固,可以采用这种方法,并且你知道字段编号,所以这种方法应该有效。

foreach 主循环外的代码没有改变。同样,检索被BEGIN ... END包裹的文本的代码保持不变:

foreach( $loops as $key => $value ) 
{
    $value = trim( $value );
    $pattern = array( "/[[:blank:]]+/", "/BEGIN(.*)/", "/END(.*)/" );
    $replacement = array( " ", "", "" );
    $value = preg_replace( $pattern, $replacement, $value );

为了检索密钥,我们使用 preg_match_all(),然后我们使用 preg_replace():

删除相关行
    preg_match_all( '/^#\d+/m', $value, $matches );
    $keys = $matches[0];

    $value = preg_replace( '/^#\d+\s*/m', '', $value );

现在,在$value中我们只有数据线。我们用空格替换所有换行符:

    $value = str_replace( "\n", " ", $value );

然后,我们通过重复键编号的字段模式来构造行模式,并通过 preg_match_all():

检索所有行
    $pattern = '/'.str_repeat( "('[^']+'|\S+)\s+", count( $keys ) ).'/';
    preg_match_all( $pattern, $value, $matches );

最后,我们使用array_slice()删除全局匹配,我们将其与$keys结合起来,得到了我们想要的结果。 foreach循环可以关闭:

    $values = array_combine( $keys, array_slice( $matches, 1 ) );
}

ideone demo

我的 $values 和你的 $loop_dic 之间的主要区别在于,在 $values 主数组中你有列,但如果你更喜欢按行排列的数组,你可以轻松地转换它.

我用许多不同的“断线”测试了代码,并且它有效。我建议你用不同的字符串仔细测试它,看看它是否在任何情况下都能正常工作。