<commit_before>package name.abuchen.portfolio.ui.util.swt;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

import javax.xml.XMLConstants;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.TextLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import name.abuchen.portfolio.ui.util.Colors;

/**
 * Instances of this class represent a non-selectable user interface object that
 * displays a string or image that can be styled.
 * <p/>
 * Supported tags are:
 * <ul>
 * <li>green</li>
 * <li>red</li>
 * </ul>
 */
public class StyledLabel extends Canvas // NOSONAR
{
    private TextLayout textLayout;
    private SAXParserFactory spf;

    public StyledLabel(Composite parent, int style)
    {
        super(parent, style);

        try
        {
            spf = SAXParserFactory.newInstance();
            spf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
            spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); //$NON-NLS-1$
        }
        catch (Exception e)
        {
            throw new IllegalArgumentException(e);
        }

        this.textLayout = new TextLayout(parent.getDisplay());
        this.textLayout.setFont(getFont());

        addListener(SWT.Paint, this::handlePaint);
        addListener(SWT.Dispose, this::handleDispose);
    }

    public void setText(String text)
    {
        checkWidget();

        try
        {
            SAXParser parser = spf.newSAXParser();

            StringBuilder plainText = new StringBuilder();
            List<StyleRange> styleRanges = new ArrayList<>();

            DefaultHandler handler = new DefaultHandler()
            {
                private int pos = -1;

                @Override
                public void startElement(String uri, String localName, String qName, Attributes attributes)
                                throws SAXException
                {
                    if (!"text".equals(qName)) //$NON-NLS-1$
                        pos = plainText.length();
                }

                @Override
                public void endElement(String uri, String localName, String qName) throws SAXException
                {
                    if (pos < 0)
                        return;

                    if ("red".equals(qName)) //$NON-NLS-1$
                        styleRanges.add(new StyleRange(pos, plainText.length() - pos, Colors.DARK_RED, null));
                    else if ("green".equals(qName)) //$NON-NLS-1$
                        styleRanges.add(new StyleRange(pos, plainText.length() - pos, Colors.DARK_GREEN, null));

                    pos = -1;
                }

                @Override
                public void characters(char[] ch, int start, int length) throws SAXException
                {
                    plainText.append(ch, start, length);
                }
            };

            parser.parse(new ByteArrayInputStream(("<text>" + text + "</text>") //$NON-NLS-1$ //$NON-NLS-2$
                            .getBytes(StandardCharsets.UTF_8)), handler);

            this.textLayout.setText(plainText.toString());
            styleRanges.forEach(r -> this.textLayout.setStyle(r, r.start, r.start + r.length));
        }
        catch (ParserConfigurationException | SAXException | IOException e)
        {
            throw new IllegalArgumentException(e);
        }

        redraw();
    }
<commit_msg>Added support for bold and italic text in StyledLabel<commit_after>package name.abuchen.portfolio.ui.util.swt;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

import javax.xml.XMLConstants;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.eclipse.jface.resource.FontDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.TextLayout;
import org.eclipse.swt.graphics.TextStyle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import name.abuchen.portfolio.ui.util.Colors;

/**
 * Instances of this class represent a non-selectable user interface object that
 * displays a string or image that can be styled.
 * <p/>
 * Supported tags are:
 * <ul>
 * <li>green</li>
 * <li>red</li>
 * <li>strong</li>
 * <li>em</li>
 * </ul>
 */
public class StyledLabel extends Canvas // NOSONAR
{
    private TextLayout textLayout;
    private SAXParserFactory spf;

    private LocalResourceManager resourceManager = new LocalResourceManager(JFaceResources.getResources());

    public StyledLabel(Composite parent, int style)
    {
        super(parent, style);

        try
        {
            spf = SAXParserFactory.newInstance();
            spf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
            spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); //$NON-NLS-1$
        }
        catch (Exception e)
        {
            throw new IllegalArgumentException(e);
        }

        this.textLayout = new TextLayout(parent.getDisplay());
        this.textLayout.setFont(getFont());

        addListener(SWT.Paint, this::handlePaint);
        addListener(SWT.Dispose, this::handleDispose);
    }

    public void setText(String text)
    {
        checkWidget();

        try
        {
            SAXParser parser = spf.newSAXParser();

            StringBuilder plainText = new StringBuilder();
            List<StyleRange> styleRanges = new ArrayList<>();

            DefaultHandler handler = new DefaultHandler()
            {
                private int pos = -1;

                @Override
                public void startElement(String uri, String localName, String qName, Attributes attributes)
                                throws SAXException
                {
                    if (!"text".equals(qName)) //$NON-NLS-1$
                        pos = plainText.length();
                }

                @Override
                public void endElement(String uri, String localName, String qName) throws SAXException
                {
                    if (pos < 0)
                        return;

                    if ("red".equals(qName)) //$NON-NLS-1$
                        styleRanges.add(new StyleRange(pos, plainText.length() - pos, Colors.DARK_RED, null));
                    else if ("green".equals(qName)) //$NON-NLS-1$
                        styleRanges.add(new StyleRange(pos, plainText.length() - pos, Colors.DARK_GREEN, null));
                    else if ("strong".equals(qName)) //$NON-NLS-1$
                        styleRanges.add(new StyleRange(pos, plainText.length() - pos, null, null, SWT.BOLD));
                    else if ("em".equals(qName)) //$NON-NLS-1$
                        styleRanges.add(new StyleRange(pos, plainText.length() - pos, null, null, SWT.ITALIC));

                    pos = -1;
                }

                @Override
                public void characters(char[] ch, int start, int length) throws SAXException
                {
                    plainText.append(ch, start, length);
                }
            };

            parser.parse(new ByteArrayInputStream(("<text>" + text + "</text>") //$NON-NLS-1$ //$NON-NLS-2$
                            .getBytes(StandardCharsets.UTF_8)), handler);

            this.textLayout.setText(plainText.toString());
            styleRanges.forEach(r -> {
                if (r.fontStyle != SWT.NORMAL)
                {
                    Font font = resourceManager
                                    .createFont(FontDescriptor.createFrom(textLayout.getFont()).setStyle(r.fontStyle));
                    textLayout.setStyle(new TextStyle(font, r.foreground, r.background), r.start, r.start + r.length);
                }
                else
                {
                    textLayout.setStyle(r, r.start, r.start + r.length);
                }
            });
        }
        catch (ParserConfigurationException | SAXException | IOException e)
        {
            throw new IllegalArgumentException(e);
        }

        redraw();
    }

{'fraction_non_alphanumeric': 0.11083233336969565, 'fraction_numerical': 0.0023999127304461655, 'mean_word_length': 2.0207578253706755, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 16}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4444958', 'n_tokens_mistral': 2626, 'n_tokens_neox': 2523, 'n_words': 522}