convert xml to objects java - java

I would appreciate your help, how can I match such xmls data in java with different tags, for example can come: <t: UserAttrs>, <ns3: UserAttrs>, <ns37: UserAttrs>
values can be different between "<" and "UserAttrs>"
I need to extract everything that is in the tag
<Rtp xmlns="http://schemas.tranzaxis.com/tran.xsd">
<Tran>
<t:Request InitiatorRid="ECACS2oob" Kind="TProcessA" LifePhase="Single" NetworkRid="V"
OrigTime="2022-07-06T08:04:00" ProcessorRid="Name" ProcessorInstId="1" OriginatorInterfaceId="1308"
xmlns=""
xmlns:ct=""
xmlns:t=""
xmlns:tc="">
<t:Parties>
<t:Term AcquirerRid="4005"><t:Caps Interactive="true"/><t:Owner Country="0" Rid="test" Title="test" Url="https://xxx" Mcc="3000"/>
</t:Term>
</t:Parties>
<t:UserAttrs>
<ct:ParamValue Rid="Reason"> <ct:Val>Authe</ct:Val>
</ct:ParamValue>
<ct:ParamValue Rid="EndTime"> <ct:Val>2022-07-06T15:04:00.462</ct:Val>
</ct:ParamValue>
</t:UserAttrs>
</t:Request>
</Tran>

You can use javax.xml.bind to unmarshall it and ignore tag namespace. Assume UserAttrs is your object class:
class UserAttrs {
#XmlElement(name = "ParamValue")
private List<ParamValue> paramValue;
}
class ParamValue {
#XmlAttribute(name = "Rid")
private String rId;
}
...
Use unmashaller:
var ctxUserAttrs = JAXBContext.newInstance(UserAttrs.class);
var inputFactory = XMLInputFactory.newInstance();
var reader = inputFactory.createXMLStreamReader(
new ByteArrayInputStream(yourXml.getBytes(StandardCharsets.UTF_8)));
// use reader.hasNext(), reader.next(), reader.nextTag() to navigate to your UserAttrs tag
var unmarshaller = ctxUserAttrs.createUnmarshaller();
var userAttrs = unmarshaller.unmarshal(reader, UserAttrs.class).getValue();
Code is untested, edit to fit your situation. I'm using akarta.xml.bind-api 2.3.3.

Related

Java - Generating XML data dynamically via Input records

I have to generate the following format of XML using java:
<document>
<creditDetails>
<accountNo>123456</accountNo>
</creditDetails>
<creditDetails>
<accountNo>123123</accountNo>
</creditDetails>
.
.
.
...
</document>
Now, I have created the above provided XML using the Document.appendChild and relevant XML code.
When I have to create a new block of Credit Details, I have to create a new XML component which is a very bad practice.
I have lets say 15 records and I have iterated those records one by one to create a single xml format for all of them as provided in my question.
Can someone guide me? How I can achieve this?
The correct way would be to use JAXB (Java Architecture for XML Binding).
Then you should have the following data classes:
#XmlRootElement(name = "document")
public class Document {
private List<CreditDetail> creditDetails = new ArrayList<>();
public List<CreditDetail> getCreditDetails() {
return creditDetails;
}
}
public class CreditDetail {
private int accountNo;
public int getAccountNo() {
return accountNo;
}
public void setAccountNo(int accountNo) {
this.accountNo = accountNo;
}
}
Now you can just create your account data in Java and the Marshaller will do the work for you:
Document doc = new Document();
CreditDetail creditDetail1 = new CreditDetail();
creditDetail1.setAccountNo(123456);
doc.getCreditDetails().add(creditDetail1);
CreditDetail creditDetail2 = new CreditDetail();
creditDetail2.setAccountNo(123123);
doc.getCreditDetails().add(creditDetail2);
// could make your classes a bit more pretty, e.g. Factory methods or constructor
JAXBContext context = JAXBContext.newInstance(Document.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter writer = new StringWriter();
marshaller.marhaller(document, writer);
writer.toString() // gives your XML
Unfortunately JAXB is not included in the JDK since Java 11 so you have to add it in your dependency management.

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();

Read XSD using org.eclipse.xsd.util.XSDResourceImpl

I successfully read an XSD schema using org.eclipse.xsd.util.XSDResourceImpl and process all contained XSD elements, types, attributes etc.
But when I want to process a reference to an element declared in the imported schema, I get null as its type. It seems the imported schemas are not processed by XSDResourceImpl.
Any idea?
final XSDResourceImpl rsrc = new XSDResourceImpl(URI.createFileURI(xsdFileWithPath));
rsrc.load(new HashMap());
final XSDSchema schema = rsrc.getSchema();
...
if (elem.isElementDeclarationReference()){ //element ref
elem = elem.getResolvedElementDeclaration();
}
XSDTypeDefinition tdef = elem.getType(); //null for element ref
Update:
I made the imported XSD invalid, but get no exception. It means it is really not parsed. Is there any way to force loading imported XSD together with the main one?
There is one important trick to process imports and includes automatically. You have to use a ResourceSet to read the main XSD file.
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.xsd.util.XSDResourceFactoryImpl;
import org.eclipse.xsd.util.XSDResourceImpl;
import org.eclipse.xsd.XSDSchema;
static ResourceSet resourceSet;
XSDResourceFactoryImpl rf = new XSDResourceFactoryImpl();
Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("xsd", rf);
resourceSet = new ResourceSetImpl();
resourceSet.getLoadOptions().put(XSDResourceImpl.XSD_TRACK_LOCATION, Boolean.TRUE);
XSDResourceImpl rsrc = (XSDResourceImpl)(resourceSet.getResource(uri, true));
XSDSchema sch = rsrc.getSchema();
Then before processing an element, an attribute or a model group you have to use this:
elem = elem.getResolvedElementDeclaration();
attr = attr.getResolvedAttributeDeclaration();
grpdef = grpdef.getResolvedModelGroupDefinition();
Could you try something like that, manually resolve type :
final XSDResourceImpl rsrc = new XSDResourceImpl(URI.createFileURI(xsdFileWithPath));
rsrc.load(new HashMap());
final XSDSchema schema = rsrc.getSchema();
for (Object content : schema.getContents())
{
if (content instanceof XSDImport)
{
XSDImport xsdImport = (XSDImport) content;
xsdImport.resolveTypeDefinition(xsdImport.getNamespace(), "");
}
}
You may have a look here. Particulary in this method :
private static void forceImport(XSDSchemaImpl schema) {
if (schema != null) {
for (XSDSchemaContent content: schema.getContents()) {
if (content instanceof XSDImportImpl) {
XSDImportImpl importDirective = (XSDImportImpl)content;
schema.resolveSchema(importDirective.getNamespace());
}
}
}
}

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