はじめ に まだ最後ではない私の記事を読んでいる皆さん、こんにちは。XML に関する複雑な問題はもう終わりました。おめでとうございます。この記事には Java のコードが含まれます。少し理論を説明してから、実践してみましょう。 SAX では 1 つの資料が Word で 10 ページに及ぶため、制限内に収まらないことがわかりました。したがって、どんなに奇妙に聞こえるかもしれませんが、第3条は3つの記事に分けられます。すべてはこの順序になります: SAX -> DOM -> JAXB。この記事では SAX のみに焦点を当てます。PS コースのどこかに、HTML ファイル内のすべての内部要素を表示する必要があるタスクがありました。この記事を読み終えると、従来の複雑な処理アルゴリズムを使用して、一行ずつ読まなくてもこれを実行できるようになります
BufferedReader
。また、同様の解決策が最後の実践例で提供されます。始めましょう :) SAX (Simple API for 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
にテキスト (たとえば「hello」) が含まれている場合、理論的には、個々の文字ごとにメソッドを 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
。要素内のテキストを読み取るには、characters メソッドがあります。これを行うには、ロジックを改善した新しいハンドラー クラスを作成する必要があります。ハンドラーは、あらゆる複雑なロジックを格納できる本格的なクラスであることを忘れないでください。したがって、ここでプロセッサを調整します。実際、常に交代name
で行うことに注意するだけで十分job
です。順序は関係ありません。名前と職業を別の変数に簡単に保存でき、両方の変数が保存されたときに従業員を作成できます。ここだけ、要素の先頭と同様に、要素内のテキストのパラメータがありません。テキストに対してメソッドを使用する必要があります。しかし、これらがまったく異なるメソッドである場合、要素内のテキスト情報をどうやって取得するのでしょうか? 私の解決策: 最後の要素の名前を覚えて、characters
どの要素で情報を読み取っているかを確認するだけです。また、<codee>characters は要素内のすべての文字を読み取ること、つまりすべてのスペースや改行も読み取られることを覚えておく必要があります。そして私たちにはそれらは必要ありません。このデータは正しくないため、無視する必要があります。</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 の問題を部分的に解決するもう 1 つの素晴らしいタスクです。少し編集する必要があるだけです。ここでは、単純に要素内のすべての要素をリストします :) タスク No. 3 - element 要素を指定すると、すべての内部要素の名前と属性が表示されます。要素が見つからない場合は、これが表示されます。 このタスクでは、次の 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>
ご覧のとおり、ここでは 3 つのシナリオが考えられます: 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
要素が見つかったときに設定される変数があり、それが 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 は非常に興味深いツールであり、非常に効果的であり、さまざまな方法や目的などに使用できることがわかりました。問題を右から見るだけで十分です。タスク No. 2 と No. 3 に示すように、SAX は問題を解決するための直接的な方法を提供しませんでしたが、私たちの創意工夫のおかげで、状況を打開する方法を思いつくことができました。記事の次の部分では、完全に DOM について説明します。SAX を知って楽しんでいただければ幸いです。実験して練習すれば、すべてが非常に単純であることが理解できるでしょう。以上です。プログラミングの頑張ってください。DOM に関する部分は近々公開されるので楽しみにしています。勉強頑張ってください:) 前の記事: [コンテスト] Java プログラマーのための XML の基礎 - パート 2/3 次の記事: [コンテスト] Java プログラマーのための XML の基礎 - パート 3.2/3 - DOM
GO TO FULL VERSION