PHP XML 파싱 오류 문제와 해결책

프리터코더·2025년 5월 31일
0

php 문제 해결

목록 보기
50/79

PHP에서 XML을 파싱할 때 인코딩 문제, 잘못된 형식, 네임스페이스 처리 등으로 인한 오류가 자주 발생합니다. 다음은 주요 원인과 해결책들입니다.

1. 인코딩 문제 해결

문제: 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;
}

2. 잘못된 XML 구조 처리

문제: 형식이 잘못된 XML로 인한 파싱 에러

해결책:

function fixAndParseXml($xmlString) {
    // 일반적인 XML 오류 수정
    $xmlString = trim($xmlString);
    
    // 닫히지 않은 태그 감지 및 수정
    $xmlString = preg_replace('/<([^>\/\s]+)([^>]*)>([^<]*)<\/([^>]+)>/', 
                             '<$1$2>$3</$1>', $xmlString);
    
    // 특수문자 이스케이프
    $xmlString = str_replace(['&', '<', '>', '"', "'"], 
                            ['&amp;', '&lt;', '&gt;', '&quot;', '&apos;'], 
                            $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);
}

3. 대용량 XML 파일 처리

문제: 메모리 부족으로 인한 대용량 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;
}

4. XML 네임스페이스 처리

문제: 네임스페이스가 있는 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");
}

5. XML 유효성 검사

문제: 스키마에 맞지 않는 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];
}

6. CDATA 섹션 처리

문제: 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);
}

7. 에러 핸들링 및 로깅

문제: 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;
}

8. 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;
}

9. 외부 엔티티 보안 처리

문제: 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);
}
profile
일용직 개발자. freetercoder@gmail.com

0개의 댓글