将 QName 作为字符串添加到 @XmlMixed@XmlAnyElement(lax = true) 列表
Posted
技术标签:
【中文标题】将 QName 作为字符串添加到 @XmlMixed@XmlAnyElement(lax = true) 列表【英文标题】:Add QName as string to @XmlMixed@XmlAnyElement(lax = true) list 【发布时间】:2021-09-30 23:05:26 【问题描述】:对不起,这个模糊的标题,我知道它并不能说明太多。
请考虑以下 xsd 类型定义:
<xsd:complexType name="TopicExpressionType" mixed="true">
<xsd:sequence>
<xsd:any processContents="lax" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="Dialect" type="xsd:anyURI" use="required"/>
<xsd:anyAttribute/>
</xsd:complexType>
完整的 XSD:http://docs.oasis-open.org/wsn/b-2.xsd
对应的JAXB生成的Java类:
package org.oasis_open.docs.wsn.b_2;
import org.w3c.dom.Element;
import javax.xml.bind.annotation.*;
import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "TopicExpressionType", propOrder =
"content"
)
public class TopicExpressionType
@XmlMixed
@XmlAnyElement(lax = true)
protected List<Object> content;
@XmlAttribute(name = "Dialect", required = true)
@XmlSchemaType(name = "anyURI")
protected String dialect;
@XmlAnyAttribute
private Map<QName, String> otherAttributes = new HashMap<QName, String>();
public List<Object> getContent()
if (content == null)
content = new ArrayList<Object>();
return this.content;
public String getDialect()
return dialect;
public void setDialect(String value)
this.dialect = value;
public Map<QName, String> getOtherAttributes()
return otherAttributes;
第一个目标是使用 JAXB 生成这样的 XML:
<wsnt:TopicExpression Dialect="http://docs.oasis-open.org/wsn/t-1/TopicExpression/Concrete" xmlns:tns="http://my.org/TopicNamespace">
tns:t1/*/t3
</wsnt:TopicExpression>
请注意以下几点:
TopicExpression
元素的值基本上是一个引用 QName 的查询字符串。示例:tns:t1/*/t3
TopicExpression
元素的值包含一个或多个类似 QName 的字符串 (tns:t1
)。它必须是示例中的字符串,不能是元素(例如:<my-expresseion>tns:t1/*/t3<my-expresseion/>
)
TopicExpression
元素的值是任意字符串(至少从架构的角度来看,它遵循此处定义的规则:https://docs.oasis-open.org/wsn/wsn-ws_topics-1.3-spec-os.pdf 第 18 页)
即使值是字符串,我也需要定义相应的名称空间声明。因此,如果我有这样的表达式:tns:t1
则必须声明 xmlns:tns
。如果我的表达式是tns:t1/*/tns2:t3
,那么xmlns:tns
和xmlns:tns2
都必须声明。
第二个目标是使用 JAXB 获取另一侧的 TopicExpression
的值以及命名空间。
我完全被卡住了,我不知道如何实现这一点。我唯一的想法是手动构建 TopicExpression
的值,并以某种方式告诉编组器包含相关的命名空间声明,尽管没有使用它的实际元素。
更新
包含前面提到的TopicExpression
的完整 SOAP 请求示例:
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
<env:Header>
<Action xmlns="http://www.w3.org/2005/08/addressing">http://docs.oasis-open.org/wsn/bw-2/NotificationProducer/SubscribeRequest</Action>
<MessageID xmlns="http://www.w3.org/2005/08/addressing">urn:uuid:57182d32-4e07-4f5f-8ab3-24838b3e33ac</MessageID>
</env:Header>
<env:Body>
<ns3:Subscribe xmlns:ns3="http://docs.oasis-open.org/wsn/b-2" xmlns:ns4="http://www.w3.org/2005/08/addressing" >
<ns3:ConsumerReference>
<ns4:Address>http://my-notification-consumer-url</ns4:Address>
</ns3:ConsumerReference>
<ns3:Filter>
<ns3:TopicExpression Dialect="http://docs.oasis-open.org/wsn/t-1/TopicExpression/Simple" xmlns:ns5="http://my.org/TopicNamespace" xmlns:ns6="http://extension.org/TopicNamespace">
ns5:t1/*/ns6:t3
<ns3:TopicExpression/>
</ns3:Filter>
</ns3:Subscribe>
</env:Body>
</env:Envelope>
【问题讨论】:
尝试将namespace="http://my.org/TopicNamespace"
添加到@XmlType
@LMC 不幸的是,命名空间不是固定的,它在运行时会发生变化,所以我无法对其进行硬编码。
如果架构不是永久性的,我会使用普通的 JAXP/XML API 而不是 JAXB 来处理这个简单的 XML,或者我会运行 XSL 转换为更标准化的 XML,并使用 JAXB 读取它。
@m4gic 当然,这不是整个 XML 文档,这只是相关部分。我将研究 XSL 转换,感谢您的提示。
【参考方案1】:
不确定,如果我正确理解了您的要求。看看这个代码示例是否对您有帮助。如果不是,那么也许可以尝试稍微编辑您的问题,让我了解您到底在寻找什么。我将尝试相应地修改和更新代码。尝试一个简单的例子比提供完整的 XSD 更好。另外,请查看以下方法:beforeMarshal
和 afterUnmarshal
。
以下是我尝试编组和解组的 XML
<tns:TopicExpression Dialect="http://docs.oasis-open.org/wsn/t-1/TopicExpression/Concrete" xmlns:tns="http://my.org/TopicNamespace">
tns:t1/*/t3
</tns:TopicExpression>
TopicExpressionType.class:
@Data
@XmlAccessorType(XmlAccessType.FIELD)
public class TestPojo
@XmlValue
private String TopicExpression;
@XmlAnyAttribute
private Map<String, Object> anyAttributes = new HashMap<>();
主类:
public class Main
public static void main(String[] args) throws JAXBException, XMLStreamException
final InputStream inputStream = Unmarshalling.class.getClassLoader().getResourceAsStream("topic.xml");
final XMLStreamReader xmlStreamReader = XMLInputFactory.newInstance().createXMLStreamReader(inputStream);
final Unmarshaller unmarshaller = JAXBContext.newInstance(TestPojo.class).createUnmarshaller();
final TestPojo topic = unmarshaller.unmarshal(xmlStreamReader, TestPojo.class).getValue();
System.out.println(topic.toString());
StringWriter stringWriter = new StringWriter();
Map<String, String> namespaces = new HashMap<String, String>();
namespaces.put("http://my.org/TopicNamespace", "tns");
Marshaller marshaller = JAXBContext.newInstance(TestPojo.class).createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.setProperty(MarshallerProperties.NAMESPACE_PREFIX_MAPPER, namespaces);
QName qName = new QName("http://my.org/TopicNamespace","TopicExpression","tns");
JAXBElement<TestPojo> root = new JAXBElement<TestPojo>(qName, TestPojo.class, topic);
marshaller.marshal(root, stringWriter);
String result = stringWriter.toString();
System.out.println(result);
如您所见,我已经直接填充了命名空间映射。如果这是动态的,那么您可以在地图中填充相同的内容,因此您可以在编组时添加它。
这将在解组期间提供以下输出:
TestPojo(TopicExpression=
tns:t1/*/t3
, anyAttributes=Dialect=http://docs.oasis-open.org/wsn/t-1/TopicExpression/Concrete)
在编组期间:
<tns:TopicExpression xmlns:tns="http://my.org/TopicNamespace" Dialect="http://docs.oasis-open.org/wsn/t-1/TopicExpression/Concrete">
tns:t1/*/t3
</tns:TopicExpression>
【讨论】:
TopicExpression
元素不得包含子元素,如<google:field1>Value1</google:field1>
。它应该包含上面指定的字符串,这是在 ws-topics 文档(上面链接)中定义的查询表达式,如果我可以将实际元素添加到 TopicExpression,那将非常好且易于实现。
@bakcsa83 好的,我已经编辑了答案和代码示例以匹配您的 XML。现在检查。
现在在您的解决方案中,TopicExpression
元素与其值 (tns) 具有相同的名称空间。这是错误的。 TopicExpression
有自己的命名空间,在我的问题中是 wsnt
。另请注意,表达式值可能有多个 QName(问题中的第 4 个要点。)。
但我没有看到 wsnt
的任何已声明命名空间。如果您使用了前缀 wsnt
,那么它应该在其自身的父 XML 元素中声明。我没有看到任何与wsnt
对应的命名空间。我验证了您的 XML 并得到了 The prefix "wsnt" for element "wsnt:TopicExpression" is not bound.
的结果。如果您有与其对应的命名空间,那么您可以在编组之前将其添加到 Map 并使用它,我相信这应该不是问题。
它不存在,因为它的值不相关,我不想引入整个上下文(SOAP、Spring 等)。关键是 TopicExpression 元素与值 (tns:t1)。【参考方案2】:
所以我实施的解决方案:
创建了一个新的TopicExpressionType
类,它不仅包含表达式字段,还包含命名空间字段,用于表达式:
public class TopicExpressionType
String dialect;
String expression;
List<Namespace> namespaces;
public TopicExpressionType(String dialect, String expression, List<Namespace> namespaces)
this.dialect = dialect;
this.expression = expression;
this.namespaces = namespaces;
public static class Namespace
String prefix;
String namespace;
public Namespace(String prefix, String namespace)
this.prefix = prefix;
this.namespace = namespace;
public String getPrefix()
return prefix;
public String getNamespace()
return namespace;
然后实现了一个XmlAdapter
,它知道细节,知道如何从表达式字符串中提取命名空间前缀,它可以在 TopicExpression XML 元素上读/写命名空间声明:
public class TopicExpressionTypeAdapter extends XmlAdapter<Element, TopicExpressionType>
@Override
public Element marshal(TopicExpressionType topicExpression) throws Exception
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.newDocument();
Element element = document.createElementNS("http://docs.oasis-open.org/wsn/b-2", "mns1:TopicExpression");
element.setAttribute("Dialect", topicExpression.getDialect());
element.setTextContent(topicExpression.getExpression());
for (var ns : topicExpression.namespaces)
element.setAttribute("xmlns:" + ns.prefix, ns.namespace);
return element;
@Override
public TopicExpressionType unmarshal(Element arg0) throws Exception
if (arg0.getFirstChild() instanceof Text text)
var expression = text.getData();
if (expression == null || expression.isBlank())
throw new TopicExpressionAdapterException("Empty content");
// Extract the prefixes from the expression
var namespacePrefixes = new ArrayList<String>();
getNamespacePrefixes(expression, namespacePrefixes);
//Now get the namespaces for the prefixes
var nsMap = new ArrayList<TopicExpressionType.Namespace>();
for (var prefix : namespacePrefixes)
var namespace = arg0.getAttribute("xmlns:" + prefix);
if (namespace == null || namespace.isBlank())
throw new TopicExpressionAdapterException("Missing namespace declaration for the following prefix: " + prefix);
nsMap.add(new TopicExpressionType.Namespace(prefix, namespace));
var dialect = arg0.getAttribute("Dialect");
if (dialect == null || dialect.isBlank())
throw new TopicExpressionAdapterException("Missing Dialect attribute");
return new TopicExpressionType(dialect, expression, nsMap);
else
throw new TopicExpressionAdapterException("Unexpected child element type: " + arg0.getFirstChild().getClass().getName());
public static class TopicExpressionAdapterException extends Exception
public TopicExpressionAdapterException(String message)
super(message);
注意:此答案有意省略了 getNamespacePrefixes()
方法的实现。
最后一步是在 JAXB 生成的类中使用 TopicExpressionType 的任何地方添加以下注释:
@XmlJavaTypeAdapter(TopicExpressionTypeAdapter.class)
TopicExpressionType topicExpression;
【讨论】:
以上是关于将 QName 作为字符串添加到 @XmlMixed@XmlAnyElement(lax = true) 列表的主要内容,如果未能解决你的问题,请参考以下文章
Worklight javax/xml/名称空间/QName