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

manifold.csv.rt.Csv Maven / Gradle / Ivy

There is a newer version: 2024.1.54
Show newest version
/*
 * Copyright (c) 2019 - Manifold Systems LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package manifold.csv.rt;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import manifold.csv.rt.parser.*;
import manifold.json.rt.api.DataBindings;
import manifold.json.rt.parser.Token;
import manifold.json.rt.parser.TokenType;
import manifold.rt.api.util.Pair;

import static manifold.json.rt.Json.toBindings;

public class Csv
{
  /**
   * Write the contents of the {@code jsonValue} to CSV formatted string following
   * RFC 4180. Note data in all fields is enclosed in double quotes.
   * 

* Additionally, since CSV is a flat file format, nesting of data is not directly supported. That is, field * values having type {@code Bindings} or {@code List}, although legal Bindings value types, have no representation * in CSV. Currently, such values are simply converted to strings via {@code toString()}, however this may change in * a future revision. */ public static String toCsv( Object jsonValue ) { StringBuilder sb = new StringBuilder(); jsonValue = toBindings( jsonValue ); if( jsonValue instanceof Map ) { toCsv( jsonValue, null, sb, 0 ); } else if( jsonValue instanceof Iterable ) { toCsv( jsonValue, "list", sb, 0 ); } else { toCsv( jsonValue, "item", sb, 0 ); } return sb.toString(); } /** * Write the contents of the {@code jsonValue} to CSV formatted string following * RFC 4180. Note data in all fields is enclosed in double quotes. *

* Additionally, since CSV is a flat file format, nesting of data is not directly supported. That is, field * values having type {@code Bindings} or {@code List}, although legal Bindings value types, have no representation * in CSV. Currently, such values are simply converted to strings via {@code toString()}, however this may change in * a future revision. */ public static void toCsv( Object jsonValue, String name, StringBuilder target, int indent ) { jsonValue = toBindings( jsonValue ); if( jsonValue instanceof Map ) { if( name == null ) { Map map = (Map)jsonValue; if( map.size() == 1 ) { // single entry with no name implies root, defer to the root Object rootKey = map.keySet().iterator().next(); Object rootValue = map.get( rootKey ); if( rootValue instanceof Pair ) { rootValue = ((Pair)rootValue).getSecond(); } toCsv( rootValue, rootKey.toString(), target, indent ); return; } else { //todo: factor out Csv.CSV_DEFAULT_ROOT name = "root_object"; } } // a single row of data consisting of the name/value pairs in the map toCsv( Collections.singletonList( jsonValue ), name, target, indent ); } else if( jsonValue instanceof Iterable ) { // A list of data toCsv( (Iterable)jsonValue, name, target, indent ); } else { // a single row of data consisting of just one column of the name/value pair toCsv( Collections.singletonList( jsonValue ), name, target, indent ); } } private static void toCsv( Iterable value, String name, StringBuilder target, int indent ) { Iterator iterator = value.iterator(); if( iterator.hasNext() ) { // Csv header Object comp = iterator.next(); comp = toBindings( comp ); if( comp instanceof Pair ) { comp = ((Pair)comp).getSecond(); } if( comp instanceof Map ) { // row of data int i = 0; for( Object key: ((Map)comp).keySet() ) { if( i > 0 ) { target.append( ',' ); } appendCsvValue( target, key ); i++; } target.append( '\n' ); } else if( comp instanceof Iterable ) { // single column of data appendCsvValue( target, name ).append( '\n' ); } } else { return; } for( Object comp: value ) { // Csv records if( comp instanceof Pair ) { comp = ((Pair)comp).getSecond(); } comp = toBindings( comp ); if( comp instanceof Map ) { int i = 0; for( Object v: ((Map)comp).values() ) { if( i > 0 ) { target.append( ',' ); } appendCsvValue( target, v ); i++; } target.append( '\n' ); } else if( comp instanceof Iterable ) { // Lists of lists not supported with CSV, just dumping text for each element to a single value target.append( '"' ); ((Iterable)comp).forEach( e -> target.append( "\"\"" ).append( value ).append( "\"\"," ) ); target.append( "\"\n" ); } else { // single column of data appendCsvValue( target, value ).append( '\n' ); } } } private static StringBuilder appendCsvValue( StringBuilder target, Object value ) { target.append( '"' ).append( String.valueOf( value ).replace( "\"", "\"\"" ) ).append( '"' ); return target; } public static Object fromCsv( String csv ) { return fromCsv( csv, false ); } public static Object fromCsv( String csv, boolean withTokens ) { try( InputStream inputStream = new BufferedInputStream( new ByteArrayInputStream( csv.getBytes() ) ) ) { CsvDataSet dataSet = CsvParser.parse( inputStream ); return withTokens ? transformType( dataSet ) : transformData( dataSet ); } catch( IOException e ) { throw new RuntimeException( e ); } } private static List transformData( CsvDataSet dataSet ) { CsvHeader header = dataSet.getHeader(); List names = new ArrayList<>(); if( header != null ) { names = header.getFields().stream().map( f -> f.getToken() ).collect( Collectors.toList() ); } else { List records = dataSet.getRecords(); if( !records.isEmpty() ) { List labels = new ArrayList<>(); for( int i = 0; i < records.get( 0 ).getSize(); i++ ) { labels.add( "Field" + (i + 1) ); } names = labels; } } List list = new ArrayList<>(); for( CsvRecord record: dataSet.getRecords() ) { DataBindings bindings = new DataBindings(); List fields = record.getFields(); for( int fieldNum = 0; fieldNum < fields.size(); fieldNum++ ) { CsvField field = fields.get( fieldNum ); Object name = names.get( fieldNum ); bindings.put( name instanceof CsvToken ? ((CsvToken)name).getData() : name.toString(), field.getToken().getData() ); } list.add( bindings ); } return list; } private static DataBindings transformType( CsvDataSet dataSet ) { CsvHeader header = dataSet.getHeader(); List names = new ArrayList<>(); if( header != null ) { names = header.getFields().stream().map( f -> f.getToken() ).collect( Collectors.toList() ); } else { List records = dataSet.getRecords(); if( !records.isEmpty() ) { List labels = new ArrayList<>(); for( int i = 0; i < records.get( 0 ).getSize(); i++ ) { labels.add( "Field" + (i + 1) ); } names = labels; } } DataBindings typeBindings = new DataBindings(); typeBindings.put( "$schema", "http://json-schema.org/draft-04/schema#" ); typeBindings.put( "synthetic", true ); // indicates this schema is not directly in the data file typeBindings.put( "type", "array" ); DataBindings items = new DataBindings(); typeBindings.put( "items", items ); items.put( "type", "object" ); DataBindings properties = new DataBindings(); items.put( "properties", properties ); List types = dataSet.getTypes(); for( int i = 0; i < types.size(); i++ ) { DataBindings property = new DataBindings(); Object nameObj = names.get( i ); if( nameObj instanceof CsvToken ) { properties.put( ((CsvToken)nameObj).getData(), makeTokensValue( (CsvToken)nameObj, property ) ); } else { properties.put( nameObj.toString(), property ); } Class type = types.get( i ); String t; String format = null; if( type == Boolean.class ) { t = "boolean"; } else if( type == Integer.class ) { t = "integer"; } else if( type == Double.class ) { t = "number"; } else { t = "string"; if( type == Long.class ) { format = "int64"; } else if( type == BigInteger.class ) { format = "big-integer"; } else if( type == BigDecimal.class ) { format = "big-decimal"; } else if( type == LocalDateTime.class ) { format = "date-time"; } else if( type == LocalDate.class ) { format = "date"; } else if( type == LocalTime.class ) { format = "time"; } } property.put( "type", t ); property.put( "nullable", true ); if( format != null ) { property.put( "format", format ); } } return typeBindings; } private static Object makeTokensValue( CsvToken name, DataBindings property ) { return new Pair<>( new Token[]{makeToken( name ), null}, property ); } private static Token makeToken( CsvToken token ) { return new Token( TokenType.STRING, token.getData(), token.getOffset(), token.getLine(), -1 ); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy