정리/Java
XML
개발아기
2023. 3. 8. 15:55
데이터의 형태
- CSV
- 용량이 작지만 구조적이지 못함
연도, 제조사, 모델
1997, Ford, E350
1999, Chevy, "Venture"
- XML
- 구조적, 정확한 문법이 필요, 큰 용량
<cars>
<car>
<연도>1997</연도>
<제조사>Ford</제조사>
<모델>E350</모델>
</car>
<car>
<연도>1999</연도>
<제조사>Chevy</제조사>
<모델>Venture Edition</모델>
<car>
</cars>
- Json
- 구조를 가지며 객체로 다른 언어와 호환
[
{
"연도": 1997,
"제조사": "Ford",
"모델": "E350"
},
{
"연도": 1999,
"제조사": "Chevy"
"모델": "Venture Edition"
}
]
XML (Extensible Markup Language)
- Markup Language
- 태그를 이용하여 문서나 데이터의 구조를 명기하는 언어
- HTML과 달리
- 필요에 따라서 태그를 확장해서 사용 가능
- 정확한 문법을 지켜야 동작 (Well Formed)
문법
- 시작 태그
- <table>
- 끝 태그
- </table>
- 빈 태그
- <br/>
- 최소 한개 이상의 element를 가져야 함
- 문서 전체를 감싸는 한 개의 Root Element가 있어야 함
- 시작태그가 있다면 그것에 매핑되는 끝 태그가 반드시 존재해야 함
- XML은 대소문자를 구분
- 엘리먼트는 포함관계가 꼬이면 안됨
- 문서의 시작은 <?xml version="1.0" encoding="UTF-8"?>
- 태그의 내용에 제한된 문자가 있다.
- ‘&’ ‘<’ ‘]]>’ 를 사용시 에러
- 제한된 문자를 replace() 메소드로 변환 후 사용
- 속성 값에는 ‘& ‘<’ 가 제한되어 있음
- 속성값은 반드시 인용부호 ( ‘’ , “”)를 사용
- ex) <table border='1' bgcolor="yellow"></table>
- 서로 다른 속성은 반드시 공백을 통해 구분
- 주석문에는 '--' 를 사용하면 안됨
- XML Spec에 맞게 작성된 문서 + 추가적인 데한 사항, 형식(DTD, XMLSchema)을 만족
- DTD : XML문서 내에 출현할 태그와 속성에 대한 정의
valid
- xml 태그는 자유롭게 생성하기 때문에 최초 작성자의 의도대로 작성되는지 확인할 필요가 있음
- DTD 또는 Schema를 이용해서 문서의 규칙 작성
파싱
- 문서에서 필요한 정보를 얻기 위해 태그를 구별하고 내용을 추출하는 과정
SAX parser
- Simple API for XML parser
- 문서를 읽으면서 태그의 시작, 종료 등 이벤트를 기반으로 처리
- 빠르고 한번에 처리하기 때문에 다양한 탐색이 어려움
- 예시
- 오늘의 박스오피스 파싱
public class BoxOffice { // DTO 작성
private Integer rank;
private String movieNm;
private Date openDt;
private Integer audiAcc;
public Date toDate(String date) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-mm-dd");
try {
return format.parse(date);
} catch (ParseException e) {
e.printStackTrace();
return new Date();
}
}
}
public class BoxOfficeSaxParser extends DefaultHandler { // Handler 작성
private final File xml = new File("./bin/com/ssafy/i_xml_ui/parse/boxoffice.xml");
// 파싱된 내용을 저장할 List
private List<BoxOffice> list = new ArrayList<>();
// 현재 파싱하고 있는 대상 객체
private BoxOffice current;
// 방금 읽은 텍스트 내용
private String content;
public List<BoxOffice> getBoxOffice() {
// TODO: SAXParser를 구성하고 boxoffice.xml을 파싱하시오.
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
parser.parse(xml, this);
} catch (ParserConfigurationException | SAXException |IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// END:
return list;
}
// TODO: 필요한 매서드를 재정의 하여 boxOffice.xml을 파싱하시오.
@Override
public void startDocument() throws SAXException {
// TODO Auto-generated method stub
System.out.println("문서 파싱 시작");
}
@Override
public void endDocument() throws SAXException {
// TODO Auto-generated method stub
System.out.println("문서 파싱 종료");
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
// TODO Auto-generated method stub
if (qName.equals("dailyBoxOffice")) {
current = new BoxOffice();
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
// TODO Auto-generated method stub
if (qName.equals("rank")) {
this.current.setRank(Integer.parseInt(content));
} else if (qName.equals("movieNm")) {
this.current.setMovieNm(this.content);
} else if (qName.equals("openDt")) {
this.current.setOpenDt(current.toDate(this.content));
} else if (qName.equals("audioAcc")) {
this.current.setAudiAcc(Integer.parseInt(this.content));
} else if (qName.equals("dailyBoxOffice")) {
this.list.add(this.current);
this.current = null;
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
// TODO Auto-generated method stub
this.content = new String(ch, start, length);
}
// END:
}
DOM parser
- Document Object Model
- 문서를 다 읽고 난 후 문서 구조 전체를 자료구조에 저장하여 탐색하는 방식
- 다양한 탐색이 가능하지만 느리고 무거우며 큰 문서를 처리하기 어려움
- DOM Tree
- 문서를 구성하는 모든 요소를 Node로 구성
- 태그들은 root 노드(주소록)을 시작으로 부모-자식의 관계 구성
public class BoxOfficeDomParser { // Handler private final File xml = new File("./ssafy_java_live/src/com/ssafy/i_xml_ui/parse/boxoffice.xml"); private List<BoxOffice> list = new ArrayList<>(); public List<BoxOffice> getBoxOffice() { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); // 문서 로딩 완료 --> 원하는 요소들 골라내기 Document doc = builder.parse(xml); // 최 상위 element Element root = doc.getDocumentElement(); parse(root); } catch (IOException | ParserConfigurationException | SAXException e) { e.printStackTrace(); } return list; } private void parse(Element root) { // TODO: root에서 dailyBoxOffice를 추출한 후 BoxOffice를 생성해 list에 저장하시오. NodeList boxOffices = root.getElementsByTagName("dayliBoxOffice"); for (int i = 0; i < boxOffices.getLength(); i++) { Node child = boxOffices.item(i); list.add(getBoxOffice(child)); } // END: } private static BoxOffice getBoxOffice(Node node) { BoxOffice boxOffice = new BoxOffice(); // TODO: node 정보를 이용해서 BoxOffice를 구성하고 반환하시오. NodeList subNodes = node.getChildNodes(); for (int j = 0; j < subNodes.getLength(); j++) { Node sub = subNodes.item(j); 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())); } } // END: return boxOffice; } }