Giriş Hələ sonuncu olmayan məqaləmin bütün oxucularını salamlayıram və sizi təbrik etmək istəyirəm: XML ilə bağlı mürəkkəb işlər arxada qalıb. Bu məqalədə Java kodu olacaq. Bir az nəzəriyyə, sonra təcrübə olacaq. SAX-dakı bir materialın Word-də 10 səhifəni doldurduğuna görə anladım ki, limitlərə sığışa bilmirəm. Ona görə də 3-cü maddə nə qədər qəribə səslənsə də, 3 ayrı maddəyə bölünəcək. Hər şey bu qaydada olacaq: SAX -> DOM -> JAXB. Bu məqalə yalnız SAX-a diqqət yetirəcəkdir. PS Kursun bir yerində HTML faylında bütün daxili elementləri göstərmək lazım olan bir tapşırıq var idi. Bu məqalədən sonra siz adi və mürəkkəb emal alqoritmləri ilə sətir-sətir oxumadan bunu edə biləcəksiniz
BufferedReader
və eyni zamanda oxşar həll sonuncu praktik nümunədə veriləcəkdir. Başlayaq :) SAX (XML üçün sadə API) - NƏZƏRİYYƏ SAX işləyicisi elə qurulmuşdur ki, o, sadəcə olaraq XML fayllarını ardıcıl oxuyur və müxtəlif hadisələrə reaksiya verir, bundan sonra o, məlumatı xüsusi hadisə işləyicisinə ötürür. Onun bir neçə hadisəsi var, lakin ən tez-tez və faydalı olanlar aşağıdakılardır:
startDocument
— sənədin başlanğıcıendDocument
- sənədin sonustartElement
- bir elementin açılmasıendElement
- bir elementin bağlanmasıcharacters
— elementlərin daxilində mətn məlumatı.
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;
İndi əvvəlcə SAXParser yaratmalıyıq:
public class SAXExample {
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
// Creation фабрики и образца parserа
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
}
}
Gördüyünüz kimi əvvəlcə fabrik yaratmalı, sonra fabrikdə parserin özünü yaratmalısınız. İndi parserin özünə sahib olduğumuz üçün onun hadisələri üçün bir işləyici lazımdır. Bunun üçün öz rahatlığımız üçün ayrıca sinifə ehtiyacımız var:
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 {
// Тут будет логика реакции на пустое пространство внутри элементов (пробелы, переносы строчек и так далее).
}
}
}
Nəzəriyyədə sadalanan hadisələri idarə etmək üçün lazım olan bütün üsullarla bir sinif yaratdıq. Bir az daha əlavə nəzəriyyə: Haqqında bir az characters
: elementdə mətn varsa, məsələn, “ salam ”, onda nəzəri olaraq metodu hər bir fərdi simvol üçün ardıcıl 5 dəfə çağırmaq olar, lakin bu o qədər də böyük bir şey deyil, çünki hər şey hələ də işləyəcək. və üsulları haqqında :startElement
endElement
uri
- bu elementin yerləşdiyi məkandır, localName
- bu, prefikssiz elementin adıdır, qName
- bu, prefiksli elementin adıdır (əgər varsa, əks halda sadəcə adı elementdən). uri
və localName
fabrikdə yer emalını aktiv etməmişiksə, həmişə boş olur. Bu, zavod üsulu ilə həyata keçirilir setNamespaceAware(true)
. uri
Sonra boşluq ( ) və qarşısında prefiks olan elementləri ( ) ala bilərik localName
. Tapşırıq №1 - Aşağıdakı XML-imiz var
<?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>
Məqsədimiz: bu fayldan bütün işçilər haqqında bütün məlumatları əldə etmək. Əvvəlcə bir sinif yaratmalıyıqEmployee
:
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;
}
}
Və əsas sinifimizdə SAXExample
bütün işçilərin olduğu bir siyahı lazımdır:
private static ArrayList<Employee> employees = new ArrayList<>();
İndi bizə lazım olan məlumatın XML faylının harada olduğuna diqqətlə baxaq. Və gördüyümüz kimi, bizə lazım olan bütün məlumatlar elementlərin atributlarıdır employee
. Və startElement
belə bir faydalı parametrimiz olduğundan attributes
, bizim kifayət qədər sadə bir vəzifəmiz var. Əvvəlcə kodumuzu qarışdırmamaq üçün lazımsız metodları silək. Bizə ancaq lazımdır startElement
. Metodun özündə isə işçi etiketinin atributlarından məlumat toplamaq lazımdır. Diqqət:
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));
}
}
}
}
Məntiq sadədir: əgər elementin adı olarsa employee
, biz sadəcə onun atributları haqqında məlumat alacağıq. attributes
Bir atributun adını bilməklə onun dəyərini əldə edə biləcəyiniz faydalı bir üsul var . İstifadə etdiyimiz budur. İndi elementin başlanğıcı üçün hadisə idarəedicisi yaratdığımız üçün XML faylımızı təhlil etməliyik . Bunu etmək üçün sadəcə bunu edin:
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));
}
}
}
}
Parse metodunda xml faylına və yaratdığınız işləyiciyə yolu keçməlisiniz. Beləliklə, bu kodu istifadə edərək, bu XML-dən məlumat çıxardıq:
<?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>
Və aşağıdakı çıxışı əldə etdik:
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
Missiya yerinə yetirildi! Tapşırıq №2 - bizdə aşağıdakı XML var:
<?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>
Məqsədimiz: bu fayldan bütün işçilər haqqında bütün məlumatları əldə etmək. Bu problem zəif strukturlaşdırılmış XML faylının kodun yazılmasını necə çətinləşdirə biləcəyini yaxşı nümayiş etdirəcək. Gördüyünüz kimi, ad və mövqe haqqında məlumat indi name
və elementləri daxilində mətn məlumatı kimi saxlanılır job
. Elementlərin içindəki mətni oxumaq üçün simvol metodumuz var. Bunun üçün biz təkmilləşdirilmiş məntiqə malik yeni işləyici sinfi yaratmalıyıq. Unutmayın ki, işləyicilər istənilən mürəkkəbliyin məntiqini saxlaya bilən tam hüquqlu siniflərdir. Buna görə də, indi prosessorumuzu sazlayacağıq. Əslində, qeyd etmək kifayətdir ki, biz həmişə növbə name
çəkirik job
və hansı ardıcıllıqla fərq etməz, biz asanlıqla adı və peşəni ayrı-ayrı dəyişənlərə saxlaya bilərik və hər iki dəyişən yadda qalanda işçimizi yarada bilərik. Yalnız burada elementin başlanğıcı ilə yanaşı, elementin daxilində mətn üçün parametrimiz yoxdur. Mətn üzərində metodlardan istifadə etməliyik. Bəs bunlar tamamilə fərqli üsullardırsa, element daxilində mətn məlumatını necə əldə edə bilərik? Mənim həllim: sadəcə sonuncu elementin adını yadda saxlamalıyıq və characters
məlumatı hansı elementdə oxuduğumuzu yoxlamalıyıq. Siz həmçinin yadda saxlamalısınız ki, <codee>simvollar elementlər daxilindəki bütün simvolları oxuyur, yəni bütün boşluqlar və hətta sətir fasilələri oxunacaq. Və onlara ehtiyacımız yoxdur. Biz bu dataya məhəl qoymamalıyıq, çünki o, yanlışdır.</codee> Kod:
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;
}
}
}
}
Gördüyünüz kimi, XML fayl strukturunun banal mürəkkəbliyi səbəbindən kodumuz xeyli mürəkkəbləşdi. Bununla belə, kod mürəkkəb deyil. Təsvir: biz işçi ( name
, job
) haqqında məlumatları saxlamaq üçün dəyişənlər yaratdıq , həmçinin lastElementName
hansı elementin içində olduğumuzu qeyd etmək üçün dəyişən yaratdıq. Bundan sonra, metodda characters
məlumatları süzgəcdən keçiririk və əgər orada hələ də məlumat varsa, deməli bu, bizə lazım olan mətndir və sonra istifadə edərək onun ad və ya peşə olub olmadığını müəyyənləşdiririk lastElementName
. Metodda endElement
biz bütün məlumatların oxunub-oxunmadığını yoxlayırıq və əgər belədirsə, işçi yaradırıq və məlumatları sıfırlayırıq. Həllin nəticəsi birinci nümunəyə bərabərdir:
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
Beləliklə, bu problem həll edildi , lakin mürəkkəbliyin daha yüksək olduğunu görə bilərsiniz. Beləliklə, belə bir nəticəyə gələ bilərik ki, mətn məlumatının atributlarda saxlanması fərdi elementlərdən daha çox düzgün olacaqdır. Və JavaRush-da HTML-də element haqqında məlumatın göstərilməsi ilə bağlı problemi qismən həll edəcək daha bir şirin tapşırıq, yalnız onu bir az redaktə etmək lazımdır, burada sadəcə elementin içindəki bütün elementləri sadalayacağıq :) Tapşırıq №3 - element elementi verildikdə, bütün daxili elementlərin adlarını və atributlarını göstərin; element tapılmadıqda, bunu göstərin. Bu tapşırıq üçün aşağıdakı XML faylından istifadə edəcəyik:
<?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>
Gördüyünüz kimi, burada üç mümkün ssenarimiz var: root
, mysql
, oracle
. Sonra proqram daxilində olan bütün elementlər haqqında bütün məlumatları göstərəcək. Bunu necə edə bilərik? Və bu olduqca sadədir: sadəcə olaraq məntiqi dəyişən elan etməliyik isEntered
ki, bu elementin içəridə bizə lazım olub-olmadığını göstərəcək və içəridə varsa, bütün məlumatları oxuyun startElement
. Həll kodu:
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;
}
}
}
Bu kodda bizə məlumat lazım olan elementi daxil edərkən bayrağı isEntered
doğru olaraq təyin edirik, yəni biz elementin içindəyik. Və elementin içərisinə girən kimi biz sadəcə olaraq hər bir yeni elementi emal edirik startElement
və bunun bizim elementimizin daxili elementi olduğunu bilirik. Beləliklə, biz elementin adını və başlığını çıxarırıq. Əgər element faylda tapılmayıbsa, o zaman bizdə isFound
element tapıldıqda təyin olunan dəyişən var və o, yanlışdırsa, elementin tapılmaması barədə mesaj göstərilir. Və gördüyünüz kimi, nümunədə konstruktora bir element SearchingXMLHandler
ötürdük . root
Onun üçün nəticə:
Найден элемент <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
Beləliklə, biz daxili elementlər və onların atributları haqqında bütün məlumatları aldıq. Problem həll olunur. <h2>Epiloq</h2>Gördünüz ki, SAX kifayət qədər maraqlı alətdir və kifayət qədər effektivdir və ondan müxtəlif yollarla, müxtəlif məqsədlər üçün istifadə edilə bilər və s., sadəcə olaraq problemə sağ tərəfdən baxmaq lazımdır. 2 və 3 nömrəli tapşırıqda göstərildiyi kimi, burada SAX problemin həlli üçün birbaşa üsullar təqdim etmədi, lakin ixtiraçılığımız sayəsində vəziyyətdən çıxış yolu tapa bildik. Məqalənin növbəti hissəsi tamamilə DOM-a həsr olunacaq. Ümid edirəm ki, SAX ilə tanış olmaqdan zövq aldınız. Təcrübə edin, məşq edin və hər şeyin olduqca sadə olduğunu başa düşəcəksiniz. Və hamısı budur, proqramlaşdırmanızda uğurlar və tezliklə DOM haqqında hissəni gözləyirik. Təhsilinizdə uğurlar :) Əvvəlki məqalə: [Müsabiqə] Java Proqramçısı üçün XML Əsasları - 3-cü hissədən 2-si Növbəti məqalə: [Müsabiqə] Java Proqramçısı üçün XML Əsasları - 3-cü hissədən 3.2 - DOM
GO TO FULL VERSION