6.8 XMLファイル

(1/1)
XMLファイル
6.5 ファイル・アクセス、同期・非同期、JSON」で使用した社員名簿データのJSONファイルをXMLファイルに変更し、クライアントPCのストレージ(ローカル・ドライブ)から読み込むプログラムを作る。
(2024年2月4日)「コラム:XPath式」を追記

目次

サンプル・プログラムの実行例

社員名簿(XMLファイル)

目次

サンプル・プログラムを実行する

サンプル・プログラム

圧縮ファイルの内容
employeeList5.htmlサンプル・プログラム‥‥社員名簿(XMLファイル)
employeeList6.htmlサンプル・プログラム‥‥社員名簿(XMLファイル+XPath式)
employeeList.xml社員名簿データ(XML)
employeeList5.html 更新履歴
バージョン 更新日 内容
2.1.0 2023/08/15 ループ処理などを見直した
2.0.0 2023/08/15 ファイル名変更
1.0 2021/10/01 初版
employeeList6.html 更新履歴
バージョン 更新日 内容
2.1.0 2023/08/15 ループ処理などを見直した
2.0.0 2023/08/15 ファイル名変更
1.0 2021/10/01 初版

DOMパーサ

JavaScriptにはXMLを扱うために DOMパーサが用意されている(DOMはDocument Object Modelの略)。
DOMツリー
6.5 ファイル・アクセス、同期・非同期、JSON」で使用した社員名簿データのJSONファイルをXMLファイルにしたものが "employeeList.xml" である。
上図のようなツリー構造になっており、これをオブジェクトとしてとらえるための仕組みが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: }

ユーザー関数 loadAndDispTable には、「6.5 ファイル・アクセス、同期・非同期、JSON」で使用したものとほぼ同じである。
読み込み完了後に、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>&nbsp;</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: }

XMLの解釈は、ユーザー関数 getTable4makeColumn の2つで行っている。

まず、ユーザー関数 getTable4 では、引数として渡された DOMオブジェクト(XMLファイルの実体が入っている)に getElementsByTagNameメソッド を使ってノード persons を取り出す。
ノード persons には複数のノード person がぶら下がっているが、これは配列になっていない。そこで、sliceメソッドと callメソッドを使って、配列に変換し、forEachメソッドを適用する。

ユーザー関数 makeColumn も同様で、同じ種類の要素が複数ある場合には Array.prototype.slice.call により配列に変換してから forEachメソッドで処理する。

コラム:他のサーバのファイルは参照しない

CORS問題(1)
CORS問題(1)
7.1 郵便番号→住所検索,Wikipedia検索 - コラム:CORS問題」で詳しく説明するが、JavaScriptはセキュリティ確保の観点から、他のサーバのファイルは参照できないような仕組みになっている。

一方で、今回のXMLファイルのように、ユーザーが読み込みを意図して操作する場合には参照できる。また、Apache のようなhttpサーバが動いており、その中に配置されたJavaScriptが自サーバにアクセスする場合は、XMLHttpRequest オブジェクトや Fetch API を利用することを参照できる。
CORS問題(2)
CORS問題(2)
つまり、httpサーバが立っていない状況で、ブラウザを使って外部ファイルを参照しようとしたら、今回紹介した方法しかない。
また、httpサーバが立っている状況でも、他サーバのファイルを参照する処理は、サーバ側の処理(PHPやPythonなどサーバサイド言語で書く)とするのがいいだろう。

PHPでDOMDocumentを使ってスクレイピング」で紹介しているが、後述する XPath式 を使うと、Webサイトから情報を抽出することをスクレイピング(scraping)を楽に行うことができる。しかし、JavaScriptには CORS問題が付いて回るので、単独でスクレイピングをするのは難しい。

コラム:XPath式

ここからは中級者以上の内容になるので読み飛ばしていただいて構わない。
一般に XMLHTML などの 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: }

XML ファイルを DOM として読み込んだら、evaluate メソッドを実行する。
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" と同じだ。
(この項おわり)
header