/******************************************************************************* * Copyright (c) 2007, 2017 David Green 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: * David Green - initial API and implementation *******************************************************************************/ package org.eclipse.mylyn.wikitext.confluence.internal.block; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.mylyn.wikitext.confluence.ConfluenceLanguage; import org.eclipse.mylyn.wikitext.parser.Attributes; import org.eclipse.mylyn.wikitext.parser.DocumentBuilder.BlockType; import org.eclipse.mylyn.wikitext.parser.markup.Block; import com.google.common.base.CharMatcher; import com.google.common.collect.ImmutableList; /** * Table block, matches blocks that start with <code>table. </code> or those that start with a table row. * * @author David Green */ public class TableBlock extends Block { private static final int LINE_PROCESSED_INDICATOR = -1; private static final List<Class<?>> NESTABLE_CELL_BLOCKS = ImmutableList.of(ListBlock.class); private static final Pattern START_PATTERN = Pattern.compile("\\s*(\\|\\|?.*$)"); //$NON-NLS-1$ private static final Pattern END_OF_CELL_CONTENT_PATTERN = Pattern.compile("((?:(?:[^\\|\\[]*)(?:\\[[^\\]]*\\])?)*)" //$NON-NLS-1$ + "(\\|\\|?\\s*)+?"); //$NON-NLS-1$ private static final Pattern END_OF_ROW_PATTERN = Pattern.compile("^\\|\\|?\\s*$"); //$NON-NLS-1$ private static final Pattern TABLE_ROW_PATTERN = Pattern .compile("\\|(\\|)?\\s*" + "((?:(?:[^\\|\\[]*)(?:\\[[^\\]]*\\])?)*)" //$NON-NLS-1$ //$NON-NLS-2$ + "(\\|\\|?\\s*$)?"); //$NON-NLS-1$ private int blockLineCount = 0; private Matcher matcher; private BlockType currentCell; private boolean nesting = false; private boolean rowStarted = false; private boolean multiLineCell = false; public TableBlock() { } @Override public int processLineContent(String line, int offset) { nesting = false; int finalOffset = processLineStart(line, offset); if (isClosed() || finalOffset == LINE_PROCESSED_INDICATOR) { return isClosed() ? 0 : LINE_PROCESSED_INDICATOR; } ++blockLineCount; if (atEndOfRow(line, finalOffset)) { ensureRowClosed(); finalOffset = LINE_PROCESSED_INDICATOR; } else { finalOffset = processCellContent(line, finalOffset); } return isClosed() ? 0 : processEndOfLine(line, finalOffset); } private int processLineStart(String line, int offset) { if (blockLineCount == 0) { Attributes attributes = new Attributes(); builder.beginBlock(BlockType.TABLE, attributes); } else if (markupLanguage.isEmptyLine(line)) { setClosed(true); } else if (offset == 0 && multiLineCell) { offset = continueMultiLineCell(line, offset); } else if (!TABLE_ROW_PATTERN.matcher(line).find()) { setClosed(true); } return offset; } private int continueMultiLineCell(String line, int offset) { String cellContent = line; Matcher rowMatcher = TABLE_ROW_PATTERN.matcher(line); if (rowMatcher.find()) { offset = rowMatcher.start(); if (offset > 0) { cellContent = line.substring(0, offset); builder.lineBreak(); emitMarkup(cellContent, 0); } ensureCellClosed(); } else { builder.lineBreak(); emitMarkup(cellContent, 0); return LINE_PROCESSED_INDICATOR; } return offset; } private boolean atEndOfRow(String line, int lineOffset) { String restOfLine = line.substring(lineOffset); return END_OF_ROW_PATTERN.matcher(restOfLine).find(); } private int processCellContent(String line, int offset) { int cellsOffset = 0; String restOfline = offset == 0 ? line : line.substring(offset); Matcher rowMatcher = TABLE_ROW_PATTERN.matcher(restOfline); if (rowMatcher.find()) { do { ensureCellClosed(); cellsOffset = startNextCell(rowMatcher); String cellContent = rowMatcher.group(2); nesting = isNestableCellContent(cellContent); if (!nesting) { emitMarkup(cellContent, offset + cellsOffset); cellsOffset = rowMatcher.end(2); } } while (!nesting && rowMatcher.find()); } else { setClosed(true); } return offset + cellsOffset; } private int startNextCell(Matcher rowMatcher) { ensureRowStarted(); String headerIndicator = rowMatcher.group(1); boolean header = "|".equals(headerIndicator); //$NON-NLS-1$ currentCell = header ? BlockType.TABLE_CELL_HEADER : BlockType.TABLE_CELL_NORMAL; builder.beginBlock(currentCell, new Attributes()); return rowMatcher.start(2); } private boolean isNestableCellContent(String cellContent) { Block startBlock = getConfluenceLanguage().startBlock(cellContent, 0); return startBlock != null && NESTABLE_CELL_BLOCKS.contains(startBlock.getClass()); } private void emitMarkup(String text, int lineOffset) { getConfluenceLanguage().emitMarkupLine(getParser(), state, lineOffset, CharMatcher.WHITESPACE.trimTrailingFrom(text), 0); } private ConfluenceLanguage getConfluenceLanguage() { return (ConfluenceLanguage) getMarkupLanguage(); } private int processEndOfLine(String line, int offset) { if (offset != LINE_PROCESSED_INDICATOR && !nesting) { if (atEndOfRow(line, offset)) { ensureRowClosed(); return LINE_PROCESSED_INDICATOR; } else { multiLineCell = true; } } return offset >= line.length() ? LINE_PROCESSED_INDICATOR : offset; } @Override public boolean beginNesting() { return nesting; } @Override public int findCloseOffset(String line, int lineOffset) { Matcher endMatcher = END_OF_CELL_CONTENT_PATTERN.matcher(line); if (lineOffset != 0) { endMatcher.region(lineOffset, line.length()); } if (endMatcher.find()) { return endMatcher.start(2); } return -1; } @Override public boolean canStart(String line, int lineOffset) { blockLineCount = 0; if (lineOffset == 0) { matcher = START_PATTERN.matcher(line); return matcher.matches(); } else { matcher = null; return false; } } private void ensureRowStarted() { if (!rowStarted) { builder.beginBlock(BlockType.TABLE_ROW, new Attributes()); rowStarted = true; } } @Override public void setClosed(boolean closed) { if (closed && !isClosed()) { ensureRowClosed(); builder.endBlock(); } super.setClosed(closed); } private void ensureRowClosed() { ensureCellClosed(); if (rowStarted) { builder.endBlock(); rowStarted = false; } } private void ensureCellClosed() { if (currentCell != null) { builder.endBlock(); currentCell = null; multiLineCell = false; } } }