Accessing previous xml values while validating the xml file from generated schema

笑着哭i 提交于 2020-01-25 00:09:19

问题


I am trying to write a dynamic xml schema that will validate an xml file against different sources depending on previous entries in the xml.

I am currently generating an xsd schema using xslt and a spring beans file. This means I can set restrictions based on the spring config. I am having problems changing which bean is referenced depending on previous input.

My (simplified) beans file (doesn't have to use spring, can just be plain xml if needed):

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="transform.xsl"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd >

    <bean id="bean1" class="com.example.package.Class1">
        <property name=validation value="[a-zA-Z]">
    </bean>

    <bean id="bean2" class="com.example.package.Class1">
        <property name=validation value="[a-s]+">
    </bean>

    <bean id="bean3" class="com.example.package.Class1">
        <property name=validation value="[a-s]+">
    </bean>
</beans>

In the transform.xsl file I am trying to do something like the following:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:spring="http://www.springframework.org/schema/beans"
    exclude-result-prefixes="spring">

<xsl:output indent="yes" />

<xsl:template match="/">
    <xsd:complexType name="toValidate">
        <xsd:simpleType name="beanChoice">
            <xsd:restriction base="xsd:short">
                <xsd:maxInclusive value="3"/>
                <xsd:minInclusive value="1"/>
            </xsd:restriction>
        </xsd:simpleType>
        <xsd:simpleType name="beanString">
            <xsd:restriction base="xsd:string">
<!---=-=-=-=-Problem here-=-=-=-=-=--->
                <xsd:pattern value="if beanChoice==1 then bean1/validation/value
                                     else if beanChoice==2 then bean2/validation/value
                                     else bean3/validation/value"/>
            </xsd:restriction>
        </xsd:simpleType>
    </xsd:complexType>
</xsl:template>

Is there any way to do this?

Thanks


Edit: To try and clear up any confusion, the choice of bean should come from the file that the generated schema is validating. So the xslt file should generate a schema that I will use to validate xml files.

The following:

<toValidate>
    <beanChoice>2</beanChoice>
    <!-- this string should be validated against bean2 as the entry for beanChoice was 2-->
    <beanString>abc</beanString> 
</toValidate>

would be valid

but

<toValidate>
    <beanChoice>1</beanChoice>
    <!-- this string should be validated against bean1 as the entry for beanChoice was 1-->
    <beanString>abc</beanString> 
</toValidate>

would not as it does not match bean1's validation value

Hope that clears it up slightly. would be allowed


回答1:


I'm not sure if you want to generate one schema per file or a single schema to validate all of them. The second case seems to me as the best option, but since your examples show only one simple type, you might want something different. In any case, I included both alternatives in this answer.

In both cases I'm assuming you have several files bean-choice-1.xml, bean-choice-2.xml, each one containing the data you wish to validate with a generated schema (I'm assuming the files have exactly the XML you included in your question (where I will use the path /toValidate/beanChoice).

1. Generating one schema containing one simple type per file

The XSLT processor will use the beans.xml file as input. You can pass the name of another file from which you wish to extract data to XSLT as a parameter to your XSLT processor, which can also load it and parse it.

Parameter passing is implementation dependent. How it works depends on how you run your processor (command line, programming language, etc.) You can also hardwire the parameter in your stylesheet (which can be overriden if you pass a new parameter when you run the processor).

For example, running Saxon on the command line you can pass the parameters at the final arguments:

java net.sf.saxon.Transform -s:beans.xml -xsl:stylesheet.xsl -o:schema-1.xsd bean-file=bean-choice-2.xml

The line above sets a bean-file parameter containing bean-choice-2.xml which will set (or override) the parameter declared with a top-level <xsl:param> in the stylesheet. You can also run the processor and set its parameters with a programming language such as Java, C#, Python, Ruby, PHP, etc.

The following XSLT stylesheet uses the document() function, which parses a XML document located by an URI (stored in the <xsl:param>) and allows its nodes to be processed in the stylesheet. It will use, as a relative URI, the file name passed as a parameter (or use the default value if no parameter was passed).

The bean-choice variable stores a value obtained after parsing the file, and extracting the value stored in the path /toValidate/beanChoice of that file (assuming that path is unique).

That string (which will contain 1 or 2 - in your examples) will be used to build a string (concatenating bean with 1 or 2) which will be compared with the id attributes of the other beans. The regex will be extracted from the one that matches:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    version="1.0"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:spring="http://www.springframework.org/schema/beans"
    exclude-result-prefixes="spring">

    <xsl:output indent="yes"/>

    <!-- You can set/reset this parameter when running your XSLT processor -->
    <xsl:param name="bean-file">bean-choice-2.xml</xsl:param>

    <xsl:variable name="bean-choice" select="document($bean-file)/toValidate/beanChoice"/>

    <xsl:template match="/">

        <xsd:schema elementFormDefault="qualified">
            <xsd:simpleType name="beanChoice">
                <xsd:restriction base="xsd:string">
                    <xsd:maxInclusive value="3"/>
                    <xsd:minInclusive value="1"/>
                </xsd:restriction>
            </xsd:simpleType>

            <xsd:simpleType name="beanString">
                <xsd:restriction base="xsd:string">
                    <xsd:pattern ref="{//spring:bean[@id=concat('bean',$bean-choice)]/spring:property[@name='validation']/@value}" />
                </xsd:restriction>
            </xsd:simpleType>
        </xsd:schema>

    </xsl:template>

</xsl:stylesheet>

If you use a different file, you will get a XML Schema containing a different pattern in that simple type declaration.

2. Generating a single schema with multiple simple types

The stylesheet I included above would generate one schema per file. I'm not sure it that's the ideal situation for you. It's usually better to have one schema for many similar files, reusing the types in different elements (even if you later use it as an imported schema in a specific scenario). For a situation like that, you would have to somehow identify your simple types in order to name them differently. You could generate a XSD like this one where the simple types are named beanString-1 and beanString-2:

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
   <xsd:simpleType name="beanChoice">
      <xsd:restriction base="xsd:string">
         <xsd:maxInclusive value="3"/>
         <xsd:minInclusive value="1"/>
      </xsd:restriction>
   </xsd:simpleType>

   <xsd:simpleType name="beanString-1">
      <xsd:restriction base="xsd:string">
         <xsd:pattern ref="[a-zA-Z]"/>
      </xsd:restriction>
   </xsd:simpleType>

   <xsd:simpleType name="beanString-2">
      <xsd:restriction base="xsd:string">
         <xsd:pattern ref="[a-s]+"/>
      </xsd:restriction>
   </xsd:simpleType>
</xsd:schema>

You might already know all the files you need to extract data from, so instead of passing parameters, you can list them in the stylesheet. In The XSLT below, those files were included in an embedded document with an arbitrary namespace (using myns as a prefix). The stylesheet itself can be loaded as document(''), and the embedded document is accessible in /*/myns:files so we can use it to process a template for each file name, adding a new <xsd:simpleType> element in each case.

This XSLT was used to generate the XSD above, using the beans.xml file as input with the files bean-choice-1.xml and bean-choice-2.xml (containing the same data as the samples from your question) located in the same directory:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    version="1.0"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:spring="http://www.springframework.org/schema/beans"
    xmlns:myns="input-file-list"
    exclude-result-prefixes="spring myns">

    <xsl:output indent="yes"/>

    <myns:files>
        <name>bean-choice-1.xml</name>
        <name>bean-choice-2.xml</name> <!-- you can add more file names -->
    </myns:files>

    <xsl:template match="/">
        <xsd:schema elementFormDefault="qualified">
            <xsd:simpleType name="beanChoice">
                <xsd:restriction base="xsd:string">
                    <xsd:maxInclusive value="3"/>
                    <xsd:minInclusive value="1"/>
                </xsd:restriction>
            </xsd:simpleType>

            <xsl:apply-templates select="document('')/*/myns:files/name">
                <xsl:with-param name="spring-beans" select="//spring:bean" />
            </xsl:apply-templates>
        </xsd:schema>
    </xsl:template>

    <!-- This template is processed for each file -->
    <xsl:template match="myns:files/name">
        <xsl:param name="spring-beans"/> <!-- this contains the collection of beans from the beans.xml file -->
        <xsl:variable name="bean-choice" select="document(.)/toValidate/beanChoice"/>

        <xsd:simpleType name="beanString-{$bean-choice}">
            <xsd:restriction base="xsd:string">
                <xsd:pattern ref="{$spring-beans[@id=concat('bean',$bean-choice)]/spring:property[@name='validation']/@value}" />
            </xsd:restriction>
        </xsd:simpleType>
    </xsl:template>

</xsl:stylesheet>


来源:https://stackoverflow.com/questions/24167003/accessing-previous-xml-values-while-validating-the-xml-file-from-generated-schem

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!