우선 나는 컴퓨터 표준을 좋아한다는 점을 인정해야 한다. 모두가 업계 표준을 따른다면 인터넷은 더 나은 매체가 될 것입니다. 표준화된 데이터 교환 형식을 사용하면 개방형 및 플랫폼 독립적 컴퓨팅 모델이 가능해집니다. 이것이 바로 제가 XML 매니아인 이유입니다.
다행스럽게도 제가 가장 좋아하는 스크립팅 언어는 XML을 지원할 뿐만 아니라 점점 더 많이 지원하고 있습니다. PHP를 사용하면 XML 문서를 인터넷에 빠르게 게시하고, XML 문서에 대한 통계 정보를 수집하고, XML 문서를 다른 형식으로 변환할 수 있습니다. 예를 들어, 나는 XML로 쓴 기사와 책을 관리하기 위해 PHP의 XML 처리 기능을 자주 사용합니다.
이 기사에서는 XML 문서를 처리하기 위해 PHP에 내장된 Expat 파서를 사용하는 방법에 대해 설명합니다. 예시를 통해 Expat의 처리 방법을 보여드리겠습니다. 동시에 이 예에서는 다음 방법을 보여줍니다.
고유한 처리 기능 만들기
XML 문서를 자신만의 PHP 데이터 구조로 변환하기
소개
XML 프로세서라고도 불리는 Expat XML의 파서는 프로그램이 XML 문서의 구조와 내용에 접근할 수 있도록 해줍니다. Expat는 PHP 스크립팅 언어용 XML 파서입니다. Mozilla, Apache, Perl과 같은 다른 프로젝트에서도 사용됩니다.
이벤트 기반 파서란 무엇입니까?
XML 파서에는 두 가지 기본 유형이 있습니다.
트리 기반 파서: XML 문서를 트리 구조로 변환합니다. 이 유형의 파서는 결과 트리의 각 요소에 액세스할 수 있는 API를 제공하면서 전체 기사를 구문 분석합니다. 일반적인 표준은 DOM(Document Object Model)입니다.
이벤트 기반 파서: XML 문서를 일련의 이벤트로 처리합니다. 특별한 이벤트가 발생하면 파서는 이를 처리하기 위해 개발자가 제공한 함수를 호출합니다.
이벤트 기반 파서에는 XML 문서에 대한 데이터 중심 보기가 있습니다. 즉, XML 문서의 구조보다는 데이터 부분에 중점을 둡니다. 이러한 파서는 문서를 처음부터 끝까지 처리하고 콜백 함수를 통해 요소 시작, 요소 끝, 기능 데이터 시작 등과 같은 이벤트를 애플리케이션에 보고합니다. 다음은 "Hello-World"에 대한 XML 문서의 예입니다
.
안녕하세요 세계
</greeting>
이벤트 기반 구문 분석기는 세 가지 이벤트로 보고합니다.
시작 요소: 인사말
CDATA 항목의 시작 값은 Hello World입니다.
종료 요소: 인사말
트리 기반 파서와 달리 이벤트 기반 파서는 문서를 설명하는 구조를 생성하지 않습니다. CDATA 항목에서 이벤트 기반 파서는 상위 요소의 인사말 정보를 가져올 수 없습니다.
그러나 더 낮은 수준의 액세스를 제공하므로 리소스 활용도가 향상되고 액세스 속도가 빨라집니다. 이렇게 하면 전체 문서를 메모리에 맞출 필요가 없습니다. 실제로 전체 문서가 실제 메모리 값보다 클 수도 있습니다.
Expat은 그러한 이벤트 기반 파서입니다. 물론 Expat을 사용하면 필요한 경우 PHP에서 완전한 기본 트리 구조를 생성할 수도 있습니다.
위의 Hello-World 예제에는 완전한 XML 형식이 포함되어 있습니다. 그러나 연관된 DTD(Document Type Definition)도 없고 내장된 DTD도 없기 때문에 유효하지 않습니다.
Expat의 경우 이는 아무런 차이가 없습니다. Expat는 유효성을 확인하지 않는 파서이므로 문서와 관련된 모든 DTD를 무시합니다. 그러나 문서의 형식을 완전히 지정해야 한다는 점에 유의해야 합니다. 그렇지 않으면 Expat(다른 XML 호환 파서와 마찬가지로)가 오류 메시지와 함께 중지됩니다.
유효성을 확인하지 않는 파서인 Exapt의 속도와 가벼움은 인터넷 애플리케이션에 매우 적합합니다.
Expat 컴파일하기
Expat는 PHP3.0.6 버전(또는 그 이상)으로 컴파일할 수 있습니다. Apache 1.3.9부터 Expat이 Apache의 일부로 포함되었습니다. Unix 시스템에서는 -with-xml 옵션으로 PHP를 구성하여 PHP로 컴파일할 수 있습니다.
PHP를 Apache 모듈로 컴파일하면 Expat가 기본적으로 Apache의 일부로 포함됩니다. Windows에서는 XML 동적 링크 라이브러리를 로드해야 합니다.
XML 예제: XMLstats
Expat의 기능을 배우는 한 가지 방법은 예제를 통해서입니다. 우리가 논의할 예는 Expat을 사용하여 XML 문서에 대한 통계를 수집하는 것입니다.
문서의 각 요소에 대해 다음 정보가 출력됩니다.
문서에서 해당 요소가 사용된 횟수
이 요소의 문자 데이터 양
요소의 상위 요소
요소의 하위 요소
참고: 데모를 위해 PHP를 사용하여 요소의 상위 요소와 하위 요소를 저장하는 구조를 생성합니다.
XML 파서 인스턴스를 생성하기 위해
준비된
함수는 xml_parser_create()입니다.이 인스턴스는 향후 모든 기능에 사용됩니다. 이 아이디어는 PHP의 MySQL 함수 연결 태그와 매우 유사합니다. 문서를 구문 분석하기 전에 이벤트 기반 구문 분석기는 일반적으로 특정 이벤트가 발생할 때 호출될 콜백 함수를 등록하도록 요구합니다. Expat에는 다음과 같은 7가지 가능한 이벤트가 정의되어 있습니다.
객체 XML 구문 분석 함수 설명
요소 xml_set_element_handler()
요소의 시작 및 끝 문자 데이터 xml_set_character_data_handler() 문자 데이터의 시작
외부 엔터티 xml_set_external_entity_ref_handler() 외부 엔터티 구문 분석되지 않은
외부 엔터티 xml_set_unparsed_entity_decl_handler( ) 해결되지 않은 외부 엔터티 처리 명령 발생
xml_set_processing_instruction_handler() 처리 명령
표기 선언 발생 xml_set_notation_decl_handler() 표기 선언 발생
default xml_set_default_handler() 지정된 핸들러 함수가 없는 기타 이벤트
모든 콜백 함수는 다음의 인스턴스를 사용해야 합니다. 파서를 첫 번째 매개변수로 사용합니다(추가로 다른 매개변수도 있음).
이 기사 마지막 부분에 있는 샘플 스크립트를 참조하세요. 주의할 점은 요소 처리 기능과 문자 데이터 처리 기능을 모두 사용한다는 점이다. 요소의 콜백 핸들러 함수는 xml_set_element_handler()를 통해 등록됩니다.
이 함수는
파서의 인스턴스라는
세 가지 매개변수를 사용합니다.
시작 요소를 처리하는 콜백 함수의 이름
닫는 요소를 처리하는 콜백 함수의 이름
XML 문서 구문 분석이 시작될 때 콜백 함수가 존재해야 합니다. 이는 PHP 매뉴얼에 설명된 프로토타입과 일관되게 정의되어야 합니다.
예를 들어 Expat는 시작 요소에 대한 핸들러 함수에 세 개의 인수를 전달합니다. 스크립트 예제에서는 다음과 같이 정의됩니다:
function start_element($parser, $name, $attrs)
첫 번째 매개변수는 파서 식별자이고, 두 번째 매개변수는 시작 요소의 이름이며, 세 번째 매개변수는 모든 속성과 속성을 포함합니다. 요소 배열의 값.
XML 문서 구문 분석을 시작하면 Expat는 start_element() 함수를 호출하고 시작 요소를 만날 때마다 매개변수를 전달합니다.
XML의 Case Folding 옵션은
xml_parser_set_option() 함수를 사용하여 Case Folding 옵션을 끕니다. 이 옵션은 기본적으로 켜져 있으며, 핸들러 함수에 전달된 요소 이름이 자동으로 대문자로 변환됩니다. 그러나 XML은 대소문자를 구분합니다. 따라서 통계 XML 문서에서는 대소문자가 매우 중요합니다. 이 예에서는 케이스 접기 옵션을 꺼야 합니다.
문서 구문 분석
모든 준비가 완료되면 이제 스크립트는 마침내 XML 문서를 구문 분석할 수 있습니다.
사용자 정의 함수인 Xml_parse_from_file()은 매개 변수에 지정된 파일을 열고 4kb 크기로 구문 분석합니다.
xml_parse_from_file()과 마찬가지로 xml_parse()도 오류가 발생하면, 즉 XML 문서의 형식이 완전히 지정되지 않은 경우 false를 반환합니다.
xml_get_error_code() 함수를 사용하여 마지막 오류의 숫자 코드를 가져올 수 있습니다. 오류 텍스트 정보를 얻으려면 이 숫자 코드를 xml_error_string() 함수에 전달하세요.
XML의 현재 줄 번호를 출력하여 디버깅을 더 쉽게 만듭니다.
구문 분석 과정에서 콜백 함수가 호출됩니다.
문서 구조 설명
문서를 구문 분석할 때 Expat로 해결해야 할 질문은 문서 구조의 기본 설명을 어떻게 유지해야 하는가입니다.
앞서 언급했듯이 이벤트 기반 파서 자체는 구조적 정보를 생성하지 않습니다.
그러나 태그 구조는 XML의 중요한 기능입니다. 예를 들어 <book><title> 요소 시퀀스는 <Figure><title>과 다른 의미를 갖습니다. 즉, 모든 저자는 책 제목과 그림 제목이 둘 다 "제목"이라는 용어를 사용하더라도 서로 관련이 없다고 말할 것입니다. 따라서 이벤트 기반 파서로 XML을 효율적으로 처리하려면 자체 스택이나 목록을 사용하여 문서에 대한 구조적 정보를 유지해야 합니다.
문서 구조를 미러링하려면 스크립트는 최소한 현재 요소의 상위 요소를 알아야 합니다. 이는 Exapt의 API에서는 불가능합니다. 상황에 맞는 정보 없이 현재 요소의 이벤트만 보고합니다. 따라서 자신만의 스택 구조를 구축해야 합니다.
스크립트 예제에서는 FILO(선입선출) 스택 구조를 사용합니다. 배열을 통해 스택은 모든 시작 요소를 저장합니다. 시작 요소 처리 함수의 경우 현재 요소는 array_push() 함수에 의해 스택의 맨 위로 푸시됩니다. 이에 따라 끝 요소 처리 함수는 array_pop()을 통해 최상위 요소를 제거합니다.
<book><title></title></book> 시퀀스의 경우 스택은 다음과 같이 채워집니다.
시작 요소 book: "book"을 스택의 첫 번째 요소($stack[0])에 할당합니다.
시작 요소 제목: 스택 상단($stack[1])에 "제목"을 할당합니다.
끝 요소 제목: 스택($stack[1])에서 맨 위 요소를 제거합니다.
끝 요소 제목: 스택($stack[0])에서 맨 위 요소를 제거합니다.
PHP3.0은 $length 변수를 통해 요소의 중첩을 수동으로 제어하여 예제를 구현합니다. 이로 인해 스크립트가 더 복잡해 보입니다. PHP4.0은 스크립트를 더욱 간결하게 보이도록 array_pop() 및 array_push() 함수를 사용합니다.
데이터 수집
각 요소에 대한 정보를 수집하려면 스크립트는 각 요소에 대한 이벤트를 기억해야 합니다. 전역 배열 변수 $elements를 사용하여 문서의 다양한 요소를 모두 저장합니다. 배열의 항목은 요소 클래스의 인스턴스이며 4개의 속성(클래스의 변수)을 갖습니다.
$count - 문서에서 요소가 발견된 횟수
$chars - 요소의 문자 이벤트 바이트 수
$parents - 상위 요소
$childs - 하위 요소
보시다시피 클래스 인스턴스를 배열에 저장하는 것은 매우 쉽습니다.
참고: PHP의 기능은 전체 해당 배열을 탐색하는 것처럼 while(list() = Each()) 루프를 통해 전체 클래스 구조를 탐색할 수 있다는 것입니다. 모든 클래스 변수(및 PHP3.0을 사용하는 경우 메소드 이름)는 문자열로 출력됩니다.
요소가 발견되면 해당 요소가 문서에 몇 번이나 나타나는지 추적하기 위해 해당 카운터를 증가시켜야 합니다. 해당 $elements 항목의 count 요소도 1씩 증가합니다.
또한 현재 요소가 하위 요소라는 것을 상위 요소에 알려야 합니다. 따라서 현재 요소의 이름이 상위 요소의 $childs 배열 항목에 추가됩니다. 마지막으로, 현재 요소는 자신의 부모가 누구인지 기억해야 합니다. 따라서 상위 요소는 현재 요소의 $parents 배열에 있는 항목에 추가됩니다.
통계 표시
나머지 코드는 $elements 배열과 해당 하위 배열을 반복하여 통계를 표시합니다. 이는 가장 간단한 중첩 루프입니다. 올바른 결과를 출력하지만 코드가 간결하지도 않고 작업을 완료하기 위해 매일 사용할 수 있는 루프일 뿐입니다.
스크립트 예제는 PHP의 CGI 접근 방식을 통해 명령줄에서 호출되도록 설계되었습니다. 따라서 통계 결과 출력 형식은 텍스트 형식입니다. 인터넷에서 스크립트를 사용하려면 출력 기능을 수정하여 HTML 형식을 생성해야 합니다.
요약
Exapt는 PHP용 XML 파서입니다. 이벤트 기반 파서로서 문서의 구조적 설명을 생성하지 않습니다. 그러나 낮은 수준의 액세스를 제공함으로써 리소스 활용도를 높이고 액세스 속도를 높일 수 있습니다.
Expat은 유효성을 검사하지 않는 파서로서 XML 문서에 첨부된 DTD를 무시하지만, 문서의 형식이 올바르지 않으면 오류 메시지와 함께 중지됩니다.
문서 처리를 위한 이벤트 핸들러 제공
XML 구조화된 정보 마크업을 활용하려면 스택 및 트리와 같은 고유한 이벤트 구조를 구축하세요.
새로운 XML 프로그램이 매일 등장하고 있으며 XML에 대한 PHP의 지원이 지속적으로 강화되고 있습니다(예를 들어 DOM 기반 XML 파서 LibXML에 대한 지원이 추가되었습니다).
PHP와 Expat을 사용하면 유효하고 개방적이며 플랫폼 독립적인 향후 표준에 대비할 수 있습니다.
예
<?
/**************************************************** ***** ******************************
* 명칭 : XML 파싱 예시 : XML 문서 정보 통계
* 설명하다
* 이 예에서는 PHP의 Expat 파서를 사용하여 XML 문서 정보(예: 각 요소, 상위 요소 및 하위 요소의 발생 횟수)를 수집하고 계산합니다.
* XML 파일을 매개변수로 사용합니다./xmlstats_PHP4.php3 test.xml
* $Requires: Expat 요구 사항: Expat PHP4.0은 CGI 모드로 컴파일됩니다.
************************************************** * ***************************/
// 첫 번째 매개변수는 XML 파일입니다.
$file = $argv[1]
// 변수 초기화
$요소 = $스택 = 배열();
$total_elements = $total_chars = 0;
//요소의 기본 클래스
클래스 요소
{
var $count = 0;
var $chars = 0;
var $parents = 배열();
var $childs = 배열();
}
// XML 파일을 구문 분석하는 함수
함수 xml_parse_from_file($parser, $file)
{
if(!file_exists($file))
{
die(""$file" 파일을 찾을 수 없습니다.");
}
if(!($fp = @fopen($file, "r")))
{
die(""$file" 파일을 열 수 없습니다.");
}
while($data = fread($fp, 4096))
{
if(!xml_parse($parser, $data, feof($fp)))
{
반환(거짓);
}
}
fclose($fp);
return(true);
}
// 결과 출력 함수(박스 형태)
함수 print_box($title, $value)
{
printf("n+%'-60s+n", "");
printf("|%20s", "$title:");
printf("%14s", $value);
printf("%26s|n", "");
printf("+%'-60s+n", "");
}
// 결과 출력 함수(라인 형태)
함수 print_line($title, $value)
{
printf("%20s", "$title:");
printf("%15sn", $value);
}
// 정렬 함수
함수 my_sort($a, $b)
{
return(is_object($a) && is_object($b) ? $b->count - $a->count: 0);
}
함수 start_element($parser, $name, $attrs)
{
global $elements, $stack;
// 요소가 이미 전역 $elements 배열에 있습니까?
if(!isset($elements[$name]))
{
// 아니요 - 요소의 클래스 인스턴스를 추가합니다.
$element = 새 요소;
$elements[$name] = $요소;
}
// 이 요소의 카운터를 1씩 증가시킵니다.
$elements[$name]->count++;
// 상위 요소가 있나요?
if(isset($stack[count($stack)-1]))
{
// 예 - 상위 요소를 $last_element에 할당합니다.
$last_element = $stack[count($stack)-1];
// 현재 요소의 상위 요소 배열이 비어 있으면 0으로 초기화합니다.
if(!isset($elements[$name]->부모[$last_element]))
{
$elements[$name]->부모[$last_element] = 0;
}
// 이 요소의 상위 요소 카운터를 1만큼 증가시킵니다.
$elements[$name]->parents[$last_element]++;
// 현재 요소의 상위 요소의 하위 요소 배열이 비어 있으면 0으로 초기화됩니다.
if(!isset($elements[$last_element]-> 자녀[$ 이름]))
{
$elements[$last_element]->childs[$name] = 0;
}
// 요소의 상위 요소의 하위 요소 카운터에 1을 추가합니다.
$elements[$last_element]->childs[$name]++;
}
//현재 요소를 스택에 추가합니다.
array_push($스택, $이름);
}
함수 stop_element($parser, $name)
{
global $stack;
// 스택의 맨 위 요소를 제거합니다.
array_pop($스택);
}
함수 char_data($parser, $data)
{
global $elements, $stack, $length;
// 현재 요소의 문자 수를 늘립니다.
$elements[$stack][count($stack)-1]]->chars += strlen(trim($data));
}
// 파서 인스턴스 생성
$parser = xml_parser_create();
// 처리 함수 설정
xml_set_element_handler($parser, "start_element", "stop_element");
xml_set_character_data_handler($parser, "char_data");
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0)
// 파일 구문 분석
$ret = xml_parse_from_file($parser, $file);
if(!$ret)
{
die(sprintf("XML 오류: %s(라인 %d)",
xml_error_string(xml_get_error_code($parser)),
xml_get_current_line_number($parser)));
}
// 파서를 해제합니다.
xml_parser_free($parser);
// 도우미 요소를 해제합니다.
unset($elements["current_element"]);
unset($elements["last_element"]);
// 요소 수에 따라 정렬
uasort($elements, "my_sort");
// $elements를 반복하여 요소 정보를 수집합니다.
while(list($name, $element) = 각각($elements))
{
print_box("요소 이름", $name)
print_line("요소 개수", $element->count);
print_line("문자 수", $element->chars);
printf("n%20sn", "* 상위 요소");
// 요소의 상위 요소를 반복하고 결과를 출력합니다.
while(list($key, $value) = 각각($element->parents))
{
print_line($key, $value);
}
if(count($element->parents) == 0)
{
printf("%35sn", "[루트 요소]");
}
// 이 요소의 하위 요소를 반복하고 결과를 출력합니다.
printf("n%20sn", "* 하위 요소");
while(list($key, $value) = 각각($element->childs))
{
print_line($key, $value);
}
if(count($element->childs) == 0)
{
printf("%35sn", "[자녀 없음]");
}
$total_elements += $element->count;
$total_chars += $element->chars;
}
// 최종 결과
print_box("총 요소", $total_elements);
print_box("총 문자 수", $total_chars);
?>