Custom (Personalizado) XML DataSource con JasperReports

Hola amigos,

Aca un ejemplo muy sencillo de como implementar las interfaces JRDataSource y JRRewindableDataSource de JasperReports para iterar un documento xml.

Clase CustomXMLDataSource

Como verán el constructor de la clase recibe, una instancia de la interface (XML) Document, luego extrae el nodo raiz y después obtiene la lista de los nodos hijos.

Para navegar por la lista de nodos se usa una variable llamada index, cuyo valor inicial es -1 para que la primera vez que se llame al método next(), este apunte al primer elemento de la lista.

El método getFieldValue usa los atributos del nodo actual para ser retornado hacia afuera. Para esto se usa el description del JRField. Lo que me gusta de este metodo es que uno acá puede implementar cualquier lógica que uno desee para obtener información de cualquier tipo de fuente de datos. Acá se podría aplicar cosas mas complejas como Xpath, o talvez hacer algo simple como buscar un nodo basado en el JRField. Alguien me comento que usar XPath puede ser un poco mas lento, es por eso que esta interface es muy importante para nosotros.

El método moveFirst es el que se encarga hacer que data source vuelva al primer nodo. Esta parte me gusto mucho, por que vi que por defecto el JRDataSource de JasperReport no implementa este metodo, seguro para hacerlo mas genérico al momento de trabajar con fuentes de datos que no sean tan fácil de manejar como una lista:).

Pruebas de unidad y código fuente

En las pruebas de unidad podrán apreciar varios casos de uso de la clase.Como ser: utilizando un xml que tiene un solo nodo, cuando son varios, etc.

El código fuente esta disponible en: https://github.com/rhuanca/examples/tree/master/jasper-reports

Saludos,
Renan

package renidev.examples.jaspereports;

import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRField;
import net.sf.jasperreports.engine.JRRewindableDataSource;

/**
 * This is a sample custom data source for xml.
 * @author Renan Huanca
 *
 */
public class CustomXMLDataSource implements JRDataSource, JRRewindableDataSource {

	private NodeList childNodes;
	private int index;
	
	public CustomXMLDataSource(Document document) {
		// Note: document.getFirstChild() will return the root node.
		this.childNodes = document.getFirstChild().getChildNodes();
		this.index = -1;
	}

	/**
	 * Implementation of the getFieldValue method.
	 * In this case this method is extracting the value 
	 * from the note's attributes.
	 */
	public Object getFieldValue(JRField jrField) throws JRException {
		String nodeName = jrField.getDescription();
		return childNodes.item(index).getAttributes().getNamedItem(nodeName).getTextContent();
	}

	public boolean next() throws JRException {
		++this.index;
		return this.index < childNodes.getLength();
	}

	public void moveFirst() throws JRException {
		this.index = -1;
	}
}
package renidev.examples.jasperreports;

import static org.junit.Assert.*;
import org.junit.Test;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRField;
import net.sf.jasperreports.engine.design.JRDesignField;
import renidev.examples.jaspereports.CustomXMLDataSource;


public class CustomXMLDataSourceTest {
	
	@Test
	public void single_case() throws JRException{
		String xml ="";
		CustomXMLDataSource dataSource = new CustomXMLDataSource(SimpleDocumentBuilder.buildDocument(xml));
		assertFalse(dataSource.next());
	}
	
	@Test
	public void one_child_node() throws JRException{
		String xml ="";
		CustomXMLDataSource dataSource = new CustomXMLDataSource(SimpleDocumentBuilder.buildDocument(xml));
		assertTrue(dataSource.next());
		assertEquals("carlos", dataSource.getFieldValue(createField("name")));
		assertFalse(dataSource.next());
	}
	
	@Test
	public void two_child_nodes() throws JRException{
		String xml =
				"" +
					"" +
					"" +
				"";
		CustomXMLDataSource dataSource = new CustomXMLDataSource(SimpleDocumentBuilder.buildDocument(xml));
		JRField nameField = createField("name");
		
		// reading data data
		assertTrue(dataSource.next());
		assertEquals("carlos", dataSource.getFieldValue(nameField));
		assertTrue(dataSource.next());
		assertEquals("gonzalo", dataSource.getFieldValue(nameField));
		
		// checking there is no more data 
		assertFalse(dataSource.next());
	}
	
	@Test
	public void two_child_nodes_and_two_attributes() throws JRException{
		String xml =
				"" +
					"" +
					"" +
				"";
		CustomXMLDataSource dataSource = new CustomXMLDataSource(SimpleDocumentBuilder.buildDocument(xml));
		JRField nameField = createField("name");
		JRField fromField = createField("from");
		
		// reading data data
		assertTrue(dataSource.next());
		assertEquals("carlos", dataSource.getFieldValue(nameField));
		assertEquals("work", dataSource.getFieldValue(fromField));
		assertTrue(dataSource.next());
		assertEquals("gonzalo", dataSource.getFieldValue(nameField));
		assertEquals("raquet", dataSource.getFieldValue(fromField));
		
		// checking there is no more data 
		assertFalse(dataSource.next());
	}

	
	@Test
	public void two_child_nodes_and_rewind() throws JRException{
		String xml =
				"" +
					"" +
					"" +
				"";
		
		CustomXMLDataSource dataSource = new CustomXMLDataSource(SimpleDocumentBuilder.buildDocument(xml));
		JRField nameField = createField("name");
		
		// bypassing two nodes
		assertTrue(dataSource.next());
		assertTrue(dataSource.next());
		
		// checking there is no more data 
		assertFalse(dataSource.next());
		
		// rewind the data source
		dataSource.moveFirst();
		
		// reading data data
		assertTrue(dataSource.next());
		assertEquals("carlos", dataSource.getFieldValue(nameField));
		assertTrue(dataSource.next());
		assertEquals("gonzalo", dataSource.getFieldValue(nameField));
	}
	
	public JRField createField(String description){
		JRDesignField field = new JRDesignField();
		field.setDescription(description);
		return field;
	}
}

Leave a Reply

Your email address will not be published. Required fields are marked *