개발아기 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/>
  1. 최소 한개 이상의 element를 가져야 함
  2. 문서 전체를 감싸는 한 개의 Root Element가 있어야 함
  3. 시작태그가 있다면 그것에 매핑되는 끝 태그가 반드시 존재해야 함
  4. XML은 대소문자를 구분
  5. 엘리먼트는 포함관계가 꼬이면 안됨
  6. 문서의 시작은 <?xml version="1.0" encoding="UTF-8"?>
  7. 태그의 내용에 제한된 문자가 있다.
    1. ‘&’ ‘<’ ‘]]>’ 를 사용시 에러
    2. 제한된 문자를 replace() 메소드로 변환 후 사용
    3. 속성 값에는 ‘& ‘<’ 가 제한되어 있음
  8. 속성값은 반드시 인용부호 ( ‘’ , “”)를 사용
    1. ex) <table border='1' bgcolor="yellow"></table>
  9. 서로 다른 속성은 반드시 공백을 통해 구분
  10. 주석문에는 '--' 를 사용하면 안됨
  11. XML Spec에 맞게 작성된 문서 + 추가적인 데한 사항, 형식(DTD, XMLSchema)을 만족
  12. 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;
        }
    }