blob: bd88a4a29986f3e67e2ea22d6228a28d8659edff [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2016, 2020 Dirk Fauth.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Dirk Fauth <dirk.fauth@googlemail.com> - Initial API and implementation
*
*****************************************************************************/
package org.eclipse.nebula.widgets.nattable.extension.nebula.richtext;
import java.io.StringReader;
import java.util.regex.Pattern;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.XMLEvent;
import org.eclipse.nebula.widgets.richtext.RichTextPainter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link MarkupProcessor} implementation that is able to process regular
* expression values to identify content that should be surrounded by a markup.
* The regular expression needs to contain one group so the replacement can be
* done by using a placeholder.
*
* @since 1.1
*/
public class RegexMarkupValue implements MarkupProcessor {
private static final Logger LOG = LoggerFactory.getLogger(RegexMarkupValue.class);
private static final String GROUP_INDEX_PLACEHOLDER = "$1";
private String originalRegexValue;
private String markupPrefix;
private String markupSuffix;
private String markupValue;
private String markupRegexValue;
private XMLInputFactory factory = XMLInputFactory.newInstance();
private boolean caseInsensitive = true;
private boolean unicodeCase = false;
/**
*
* @param value
* The regular expression that specifies the value that should be
* surrounded by a markup.
* @param markupPrefix
* The String that should be added as prefix.
* @param markupSuffix
* The String that should be added as suffix.
*/
public RegexMarkupValue(String value, String markupPrefix, String markupSuffix) {
this.originalRegexValue = value;
this.markupPrefix = markupPrefix;
this.markupSuffix = markupSuffix;
this.markupValue = markupPrefix + GROUP_INDEX_PLACEHOLDER + markupSuffix;
this.markupRegexValue = markupPrefix + value + markupSuffix;
}
@Override
public String applyMarkup(String input) {
String result = "";
if (getOriginalRegexValue() != null && !getOriginalRegexValue().isEmpty()) {
XMLEventReader parser = null;
try (StringReader reader = new StringReader(RichTextPainter.FAKE_ROOT_TAG_START + input + RichTextPainter.FAKE_ROOT_TAG_END)) {
parser = this.factory.createXMLEventReader(reader);
while (parser.hasNext()) {
XMLEvent event = parser.nextEvent();
switch (event.getEventType()) {
case XMLStreamConstants.START_DOCUMENT:
break;
case XMLStreamConstants.END_DOCUMENT:
parser.close();
break;
case XMLStreamConstants.CHARACTERS:
Characters characters = event.asCharacters();
String text = characters.getData();
if (this.caseInsensitive) {
int flags = this.unicodeCase ? Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE : Pattern.CASE_INSENSITIVE;
result += Pattern.compile(getOriginalRegexValue(), flags).matcher(text).replaceAll(this.markupValue);
} else {
result += Pattern.compile(getOriginalRegexValue()).matcher(text).replaceAll(this.markupValue);
}
break;
default:
result += event.toString();
}
}
} catch (XMLStreamException e) {
LOG.error("Error on XML processing", e);
} finally {
if (parser != null) {
try {
parser.close();
} catch (XMLStreamException e) {
LOG.error("Error on closing the XMLEventReader", e);
}
}
}
result = result.replace(RichTextPainter.FAKE_ROOT_TAG_START, "").replace(RichTextPainter.FAKE_ROOT_TAG_END, "");
} else {
result = input;
}
return result;
}
@Override
public String removeMarkup(String input) {
if (getOriginalRegexValue() != null && !getOriginalRegexValue().isEmpty()) {
return input.replaceAll(getMarkupRegexValue(), GROUP_INDEX_PLACEHOLDER);
}
return input;
}
/**
* Set the regular expression that specifies the value that should be
* surrounded by a markup.
*
* @param value
* The regular expression that specifies the value that should be
* surrounded by a markup.
*/
public void setRegexValue(String value) {
this.originalRegexValue = value;
this.markupRegexValue = this.markupPrefix + value + this.markupSuffix;
}
/**
* Returns the regular expression that specifies the value that should be
* surrounded by a markup. Subclasses can override this method to provide a
* dynamic markup value, e.g. for highlighting values inserted into a text
* field.
*
* @return The regular expression that specifies the value that should be
* surrounded by a markup.
*/
protected String getOriginalRegexValue() {
return this.originalRegexValue;
}
/**
* Returns the regular expression that specifies the value that should be
* surrounded by a markup, with the applied markup.
*
* @return The original regex value with applied markup.
*/
protected String getMarkupRegexValue() {
return this.markupRegexValue;
}
/**
* Return whether the {@link Pattern#CASE_INSENSITIVE} flag is applied to
* enable case insensitive matching.
* <p>
* <b>Note:</b> Case-insensitive matching can also be enabled via the
* embedded flag expression (?i) if the flag in this
* {@link RegexMarkupValue} is disabled.
* </p>
*
* @return <code>true</code> if case insensitive matching is enabled,
* <code>false</code> if matching is case sensitive.
*
* @since 1.2
*/
public boolean isCaseInsensitive() {
return this.caseInsensitive;
}
/**
* Configure whether the {@link Pattern#CASE_INSENSITIVE} flag should be
* applied to enable case insensitive matching.
* <p>
* <b>Note:</b> Case-insensitive matching can also be enabled via the
* embedded flag expression (?i) if the flag in this
* {@link RegexMarkupValue} is disabled.
* </p>
*
* @param caseInsensitive
* <code>true</code> if case insensitive matching should be
* enabled, <code>false</code> if matching should be case
* sensitive.
*
* @since 1.2
*/
public void setCaseInsensitive(boolean caseInsensitive) {
this.caseInsensitive = caseInsensitive;
}
/**
* Return whether the {@link Pattern#UNICODE_CASE} flag is applied to enable
* Unicode aware case folding. Only works if case insensitive matching is
* enabled.
* <p>
* <b>Note:</b> Unicode-aware case folding can also be enabled via the
* embedded flag expression (?u) if the flag in this
* {@link RegexMarkupValue} is disabled.
* </p>
*
* @return <code>true</code> if Unicode aware case folding is enabled,
* <code>false</code> if case-insensitive matching assumes that only
* characters in the US-ASCII charset are being matched.
*
* @since 1.2
*/
public boolean isUnicodeCase() {
return this.unicodeCase;
}
/**
* Configure whether the {@link Pattern#UNICODE_CASE} flag should be applied
* to enable Unicode aware case folding. Only works if case insensitive
* matching is enabled.
* <p>
* <b>Note:</b> Unicode-aware case folding can also be enabled via the
* embedded flag expression (?u) if the flag in this
* {@link RegexMarkupValue} is disabled.
* </p>
*
* @param unicodeCase
* <code>true</code> if Unicode aware case folding should be
* enabled, <code>false</code> if case-insensitive matching
* should only match characters in the US-ASCII charset.
*
* @since 1.2
*/
public void setUnicodeCase(boolean unicodeCase) {
this.unicodeCase = unicodeCase;
}
}