| /***************************************************************************** |
| * 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; |
| } |
| |
| } |