org.structr.csv.ToCsvFunction.java Source code

Java tutorial

Introduction

Here is the source code for org.structr.csv.ToCsvFunction.java

Source

/**
 * Copyright (C) 2010-2018 Structr GmbH
 *
 * This file is part of Structr <http://structr.org>.
 *
 * Structr is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * Structr is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Structr.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.structr.csv;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.structr.common.error.FrameworkException;
import org.structr.core.GraphObject;
import org.structr.core.Result;
import org.structr.core.app.StructrApp;
import org.structr.core.function.LocalizeFunction;
import org.structr.core.property.DateProperty;
import org.structr.core.property.PropertyKey;
import org.structr.schema.action.ActionContext;
import org.structr.schema.parser.DatePropertyParser;
import org.structr.web.function.UiFunction;

/**
 *
 */
public class ToCsvFunction extends UiFunction {
    public static final String ERROR_MESSAGE_TO_CSV = "Usage: ${to_csv(nodes, propertiesOrView[, delimiterChar[, quoteChar[, recordSeparator[, includeHeader[, localizeHeader[, headerLocalizationDomain]]]])}. Example: ${to_csv(find('Page'), 'ui')}";
    public static final String ERROR_MESSAGE_TO_CSV_JS = "Usage: ${{Structr.to_csv(nodes, propertiesOrView[, delimiterChar[, quoteChar[, recordSeparator[, includeHeader[, localizeHeader[, headerLocalizationDomain]]]])}}. Example: ${{Structr.to_csv(Structr.find('Page'), 'ui'))}}";

    @Override
    public String getName() {
        return "to_csv()";
    }

    @Override
    public Object apply(ActionContext ctx, Object caller, Object[] sources) throws FrameworkException {

        try {

            assertArrayHasMinLengthAndMaxLengthAndAllElementsNotNull(sources, 2, 8);

            if (!(sources[0] instanceof List)) {
                logParameterError(caller, sources, ctx.isJavaScriptContext());
                return "ERROR: First parameter must be a collection!".concat(usage(ctx.isJavaScriptContext()));
            }

            final List<GraphObject> nodes = (List) sources[0];
            String delimiterChar = ";";
            String quoteChar = "\"";
            String recordSeparator = "\n";
            boolean includeHeader = true;
            boolean localizeHeader = false;
            String headerLocalizationDomain = null;
            String propertyView = null;
            List<String> properties = null;

            // we are using size() instead of isEmpty() because NativeArray.isEmpty() always returns true
            if (nodes.size() == 0) {
                logger.warn("to_csv(): Can not create CSV if no nodes are given!");
                logParameterError(caller, sources, ctx.isJavaScriptContext());
                return "";
            }

            switch (sources.length) {
            case 8:
                headerLocalizationDomain = (String) sources[7];
            case 7:
                localizeHeader = (Boolean) sources[6];
            case 6:
                includeHeader = (Boolean) sources[5];
            case 5:
                recordSeparator = (String) sources[4];
            case 4:
                quoteChar = (String) sources[3];
            case 3:
                delimiterChar = (String) sources[2];
            case 2: {
                if (sources[1] instanceof String) {
                    // view is given
                    propertyView = (String) sources[1];

                } else if (sources[1] instanceof List) {
                    // named properties are given
                    properties = (List) sources[1];

                    // we are using size() instead of isEmpty() because NativeArray.isEmpty() always returns true
                    if (properties.size() == 0) {
                        logger.warn("to_csv(): Can not create CSV if list of properties is empty!");
                        logParameterError(caller, sources, ctx.isJavaScriptContext());
                        return "";
                    }

                } else {
                    logParameterError(caller, sources, ctx.isJavaScriptContext());
                    return "ERROR: Second parameter must be a collection of property names or a single property view!"
                            .concat(usage(ctx.isJavaScriptContext()));
                }
            }
            }

            try {

                final StringWriter writer = new StringWriter();
                writeCsv(nodes, writer, propertyView, properties, quoteChar.charAt(0), delimiterChar.charAt(0),
                        recordSeparator, includeHeader, localizeHeader, headerLocalizationDomain, ctx.getLocale());
                return writer.toString();

            } catch (Throwable t) {
                logger.warn("to_csv(): Exception occurred", t);
                return "";
            }

        } catch (IllegalArgumentException e) {

            logParameterError(caller, sources, e.getMessage(), ctx.isJavaScriptContext());
            return usage(ctx.isJavaScriptContext());
        }
    }

    @Override
    public String usage(boolean inJavaScriptContext) {
        return (inJavaScriptContext ? ERROR_MESSAGE_TO_CSV_JS : ERROR_MESSAGE_TO_CSV);
    }

    @Override
    public String shortDescription() {
        return "Returns a CSV representation of the given nodes";
    }

    public static void writeCsv(final Result result, final Writer out, final String propertyView,
            final List<String> properties, final char quoteChar, final char delimiterChar,
            final String recordSeparator, final boolean includeHeader, final boolean localizeHeader,
            final String headerLocalizationDomain, final Locale locale) throws IOException {

        final List<GraphObject> list = result.getResults();

        writeCsv(list, out, propertyView, properties, quoteChar, delimiterChar, recordSeparator, includeHeader,
                localizeHeader, headerLocalizationDomain, locale);
    }

    public static void writeCsv(final List list, final Writer out, final String propertyView,
            final List<String> properties, final char quoteChar, final char delimiterChar,
            final String recordSeparator, final boolean includeHeader, final boolean localizeHeader,
            final String headerLocalizationDomain, final Locale locale) throws IOException {

        final StringBuilder row = new StringBuilder();

        if (includeHeader) {

            row.setLength(0);

            boolean isFirstCol = true;

            if (propertyView != null) {

                final Object obj = list.get(0);

                if (obj instanceof GraphObject) {
                    for (PropertyKey key : ((GraphObject) obj).getPropertyKeys(propertyView)) {
                        String value = key.dbName();
                        if (localizeHeader) {
                            try {
                                value = LocalizeFunction.getLocalization(locale, value, headerLocalizationDomain);
                            } catch (FrameworkException fex) {
                                logger.warn("to_csv(): Exception", fex);
                            }
                        }

                        isFirstCol = appendColumnString(row, value, isFirstCol, quoteChar, delimiterChar);
                    }
                } else {
                    row.append(
                            "Error: Object is not of type GraphObject, can not determine properties of view for header row");
                }

            } else if (properties != null) {

                for (final String colName : properties) {
                    String value = colName;
                    if (localizeHeader) {
                        try {
                            value = LocalizeFunction.getLocalization(locale, value, headerLocalizationDomain);
                        } catch (FrameworkException fex) {
                            logger.warn("to_csv(): Exception", fex);
                        }
                    }

                    isFirstCol = appendColumnString(row, value, isFirstCol, quoteChar, delimiterChar);
                }
            }

            out.append(row).append(recordSeparator).flush();

        }

        for (final Object obj : list) {

            row.setLength(0);

            boolean isFirstCol = true;

            if (propertyView != null) {

                if (obj instanceof GraphObject) {

                    for (PropertyKey key : ((GraphObject) obj).getPropertyKeys(propertyView)) {

                        final Object value = ((GraphObject) obj).getProperty(key);
                        isFirstCol = appendColumnString(row, value, isFirstCol, quoteChar, delimiterChar);
                    }
                } else {
                    row.append("Error: Object is not of type GraphObject, can not determine properties of object");
                }

            } else if (properties != null) {

                if (obj instanceof GraphObject) {

                    final GraphObject castedObj = (GraphObject) obj;

                    for (final String colName : properties) {
                        final PropertyKey key = StructrApp.key(obj.getClass(), colName);
                        final Object value = castedObj.getProperty(key);
                        isFirstCol = appendColumnString(row, value, isFirstCol, quoteChar, delimiterChar);
                    }
                } else if (obj instanceof Map) {

                    final Map castedObj = (Map) obj;

                    for (final String colName : properties) {
                        final Object value = castedObj.get(colName);
                        isFirstCol = appendColumnString(row, value, isFirstCol, quoteChar, delimiterChar);
                    }

                }
            }

            // Replace \r and \n so we dont get multi-line CSV (needs to be four backslashes because regex)
            final String rowWithoutRecordSeparator = row.toString().replaceAll("\n", "\\\\n").replaceAll("\r",
                    "\\\\r");

            out.append(rowWithoutRecordSeparator).append(recordSeparator).flush();
        }

    }

    private static boolean appendColumnString(final StringBuilder row, final Object value, boolean isFirstColumn,
            final char quoteChar, final char delimiter) {
        if (!isFirstColumn) {
            row.append(delimiter);
        }
        row.append(escapeForCsv(value, quoteChar));

        return false;
    }

    private static String escapeForCsv(final Object value, final char quoteChar) {

        String result;

        if (value == null) {

            result = "";

        } else if (value instanceof String[]) {

            // Special handling for StringArrays
            ArrayList<String> quotedStrings = new ArrayList();
            for (final String str : Arrays.asList((String[]) value)) {
                // The strings can contain quotes - these need to be escaped with 3 slashes in the output
                quotedStrings.add("\\" + quoteChar + StringUtils.replace(str, "" + quoteChar, "\\\\\\" + quoteChar)
                        + "\\" + quoteChar);
            }

            result = quotedStrings.toString();

        } else if (value instanceof Collection) {

            // Special handling for collections of nodes
            ArrayList<String> quotedStrings = new ArrayList();
            for (final Object obj : (Collection) value) {
                quotedStrings.add("\\" + quoteChar + obj.toString() + "\\" + quoteChar);
            }

            result = quotedStrings.toString();

        } else if (value instanceof Date) {

            result = DatePropertyParser.format((Date) value, DateProperty.getDefaultFormat());

        } else {

            result = StringUtils.replace(value.toString(), "" + quoteChar, "\\" + quoteChar);

        }

        return "".concat("" + quoteChar)
                .concat(StringUtils.replace(StringUtils.replace(result, "\r\n", "\\n"), "\r", "\\n"))
                .concat("" + quoteChar);
    }

}