/******************************************************************************* * Copyright (c) 2016 Jeremie Bresson and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Jeremie Bresson - initial API and implementation *******************************************************************************/ package org.eclipse.mylyn.wikitext.asciidoc.internal.block; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import org.eclipse.mylyn.wikitext.asciidoc.internal.util.LanguageSupport; import org.eclipse.mylyn.wikitext.parser.DocumentBuilder.BlockType; import org.eclipse.mylyn.wikitext.parser.TableAttributes; import org.eclipse.mylyn.wikitext.parser.TableCellAttributes; import org.eclipse.mylyn.wikitext.parser.TableRowAttributes; import com.google.common.base.Splitter; /** * Text block containing a table */ public class TableBlock extends AsciiDocBlock { private TableFormat format; private String separator; private enum TableFormat { PREFIX_SEPARATED_VALUES, //PSV DELIMITER_SEPARATED_VALUES, //DSV COMMA_SEPARATED_VALUES //CSV } private int cellsCount = 0; private List<TableCellAttributes> colsAttribute; private boolean hasHeader = false; private boolean cellBlockIsOpen = false; public TableBlock() { super(Pattern.compile("^(\\||,|:)===\\s*")); //$NON-NLS-1$ } @Override protected void processBlockStart() { if (startDelimiter.startsWith(",")) { //$NON-NLS-1$ // ",===" is the shorthand notation for [format="csv", options="header"] format = TableFormat.COMMA_SEPARATED_VALUES; hasHeader = true; } else if (startDelimiter.startsWith(":")) { //$NON-NLS-1$ // ":===" is the shorthand notation for [format="dsv", options="header"] format = TableFormat.DELIMITER_SEPARATED_VALUES; hasHeader = true; } else { // default table format is PSV with separator "|" format = TableFormat.PREFIX_SEPARATED_VALUES; separator = "|"; //$NON-NLS-1$ } Map<String, String> lastProperties = getAsciiDocState().getLastProperties(Collections.emptyList()); colsAttribute = LanguageSupport.computeColumnsAttributeList(lastProperties.get("cols")); //$NON-NLS-1$ String formatProperty = lastProperties.get("format"); //$NON-NLS-1$ if (formatProperty != null) { switch (formatProperty) { case "dsv": //$NON-NLS-1$ format = TableFormat.DELIMITER_SEPARATED_VALUES; break; case "csv": //$NON-NLS-1$ format = TableFormat.COMMA_SEPARATED_VALUES; break; } } String separator = lastProperties.get("separator"); //$NON-NLS-1$ if (separator != null) { this.separator = separator; } String options = lastProperties.get("options"); //$NON-NLS-1$ if (options != null) { hasHeader = options.contains("header"); //$NON-NLS-1$ } TableAttributes tableAttributes = new TableAttributes(); tableAttributes.setWidth(lastProperties.get("width")); //$NON-NLS-1$ builder.beginBlock(BlockType.TABLE, tableAttributes); } @Override protected void processBlockContent(String line) { if (!line.trim().isEmpty()) { if (colsAttribute.isEmpty() && !cellBlockIsOpen) { TableRowAttributes tableRowAttributes = new TableRowAttributes(); builder.beginBlock(BlockType.TABLE_ROW, tableRowAttributes); } boolean firstCellInLine = true; for (String cell : createRowCellSplitter(line)) { String cellContent = cell.trim(); if (format == TableFormat.PREFIX_SEPARATED_VALUES && cellBlockIsOpen && !cellContent.isEmpty() && firstCellInLine) { markupLanguage.emitMarkupLine(parser, state, " " + cellContent, 0); //$NON-NLS-1$ } else { if (colsAttribute.isEmpty() && cellBlockIsOpen && firstCellInLine) { closeCellBlockIfNeeded(); builder.endBlock(); // close table row colsAttribute = LanguageSupport.createDefaultColumnsAttributeList(cellsCount); } if (!cellContent.isEmpty() || !firstCellInLine) { handleCellContent(cellContent); } } firstCellInLine = false; } } } private Iterable<String> createRowCellSplitter(String line) { if (format == TableFormat.COMMA_SEPARATED_VALUES) { return Splitter.on(Pattern.compile(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)")).split(line); //$NON-NLS-1$ } String delimiter = getCellSeparator(); return Splitter.on(Pattern.compile("(?<!\\\\)" + Pattern.quote(delimiter))).split(line); //$NON-NLS-1$ } private String getCellSeparator() { switch (format) { case COMMA_SEPARATED_VALUES: return ","; //$NON-NLS-1$ case DELIMITER_SEPARATED_VALUES: return ":"; //$NON-NLS-1$ case PREFIX_SEPARATED_VALUES: default: return separator; } } private void handleCellContent(String cellContent) { closeCellBlockIfNeeded(); String blockContent; if (format == TableFormat.COMMA_SEPARATED_VALUES) { if (cellContent.startsWith("\"") && cellContent.endsWith("\"")) { blockContent = cellContent.substring(1, cellContent.length() - 1).replaceAll("\"\"", "\""); } else { blockContent = cellContent; } } else { String delimiter = getCellSeparator(); blockContent = cellContent.replaceAll("\\\\" + Pattern.quote(delimiter), delimiter);//$NON-NLS-1$ } if (!colsAttribute.isEmpty() && cellsCount % colsAttribute.size() == 0) { TableRowAttributes tableRowAttributes = new TableRowAttributes(); builder.beginBlock(BlockType.TABLE_ROW, tableRowAttributes); } TableCellAttributes attributes; if (colsAttribute.isEmpty()) { attributes = new TableCellAttributes(); } else { attributes = colsAttribute.get(cellsCount % colsAttribute.size()); } if (hasHeader && (colsAttribute.isEmpty() || cellsCount < colsAttribute.size())) { builder.beginBlock(BlockType.TABLE_CELL_HEADER, attributes); } else { builder.beginBlock(BlockType.TABLE_CELL_NORMAL, attributes); } cellBlockIsOpen = true; markupLanguage.emitMarkupLine(parser, state, blockContent, 0); } @Override protected void processBlockEnd() { closeCellBlockIfNeeded(); if (colsAttribute.isEmpty() || (!colsAttribute.isEmpty() && cellsCount % colsAttribute.size() != 0)) { builder.endBlock(); // close table row } builder.endBlock(); // close table } private void closeCellBlockIfNeeded() { if (cellBlockIsOpen) { builder.endBlock(); // close table cell cellBlockIsOpen = false; cellsCount = cellsCount + 1; if (!colsAttribute.isEmpty() && cellsCount % colsAttribute.size() == 0) { builder.endBlock(); // close table row } } } }