مقدمه سلام به همه خوانندگان مقاله من که هنوز آخرین آن نیست، و می خواهم به شما تبریک بگویم: چیزهای پیچیده در مورد XML پشت سر ماست. این مقاله حاوی کدهای جاوا خواهد بود. کمی تئوری و سپس عمل وجود خواهد داشت. با توجه به این واقعیت که یک قطعه از مطالب در SAX 10 صفحه را در Word پر می کرد، متوجه شدم که نمی توانم در محدودیت ها قرار بگیرم. بنابراین، مقاله 3 هر چقدر هم که عجیب به نظر برسد به 3 مقاله جداگانه تقسیم می شود. همه چیز به این ترتیب خواهد بود: SAX -> DOM -> JAXB. این مقاله فقط بر SAX تمرکز خواهد کرد. PS یک کار در جایی در دوره وجود داشت که در آن لازم بود همه عناصر داخلی در یک فایل HTML نمایش داده شوند. بعد از این مقاله بدون مطالعه خط به خط با الگوریتم های پردازش مرسوم و پیچیده قادر به انجام این کار خواهید بود
BufferedReader
و همچنین در آخرین مثال عملی راه حلی مشابه ارائه خواهد شد. بیایید شروع کنیم :) SAX (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();
}
}
همانطور که می بینید، ابتدا باید یک کارخانه ایجاد کنید و سپس خود تجزیه کننده را در کارخانه ایجاد کنید. اکنون که خود تجزیه کننده را داریم، برای رویدادهای آن به یک هندلر نیاز داریم. برای این ما به یک کلاس جداگانه برای راحتی خود نیاز داریم:
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));
}
}
}
}
در متد parse باید مسیر فایل 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;
}
}
}
در این کد هنگام وارد کردن عنصری که در مورد آن به اطلاعات نیاز داریم، پرچم را isEntered
روی true قرار می دهیم که به این معنی است که داخل عنصر هستیم. و به محض اینکه در داخل عنصر قرار گرفتیم، به سادگی هر عنصر جدید را پردازش می کنیم 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>Epilogue</h2>دیدید که SAX یک ابزار کاملا جالب و کاملا موثر است و می توان از آن به روش های مختلف، برای اهداف مختلف و غیره استفاده کرد، فقط باید از سمت راست به مشکل نگاه کنید. سمت، همانطور که در کار شماره 2 و شماره 3 نشان داده شده است، که در آن SAX روش های مستقیمی را برای حل مشکل ارائه نکرد، اما، به لطف نبوغ خود، ما توانستیم راهی برای خروج از وضعیت پیدا کنیم. بخش بعدی مقاله به طور کامل به DOM اختصاص داده خواهد شد. امیدوارم از آشنایی با SAX لذت برده باشید. آزمایش کنید، تمرین کنید و خواهید فهمید که همه چیز بسیار ساده است. و این همه، در برنامه نویسی خود موفق باشید و به زودی منتظر بخش مربوط به DOM باشید. در تحصیل موفق باشید :) مقاله قبلی: [مسابقه] مبانی XML برای برنامه نویس جاوا - قسمت 2 از 3 مقاله بعدی: [مسابقه] مبانی XML برای برنامه نویس جاوا - قسمت 3.2 از 3 - DOM
GO TO FULL VERSION