[Android] XML, HTML Parsing Test & Error

Oxong·2021년 6월 11일

21.06.09 ~ 21.06.11

공부한 것을 정리하는 용도의 글이므로 100% 정확하지 않을 수 있습니다.
참고용으로만 봐주시고, 내용이 부족하다고 느끼신다면 다른 글도 보시는 것이 좋습니다.
+ 틀린 부분, 수정해야 할 부분은 언제든지 피드백 주세요. 😊

※ 해당 내용은 Window환경에서 실행되었습니다.

                                                 by. ryalya



무엇을 구현하려 하는가?

웹 크롤링, 웹 자체의 내용 전체를 Parsing에서 그치는 것이 아니라 해당 내용을 한 줄 씩 파싱하여 담아서 활용하려 함.
ex) 외부에서 Response받은 HTML 또는 XML 파일을 Parsing해서 UI로 사용할 수 있을까?

→ 내가 검색하는 키워드가 잘못된 것인지 아니면 이렇게 사용하는 사람들이 거의 없는 것인지 구글링을 해도 데이터 자체를 파싱하는 내용만 나오고, 파싱해서 UI로 사용하는 케이스를 확인하기 어려웠음.

→ XML과 HTML 전체를 파싱하는 것은 어려운 일은 아님.


◆ XML 파싱 종류

XML은 트리구조를 가지고 있기 때문에 데이터 파싱은 태그를 구분하여 파싱할 수 있고, 시작 태그와 종료태그를 구분해야 한다.

RSS 뉴스, 공공기관 데이터 등 데이터 전달 포멧으로 많이 사용되지만 가독성 문제, 파싱의 어려움, 처리속도 느림 등의 단점으로 인해 json 등 다른 포멧을 활용하는 것이 더 좋을 수 있다.

XML 파싱은 DOM방식과 SAX 방식이 있는데

DOM (Document Object Model)

→ 문서 내용을 모두 해석 후, 메모리에 트리 구조로 펼쳐서 파싱
장점 : 메모리가 모두 로드되어 있어 노드의 검색, 수정, 구조 변경이 빠름.
단점 : 문서 전체를 로딩해야 하므로 처리 속도가 느리고, 메모리 사용이 많다.

SAX (Simple API for XML)

→ XML 데이터를 라인 태그(시작태그 & 종료태그)단위로 마킹하여 순차적으로 읽어가며 파싱
장점 : 메모리를 차지하는 공간이 적고, 대용량 XML 파싱을 해도 속도가 빠름.
단점 : 지나간 요소(element)를 읽기 위해서는 처음부터 다시 읽어야 하고, 특정 엘리먼트 동작은 사용자가 직접 정의해줘야 함. DOM보다 복잡하고 어려움. 노드 수정이 어려움.



- 21. 06. 10

아래 내용 추가 및 XML 테스트 시도한 프로젝트 폐기 & 글 내용 폐기

◆ UI XML 파싱 시도, 실패.. 시도, 실패를 반복하다가 stackOverFlow에서 해당 질문글을 발견.


해석 : XML 레이아웃을 동적으로 로드할 수 있나요?
→ resource(DB 또는 그 일부)에서 XML파일을 로드하여 해당 활동이 시작될 때 동적으로 로드할 수 있을까요?
해당 소스는 웹 서버에서 기기로 보내집니다.

해당 질문에 대해 여러 답변들이 있었다.

  • XML은 빌드 될 때 사전 처리가 된다.
  • 빌드 프로세스 중에 포함되지 않은 XML은 가능하지 않다.

다 맞는 말이었지만 정말 안되는게 맞는 건지 확실한 증거가 필요했는데
답변들 중 Java문서의 Layoutinfater 클래스 부분의 글을 직접 가져온 답변이 있었다.

답변을 해석해보면 성능 상의 이유로 뷰는 빌드 시 수행되는 XML파일의 사전 처리에 의존하며,

일반 XML파일에 대해 XmlPullParser와 Layoutinflater를 사용할 수 없다는 것!


안되는거였넹 ㅎㅎㅎ 다행이다 내가 못하는 게 아니라서 ㅎㅎㅎ 😂




다시 처음으로 돌아가서,

무엇을 구현하려 하는가?

  • XML이 안된다면 HTML을 바로 로드해서 웹뷰를 띄워주면 되지 않을까?
        → loadUrl을 걸어서 웹뷰에 전체 내용을 한 번에 띄워주는 것은 쉽지만, 그 뷰 위에 나는 데이터 조합도 할것이기 때문에 이 방법은 적합하지 않음 (X).
  • 서버에서 보낼 때, 탬플릿만 HTML 형식으로 보내는 것이 아니라 데이터 값까지 포함해서 보내면 파싱이 가능할까?
        → 시도해보자.
  • 서버에서 올 때, HTML 형식만 오고 데이터 값은 따로 오거나 오지 않는다면 부분 조합으로 같이 띄워 줄 수 있을까?
        → 위가 성공하면 시도해보자.

◆ HTML 파싱 (Jsoup)

HTML 파싱도 종류에 대해 검색해보았으나 jsoup을 사용하는 사람들이 압도적으로 많아 간단하게 찾아보고, 파싱에 사용해보기로 했다.

🔹 jsoup이란?

→ 자바(Java)로 만들어진 HTML 파서(Parser)로서, JAVA 오픈 소스 라이브러리
→ 문자열을 트리 형태로 펼쳐 메소드를 이용하여 원하는 DOM을 찾을 수 있도록 해줌.
→ Element를 탐색하는 기능
  - CSS Selector를 이용하는 select 함수
  - Javascript에서 사용되는 getElementsByClass와 같이 속성값을 통해 탐색하는 함수들을 제공.
  - XPath를 지원하지 않음!!!!
  - 그러나 xsoup이라는 확장 라이브러리를 사용하면 XPath를 사용할 수 있다고 하는데..?(확인해봐야 할듯)

🔹 사용 준비

오픈소스 라이브러리이기 때문에 직접 jar파일을 lib에 넣어주거나 gradle을 추가해줘야 한다.

아래 사이트에서 다운로드 외에도 jsoup에 대해 필요한 정보를 얻을 수 있음!
https://jsoup.org/


아래의 경우 중, 자신의 개발 환경에 맞춰서 프로젝트 세팅

lib 직접 추가
위의 사이트에서 jsoup-1.31.3.jar 파일 다운로드 후,
lib 폴더에 넣어주고 add as liabrary해줌

gradle 추가

implementation 'org.​jsoup:jsoup:1.31.1'

메이븐 사용자 → 의존성 추가

<dependency> 
	<groupId>org.jsoup</groupId>
	<artifactId>jsoup</artifactId>
	<version>1.10.3</version>
</dependency>

참고 : https://mvnrepository.com/artifact/org.jsoup/jsoup


🔹 사용법

사용법에 앞서, 아래 내용을 숙지하고 있으면 크롤링이 용이함.

DOM 을 찾기 위한 속성

  • tag : HTML 문서의 구조를 나타내기 위한 명령어. (중복 가능)

  • id : 하나의 DOM을 구분하기 위해서 태그에 붙인 식별자. (중복 불가)

  • class : 여러 DOM에 동일한 디자인을 적용하기 위한 라벨. (중복 가능)

  • name : 서버에게 데이터를 전달할 때 서버에서 인식할 라벨(이름). (중복 가능)

DOM 을 찾기 위한 메소드

  • getElementById(String id) : Element 객체 반환 / 없으면 null

  • getElementsByTagName(String tag) : Element 객체 반환 / 없으면 size() = 0

  • getElementsByClass(String class) : Elements 객체를 반환합니다. 없으면 size() 가 0

Element 객체

  • attr(String key) 로 속성의 값을 얻을 수 있고, attr(String key, String value)로 속성 값 설정 가능.

  • id(), className() 은 id와 class속성의 값을 가져옴. class는 여러개 지정되면 하나의 문자열로 반환됨.

ex) 예로 요소가
<div class="center red">라면 className() 은 "center red" 를 반환.
하나씩 구하기 위해서는 classNames() 메소드 사용. Set<String> 타입으로 반환.

  • text()로 순수 텍스트로 변환하여 반환. (tag 다 떼고 오는 것!!!)

  • toString() → tag 그대로 붙여서 반환.


◎ URL로 parsing하는 방법

Document doc = Jsoup.connect("http://m.naver.com/").get(); 
String title = doc.title();

→ get방식으로 호출. post도 가능하긴 하지만 지정해야 될 것이 많음.


◎ 파일로 parsing하는 방법

File input = new File("/test/test.html");
Document doc = Jsoup.parse(test, "UTF-8", "http://example.com/");

Tip. Assets 폴더 생성하여 넣어놓으면 경로 찾기가 좀 쉬움..

나는 톰캣이용해서 URL로 테스트 해보고, 파일로 다시 테스트해보았다.
확실히 URL이 경로를 일일히 지정해줄 필요 없으므로 더 효율적인 편.


CSS Select로 태그를 선택하여 select 메소드 수행

* doc.select("a") : <a> 요소를 모두 선택.
* doc.select("#logo") : id="logo" 인 요소를 선택.
* doc.select(".head") : class="head"인 요소들을 선택.
* doc.select("[href]") : href 속성을 가진 요소들을 선택.
* doc.select("[width=500]") : width 속성의 값이 500인 모든 요소들을 선택.

요고 아주 중요한 코드들!!!! 일부 파싱을 원한다면 컨트롤을 잘 해야하니까.
위에서는 간단하게 핵심만 정리했지만, 실제로 크롤링하는 환경에서는 태그들이
저렇게 간단하지 않으므로 조합 + 조합 + 조합이 필요함.


이미지 반환 가능?
해당 코드는 img> 태그들중 첫 번째 img 요소의 src 속성 값을 구하는 코드.

Elements imgs = doc.getElementByTag("img");
if(imgs.size() > 0) {
    String src = imgs.get(0).attr("src');
}

나는 무슨 문제인지 아직 모르지만 이미지 코드만 들어가면 파싱이 catch로 빠져버려서
해당 코드로는 진행하지 않았음...



🔸 Jsoup Error 정리

Jsoup을 사용하면서 다양한 오류를 많이 경험했다.
검색해서 찾은 것도 있지만, 구글링으로 잘 안나오고 (다들 에러 없이 한 번에 했나보지..?🤨부럽군)
혼자 어찌저찌 찾은 것도 있어서 내가 겪은 에러들만 정리해보았다.

URL Connect Error

Document documetn = Jsoup.connect(url).get();

→ 위와 같이 URL연결 코드를 설정했을 때,
연결이 안되고 계속 에러가 걸려있다면 import 확인!

import org.w3c.dom.Documnet;

위와 같이 import가 되어있다면 아래로 변경해주면 된다.

import org.jsoup.nodes.Document;

Select()문법 에러

jsoup은 css query인 css Select()를 사용한다.
그런데 .select()를 사용하는데 함수를 사용할 수 없는 경우가 이에 해당한다.

이 오류에 대해 구글링했을 때, 아무도 지적이 없어서 꽤 많은 시간을 헤맸다...

진짜 내가 에러를 창조한 건가? 싶을 정도로 아무도 말이 없었음...

해결 방법을 찾아내긴했는데 import 쪽 문제였다^^

이유는 모르겠는데,

import org.jsoup.select.Element;

위와 같이 import가 되어있다면 Element뒤에 s를 붙여주자^^....

진짜 찾고 찾다가 Elements 객체를 사용하는데 왜 import는 Element라고 되어있네?

해서 Elements로 바꿔주었더니 해결됨.... 😑

현재 Test중인 프로젝트의 파일의 Jsoup import는 아래와 같음.

import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.jsoup.nodes.Document;

다른 부분이 있다면 위와 같이 고쳐보세요..



Jsoup만으로는 내가 원하는 것의 구현이 어렵다고 생각하는 이유

핵심적인 이유는 데이터가 따로 올 경우, WebView에서 데이터를 조합할 수 없다는 것.
(WebView에 TextView를 겹쳐 띄울 수는 없으므로)

또한, Jsoup을 파싱한 결과를 WebView에 띄워주는 것은 어렵지 않으나,
일부분 파싱이 아니라면 데이터를 일일히 담아두는 것이 어려움.

→ 물론 Jsoup을 완벽하게 숙지하진 못했기 때문에, 구글링 말고 공식 문서를 참고하고 공부하면
위의 방법들을 해결할 수도 있겠지만 계속 이걸 붙잡고 있을 수는 없으므로 다른 파싱 방법을 찾아보았다.

→ WebView 말고 텍스트뷰로 부분 조합 구현이 가능한지 다시 시도해볼 예정





+) 파싱 방법 추가

→ Jsoup 파싱은 가능하지만 이걸 화면 UI처럼 어떻게 띄워줘야하는지 고민이 됐다.

→ 구글링으로 나오는 Jsoup 예제들은 모두 크롤링 관련한 것이어서 내가 구현하려는 것과는 상응하지 않았음.

→ 근데 이 때, HTML코드로 작성된 탬플릿과 값을 String(문자열)값으로 한 번에 보내주면 그대로 띄워주기만 하면 될수도 있다는 말을 들었다.

→ Jsoup은 코드 일부를 가져올 때 좀 더 효율적임. 값을 굳이 바꿀 필요가 없이 띄워서 보여주기만 하면 된다하여 String 코드 해석이 편하면서, 이미지를 파싱해서 텍스트뷰로 띄워주기 위해 Html.fromHtml()함수와 혼용하여 사용해보기로 함.


◆ HTML 파싱 (Html.fromHtml)

🔹 fromHtml이란?

→ HTML을 해석하여 텍스트 뷰 화면에 뿌려주는 함수.
→ 모든 속성 태그를 지원하는 것은 아니므로 (생각보다 지원하는 태그가 적음 ㅠㅠ) 참고 후 사용.

🔹 사용법

직접 사용(테스트)해본 결과!
서식이 적용된 '텍스트 자체'만 해석해서 보여주려한다면 Jsoup에 비해 100배 정도는 쉬운 편...!
--------------------------------------------------------------------------------------------------------------------------------------

HTML String 예시

String htmlCode = "
	<a href="https://www.naver.com/">네이버</a>
   	<p>
		<img src="test.jpg" width="300" height="300">
	</p>
    <p style="color:red">이름</p>";

위와 같은 코드가 있을 때, 진짜 코드 몇줄 만으로도 화면에 보여주는 것이 가능!

TextView textView = findViewById(R.id.textView);
textView.setText(Html.fromHtml(htmlCode));
// a link click
textView.setMovementMethod(LinkMovementMethod.getInstance());

하다고, 다른 참고 문서에는 이렇게 짧은 코드만으로 구현이 가능하다 하는데
테스트했을 때는 지원하지 않는 서식 태그가 있기 때문인가..? 파싱이 제대로 되지 않음.

→ 텍스트 자체만 해석할 경우에는 파싱이 필요없지만, 나의 경우에는
화면 UI처럼 띄워주기 위해 (색깔, 이미지 등) Jsoup 파싱을 함께 혼용!

from.Html()의 경우, 이미지를 가져오기 위해서는 ImageGetter 매개 변수로 변환해주는 작업이 필요함!
아래 메서드를 활용하여 이미지도 뿌려주었다.

    private class ImageGetter implements Html.ImageGetter {
        public Drawable getDrawable(String source) {
            int id = getResources().getIdentifier(source, "drawable",getPackageName());
            Drawable d = getResources().getDrawable(id);
            int w = d.getIntrinsicWidth();
            int h = d.getIntrinsicHeight();
            d.setBounds(0, 0, w * 500 / h, 500); //이미지 크기 설정
            return d;
        }
    };

나는 아래와 같은 과정을 거쳐서

String Code
→ Jsoup 메소드를 활용하여 Code 전체 파싱
→ 이미지는 ImageGetter 메서드 사용
→ Html.fromHtml()함수를 사용하여 TextView에 뿌려줌.

텍스트뷰에 화면에 구현하는 것에 성공함.



 ---------------------------------------------------------------------------------------------------------------------------------

하지만

위의 테스트는 HTML 코드를 바로 화면에 보여주는 것이므로 데이터 값이 다 포함되어 있어야 가능함.

데이터 값이 포함되지 않고 따로 오는 경우도 대비하여 부분 조합 구현이 가능한지 다시 시도해볼 예정



Reference

0개의 댓글