I am working on a android(2.2) project which needs xsl transformation. The below code works perfectly in a regular non-android java project
public static String transform() throws TransformerException {
Source xmlInput = new StreamSource(new File("samplexml.xml"));
Source xslInput = new StreamSource(new File("samplexslt.xslt"));
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer(xslInput);
OutputStream baos = new ByteArrayOutputStream();
Result result = new StreamResult(baos);
transformer.transform(xmlInput, result);
return baos.toString();
}
I need similar functionality on android. For this I created 2 files under resources/raw:
samplexml.xml
samplexslt.xslt
(contents of these files come from here.
I tried the below code & it does not work (note the StreamSource constructor arg):
public static String transform() throws TransformerException {
TransformerFactory factory = TransformerFactory.newInstance();
Source xmlInput = new StreamSource(this.getResources().openRawResource(R.raw.samplexml));
Source xslInput = new StreamSource(this.getResources().openRawResource(R.raw.samplexslt));
Transformer transformer = factory.newTransformer(xslInput);//NullPointerException here
OutputStream baos = new ByteArrayOutputStream();
Result result = new StreamResult(baos);
transformer.transform(xmlInput, result);
}
I saw the spec & believe I need to set a systemId. But I couldn't get the above code to work.
So, in an android project, how to handle xslt transformations? Please provide your thoughts.
As we know that we Cannot usethisin a static context and you are doing this in your static method transform(). You can do it like this_
public class YourLoadXSLClass extends Activity {
static Resources res;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
res = getResources();
String strHTML = transform();
// Other code.....
}
/*
* Your method that Transform CSLT.
*/
public static String transform() throws TransformerException {
TransformerFactory factory = TransformerFactory.newInstance();
// Now your raw files are accessible here.
Source xmlInput = new StreamSource(
LoadXSLTinWebview.res.openRawResource(R.raw.samplexml));
Source xslInput = new StreamSource(
LoadXSLTinWebview.res.openRawResource(R.raw.samplexslt));
Transformer transformer = factory.newTransformer(xslInput);
OutputStream baos = new ByteArrayOutputStream();
Result result = new StreamResult(baos);
transformer.transform(xmlInput, result);
return baos.toString();
}
}
Here is the complete class code that do the needful. I hope this will help you & all!
I've never done anything with XSLT but, looking at your code, logically there are only two things that could cause an NPE on that line. The first would be that factory might be null but that doesn't make sense.
That leaves xslInput as being the culprit which suggests openRawResource(R.raw.samplexslt) is failing to return a valid InputStream for the StreamSource constructor to use. Try putting a log statement in such as...
if (xslInput != null {
Transformer transformer = factory.newTransformer(xslInput);
...
}
else
Log.d("SomeTAG", "xslInput is null!!!");
If it turns out that xslInput is actually null then it suggests openRawResource(...) can't find/process the .xslt file properly. In that case I'd suggest using AssetManagerto open the .xslt file by name...
AssetManager am = this.getAssets();
Source xslInput = new StreamSource(am.open("samplexslt.xslt"));
Related
Apologies in advance - I know this has been asked a thousand times but I've looked through so many articles/documentation and I'm just so f****** lost.
I have a class that takes in an XML file and then uses DocumentBuilder to parse it into a new file that will be used as a source for other classes to use.
I need to test my method (which is void). My project is completed but I need to test.
If anyone could be so kind to show me how this would be done, I can go ahead and follow that same logic with my other classes, as 90% of the methods in my project do not return anything.
Thanks...
public class XmlToCsv {
public static void xmlToCsv(String sourceXlsFile, String sourceCsvFile, String sourceXmlFile) throws Exception {
//define the files
File stylesheet = new File(sourceXlsFile);
File xmlSource = new File(sourceXmlFile);
//create the DocumentBuilder to parse the XML file
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(xmlSource);
//input the stylesheet to transform the XML to
StreamSource stylesource = new StreamSource(stylesheet);
Transformer transformer = TransformerFactory.newInstance().newTransformer(stylesource);
//write a new output file using the stylesheet format
Source source = new DOMSource(document);
Result outputTarget = new StreamResult(new File(sourceCsvFile));
transformer.transform(source, outputTarget);
}
}
What you trying to do is actually not the way to do it. You should test ONLY the XmlToCsv class and not the classes that are used by this class (DocumentBuilderFactory, DocumentBuilder, Document, StreamSource, Transformer, Source, Result).
There are now 2 ways you can go: The clean code way, or the dirty test way.
The best solution is that you have a dependency framework for the classes you use:
public class XmlToCsv {
#Inject
DocumentBuilderFactory factory;
#Inject
StreamSource stylesource;
#Inject
TransformerFactory transformerFactory;
public void xmlToCsv(String sourceXlsFile, String sourceCsvFile, String sourceXmlFile) throws Exception {
//define the files
File stylesheet = new File(sourceXlsFile);
File xmlSource = new File(sourceXmlFile);
//create the DocumentBuilder to parse the XML file
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(xmlSource);
//input the stylesheet to transform the XML to
StreamSource stylesource = new StreamSource(stylesheet);
Transformer transformer = transformerFactory.newInstance().newTransformer(stylesource);
//write a new output file using the stylesheet format
Source source = new DOMSource(document);
Result outputTarget = new StreamResult(new File(sourceCsvFile));
transformer.transform(source, outputTarget);
}
}
Testing now can be done by injecting mocks into the injectable fields:
#RunWith(MockitoJUnitRunner.class)
public class XmlToCsvTest {
#Mock
DocumentBuilderFactory factory;
#Mock
StreamSource style source;
#Mock
TransformerFactory transformerFactory;
#InjectMocks
XmlToCsv sut; // System Under Test
#Test
public void testOk() throws Exception {
// Mocks
DocumentBuilder documentBuilder = Mockito.mock(DocumentBuilder.class);
Document document = Mockito.mock(Document.class);
// Now you control all objects created in the class and you can test if the right methods are called
// when-clauses
Mockito.when(factory.newDocumentBuilder).thenReturn(documentBuilder);
Mockito.when(documentBuilder.parse(any(File.class)).thenReturn(document);
// Add all when's here
// now call the class
sut.xmlToCsv("", "", "");
// now verify all calls
verify(factory, times(1)).newDocumentBuilder();
verify(documentBuilder, times(1)).parse(any(File.class));
// etc.
}
}
The dirty way is using PowerMockito. With PowerMockito you can override the new methods of existing classes. It is really a last resort and I wouldn't recommend it, but you can use it when you can't change the source code. It will look something like this:
#RunWith(PowerMockRunner.class)
#PrepareForTest({XmlToCsv.class, DocumentBuilderFactory.class})
public class XmlToCsvTest {
XmlToCsv sut;
#Test
public void testXmlToCsv() throws Exception {
DocumentBuilder documentBuilder = Mockito.mock(DocumentBuilder.class);
Document document = Mockito.mock(Document.class);
//when phase
PowerMockito.mockStatic(DocumentBuilderFactory.newInstance).thenReturn(documentBuilder);
Mockito.when(factory.newDocumentBuilder).thenReturn(documentBuilder);
Mockito.when(documentBuilder.parse(any(File.class)).thenReturn(document);
// now call the class
sut.xmlToCsv("", "", "");
//now verify
verify(documentBuilder, times(1)).parse(any(File.class));
}
}
As you see the examples aren't complete, but you get the difference.
It looks like the way you'd want to test this method is to validate the expected contents of the file written to the sourceCsvFile argument, which you could do by reading in the contents after your method is called. I don't think you need to do anything with Mockito - all of your arguments are String objects, and so there's no need to create any mocks.
To test a code generator, this is the best approach I found:
Prepare a set of test cases with the same XSL: For each one, an XML input file and an expected CSV output file. Put the input files into a directory, and the expected ones into another one, but set the same names for each pair of files (case1.xml and case1.csv).
Code a JUnit class with a private method which should do the test and the comparison, and then add one #Test method for each case you want to test:
import java.io.File;
import org.apache.commons.io.FileUtils;
public class XmlToCsvTest
{
private final File inputDir=new File("my_input_xml_files");
private final File expectedDir=new File("my_expected_csv_files");
private final File generatedDir=new File("my_generated_files"); // This is just a working dir
private void xmlToCsv(String xslFile, String inputFileName)
{
try
{
File inputXmlFile=new File(this.inputDir, inputFileName + ".xml");
File outputCsvFile=new File(this.generatedDir, inputFileName + ".csv");
File expectedCsvFile=new File(this.expectedDir, inputFileName + ".csv");
xmlToCsv(xslFile, outputCsvFile.getAbsolutePath(), inputXmlFile.getAbsolutePath());
FileUtils.contentEquals(expectedCsvFile, outputCsvFile);
}
catch (Exception e)
{
fail(e.toString());
}
}
#Test
public void xmlToCsvWithCase1()
{
xmlToCsv("myFirst.xsl", "case1");
}
#Test
public void xmlToCsvWithEmptyFile()
{
xmlToCsv("myFirst.xsl", "empty");
}
#Test
public void xmlToCsvWithOneRow()
{
xmlToCsv("myFirst.xsl", "one-row");
}
...
}
Once you have mastered this technique, you can add more complexity to your tests, by adding other XSLs, with its own testing cases.
Don't forget to add the set of files to your project as resources, to become part of the source control system.
Note: This approach assumes that each output file depends only on the contents of the input file. If the generator adds some independent content (as the current date, current user, or so) a previous pre-processing must be done.
So i am trying to replace the values of username and password in the XML file containing a SOAP message. Here are the elements:
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<o:UsernameToken u:Id="uuid-68f84594-d592-470b-9bbc-b29f58b4756f-1">
<o:Username></o:Username>
<o:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText"></o:Password>
</o:UsernameToken>
</o:Security>
Basically, i want to take the username and password values from my config file, and place them in the username and password fields within the XML file containing the soap message. This is my attempt, and it throws a NPE at the docElement.getElementsByTagName lines:
public void updateUserDetails() {
final Properties configProperties = new Properties();
try {
configProperties.load(new FileInputStream(PROPERTIES));
final Document requestDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new FileInputStream(SOAP_REQUEST));
final Element docElement = requestDoc.getDocumentElement();
docElement.getElementsByTagName("Username").item(0).setTextContent(configProperties.getProperty("username"));
docElement.getElementsByTagName("Password").item(0).setTextContent(configProperties.getProperty("password"));
} catch(IOException | ParserConfigurationException | SAXException exception) {
LOGGER.error("There was an error loading the properties file", exception);
}
}
Any help will be appreciated!
It seems to be related to a namespace problem.
Try specifying the namespace for your tag:
String namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
final Document requestDoc = factory.newDocumentBuilder().parse(new FileInputStream(SOAP_REQUEST));
docElement.getElementsByTagNameNS(namespace, "Username").item(0).setTextContent(configProperties.getProperty("username"));
docElement.getElementsByTagNameNS(namespace, "Password").item(0).setTextContent(configProperties.getProperty("password"));
Also, don't forget in the end to write the result back to file:
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
DOMSource domSource = new DOMSource(requestDoc);
StreamResult streamResult = new StreamResult(new File(SOAP_REQUEST));
transformer.transform(domSource, streamResult);
In a xsl transformation I have a xslt file that includes some other xslt. The problem is that the URI for these xslt contains illegal characters, in particular '##'. The xslt looks like this:
<xsl:include href="/appdm/tomcat/webapps/sentys##1.0.0/WEB-INF/classes/xslt/release_java/xslt/gen.xslt" />
and when I try to instantiate a java Transformer I get the error:
javax.xml.transform.TransformerConfigurationException: javax.xml.transform.TransformerConfigurationException: javax.xml.transform.TransformerException: org.xml.sax.SAXException: org.apache.xml.utils.URI$MalformedURIException: Fragment contains invalid character:#
This is the java code:
public String xslTransform2String(String sXml, String sXslt) throws Exception {
String sResult = null;
try {
Source oStrSource = createStringSource(sXml);
DocumentBuilderFactory oDocFactory = DocumentBuilderFactory.newInstance();
oDocFactory.setNamespaceAware(true);
//sXslt is the xslt content with the inclusions
//<xsl:include href="/appdm/tomcat/webapps/sentys##1.0.0/WEB-INF/classes/xslt/release_java/xslt/gen.xslt" />"
Document oDocXslt = oDocFactory.newDocumentBuilder().parse(new InputSource(new StringReader(sXslt)));
Source oXsltSource = new DOMSource(oDocXslt);
StringWriter oStrOut = new StringWriter();
Result oTransRes = createStringResult(oStrOut);
Transformer oTrans = createXsltTransformer(oXsltSource);
oTrans.transform(oStrSource, oTransRes);
sResult = oStrOut.toString();
} catch (Exception oEx) {
throw new BddException(oEx, XmlProvider.ERR_XSLT, null);
}
return sResult;
}
private Transformer createXsltTransformer(Source oXsltSource) throws Exception {
Transformer transformer = getXsltTransformerFactory().newTransformer(
oXsltSource);
ErrorListener errorListener = new DefaultErrorListener();
transformer.setErrorListener(errorListener);
return transformer;
}
is there a way I can go with relative paths instead of absolute path?
Thank you
To avoid the MalformedURIException, replace the second or both # with %23.
See https://stackoverflow.com/a/5007362/4092205
I have the folowwing XSLT based on Xalan:
TransformerFactory factory = TransformerFactory.newInstance();
XalanErrorListener listener = new XalanErrorListener();
factory.setErrorListener(listener);
// Create transformer
StreamSource config = new StreamSource(xslPath);
Transformer transformer = factory.newTransformer(config);
// Create input / ouput
StreamSource source = new StreamSource(inputPath);
StreamResult result = new StreamResult(outputPath);
// Transform
transformer.transform(source, result);
My XalanErrorListener simply overrides error, fatalError and warning methods from the javax.xml.transform.ErrorListener class and logs the exception:
public final class XalanErrorListener implements ErrorListener {
static final Logger LOGGER = LoggerFactory.getLogger(XalanErrorListener.class);
#Override
public void error(TransformerException exception) throws TransformerException {
LOGGER.error(exception);
}
#Override
public void fatalError(TransformerException exception) throws TransformerException {
LOGGER.error(exception);
}
#Override
public void warning(TransformerException exception) throws TransformerException {
LOGGER.warn(exception);
}
}
Yet, when executing on a badly encoded file, I get the following message in the console:
(Location of error unknown)
com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException:
Invalid byte 2 of 2-byte UTF-8 sequence.
The program executes normally: no exception is thrown or logged and the generated file is empty!
How can I catch the exception to handle it the way I want?
The ErrorListener you supply to Xalan catches transformation errors, but it does not catch XML parsing errors. For that you need to supply an ErrorHandler to the Xerces parser.
The problem came from the fact that the ErrorListener needed to be set to the Transformer and not the TransformerFactory:
Transformer transformer = factory.newTransformer(config);
transformer.setErrorListener(listener);
I am using jaxb for my application configurations
I feel like I am doing something really crooked and I am looking for a way to not need an actual file or this transaction.
As you can see in code I:
1.create a schema into a file from my JaxbContext (from my class annotation actually)
2.set this schema file in order to allow true validation when I unmarshal
JAXBContext context = JAXBContext.newInstance(clazz);
Schema mySchema = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(schemaFile);
jaxbContext.generateSchema(new MySchemaOutputResolver()); // ultimately creates schemaFile
Unmarshaller u = m_context.createUnmarshaller();
u.setSchema(mySchema);
u.unmarshal(...);
do any of you know how I can validate jaxb without needing to create a schema file that sits in my computer?
Do I need to create a schema for validation, it looks redundant when I get it by JaxbContect.generateSchema ?
How do you do this?
Regarding ekeren's solution above, it's not a good idea to use PipedOutputStream/PipedInputStream in a single thread, lest you overflow the buffer and cause a deadlock. ByteArrayOutputStream/ByteArrayInputStream works, but if your JAXB classes generate multiple schemas (in different namespaces) you need multiple StreamSources.
I ended up with this:
JAXBContext jc = JAXBContext.newInstance(Something.class);
final List<ByteArrayOutputStream> outs = new ArrayList<ByteArrayOutputStream>();
jc.generateSchema(new SchemaOutputResolver(){
#Override
public Result createOutput(String namespaceUri, String suggestedFileName) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
outs.add(out);
StreamResult streamResult = new StreamResult(out);
streamResult.setSystemId("");
return streamResult;
}});
StreamSource[] sources = new StreamSource[outs.size()];
for (int i=0; i<outs.size(); i++) {
ByteArrayOutputStream out = outs.get(i);
// to examine schema: System.out.append(new String(out.toByteArray()));
sources[i] = new StreamSource(new ByteArrayInputStream(out.toByteArray()),"");
}
SchemaFactory sf = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI );
m.setSchema(sf.newSchema(sources));
m.marshal(docs, new DefaultHandler()); // performs the schema validation
I had the exact issue and found a solution in the Apache Axis 2 source code:
protected List<DOMResult> generateJaxbSchemas(JAXBContext context) throws IOException {
final List<DOMResult> results = new ArrayList<DOMResult>();
context.generateSchema(new SchemaOutputResolver() {
#Override
public Result createOutput(String ns, String file) throws IOException {
DOMResult result = new DOMResult();
result.setSystemId(file);
results.add(result);
return result;
}
});
return results;
}
and after you've acquired your list of DOMResults that represent the schemas, you will need to transform them into DOMSource objects before you can feed them into a schema generator. This second step might look something like this:
Unmarshaller u = myJAXBContext.createUnmarshaller();
List<DOMSource> dsList = new ArrayList<DOMSource>();
for(DOMResult domresult : myDomList){
dsList.add(new DOMSource(domresult.getNode()));
}
String schemaLang = "http://www.w3.org/2001/XMLSchema";
SchemaFactory sFactory = SchemaFactory.newInstance(schemaLang);
Schema schema = sFactory.newSchema((DOMSource[]) dsList.toArray(new DOMSource[0]));
u.setSchema(schema);
I believe you just need to set a ValidationEventHandler on your unmarshaller. Something like this:
public class JAXBValidator extends ValidationEventCollector {
#Override
public boolean handleEvent(ValidationEvent event) {
if (event.getSeverity() == event.ERROR ||
event.getSeverity() == event.FATAL_ERROR)
{
ValidationEventLocator locator = event.getLocator();
// change RuntimeException to something more appropriate
throw new RuntimeException("XML Validation Exception: " +
event.getMessage() + " at row: " + locator.getLineNumber() +
" column: " + locator.getColumnNumber());
}
return true;
}
}
And in your code:
Unmarshaller u = m_context.createUnmarshaller();
u.setEventHandler(new JAXBValidator());
u.unmarshal(...);
If you use maven using jaxb2-maven-plugin can help you. It generates schemas in generate-resources phase.