제가 그냥 개인적으로 만들어 쓰고 있는 자바 기반 Html 파서 , YGHtml Parser 0.3.3 버전입니다.

이 Parser는 최대한 가벼우면서도 정확히 Token을 추출할 수 있어야 한다는 목적으로 제작되고 있습니다..
타 공개 Parser에 비해 제공 기능은 많이 떨어지지만, 가볍고 빠르고 대부분의 공개 Parser에서 잘못된 처리를 하는 JavaScript나 Comment 부분의 Token을 비교적 정확히 추출하는데 중점을 두었습니다.

이전 버전에 비해 개선사항은 아래와 같습니다.

- String 연산을 StringBuffer로 대체, 처리속도 대폭향상
- 일부 Lexing 오류 제거

** 보고된 문제점
- Style Tag Value 처리 불가

아래는 해당 프로젝트내에 org.yglib.html.ui.NodeViewer를 실행하여 얻은 Google 첫 페이지, HTML DOM Tree Rendering 화면입니다.

사용자 삽입 이미지

국내 대형포탈 첫화면은 대부분 정상적으로 처리하였으나 일부 페이지에서는 여전히 Parse Tree 생성시 모호성 처리상의 문제로 정상적으로 동작하지 않습니다.

사용법은 프로젝트 내의 각 파일의 main method의 코드를 참조하시면 충분하리라 생각됩니다.

프로젝트 다운로드 :

 아직 최종배포를 위한 Interface는 정의되지 않았습니다. DOM 부분은 표준화된 XML 기반의 API를 참조하여 인터페이스를 구현할 예정입니다.

이번이 여기서 배포되는 마지막 버전이 될지도 모를것 같네요. 흠..
 
YGHTML V0.1.1버전에 다음의 기능을 추가하거나 개선하였습니다.

- HTML DOM Tree 생성 기능 추가
- DOM Tree Viewer 추가
- 주석 파싱 오류 제거
- 약간의 리펙토링(-_-;)

보고된 문제점
- Style Tag 처리불가

아래는 Sample 소스를 이용하여, DOM Tree를 생성, Viewer로 출력된 이미지 입니다.

파싱대상 소스(test.html)

<!document html>
<html>
<head>
  <title>가라지하 사바하</title>
</head>
<script>
<!--
  <a href="dcinside.com">아햏햏</a> fdsf <
  adf, < "" <:>
-->
</script>
<body bgcolor=red background="img/bg.jpg">
<br>
<br>
<br>
  <a href="http://kr.yahoo.com">야후</a>
  <a href="kkk"><img src="yahoo.jpg"></img><img src="yahoo.jpg"/></a>
  <table bgcolor=lightgrey width=95%>
  <tr><td>..</td></tr>
  </table>
  <!-- 비정규 파싱 -->
</body>
</html>


사용자 삽입 이미지

아래는 Naver 메인화면 소스를 적용해 본 이미지 입니다. (역시 복잡합니다.)

사용자 삽입 이미지

해당 소스파일 다운로드 ( Eclipse Project )

정확한 요구사항 분석도 없이,
설계에 대한 개념도 없이 HTML Parser가 절실한 상황에서 급조된 코드입니다..
분명히 저같이 Parser가 절실한 사람이 있다고 생각되기에, 저질 코드라도 용감, 무식하게 공개합니다..

소스의 질적인 부분에 대한 지적은 할말 없습니다..
또한 제가 파서에 대한 개념이 많이 부족한 상태라,
해당소스에서 더 괜찮은 아이디어나 제안이 있으시다면 언제든지 리플 부탁해요..

- 김영곤 (gonni21c@gmail.com)

Java로 만들어진 Html Parser는 제법 많이 있다.

- org.htmlparser
- nekoParser

정도가 있는데, htmlParser는 다소 무겁기는 하지만 잘 정의된 구조에, 4만 페이지 정도의 Hard한 환경에서 Parsing Test를 해보았으나, 모든 문서를 Parsing해 내는(파싱 알고리즘 통과) 안정성을 자랑하였다.

하지만, HTML Parser를 한번이라도 개발한 경험이 있는 사람들은 알겠지만..
HTML Parser의 최대 난제는 아무래도 script value element 처리를 비롯한 html 문법을 무시해야 하는 영역의 구현이 아닐까 싶다.
불행히도 대부분의 Open Source 파서는 이러한 부분에 대한 정확한 처리를 하지 못한다. Script 내부의 Text까지 HTML 문법을 적용하여 Parsing하고, Source 구조상 이부분의 수정이 쉽지가 않았다.

Parser의 생명은 Parsing의 정확도와 속도다.

 정확도에 있어서, HTML은 XML과 틀리게 상당히 변칙적인 문법을 허용한다. 이것은 Parse 트리 구축시 모호성이 발생한다는 것을 의미한다. IE를 비롯한 대부분의 Browser에 내장된 HTML Parser의 경우 HTML 문서의 전처리를 거쳐 well-formed 문서로 만들고(1Pass), XML Parser등을 적용, DOM Tree를 생성한다.(2Pass)
 최소 2Pass를 거쳐야 하므로 속도는 궁극적으로 떨어지며, 일반적으로 구현시 고성능의 ReadBuffer의 구현으로 하거나 1Pass시 불완전 DOM Tree 구축후 2Pass 단계에서 보정방식을 사용하기도 한다.

 속도에 있어 또다른 문제는, SCRIPT부분의 처리이다. SCRIPT 내부 value 처리시 Script 처리 Parser로 동적 전환되고, </SCRIPT>라는 문자열이 발견되기 이전 시점에 Html Parser로 전환되는 과정을 거쳐야 한다. 이것은 Parser 구현시 미리읽기 Buffer를 구현하거나 혹은 Read Cursor가 Lexing 과정중 Back 될 수도 있음을 의미한다. 이런 구현은 속도에는 치명적이며 구현 복잡도는 매우 증가한다.
(그런데 이문제에 대해 별다른 해결책은 없어 보인다, 물론 돈과 시간이 적절히 주어진다면 대체할수 있는 구현은 가능 하겠지만.. 하드웨어가 저질코드의 성능을 충분히 커버해 주는 시대이므로 거기 까지 갈일은 없을것 같다..ㅋㅋ)

 주말 이틀 반납해가며, SCRIPT 부분에 대해 Parsing이 되는 HTML Parser를 만들어 보았다.

 Version은 0.1 정도로 보면 되겠다. 가장 문법이 변태적인 Naver정도는 정확하게 Parsing을 해냈으나 아직까지 많은 테스트와 수정, 추가가 필요한 상황이다. 연구용 정도로 쓰기는에는 불편함이 없을듯 하며, 아직 DOM Tree를 생성하는 부분의 구현은 안되었다.(시간 부족)

 Lexer가 어느정도 안정화 되었으므로 Stack 정도만 적절히 쓴다면 DOM Tree 정도는 쉽게 구현이 가능할 것이다.

소스코드(Eclipse Project) 다운로드


아래는 대략적인 사용법이다.

파싱대상 소스(test.html)

<html>
<head>
  <title>가라지하 사바하</title>
</head>
<script>
<!--
  <a href="dcinside.com">아햏햏</a> fdsf <
  adf, < "" <:>
-->
</script>
<body bgcolor=red background="img/bg.jpg">
  <a href="http://kr.yahoo.com">야후</a>
</body>
</html>

출력결과

tag>html
tag>head
tag>title
latest Tag :title
text>가라지하 사바하
tag>/title
tag>/head
tag>script
text>
<!--
  <a href="dcinside.com">아햏햏</a> fdsf <
  adf, < "" <:>
-->

tag>/script
tag>body
 -bgcolor=red
 -background=img/bg.jpg
tag>a
 -href=http://kr.yahoo.com
latest Tag :a
text>야후
tag>/a
tag>/body
tag>/html


Lexer Code

File file = new File("d:\\test.html");
   Lexer lexer = new Lexer(file);
   Node node = null;
   while((node = lexer.getNextToken()) != null)
   {
    if(node instanceof TagNode)
    {
     String isClosed = "";
     TagNode tag = (TagNode)node;
     if(tag.isClosedTag()) isClosed = "/";
     System.out.println("tag>" + tag);
    }
    else if(node instanceof TextNode)
    {
     TextNode txNode = (TextNode)node;
     System.out.println("text>" + txNode.getParsedText());
    }
   }

막 구현된 소스라 리펙토링이 전혀 안된 상태이며, 구현과 설계를 동시에 하는 본인의 업무 특성상,
스파게티 코드를 보고 충격을 받지 말길 바란다. 차츰차츰 바로 잡아갈테니.. ㅋㅋ

+ Recent posts