JavaRush /Java Blog /Random-KO /Java 프로그래머를 위한 XML 기본 - 3부 중 3.1부 - SAX
Ярослав
레벨 40
Днепр

Java 프로그래머를 위한 XML 기본 - 3부 중 3.1부 - SAX

Random-KO 그룹에 게시되었습니다
소개 아직 마지막 기사가 아닌 독자 여러분께 안녕하세요. 축하하고 싶습니다. XML에 관한 복잡한 내용은 이제 끝났습니다. 이 기사에는 Java 코드가 포함되어 있습니다. 약간의 이론이 있고 실습이 있을 것입니다. SAX의 한 자료가 Word의 10페이지를 채우고 있다는 사실 때문에 나는 한계에 맞지 않는다는 것을 깨달았습니다. 따라서 기사 3은 아무리 이상하게 들리더라도 3개의 개별 기사로 나누어집니다. 모든 것은 SAX -> DOM -> JAXB 순서로 진행됩니다. 이 기사에서는 SAX에만 중점을 둘 것입니다. 추신: 강좌 어딘가에 HTML 파일의 모든 내부 요소를 표시해야 하는 작업이 있었습니다. 이 기사 이후에는 기존의 복잡한 처리 알고리즘을 사용하여 한 줄씩 읽지 않고도 이 작업을 수행할 수 BufferedReader있으며 마지막 실제 예제에서도 유사한 솔루션이 제공됩니다. 시작해 보겠습니다 :) SAX(Simple API for XML) - 이론 SAX 핸들러는 단순히 XML 파일을 순차적으로 읽고 다양한 이벤트에 반응한 후 해당 정보를 특수 이벤트 핸들러에 전달하는 방식으로 설계되었습니다. 꽤 많은 이벤트가 있지만 가장 자주 발생하고 유용한 이벤트는 다음과 같습니다.
  1. startDocument— 문서의 시작 부분
  2. endDocument- 문서 끝
  3. startElement- 요소 열기
  4. endElement- 요소 닫기
  5. characters— 요소 내부의 텍스트 정보.
모든 이벤트는 이벤트 핸들러 에서 처리되며 , 이벤트 핸들러를 생성하고 메소드를 재정의 해야 합니다 . 장점: 데이터를 "직접" 읽는 방법으로 인한 높은 성능, 낮은 메모리 비용. 단점: 기능이 제한되어 있어 비선형 문제에서는 이를 개선해야 합니다. SAX(Simple API for XML) – 연습 아무것도 검색하거나 혼동하지 않도록 즉시 가져오기 목록을 가져옵니다.
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
이제 먼저 SAXParser를 만들어야 합니다.
public class SAXExample {
    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        // Creation фабрики и образца parserа
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();
    }
}
보시다시피 먼저 팩토리를 생성한 다음 팩토리에서 파서 자체를 생성해야 합니다. 이제 파서 자체가 있으므로 해당 이벤트에 대한 핸들러가 필요합니다. 이를 위해서는 편의를 위해 별도의 클래스가 필요합니다.
public class SAXExample {
    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();
    }

    private static class XMLHandler extends DefaultHandler {
        @Override
        public void startDocument() throws SAXException {
            // Тут будет логика реакции на начало documentа
        }

        @Override
        public void endDocument() throws SAXException {
            // Тут будет логика реакции на конец documentа
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            // Тут будет логика реакции на начало element
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            // Тут будет логика реакции на конец element
        }

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            // Тут будет логика реакции на текст между elementми
        }

        @Override
        public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
            // Тут будет логика реакции на пустое пространство внутри элементов (пробелы, переносы строчек и так далее).
        }
    }
}
우리는 이론에 나열된 이벤트를 처리하는 데 필요한 모든 메서드를 포함하는 클래스를 만들었습니다. 조금 더 추가 이론: 약간 characters: 요소에 " hello "와 같은 텍스트가 포함된 경우 이론적으로 각 개별 문자에 대해 메서드를 연속으로 5번 호출할 수 있지만 이는 무서운 것은 아닙니다. 여전히 작동합니다. 및 메소드 정보 :startElementendElementuri - 요소가 위치한 공간입니다. localName- 접두사가 없는 요소의 이름입니다. qName- 접두사가 있는 요소의 이름입니다(있는 경우 그렇지 않으면 이름만). 요소). 공장에서 공간 처리를 활성화하지 않은 경우 항상 비어 있습니다 uri. localName이는 팩토리 메소드를 사용하여 수행됩니다 setNamespaceAware(true). uri그런 다음 공백( )과 그 앞에 접두사가 있는 요소( )를 얻을 수 있습니다 localName. 작업 #1 - 다음과 같은 XML이 있습니다.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<company>
    <name>IT-Heaven</name>
    <offices>
        <office floor="1" room="1">
            <employees>
                <employee name="Maksim" job="Middle Software Developer" />
                <employee name="Ivan" job="Junior Software Developer" />
                <employee name="Franklin" job="Junior Software Developer" />
            </employees>
        </office>
        <office floor="1" room="2">
            <employees>
                <employee name="Herald" job="Middle Software Developer" />
                <employee name="Adam" job="Middle Software Developer" />
                <employee name="Leroy" job="Junior Software Developer" />
            </employees>
        </office>
    </offices>
</company>
우리의 목표는 이 파일에서 모든 직원에 대한 모든 정보를 얻는 것입니다. 먼저 클래스를 생성해야 합니다Employee.
public class Employee {
    private String name, job;

    public Employee(String name, String job) {
        this.name = name;
        this.job = job;
    }

    public String getName() {
        return name;
    }

    public String getJob() {
        return job;
    }
}
그리고 메인 클래스에는 SAXExample모든 직원이 포함된 목록이 필요합니다.
private static ArrayList<Employee> employees = new ArrayList<>();
이제 필요한 정보가 XML 파일의 어디에 있는지 주의 깊게 살펴보겠습니다. 그리고 보시다시피 우리에게 필요한 모든 정보는 요소의 속성입니다 employee. 그리고 startElement와 같은 유용한 매개변수가 있기 때문에 attributes작업이 매우 간단합니다. 먼저, 코드가 복잡해지지 않도록 불필요한 메서드를 제거해 보겠습니다. 우리는 단지 startElement. 그리고 메서드 자체에서는 직원 태그의 속성에서 정보를 수집해야 합니다. 주목:
public class SAXExample {
    private static ArrayList<Employee> employees = new ArrayList<>();

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();
    }

    private static class XMLHandler extends DefaultHandler {
        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            if (qName.equals("employee")) {
                String name = attributes.getValue("name");
                String job = attributes.getValue("job");
                employees.add(new Employee(name, job));
            }
        }
    }
}
논리는 간단합니다. 요소의 이름이 이면 employee해당 속성에 대한 정보만 받게 됩니다. attributes속성의 이름을 알면 해당 값을 얻을 수 있는 유용한 방법이 있습니다 . 그것이 우리가 사용한 것입니다. 이제 요소 시작에 대한 이벤트 핸들러를 만들었으므로 XML 파일을 구문 분석해야 합니다 . 이렇게 하려면 다음을 수행하세요.
public class SAXExample {
    private static ArrayList<Employee> employees = new ArrayList<>();

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();

        XMLHandler handler = new XMLHandler();
        parser.parse(new File("resource/xml_file1.xml"), handler);

        for (Employee employee : employees)
            System.out.println(String.format("Name сотрудника: %s, его должность: %s", employee.getName(), employee.getJob()));
    }

    private static class XMLHandler extends DefaultHandler {
        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            if (qName.equals("employee")) {
                String name = attributes.getValue("name");
                String job = attributes.getValue("job");
                employees.add(new Employee(name, job));
            }
        }
    }
}
구문 분석 메소드에서는 생성한 핸들러와 xml 파일의 경로를 전달해야 합니다. 그래서 우리는 이 코드를 사용하여 이 XML에서 정보를 추출했습니다.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<company>
    <name>IT-Heaven</name>
    <offices>
        <office floor="1" room="1">
            <employees>
                <employee name="Maksim" job="Middle Software Developer" />
                <employee name="Ivan" job="Junior Software Developer" />
                <employee name="Franklin" job="Junior Software Developer" />
            </employees>
        </office>
        <office floor="1" room="2">
            <employees>
                <employee name="Herald" job="Middle Software Developer" />
                <employee name="Adam" job="Middle Software Developer" />
                <employee name="Leroy" job="Junior Software Developer" />
            </employees>
        </office>
    </offices>
</company>
그리고 우리는 다음과 같은 결과를 얻었습니다.
Name сотрудника: Maksim, его должность: Middle Software Developer
Name сотрудника: Ivan, его должность: Junior Software Developer
Name сотрудника: Franklin, его должность: Junior Software Developer
Name сотрудника: Herald, его должность: Middle Software Developer
Name сотрудника: Adam, его должность: Middle Software Developer
Name сотрудника: Leroy, его должность: Junior Software Developer
임무 완수! 작업 #2 - 다음과 같은 XML이 있습니다.
<?xml version="1.0" encoding="UTF-8"?>
<company>
    <name>IT-Heaven</name>
    <offices>
        <office floor="1" room="1">
            <employees>
                <employee>
                    <name>Maksim</name>
                    <job>Middle Software Developer</job>
                </employee>
                <employee>
                    <name>Ivan</name>
                    <job>Junior Software Developer</job>
                </employee>
                <employee>
                    <name>Franklin</name>
                    <job>Junior Software Developer</job>
                </employee>
            </employees>
        </office>
        <office floor="1" room="2">
            <employees>
                <employee>
                    <name>Herald</name>
                    <job>Middle Software Developer</job>
                </employee>
                <employee>
                    <name>Adam</name>
                    <job>Middle Software Developer</job>
                </employee>
                <employee>
                    <name>Leroy</name>
                    <job>Junior Software Developer</job>
                </employee>
            </employees>
        </office>
    </offices>
</company>
우리의 목표는 이 파일에서 모든 직원에 대한 모든 정보를 얻는 것입니다. 이 문제는 구조가 잘못된 XML 파일이 코드 작성을 어떻게 더 어렵게 만드는지 잘 보여줍니다. 보시다시피 이름과 위치에 대한 정보는 이제 name및 요소 내부에 텍스트 정보로 저장됩니다 job. 요소 내부의 텍스트를 읽으려면 문자 메소드가 있습니다. 이를 위해서는 향상된 로직을 갖춘 새로운 핸들러 클래스를 생성해야 합니다. 핸들러는 복잡한 논리를 저장할 수 있는 본격적인 클래스라는 점을 잊지 마십시오. 따라서 이제 프로세서를 조정하겠습니다. 사실 우리는 항상 번갈아 name가며 job어떤 순서로 하든 상관없이 이름과 직업을 별도의 변수에 쉽게 저장하고, 두 변수를 모두 저장하면 직원을 생성한다는 점만 참고해도 충분하다. 여기서만 요소의 시작 부분과 함께 요소 내부의 텍스트에 대한 매개변수가 없습니다. 텍스트에 메서드를 사용해야 합니다. 하지만 완전히 다른 방법이라면 요소 내부의 텍스트 정보를 어떻게 얻을 수 있을까요? 내 해결책: 마지막 요소의 이름을 기억하고 characters어떤 요소에서 정보를 읽고 있는지 확인하면 됩니다. 또한 <codee> 문자는 요소 내부의 모든 문자를 읽는다는 점을 기억해야 합니다. 즉, 모든 공백과 줄 바꿈까지 읽힌다는 의미입니다. 그리고 우리는 그것들이 필요하지 않습니다. 이 데이터는 정확하지 않으므로 무시해야 합니다.</codee> 코드:
public class SAXExample {
    private static ArrayList<Employee> employees = new ArrayList<>();

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();

        AdvancedXMLHandler handler = new AdvancedXMLHandler();
        parser.parse(new File("resource/xml_file2.xml"), handler);

        for (Employee employee : employees)
            System.out.println(String.format("Name сотрудника: %s, его должность: %s", employee.getName(), employee.getJob()));
    }

    private static class AdvancedXMLHandler extends DefaultHandler {
        private String name, job, lastElementName;

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            lastElementName = qName;
        }

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            String information = new String(ch, start, length);

            information = information.replace("\n", "").trim();

            if (!information.isEmpty()) {
                if (lastElementName.equals("name"))
                    name = information;
                if (lastElementName.equals("job"))
                    job = information;
            }
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            if ( (name != null && !name.isEmpty()) && (job != null && !job.isEmpty()) ) {
                employees.add(new Employee(name, job));
                name = null;
                job = null;
            }
        }
    }
}
보시다시피 XML 파일 구조의 진부한 복잡성으로 인해 코드가 훨씬 더 복잡해졌습니다. 그러나 코드는 복잡하지 않습니다. 설명: 직원 ( name, job) 에 대한 데이터를 저장하는 변수 와 lastElementName우리가 내부에 있는 요소를 기록하는 변수를 만들었습니다. 그런 다음 방법으로 characters정보를 필터링하고 여전히 정보가 있으면 이것이 필요한 텍스트임을 의미하며 를 사용하여 이름인지 직업인지 확인합니다 lastElementName. 메소드에서는 endElement모든 정보를 읽었는지 확인하고, 읽혔다면 직원을 생성하고 정보를 재설정합니다. 솔루션의 출력은 첫 번째 예와 동일합니다.
Name сотрудника: Maksim, его должность: Middle Software Developer
Name сотрудника: Ivan, его должность: Junior Software Developer
Name сотрудника: Franklin, его должность: Junior Software Developer
Name сотрудника: Herald, его должность: Middle Software Developer
Name сотрудника: Adam, его должность: Middle Software Developer
Name сотрудника: Leroy, его должность: Junior Software Developer
따라서 이 문제는 해결 되었지만 복잡성이 더 높다는 것을 알 수 있습니다. 따라서 텍스트 정보를 속성에 저장하는 것이 개별 요소에 저장하는 것보다 더 정확하다는 결론을 내릴 수 있습니다. 그리고 HTML의 요소에 대한 정보를 표시하는 것과 관련된 JavaRush의 문제를 부분적으로 해결하는 또 하나의 멋진 작업은 약간만 편집하면 됩니다. 여기서는 요소 내부의 모든 요소를 ​​간단히 나열하겠습니다 :) 작업 번호 3 - 요소 요소가 주어지면 모든 내부 요소의 이름과 속성을 표시하고 요소를 찾을 수 없으면 이를 표시합니다. 이 작업에서는 다음 XML 파일을 사용합니다.
<?xml version="1.0" encoding="UTF-8"?>
<root>
    <oracle>
        <connection value="jdbc:oracle:thin:@10.220.140.48:1521:test1" />
        <user value="secretOracleUsername" />
        <password value="111" />
    </oracle>

    <mysql>
        <connection value="jdbc:mysql:thin:@10.220.140.48:1521:test1" />
        <user value="secretMySQLUsername" />
        <password value="222" />
    </mysql>
</root>
보시다시피 여기에는 세 가지 가능한 시나리오가 있습니다: root, mysql, oracle. 그러면 프로그램은 내부의 모든 요소에 대한 모든 정보를 표시합니다. 어떻게 할 수 있나요? 매우 간단합니다. isEntered내부에 요소가 필요한지 여부를 나타내는 논리 변수 를 선언하고, 내부에 있으면 에서 모든 데이터를 읽으면 됩니다 startElement. 솔루션 코드:
public class SAXExample {
    private static boolean isFound;

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();

        SearchingXMLHandler handler = new SearchingXMLHandler("root");
        parser.parse(new File("resource/xml_file3.xml"), handler);

        if (!isFound)
            System.out.println("Элемент не был найден.");
    }

    private static class SearchingXMLHandler extends DefaultHandler {
        private String element;
        private boolean isEntered;

        public SearchingXMLHandler(String element) {
            this.element = element;
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            if (isEntered) {
                System.out.println(String.format("Найден элемент <%s>, его атрибуты:", qName));

                int length = attributes.getLength();
                for(int i = 0; i < length; i++)
                    System.out.println(String.format("Name атрибута: %s, его meaning: %s", attributes.getQName(i), attributes.getValue(i)));
            }

            if (qName.equals(element)) {
                isEntered = true;
                isFound = true;
            }
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            if (qName.equals(element))
                isEntered = false;
        }
    }
}
이 코드에서는 정보가 필요한 요소를 입력할 때 플래그를 isEnteredtrue로 설정합니다. 이는 요소 내부에 있음을 의미합니다. 그리고 요소 내부에 들어가자마자 우리는 startElement그것이 정확히 우리 요소의 내부 요소라는 것을 알고 각각의 새로운 요소를 간단히 처리합니다. 그래서 우리는 요소 이름과 제목을 출력합니다. 파일에서 요소를 찾을 수 없으면 isFound요소를 찾을 때 설정되는 변수가 있고, false인 경우 요소를 찾을 수 없다는 메시지가 표시됩니다. 보시다시피, 예제에서는 생성자에 요소를 SearchingXMLHandler전달했습니다 . root그에 대한 결론:
Найден элемент <oracle>, его атрибуты:
Найден элемент <connection>, его атрибуты:
Name атрибута: value, его meaning: jdbc:oracle:thin:@10.220.140.48:1521:test1
Найден элемент <user>, его атрибуты:
Name атрибута: value, его meaning: secretOracleUsername
Найден элемент <password>, его атрибуты:
Name атрибута: value, его meaning: 111
Найден элемент <mysql>, его атрибуты:
Найден элемент <connection>, его атрибуты:
Name атрибута: value, его meaning: jdbc:mysql:thin:@10.220.140.48:1521:test1
Найден элемент <user>, его атрибуты:
Name атрибута: value, его meaning: secretMySQLUsername
Найден элемент <password>, его атрибуты:
Name атрибута: value, его meaning: 222
따라서 우리는 내부 요소와 해당 속성에 대한 모든 정보를 받았습니다. 문제가 해결되었습니다. <h2>에필로그</h2>SAX가 매우 흥미롭고 매우 효과적인 도구이며 다양한 방식, 다양한 목적 등으로 사용될 수 있다는 점을 살펴보았습니다. 문제를 올바른 측면에서 바라볼 필요가 있습니다. 작업 2번과 3번에서 볼 수 있듯이 SAX는 문제 해결을 위한 직접적인 방법을 제공하지 않았지만 우리의 독창성 덕분에 상황에서 벗어날 수 있는 방법을 찾을 수 있었습니다. 이 기사의 다음 부분은 전적으로 DOM에 대해 다룰 것입니다. SAX를 알게 되어 즐거웠기를 바랍니다. 실험하고 연습하면 모든 것이 매우 간단하다는 것을 이해할 것입니다. 그게 전부입니다. 프로그래밍에 행운이 있기를 바라며 곧 DOM에 대한 부분을 기대하겠습니다. 학업에 행운이 있기를 바랍니다 :) 이전 기사: [대회] Java 프로그래머를 위한 XML 기초 - 3부 중 2부 다음 기사: [대회] Java 프로그래머를 위한 XML 기초 - 3부 중 3.2 - DOM
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION