目次
サンプル・プログラムの実行例
サンプル・プログラムを実行する
サンプル・プログラム
employeeList5.html | サンプル・プログラム‥‥社員名簿(XMLファイル) |
employeeList6.html | サンプル・プログラム‥‥社員名簿(XMLファイル+XPath式) |
employeeList.xml | 社員名簿データ(XML) |
バージョン | 更新日 | 内容 |
---|---|---|
2.1.0 | 2023/08/15 | ループ処理などを見直した |
2.0.0 | 2023/08/15 | ファイル名変更 |
1.0 | 2021/10/01 | 初版 |
バージョン | 更新日 | 内容 |
---|---|---|
2.1.0 | 2023/08/15 | ループ処理などを見直した |
2.0.0 | 2023/08/15 | ファイル名変更 |
1.0 | 2021/10/01 | 初版 |
DOMパーサ
上図のようなツリー構造になっており、これをオブジェクトとしてとらえるための仕組みがDOMである。XMLのタグをノード(結節点)ととらえていると考えてもらえばいい。
最上位のノードdirectoryは、DOMパーサに備わっている parseFromString メソッドの戻り値である。
これ以下のノードは、getElementsByTagName プロパティによって取得していく。ノードが複数あるかの製があるので、このプロパティは配列である。もしノードがない(タグ名がない)ときは、lengthプロパティが0になる。
これを繰り返すことで、どんなに深いXMLのノードでも取得ができる。
解説:ファイル読み込み
158: /**
159: * 社員名簿データを読み込む.
160: * @param Object filelist 社員名簿データ・ファイル(filelistオブジェクト)
161: * @return Object 社員名簿データ
162: */
163: function loadAndDispTable(filelist) {
164: //表示クリア
165: reset();
166:
167: //ファイルが複数ある場合を想定
168: Array.from(filelist.files).forEach(function (file) {
169: //FileReaderオブジェクト生成
170: let reader = new FileReader();
171:
172: //読み込みエラー処理
173: reader.addEventListener('error', () => {
174: document.getElementById('error').innerHTML = 'エラー:社員名簿ファイルの読み込みに失敗しました.';
175: return;
176: });
177:
178: //XMLファイル以外はエラー
179: if (! file.type.match(/xml/i)) {
180: document.getElementById('error').innerHTML = 'エラー:正しい社員名簿を用意してください.';
181: return;
182: }
183:
184: //ファイル読み込み
185: reader.readAsText(file);
186:
187: //読み込み完了後の処理
188: reader.onload = function () {
189: let parser = new DOMParser(); //DOMパーサ
190: let persons = parser.parseFromString(reader.result, 'text/xml');
191: console.log(persons);
192: document.getElementById('tableEmployeeList').innerHTML = getTable4(persons); //一覧表示
193: document.getElementById('save').innerHTML = '社員一覧表保存';
194: }
195: });
196: }
読み込み完了後に、DOMparser オブジェクトを生成し、XMLの解釈ができるようにする。
解説:XMLの解釈
79: /**
80: * 社員名簿をTABLEタグの形で返す.
81: * @param Object persons 社員名簿(DOMオブジェクト)
82: * @return String TABLEタグ
83: */
84: function getTable4(persons) {
85: //社員名簿の「要素:内容」対応表
86: const personsProperties = {
87: number: "社員番号",
88: last_name: "姓",
89: first_name: "名",
90: sex: "性別",
91: hire: "入社日",
92: department: "所属部署",
93: duties: "職掌",
94: sales: "今期売上<br>(千円)"
95: };
96:
97: //見出しを作成する.
98: let html = '<tr>\n';
99: for (const property in personsProperties) {
100: html += '<th>' + personsProperties[property] + '</th>\n';
101: }
102: html += '</tr>\n';
103:
104: //名簿データを一覧にする.
105: let sum = 0;
106: let n = 0;
107: const elements = persons.getElementsByTagName('person');
108: console.log(elements);
109: let div = Array.prototype.slice.call(elements);
110: console.log(div);
111: div.forEach(function(person, i) {
112: html += '<tr>\n';
113: for (const property in personsProperties) {
114: html += makeColumn(person, property);
115: }
116: html += '</tr>\n';
117: //売上合計に加算する.
118: if (person.getElementsByTagName('sales').length > 0) {
119: let element = person.getElementsByTagName('sales');
120: sum += parseFloat(element[0].textContent);
121: n++;
122: }
123: });
124:
125: //売上合計を表示する.
126: html +=`
127: <tr>
128: <td colspan="7">売上合計</td>
129: <td class="num">${sum.toLocaleString()}</td>
130: </tr>
131: <tr>
132: <td colspan="7">売上平均</td>
133: <td class="num">${(sum / n).toLocaleString()}</td>
134: </tr>
135: `;
136: return html;
137: }
46: /**
47: * 1名分の名簿を作成する.
48: * @param Object obj 社員1名分のDOMオブジェクト
49: * @param String key 属性名
50: * @return String 1名分のtdタグ
51: */
52: function makeColumn(obj, key) {
53: let html = '';
54: let element = obj.getElementsByTagName(key);
55:
56: //未定義
57: if (element.length == 0) {
58: html = '<td> </td>\n';
59: //売上
60: } else if (key == 'sales') {
61: html += '<td class="num">' + parseFloat(element[0].textContent).toLocaleString() + '</td>\n';
62: //要素が複数ある
63: } else if (element.length > 1) {
64: let div = Array.prototype.slice.call(element);
65: html += '<td>';
66: div.forEach(function (val, index) {
67: html += index > 0 ? '<br>' : '';
68: html += div[0].textContent.toString();
69: });
70: html += '</td>\n';
71: //一般的な要素
72: } else {
73: html += '<td>' + element[0].textContent.toString() + '</td>\n';
74: }
75:
76: return html;
77: }
まず、ユーザー関数 getTable4 では、引数として渡された DOMオブジェクト(XMLファイルの実体が入っている)に getElementsByTagNameメソッド を使ってノード persons を取り出す。
ノード persons には複数のノード person がぶら下がっているが、これは配列になっていない。そこで、sliceメソッドと callメソッドを使って、配列に変換し、forEachメソッドを適用する。
ユーザー関数 makeColumn も同様で、同じ種類の要素が複数ある場合には Array.prototype.slice.call により配列に変換してから forEachメソッドで処理する。
コラム:他のサーバのファイルは参照しない
一方で、今回のXMLファイルのように、ユーザーが読み込みを意図して操作する場合には参照できる。また、Apache のようなhttpサーバが動いており、その中に配置されたJavaScriptが自サーバにアクセスする場合は、XMLHttpRequest オブジェクトや Fetch API を利用することを参照できる。
また、httpサーバが立っている状況でも、他サーバのファイルを参照する処理は、サーバ側の処理(PHPやPythonなどサーバサイド言語で書く)とするのがいいだろう。
「PHPでDOMDocumentを使ってスクレイピング」で紹介しているが、後述する XPath式 を使うと、Webサイトから情報を抽出することをスクレイピング(scraping)を楽に行うことができる。しかし、JavaScriptには CORS問題が付いて回るので、単独でスクレイピングをするのは難しい。
コラム:XPath式
一般に XML や HTML などの DOM を評価する方法として XPath式というものがあり、JavaScriptにも実装されている。
ここでは、XPath式を使って上述の XML を解釈するサンプル・プログラムを紹介する。
80: /**
81: * 社員名簿をTABLEタグの形で返す.
82: * @param Object persons 社員名簿(DOMオブジェクト)
83: * @return String TABLEタグ
84: */
85: function getTable6(persons) {
86: //社員名簿の「要素:内容」対応表
87: const personsProperties = {
88: number: "社員番号",
89: last_name: "姓",
90: first_name: "名",
91: sex: "性別",
92: hire: "入社日",
93: department: "所属部署",
94: duties: "職掌",
95: sales: "今期売上<br>(千円)"
96: };
97:
98: //見出しを作成する.
99: let html = '<tr>\n';
100: for (const property in personsProperties) {
101: html += '<th>' + personsProperties[property] + '</th>\n';
102: }
103: html += '</tr>\n';
104:
105: //名簿データを一覧にする(XPath式を利用)
106: let sum = 0;
107: let n = 0;
108: const xpath = '/persons/person';
109: const iterator = persons.evaluate(xpath, persons, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
110: while ((person = iterator.iterateNext())) {
111: console.log(person);
112: html += '<tr>\n';
113: for (const property in personsProperties) {
114: html += makeColumn(person, property);
115: }
116: html += '</tr>\n';
117: //売上合計に加算する.
118: if (person.getElementsByTagName('sales').length > 0) {
119: let element = person.getElementsByTagName('sales');
120: sum += parseFloat(element[0].textContent);
121: n++;
122: }
123: }
124:
125: //売上合計を表示する.
126: html +=`
127: <tr>
128: <td colspan="7">売上合計</td>
129: <td class="num">${sum.toLocaleString()}</td>
130: </tr>
131: <tr>
132: <td colspan="7">売上平均</td>
133: <td class="num">${(sum / n).toLocaleString()}</td>
134: </tr>
135: `;
136: return html;
137: }
evaluate メソッドは、第1引数に XPath式 を、第2引数に DOM のノードを、第3引数に名前空間の接頭辞を、第4引数に戻り値のタイプを整数で、第5引数に戻り値となる XPathResultオブジェクトを指定する。第3引数以降は省略可能で、特定の引数を省略したい場合には null を指定する。
サンプル・プログラムでは、第3引数、第5引数を省略し、第4引数は XPathResult.ORDERED_NODE_ITERATOR_TYPE を指定し、文書順にソートしたイテレータで返す。
XPath式 "/persons/person]" は、ルートから <persons> をたどり、その子ノードの <person> を選ぶという意味である。子ノードは複数あるから、イテレータで返すようにしてある。
イテレータ person に対する処理は、前述のスクリプト "employeeList5.html" と同じだ。