Controlling namespace prefixes in JAXB - java

How does jaxb determine the list of namespace prefix declarations whem marshalling an object? I used xjc to compile java classes for ebics (ebics schema). When I create an instance for an ebicsRequest it looks like this:
<?xml version="1.0" encoding="UTF-16"?>
<ns2:ebicsRequest xmlns:ns2="http://www.ebics.org/H003" Revision="1" Version="H003" xmlns="http://www.ebics.org/H003" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:ns4="http://www.ebics.org/S001" xmlns:ns5="http://www.ebics.org/H000">
<ns2:header authenticate="true">
<ns2:static>
<ns2:HostID>SIZBN001</ns2:HostID>
<ns2:Nonce>A5488F43223063171CA0FA59ADC635F0</ns2:Nonce>
<ns2:Timestamp>2009-08-04T08:41:56.967Z</ns2:Timestamp>
<ns2:PartnerID>EBICS</ns2:PartnerID>
<ns2:UserID>EBIX</ns2:UserID>
<ns2:Product Language="de">EBICS-Kernel V2.0.4, SIZ/PPI</ns2:Product>
<ns2:OrderDetails>
<ns2:OrderType>FTB</ns2:OrderType>
<ns2:OrderID>A037</ns2:OrderID>
<ns2:OrderAttribute>OZHNN</ns2:OrderAttribute>
<ns2:StandardOrderParams/>
</ns2:OrderDetails>
<ns2:BankPubKeyDigests>
<ns2:Authentication Algorithm="RSA" Version="X002">...</ns2:Authentication>
<ns2:Encryption Algorithm="RSA" Version="E002">...</ns2:Encryption>
</ns2:BankPubKeyDigests>
<ns2:SecurityMedium>0000</ns2:SecurityMedium>
<ns2:NumSegments>1</ns2:NumSegments>
</ns2:static>
<ns2:mutable>
<ns2:TransactionPhase>Initialisation</ns2:TransactionPhase>
</ns2:mutable>
</ns2:header>
<ns2:AuthSignature>
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#xpointer(//*[#authenticate='true'])">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>CSbjPbiNcFqSl6lCI1weK5x1nMeCH5bTQq5pedq5uI0=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>...</ds:SignatureValue>
</ns2:AuthSignature>
<ns2:body>
<ns2:DataTransfer>
<ns2:DataEncryptionInfo authenticate="true">
<ns2:EncryptionPubKeyDigest Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" Version="E002">dFAYe281vj9NB7w+VoWIdfHnjY9hNbZLbHsDOu76QAE=</ns2:EncryptionPubKeyDigest>
<ns2:TransactionKey>...</ns2:TransactionKey>
</ns2:DataEncryptionInfo>
<ns2:SignatureData authenticate="true">...</ns2:SignatureData>
</ns2:DataTransfer>
</ns2:body>
</ns2:ebicsRequest>
I have used a custom NamespacePrefixMapper to declare the default namespace and prefixes for ds and xsi. For the namespace ds it works fine. But for the default namespace it does not. It is declared two times once as ns2 and once as "" the latter coming from my custom NamespacePrefixMapper.getPreDeclaredNamespaceUris. I have played around a lot with this class. Also I tried to use the package-info.java but I was not able to make jaxb use "http://www.ebics.org/H003" as default namespace. What I also do not understand is the appearance of ns4 and ns5 which are not at all part of the xml document.
My NamespacePrefixMapper class looks like
public class NamespacePrefixMapperImpl extends NamespacePrefixMapper implements NamespaceContext {
private static final String[] EMPTY_STRING = new String[0];
private Map prefixToUri = null;
private Map uriToPrefix = null;
private void init(){
prefixToUri = new HashMap();
prefixToUri.put("", "http://www.ebics.org/H003" );
prefixToUri.put("ds", "http://www.w3.org/2000/09/xmldsig#" );
prefixToUri.put("xsi", "http://www.w3.org/2001/XMLSchema-instance" );
prefixToUri.put(XMLConstants.XML_NS_PREFIX, XMLConstants.XML_NS_URI );
prefixToUri.put(XMLConstants.XMLNS_ATTRIBUTE , XMLConstants.XMLNS_ATTRIBUTE_NS_URI );
uriToPrefix = new HashMap();
for(String prefix : prefixToUri.keySet()){
uriToPrefix.put(prefixToUri.get(prefix), prefix);
}
}
#Override
public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
if (uriToPrefix == null)
init();
if (uriToPrefix.containsKey(namespaceUri)){
return uriToPrefix.get(namespaceUri);
}
return suggestion;
}
#Override
public String[] getContextualNamespaceDecls() {
// TODO Auto-generated method stub
return EMPTY_STRING;
}
#Override
public String[] getPreDeclaredNamespaceUris() {
// TODO Auto-generated method stub
return EMPTY_STRING;
}
#Override
public String[] getPreDeclaredNamespaceUris2() {
return new String [] {"", prefixToUri.get("")};
}
public String getNamespaceURI(String prefix) {
if (prefixToUri == null)
init();
if (prefixToUri.containsKey(prefix)) {
return prefixToUri.get(prefix);
} else {
return XMLConstants.NULL_NS_URI;
}
}
public String getPrefix(String namespaceURI) {
if (uriToPrefix == null)
init();
if (uriToPrefix.containsKey(namespaceURI)){
return uriToPrefix.get(namespaceURI);
} else {
return null;
}
}
public Iterator getPrefixes(String namespaceURI) {
if (uriToPrefix == null)
init();
List prefixes = new LinkedList();
if (uriToPrefix.containsKey(namespaceURI)){
prefixes.add(uriToPrefix.get(namespaceURI));
}
return prefixes.iterator();
}
}
I am using
Manifest-Version: 1.0
Ant-Version: Apache Ant 1.6.5
Created-By: 1.5.0-b64 (Sun Microsystems Inc.)
Specification-Title: Java Architecture for XML Binding
Specification-Version: 2.0
Specification-Vendor: Sun Microsystems, Inc.
Implementation-Title: JAXB Reference Implementation
Implementation-Version: 2.0.2
Implementation-Vendor: Sun Microsystems, Inc.
Implementation-Vendor-Id: com.sun
Extension-Name: com.sun.xml.bind
Build-Id: b01
Class-Path: jaxb-api.jar activation.jar jsr173_1.0_api.jar jaxb1-impl.
jar
Name: com.sun.xml.bind.v2.runtime
Implementation-Version: 2.0.2-b01-fcs

JAXB always adds all namespaces that are known by the JAXBContext to the root element of the XML document for performance reasons. See this comment by Kohsuke on JAXB-103 for more information.
The only way I found to deal with this, is to traverse the document myself after it has been created with JAXB and remove all unused namespaces using the following helper class:
public class RemoveUnusedNamespaces {
private static final String XML_NAMESPACE_SCHEMA_INSTANCE = "http://www.w3.org/2001/XMLSchema-instance";
private static final String XML_NAMESPACE_NAMESPACE = "http://www.w3.org/2000/xmlns/";
private interface ElementVisitor {
void visit(Element element);
}
public void process(Document document) {
final Set<String> namespaces = new HashSet<String>();
Element element = document.getDocumentElement();
traverse(element, new ElementVisitor() {
public void visit(Element element) {
String namespace = element.getNamespaceURI();
if (namespace == null)
namespace = "";
namespaces.add(namespace);
NamedNodeMap attributes = element.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node node = attributes.item(i);
if (XML_NAMESPACE_NAMESPACE.equals(node.getNamespaceURI()))
continue;
String prefix;
if (XML_NAMESPACE_SCHEMA_INSTANCE.equals(node.getNamespaceURI())) {
if ("type".equals(node.getLocalName())) {
String value = node.getNodeValue();
if (value.contains(":"))
prefix = value.substring(0, value.indexOf(":"));
else
prefix = null;
} else {
continue;
}
} else {
prefix = node.getPrefix();
}
namespace = element.lookupNamespaceURI(prefix);
if (namespace == null)
namespace = "";
namespaces.add(namespace);
}
}
});
traverse(element, new ElementVisitor() {
public void visit(Element element) {
Set<String> removeLocalNames = new HashSet<String>();
NamedNodeMap attributes = element.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node node = attributes.item(i);
if (!XML_NAMESPACE_NAMESPACE.equals(node.getNamespaceURI()))
continue;
if (namespaces.contains(node.getNodeValue()))
continue;
removeLocalNames.add(node.getLocalName());
}
for (String localName : removeLocalNames)
element.removeAttributeNS(XML_NAMESPACE_NAMESPACE, localName);
}
});
}
private final void traverse(Element element, ElementVisitor visitor) {
visitor.visit(element);
NodeList children = element.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeType() != Node.ELEMENT_NODE)
continue;
traverse((Element) node, visitor);
}
}
}

EclipseLink JAXB (MOXy) uses the prefixes as specified in the #XmlSchema annotation (I'm the MOXy lead). Check out my answer to a similar question for an example:
How to customize namespace prefixes on Jersey(JAX-WS)

The way I've found to get JAXB to remove the ns2 prefix is to include the following attribute in the xs:schema element: elementFormDefault="qualified". So it would look something like this:
<xs:schema targetNamespace="urn:blah:blah" xmlns="urn:blah:blah" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">

I generated my JAXB classes using xjc, but the SOAP WebService i am using force me to follow some rules, like not using namespace prefix.
This is invalid:
<envEvento versao="1.00" xmlns="http://www.portalfiscal.inf.br/nfe" xmlns:ns2="http://www.w3.org/2000/09/xmldsig#">
<idLote>123</idLote>
<evento>
<ns2:Signature/>
</evento>
</envEvento>
This is valid:
<envEvento versao="1.00" xmlns="http://www.portalfiscal.inf.br/nfe">
<idLote>123</idLote>
<evento>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#"/>
</evento>
</envEvento>
As pointed out, JAXB put the namespace declaration at the root element.
To overcome this, the first approach i use is to avoid unnecessary elements in the context.
For example, setting the context of the marshaller like this:
JAXBContext.newInstance("path.to.package");
Can lead JAXB to make some unncessary declarations of namespaces.
Sometimes, i can get ride of an annoying xmlns="http://www.w3.org/2000/09/xmldsig#" just setting the context with the necessary Root Element:
JAXBContext.newInstance(MyRootElement.class);
A second approach i use when the first is not enough, is to make the entire context use the same namespace. Just changing the unwanted "http://www.w3.org/2000/09/xmldsig#", in every namespace declaration (like #XmlElement or #XSchema), to the unique namespace allowed (http://www.portalfiscal.inf.br/nfe)
Then, i just create an attribute at the desired child:
#XmlAttribute(name="xmlns")
String xmlns = "http://www.w3.org/2000/09/xmldsig#";
Now i have the namespace declaration out of the root, in the correct element, without using any prefix.

After crawling many posts, solutions using NamespacePrefixMapper has dependency on JDK version (which may break the code in the future) or the XML DOM tree manipulation looks complicated.
My brute-force approach is to manipulate the generated XML itself.
/**
* Utility method to hide unused xmlns definition in XML root.
* #param sXML Original XML string.
* #return
*/
public static String hideUnUsedNamespace(String sXML) {
int iLoc0 = sXML.indexOf("?><");
int iLoc1 = sXML.indexOf("><",iLoc0+3)+1;
String sBegin = sXML.substring(0,iLoc0+2);
String sHeader = sXML.substring(iLoc0+2, iLoc1-1);
String sRest = sXML.substring(iLoc1);
//System.out.println("sBegin=" + sBegin);
//System.out.println("sHeader=" + sHeader);
//System.out.println("sRest=" + sRest);
String[] saNS = sHeader.split(" ");
//System.out.println("saNS=" + java.util.Arrays.toString(saNS));
StringBuffer sbHeader = new StringBuffer();
for (String s: saNS) {
//System.out.println(s);
if (s.startsWith("xmlns:")) {
String token = "<" + s.substring(6,s.indexOf("="));
//System.out.println("token=" + token + ",indexOf(token)=" + sRest.indexOf(token));
if (sRest.indexOf(token) >= 0) {
sbHeader = sbHeader.append(s).append(" ");
//System.out.println("...included");
}
} else {
sbHeader = sbHeader.append(s).append(" ");
}
}
return (sBegin + sbHeader.toString().trim() + ">" + sRest);
}
/**
* Main method for testing
*/
public static void main(String[] args) {
String sXML ="<?xml version=\"1.0\" encoding=\"UTF-16\"?><ns2:ebicsRequest xmlns:ns2=\"http://www.ebics.org/H003\" Revision=\"1\" Version=\"H003\" xmlns=\"http://www.ebics.org/H003\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:ns4=\"http://www.ebics.org/S001\" xmlns:ns5=\"http://www.ebics.org/H000\"><ns2:header authenticate=\"true\"><ns2:static><ns2:HostID>SIZBN001</ns2:HostID><ns2:Nonce>A5488F43223063171CA0FA59ADC635F0</ns2:Nonce><ns2:Timestamp>2009-08-04T08:41:56.967Z</ns2:Timestamp><ns2:PartnerID>EBICS</ns2:PartnerID><ns2:UserID>EBIX</ns2:UserID><ns2:Product Language=\"de\">EBICS-Kernel V2.0.4, SIZ/PPI</ns2:Product><ns2:OrderDetails><ns2:OrderType>FTB</ns2:OrderType><ns2:OrderID>A037</ns2:OrderID><ns2:OrderAttribute>OZHNN</ns2:OrderAttribute><ns2:StandardOrderParams/></ns2:OrderDetails><ns2:BankPubKeyDigests><ns2:Authentication Algorithm=\"RSA\" Version=\"X002\">...</ns2:Authentication><ns2:Encryption Algorithm=\"RSA\" Version=\"E002\">...</ns2:Encryption></ns2:BankPubKeyDigests><ns2:SecurityMedium>0000</ns2:SecurityMedium><ns2:NumSegments>1</ns2:NumSegments></ns2:static><ns2:mutable><ns2:TransactionPhase>Initialisation</ns2:TransactionPhase></ns2:mutable></ns2:header><ns2:AuthSignature><ds:SignedInfo><ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315\"/><ds:SignatureMethod Algorithm=\"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256\"/><ds:Reference URI=\"#xpointer(//*[#authenticate='true'])\"><ds:Transforms><ds:Transform Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315\"/></ds:Transforms><ds:DigestMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\"/><ds:DigestValue>CSbjPbiNcFqSl6lCI1weK5x1nMeCH5bTQq5pedq5uI0=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>...</ds:SignatureValue></ns2:AuthSignature><ns2:body><ns2:DataTransfer><ns2:DataEncryptionInfo authenticate=\"true\"><ns2:EncryptionPubKeyDigest Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\" Version=\"E002\">dFAYe281vj9NB7w+VoWIdfHnjY9hNbZLbHsDOu76QAE=</ns2:EncryptionPubKeyDigest><ns2:TransactionKey>...</ns2:TransactionKey></ns2:DataEncryptionInfo><ns2:SignatureData authenticate=\"true\">...</ns2:SignatureData></ns2:DataTransfer></ns2:body></ns2:ebicsRequest>";
System.out.println("Before=" + sXML);
System.out.println("After =" + hideUnUsedNamespace(sXML));
}
The output shows un-used xmlns namespace is filtered out:
<ns2:ebicsRequest xmlns:ns2="http://www.ebics.org/H003" Revision="1" Version="H003" xmlns="http://www.ebics.org/H003" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">

Related

Apache POI - Java - Get Section Name - PowerPoint

Microsoft Powerpoint has a feature to split the slides by section (a logical grouping).
What's the best way to extract the section name?
Tech Stack -
Apache POI - v5.2.2
Java
I've achieved the same with VBA
sectionName = ActivePresentation.SectionProperties.Name(currentSlide.sectionIndex)
The Office Open XML which Apache POI uses is Office Open XML defined in 2006 and first published in Office 2007. This OOXML does not know something about sections in presentations. Sections were introduced later (2010).
Even ECMA-376 5th edition does not contain anything about sections in presentations. So Microsoft has not public published XSDs for this extension yet. So XmlBeans can't have created classes for it.
So if one would want using that feature, one would must manipulate the XML directly.
How to get what XML needs to be manipulated?
All Office Open XML files, so also PowerPoint *.pptx, are ZIP archives containing XML files and other files in a special directory structure. One can simply unzip a *.pptx file and have a look into.
Have a look into the /ppt/presentation.xml and you will see the XML.
What to use to manipulate the XML?
One can use org.openxmlformats.schemas.presentationml.x2006.main.* classes contained in poi-ooxml-full-5.*.jar as long as possible and else org.apache.xmlbeans.XmlObject and/or org.apache.xmlbeans.XmlCursorcontained in xmlbeans-5.*.jar. But using XmlObject directly can be very laborious.
Complete example for how to get the sections and the section names:
import java.io.FileInputStream;
import org.apache.poi.xslf.usermodel.*;
import org.apache.xmlbeans.XmlObject;
import javax.xml.namespace.QName;
public class PowerPointGetSectionProperties {
static Long getSlideId(XSLFSlide slide) {
if (slide == null) return null;
Long slideId = null;
XMLSlideShow presentation = slide.getSlideShow();
String slideRId = presentation.getRelationId(slide);
org.openxmlformats.schemas.presentationml.x2006.main.CTPresentation ctPresentation = presentation.getCTPresentation();
org.openxmlformats.schemas.presentationml.x2006.main.CTSlideIdList sldIdLst = ctPresentation.getSldIdLst();
for (org.openxmlformats.schemas.presentationml.x2006.main.CTSlideIdListEntry sldId : sldIdLst.getSldIdList()) {
if (sldId.getId2().equals(slideRId)) {
slideId = sldId.getId();
break;
}
}
return slideId;
}
static XmlObject[] getSections(org.openxmlformats.schemas.presentationml.x2006.main.CTExtensionList extList) {
if (extList == null) return new XmlObject[0];
XmlObject[] sections = extList.selectPath(
"declare namespace p14='http://schemas.microsoft.com/office/powerpoint/2010/main' "
+".//p14:section");
return sections;
}
static XmlObject[] getSectionSldIds(XmlObject section) {
if (section == null) return new XmlObject[0];
XmlObject[] sldIds = section.selectPath(
"declare namespace p14='http://schemas.microsoft.com/office/powerpoint/2010/main' "
+".//p14:sldId");
return sldIds;
}
static Long getSectionSldId(XmlObject sectionSldId) {
if (sectionSldId == null) return null;
Long sldIdL = null;
XmlObject sldIdO = sectionSldId.selectAttribute(new QName("id"));
if (sldIdO instanceof org.apache.xmlbeans.impl.values.XmlObjectBase) {
String sldIsS = ((org.apache.xmlbeans.impl.values.XmlObjectBase)sldIdO).getStringValue();
try {
sldIdL = Long.valueOf(sldIsS);
} catch (Exception ex) {
// do nothing
}
}
return sldIdL;
}
static XmlObject getSection(XSLFSlide slide) {
Long slideId = getSlideId(slide);
if (slideId != null) {
XMLSlideShow presentation = slide.getSlideShow();
org.openxmlformats.schemas.presentationml.x2006.main.CTPresentation ctPresentation = presentation.getCTPresentation();
org.openxmlformats.schemas.presentationml.x2006.main.CTExtensionList extList = ctPresentation.getExtLst();
XmlObject[] sections = getSections(extList);
for (XmlObject section : sections) {
XmlObject[] sectionSldIds = getSectionSldIds(section);
for (XmlObject sectionSldId : sectionSldIds) {
Long sldIdL = getSectionSldId(sectionSldId);
if (slideId.equals(sldIdL)) {
return section;
}
}
}
}
return null;
}
static String getSectionName(XmlObject section) {
if (section == null) return null;
String sectionName = null;
XmlObject name = section.selectAttribute(new QName("name"));
if (name instanceof org.apache.xmlbeans.impl.values.XmlObjectBase) {
sectionName = ((org.apache.xmlbeans.impl.values.XmlObjectBase)name).getStringValue();
}
return sectionName;
}
public static void main(String args[]) throws Exception {
XMLSlideShow slideShow = new XMLSlideShow(new FileInputStream("./PPTXUsingSections.pptx"));
for (XSLFSlide slide : slideShow.getSlides()) {
System.out.println(slide.getSlideName());
XmlObject section = getSection(slide);
String sectionName = getSectionName(section);
System.out.println(sectionName);
}
slideShow.close();
}
}

Java: generic XML reader using reflection

So, I have a class XmlReader, that is able to load and return a list of entities of type Client (which is defined by me), from an xml file. But, I also may have an xml file having entities of type "Movie" or any other type, and I want that my XmlReader to be able to read and retrieve that from the file.. I heard something that reflection could be used in order to make a generic XmlReader class, but I do not know how to do that.. can anyone help me? Thanks in advance! My code for the XmlReader class is:
public class XmlReader<ID, T extends MyObject<ID>>
{
private String fileName;
public XmlReader(String fileName)
{
this.fileName = fileName;
}
public List<Client> loadEntities()
{
List<Client> entities = new ArrayList<>();
XmlHelper docXml = new XmlHelper();
Document document = docXml.loadDocument(this.fileName);
document.getDocumentElement().normalize();
Element root = document.getDocumentElement();
NodeList clientElements = root.getElementsByTagName("field");
int id=-1;
String name="";
for (int i=0; i < clientElements.getLength(); i++)
{
Node clientElement = clientElements.item(i);
Element el = (Element) clientElement;
if (clientElement.getNodeType() != Node.ELEMENT_NODE)
{
// ignoring element due to wrong node type
continue;
}
if (el.hasAttribute("name") && el.getAttribute("name").equals("id"))
{
id = Integer.parseInt(el.getAttribute("value"));
}
if (el.hasAttribute("name") && el.getAttribute("name").equals("name"))
{
name = el.getAttribute("value");
}
if (i % 2 ==1)
{
Client newClient = new Client(id, name);
entities.add(newClient);
}
}
return entities;
}
}
And the xml file looks like:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Clients>
<entity class="class Domain.Client">
<field name="name" type="class java.lang.String" value="Liviu"/>
<field name="id" type="class java.lang.Object" value="1"/>
</entity>
</Clients>
If you are extracting the name of the class from within your XML you could do something like:
String className = ... // extract your class name into this
YourClass c = (YourClass) Class.forName(className).newInstance();

How to bind input externally to xquery using saxon?

I have to invoke external java methods in xquery using saxon HE. I could able to invoke the methods with the below code. But the problem is i want to bind my input externally.
final Configuration config = new Configuration();
config.registerExtensionFunction(new ShiftLeft());
final StaticQueryContext sqc = new StaticQueryContext(config);
final XQueryExpression exp = sqc.compileQuery(new FileReader(
"input/names.xq"));
final DynamicQueryContext dynamicContext = new DynamicQueryContext(config);
String xml = "<student_list><student><name>George Washington</name><major>Politics</major><phone>312-123-4567</phone><email>gw#example.edu</email></student><student><name>Janet Jones</name><major>Undeclared</major><phone>311-122-2233</phone><email>janetj#example.edu</email></student><student><name>Joe Taylor</name><major>Engineering</major><phone>211-111-2333</phone><email>joe#example.edu</email></student></student_list>";
DocumentBuilderFactory newInstance = DocumentBuilderFactory.newInstance();
newInstance.setNamespaceAware(true);
Document parse = newInstance.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
DocumentWrapper sequence = new DocumentWrapper(parse, "", config);
StructuredQName qname = new StructuredQName("", "", "student_list");
dynamicContext.setParameter(qname, sequence);
Properties props = new Properties();
final SequenceIterator iter = exp.iterator(dynamicContext);
props.setProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
props.setProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
QueryResult.serializeSequence(iter, config, writer, props);
System.out.println("Result is " + writer);
names.xq
declare namespace eg="http://example.com/saxon-extension";
declare namespace xs = "http://www.w3.org/2001/XMLSchema";
declare variable $student_list as element(*) external;
<Students>
<value> {
let $n := eg:shift-left(2, 2)
return $n
}</value>
<student_names>
{ $student_list//student_list/student/name }
</student_names>
</Students>
But getting the below error
Error at procedure student_list on line 3 of students.xml:
XPTY0004: Required item type of value of variable $student_list is element(); supplied
value has item type document-node(element(Q{}student_list))
net.sf.saxon.trans.XPathException: Required item type of value of variable $student_list is element(); supplied value has item type document- node(element(Q{}student_list))
at net.sf.saxon.expr.ItemTypeCheckingFunction.testConformance(ItemTypeCheckingFunction.java:69)
at net.sf.saxon.expr.ItemTypeCheckingFunction.mapItem(ItemTypeCheckingFunction.java:50)
at net.sf.saxon.expr.ItemMappingIterator.next(ItemMappingIterator.java:95)
at net.sf.saxon.expr.CardinalityCheckingIterator.<init>(CardinalityCheckingIterator.java:52)
at net.sf.saxon.type.TypeHierarchy.applyFunctionConversionRules(TypeHierarchy.java:230)
at net.sf.saxon.expr.instruct.GlobalParameterSet.convertParameterValue(GlobalParameterSet.java:105)
at net.sf.saxon.expr.instruct.Bindery.useGlobalParameter(Bindery.java:136)
at net.sf.saxon.expr.instruct.GlobalParam.evaluateVariable(GlobalParam.java:62)
at net.sf.saxon.expr.GlobalVariableReference.evaluateVariable(GlobalVariableReference.java:105)
at net.sf.saxon.expr.VariableReference.evaluateItem(VariableReference.java:460)
at net.sf.saxon.expr.Atomizer.evaluateItem(Atomizer.java:313)
at net.sf.saxon.expr.Atomizer.evaluateItem(Atomizer.java:35)
at net.sf.saxon.expr.AtomicSequenceConverter.evaluateItem(AtomicSequenceConverter.java:275)
at net.sf.saxon.expr.AtomicSequenceConverter.evaluateItem(AtomicSequenceConverter.java:30)
at net.sf.saxon.functions.Doc.doc(Doc.java:235)
at net.sf.saxon.functions.Doc.evaluateItem(Doc.java:190)
at net.sf.saxon.functions.Doc.evaluateItem(Doc.java:28)
at net.sf.saxon.expr.SimpleStepExpression.iterate(SimpleStepExpression.java:85)
at net.sf.saxon.expr.SlashExpression.iterate(SlashExpression.java:842)
at net.sf.saxon.expr.sort.DocumentSorter.iterate(DocumentSorter.java:168)
at net.sf.saxon.expr.SlashExpression.iterate(SlashExpression.java:842)
at net.sf.saxon.expr.sort.DocumentSorter.iterate(DocumentSorter.java:168)
at net.sf.saxon.expr.Expression.process(Expression.java:552)
at net.sf.saxon.expr.instruct.ElementCreator.processLeavingTail(ElementCreator.java:450)
at net.sf.saxon.expr.instruct.ElementCreator.processLeavingTail(ElementCreator.java:389)
at net.sf.saxon.expr.instruct.Block.processLeavingTail(Block.java:669)
at net.sf.saxon.expr.instruct.Instruction.process(Instruction.java:144)
at net.sf.saxon.expr.instruct.ElementCreator.constructElement(ElementCreator.java:539)
at net.sf.saxon.expr.instruct.ElementCreator.evaluateItem(ElementCreator.java:476)
at net.sf.saxon.expr.instruct.Instruction.iterate(Instruction.java:363)
at net.sf.saxon.query.XQueryExpression.iterator(XQueryExpression.java:332)
at com.example.saxon.ExternalMethodCaller.main(ExternalMethodCaller.java:77)
Thanks in advance..
Unless you have a very good reason not to, my advice is to use Snappi (the Saxon 9 API, or s9api):
Processor saxon = new Processor(false);
saxon.registerExtensionFunction(new MyExtension());
XQueryCompiler compiler = saxon.newXQueryCompiler();
XQueryExecutable exec = compiler.compile(new File("input/names.xq"));
XQueryEvaluator query = exec.load();
DocumentBuilder builder = saxon.newDocumentBuilder();
String students = "<xml>...</xml>";
Source src = new StreamSource(new StringReader(students));
XdmNode doc = builder.build(src);
query.setExternalVariable(new QName("student_list"), doc);
XdmValue result = query.evaluate();
With MyExtension looking something like the following:
public class MyExtension
implements ExtensionFunction
{
#Override
public QName getName()
{
return new QName("http://example.org/my-project", "my-fun");
}
#Override
public SequenceType getResultType()
{
return SequenceType.makeSequenceType(
ItemType.INTEGER, OccurrenceIndicator.ONE);
}
#Override
public SequenceType[] getArgumentTypes()
{
return new SequenceType[] {
SequenceType.makeSequenceType(
ItemType.INTEGER, OccurrenceIndicator.ONE),
SequenceType.makeSequenceType(
ItemType.INTEGER, OccurrenceIndicator.ONE)
};
}
#Override
public XdmValue call(XdmValue[] args) throws SaxonApiException
{
long first = ((XdmAtomicValue)args[0].itemAt(0)).getLongValue();
long second = ((XdmAtomicValue)args[0].itemAt(0)).getLongValue();
long result = ...;
return new XdmAtomicValue(result);
}
}
See the documentation at http://www.saxonica.com/documentation9.5/extensibility/integratedfunctions/ext-simple-J.html for details.
EXPath also has a project called tools-saxon, containing several tools for using Saxon in Java. Including extension functions. It introduces the concept of a function library, which is convenient if you have several extension functions. It also introduces a function definition builder, allowing one to build a function definition with as less boiler plate code as possible (and providing convenient shortcuts for type sequences). In the above code, replace the function registering (the first 2 lines) by:
Processor saxon = new Processor(false);
Library lib = new MyLibrary();
lib.register(saxon.getUnderlyingConfiguration());
and replace the extension class with the 2 following classes (a library and a function, resp.):
public class MyLibrary
extends Library
{
public MyLibrary()
{
super("http://example.org/my-project", "my");
}
#Override
protected Function[] functions()
{
return new Function[] {
new MyFunction(this)
};
}
}
public class MyFunction
extends Function
{
public MyFunction(Library lib)
{
super(lib);
}
#Override
protected Definition makeDefinition()
{
return library()
.function(this, "my-fun")
.returns(Types.SINGLE_INTEGER)
.param(Types.SINGLE_INTEGER, "first")
.param(Types.SINGLE_INTEGER, "second")
.make();
}
#Override
public Sequence call(XPathContext ctxt, Sequence[] args)
throws XPathException
{
Parameters params = checkParams(args);
long first = params.asLong(0, true);
long second = params.asLong(1, true);
long result = 0;
return Return.value(result);
}
}
See all informatio on the project home on Github, at https://github.com/expath/tools-saxon.
Note: not tested.

XML Parsing - Getting Nodes only from parent with specific ID

I am building a steam-clone game manager in Java and I am having problems with one final part of the project. On the left hand side of the GUI I have automatically populated "playlists" of games that are parsed from a library XML file, and I would like to retrieve the games from only that playlist when it is clicked on through a ListSelectionListener. I am currently able to populate ALL games stored in the library by using getElementsByTagName("Game"), but I need them to be specific to the playlist which is also assigned a unique id with an attributeID set to true for "Id".
However, in the below code, I need to do something like readLib.getElementById(id).getChildNodes(); but every time I do so I get a nullpointer exception at that line. Any ideas? I feel like I'm super close.
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
String[] gameArray = null;
try {
DocumentBuilder builder = factory.newDocumentBuilder();
Document readLib = builder.parse(LIBRARY_FILE_PATH);
System.out.println("ID:" + id);
NodeList gameNodes = readLib.getElementsByTagName("Game");
gameArray = new String[gameNodes.getLength()];
for (int i = 0; i < gameNodes.getLength(); i++) {
Node p = gameNodes.item(i);
if (p.getNodeType() == Node.ELEMENT_NODE) {
Element Game = (Element) p;
String gameNames = Game.getAttribute("Name");
gameArray[i] = gameNames;
}
}
} catch (ParserConfigurationException e) {
LogToFile.writeFile("[GM-Logging] Parser configuratoin exception when generating game list");
} catch (SAXException e) {
LogToFile.writeFile("[GM-Logging] General SAXException in generateGames() method");
} catch (IOException e) {
LogToFile.writeFile("[GM-Logging] IOException while generating game names in Library Manager engine");
}
This is an example of what the XML library looks like:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Library>
<Playlist Id="0" list="First Person Shooters">
<Game Name="Counter-Strike: Source">
<Path>C:\\Program Files\\Games\\CSS\cstrike.exe</Path>
<Executable>cstrike.exe</Executable>
</Game>
<Game Name="Counter-Strike: Global Offense">
<Path>C:\\Program Files\\Games\\CSGO\csgo.exe</Path>
<Executable>csgo.exe</Executable>
</Game>
<Game Name="Crysis 3">
<Path>C:\\Program Files\\Games\\Crytek\crysislauncher.exe</Path>
<Executable>crysislauncher.exe</Executable>
</Game>
</Playlist>
<Playlist Id="1" list="Grand Theft Auto Series">
<Game Name="Grand Theft Auto V">
<Path>C:\\Program Files\\Games\\Rockstar\gtav.exe</Path>
<Executable>gtav.exe</Executable>
</Game>
<Game Name="Grand Theft Auto IV: Ballad of Gay Tony">
<Path>C:\\Program Files\\Games\\Rockstar\gtaiv\gtaiv.exe</Path>
<Executable>gtaiv.exe</Executable>
</Game>
</Playlist>
<Playlist Id="2" list="Survival and Horror Games"></Playlist>
I'd suggest to look into xpath. That will help you get specific part of an XML especially when you have more complex filter than just id attribute later. I'm not a java guy, the following codes constructed based on this : How to read XML using XPath in Java :
DocumentBuilder builder = factory.newDocumentBuilder();
Document readLib = builder.parse(LIBRARY_FILE_PATH);
XPathFactory xPathfactory = XPathFactory.newInstance();
XPath xpath = xPathfactory.newXPath();
XPathExpression expr = xpath.compile("//Playlist[#id='" + id + "']/Game");
System.out.println("ID:" + id);
NodeList gameNodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
//the rest can be the same code
.......
short explanation about xpath being used :
Above codes generate xpath expression that looks like this :
//Playlist[#id='certain_id']/Game
//Playlist : find <Playlist> elements anywhere in the XML document
[#id='certain_id'] : filter to return only those having id attribute equals "certain_id"
/Game : from each of <Playlist> element that satisfies above criterion, get child element <Game>
Looking at your xml, below is my suggestion.
create a class,
#XmlRootElement(name = "Library")
public class Library {
private List<Playlist> playlist = new ArrayList<Playlist>();
..... getter
#XmlElement(name = "Playlist")
public void setPlaylist(List<Playlist> playlist) {
this.playlist = playlist;
}
#XmlRootElement(name = "Playlist")
public class Playlist {
private Game game;
private String path;
private String executable;
#XmlElement(name = "path")
public void setPath(String path) {
this.path = path;
}
... similarly write getter and setter for game & executable
}
#XmlRootElement(name = "Game")
public class Game {
private String name;
#XmlElement(name = "name")
public void setName(String name) {
this.name = name;
}
... write getter
}
in your main class
JAXBContext jaxbContext = JAXBContext.newInstance(Library.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
String xmlStr = xmlString; // the entire xml as string
InputStream is = new ByteArrayInputStream(xmlStr.getBytes());
Library library = (Library) jaxbUnmarshaller.unmarshal(is);
List<Playlist> playLst = library.getPlayList();
now you have playLst object which is parsed XML

How to generate multiple, slightly different XSD schemas from one Java model with JAXB?

I have a set of related Java classes, which are able to hold data I need. Below is a simplified class diagram of what I have:
Now I need to import data from XML and for that I want to generate XSD schema. The problem is that I want several XSD schemas like this:
One that allows the whole data graph to be imported.
One that allows only RootNote.fieldA and ChildNodeA.
One that allows only RootNote.fieldB and ChildNodeB.
I can easily generate XSD that meets the requirements of nr.1 using JAXB (programmatically). But is there a way to do that for cases nr.2 and nr.3 for the same classes? In other words, it seems I need something like "profiles" in JAXB.
Update:
Here is how I generate XSD schema:
JAXBContext jc = JAXBContext.newInstance(RootNode.class);
final File baseDir = new File(".");
class MySchemaOutputResolver extends SchemaOutputResolver {
public Result createOutput( String namespaceUri, String suggestedFileName ) throws IOException {
return new StreamResult(new File(baseDir,suggestedFileName));
}
}
jc.generateSchema(new MySchemaOutputResolver());
This is not a full answer, just an idea.
You probably use the javax.xml.bind.JAXBContext.generateSchema(SchemaOutputResolver) method to generate your schema, so you basically use a specific JAXBContext instance. This instance is built based on the annotations in classes. When building the context, these annotations are read an organized into a model which is then used for all the operations.
So to generate different schemas you probably need to create different contexts. You can't change the annotations per case, but you can read annotations in different ways.
Take a look at the AnnotationReader. This is what JAXB RI uses behind the scenes to load annotations from Java classes. You can create your own implementation and use it when creating the JAXBContext. Here's an example of something similar:
final AnnotationReader<Type, Class, Field, Method> annotationReader = new AnnoxAnnotationReader();
final Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBRIContext.ANNOTATION_READER, annotationReader);
final JAXBContext context = JAXBContext.newInstance(
"org.jvnet.annox.samples.po",
Thread.currentThread().getContextClassLoader(),
properties);
So how about writing your own annotation reader, which would consider what you call "profiles"? You can invent your own annotation #XmlSchemaProfile(name="foo"). Your annotation reader would then check if this annotation is present with the desired value and then either return it or ignore it. You'll be able to build different contexts from the same Java model - and consequently produce different schemas according to profiles defined by your #XmlSchemaProfile annotations.
I found a solution that suited me. The idea is to output the result of XSD generation into an XML Document (in-memory DOM). JAXB allows that. After this, you can manipulate the document any way you wish, adding or removing parts.
I wrote some filters that whitelist or blacklist fields (in XSD they are elements) and classes (in XSD they are complex types). While I see a lot of potential problems with this approach, it did the job in my case. Below is the code for case 2 schema:
// This SchemaOutputResolver implementation saves XSD into DOM
static class DOMResultSchemaOutputResolver extends SchemaOutputResolver {
private List<DOMResult> results = new LinkedList<DOMResult>();
#Override
public Result createOutput(String ns, String file) throws IOException {
DOMResult result = new DOMResult();
result.setSystemId(file);
results.add(result);
return result;
}
public Document getDocument() {
return (Document)results.get(0).getNode();
}
public String getFilename() {
return results.get(0).getSystemId();
}
}
// This method serializes the DOM into file
protected void serializeXsdToFile(Document xsdDocument, String filename) throws IOException {
OutputFormat format = new OutputFormat(xsdDocument);
format.setIndenting(true);
FileOutputStream os = new FileOutputStream(filename);
XMLSerializer serializer = new XMLSerializer(os, format);
serializer.serialize(xsdDocument);
}
#Test
public void generateSchema2() throws JAXBException, IOException, XPathExpressionException {
JAXBContext context = JAXBContext.newInstance(RootNode.class);
DOMResultSchemaOutputResolver schemaOutputResolver = new DOMResultSchemaOutputResolver();
context.generateSchema(schemaOutputResolver);
// Do your manipulations here as you want. Below is just an example!
filterXsdDocumentComplexTypes(schemaOutputResolver.getDocument(), asList("childNodeA"), true);
filterXsdDocumentElements(schemaOutputResolver.getDocument(), asList("fieldB"));
serializeXsdToFile(schemaOutputResolver.getDocument(), "xf.xsd");
}
private boolean shouldComplexTypeBeDeleted(String complexTypeName, List<String> complexTypes, boolean whitelist) {
return (whitelist && !complexTypes.contains(complexTypeName)) || (!whitelist && complexTypes.contains(complexTypeName));
}
protected void filterXsdDocumentComplexTypes(Document xsdDocument, List<String> complexTypes, boolean whitelist) throws XPathExpressionException {
XPath xPath = XPathFactory.newInstance().newXPath();
NodeList complexTypeNodes = (NodeList)xPath.evaluate("//*[local-name() = 'complexType']", xsdDocument, XPathConstants.NODESET);
for (int i = 0; i < complexTypeNodes.getLength(); i++) {
Node node = complexTypeNodes.item(i);
Node complexTypeNameNode = node.getAttributes().getNamedItem("name");
if (complexTypeNameNode != null) {
if (shouldComplexTypeBeDeleted(complexTypeNameNode.getNodeValue(), complexTypes, whitelist)) {
node.getParentNode().removeChild(node);
}
}
}
NodeList elements = (NodeList)xPath.evaluate("//*[local-name() = 'element']", xsdDocument, XPathConstants.NODESET);
for (int i = 0; i < elements.getLength(); i++) {
Node node = elements.item(i);
Node typeNameNode = node.getAttributes().getNamedItem("type");
if (typeNameNode != null) {
if (shouldComplexTypeBeDeleted(typeNameNode.getNodeValue(), complexTypes, whitelist) && !typeNameNode.getNodeValue().startsWith("xs")) {
node.getParentNode().removeChild(node);
}
}
}
}
protected void filterXsdDocumentElements(Document xsdDocument, List<String> blacklistedElements) throws XPathExpressionException {
XPath xPath = XPathFactory.newInstance().newXPath();
NodeList elements = (NodeList)xPath.evaluate("//*[local-name() = 'element']", xsdDocument, XPathConstants.NODESET);
for (int i = 0; i < elements.getLength(); i++) {
Node node = elements.item(i);
if (blacklistedElements.contains(node.getAttributes().getNamedItem("name").getNodeValue())) {
node.getParentNode().removeChild(node);
}
}
}

Categories

Resources