codeCompiler77
codeCompiler77

Reputation: 526

Java XML XSLT to generate HTML Report

EDIT Now i know why people use string builder, using JAXB is a pain and very time consuming, at least for me it was because i have a very complex XML to generate. Appreciate the help :)

I have a java application which collects user data.

I also created a template XSL. Now i am stuck. What is my next step? Should i use the java to code to create an XML? If so, what kind of XML am i creating, i need my HTML to be strict at the end. Sorry if this is a duplicate, i couldn't find anything and i'm completely new to XML. So one more question is, would this be valid syntax for an xml:

StringBuilder sb = new StringBuilder(500);
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
sb.append("<report version=\"").append(reportVersion).append("\" generated=\"").append(scanVersion).append("\">\r\n");
sb.append("</report >");

The output would be

<?xml version="1.0" encoding="UTF-8"?>
<report version="alpha" generated="1">
</report >

How do i use java now to get the version and generated?

Also i understand there is xslt 1.0 and 2.0, which one should i pick?

EDIT:

I was playing around with JAXB and got the following two classes made.

import java.util.ArrayList;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Report
{
    String              title;
    String              reportBy;
    String              reportFor;
    String              scanDate;
    String              scanVersion;
    String              reportDate;
    String              reportVersion;
    String              desc;
    ArrayList<String>   alertSeverity;
    ArrayList<String>   alertDetails;

    public String getTitle()
    {
        return title;
    }

    @XmlElement
    public void setTitle(String title)
    {
        this.title = title;
    }

    public String getBy()
    {
        return reportBy;
    }

    @XmlElement
    public void setBy(String reportBy)
    {
        this.reportBy = reportBy;
    }

    public String getFor()
    {
        return reportFor;
    }

    @XmlElement
    public void setFor(String reportFor)
    {
        this.reportFor = reportFor;
    }

    public String getScanDate()
    {
        return scanDate;
    }

    @XmlElement
    public void setScanDate(String scanDate)
    {
        this.scanDate = scanDate;
    }

    public String getScanVersion()
    {
        return scanVersion;
    }

    @XmlElement
    public void setScanVersion(String scanVersion)
    {
        this.scanVersion = scanVersion;
    }

    public String getReportDate()
    {
        return reportDate;
    }

    @XmlElement
    public void setReportDate(String reportDate)
    {
        this.reportDate = reportDate;
    }

    public String getReportVersion()
    {
        return reportVersion;
    }

    @XmlElement
    public void setReportVersion(String reportVersion)
    {
        this.reportVersion = reportVersion;
    }


    public String getDesc()
    {
        return reportVersion;
    }

    @XmlElement
    public void setDesc(String desc)
    {
        this.desc = desc;
    }
}

and the main:

import java.io.File;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

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

public class JAXBExample {
    public static void main(String[] args) {
        DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        Date date = new Date();

        Report report = new Report();
        report.setTitle("my custom title");
        report.setBy("hans");
        report.setFor("pepe");
        report.setScanDate(dateFormat.format(date));
        report.setScanVersion("orignal version");
        report.setReportDate(dateFormat.format(date));
        report.setReportVersion("version is alpha");
        report.setDesc("some random desc");

      try {

        File file = new File("C:\\Users\\testuser\\Desktop\\file.xml");


        JAXBContext jaxbContext = JAXBContext.newInstance(Report.class);
        Marshaller jaxbMarshaller = jaxbContext.createMarshaller();

        // output pretty printed
        jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

        jaxbMarshaller.marshal(report, file);
        jaxbMarshaller.marshal(report, System.out);

          } catch (JAXBException e) {
        e.printStackTrace();
          }

    }
}

I just have no idea what to do with my ArrayList which i need to loop to add elements in, etc.

The current output is:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<report>
    <by>hans</by>
    <desc>version is alpha</desc>
    <for>pepe</for>
    <reportDate>2016/02/28 18:36:33</reportDate>
    <reportVersion>version is alpha</reportVersion>
    <scanDate>2016/02/28 18:36:33</scanDate>
    <scanVersion>orignal version</scanVersion>
    <title>my custom title</title>
</report>

Now i have two arrays that are supposed to go into this report, not sure what to do about them. Not only that but if the user did not pick the item to go in, then it's not supposed to. SO the xml won't always be the same, one attribute might not be there and sometimes it might be. Does that make sense?

EDIT 2: What i need is something like this

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<report>
    <by>hans</by>
    <desc>version is alpha</desc>
    <for>pepe</for>
    <reportDate>2016/02/28 18:36:33</reportDate>
    <reportVersion>version is alpha</reportVersion>
    <scanDate>2016/02/28 18:36:33</scanDate>
    <scanVersion>orignal version</scanVersion>
    <title>my custom title</title>
    <site host="1" name="1" port="80" ssl="true">
        <items>
            <item>
                <name>item 1</name>
                <id>1</id>
            </item>
            <item>
                <name>item 2</name>
                <id>1</id>
            </item>
        </items>
    </site>
    <site host="2" name="2" port="80" ssl="true">
        <items>
            <item>
                <name>item 1</name>
                <id>1</id>
            </item>
            <item>
                <name>item 2</name>
                <id>1</id>
            </item>
        </items>
    </site>
</report>

All of this is contained in an ArrayList How do i go about using JAXB to create this xml? Any input would be appreciated :)

Upvotes: 0

Views: 1336

Answers (1)

Spotted
Spotted

Reputation: 4091

Step 1

Create an XSD that represents the structure you want in the XML. If you're not familiar with XSD, I encourage you to read this tutorial.

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

    <xs:element name="report">
        <xs:complexType>
            <xs:sequence>
                <xs:element minOccurs="0" maxOccurs="1" name="by" type="xs:string" />
                <xs:element minOccurs="0" maxOccurs="1" name="desc" type="xs:string" />
                <xs:element minOccurs="0" maxOccurs="1" name="for" type="xs:string" />
                <xs:element minOccurs="0" maxOccurs="1" name="reportDate"
                    type="xs:dateTime" />
                <xs:element minOccurs="0" maxOccurs="1" name="reportVersion"
                    type="xs:string" />
                <xs:element minOccurs="0" maxOccurs="1" name="scanDate"
                    type="xs:dateTime" />
                <xs:element minOccurs="0" maxOccurs="1" name="scanVersion"
                    type="xs:string" />
                <xs:element minOccurs="0" maxOccurs="1" name="title"
                    type="xs:string" />
                <xs:element minOccurs="0" maxOccurs="unbounded" ref="site" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:element name="site">
        <xs:complexType>
            <xs:sequence>
                <xs:element minOccurs="1" maxOccurs="1" ref="items" />
            </xs:sequence>
            <xs:attribute use="required" name="host" type="xs:string" />
            <xs:attribute use="required" name="name" type="xs:string" />
            <xs:attribute use="required" name="port" type="xs:string" />
            <xs:attribute use="required" name="ssl" type="xs:string" />
        </xs:complexType>
    </xs:element>

    <xs:element name="items">
        <xs:complexType>
            <xs:sequence>
                <xs:element minOccurs="0" maxOccurs="unbounded" ref="item" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:element name="item">
        <xs:complexType>
            <xs:sequence>
                <xs:element minOccurs="1" maxOccurs="1" name="name" type="xs:string" />
                <xs:element minOccurs="1" maxOccurs="1" name="id" type="xs:string" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>

</xs:schema>

Note 1: the type xs:dateTime allows only datetimes formatted in ISO 8601. Source W3C. If you really want a custom datetimes format, you will have to write your own pattern based on strings (and therefore loosing the full power of dates validation).

Note 2: as you noticed, xs:element have a minOccurs and maxOccurs that can be tweaked if you know an element is mandatory or not.

Step 2

You can use the tool XJC (bundled with Java) to generate automatically the corresponding Java classes. These classes will be directly usable by JAXB to serialize data into the desired XML format.

The following command should do the trick:

/path/to/jdk/bin/xjc -d /path/to/output/folder -p your.package.name /path/to/report.xsd

The following classes should be generated (sample)

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "name",
    "id"
})
@XmlRootElement(name = "item")
public class Item {

    @XmlElement(required = true)
    protected String name;
    @XmlElement(required = true)
    protected String id;

    public String getName() {
        return name;
    }

    public void setName(String value) {
        this.name = value;
    }

    public String getId() {
        return id;
    }

    public void setId(String value) {
        this.id = value;
    }
}

Note: the generated classes are simple POJO that I don't encourage to directly work with (only JAXB should work with). If you already have a Report class that is structured according to your business, you could create a few XmlAdapter to map between business objects and JAXB-POJO.

Step 3

Fill these objects with report's data:

Item i1 = new Item();
i1.setName("item 1");
i1.setId("1");

Item i2 = new Item();
i2.setName("item 2");
i2.setId("2");

Items items = new Items();
items.getItem().addAll(Arrays.asList(i1, i2));

Site s1 = new Site();
s1.setHost("1");
s1.setName("1");
s1.setPort("80");
s1.setSsl("true");
s1.setItems(items);

Site s2 = new Site();
s2.setHost("2");
s2.setName("2");
s2.setPort("80");
s2.setSsl("true");
s2.setItems(items);

Report r = new Report();
r.setBy("hans");
r.setDesc("version is alpha");
r.setFor("pepe");
r.setReportDate(DatatypeFactory.newInstance().newXMLGregorianCalendar("2016-02-28T18:36:33"));
r.setReportVersion("version is alpha");
r.setScanDate(DatatypeFactory.newInstance().newXMLGregorianCalendar("2016-02-28T18:36:33"));
r.setScanVersion("original version");
r.setTitle("my custom title");
r.getSite().addAll(Arrays.asList(s1, s2));

Note: if you have xmlAdapters, this step becomes less tedious.

Step 4

Save them in XML

JAXBContext jaxbContext = JAXBContext.newInstance(Report.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.marshal(r, new File("report.xml"));

Note: report.xml will be exactly in the format you need.

Step 5

Transform the XML in HTML

StreamResult result = new StreamResult(new File("output.html"));
StreamSource source = new StreamSource(new File("report.xml"));
StreamSource xslt = new StreamSource(new File("transform.xslt"));

Transformer transformer = TransformerFactory.newInstance().newTransformer(xslt);

transformer.transform(source, result);

Upvotes: 1

Related Questions