บทนำ สวัสดีผู้อ่านบทความที่ยังไม่จบของฉันทุกคน และฉันอยากจะแสดงความยินดีกับคุณ: เรื่องที่ซับซ้อนเกี่ยวกับ XML อยู่ข้างหลังเราแล้ว บทความนี้จะมีโค้ดในภาษา Java จะมีทฤษฎีเล็กๆ น้อยๆ แล้วจึงค่อยฝึกฝน
เนื่องจากข้อเท็จจริงที่ว่าเนื้อหาชิ้นหนึ่งใน SAX เต็มไปด้วย 10 หน้าใน Word ฉันจึงตระหนักว่าฉันไม่สามารถพอดีกับขีดจำกัดได้ ดังนั้นข้อ 3 จะแบ่งเป็น 3 บทความแยกกัน ไม่ว่าจะฟังดูแปลกแค่ไหนก็ตาม ทุกอย่างจะอยู่ในลำดับนี้: SAX -> DOM -> JAXB บทความนี้จะเน้นเฉพาะ SAX เท่านั้น ป.ล. มีงานบางแห่งในหลักสูตรที่จำเป็นต้องแสดงองค์ประกอบภายในทั้งหมดในไฟล์ HTML หลังจากบทความนี้ คุณจะสามารถทำได้โดยไม่ต้องอ่านทีละบรรทัดด้วย
มีเหตุการณ์ค่อนข้างน้อย แต่เหตุการณ์ที่พบบ่อยและมีประโยชน์ที่สุดมีดังต่อไปนี้:
BufferedReader
อัลกอริธึมการประมวลผลแบบธรรมดาและซับซ้อน และจะมีวิธีแก้ปัญหาที่คล้ายกันในตัวอย่างภาคปฏิบัติสุดท้ายด้วย มาเริ่มกันเลย :) SAX (Simple API สำหรับ XML) - ทฤษฎี ตัวจัดการ SAX ได้รับการออกแบบในลักษณะที่จะอ่านไฟล์ XML ตามลำดับและตอบสนองต่อเหตุการณ์ต่างๆ หลังจากนั้นจะส่งข้อมูลไปยังตัวจัดการเหตุการณ์พิเศษ 
startDocument
— จุดเริ่มต้นของเอกสารendDocument
- สิ้นสุดเอกสารstartElement
- การเปิดองค์ประกอบendElement
- การปิดองค์ประกอบcharacters
— ข้อมูลข้อความภายในองค์ประกอบ
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();
}
}
อย่างที่คุณเห็น คุณต้องสร้างโรงงานก่อน จากนั้นจึงสร้างตัวแยกวิเคราะห์ในโรงงาน ตอนนี้เรามี parser แล้ว เราต้องการตัวจัดการสำหรับเหตุการณ์ของมัน เพื่อสิ่งนี้ เราจำเป็นต้องมีชั้นเรียนแยกต่างหากเพื่อความสะดวกของเราเอง:
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
:หากองค์ประกอบมีข้อความเช่น " สวัสดี " ในทางทฤษฎีแล้ววิธีการนี้สามารถเรียกได้ 5 ครั้งติดต่อกันสำหรับอักขระแต่ละตัว แต่นี่ไม่น่ากลัวเพราะทุกอย่าง จะยังคงทำงานอยู่ เกี่ยวกับ วิธีการ startElement
และendElement
:uri
- นี่คือช่องว่างที่องค์ประกอบตั้งอยู่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 โค้ดของเราจึงมีความซับซ้อนมากขึ้นอย่างมาก อย่างไรก็ตามโค้ดไม่ได้ซับซ้อน Description:เราสร้างตัวแปรเพื่อเก็บข้อมูลเกี่ยวกับพนักงาน( 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
ดังนั้นปัญหานี้ได้รับการแก้ไขแล้วแต่คุณสามารถสังเกตได้ว่าความซับซ้อนนั้นสูงขึ้น ดังนั้นเราจึงสรุปได้ว่าการจัดเก็บข้อมูลข้อความในแอตทริบิวต์มักจะถูกต้องมากกว่าในองค์ประกอบแต่ละรายการ และอีกหนึ่งงานที่แสนหวานที่จะแก้ปัญหาบางส่วนใน JavaRush เกี่ยวกับการแสดงข้อมูลเกี่ยวกับองค์ประกอบใน HTML เพียงแต่จะต้องแก้ไขเพียงเล็กน้อยเท่านั้น ที่นี่เราจะแสดงรายการองค์ประกอบทั้งหมดภายในองค์ประกอบ :) ภารกิจที่ 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;
}
}
}
ในโค้ดนี้ เมื่อป้อนองค์ประกอบที่เราต้องการข้อมูล เราจะตั้งค่าสถานะisEntered
เป็นจริง ซึ่งหมายความว่าเราอยู่ในองค์ประกอบนั้น และทันทีที่เราอยู่ภายในองค์ประกอบ เราก็เพียงประมวลผลแต่ละองค์ประกอบใหม่startElement
โดยรู้ว่ามันเป็นองค์ประกอบภายในขององค์ประกอบของเราอย่างแน่นอน ดังนั้นเราจึงแสดงชื่อองค์ประกอบและชื่อองค์ประกอบ หากไม่พบองค์ประกอบในไฟล์ แสดงว่ามีตัวแปรisFound
ที่ตั้งค่าไว้เมื่อพบองค์ประกอบ และหากเป็นเท็จ ข้อความจะปรากฏขึ้นว่าไม่พบองค์ประกอบ และอย่างที่คุณเห็น ในตัวอย่าง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 เร็วๆ นี้ ขอให้โชคดีในการศึกษาของคุณ :) บทความก่อนหน้า: [การแข่งขัน] พื้นฐาน XML สำหรับโปรแกรมเมอร์ Java - ส่วนที่ 2 จาก 3 บทความถัดไป: [การแข่งขัน] พื้นฐาน XML สำหรับโปรแกรมเมอร์ Java - ส่วนที่ 3.2 จาก 3 - DOM
GO TO FULL VERSION