1. 2007.10.05 XML 문서파싱 - SAX 방식 , DOM 방식
  2. 2007.09.19 php에서 파싱시 한글 깨짐 현상

XML 문서파싱 - SAX 방식 , DOM 방식

XML 문서파싱은 PHP에서 제공하는 파서로 한다.

SAX(Simple API for XML) 파서, DOM(Document Object Model) 방식 파서 2가지가 있다.

출처 : http://blog.naver.com/cowboy0626?Redirect=Log&logNo=30001743482

■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


SAX 방식


: 엘리먼트, 처리지시문, 선언문 등 XML문서 구성요소를 읽어들일때마다 이벤트를 발생시켜서 처리, 빠르나 한번 처리한 데이터를 다시 이용할 수 없는 단점. 용량이 큰 문서나 많은 문서를 처리할 때 적절

- SAX 파서의 경우 Start Tag, End Tag를 만나면 이벤트를 발생시킴. xml_set_element_handler()함수 이용 처리...


cf. DOM 방식 : 전체문서를 메모리에 저장하고 처리하는 방식으로 상대적으로 느림


SAX 파싱을 위한 함수


1. xml_parser_create : 파서 생성


2. xml_parser_free : 생성된 파서 해제


3. xml_parse(resource parser, string data [,bool is_final]) : xml 문서를 파싱한다.

- 파싱할 때 문자열에 저장해 한꺼번에 하는 방법과 fopen(), fget() 함수를 사용하여 여러조각으로 나누어 파싱하는 방법이 있는데 후자때문에 'is_final' 값을 이용하여 문서의 마지막 데이터인지 확인필요


[한꺼번에 파싱]

if(!@xml_parse($xml_parser,$document_xml,TRUE))
 {
 die ("error acurred during parsing");
 }


[나누어서 파싱]

$fp = @fopen("book.xml","r") or die("failed to get file");

while(!feof($fp))
{
 $data = fgets($fp);
 if(!xml_parse($xml_parser,$data,feof($fp)))
 {
  die ("error accurred during parsing");
 }
}


4. xml_get_current_line_number (resource parser) : 현재파싱하고 있는 문서 줄번호 반환

5. xml_get_current_column_number (resource parser) : 현재파싱하고 있는 열번호 반환

6. xml_get_error_code (resource parser) : 에러코드를 반환

7. xml_error_string(int error code) : 에러메시지 반환


[샘플] 위 4개를 이용하면 XML문서자체의 구조에러를 찾아내는 데 좋을 것 같다.


$fp = @fopen("abnormal_book.xml","r") or die("failed to get file");

while(!feof($fp))
{
 $data = fgets($fp);
 if(!xml_parse($xml_parser,$data,feof($fp)))
 {
  die ("error accurred during parsing."."<br>\n"." error : ".xml_get_current_line_number($xml_parser)."line".xml_get_current_column_number($xml_parser)."column<br>\n"."error Message : ".xml_error_string(xml_get_error_code($xml_parser)));
 }
}


8. xml_set_element_handler($xml_parser,"startElement","endElement") : 앨리먼트 핸들러  정의

- 시작태그 핸들러의 속성값들은 Array 형태로 인자값을 넘겨준다.따라서 startElement 함수는 Array 값을 처리하는 형태로 로직구성됨.

[샘플]

function startElement($parser,$name,$attr)
{
 $output="";
 $output.="&lt;".$name;
 if(is_array($attr)&&sizeof($attr)>0)
 {
  while(list($attr_name,$attr_value)=each($attr))
  {
   $output.="".$attr_name."=\"".$attr_value."\"";
  }
 }

 $output.="&gt;";
 echo $output;
}


9. xml_parse_set_option(resource parser, int option, mixed value) : 파싱 옵션지정

1) XML_OPTION_CASE_FOLDING : TRUE 또는 FALSE

2) XML_OPTION_TARGET_ENCODING : "ISO-8859-1","UTF-8","US_ASCII"

3) XML_OPTION_SKIP_WHITE : TRUE 또는 FALSE


[샘플]

xml_set_element_handler($xml_parser,"startElement","endElement"); // event handler 정의
xml_set_character_data_handler($xml_parser,"characterData");
xml_parser_set_option($xml_parser,XML_OPTION_CASE_FOLDING,FALSE);


10. xml_parser_get_option(resource parser, int option) : 설정된 옵션값 반환


11. xml_set_character_data_handler(resource parser,callback handler) : 캐릭터 데이터 만났을 때 호출되는 이벤트 핸들러 정의

- 공백, 개행도 문자로 보기 때문에 예상과 다른 횟수만큼 characterData() 함수가 호출됨. 이를 보정할 수 있음

-


[샘플]

xml_set_character_data_handler($xml_parser,"characterData");

function characterData($parser,$cdata)
{
 echo "&lt;b&gt;".iconv("UTF-8","EUC-KR",$cdata)."&lt;/b&gt;";
}


■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


DOM (Document Object Model) 방식


W3C가 국제표준으로 제안한 XML 프로그래밍 인터페이스

XML문서를 구성하는 각각의 구성요소를 객체모델링화함으로써 객체가 가진 '속성'과 '메소드'를 통해 문서의 구조와 데이터를 다룰 수 있도록 정의한 표준화된 프로그래밍 인터페이스


그러나, 실제 문서를 처리하는 클래스는 C#, 자바, PHP같은 프로그래밍언어에서 지원하며, DOM API는 표준규약만을 제공한다. 따라서 프로그래밍 언어나 운영체제에 중립적이고 독립적임.


DOM 파서는 XML 문서를 해석하여 DOMTree형태로 메모리에 올린다.

이 DOMTree 에 PHP로 작성한 응용프로그램은 DOM API를 통해 접근, 제어,처리한다.

PHP에서 제공하는 DOM API는 모드 클래스형태로 지원되고 있으며, 따라서 객체지향프로그래밍에 대한 기초가 있어야 이해가능하다.

XML 문서가 메모리에 올라온 형태인 DOM Tree 에 접근하기 위해 사용해야 하는 DOM API 중 가장 기본적이고 많이 쓰이는 것이 DOMDocument 클래스이다.

DOMDocument 클래스는 XML이나 HTML 문서를 모델링한 클래스이다. 이를 이용하여 새로운 문서를 생성하거나 이미 존재하는 문서구조에 새로운 노드를 추가할 수도 있다.


DOMDocument 클래스의 속성은 읽을수만 있는지.. API를 통해 속성을 변화시킬 수 있는지 여부에 따라서 ReadOnly와 Read&Write 타입의 속성을 가질 수 있다.


1. DOMDocument 클래스의 속성


[readonly DOMElement documentElement]


루트앨리먼트 노드, 즉 문서의 최상위 앨리먼트를 가리키는 속성


<?

$book = new DOMDocument();

$book->loadXML("<book><subject>Requirement Analysis</subject></book>");


echo get_class($book->documentElement).",node name :".$book->documentElement->nodeName;

?>


XML 문서를 생성시키고 루트앨리먼트의 이름과 노드이름을 출력하는 예제


[readonly DOMDocumentType doctype]


[version]

[encoding]

[preserveWhiteSpace] 앨리먼트 사이에 공백문자 처리여부 속성, 기본값 True, 공백을 가지고 파싱한다.

[documentRUI] XML 문서의 저장경로

[formatOutput] 보여지는 데로 XML 문서 출력하는 여부, 기본값 False


2. DOMDocument 클래스의 메소드


loadXML()  - XML 문서를 불러온다.

saveXML() - 메모리에 올라가 있는 DOM Tree 를 XML문서로 변환한다.

load() - 파일로 부터 XML 문서를 불러온다.

save() - DOM Tree를 파일로 저장한다.

validate() - DTD를 기준으로 XML 문서의 유효성을 검증한다.

schemaValidate() - XML 스키마를 기준으로 XML 문서의 유효성을 검증한다.

schemaValidateSource() - XML 스키마를 기준으로 XML 문서의 유효성을 검증한다.

createElement() - 새로운 앨리먼트 노드 생성

createTextNode() - 새로운 텍스트 노드 생성

createAttribute() - 새로운 속성 노드 생성

createElementNS() - 네임스페이스 영역을 갖는 앨리먼트 노드 생성

createAttibuteNS() - 네임스페이스 영역을 갖는 속성 노드 생성

createComment() - 새로운 주석노드 생성

createCDATASection() - 새로운 CDATA 섹션 노드를 생성한다.

getElementsByTagName() - 지정한 태그명을 갖는 앨리먼트의 노드 리스트 출력

getElementsByTagNameNS() - 특정한 네임스페이스 영역에서 지정한 태그명을 갖는 앨리먼트의 노드리스트를 반환한다.

importNode - XML 문서에 붙일 특정노드를 가져온다.



DOM API로  새로운 XML 문서 작성하기


<?


// DOM 객체 및 루트앨리먼트 생성


$doc = new DOMDocument();

$doc->loadXML("<books></books>");


//<book> 앨리먼트 생성. 생성 후 추가해야 함

$bookNode = $doc->createElement("book"); // 노드 생성

$doc->documentElement->appendChild($bookNode); // 노드 추가


// 새로생성된 book노드에 isbn 속성 생성 및 추가

$isbnNode = $doc->createAttibute("isbn");

$isbnNode->value="19999999";

$bookNode->setAttributeNode($isbnNode);


// subject , authors 앨리먼트 생성

$subjectNode = $doc->createElement("subject","Test Book");

$authorsNode=$doc->createElement("authors");


// author 앨리먼트 생성, 자식 노드 추가

$authorsNode->appendChild(new DOMElement("author","Craig Walls"));

$authorsNode->appendChild(new DOMElement("author","Norman Richards"));

'WebDevelop > PHP' 카테고리의 다른 글

PHP에서 자바 클래스 호출  (0) 2007.10.05
PHP란 무엇인가...  (0) 2007.09.17
간단한 페이징 처리  (0) 2007.08.31

php에서 파싱시 한글 깨짐 현상

어디까지나 팁이니까 팁에 해당되는 부분을 위주로 적을께요..
제가 2주동안 혼자서 열라게 피똥싸다가 생각한 거라서 다른 분들도 저처럼 고생하시지
않으셨으면 해서 올려봅니다.
일단 한글깨짐(부분깨짐) 문제가 발생하는 것은 EUC-KR 로 인코딩된 XML 파일을
파싱하고서 출력 할때 나타나는 괴(?) 현상인것 같습니다. 한글이 잘 출력되다가 중간
중간마다 부분적으로 깨지죠.. 예를들면 "이?br>岳?맞도록" 과 같이 잘 출력되다가도
중간에 깨지는 현상이 이에 해당됩니다. 이때 더 환장하게 만드는 점은 마우스 오른쪽
버튼으로 소스보기 하고 텍스트로 보면 희한하게도 정상적으로 나타난다는...
그런데 꼭 익스플로어에서 보면 깨질 겁니다. 이런 경우가 다른 분들도 있을거라 가정
하고서 팁을 적습니다.(QNA 게시판에 비슷한 문제를 격는 분이 있던거 같던데..)

일단 제가 처한 환경은 zip 파일에서 xml 파일을 실시간으로 압축을 풀어서 보여주기
위한 XML 파싱을 하는 거였구요.. 압축을 푼 xml 파일은 euc-kr 포맷인것 같았습니다.
눈으로 보면 다 한글인데 헤더에 EUC-KR 이라고 명시되어 있으니 밑어야죠.. ㅎㅎㅎ
안그렇숩니깡.. <?xml version="1.0" encoding="euc-kr" ?> 이렇게 파일의 헤더에
써있더군요.. 이 XML 파일은 법률정보가 들어있고 태그는 좀 복잡하게 되있습니다.
한글이 많이 들어가 있는 편이고 LEVEL 이 깊다고 해야 하나요..? xml 파싱시의 level...
어쨌든, 일단 xml 파서를 하나 만들었고 아주 기본적인 파서를 쓰기로 했습니다.
소스를 보면 다음과 같습니다. 저 같은 경우는 본문 출력만 필요했기 때문에 다른 부분은
구현 안하고 빈껍데기 함수에 cdataHandler 라는 함수만 사용했습니다.
먼저, 팁의 설명은 문제가 되는 소스와 문제의 출력을 보여드리고 해결하는 방법을
제시하는 형식으로 적어나가겠습니다. 문제가 되었던 파서의 소스부터 보겠습니다.
(이미 한글깨지는 거때문에 수차례 고생을 겪으신 분이면 소스가 눈에 정겹겠죠..? ㅎㅎ)

------------------------- XMLParser.php -------------------------
<?php
class XMLParser {
    var $parser;
    var $out;
    var $encoding = 'ISO-8859-1';

    function XMLParser() {
        $this->_create();
    }

    function _create() {
        $this->parser = @xml_parser_create($this->encoding);

        if (is_resource($this->parser)) {
            //xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
            xml_parser_set_option($this->parser, XML_OPTION_SKIP_WHITE, 1);
            xml_set_object($this->parser, &$this);
            xml_set_element_handler($this->parser, 'startHandler', 'endHandler');
            xml_set_character_data_handler($this->parser, 'cdataHandler');
            return true;
        }
        return false;
    }

    function free() {
        if (is_resource($this->parser)) {
            xml_parser_free($this->parser);
            unset($this->parser);
        }
        return null;
    }

    function startHandler($parser, $element, $attr) {
        $this->tag = $element;
    }

    function endHandler($parser, $element){
    }

    function cdataHandler($parser, $cdata) {
        if (($cdata = trim($cdata)) == '') return;

        switch($this->tag) {
                case 'COURT':
                case 'PRONOUNCEDATE':
                case 'PRONOUNCE':
                case 'CASENUM':
                case 'DECISIONTYPE':
                case 'UNDERLINE':
                case 'LawRef':
                case 'SECTIONTITLE':
                    print $cdata;
                    break;

                case "CASENAME":
                    print '【'.$cdata.'】';
                    break;

                case 'PARA':
                    print '<span style="size: 2pt;">'.$cdata.'</span>';
                    break;

                default:
                    break;
        }
    }

    function parse($data) {
        xml_parse($this->parser, $data);
        return true;
    }
}
?> 
-----------------------------------------------------------
여기까지가 문제의 파서..

------------------------ xmlout.php ------------------------
<?php
require 'XMLParser.php';

$file = 'test.xml';

$buffer = file_get_contents($file);

$xml = new XMLParser();
$xml->parse($buffer);
?>
-----------------------------------------------------------
요기까지가 사용하는 예제...

xmlout.php 를 웹에서 불러들이면 test.xml 파일이 파싱되어 출력됩니다.
일단 저는 test.xml 파일을 열어서 출력을 해봤지요.. 물론 test.xml 파일은 실제로
파싱해야할 파일중에 하나였구요.. XMLParser.php 에 보시면 저는 본문만 출력할려는
목적이었기 때문에 아예 클래스 본체에 박았습니다.
cdataHandler 함수에서 해당 태그에 해당하는 본문을 무조건 출력합니다.
실행시켜봤더니 한글이 아주 잘 나옵니다. 매우 잘...
그런데 위에서 문제 제기를 했던 한글 깨짐이 나타납니다.

------------------- < 예: 한글깨짐 상태 >-------------------
거래의 관행 등을 종합적으로 고려하여 사회정의와 형평의 이?br>岳?맞도록 논리와
경험의 법칙, 그리고 사회일반의 상식과 거래의 통념에 따라 합리적으로 해석하여야 한다.
-----------------------------------------------------------

잘 출력되다가 "이?br>岳?" 이렇게 되길래 분명히 1바이트가 깨져서 겹쳐졌구나라고
생각을 하고 일단은 인코딩을 바꿔봤습니다. encoding 값에 UTF-8 도 줘보고 US-ASCII
도 줘보고 해봤는데 출력은 ISO-8859-1 일때만 출력이 되고 나머지는 출력 조차도
안되더군요..(EUC-KR 은 왜 없는거얏..) 1주가 넘어가고 2주가 넘어가고 서치에 서치를
거듭하면서 지쳐갈 무렵 var_dump 를 우연히 사용해 봤습니다. 사용법은 아래에서 처럼
cdataHandler 함수에 var_dump 를 사용하였습니다.
-----------------------------------------------------------
... 앞부분 생략
    function cdataHandler($parser, $cdata) {
        if(($cdata = trim($cdata)) == '') return;

        var_dump($parser, $cdata);        // 추가 부분
        print '<br>';                     // 웹에서 한줄 띄워보기 위해서..

/*
        switch($this->tag) {
            case 'COURT':
                print $cdata;
                break;

... 생략

            default:
                break;
        }
*/
    }
---------------------------------------------
이렇게 해서 찍어보니 다음과 같이 나오더군요..(출력의 뒷쪽은 짤랐음..)

resource(4) of type (xml) string(552) "[1] 법률행위의 해석은 당사자가 그
어디까지나 당사자의 내심적 의사의 여하에 관계없이 그 서면의 기재
그 객관적인 의미가 명확하게 드러나지 않는 경우에는 그 문언의
종합적으로 고려하여 사회정의와 형평의 이?

resource(4) of type (xml) string(102) "岳?맞도록 논리와 경험의 법칙,

보는 것처럼 처음에는 (552) 문자열이 저장되어 있다고 string(552) 라고 나오면서
맨 뒤에 "이?" 이렇게 깨져있죠.. 파싱하면서 끝에 문자열이 짤리고 뒤에 string(102)에서
그 짤린 문자열부터 들어가있음.. 그래서 한글을 출력하니 깨져서 출력되는 것이었음..
역시나 추측이 확실시 되고나니 일단은 string 값의 한계값을 늘려주면 안짤리지 않을까
라고 생각해서 구글서칭을 해보니 그런 옵션은 있지도 않고 유니코드 UTF-8 을 사용
해서 파싱을 하면 유니코드 파싱을 할 수 있다고 해서 일단은 머리를 굴려보니 문서는
EUC-KR 인것 같은데 UTF-8 인코딩 옵션만 주고 과연 XML 파싱이 될까라는 의문이
들더군요.. 역시나 해보니깐 안됩디다.. 그래서 EUC-KR 문서인 xml 파일을 UTF-8 로
바꾸면 되겠다 싶어서 찾아보니 C 언어에서 쓰는것처럼 iconv 함수가 존재하더군요..
(원래 C 프로그래밍만 주로하다보니.. php 에 iconv 가..)
그래서 일단 xml 파일을 읽어서 내용을 변수로 저장한 후에는 변수만 iconv 함수에
담궜다 빼는 방식으로 EUC-KR 을 UTF-8 로 인코딩 시키기로 했습니다. 그냥 감으로
때려줬는데 잘 작동해서 만족했죠..

--------------------------- xmlout.php -----------------------
<?php
require 'XMLParser.php';

$file = 'test.xml';

$buffer = file_get_contents($file);
$buffer = iconv('EUC-KR', 'UTF-8', $buffer);    // iconv 함수 추가

$xml = new XMLParser();
$xml->parse($buffer);
?>
---------------------------------------------
위와 같이 iconv 함수를 추가했습니다.

그리고 XMLParser.php 파일에 인코딩을 ISO-8859-1 에서 UTF-8 로 바꿔줍니다.
iconv 로 UTF-8 로 인코딩을 바꿔 넘겨주니 파싱을 할때는 당연히 UTF-8 로 파싱을
해야겠죠...

--------------  XMLParser.php --------------------------
class XMLParser {
    var $parser;
    var $out;
    var $encoding = 'UTF-8';    // UTF-8 로 수정

    function XMLParser() {
        $this->_create();
    }

... 생략

    // 전에 추가했던 var_dump 함수부분을 삭제함..
    // 주석처리 했던 switch 부분을 풀음...

... 생략
-----------------------------------------------------------------

이제 다시 실행을 시켜보니... 오우~ 몬가 좀 되나 싶으면서 익스플로어 화면에
읽고싶어도 차마 읽을 수 없는 글씨들이 좌르륵 나오는게 뭔가 좀 인코딩이 많이
됐나 싶더군요... 익스플로어 인코딩메뉴에서 인코딩 방식을 UTF-8 바꿔주니깐
깨지던 한글이 하나도 안깨지고 잘 나타났습니다. 이제 남은 문제는 자동으로 UTF-8
로 익스플로어에 보여지도록 해야 하는데 이게 또 말썽이 발생합디다..
자동으로 익스플로어에 인코딩 UTF-8 이다라는걸 알려주기 위해 별별짓을 다 해보고
header("Content-Type: 어쩌구리.. charset 어쩌구리 UTF-8 로도 지정해보고
<meta 태그 함수도 써봤지만 전혀 효과가 없더군요..
그래서 꽁수를 생각해낸 것이 일단 XML을 UTF-8 로 인코딩해서 파싱하고 난 다음에는
출력만 다시 EUC-KR 로 디코딩(?)해서 출력하면 익스플로어에 정상적으로 출력이
되겠다 싶어 출력부에 iconv 함수를 하나 더 사용했습니다.
    function cdataHandler($parser, $cdata) {

        if (($cdata = trim($cdata)) == '') return;

        $cdata = ($cdata);
        $cdata = iconv('UTF-8', 'EUC-KR', $cdata);

        switch ($this->tag) {
            case 'COURT':
                print $cdata;
위와 같이 출력이 되는 부분 앞단에다가 UTF-8 로 파싱된 것을 다시 EUC-KR 로 돌리도록
했더니 익스플로어에 한글이 아주 자~알 나타나네요.. 물론 깨짐도 없습니다...
저처럼 XML 파싱에 처음 발 담았다가 고생하시는 분이 또 계실까봐 팁을 쏩니다...
긴글 읽어주셔서 감사합니다... ^^

출처 : http://www.phpschool.com/gnuboard4/bbs/board.php?bo_table=tipntech&wr_id=45322


Return top