Source: xsd.js

/*

  The MIT License (MIT)

  Copyright (c) 2018 Koji Toyota

  Permission is hereby granted, free of charge, to any person
  obtaining a copy of this software and associated documentation files
  (the "Software"), to deal in the Software without restriction,
  including without limitation the rights to use, copy, modify, merge,
  publish, distribute, sublicense, and/or sell copies of the Software,
  and to permit persons to whom the Software is furnished to do so,
  subject to the following conditions:

  The above copyright notice and this permission notice shall be
  included in all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  SOFTWARE.
*/

"use strict";

const IMILib2017Common = require('./imi-lib-2017-common');
const beautifier = require('./beautifier');

/**
 * Constructor
 * @param {string} imivText 語彙定義テキスト
 * @param {boolean} bc 後方互換の要素を読み込むかどうか
 * @param {vocabulary} externalVocabulary - IMI 語彙記法から参照される語彙を定義する Vocabulary Extension のインスタンス(optional)
 */
function Xsd (ivd, bc = false, externalVocabulary = null) {
    //語彙定義テキストのチェック
    this.vocabularyObject;
    if (externalVocabulary !== null) {
        try {
            this.vocabularyObject = new IMILib2017Common.Vocabulary(ivd, externalVocabulary.vocabularyObject);
        } catch (e) {
            console.log(e);
            throw e;
        }
    } else {
        try {
            this.vocabularyObject = new IMILib2017Common.Vocabulary(ivd);
        } catch (e) {
            console.log(e);
            throw e;
        }
    }
    if (this.vocabularyObject.error.length > 0) {
        console.log('語彙定義テキストに不整合がありました。');
        throw this.vocabularyObject.error;
    }

    this.ivd = ivd;

    var vocabulary = this.vocabulary = {
        "targetNamespace": "",
        "metadata": {},
        "prefix": [],
        "classes": [],
        "properties": [],
        "info": []
    };

    this.bc = bc;

    //以下で語彙を調整する
    var obj = new IMILib2017Common.parse_v(ivd);

    //クラス順序保存用
    var classQue = [];
    //クラスのセット文の生成用一時変数
    var classTemp = {};
    //セット文処理中にクラスに登録できなかった漏れたセット文のArray
    var leakSets = [];
    //プロパティの中の名前空間のリスト
    var prefixList = [];

    //語彙定義JSONを順に読んで処理
    for(var i = 0; i < obj.length; i++) {
        switch (obj[i].type) {
            //クラスの場合
            case 'class':
                //クラス一時保存変数にクラスを保存
                classTemp[obj[i].prefix + ':' + obj[i].name] = obj[i];
                //一時保存変数にSETを保存するArrayを作成
                classTemp[obj[i].prefix + ':' + obj[i].name]['sets'] = [];
                //クラスの順序を保存するQueに保存
                classQue.push(obj[i].prefix + ':' + obj[i].name);

                //クラスに使用されるネームスペースのチェックのための処理
                var classType = findRestriction(obj[i]['restriction'], 'type');
                if(classType.length === 0) {
                    //非推奨クラスは、後方互換プロパティがtrueの場合以外は処理しない
                    if(typeof(obj[i]['deprecated']) === 'undefined' || bc) {
                        if (obj[i]['prefix'] + ':' + obj[i]['name'] !== 'ic:概念型') {
                            //同じものは保存しない
                            if (!prefixList.includes(classType['prefix'])) {
                                //クラス中の名前空間リストに追加
                                prefixList.push(classType['prefix']);
                            }
                        }
                    }
                }
                break;
            //セット文の場合
            case 'set':
                //クラスにセット文を保存する
                try{
                    classTemp[obj[i].class.prefix + ':' + obj[i].class.name].sets.push(obj[i]);
                } catch (e) {
                    //クラスがセット文にない場合、あとで処理する配列に保存する。(IMIV語彙記法上の語彙を継承せずに拡張した場合に対処)
                    leakSets.push(obj[i]);
                    console.log(e);
                }

                break;
            //プロパティの場合
            case 'property':
                //プロパティに保存する
                vocabulary.properties.push(obj[i]);

                //プロパティに使用されるネームスペースのチェックのための処理
                var propertyType = findRestriction(obj[i]['restriction'], 'type');
                var propertyPrefix = obj[i]['prefix'];
                //非推奨プロパティは、後方互換プロパティがtrueの場合以外は処理しない
                if(typeof(obj[i]['deprecated']) === 'undefined' || bc) {
                    //同じものは保存しない
                    if (!prefixList.includes(propertyType['prefix'])) {
                        //プロパティ中の名前空間リストに追加
                        prefixList.push(propertyType['prefix']);
                    }
                    if (!prefixList.includes(propertyPrefix)) {
                        //プロパティ中の名前空間リストに追加
                        prefixList.push(propertyPrefix);
                    }
                }

                break;
            //語彙の場合
            case 'vocabulary':
                //targetNamespaceを保存する
                vocabulary.targetNamespace = obj[i].data;
                //メタデータを保存する
                vocabulary.metadata = obj[i].metadata;
                break;
        }
    }

    //クラス順序保存用配列にしたがって、クラスを調整済み語彙のオブジェクトに書き込む
    for(var i = 0; i < classQue.length; i++) {
        vocabulary.classes.push(classTemp[classQue[i]]);
    }

    //使用されている名前空間のprefix一覧を読み込み、語彙のメタデータの名前空間参照情報に存在していることを確認し、クラスを調整済み語彙のオブジェクトに書き込む
    for (var i = 0; i < prefixList.length; i++) {
        //xsdはスキップ
        if(prefixList[i] === 'xsd') continue;

        var prefix = findPrefix(vocabulary.metadata, prefixList[i]);
        if (!prefix) {
            alert('語彙定義の中で使用されている名前空間が解決できませんでした。');
            throw '語彙定義の中で使用されている名前空間が解決できませんでした。'
        }
        vocabulary.prefix.push(prefix);
    }

    // 外部語彙を継承した語彙を読み込む
    if (externalVocabulary === null || typeof(externalVocabulary.baseVocabulary) === 'undefined') {
        //読み込んだ名前空間の語彙の全て
        this.baseVocabulary = {
            "prefix": [],
            "classes": [],
            "properties": []
        }
    } else {
        //読み込んだ名前空間の語彙の全て
        this.baseVocabulary = externalVocabulary.baseVocabulary;
    }

    //読み込んだ名前空間の語彙の全てへ、語彙定義テキストから読み込んだ語彙を追加する。
    ["classes", "properties", "prefix"].forEach(a => {
        this.vocabulary[a].forEach(b => {
            this.baseVocabulary[a].push(b);
        });
    });

    //セット文処理中にクラスに登録できなかった要素の登録(IMIV語彙記法上の語彙を継承せずに拡張した場合に対処)
    for(var i = 0; i < leakSets.length; i++) {
        var extClass = findClass(this.baseVocabulary.classes, leakSets[i]['class']['prefix'] + ':' + leakSets[i]['class']['name']);
        extClass.sets.push(leakSets[i]);
        this.vocabulary.classes.push(extClass);
    }
}

/**
 * XMLスキーマを出力する
 */
Xsd.prototype.toXsd = function () {
    return generateImiXsd(this.vocabulary, this.bc);
};

/**
 * XMLスキーマを生成する
 * @param {Object} vocabulary スキーマ生成用に調整した語彙のオブジェクト
 * @param {boolean} bc 後方互換の要素を出力するかどうか
 * @return {string} 語彙のXMLスキーマ(整形済みテキスト)
 */
function generateImiXsd(vocabulary, bc) {
    var domParser = new DOMParser();
    var dom = domParser.parseFromString('<?xml version="1.0" encoding="utf-8"?>', 'text/xml');

    //xsd:schema BASE
    var xsdSchema = dom.createElement('xsd:schema');
    xsdSchema.setAttribute('xmlns:foaf', 'http://xmlns.com/foaf/0.1/');
    xsdSchema.setAttribute('xmlns:dct', 'http://purl.org/dc/terms/');
    xsdSchema.setAttribute('xmlns:xsd', 'http://www.w3.org/2001/XMLSchema');
    xsdSchema.setAttribute('targetNamespace', vocabulary.targetNamespace.replace('#', ''));
    xsdSchema.setAttribute('version', findMetadata(vocabulary.metadata, 'version'));
    for (var i = 0; i < vocabulary.prefix.length; i++) {
        if (vocabulary.prefix[i] !== 'xsd') {
            xsdSchema.setAttribute('xmlns:' + vocabulary.prefix[i]['prefix'] , vocabulary.prefix[i]['data'].replace('#', ''));
        }
    }
    //xsd:schema end

    //xsd;annotation
    var annotationElement = dom.createElement('xsd:annotation');

    //child
    annotationElement.appendChild(createNode('xsd:documentation', [{'name': 'xml:lang', 'value': 'ja'}], findMetadata(vocabulary.metadata, 'description', 'ja')));
    annotationElement.appendChild(createNode('xsd:documentation', [{'name': 'xml:lang', 'value': 'en'}], findMetadata(vocabulary.metadata, 'description', 'en')));

    //xsd:appinfo
    var xsdAppinfo = dom.createElement('xsd:appinfo');
    xsdAppinfo.appendChild(createNode('dct:title', [{'name': 'xml:lang', 'value': 'ja'}], findMetadata(vocabulary.metadata, 'name', 'ja')));
    xsdAppinfo.appendChild(createNode('dct:title', [{'name': 'xml:lang', 'value': 'en'}], findMetadata(vocabulary.metadata, 'name', 'en')));
    xsdAppinfo.appendChild(createNode('dct:license', [], findMetadata(vocabulary.metadata, 'license_ref')));
    xsdAppinfo.appendChild(createNode('dct:issued', [], findMetadata(vocabulary.metadata, 'published_date')));
    //xsd:appinfo end

    var groups = getCreatorGroups(vocabulary.metadata);
    
    for (var i = 0; i < groups.length; i++) {
        var dctCreator = createNode('dct:creator');
        dctCreator.appendChild(createNode('foaf:name', [{'name': 'xml:lang', 'value': 'ja'}], findMetadata(groups[i], 'creator', 'ja')));
        dctCreator.appendChild(createNode('foaf:name', [{'name': 'xml:lang', 'value': 'en'}], findMetadata(groups[i], 'creator', 'en')));
        dctCreator.appendChild(createNode('foaf:homepage', [], findMetadata(groups[i], 'creator_ref')));
        xsdAppinfo.appendChild(dctCreator);
    }
    annotationElement.appendChild(xsdAppinfo);

    xsdSchema.appendChild(annotationElement);
    //xsd:annotation end

    if(bc) {
        var bcAnnotationElement = dom.createElement('xsd:annotation');
        bcAnnotationElement.appendChild(createNode('xsd:documentation', [{'name': 'xml:lang','value': 'ja'}], 'このXMLスキーマ(Core241bc.xsd)には、将来的に廃止されることが決定している用語の定義も含まれています。廃止予定の用語を使わない場合は、それらの定義を含まないXMLスキーマとして別途提供されている Core241.xsd をお使いください。'));
        xsdSchema.appendChild(bcAnnotationElement);
    }

    //xsd:import
    //<xsd:import namespace="<%= prefix[i]['data'].replace('#', '') %>" schemaLocation="schemaLocation"/>
    for (var i = 0; i < vocabulary.prefix.length; i++) {
        if (vocabulary.prefix[i]['prefix'] !== 'xsd' && vocabulary.prefix[i]['prefix'] !== 'ic') {
            xsdSchema.appendChild(createNode('xsd:import',[{'name': 'namespace', 'value': vocabulary.prefix[i]['data'].replace('#', '')}, {'name': 'schemaLocation', 'value': 'schemaLocation'}]));
        }
    }
    //xsd:import end

    //classes
    for (var i = 0; i < vocabulary.classes.length; i++) {
        if (typeof(vocabulary.classes[i]['deprecated']) === 'undefined' || bc) {
            xsdSchema.appendChild(createClassComplexTypeNode(vocabulary.classes[i]));
            if (vocabulary.classes[i]['name'] !== '電話番号型') {
                if (vocabulary.classes[i]['sets'].length !== 0) {
                    xsdSchema.appendChild(createClassGroupNode(vocabulary.classes[i], bc));
                }
            }
        }
    }
    //classes end

    //properties
    for (var i = 0; i < vocabulary.properties.length; i++) {
        if (typeof(vocabulary.properties[i]['deprecated']) === 'undefined' || bc) {
            xsdSchema.appendChild(createPropertyNode(vocabulary.properties[i]));
            if(findRestriction(vocabulary.properties[i]['restriction'], 'type')['prefix']  === 'xsd' && vocabulary.properties[i]['name'] !== '識別値') {
                var xsdSimpleType = createNode('xsd:simpleType', [{'name': 'name', 'value': vocabulary.properties[i]['name'] + '-単純型'}]);
                xsdSimpleType.appendChild(createNode('xsd:restriction', [{'name': 'base', 'value': findRestriction(vocabulary.properties[i]['restriction'], 'type')['prefix'] + ':' + findRestriction(vocabulary.properties[i]['restriction'], 'type')['name']}]));
                //xsdSimpleType.appendChild(xsdRestriction);
                xsdSchema.appendChild(xsdSimpleType);
            }
        }
    }
    //properties end

    //classes
    for (var i = 0; i < vocabulary.classes.length; i++) {
        if (!checkPropertyName(vocabulary, vocabulary.classes[i]['name'])) {
            if (typeof(vocabulary.classes[i]['deprecated']) === 'undefined' || bc) {
                xsdSchema.appendChild(createClassElement(vocabulary.classes[i]));
            }
        }
    }
    //classes end

    dom.appendChild(xsdSchema);
    
    var xmlStr = new XMLSerializer().serializeToString(dom);

    //XMLの整形
    var beautifierHtml = beautifier.html;
    xmlStr = beautifierHtml(xmlStr);

    return xmlStr;
}

/**
 * Classのxsd:complexTypeを生成する
 * @param {Object} clazz クラスのオブジェクト
 * @return {string} Classのxsd:complexTypeのテキスト
 */
/*
sample:
<xsd:complexType name="人型">
    <xsd:annotation>
        <xsd:documentation xml:lang="ja">人の情報を表現するためのクラス用語</xsd:documentation>
        <xsd:documentation xml:lang="en">A class term to express information of a person.</xsd:documentation>
    </xsd:annotation>
    <xsd:complexContent>
        <xsd:extension base="ic:実体型">
            <xsd:sequence>
                <xsd:group ref="ic:人型-固有要素グループ" />
            </xsd:sequence>
        </xsd:extension>
    </xsd:complexContent>
</xsd:complexType>
*/
function createClassComplexTypeNode(clazz) {
    var xsdComplexType = createNode('xsd:complexType', [{'name': 'name', 'value': clazz['name']}]);
    var xsdAnnotation = createNode('xsd:annotation');
    xsdAnnotation.appendChild(createNode('xsd:documentation', [{'name': 'xml:lang', 'value': 'ja'}], findMetadata(clazz['metadata'], 'description', 'ja')));
    xsdAnnotation.appendChild(createNode('xsd:documentation', [{'name': 'xml:lang', 'value': 'en'}], findMetadata(clazz['metadata'], 'description', 'en')));
    xsdComplexType.appendChild(xsdAnnotation);

    if (findRestriction(clazz['restriction'], 'type')['prefix'] === 'xsd') {
        var xsdSimpleContent = createNode('xsd:simpleContent');
        var xsdExtension = createNode('xsd:extension', [{'name': 'base', 'value': 'xsd:string'}]);
        xsdExtension.appendChild(createNode('xsd:attribute', [{'name': 'name', 'value': 'id'}, {'name': 'type', 'value': 'xsd:ID'}]));
        xsdExtension.appendChild(createNode('xsd:attribute', [{'name': 'name', 'value': 'ref'}, {'name': 'type', 'value': 'xsd:IDREF'}]));
        xsdSimpleContent.appendChild(xsdExtension);
        xsdComplexType.appendChild(xsdSimpleContent);
    }
    else if (typeof(findRestriction(clazz['restriction'], 'type')['prefix']) === 'undefined') {//概念型
        var xsdSequence = createNode('xsd:sequence', []);
        xsdSequence.appendChild(createNode('xsd:group', [{'name': 'ref', 'value': clazz['prefix'] + ':' + clazz['name'] + '-固有要素グループ'}]));
        xsdComplexType.appendChild(xsdSequence);
        xsdComplexType.appendChild(createNode('xsd:attribute', [{'name': 'name', 'value': 'id'}, {'name': 'type', 'value': 'xsd:ID'}]));
        xsdComplexType.appendChild(createNode('xsd:attribute', [{'name': 'name', 'value': 'ref'}, {'name': 'type', 'value': 'xsd:IDREF'}]));
    }
    else {
        var xsdComplexContent = createNode('xsd:complexContent');
        var xsdExtension = createNode('xsd:extension', [{'name': 'base', 'value': findRestriction(clazz['restriction'], 'type')['prefix'] + ':' + findRestriction(clazz['restriction'], 'type')['name']}]);
        if (clazz['sets'].length !== 0) {
            var xsdSequence = createNode('xsd:sequence');
            xsdSequence.appendChild(createNode('xsd:group', [{'name': 'ref', 'value': clazz['prefix'] + ':' + clazz['name'] + '-固有要素グループ'}]));
            xsdExtension.appendChild(xsdSequence);
        } else {
            xsdExtension.textContent = '';
        }
        xsdComplexContent.appendChild(xsdExtension);
        xsdComplexType.appendChild(xsdComplexContent);
    }

    return xsdComplexType;
}

/**
 * Classのxsd:groupを生成する
 * @param {Object} clazz クラスのオブジェクト
 * @param {boolean} bc 後方互換で作成する
 * @return {string} Classのxsd:groupのテキスト
 */
/*
sample:
<xsd:group name="人型-固有要素グループ">
    <xsd:sequence>
        <xsd:element ref="ic:氏名" minOccurs="0" maxOccurs="unbounded" />
        <xsd:element ref="ic:性別" minOccurs="0" maxOccurs="1" />
        <xsd:element ref="ic:性別コード" minOccurs="0" maxOccurs="1" />
        <xsd:element ref="ic:生年月日" minOccurs="0" maxOccurs="1" />
        <xsd:element ref="ic:死亡年月日" minOccurs="0" maxOccurs="1" />
        <xsd:element ref="ic:住所" minOccurs="0" maxOccurs="unbounded" />
        <xsd:element ref="ic:本籍" minOccurs="0" maxOccurs="1" />
        <xsd:element ref="ic:連絡先" minOccurs="0" maxOccurs="unbounded" />
        <xsd:element ref="ic:国籍" minOccurs="0" maxOccurs="unbounded" />
        <xsd:element ref="ic:国籍コード" minOccurs="0" maxOccurs="unbounded" />
        <xsd:element ref="ic:出生国" minOccurs="0" maxOccurs="1" />
        <xsd:element ref="ic:出生国コード" minOccurs="0" maxOccurs="1" />
        <xsd:element ref="ic:出生地" minOccurs="0" maxOccurs="1" />
        <xsd:element ref="ic:年齢" minOccurs="0" maxOccurs="1" />
        <xsd:element ref="ic:身長" minOccurs="0" maxOccurs="1" />
        <xsd:element ref="ic:体重" minOccurs="0" maxOccurs="1" />
    </xsd:sequence>
</xsd:group>
*/
function createClassGroupNode(clazz, bc) {
    var xsdGroup = createNode('xsd:group', [{'name': 'name', 'value': clazz['name'] + '-固有要素グループ'}]);
    var xsdSequence = createNode('xsd:sequence');
    for (var i = 0; i < clazz['sets'].length; i++) {
        if (typeof(clazz['sets'][i]['deprecated']) === 'undefined' || bc) {
            var xsdElement = createNode(
                'xsd:element',
                [
                    {'name': 'ref', 'value': clazz['sets'][i]['property']['prefix'] + ':' + clazz['sets'][i]['property']['name']},
                    {'name': 'minOccurs', 'value': getMinOccur(clazz['sets'][i]['restriction'])},
                    {'name': 'maxOccurs', 'value': getMaxOccur(clazz['sets'][i]['restriction'])}
                ]
            );
            xsdSequence.appendChild(xsdElement);
        }
    }
    xsdGroup.appendChild(xsdSequence);
    return xsdGroup;
}

/**
 * Propertyのxsd:elementを作成する
 * @param {Object} property プロパティのオブジェクト
 * @return {string} Propertyのxsd:elementのテキスト
 */
/*
sample:
<xsd:element name="体系" type="ic:ID体系型" nillable="true">
    <xsd:annotation>
        <xsd:documentation xml:lang="ja">IDの体系を記述するためのプロパティ用語</xsd:documentation>
        <xsd:documentation xml:lang="en">The system for specifying identifiers.</xsd:documentation>
    </xsd:annotation>
</xsd:element>
*/
function createPropertyNode(property) {
    if (property['name'] === '識別値') {
        var xsdElement = createNode(
            'xsd:element',
            [
                {'name': 'name', 'value': property['name']},
                {'name': 'type', 'value': findRestriction(property['restriction'], 'type')['prefix'] + ':' + findRestriction(property['restriction'], 'type')['name']}
            ]
        );
    } else if (findRestriction(property['restriction'], 'type')['prefix']  === 'xsd') {
        var xsdElement = createNode(
            'xsd:element',
            [
                {'name': 'name', 'value': property['name']},
                {'name': 'type', 'value': property['prefix'] + ':' + property['name'] + '-単純型'}
            ]
        );
    } else if (findRestriction(property['restriction'], 'type')['prefix'].match(/^uncefact.*$/)) {
        var xsdElement = createNode(
            'xsd:element',
            [
                {'name': 'name', 'value': property['name']},
                {'name': 'type', 'value': findRestriction(property['restriction'], 'type')['prefix'] + ':' + findRestriction(property['restriction'], 'type')['name']}
            ]
        );
    } else {
        var xsdElement = createNode(
            'xsd:element',
            [
                {'name': 'name', 'value': property['name']},
                {'name': 'type', 'value': findRestriction(property['restriction'], 'type')['prefix'] + ':' + findRestriction(property['restriction'], 'type')['name']},
                {'name': 'nillable', 'value': 'true'},
            ]
        );
    }
    //xsdElement.appendChild(createPropertyAnnotation(property));
    var xsdAnnotation = createNode('xsd:annotation');
    xsdAnnotation.appendChild(createNode('xsd:documentation', [{'name': 'xml:lang', 'value': 'ja'}], findMetadata(property['metadata'], 'description', 'ja')));
    xsdAnnotation.appendChild(createNode('xsd:documentation', [{'name': 'xml:lang', 'value': 'en'}], findMetadata(property['metadata'], 'description', 'en')));
    xsdElement.appendChild(xsdAnnotation);
    return xsdElement;
}

/**
 * Classのxsd:elementを生成する
 * @param {Object} clazz クラスのオブジェクト
 * @return {string} Classのxsd:elementのテキスト
 */
/*
sample:
<xsd:element name="人" type="ic:人型" nillable="true">
    <xsd:annotation>
        <xsd:documentation xml:lang="ja">人の情報を表現するためのクラス用語</xsd:documentation>
        <xsd:documentation xml:lang="en">A class term to express information of a person.</xsd:documentation>
    </xsd:annotation>
</xsd:element>
*/
function createClassElement(clazz) {
    var xsdElement = createNode(
        'xsd:element',
        [
            {'name': 'name', 'value': clazz['name'].replace('型', '')},
            {'name': 'type', 'value': clazz['prefix'] + ':' + clazz['name']},
            {'name': 'nillable', 'value': 'true'}
        ]
    );
    var xsdAnnotation = createNode('xsd:annotation');
    xsdAnnotation.appendChild(createNode('xsd:documentation', [{'name': 'xml:lang', 'value': 'ja'}], findMetadata(clazz['metadata'], 'description', 'ja')));
    xsdAnnotation.appendChild(createNode('xsd:documentation', [{'name': 'xml:lang', 'value': 'en'}], findMetadata(clazz['metadata'], 'description', 'en')));
    xsdElement.appendChild(xsdAnnotation);
    return xsdElement;
}

/**
 * 要素名、属性、内容よりXMLの要素を作成する
 * @param {String} nodeName 要素名
 * @param {Array<Object>} attributes 属性のArray [{'name': '属性名'}, {'value': '値'}]
 * @param {String} textContent 要素の内容(テキストのみ)
 * @return {Node} javascript Nodeオブジェクト
 */
function createNode(nodeName, attributes = [], textContent = '') {
    var domParser = new DOMParser();
    var document = domParser.parseFromString('<?xml version="1.0" encoding="utf-8"?>');
    var dom = document.createElement(nodeName);

    for (var i = 0; i < attributes.length; i++) {
        dom.setAttribute(attributes[i]['name'], attributes[i]['value']);
    }
    if (textContent !== '') {
        dom.textContent = textContent;
    }
    return dom;
}

/**
 * メタデータをメタデータ名と言語コードで検索する
 * @param {Array<Object>} metadata 検索対象メタデータ
 * @param {String} name メタデータ名
 * @param {String} lang 言語
 * @return {string} 検索したメタデータの値
 */
function findMetadata(metadata, name, lang = 'ja') {
    //メタデータが空の場合
    if(metadata === null || typeof(metadata) === 'undefined') {
        return '';
    }
    var temp = [];
    for(var i = 0; i < metadata.length; i++) {
        if (metadata[i]['type'] === name) {
            temp.push(metadata[i]);
        }
    }
    if (temp.length === 0) {
        return '';
    }
    var targetMetadata = {};
    if (lang === 'ja') {
        for(var i = 0; i < temp.length; i++) {
            if(typeof(temp[i]['language']) === 'undefined') {
                targetMetadata = temp[i];
            }
        }
    }
    else {
        for(var i = 0; i < temp.length; i++) {
            if(temp[i]['language'] === lang) {
                targetMetadata = temp[i];
            }
        }
    }
    
    return targetMetadata['data'];
}

/**
 * 語彙のメタデータより指定されたprefixの情報を取得する
 * @param {Array<Object>} vocabularyMetadata 語彙メタデータ
 * @param {String} prefixName 検索する名前空間名
 * @return {string} 検索したprefix
 */
function findPrefix(vocabularyMetadata, prefixName) {
    for (var i = 0; i < vocabularyMetadata.length; i++) {
        if (vocabularyMetadata[i]['type'] === 'prefix') {
            if (vocabularyMetadata[i]['prefix'] === prefixName) {
                return vocabularyMetadata[i];
            }
        }
    }
    return null;
}

/**
 * 型制約のArrayから目的の制約を抽出
 * @param {Array<Object>} restriction 検索対象型制約Array
 * @param {String} type 制限値の種別
 * @return {Object} 検索した型制約
 */
function findRestriction(restriction, type) {
    //概念型、電話番号型
    if (typeof(restriction) === 'undefined') {
        return {};
    }

    var targetMetadata = {};
    for(var i = 0; i < restriction.length; i++) {
        if(restriction[i]['type'] === type) {
            targetMetadata = restriction[i];
        }
    }

    return targetMetadata;
}

/**
 * 値制約より下限値を抽出
 * @param {Object} restriction 値制約
 * @return {string} 下限値
 */
function getMinOccur(restriction) {
    var cardinarity = findRestriction(restriction, 'cardinality');

    if (typeof(cardinarity['min']) === 'undefined') {
        return 0;
    }

    return cardinarity['min'];
}

/**
 * 値制約より上限値を抽出
 * @param {Object} restriction 値制約
 * @return {string} 上限値
 */
function getMaxOccur(restriction) {
    var cardinarity = findRestriction(restriction, 'cardinality');

    if (typeof(cardinarity['max']) === 'undefined') {
        return 'unbounded';
    }

    return cardinarity['max'];
}

/**
 * 語彙のメタデータからCreatorのグループを抽出
 * @param {Array<Object>} metadata 語彙のメタデータArray
 * @return {Array<Object>} 分類されたCreatorのArray
 */
function getCreatorGroups(metadata) {
    var creatorGroups = [];
    var temp = {};
    for (var i = 0; i < metadata.length; i++) {
        if(metadata[i]['type'].match(/^creator.*$/)) {
            if (typeof(temp[metadata[i]['group']]) === 'undefined') temp[metadata[i]['group']] = [];
            temp[metadata[i]['group']].push(metadata[i]);
        }
    }
    var tempKeys = Object.keys(temp);
    for (var i = 0; i < tempKeys.length; i++) {
        creatorGroups.push(temp[tempKeys[i]]);
    }
    return creatorGroups;
}

/**
 * 語彙のクラスの配列から識別子検索する
 * @param {Array<Object>} classes 検索対象クラス配列
 * @param {String} identifier クラス識別子
 * @return {Object} クラス
 */
function findClass(classes, identifier) {
    for(var i = 0; i < classes.length; i++) {
        if (classes[i]['prefix'] + ':' + classes[i]['name'] === identifier) {
            return classes[i]
        }
    }
    return null;
}

/**
 * クラスと同名のプロパティがあるかチェック
 * @param {String} className クラス名
 * @return {boolean} チェック結果(true:同名のプロパティがある/false:ない)
 */
function checkPropertyName(vocabulary, className) {
    var flag = false;
    for (var i = 0; i < vocabulary.properties.length; i++) {
        if (vocabulary.properties[i]['name'] === className.replace('型', '')) {
            flag = true;
        }
    }
    return flag;
}

module.exports = Xsd