From f51684367a0d7950f9043e79d4c3ddce351e5bec Mon Sep 17 00:00:00 2001 From: OneWay Date: Fri, 18 Sep 2026 01:50:00 +0800 Subject: [PATCH] fix(dsl): render articles faithfully Split lines on LF, CRLF and lone CR like the indexer, strip leading whitespace from body lines, break the paragraph after a closing margin tag, and apply named colors from [c] tags. --- .../research/data/parser/DslArticleReader.kt | 11 ++- .../research/ui/article/DslAnnotatedParser.kt | 95 ++++++++++++++----- 2 files changed, 77 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/com/example/research/data/parser/DslArticleReader.kt b/app/src/main/java/com/example/research/data/parser/DslArticleReader.kt index 537f0bb..87be527 100644 --- a/app/src/main/java/com/example/research/data/parser/DslArticleReader.kt +++ b/app/src/main/java/com/example/research/data/parser/DslArticleReader.kt @@ -180,14 +180,19 @@ internal fun parseDslArticleContent(rawContent: String): String { while (i < len) { val lineStart = i - while (i < len && rawContent[i] != '\n') i++ + while (i < len && rawContent[i] != '\n' && rawContent[i] != '\r') i++ val lineEnd = i - if (i < len && rawContent[i] == '\n') i++ + if (i < len && rawContent[i] == '\r') { + i++ + if (i < len && rawContent[i] == '\n') i++ + } else if (i < len && rawContent[i] == '\n') { + i++ + } if (lineStart == lineEnd && i >= len) break val line = rawContent.substring(lineStart, lineEnd) - val trimmed = line.trimEnd() + val trimmed = line.trim() if (trimmed.isEmpty()) continue if (skippingHeadwords) { diff --git a/app/src/main/java/com/example/research/ui/article/DslAnnotatedParser.kt b/app/src/main/java/com/example/research/ui/article/DslAnnotatedParser.kt index 3c5228b..7f61c77 100644 --- a/app/src/main/java/com/example/research/ui/article/DslAnnotatedParser.kt +++ b/app/src/main/java/com/example/research/ui/article/DslAnnotatedParser.kt @@ -24,6 +24,40 @@ object DslAnnotatedParser { "ref", "ex", "e", "trn", "com", "lang", "sup", "'" ) + private val DSL_NAMED_COLORS = mapOf( + "black" to Color(0xFF000000), + "silver" to Color(0xFFC0C0C0), + "gray" to Color(0xFF808080), + "grey" to Color(0xFF808080), + "lightgray" to Color(0xFFD3D3D3), + "lightgrey" to Color(0xFFD3D3D3), + "darkgray" to Color(0xFFA9A9A9), + "darkgrey" to Color(0xFFA9A9A9), + "white" to Color(0xFFFFFFFF), + "maroon" to Color(0xFF800000), + "red" to Color(0xFFFF0000), + "darkred" to Color(0xFF8B0000), + "crimson" to Color(0xFFDC143C), + "brown" to Color(0xFFA52A2A), + "orange" to Color(0xFFFFA500), + "pink" to Color(0xFFFFC0CB), + "purple" to Color(0xFF800080), + "violet" to Color(0xFFEE82EE), + "fuchsia" to Color(0xFFFF00FF), + "magenta" to Color(0xFFFF00FF), + "olive" to Color(0xFF808000), + "yellow" to Color(0xFFFFFF00), + "green" to Color(0xFF008000), + "darkgreen" to Color(0xFF006400), + "lime" to Color(0xFF00FF00), + "teal" to Color(0xFF008080), + "aqua" to Color(0xFF00FFFF), + "cyan" to Color(0xFF00FFFF), + "navy" to Color(0xFF000080), + "blue" to Color(0xFF0000FF), + "darkblue" to Color(0xFF00008B), + ) + data class ColorScheme( val secondaryText: Color, val labelText: Color, @@ -54,9 +88,9 @@ object DslAnnotatedParser { val stack = ArrayDeque() val refStack = ArrayDeque>() val lazyRefs = mutableListOf() - var lastChar: Char? = null var accentNext = false var currentIndent = 0 + var mClosedPendingBreak = false val exampleStyle = SpanStyle(color = colorScheme.secondaryText) val labelStyle = SpanStyle(color = colorScheme.labelText) val linkStyle = SpanStyle(color = colorScheme.linkText, textDecoration = TextDecoration.Underline) @@ -84,6 +118,20 @@ object DslAnnotatedParser { stack.forEach { builder.pushStyle(it) } lazyRefs.clear() } + mClosedPendingBreak = false + } + + fun appendChar(char: Char) { + if (mClosedPendingBreak) { + mClosedPendingBreak = false + if (refStack.isEmpty() && !char.isWhitespace()) { + flushBlock() + } + } + builder.append(char) + if (refStack.isNotEmpty()) { + refStack.last().second.append(char) + } } fun marginIndent(tagName: String): Int { @@ -99,20 +147,11 @@ object DslAnnotatedParser { when (val char = dsl[i]) { '\\' -> { if (i + 1 < length) { - val next = dsl[i + 1] - builder.append(next) - if (refStack.isNotEmpty()) { - refStack.last().second.append(next) - } - lastChar = next + appendChar(dsl[i + 1]) i += 2 continue } - builder.append(char) - if (refStack.isNotEmpty()) { - refStack.last().second.append(char) - } - lastChar = char + appendChar(char) i++ } '[' -> { @@ -148,6 +187,9 @@ object DslAnnotatedParser { } else if (tagName == "'") { accentNext = false } + if (tagName.startsWith("m")) { + mClosedPendingBreak = true + } pop() } else { when (tagName) { @@ -161,17 +203,15 @@ object DslAnnotatedParser { } else { builder.append("\n") } - lastChar = '\n' } "p" -> { push(labelStyle) } "b" -> push(SpanStyle(fontWeight = FontWeight.Bold)) "m", "m0", "m1", "m2", "m3", "m4", "m5" -> { - if (lastChar != '\n' && lastChar != null) { + if (builder.length > 0) { if (refStack.isEmpty()) flushBlock() else builder.append("\n") } - lastChar = '\n' currentIndent = marginIndent(tagName) push(exampleStyle) } @@ -181,7 +221,18 @@ object DslAnnotatedParser { refStack.addLast(builder.length to StringBuilder()) } "ex", "e" -> push(exampleStyle) - "c", "t", "trn", "lang", "sup" -> push(SpanStyle()) + "c" -> { + val colorName = if (spaceIdx >= 0) { + tagWithParams.substring(spaceIdx + 1).trim().lowercase() + } else { + null + } + push(when { + colorName == null -> labelStyle + else -> DSL_NAMED_COLORS[colorName]?.let { SpanStyle(color = it) } ?: SpanStyle() + }) + } + "t", "trn", "lang", "sup" -> push(SpanStyle()) "com" -> push(SpanStyle(fontStyle = FontStyle.Italic, color = colorScheme.labelText)) } } @@ -189,18 +240,11 @@ object DslAnnotatedParser { continue } } - builder.append(char) - if (refStack.isNotEmpty()) { - refStack.last().second.append(char) - } - lastChar = char + appendChar(char) i++ } else -> { - builder.append(char) - if (refStack.isNotEmpty()) { - refStack.last().second.append(char) - } + appendChar(char) if (accentNext) { builder.append('\u0301') @@ -210,7 +254,6 @@ object DslAnnotatedParser { accentNext = false } - lastChar = char i++ } }