XML 은 태그 등을 이용하여 문서나 데이터를 구조화하는 언어이다.
xml 태그는 자유롭게 생성하기 때문에 최초 작성자의 의도대로 작성되는지 확인할 필요가 있으며 DTD 또는 Schema를 이용해서 문서의 규칙을 작성한다. 이러한 DTD, Schema를 잘 따른 문서를 "valid 하다" 라고한다.
파싱이란 문서에서 필요한 정보를 얻기 위해 태그를 구별하고 내용을 추출하는 과정이다.
문서의 파싱에는 대표적으로 3가지 방식이 있다
SAX(Simple API for XML) parser
- 문서를 읽으면서 태그의 시작, 종료 등 이벤트 기반으로 처리하는 방식
- 빠르고 한 번에 처리하기 때문에 다양한 탐색이 어렵다
DOM(Document Object Model) parser
- 문서를 전부 로딩한 후 문서 구조 전체를 자료구조에 저장하여 탐색하는 방식
- 다양한 탐색이 가능하지만 느리고 무거우며 큰 문서를 처리하기 힘들다
JSON
- 객체를 key-value 의 쌍으로 관리한다.
SAX parser
SAX Parser Factory
가 SAX parser
을 생성하고 이벤트를 체크하는 메서드를 가진 DefaultHandler
를 등록한 후, 문서를 읽다가 이벤트가 발생하면 메서드를 호출하여 문서를 처리한다.
다음과 같은 메서드를 XML 태그에 따라 재정의하여 사용한다.qName
은 태그명이다.
- startElement(String uri, String localName, String qName, Attributes attributes): 태그 시작
- endElement(String uri, String localName, String qName): 태그 끝
- characters(char[] ch, int start, int length): 문자열 시작
💻[실습] 일별 박스오피스 XML 파일이 있을 때, SAX parser 를 이용하여 파싱해보기
① 등수, 영화제목, 개봉일, 누적 관객 수를 저장할 BoxOffice DTO를 생성하여 데이터를 저장한다
② Handler 작성
SAXParser
을 구성하고 파라미터로 받은 xml 파일을 파싱한다.
public class BoxOfficeSaxParser extends DefaultHandler implements BoxOfficeParser {
private List<BoxOffice> list = new ArrayList<>(); // 파싱된 내용을 저장할 List
private BoxOffice current; // 현재 파싱하고 있는 대상 객체
private String content; // 방금 읽은 텍스트 내용
// singleton 구조
private static BoxOfficeSaxParser parser = new BoxOfficeSaxParser();
public static BoxOfficeSaxParser getParser() {
return parser;
}
@Override
public List<BoxOffice> getBoxOffice(InputStream resource) {
// SAXParser를 구성하고 boxoffice.xml을 파싱
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
parser.parse(resource, this);
} catch (IOException | ParserConfigurationException | SAXException e) {
e.printStackTrace();
}
return list;
}
...
}
DefaultHanlder 를 상속받아 메서드를 태그에 따라 재정의해준다.
...
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if (qName.equals("dailyBoxOffice")) {
current = new BoxOffice();
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (qName.equals("rank")) {
current.setRank(Integer.parseInt(content));
} else if (qName.equals("movieNm")) {
current.setMovieNm(content);
} else if (qName.equals("openDt")) {
current.setOpenDt(current.toDate(content));
} else if (qName.equals("audiAcc")) {
current.setAudiAcc(Integer.parseInt(content));
} else if (qName.equals("dailyBoxOffice")) {
list.add(this.current);
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
this.content = String.valueOf(ch, start, length);
}
③ XML 파싱 및 결과확인
public class BoxOfficeCLI {
private BoxOfficeParser parser = null;
private InputStream resource = null;
private void readBoxOfficeList() {
// resource와 parser를 구성해서 정보 가져오기
this.resource = BoxOfficeCLI.class.getResourceAsStream("../res/boxoffice.xml");
this.parser = BoxOfficeSaxParser.getParser();
List<BoxOffice> list = parser.getBoxOffice(resource);
for (BoxOffice boxOffice : list) {
System.out.println(boxOffice);
}
}
}
DOM parser
문서를 완전히 메모리에 로딩 후 필요한 내용을 찾는다. DocumentBuilderFacotry
로 DocumentBuilder
를 생성한 후 DOM Tree 를 구성한다.
DOM Tree
- 문서를 구성하는 모든 요소를 Node(태그, 속성, 값) 으로 구성
- 태그들은 root 노드를 시작으로 부모-자식의 관계 구성
① getElementsByTagName
을 통해 최상위 element을 추출한 후 반복문을 통해 하위 element 들을 끄집어낸다.
② 끄집어낸 하위 노드들에서 반복문을 돌며 getNodeName() 을 통해 태그명을 찾고 getTextContent() 를 통해 원하는 데이터를 추출한다.
public class BoxOfficeDomParser implements BoxOfficeParser {
private List<BoxOffice> list = new ArrayList<>();
private static BoxOfficeDomParser parser = new BoxOfficeDomParser();
private BoxOfficeDomParser() { }
public static BoxOfficeDomParser getParser() {
return parser;
}
@Override
public List<BoxOffice> getBoxOffice(InputStream resource) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(resource); // 문서 로딩
Element root = doc.getDocumentElement(); // 최상위 element
parse(root);
} catch (IOException | ParserConfigurationException | SAXException e) {
e.printStackTrace();
}
return list;
}
private void parse(Element root) {
// root에서 dailyBoxOffice를 추출한 후 BoxOffice를 생성해 list에 저장
NodeList boxOffices = root.getElementsByTagName("dailyBoxOffice");
for (int i = 0; i < boxOffices.getLength(); i++) {
Node child = boxOffices.item(i);
list.add(getBoxOffice(child));
}
}
private static BoxOffice getBoxOffice(Node node) {
BoxOffice boxOffice = new BoxOffice();
// node 정보를 이용해서 BoxOffice를 구성하고 반환
NodeList subNodes = node.getChildNodes();
for (int i = 0; i < subNodes.getLength(); i++) {
Node sub = subNodes.item(i);
if (sub.getNodeName().equals("rank")) {
boxOffice.setRank(Integer.parseInt(sub.getTextContent()));
} else if(sub.getNodeName().equals("movieNm")) {
boxOffice.setMovieNm(sub.getTextContent());
} else if(sub.getNodeName().equals("openDt")) {
boxOffice.setOpenDt(boxOffice.toDate(sub.getTextContent()));
} else if(sub.getNodeName().equals("audiAcc")) {
boxOffice.setAudiAcc(Integer.parseInt(sub.getTextContent()));
}
}
return boxOffice;
}
}
③ XML 파싱 및 결과확인
private void readBoxOfficeList() {
this.resource = BoxOfficeCLI.class.getResourceAsStream("../res/boxoffice.json");
this.parser = BoxOfficeDomParser.getParser();
List<BoxOffice> list = parser.getBoxOffice(resource);
System.out.println("list size: " + list.size());
for (BoxOffice boxOffice : list) {
System.out.println(boxOffice);
}
}
JSON
간결한 문법, 단순한 텍스트, 적은 용량으로 대부분의 언어 및 플랫폼에서 사용 가능하다
JSON 파일을 자바로 생각해보면 Map<String, Map<String, Object>> 구조로 되어있다. 내가 필요한 데이터는 dailyBoxOfficeList 로 List<Map<String,Object>> 구조이다.
① ObjectMapper
를 이용하여 문서를 읽고 원하는 데이터를 get()
을 이용하여 끄집어낸다.
② 끄집어 낸 데이터를 convertValue()
를 통해 BoxOffice 객체에 저장한다.
public class BoxOfficeJsonParser implements BoxOfficeParser {
private List<BoxOffice> list = new ArrayList<>();
private static BoxOfficeJsonParser parser = new BoxOfficeJsonParser();
public static BoxOfficeJsonParser getParser() {
return parser;
}
@Override
public List<BoxOffice> getBoxOffice(InputStream resource) {
ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd")); // 날짜 변경
try {
Map<String, Map<String, Object>> result = mapper.readValue(resource, Map.class);
List<Map<String, Object>> list = (List) result.get("boxOfficeResult").get("dailyBoxOfficeList");
for (Map<String, Object> info : list) {
BoxOffice office = mapper.convertValue(info, BoxOffice.class);
this.list.add(office);
}
} catch (IOException e) {
e.printStackTrace();
}
return list;
}
}
③ 문서에서 BoxOffice DTO 의 멤버변수와 일치하는 데이터만을 가져오기 위해 BoxOffice 클래스에 @JsonIgnoreProperties
어노테이션을 추가한다.
@JsonIgnoreProperties(ignoreUnknown = true)
public class BoxOffice {
private Integer rank; // 등수
private String movieNm; // 영화제목
private Date openDt; // 개봉일
private Integer audiAcc;// 누적 관객 수
...
}
④ XML파싱 및 결과 실행
private void readBoxOfficeList() {
this.resource = BoxOfficeCLI.class.getResourceAsStream("../res/boxoffice.json");
this.parser = BoxOfficeJsonParser.getParser();
List<BoxOffice> list = parser.getBoxOffice(resource);
System.out.println("list size: " + list.size());
for (BoxOffice boxOffice : list) {
System.out.println(boxOffice);
}
}
'Language > JAVA' 카테고리의 다른 글
[JAVA] Dangling meta character ‘+’ near index 0 에러 해결 (0) | 2024.03.10 |
---|---|
[JAVA] 자바 스트림 API (0) | 2023.09.08 |
[JAVA] 표준 API 함수적 인터페이스 - 람다식 및 메서드 참조(Method Reference) (0) | 2023.09.07 |
[JAVA] 람다 표현식의 개념과 특징 (0) | 2023.09.07 |
[JAVA] 컬렉션 Collection API (0) | 2023.07.24 |