From 9c93fa6bb39b54f70c8e0f15a62bfe89bb578a96 Mon Sep 17 00:00:00 2001 From: Andrew Valencik Date: Mon, 18 Nov 2024 13:56:41 -0500 Subject: [PATCH] Add FragmentFormatter --- .../highlight/FragmentFormatter.scala | 51 +++++++++++++++++ .../highlight/FragmentFormatterSuite.scala | 55 +++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 core/src/main/scala/pink/cozydev/protosearch/highlight/FragmentFormatter.scala create mode 100644 core/src/test/scala/pink/cozydev/protosearch/highlight/FragmentFormatterSuite.scala diff --git a/core/src/main/scala/pink/cozydev/protosearch/highlight/FragmentFormatter.scala b/core/src/main/scala/pink/cozydev/protosearch/highlight/FragmentFormatter.scala new file mode 100644 index 00000000..408d873e --- /dev/null +++ b/core/src/main/scala/pink/cozydev/protosearch/highlight/FragmentFormatter.scala @@ -0,0 +1,51 @@ +package pink.cozydev.protosearch.highlight + +case class FragmentFormatter( + maxSize: Int, + startTag: String, + endTag: String, +) { + private val tagSize = startTag.size + endTag.size + private val tags: Array[String] = Array(startTag, endTag) + + def format(fragment: String, offsets: Iterable[Int]): String = + if (offsets.size == 0) fragment + else { + val offsetArr = offsets.toArray + assert(offsetArr.size % 2 == 0, "even number of offsets required") + + val sb = new StringBuilder() + sb.sizeHint(fragment.size + tagSize * (offsets.size / 2)) + val chars = fragment.toCharArray() + + try { + // Add initial characters + sb.appendAll(chars, 0, offsetArr(0)) + var charsOffset = offsetArr(0) + + // Loop through offsets, two at a time + var i = 0 + while (i < offsetArr.size) { + val offsetStart = offsetArr(i) + // add chars between offsets + val inbetweenChars = offsetStart - charsOffset + sb.appendAll(chars, charsOffset, inbetweenChars) + charsOffset += inbetweenChars + + // start new offset + sb.append(startTag) + val offsetLength = offsetArr(i + 1) + sb.appendAll(chars, charsOffset, offsetLength) + sb.append(endTag) + charsOffset += offsetLength + i += 2 + } + // Add remaining characters + sb.appendAll(chars, charsOffset, chars.size - charsOffset) + sb.result() + } catch { + case _: IndexOutOfBoundsException => + throw new IllegalArgumentException("Offset exceeded string length") + } + } +} diff --git a/core/src/test/scala/pink/cozydev/protosearch/highlight/FragmentFormatterSuite.scala b/core/src/test/scala/pink/cozydev/protosearch/highlight/FragmentFormatterSuite.scala new file mode 100644 index 00000000..d5ac0f56 --- /dev/null +++ b/core/src/test/scala/pink/cozydev/protosearch/highlight/FragmentFormatterSuite.scala @@ -0,0 +1,55 @@ +package pink.cozydev.protosearch.highlight + +import munit.FunSuite + +class FragmentFormatterSuite extends FunSuite { + val formatter = FragmentFormatter(100, "", "") + + test("formats string with empty offsets") { + val s = "hello world" + val actual = formatter.format(s, List.empty) + val expected = s + assertEquals(actual, expected) + } + + test("formats string with one pair of offsets") { + val s = "hello world" + val actual = formatter.format(s, List(6, 5)) + val expected = "hello world" + assertEquals(actual, expected) + } + + test("formats string with offset in the middle") { + val s = "hello world, how are you?" + val actual = formatter.format(s, List(6, 5)) + val expected = "hello world, how are you?" + assertEquals(actual, expected) + } + + test("formats string with two pair of offsets") { + val s = "hello world" + val actual = formatter.format(s, List(0, 5, 6, 5)) + val expected = "hello world" + assertEquals(actual, expected) + } + + test("formats whole string if one offset") { + val s = "hello world" + val actual = formatter.format(s, List(0, 11)) + val expected = "hello world" + assertEquals(actual, expected) + } + + test("throws if offset length exceeds string boundary") { + val s = "hello world" + intercept[java.lang.IllegalArgumentException] { + formatter.format(s, List(6, 5 + 1)) + } + } + + test("throws if odd number of offset integers") { + intercept[java.lang.AssertionError] { + formatter.format("", List(1)) + } + } +}