3

在 JAXB 输出中,我试图填充 xsi:schemaLocation 属性,以便它包含所有使用的名称空间和模式的完整信息。我试图找到一种比硬编码更好的方法!

我们有一个分布在大约 50 个文件中的 XML 模式。每个文件都定义了一个类型并拥有自己的命名空间。(我认为这不是一个明智的决定,但争论这个决定为时已晚。) XJC 愉快地编译整个混乱并生成类。当输出被编组时,所有 50 多个命名空间都被正确地放在根元素上。

问题是我被要求用所有命名空间和关联的模式源填充 xsi:schemaLocation 属性。因此,我需要放置 50 多对,而不是仅仅一对命名空间-URL 对(“my.co.schema.v1 http://my.company.com/schemas/my.co.schema.v1.xsd ”)在那里。当然我可以对其进行硬编码,但这是错误的。

我想向 JAXB 查询它使用的所有来源,但这似乎不可能。我开始认为我必须使用 Xerces 语法池并再次读取所有模式。那是最好的选择吗?

我在下面添加了一个 SCCE。工作正常,但正如我所提到的, xsi:schemaLocation 属性是硬编码且不完整的。

请帮助,提前谢谢。

package my.xml.generator;

import java.io.File;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;

import my.schema.ComponentType;
import my.schema.ObjectFactory;
import my.schema.ProductType;

/**
 * Builds a JAXB object and serializes to a file.
 * 
 * Many thanks to
 * http://blog.bdoughan.com/2010/12/jaxb-and-marshalunmarshal-schema.html
 */
public class SimpleXmlBuilder {

public static void main(String[] argv) throws Exception {
    if (argv.length != 1)
        throw new IllegalArgumentException(
                "Usage: SimpleXmlBuilder output-file-path");

    // Create a trivial component
    ObjectFactory objectFactory = new ObjectFactory();
    ProductType product = objectFactory.createProductType();
    product.setDeprecated(true);
    // Wrap in a root element
    JAXBElement<ProductType> rootElement = objectFactory
            .createProduct(product);

    // Get JAXB going
    Class<?>[] rootElementClass = { ComponentType.class };
    JAXBContext context = JAXBContext.newInstance(rootElementClass);
    Marshaller marshaller = context.createMarshaller();
    // Leave some whitespace for humans
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
    // The critical part of the problem!
    marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION,
            "My.Name.Space.v1" + " "
                    + "http://my.server.com/schemas/My.Name.Space.v1.xsd");

    // Write to the named file
    marshaller.marshal(rootElement, new File(argv[0]));
    System.out.println("Wrote generated XML to " + argv[0]);
  }

}
4

2 回答 2

1

演示代码

您可以利用javax.xml.validationAPI 使用LSResourceResolver.

import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.*;
import org.w3c.dom.ls.*;

public class Demo {

    public static void main(String[] args) throws Exception {
        SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        MyLSResourceResolver resourceResolver = new MyLSResourceResolver();
        sf.setResourceResolver(resourceResolver);

        StreamSource rootSchemaSource = new StreamSource("src/forum17992435/schema1.xsd");
        sf.setResourceResolver(resourceResolver);
        Schema schema = sf.newSchema(rootSchemaSource);
    }

    private static class MyLSResourceResolver implements LSResourceResolver {

        @Override
        public LSInput resolveResource(String type, String namespaceURI,
                String publicId, String systemId, String baseURI) {
            System.out.print(namespaceURI);
            System.out.print(" - ");
            System.out.println(systemId);
            return null;
        }

    }

}

XML 模式

schema1.xsd

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.org/schema1" xmlns:tns="http://www.example.org/schema1" elementFormDefault="qualified">

    <import namespace="http://www.example.org/schema2" schemaLocation="schema2.xsd"/>

</schema>

schema2.xsd

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.org/schema2" xmlns:tns="http://www.example.org/schema2" elementFormDefault="qualified">

    <import namespace="http://www.example.org/schema3" schemaLocation="schema3.xsd"/>

</schema>

架构3.xsd

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.org/schema3" xmlns:tns="http://www.example.org/schema3" elementFormDefault="qualified">
</schema>

输出

该演示将输出 2 个导入模式的位置。您需要提供第一个 XML 模式的位置。

http://www.example.org/schema2 - schema2.xsd
http://www.example.org/schema3 - schema3.xsd
于 2013-08-01T13:57:42.503 回答
0

非常感谢布莱斯的指点!他的解决方案绝对有效,它确认我必须再次阅读所有模式,从根模式开始。该信息无法从任何地方的 JAXB 工件中获得。

示例代码如下,资源解析器收集架构工厂找到的 URI 和关联的系统 ID 对。解析器还使用真正的 XML 目录文件,因此模式处理器使用本地缓存的模式文件(而不是通过网络读取 URL)。还添加了代码以使用新加载的模式验证内存中的 JAXB 对象。

下面的解决方案只需要 Java 1.6,不需要 3rd-party 库。我担心使用标记为“内部”的类。

package my.xml.generator;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.bind.util.JAXBSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.w3c.dom.ls.LSInput;

import my.schema.ObjectFactory;
import my.schema.ProductType;

import com.sun.org.apache.xerces.internal.util.XMLCatalogResolver;

/**
 * Builds a JAXB object, validates it then serializes it to a file.
 * 
 * Credit to Blaise Doughan for answering questions and these posts:
 * http://blog.bdoughan.com/2010/12/jaxb-and-marshalunmarshal-schema.html
 * http://blog.bdoughan.com/2010/11/validate-jaxb-object-model-with-xml.html
 *
 * Also helpful:
 * http://stackoverflow.com/questions/7364840/xmlschema-validation-with-
 * catalog-xml-file-for-entity-resolving
 */
public class ValidatingXmlBuilder {

/**
 * Hooks in to the system resolver to gather details about schemas.
 */
static class MyXMLCatalogResolver extends XMLCatalogResolver {

    /** List of "URI - SystemID" strings */
    private final List<String> namespaceUrlPairs;

    public MyXMLCatalogResolver(String[] catalogs) {
        super(catalogs);
        namespaceUrlPairs = new ArrayList<String>();
    }

    /**
     * Gathers namespace URI and system ID pairs
     */
    @Override
    public LSInput resolveResource(String type, String namespaceURI,
            String publicId, String systemId, String baseURI) {
        String pair = namespaceURI + " " + systemId;
        namespaceUrlPairs.add(pair);
        // System.out.println("Resolver found URI-system ID: " + pair);
        return super.resolveResource(type, namespaceURI, publicId,
                systemId, baseURI);
    }

    /**
     * @return String containing pairs of namespace URI and corresponding
     *         system ID, using a single space as separators
     */
    public String getSchemaLocationPairs() {
        StringBuffer sb = new StringBuffer();
        for (String pair : namespaceUrlPairs)
            sb.append(pair + " ");
        return sb.toString().trim();
    }
}

public static void main(String[] argv) throws Exception {
    if (argv.length != 3)
        throw new IllegalArgumentException(
                "Usage: ValidatingXmlBuilder schema-file catalog-file output-file");

    // TODO validate existence of files
    String schemaPath = argv[0];
    String catalogPath = argv[1];
    String outputPath = argv[2];

    // Create a schema with a catalog resolver to gather details
    System.out.println("Reading schema from " + schemaPath);
    System.out.println("Reading catalog from " + catalogPath);
    MyXMLCatalogResolver hookedResolver = new MyXMLCatalogResolver(
            new String[] { catalogPath });
    SchemaFactory schemaFactory = SchemaFactory
            .newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
    schemaFactory.setResourceResolver(hookedResolver);

    // Read in the schema to gather the info
    Schema schema = schemaFactory.newSchema(new File(schemaPath));

    // Create a trivial product and wrap it in root element
    ObjectFactory objectFactory = new ObjectFactory();
    ProductType product = objectFactory.createProductType();
    product.setSchemaVersion("1.0");
    JAXBElement<ProductType> rootElement = objectFactory
            .createProduct(product);

    // Get JAXB going for a this root element type
    Class<?>[] rootElementClass = { ProductType.class };
    JAXBContext jaxbContext = JAXBContext.newInstance(rootElementClass);

    // Create a validator and check the object
    // TODO: Create custom instance of org.xml.sax.ErrorHandler
    Validator validator = schema.newValidator();
    JAXBSource source = new JAXBSource(jaxbContext, rootElement);
    validator.validate(source);

    // Create and configure marshaller with gathered info
    Marshaller marshaller = jaxbContext.createMarshaller();
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
    // TODO: Fixup URI of root schema, specified here as file
    marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION,
            hookedResolver.getSchemaLocationPairs());

    // Write to the named file
    marshaller.marshal(rootElement, new File(outputPath));
    System.out.println("Wrote generated XML to " + outputPath);
  }

}
于 2013-08-06T11:08:09.923 回答