All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tri.ai.text.docs.FormattedText.kt Maven / Gradle / Ivy

/*-
 * #%L
 * tri.promptfx:promptfx
 * %%
 * Copyright (C) 2023 - 2025 Johns Hopkins University Applied Physics Laboratory
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package tri.ai.text.docs

import com.fasterxml.jackson.annotation.JsonIgnore

/** Basic markup text that may contain formatted elements and links. */
class FormattedText(val nodes: List) {
    constructor(text: String) : this(listOf(FormattedTextNode(text)))

    @get:JsonIgnore
    var hyperlinkOp: (String) -> Unit = { }

    override fun toString() = nodes.joinToString("") { it.text }
}

/** A node within [FormattedText]. */
data class FormattedTextNode(
    val text: String,
    val style: String? = null,
    val hyperlink: String? = null
)

//region CONVERSIONS

/** Convert a [FormattedText] to HTML string. */
fun FormattedText.toHtml(): String {
    val text = StringBuilder("\n")
    nodes.forEach {
        val style = it.style ?: ""
        val bold = style.contains("-fx-font-weight: bold")
        val italic = style.contains("-fx-font-style: italic")
        val color = if (style.contains("-fx-fill:"))
            style.substringAfter("-fx-fill:").let { it.substringBefore(";", it).trim() }
        else null
        val prefix = (if (bold) "" else "") + (if (italic) "" else "") +
                (if (color != null) "" else "")
        val textWithBreaks = it.text.replace("\n", "
") val suffix = (if (color != null) "
" else "") + (if (italic) "
" else "") + (if (bold) "
" else "") text.append(prefix) if (it.hyperlink != null) text.append("${it.hyperlink}") else text.append(textWithBreaks) text.append(suffix) } return text.toString() } //endregion //region SPLITTING UTILS /** Splits this node by a [Regex], keeping the same style and hyperlink. */ fun FormattedTextNode.splitOn(find: Regex, replace: (String) -> FormattedTextNode): List { if (hyperlink != null) return listOf(this) val result = mutableListOf() var index = 0 find.findAll(text).forEach { val text0 = text.substring(index, it.range.first) if (text0.length > 1) { result += FormattedTextNode(text0, style, hyperlink) } result += replace(it.value) index = it.range.last + 1 } result += FormattedTextNode(text.substring(index), style, hyperlink) return result } /** Splits all text elements on a given search string. */ internal fun MutableList.splitOn(find: String, replace: (String) -> FormattedTextNode) = splitOn(Regex.fromLiteral(find), replace) /** Splits all text elements on a given search string. */ internal fun MutableList.splitOn(find: Regex, replace: (String) -> FormattedTextNode) = toList().forEach { val newNodes = it.splitOn(find, replace) if (newNodes != listOf(it)) { addAll(indexOf(it), newNodes) remove(it) } } //endregion




© 2015 - 2025 Weber Informatics LLC | Privacy Policy