的片段
* @param int $tableIndex 第幾個 table (0-based)
* @return array [ ['header'=>[], 'rows'=>[[]]] ]
*/
function htmlTableToArray(string $html, int $tableIndex = 0): array
{
libxml_use_internal_errors(true);
$dom = new DOMDocument();
$dom->loadHTML('' . $html);
libxml_clear_errors();
$tables = $dom->getElementsByTagName('table');
if ($tables->length <= $tableIndex) return [];
$table = $tables->item($tableIndex);
$rows = $table->getElementsByTagName('tr');
$grid = []; // 虛擬網格,儲存 [row][col] = cellData
$maxCols = 0;
foreach ($rows as $rowIdx => $tr) {
$cells = $tr->getElementsByTagName('td');
if ($cells->length === 0) {
$cells = $tr->getElementsByTagName('th'); // thead 可能用 th
}
$colIdx = 0;
foreach ($cells as $cell) {
// 找下一個可用欄位 (跳過被 rowspan/colspan 佔用的)
while (isset($grid[$rowIdx][$colIdx])) $colIdx++;
$rowspan = (int)$cell->getAttribute('rowspan');
$colspan = (int)$cell->getAttribute('colspan');
$rowspan = $rowspan > 0 ? $rowspan : 1;
$colspan = $colspan > 0 ? $colspan : 1;
$text = trim($cell->textContent);
// 可選:保留 HTML
// $htmlContent = $dom->saveHTML($cell);
// 填入虛擬網格
for ($r = 0; $r < $rowspan; $r++) {
for ($c = 0; $c < $colspan; $c++) {
$grid[$rowIdx + $r][$colIdx + $c] = [
'text' => $text,
'is_merged' => ($r > 0 || $c > 0),
'rowspan' => $rowspan,
'colspan' => $colspan,
];
}
}
$colIdx += $colspan;
$maxCols = max($maxCols, $colIdx);
}
}
// 將網格轉為陣列 (補齊缺失欄位)
$result = [];
for ($r = 0; $r < count($grid); $r++) {
$row = [];
for ($c = 0; $c < $maxCols; $c++) {
$row[] = $grid[$r][$c]['text'] ?? '';
}
$result[] = $row;
}
// 分離表頭 (假設第一行或 thead)
$header = array_shift($result);
return ['header' => $header, 'rows' => $result];
}
// 使用
$html = file_get_contents('https://example.com/report.html');
$tableData = htmlTableToArray($html);
print_r($tableData['header']);
print_r($tableData['rows']);
進階:直接對應關聯陣列 (以表頭為鍵)
function tableToAssocArray(string $html, int $tableIndex = 0): array
{
$data = htmlTableToArray($html, $tableIndex);
$header = $data['header'];
$rows = $data['rows'];
$assoc = [];
foreach ($rows as $row) {
// 若欄位數不足,補空值
$row = array_pad($row, count($header), '');
$assoc[] = array_combine($header, $row);
}
return $assoc;
}
/*
輸出範例:
[
['姓名' => '張三', '年齡' => '25', '城市' => '台北'],
['姓名' => '李四', '年齡' => '30', '城市' => '台中'],
]
*/
資料清洗與型別轉換
function cleanTableData(array $assocData, array $typeMap = []): array
{
return array_map(function ($row) use ($typeMap) {
foreach ($row as $key => $value) {
$type = $typeMap[$key] ?? 'string';
$row[$key] = match ($type) {
'int' => (int)filter_var($value, FILTER_SANITIZE_NUMBER_INT),
'float' => (float)filter_var($value, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION),
'date' => \DateTime::createFromFormat('Y/m/d', $value)?->format('Y-m-d') ?? $value,
'bool' => in_array(strtolower($value), ['true', '1', 'yes', '是', 'y']),
default => trim($value),
};
}
return $row;
}, $assocData);
}
// 使用
$clean = cleanTableData($assocData, [
'年齡' => 'int',
'薪資' => 'float',
'入職日期' => 'date',
'是否主管' => 'bool',
]);
大表格串流處理 (記憶體優化)
function parseLargeTableStream(string $filePath, callable $rowCallback): void
{
// 使用 XMLReader (SAX 模式) 逐行解析,不載入整份文件
$reader = new XMLReader();
$reader->open($filePath);
$inTable = $inRow = false;
$currentRow = [];
$cellBuffer = '';
while ($reader->read()) {
if ($reader->nodeType === XMLReader::ELEMENT) {
if ($reader->name === 'table') $inTable = true;
if (!$inTable) continue;
if ($reader->name === 'tr') { $inRow = true; $currentRow = []; }
if ($inRow && in_array($reader->name, ['td', 'th'])) {
$cellBuffer = '';
}
} elseif ($reader->nodeType === XMLReader::TEXT && $inRow) {
$cellBuffer .= $reader->value;
} elseif ($reader->nodeType === XMLReader::END_ELEMENT) {
if ($inRow && in_array($reader->name, ['td', 'th'])) {
$currentRow[] = trim($cellBuffer);
}
if ($reader->name === 'tr') {
$inRow = false;
$rowCallback($currentRow); // 逐行回調處理
}
if ($reader->name === 'table') break;
}
}
$reader->close();
}
- 上一篇文章: PHP 解析網頁所有超連結:DOMDocument 健壯提取、相對轉絕對、錨點文字、過濾去重
- 下一篇文章: PHP 常用工具靜態類:隨機字串、加密解密、驗證正則與字符串過濾
Copyright ©
程式碼之家 All Rights Reserved