All Downloads are FREE. Search and download functionalities are using the official Maven repository.

uk.ac.starlink.votable.VOTableWriter Maven / Gradle / Ivy

There is a newer version: 4.3
Show newest version
package uk.ac.starlink.votable;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import uk.ac.starlink.fits.AbstractFitsTableWriter;
import uk.ac.starlink.table.MultiStarTableWriter;
import uk.ac.starlink.table.StarTable;
import uk.ac.starlink.table.StarTableOutput;
import uk.ac.starlink.table.StarTableWriter;
import uk.ac.starlink.table.TableFormatException;
import uk.ac.starlink.table.TableSequence;
import uk.ac.starlink.table.Tables;
import uk.ac.starlink.table.formats.DocumentedIOHandler;
import uk.ac.starlink.util.ConfigMethod;
import uk.ac.starlink.util.DataBufferedOutputStream;
import uk.ac.starlink.util.IOUtils;

/**
 * Implementation of the StarTableWriter interface for
 * VOTables.  The dataFormat and inline attributes
 * can be modified to affect how the bulk cell data are output -
 * this may be in TABLEDATA, FITS, BINARY or BINARY2 format, and in the 
 * latter three cases may be either inline as base64 encoded CDATA or
 * to a separate stream.
 *
 * 

Some of the Auxiliary metadata items of the ColumnInfo metadata * from written tables are respected: *

    *
  • {@link uk.ac.starlink.table.Tables#NULL_VALUE_INFO}: * sets the value of "magic" blank value for * integer columns
  • *
  • {@link uk.ac.starlink.table.Tables#UBYTE_FLAG_INFO}: * if set to Boolean.TRUE and if the column has content class * Short or short[], the data will be written * with datatype="unsignedByte" instead of * (signed 16-bit) "short".
  • *
  • The COOSYS_*_INFO and TIMESYS_*_INFO * items defined in the {@link uk.ac.starlink.votable.VOStarTable} class; * suitable COOSYS/TIMESYS elements will be written and referenced * as required to honour these items.
  • *
  • Various other of the *_INFO items defined in the * {@link uk.ac.starlink.votable.VOStarTable} class; * this has the effect that VOTable column attributes read in from * a VOTable will be passed through if the same table is written * out to a VOTable (or VOTable-based format like FITS-plus).
  • *
* * @author Mark Taylor (Starlink) */ public class VOTableWriter implements StarTableWriter, MultiStarTableWriter, DocumentedIOHandler { private DataFormat dataFormat_; private boolean inline_; private VOTableVersion version_; private Boolean compact_; private Charset encoding_; private boolean writeSchemaLocation_; private String xmlDeclaration_ = DEFAULT_XML_DECLARATION; /** Default XML declaration in written documents. */ public static final String DEFAULT_XML_DECLARATION = ""; private static final Logger logger = Logger.getLogger( "uk.ac.starlink.votable" ); /** * Constructs a default VOTableWriter. * Output is in TABLEDATA format. */ public VOTableWriter() { this( DataFormat.TABLEDATA, true ); } /** * Constructs a VOTableWriter with specified output type and default * VOTable version. * * @param dataFormat the format in which tables will be written * @param inline whether output of streamed formats should be * inline and base64-encoded or not */ public VOTableWriter( DataFormat dataFormat, boolean inline ) { this( dataFormat, inline, VOTableVersion.getDefaultVersion() ); } /** * Constructs a VOTableWriter with specified output characterstics * and a given version of the VOTable standard. * * @param dataFormat the format in which tables will be written * @param inline whether output of streamed formats should be * inline and base64-encoded or not * @param version version of the VOTable standard */ public VOTableWriter( DataFormat dataFormat, boolean inline, VOTableVersion version ) { dataFormat_ = dataFormat; inline_ = inline; version_ = version; encoding_ = StandardCharsets.UTF_8; } /** * Writes a StarTable to a given location. * * @param startab the table to write * @param location the filename to which to write the table * @param sto object used for location resolution */ public void writeStarTable( StarTable startab, String location, StarTableOutput sto ) throws IOException { writeStarTables( Tables.singleTableSequence( startab ), location, sto ); } /** * Writes a sequence of tables to a given location. * They are written as separate TABLE elements in the same VOTable document. * * @param tableSeq table sequence * @param location the filename to which to write the table * @param sto object used for location resolution */ public void writeStarTables( TableSequence tableSeq, String location, StarTableOutput sto ) throws IOException { /* Get the stream to write to. */ OutputStream out = null; try { out = sto.getOutputStream( location ); File file = out instanceof FileOutputStream ? new File( location ) : null; if ( ! inline_ && file == null ) { throw new TableFormatException( "Can't write non-inline format" + " to a stream" ); } writeStarTables( tableSeq, out, file ); } finally { if ( out != null ) { out.close(); } } } /** * Writes a StarTable to a given stream; must be inline. * Same as writeStarTable(startab,out,(File)null). * * @param startab the table to write * @param out the stream down which to write the table */ public void writeStarTable( StarTable startab, OutputStream out ) throws IOException { writeStarTable( startab, out, null ); } /** * Writes a sequence of tables to a given stream; must be inline. * Same as writeStarTables(tableSeq,out,null). * * @param tableSeq tables to write * @param out destination stream */ public void writeStarTables( TableSequence tableSeq, OutputStream out ) throws IOException { writeStarTables( tableSeq, out, null ); } /** * Writes a StarTable to a given stream. * * @param startab the table to write * @param out the stream down which to write the table * @param file the filename to which out refers; this is used * if necessary to come up with a suitable filename for * related files which need to be written. May be null. */ public void writeStarTable( StarTable startab, OutputStream out, File file ) throws IOException { writeStarTables( Tables.singleTableSequence( startab ), out, file ); } /** * Writes a sequence of tables to a given stream. * * @param tableSeq table sequence to write * @param out destination stream * @param file the filename to which out refers; this is used * if necessary to come up with a suitable filename for * related files which need to be written. May be null. */ public void writeStarTables( TableSequence tableSeq, OutputStream out, File file ) throws IOException { /* Set up the XML output. */ Charset encoding = encoding_; BufferedWriter writer = new BufferedWriter( new OutputStreamWriter( out, encoding ) ); boolean isUtf8 = StandardCharsets.UTF_8.equals( encoding ); /* Output preamble. */ writePreTableXML( writer ); /* Loop over all tables for output. */ int itable = 0; for ( StarTable startab; ( startab = tableSeq.nextTable() ) != null; itable++ ) { if ( itable > 0 ) { writeBetweenTableXML( writer ); } /* Get the format to provide a configuration object which describes * exactly how the data from each cell is going to get written. */ VOSerializer serializer = VOSerializer.makeSerializer( dataFormat_, version_, startab ); if ( compact_ != null ) { serializer.setCompact( compact_.booleanValue() ); } /* Begin TABLE element including FIELDs etc. */ serializer.writePreDataXML( writer ); /* Now write the DATA element. */ /* First Treat the case where we write data inline. */ if ( inline_ || file == null ) { if ( ! inline_ ) { assert file == null; logger.warning( "Writing VOTable inline - can't do href " + "when no filename is supplied" ); } writer.flush(); /* Treat UTF8 using the dedicated method, which may be * optimised. This is the usual case. */ if ( isUtf8 ) { serializer.writeInlineDataElementUTF8( out ); } /* Otherwise use generic XML output. */ else { serializer.writeInlineDataElement( writer ); } } /* Treat the case where the data is streamed to an external file. */ else { assert file != null; String basename = file.getName(); int dotpos = basename.lastIndexOf( '.' ); basename = dotpos > 0 ? basename.substring( 0, dotpos ) : basename; String extension = dataFormat_ == DataFormat.FITS ? ".fits" : ".bin"; String dataname = basename + "-data" + ( itable > 0 ? Integer.toString( itable ) : "" ) + extension; File datafile = new File( file.getParentFile(), dataname ); logger.info( "Writing VOTable href data at " + datafile ); DataBufferedOutputStream dataout = new DataBufferedOutputStream( new FileOutputStream( datafile ) ); serializer.writeHrefDataElement( writer, dataname, dataout ); dataout.close(); } /* Write postamble. */ serializer.writePostDataXML( writer ); } writePostTableXML( writer ); /* Tidy up. */ writer.flush(); } /** * Writes a table directly to a stream. * * @param startab table to write * @param writer destination stream */ public void writeInlineStarTable( StarTable startab, BufferedWriter writer ) throws IOException { writeInlineStarTables( new StarTable[] { startab }, writer ); } /** * Writes multiple tables directly to a stream. * * @param startabs tables * @param writer destination stream */ public void writeInlineStarTables( StarTable[] startabs, BufferedWriter writer ) throws IOException { writePreTableXML( writer ); for ( int i = 0; i < startabs.length; i++ ) { if ( i > 0 ) { writeBetweenTableXML( writer ); } VOSerializer.makeSerializer( dataFormat_, version_, startabs[ i ] ) .writeInlineTableElement( writer ); } writePostTableXML( writer ); writer.flush(); } /** * Outputs all the text required before any tables are written. * This method can be overridden to alter the behaviour of the * writer if required. * * @param writer destination stream * @see #writePostTableXML */ protected void writePreTableXML( BufferedWriter writer ) throws IOException { /* Output XML declaration if required. */ if ( xmlDeclaration_ != null && xmlDeclaration_.length() > 0 ) { writer.write( xmlDeclaration_ ); writer.newLine(); } /* Output document declaration if required. */ String doctypeDeclaration = version_.getDoctypeDeclaration(); if ( doctypeDeclaration != null && doctypeDeclaration.length() > 0 ) { writer.write( doctypeDeclaration ); writer.newLine(); } /* Output the VOTABLE start tag. */ writer.write( "" ); writer.newLine(); /* Output a comment claiming authorship. */ writer.write( "" ); writer.newLine(); /* Output RESOURCE element start tag. */ writer.write( "" ); writer.newLine(); } /** * Outputs text between one table (TABLE and possibly other associated * elements) and the next. It's only called as a separator between * adjacent tables, not at the start or end of a sequence of them; * hence it's not called if only a single table is being output. * This method can be overridden to alter the behaviour of the * writer if required. * *

This method closes one RESOURCE element and opens another one. * * @param writer destination stream */ protected void writeBetweenTableXML( BufferedWriter writer ) throws IOException { writer.write( "" ); writer.newLine(); writer.write( "" ); writer.newLine(); } /** * Outputs all the text required after any tables in the * output table document. This method can be overridden to alter * the behaviour of this writer if required. * * @param writer destination stream * @see #writePreTableXML */ protected void writePostTableXML( BufferedWriter writer ) throws IOException { /* Close the open elements. */ writer.write( "" ); writer.newLine(); writer.write( "" ); writer.newLine(); } public String[] getExtensions() { return new String[] { "vot", "votable", "xml", }; } public boolean looksLikeFile( String filename ) { return DocumentedIOHandler.matchesExtension( this, filename ); } public boolean docIncludesExample() { return false; } public String getXmlDescription() { return readText( "VOTableWriter.xml" ); } public String getFormatName() { StringBuffer fname = new StringBuffer( "votable" ); if ( dataFormat_ == DataFormat.TABLEDATA ) { return fname.toString(); } if ( dataFormat_ == DataFormat.FITS ) { fname.append( "-fits" ); } else if ( dataFormat_ == DataFormat.BINARY ) { fname.append( "-binary" ); } else if ( dataFormat_ == DataFormat.BINARY2 ) { fname.append( "-binary2" ); } else { assert false; } fname.append( inline_ ? "-inline" : "-href" ); return fname.toString(); } public String getMimeType() { String type = "application/x-votable+xml"; String serialization; if ( dataFormat_ == DataFormat.TABLEDATA ) { serialization = "TABLEDATA"; } else if ( dataFormat_ == DataFormat.BINARY ) { serialization = "BINARY"; } else if ( dataFormat_ == DataFormat.BINARY2 ) { serialization = "BINARY2"; } else if ( dataFormat_ == DataFormat.FITS ) { serialization = "FITS"; } else { serialization = null; } StringBuffer sbuf = new StringBuffer( type ); if ( serialization != null ) { sbuf.append( "; serialization=" ) .append( serialization ); } return sbuf.toString(); } /** * Sets the format in which the table data will be output. * * @param format bulk data format */ @ConfigMethod( property = "format", usage = "TABLEDATA|BINARY|BINARY2|FITS", doc = "

Gives the serialization type (DATA element content) " + "of output VOTables.

", example = "BINARY2", sequence = 1 ) public void setDataFormat( DataFormat format ) { dataFormat_ = format; } /** * Returns the format in which this writer will output the bulk table data. * * @return bulk data format */ public DataFormat getDataFormat() { return dataFormat_; } /** * Sets whether STREAM elements should be written inline or to an * external file in the case of FITS and BINARY encoding. * * @param inline true iff streamed data will be encoded * inline in the STREAM element */ @ConfigMethod( property = "inline", doc = "If true, STREAM elements are written base64-encoded " + "within the body of the document, " + "and if false they are written to a new external binary file " + "whose name is derived from that of the output VOTable document. " + "This is only applicable to BINARY, BINARY2 and FITS formats " + "where output is not to a stream.", sequence = 3 ) public void setInline( boolean inline ) { inline_ = inline; } /** * Indicates whether STREAM elements will be written inline or to * an external file in the case of FITS and BINARY encoding. * * @return true iff streamed data will be encoded inline in * the STREAM element */ public boolean getInline() { return inline_; } /** * Sets the XML declaration which will be used by this writer * at the head of any document written. * By default this is the value of {@link #DEFAULT_XML_DECLARATION}. * * @param xmlDecl new XML declaration */ public void setXMLDeclaration( String xmlDecl ) { xmlDeclaration_ = xmlDecl; } /** * Returns the XML declaration which is used by this writer * at the head of any document written. * * @return XML declaration */ public String getXMLDeclaration() { return xmlDeclaration_; } /** * Sets the version of the VOTable standard to which the output * of this writer will conform. * * @param version new version */ @ConfigMethod( property = "version", usage = "V10|V11|V12|V13|V14", example = "V13", doc = "

Gives the version of the VOTable format which will be used " + "when writing the VOTable.\n" + "\"V10\" is version 1.0 etc.

", sequence = 2 ) public void setVotableVersion( VOTableVersion version ) { version_ = version; } /** * Returns the version of the VOTable standard to which the output * of this writer conforms. * * @return version */ public VOTableVersion getVotableVersion() { return version_; } /** * Controls whitespace formatting for TABLEDATA output, * ignored for other formats. * If null (the default), * a decision will be taken dependent on table width. * * @param compact TRUE for compact TABLEDATA output, * FALSE for more whitespace, * null for auto */ @ConfigMethod( property = "compact", example = "true", doc = "

Controls whitespace formatting for TABLEDATA output,\n" + "ignored for other formats.\n" + "By default a decision will be taken dependent on table width.\n" + "

", sequence = 4 ) public void setCompact( Boolean compact ) { compact_ = compact; } /** * Returns whitespace formatting policy for TABLEDATA output. * * @return TRUE for compact TABLEDATA output, FALSE for more whitespace, * null for auto */ public Boolean isCompact() { return compact_; } /** * Sets the XML encoding used for the output VOTable. * The default value is UTF-8. * Note that certain optimisations are in place for UTF-8 output * which means that other encodings may be significantly slower. * * @param encoding encoding charset */ @ConfigMethod( property = "encoding", usage = "UTF-8|UTF-16|...", example = "UTF-16", doc = "

Specifies the XML encoding used in the output VOTable.\n" + "The default value is UTF-8.\n" + "Note that certain optimisations are in place for UTF-8 output\n" + "which means that other encodings may be significantly slower.\n" + "

", sequence = 5 ) public void setEncoding( Charset encoding ) { encoding_ = encoding; } /** * Returns the character encoding used for XML output. * * @return encoding charset */ public Charset getEncoding() { return encoding_; } /** * Determines whether the schema location attribute will be written * on opening VOTABLE tags. * * @param writeSchemaLocation whether to write xsi:schemaLocation atts */ public void setWriteSchemaLocation( boolean writeSchemaLocation ) { writeSchemaLocation_ = writeSchemaLocation; } /** * Indicates whether the schema location attribute will be written * on opening VOTABLE tags. * * @return whether xsi:schemaLocation attributes will be written */ public boolean getWriteSchemaLocation() { return writeSchemaLocation_; } @Override public String toString() { StringBuffer sbuf = new StringBuffer(); sbuf.append( dataFormat_.toString() ); if ( dataFormat_ != DataFormat.TABLEDATA ) { sbuf.append( "," ) .append( inline_ ? "inline" : "href" ); } sbuf.append( ",v" ) .append( version_.getVersionNumber() ); return sbuf.toString(); } /** * Returns a list of votable writers with variant values of attributes. * * @return non-standard VOTableWriters. */ public static StarTableWriter[] getStarTableWriters() { VOTableVersion version = VOTableVersion.getDefaultVersion(); List wlist = new ArrayList(); wlist.add( new VOTableWriter( DataFormat.TABLEDATA, true, version ) ); wlist.add( new VOTableWriter( DataFormat.BINARY, true, version ) ); if ( version.allowBinary2() ) { wlist.add( new VOTableWriter( DataFormat.BINARY2, true, version ) ); } wlist.add( new VOTableWriter( DataFormat.FITS, false, version ) ); wlist.add( new VOTableWriter( DataFormat.BINARY, false, version ) ); if ( version.allowBinary2() ) { wlist.add( new VOTableWriter( DataFormat.BINARY2, false, version ) ); } wlist.add( new VOTableWriter( DataFormat.FITS, true ) ); return wlist.toArray( new StarTableWriter[ 0 ] ); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy