
uk.ac.starlink.ecsv.SnakeYamlParser Maven / Gradle / Ivy
package uk.ac.starlink.ecsv;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.error.MarkedYAMLException;
import org.yaml.snakeyaml.error.YAMLException;
/**
* YamlParser implementation based on the SnakeYAML library
*
* The current implementation is based on SnakeYAML 1.25,
* which in turn claims to understand YAML 1.1.
*
* @author Mark Taylor
* @since 28 Apr 2020
* @see https://bitbucket.org/asomov/snakeyaml/src/
*/
public class SnakeYamlParser implements YamlParser {
private static final Logger logger_ =
Logger.getLogger( "uk.ac.starlink.ecsv" );
public EcsvMeta parseMeta( String[] lines ) throws EcsvFormatException {
/* Get SnakeYAML to perform the parse. */
StringBuffer sbuf = new StringBuffer();
for ( String line : lines ) {
sbuf.append( line )
.append( '\n' );
}
final Object yaml;
try {
yaml = new Yaml().load( sbuf.toString() );
}
catch ( MarkedYAMLException e ) {
throw new EcsvFormatException( "YAML parse error: "
+ e.getProblem(), e );
}
catch ( YAMLException e ) {
throw new EcsvFormatException( "YAML parse error: "
+ e.getMessage(), e );
}
if ( yaml == null ) {
throw new EcsvFormatException( "No YAML content" );
}
else if ( ! ( yaml instanceof Map ) ) {
throw new EcsvFormatException( "Unexpected YAML content "
+ yaml.getClass().getName() );
}
Map,?> map = (Map) yaml;
/* Extract the required information from the parsed structure
* and turn it into an EcsvMeta object. */
final char delimiter = getDelimiter( map.get( "delimiter" ) );
final Map,?> tableMeta = getMeta( map.get( "meta" ) );
final EcsvColumn>[] columns = getColumns( map.get( "datatype" ) );
final String schema = getStringValue( map, "schema" );
return new EcsvMeta() {
public char getDelimiter() {
return delimiter;
}
public EcsvColumn>[] getColumns() {
return columns;
}
public Map,?> getTableMeta() {
return tableMeta;
}
public String getSchema() {
return schema;
}
};
}
/**
* Converts a suitable object to an ECSV delimiter character.
*
* @param delimObj item that should contain delimiter
* @return best guess at delimiter character, hopefully comma or space
*/
private char getDelimiter( Object delimObj ) {
if ( delimObj instanceof String && ((String) delimObj).length() == 1 ) {
return ((String) delimObj).charAt( 0 );
}
else if ( delimObj instanceof Character ) {
return ((Character) delimObj).charValue();
}
if ( delimObj != null ) {
logger_.warning( "Unexpected ECSV delimiter declaration"
+ "\"" + delimObj + "\"" );
}
return ' ';
}
/**
* Converts a suitable object to a generic table metadata map.
*
* @param metaObj item that should contain a metadata map
* @param metadata map, or null if absent or unsuitable
*/
private Map,?> getMeta( Object metaObj ) {
if ( metaObj == null ) {
return null;
}
else if ( metaObj instanceof Map ) {
return (Map,?>) metaObj;
}
else {
logger_.warning( "Ignoring malformed table metadata of type "
+ metaObj.getClass().getName() );
return null;
}
}
/**
* Converts a suitable object to a list of column specifiers.
*
* @param colsObj item that should contain a list of datatype specifiers
* @return array of column metadata objects
*/
private EcsvColumn>[] getColumns( Object colsObj )
throws EcsvFormatException {
if ( colsObj instanceof List ) {
return getColumnsArray( ((List) colsObj).toArray() );
}
else if ( colsObj instanceof Object[] ) {
return getColumnsArray( (Object[]) colsObj );
}
else if ( colsObj == null ) {
throw new EcsvFormatException( "No datatype array in metadata" );
}
else {
throw new EcsvFormatException( "No comprehensible datatype array "
+ "in metadata" );
}
}
/**
* Converts an array of suitable objects to a list of column specifiers.
*
* @param colObjs array of items that should be datatype specifiers
* @return array of column metadata objects
*/
private EcsvColumn>[] getColumnsArray( Object[] colObjs )
throws EcsvFormatException {
int ncol = colObjs.length;
EcsvColumn>[] cols = new EcsvColumn>[ ncol ];
for ( int ic = 0; ic < ncol; ic++ ) {
Object colObj = colObjs[ ic ];
if ( colObj instanceof Map ) {
cols[ ic ] = createColumn( (Map,?>) colObj );
}
else if ( colObj == null ) {
throw new EcsvFormatException( "Null element "
+ "in column metadata array");
}
else {
throw new EcsvFormatException( "Unexpected element type "
+ colObj.getClass().getName()
+ " in column metadata array" );
}
}
return cols;
}
/**
* Converts a suitable object to a column specifier.
*
* @param colMap map that should contain metadata specification
* @return corresponding column metadata object
*/
private EcsvColumn> createColumn( Map,?> colMap )
throws EcsvFormatException {
final String name = getStringValue( colMap, "name" );
String datatype = getStringValue( colMap, "datatype" );
if ( name == null ) {
throw new EcsvFormatException( "Column has no name" );
}
if ( datatype == null ) {
throw new EcsvFormatException( "Column " + name
+ " has no datatype" );
}
String subtype = getStringValue( colMap, "subtype" );
final EcsvDecoder> decoder =
EcsvDecoder.createDecoder( datatype, subtype );
if ( decoder == null ) {
throw new EcsvFormatException( "Unknown/unsupported datatype "
+ datatype );
}
return createColumn( name, decoder, colMap );
}
/**
* Creates a typed column metadata object.
*
* @param name column name
* @param decoder value decoder
* @param colMap map containing all available parsed column metadata
* @return column metadata object
*/
private static EcsvColumn createColumn( final String name,
final EcsvDecoder decoder,
Map,?> colMap ) {
final String unit = getStringValue( colMap, "unit" );
final String format = getStringValue( colMap, "format" );
final String description = getStringValue( colMap, "description" );
final String datatype = getStringValue( colMap, "datatype" );
Object mapObj = colMap.get( "meta" );
final Map,?> meta = mapObj instanceof Map ? (Map,?>) mapObj : null;
return new EcsvColumn() {
public String getName() {
return name;
}
public EcsvDecoder getDecoder() {
return decoder;
}
public String getDatatype() {
return datatype;
}
public String getUnit() {
return unit;
}
public String getFormat() {
return format;
}
public String getDescription() {
return description;
}
public Map,?> getMeta() {
return meta;
}
};
}
/**
* Attempts to acquire a string value from a map.
*
* @param map map
* @param key map key
* @return string value corresponding to key
, or null
*/
private static String getStringValue( Map,?> map, String key ) {
Object valueObj = map.get( key );
if ( valueObj instanceof String ) {
return (String) valueObj;
}
else if ( valueObj == null ) {
return null;
}
else {
logger_.warning( "Non-string value for key \"" + key + "\"" );
return null;
}
}
}