Commit 9711900d authored by R.W.Majeed's avatar R.W.Majeed

export prototype implementation (xpaths need fixing)

parent 61915aea
package de.sekmi.histream.export;
import java.util.Iterator;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import de.sekmi.histream.io.GroupedXMLWriter;
public class DwhNamespaceResolver implements NamespaceContext{
@Override
public String getNamespaceURI(String prefix) {
if( prefix == null ){
throw new IllegalArgumentException("prefix is null");
}else if( prefix.equals(XMLConstants.DEFAULT_NS_PREFIX) ){
return GroupedXMLWriter.NAMESPACE;
}else{
return XMLConstants.NULL_NS_URI;
}
}
@Override
public String getPrefix(String namespaceURI) {
throw new UnsupportedOperationException();
}
@Override
public Iterator<String> getPrefixes(String namespaceURI) {
throw new UnsupportedOperationException();
}
}
package de.sekmi.histream.export;
public interface ExportWriter {
TableWriter openPatientTable();
TableWriter openVisitTable();
TableWriter openEAVTable(String id);
}
package de.sekmi.histream.export;
import java.io.IOException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLStreamException;
import javax.xml.xpath.XPath;
import org.w3c.dom.Node;
import de.sekmi.histream.ObservationException;
import de.sekmi.histream.export.config.ExportDescriptor;
import de.sekmi.histream.export.config.ExportException;
public class FragmentExporter extends VisitFragmentParser {
TableParser patientParser;
TableParser visitParser;
protected FragmentExporter(XPath xpath, ExportDescriptor desc, ExportWriter writer) throws ExportException, XMLStreamException, ParserConfigurationException {
super();
patientParser = desc.getPatientTable().createParser(writer.openPatientTable(), xpath);
visitParser = desc.getVisitTable().createParser(writer.openVisitTable(), xpath);
}
@Override
protected void patientFragment(Node patient) throws ObservationException {
try {
patientParser.writeRow(patient);
} catch (ExportException e) {
throw new ObservationException(e);
}
}
@Override
protected void visitFragment(Node visit) throws ObservationException {
try {
visitParser.writeRow(visit);
} catch (ExportException e) {
throw new ObservationException(e);
}
}
@Override
public void close(){
super.close();
try{
patientParser.close();
}catch( IOException e ){
reportError(new ObservationException(e));
}
try{
visitParser.close();
}catch( IOException e ){
reportError(new ObservationException(e));
}
}
}
package de.sekmi.histream.export;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLStreamException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;
import de.sekmi.histream.ObservationSupplier;
import de.sekmi.histream.export.config.ExportDescriptor;
import de.sekmi.histream.export.config.ExportException;
import de.sekmi.histream.io.Streams;
public class TableExportFactory {
public void export(ObservationSupplier supplier){
private ExportDescriptor desc;
private XPathFactory factory;
private NamespaceContext ns;
public TableExportFactory(ExportDescriptor desc){
this.desc = desc;
factory = XPathFactory.newInstance();
ns = new DwhNamespaceResolver();
}
private XPath createXPath(){
XPath xpath = factory.newXPath();
xpath.setNamespaceContext(ns);
return xpath;
}
public void export(ObservationSupplier supplier, ExportWriter writer) throws ExportException{
try( FragmentExporter e = new FragmentExporter(createXPath(), desc, writer) ){
Streams.transfer(supplier, e);
} catch (XMLStreamException | ParserConfigurationException e) {
throw new ExportException("Unable to create exporter", e);
}
}
}
package de.sekmi.histream.export;
import java.io.IOException;
import java.util.Objects;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import org.w3c.dom.Node;
import de.sekmi.histream.export.config.AbstractTable;
import de.sekmi.histream.export.config.Column;
import de.sekmi.histream.export.config.ExportException;
/**
* Parses a XML fragment for row values
* @author R.W.Majeed
*
*/
public class TableParser implements AutoCloseable{
private AbstractTable table;
private XPath xpath;
private XPathExpression[] xpaths;
private TableWriter writer;
public TableParser(AbstractTable table, TableWriter writer, XPath xpath) throws ExportException{
this.table = table;
this.writer = writer;
this.xpath = xpath;
Objects.requireNonNull(table);
Objects.requireNonNull(writer);
compileXPaths();
writeHeaders();
}
private void compileXPaths() throws ExportException{
Column[] columns = table.getColumns();
xpaths = new XPathExpression[columns.length];
for( int i=0; i<columns.length; i++ ){
try {
xpaths[i] = xpath.compile(columns[i].getXPath());
} catch (XPathExpressionException e) {
throw new ExportException("Unable to compile XPath expression for "+table.getId()+"."+columns[i].getHeader(), e);
}
}
}
private void writeHeaders(){
writer.header(table.getHeaders());
}
public void writeRow(Node node) throws ExportException{
try {
writer.row(valuesForFragment(node));
} catch (IOException e) {
throw new ExportException("Unable to write row", e);
}
}
private String[] valuesForFragment(Node node) throws ExportException{
Column[] columns = table.getColumns();
String[] values = new String[columns.length];
for( int i=0; i<columns.length; i++ ){
try {
values[i] = (String)xpaths[i].evaluate(node, XPathConstants.STRING);
} catch (XPathExpressionException e) {
throw new ExportException("XPath evaluation failed for "+table.getId()+"."+columns[i].getHeader(), e);
}
}
return values;
}
@Override
public void close() throws IOException {
writer.close();
}
}
package de.sekmi.histream.export;
import java.io.IOException;
public interface TableWriter extends AutoCloseable{
void header(String[] headers);
void row(String[] columns) throws IOException;
@Override
void close() throws IOException;
}
......@@ -2,6 +2,7 @@ package de.sekmi.histream.export;
import java.util.Objects;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
......@@ -17,6 +18,7 @@ import org.w3c.dom.Node;
import de.sekmi.histream.ObservationException;
import de.sekmi.histream.ext.Patient;
import de.sekmi.histream.ext.Visit;
import de.sekmi.histream.impl.ObservationImpl;
import de.sekmi.histream.io.GroupedXMLWriter;
public abstract class VisitFragmentParser extends GroupedXMLWriter {
......@@ -24,6 +26,7 @@ public abstract class VisitFragmentParser extends GroupedXMLWriter {
private Document doc;
private DocumentFragment currentPatient;
private DocumentFragment currentVisit;
private boolean firstVisit;
protected VisitFragmentParser() throws XMLStreamException, ParserConfigurationException {
super();
......@@ -37,22 +40,39 @@ public abstract class VisitFragmentParser extends GroupedXMLWriter {
private void setDOMWriter(Node node) throws XMLStreamException{
Result result = new DOMResult(node);
this.writer = factory.createXMLStreamWriter(result);
writer = factory.createXMLStreamWriter(result);
// XXX need this?
writer.setDefaultNamespace(ObservationImpl.XML_NAMESPACE);
writer.setPrefix(XMLConstants.DEFAULT_NS_PREFIX, ObservationImpl.XML_NAMESPACE);
writer.setPrefix("xsi", XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
}
private void createDocument() throws ParserConfigurationException{
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
f.setNamespaceAware(true);
f.setIgnoringComments(true);
f.setCoalescing(true);
f.setIgnoringElementContentWhitespace(true);
DocumentBuilder builder = f.newDocumentBuilder();
doc = builder.newDocument();
doc.getDomConfig().setParameter("namespaces", true);
doc.getDomConfig().setParameter("namespace-declarations", true);
// doc.getDomConfig().setParameter("namespaces", true);
// doc.getDomConfig().setParameter("namespace-declarations", true);
//return doc;
}
@Override
protected void endPatient(Patient patient) throws ObservationException {
// TODO Auto-generated method stub
super.endPatient(patient);
if( firstVisit == true ){
// patient fragment already processed
firstVisit = false;
}else{
// No visit for patient.
// process patient fragment anyway
patientFragment(currentPatient.getFirstChild());
}
}
@Override
......@@ -69,8 +89,16 @@ public abstract class VisitFragmentParser extends GroupedXMLWriter {
@Override
protected void beginEncounter(Visit visit) throws ObservationException {
if( firstVisit == false ){
// patient fragment was parsed
patientFragment(currentPatient.getFirstChild());
firstVisit = true;
}
// write visit info to visit fragment
currentVisit = doc.createDocumentFragment();
// XXX verify default namespace
//currentVisit.isDefaultNamespace(namespaceURI)
try {
setDOMWriter(currentVisit);
} catch (XMLStreamException e) {
......@@ -88,6 +116,22 @@ public abstract class VisitFragmentParser extends GroupedXMLWriter {
Objects.requireNonNull(node);
visitFragment(currentVisit.getFirstChild());
}
protected abstract void visitFragment(Node visit);
/**
* Called after each patient fragment was parsed.
* The patient fragment does not contain any encounters,
* these are provided to {@link #visitFragment(Node)}.
* @param patient patient node
* @throws ObservationException error
*/
protected void patientFragment(Node patient)throws ObservationException{
}
/**
* Called for each parsed visit fragment. The visit
* fragment will contain facts.
*
* @param visit visit node
* @throws ObservationException error
*/
protected abstract void visitFragment(Node visit) throws ObservationException;
}
......@@ -4,6 +4,10 @@ import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.xpath.XPath;
import de.sekmi.histream.export.TableParser;
import de.sekmi.histream.export.TableWriter;
@XmlTransient
@XmlAccessorType(XmlAccessType.NONE)
......@@ -11,6 +15,10 @@ public abstract class AbstractTable {
@XmlElement(name="column")
Column[] columns;
@XmlTransient
public abstract String getId();
public String[] getHeaders(){
String[] headers = new String[columns.length];
for( int i=0; i<columns.length; i++ ){
......@@ -21,4 +29,12 @@ public abstract class AbstractTable {
public Column getColumn(int index){
return columns[index];
}
}
public Column[] getColumns(){
return columns;
}
public TableParser createParser(TableWriter writer, XPath xpath) throws ExportException{
return new TableParser(this, writer, xpath);
}
}
\ No newline at end of file
......@@ -36,4 +36,11 @@ public class Column {
// constructor for JAXB
protected Column(){
}
public String getXPath(){
return xpath;
}
public String getHeader(){
return header;
}
}
......@@ -13,4 +13,9 @@ public class EavTable extends AbstractTable{
@XmlIDREF
@XmlAttribute(name="class")
ConceptGroup clazz;
@Override
public String getId() {
return id;
}
}
package de.sekmi.histream.export.config;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
@XmlRootElement(name="export")
@XmlAccessorType(XmlAccessType.NONE)
public class ExportDescriptor {
@XmlElement
......@@ -17,4 +21,14 @@ public class ExportDescriptor {
@XmlElement(name="eav-table")
EavTable[] tables;
public PatientTable getPatientTable(){
return patient;
}
public AbstractTable getVisitTable() {
return visit;
}
}
package de.sekmi.histream.export.config;
public class ExportException extends Exception{
/**
* serial version
*/
private static final long serialVersionUID = 1L;
public ExportException(String message, Throwable cause){
super(message, cause);
}
}
......@@ -3,4 +3,9 @@ package de.sekmi.histream.export.config;
public class PatientTable extends AbstractTable{
@Override
public String getId() {
return "patients";
}
}
......@@ -2,4 +2,9 @@ package de.sekmi.histream.export.config;
public class VisitTable extends AbstractTable{
@Override
public String getId() {
return "visits";
}
}
package de.sekmi.histream.export;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class MemoryExportWriter implements ExportWriter{
List<MemoryTable> tables;
public MemoryExportWriter(){
tables = new LinkedList<>();
}
public static class MemoryTable implements TableWriter{
List<String[]> rows;
String[] headers;
String id;
public MemoryTable(String id){
this.id = id;
this.rows = new ArrayList<>();
}
@Override
public void header(String[] headers) {
this.headers = headers;
}
@Override
public void row(String[] columns) throws IOException {
rows.add(columns);
}
@Override
public void close() throws IOException {
}
private void dumpRow(String[] row){
for( int i=0; i<row.length; i++ ){
if( i != 0 )System.out.print("\t");
System.out.print(row[i]);
}
System.out.println();
}
public void dump(){
System.out.println("Table "+id+":");
dumpRow(headers);
for( String[] row : rows ){
dumpRow(row);
}
}
}
@Override
public TableWriter openPatientTable() {
MemoryTable t = new MemoryTable("patients");
tables.add(t);
return t;
}
@Override
public TableWriter openVisitTable() {
MemoryTable t = new MemoryTable("visits");
tables.add(t);
return t;
}
@Override
public TableWriter openEAVTable(String id) {
MemoryTable t = new MemoryTable(id);
tables.add(t);
return t;
}
public void dump(){
for( MemoryTable t : tables ){
t.dump();
System.out.println();
}
}
}
package de.sekmi.histream.export;
import javax.xml.bind.JAXB;
import org.junit.Test;
import de.sekmi.histream.ObservationSupplier;
import de.sekmi.histream.export.config.ExportDescriptor;
import de.sekmi.histream.io.FileObservationProviderTest;
public class TestExport {
@Test
public void verifyExport() throws Exception{
ExportDescriptor d = JAXB.unmarshal(getClass().getResourceAsStream("/export1.xml"), ExportDescriptor.class);
MemoryExportWriter m = new MemoryExportWriter();
TableExportFactory e = new TableExportFactory(d);
FileObservationProviderTest t = new FileObservationProviderTest();
t.initializeObservationFactory();
try( ObservationSupplier s = t.getExampleSupplier() ){
e.export(s, m);
}
m.dump();
// TODO something wrong with namespaces in xpath/dom
}
}
package de.sekmi.histream.export.config;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.junit.Test;
import org.w3c.dom.Node;
import de.sekmi.histream.ObservationSupplier;
import de.sekmi.histream.export.DwhNamespaceResolver;
import de.sekmi.histream.export.VisitFragmentSupplier;
import de.sekmi.histream.io.FileObservationProviderTest;
import de.sekmi.histream.xml.XMLUtils;
......@@ -18,9 +24,26 @@ public class TestVisitFragmentParser {
try( ObservationSupplier s = t.getExampleSupplier() ){
VisitFragmentSupplier sup = new VisitFragmentSupplier(s);
Node n = sup.get();
//System.out.println(n.toString());
System.out.println("nodeName="+n.getNodeName());
System.out.println("localName="+n.getLocalName());
System.out.println("nsUri="+n.getNamespaceURI());
System.out.println("docRootNS="+n.getOwnerDocument().getFirstChild().getNamespaceURI());
System.out.println();
XMLUtils.printDOM(n, System.out);
testXPath(n);
}
}
private void testXPath(Node visit) throws XPathExpressionException{
XPathFactory f = XPathFactory.newInstance();
XPath xp = f.newXPath();
xp.setNamespaceContext(new DwhNamespaceResolver());
// XXX namespace resolver is not working
String ret = (String)xp.evaluate("namespace-uri(./*[6])", visit, XPathConstants.STRING);
System.out.println("XPath="+ret);
ret = (String)xp.evaluate("count(fact)", visit, XPathConstants.STRING);
System.out.println("Facts:"+ret);
}
}
......@@ -4,11 +4,20 @@
<group class="diag">
<concept wildcard-notation="ICD10:*"/>
</group>
<concept notation="ABC"/>
<concept notation="T:full"/>
<concept wildcard-notation="CEDIS:*"/>
</concepts>
<patient-table>
<column header="pid" xpath="@id"/>
<column header="dob" xpath="birthdate"/>
<column header="sex" xpath="gender"/>
</patient-table>
<visit-table>
<column header="vid" xpath="@id"/>
<column header="start" xpath="start"/>
<column header="f_start" xpath="fact[@concept='T:full']/@start"/>
<column header="f_end" xpath="fact[@concept='T:full']/@end"/>
<column header="f_loc" xpath="fact[@concept='T:full']/@location"/>
<column header="f_val" xpath="fact[@concept='T:full']/value"/>
</visit-table>
</ns2:export>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment