org.fxmisc.richtext.demo.TextArea4XML Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of richtextfx Show documentation
Show all versions of richtextfx Show documentation
FX-Text-Area for formatted text and other special effects.
package org.fxmisc.richtext.demo;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.time.Duration;
import javafx.beans.value.ChangeListener;
import javafx.concurrent.Task;
import org.fxmisc.richtext.LineNumberFactory;
import org.fxmisc.richtext.model.StyleSpans;
import org.fxmisc.richtext.model.StyleSpansBuilder;
import com.sta.mlogger.MLogger;
/**
* Name: TextArea4XML
* Description: .
*
* Comment: ...
*
* Copyright: Copyright (c) 2018, 2019
* Company: >StA-Soft<
* @author StA
* @version 1.0
*/
public class TextArea4XML extends TextAreaBase
{
private static final Pattern XML_TAG = Pattern.compile(
// "(?(?\\h*)(\\w+)([^<>]*)(\\h*/?>))|" +
"(?(?\\??\\h*)(\\w+)([^<>]*)(\\h*/?\\??>))|" +
// "(?)" // Original = falsch!!!
"(?).)*-->)" // ?s = DOTALL = "." matcht auch Zeilenumbruch, somit Reduktion der Klammer-Ebenen und damit der Rekursion, sonst StackOverflow!
// "(?)" // \\R scheint falsch zu sein, es muss wohl \r hei?en, dann kommt noch \n hinzu, ist aber eine Klammer-Ebene zu viel, daher StackOverflow!
// "(?)" // "." funktioniert an dieser Stelle nicht
// "(?)" // "." funktioniert, aber wieder eine Klammer-Ebene zu viel, StackOverflow!
// "(?)(.|\\R))*-->)" // wieder eine Klammer-Ebene zu viel, StackOverflow!
);
private static final Pattern ATTRIBUTES = Pattern.compile("(\\w+:?\\w*\\h*)(=)(\\h*\"[^\"]*\")");
private static final int GROUP_OPEN_BRACKET = 2;
private static final int GROUP_ELEMENT_NAME = 3;
private static final int GROUP_ATTRIBUTES_SECTION = 4;
private static final int GROUP_CLOSE_BRACKET = 5;
private static final int GROUP_ATTRIBUTE_NAME = 1;
private static final int GROUP_EQUAL_SYMBOL = 2;
private static final int GROUP_ATTRIBUTE_VALUE = 3;
//===========================================================================
private static StyleSpans> computeHighlighting(String text)
{
Matcher matcher = XML_TAG.matcher(text);
int lastKwEnd = 0;
StyleSpansBuilder> spansBuilder = new StyleSpansBuilder<>();
while (matcher.find())
{
spansBuilder.add(Collections.emptyList(), matcher.start() - lastKwEnd);
if (matcher.group("COMMENT") != null)
{
spansBuilder.add(Collections.singleton("comment"), matcher.end() - matcher.start());
}
else
{
if (matcher.group("ELEMENT") != null)
{
String attributesText = matcher.group(GROUP_ATTRIBUTES_SECTION);
spansBuilder.add(Collections.singleton("tagmark"), matcher.end(GROUP_OPEN_BRACKET) - matcher.start(GROUP_OPEN_BRACKET));
spansBuilder.add(Collections.singleton("anytag"), matcher.end(GROUP_ELEMENT_NAME) - matcher.end(GROUP_OPEN_BRACKET));
if (!attributesText.isEmpty())
{
lastKwEnd = 0;
Matcher amatcher = ATTRIBUTES.matcher(attributesText);
while (amatcher.find())
{
spansBuilder.add(Collections.emptyList(), amatcher.start() - lastKwEnd);
spansBuilder.add(Collections.singleton("attribute"), amatcher.end(GROUP_ATTRIBUTE_NAME) - amatcher.start(GROUP_ATTRIBUTE_NAME));
spansBuilder.add(Collections.singleton("tagmark"), amatcher.end(GROUP_EQUAL_SYMBOL) - amatcher.end(GROUP_ATTRIBUTE_NAME));
spansBuilder.add(Collections.singleton("avalue"), amatcher.end(GROUP_ATTRIBUTE_VALUE) - amatcher.end(GROUP_EQUAL_SYMBOL));
lastKwEnd = amatcher.end();
}
if (attributesText.length() > lastKwEnd)
{
spansBuilder.add(Collections.emptyList(), attributesText.length() - lastKwEnd);
}
}
lastKwEnd = matcher.end(GROUP_ATTRIBUTES_SECTION);
spansBuilder.add(Collections.singleton("tagmark"), matcher.end(GROUP_CLOSE_BRACKET) - lastKwEnd);
}
}
lastKwEnd = matcher.end();
}
spansBuilder.add(Collections.emptyList(), text.length() - lastKwEnd);
return spansBuilder.create();
}
//===========================================================================
public TextArea4XML(boolean sync)
{
super();
init(sync);
}
public TextArea4XML(boolean sync, String text)
{
super(text);
init(sync);
}
public TextArea4XML(boolean sync, ChangeListener cl)
{
super(cl);
init(sync);
}
//===========================================================================
private void init(boolean sync)
{
setParagraphGraphicFactory(LineNumberFactory.get(this));
if (sync)
{
initSync();
}
else
{
initASync();
}
getStylesheets().add(getClass().getResource("xml-highlighting.css").toExternalForm());
}
private void initSync()
{
// ?bernommen aus Java/SQL:
richChanges()
.filter(ch -> !ch.getInserted().equals(ch.getRemoved())) // XXX
.subscribe(change ->
{
setStyleSpans(0, computeHighlighting(getText()));
});
/*
// Original f?r XML:
textProperty().addListener((obs, oldText, newText) -> {
setStyleSpans(0, computeHighlighting(newText));
});
*/
}
private void initASync()
{
richChanges()
.filter(ch -> !ch.getInserted().equals(ch.getRemoved())) // XXX
.successionEnds(Duration.ofMillis(500))
.supplyTask(this::computeHighlightingAsync)
.awaitLatest(richChanges())
.filterMap(t ->
{
if (t.isSuccess())
{
return Optional.of(t.get());
}
else
{
MLogger.err("", t.getFailure());
return Optional.empty();
}
})
.subscribe(this::applyHighlighting);
}
private Task>> computeHighlightingAsync()
{
String text = getText();
Task>> task = new Task>>()
{
@Override
protected StyleSpans> call() throws Exception
{
return computeHighlighting(text);
}
};
ExecutorHelper.getExecutor().execute(task);
return task;
}
private void applyHighlighting(StyleSpans> highlighting)
{
setStyleSpans(0, highlighting);
}
}