PHP에서 XML을 파싱할 때 인코딩 문제, 잘못된 형식, 네임스페이스 처리 등으로 인한 오류가 자주 발생합니다. 다음은 주요 원인과 해결책들입니다.
문제: XML 인코딩 불일치로 인한 파싱 실패
해결책:
function parseXmlWithEncoding($xmlString) {
// UTF-8로 변환
$encoding = mb_detect_encoding($xmlString, ['UTF-8', 'EUC-KR', 'CP949'], true);
if ($encoding !== 'UTF-8') {
$xmlString = mb_convert_encoding($xmlString, 'UTF-8', $encoding);
}
// BOM 제거
$xmlString = preg_replace('/^\xEF\xBB\xBF/', '', $xmlString);
libxml_use_internal_errors(true);
$xml = simplexml_load_string($xmlString);
if ($xml === false) {
$errors = libxml_get_errors();
throw new Exception('XML parsing failed: ' . $errors[0]->message);
}
return $xml;
}
문제: 형식이 잘못된 XML로 인한 파싱 에러
해결책:
function fixAndParseXml($xmlString) {
// 일반적인 XML 오류 수정
$xmlString = trim($xmlString);
// 닫히지 않은 태그 감지 및 수정
$xmlString = preg_replace('/<([^>\/\s]+)([^>]*)>([^<]*)<\/([^>]+)>/',
'<$1$2>$3</$1>', $xmlString);
// 특수문자 이스케이프
$xmlString = str_replace(['&', '<', '>', '"', "'"],
['&', '<', '>', '"', '''],
$xmlString);
// XML 헤더가 없으면 추가
if (strpos($xmlString, '<?xml') === false) {
$xmlString = '<?xml version="1.0" encoding="UTF-8"?>' . $xmlString;
}
$dom = new DOMDocument();
$dom->recover = true; // 복구 모드
$dom->strictErrorChecking = false;
if (!$dom->loadXML($xmlString)) {
throw new Exception('Cannot fix XML structure');
}
return simplexml_import_dom($dom);
}
문제: 메모리 부족으로 인한 대용량 XML 파싱 실패
해결책:
function parseXmlStream($xmlFile, $targetElement) {
$reader = new XMLReader();
if (!$reader->open($xmlFile)) {
throw new Exception('Cannot open XML file');
}
$results = [];
while ($reader->read()) {
if ($reader->nodeType == XMLReader::ELEMENT &&
$reader->localName == $targetElement) {
$doc = new DOMDocument();
$node = $doc->importNode($reader->expand(), true);
$doc->appendChild($node);
$results[] = simplexml_import_dom($doc);
// 메모리 정리
unset($doc, $node);
}
}
$reader->close();
return $results;
}
문제: 네임스페이스가 있는 XML 파싱 실패
해결책:
function parseXmlWithNamespace($xmlString) {
$xml = simplexml_load_string($xmlString);
if ($xml === false) {
throw new Exception('XML parsing failed');
}
// 네임스페이스 정보 추출
$namespaces = $xml->getNamespaces(true);
$result = [];
foreach ($namespaces as $prefix => $uri) {
$elements = $xml->xpath("//{$prefix}:*");
foreach ($elements as $element) {
$result[] = [
'name' => $element->getName(),
'value' => (string)$element,
'namespace' => $uri
];
}
}
return $result;
}
// 특정 네임스페이스 요소 접근
function getNamespacedElements($xml, $namespace, $element) {
$xml->registerXPathNamespace('ns', $namespace);
return $xml->xpath("//ns:$element");
}
문제: 스키마에 맞지 않는 XML 데이터 처리
해결책:
function validateXmlWithXSD($xmlString, $xsdFile) {
$dom = new DOMDocument();
if (!$dom->loadXML($xmlString)) {
return ['valid' => false, 'errors' => ['Invalid XML format']];
}
libxml_use_internal_errors(true);
$isValid = $dom->schemaValidate($xsdFile);
$errors = [];
if (!$isValid) {
$xmlErrors = libxml_get_errors();
foreach ($xmlErrors as $error) {
$errors[] = "Line {$error->line}: {$error->message}";
}
libxml_clear_errors();
}
return ['valid' => $isValid, 'errors' => $errors];
}
// DTD 검증
function validateXmlWithDTD($xmlString) {
$dom = new DOMDocument();
$dom->validateOnParse = true;
libxml_use_internal_errors(true);
$result = $dom->loadXML($xmlString);
$errors = libxml_get_errors();
libxml_clear_errors();
return ['valid' => $result, 'errors' => $errors];
}
문제: CDATA 내용이 제대로 파싱되지 않음
해결책:
function parseCDATA($xmlString) {
$dom = new DOMDocument();
$dom->loadXML($xmlString);
$xpath = new DOMXPath($dom);
$cdataNodes = $xpath->query('//text()[normalize-space()]');
$result = [];
foreach ($cdataNodes as $node) {
if ($node->nodeType === XML_CDATA_SECTION_NODE) {
$result[] = [
'parent' => $node->parentNode->nodeName,
'content' => $node->nodeValue
];
}
}
return $result;
}
// CDATA가 포함된 XML 안전하게 파싱
function parseXmlWithCDATA($xmlString) {
// CDATA 마커 제거하고 내용만 추출
$xmlString = preg_replace_callback(
'/<!\[CDATA\[(.*?)\]\]>/s',
function($matches) {
return htmlspecialchars($matches[1]);
},
$xmlString
);
return simplexml_load_string($xmlString);
}
문제: XML 파싱 에러 원인 파악이 어려움
해결책:
function parseXmlWithErrorHandling($xmlString, $logFile = null) {
libxml_use_internal_errors(true);
libxml_clear_errors();
$xml = simplexml_load_string($xmlString);
if ($xml === false) {
$errors = libxml_get_errors();
$errorMessages = [];
foreach ($errors as $error) {
$message = sprintf(
"Level: %s, Line: %d, Column: %d, Message: %s",
$error->level === LIBXML_ERR_WARNING ? 'Warning' :
($error->level === LIBXML_ERR_ERROR ? 'Error' : 'Fatal'),
$error->line,
$error->column,
trim($error->message)
);
$errorMessages[] = $message;
}
if ($logFile) {
error_log(implode("\n", $errorMessages), 3, $logFile);
}
throw new Exception('XML parsing failed: ' . implode('; ', $errorMessages));
}
return $xml;
}
문제: XML 데이터를 PHP 배열로 변환할 때 구조 손실
해결책:
function xmlToArray($xmlString) {
$xml = simplexml_load_string($xmlString);
if ($xml === false) {
throw new Exception('Invalid XML');
}
return json_decode(json_encode($xml), true);
}
// 더 정확한 변환
function xmlToArrayAdvanced($xmlNode) {
$array = [];
foreach ($xmlNode->children() as $child) {
$name = $child->getName();
if (count($child->children()) > 0) {
$array[$name][] = xmlToArrayAdvanced($child);
} else {
$array[$name][] = (string)$child;
}
}
// 단일 요소는 배열에서 제거
foreach ($array as $key => $value) {
if (count($value) === 1) {
$array[$key] = $value[0];
}
}
return $array;
}
문제: XXE(XML External Entity) 공격 취약점
해결책:
function parseXmlSecurely($xmlString) {
// 외부 엔티티 로딩 비활성화
$oldSetting = libxml_disable_entity_loader(true);
// 추가 보안 설정
libxml_use_internal_errors(true);
$dom = new DOMDocument();
$dom->resolveExternals = false;
$dom->substituteEntities = false;
$result = $dom->loadXML($xmlString, LIBXML_NOENT | LIBXML_DTDLOAD | LIBXML_DTDATTR);
// 설정 복원
libxml_disable_entity_loader($oldSetting);
if (!$result) {
throw new Exception('Secure XML parsing failed');
}
return simplexml_import_dom($dom);
}