XSLT для генерации мощностей элементов

Я хотел бы узнать структуру набора данных XML, для которого у меня нет схемы XML. В рамках этого открытия я хотел бы вычислить минимальное и максимальное количество элементов (minOccurs и maxOccurs) элементов набора данных. Я пробовал различные инструменты для создания схем XML из документов XML, но они не генерируют minOccurs и maxOccurs. Однако я подозреваю, что это возможно с XSLT (2.0+).

Более конкретно, скажем, у меня есть следующий XML-документ:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <a/>
    <b>
        <c/>
    </b>
    <b/>
</root>

Я хотел бы иметь возможность вычислять кардинальности в такой форме:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <a minOccurs="1" maxOccurs="1"/>
    <b minOccurs="2" maxOccurs="2">
        <c minOccurs="0" maxOccurs="1"/>
    </b>
</root>

Дети корня всегда будут иметь одинаковую максимальную и минимальную кардинальность, поэтому эту часть можно вычислить примерно так:

<xsl:template match="/*">
    <xsl:element name="{name()}">
        <xsl:for-each-group select="*" group-by="name()">
            <xsl:sort select="current-grouping-key()"/>
            <xsl:element name="{current-grouping-key()}">
                <xsl:variable name="cardinality" select="count(current-group())"/>
                <xsl:attribute name="minOccurs" select="$cardinality"/>
                <xsl:attribute name="maxOccurs" select="$cardinality"/>
            </xsl:element>
        </xsl:for-each-group>
    </xsl:element>
</xsl:template>

Однако я не могу понять, как продолжить работу с мощностями внуков. Я подозреваю, что это можно абстрагировать в рекурсивный xsl:function.

Любые предложения о том, как действовать, приветствуются!


person Jindřich Mynarz    schedule 21.09.2016    source источник
comment
Дети корня with всегда имеют одинаковую максимальную и минимальную кардинальность Почему так?   -  person michael.hor257k    schedule 21.09.2016
comment
Поскольку существует только один корневой элемент, его дочерние элементы будут иметь только одну мощность в одном XML-документе.   -  person Jindřich Mynarz    schedule 21.09.2016
comment
› Я пробовал различные инструменты для создания XML-схем из XML-документов ‹ Пробовали ли вы Oxygen -> генерировать XML? Документ Oxygen   -  person uL1    schedule 21.09.2016
comment
Дочерний элемент root не может быть необязательным (т.е. иметь minOccurs=0)?   -  person michael.hor257k    schedule 21.09.2016
comment
Да, я пробовал генерацию схемы Oxygen. Это почти удовлетворяет мой вариант использования. Он использует maxOccurs="unbounded" вместо конкретного числа. Кроме того, я хотел бы иметь возможность расширить генерацию схемы для учета только непустых элементов.   -  person Jindřich Mynarz    schedule 21.09.2016
comment
Дочерний элемент root может быть необязательным, однако, когда вы смотрите на один XML-документ (мой вариант использования), он будет иметь одну кардинальность. Было бы иначе, если бы я попытался вычислить кардинальность из нескольких XML-документов.   -  person Jindřich Mynarz    schedule 21.09.2016


Ответы (1)


Я не уверен на 100%, подойдет ли это вам, но я придумал этот XSLT. Он работает путем группировки элементов по их путям (например, «root/a/b»).

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">

    <xsl:output indent="yes"/>

    <xsl:key name="parent_path" match="*" use="string-join(ancestor::*/name(), '/')" />
    <xsl:key name="full_path" match="*" use="string-join(ancestor-or-self::*/name(), '/')" />

    <xsl:template match="/*" priority="2">
        <xsl:element name="{name()}">
            <xsl:call-template name="element" />
        </xsl:element>
    </xsl:template>

    <xsl:template match="*" name="element">
        <xsl:variable name="path" select="string-join(ancestor-or-self::*/name(), '/')" />
        <xsl:for-each-group select="key('parent_path', $path)" group-by="name()">
            <xsl:sort select="current-grouping-key()"/>
            <xsl:element name="{current-grouping-key()}">
                <xsl:variable name="counts" select="key('full_path', $path)/count(*[name() = name(current())])" />
                <xsl:variable name="min" select="min($counts)" />
                <xsl:variable name="max" select="max($counts)"/>
                <xsl:attribute name="minOccurs" select="if (not(contains($path, '/'))) then $max else $min"/>
                <xsl:attribute name="maxOccurs" select="$max"/>
                <xsl:apply-templates select="." />
            </xsl:element>
        </xsl:for-each-group>
    </xsl:template>
</xsl:stylesheet>

Применительно к этому XML

<root>
    <a/>
    <b>
        <c/>
        <c/>
    </b>
    <b>
        <c/>
        <d>
            <e />
        </d>
        <g></g>
        <g></g>
        <g></g>
    </b>
    <b>
        <c/>
        <d>
            <e />
            <e />
        </d>
        <g></g>
        <g></g>
    </b>
    <a/>
</root>

Выводится следующее....

<root>
   <a minOccurs="2" maxOccurs="2"/>
   <b minOccurs="3" maxOccurs="3">
      <c minOccurs="1" maxOccurs="2"/>
      <d minOccurs="0" maxOccurs="1">
         <e minOccurs="1" maxOccurs="2"/>
      </d>
      <g minOccurs="0" maxOccurs="3"/>
   </b>
</root>
person Tim C    schedule 21.09.2016
comment
Минимальная кардинальность вычислена неправильно. Если я правильно понимаю if (not(contains($path, '/'))) then $cardinality else 0, то все элементы с некорневым родителем будут иметь минимальную мощность, установленную на 0, а остальные будут иметь одинаковую минимальную и максимальную мощность. - person Jindřich Mynarz; 21.09.2016
comment
Извините, я сделал предположение, что для некорневых элементов всегда требуется minOccurs, равный 0. Однако я изменил свой ответ, поэтому теперь он должен вычислять минимум на основе минимального количества дочерних элементов, а не принимать 0. - person Tim C; 21.09.2016