keep a match's quotes and brackets around the replacement in replace()

This commit is contained in:
dualfroz committed 2026-10-14 19:51:12 +02:00
1 parent b3dcee7617
commit 3613d1a0e2
2 files changed
+75 -14

No files matched your search

+35 -14
View File
@@ -1,3 +1,5 @@
import fmts from '../../output/api/_fmts.js'
const dollarStub = /\$[0-9a-z]+/g
const fns = {}
@@ -6,10 +8,30 @@ const isTitleCase = (str) => /^\p{Lu}[\p{Ll}'’]/u.test(str) || /^\p{Lu}$/u.tes
const toTitleCase = (str) => str.replace(/^\p{Ll}/u, x => x.toUpperCase())
const toLowerCase = (str) => str.replace(/^\p{Lu}/u, x => x.toLowerCase())
// the match's pre and post are kept by replaceWith(), so m.text() should leave them out
const withoutEnds = function (m) {
const text = m.text
m.text = fmt => {
const opts = typeof fmt === 'string' ? fmts[fmt] : fmt
return text.call(m, { keepEndPunct: false, ...opts })
}
return m
}
// put the match's own punctuation around the replacement's, unless it already has it
const outerPre = (pre, fresh) => {
const punct = pre.trimStart()
return fresh.startsWith(punct) ? pre.slice(0, pre.length - punct.length) + fresh : pre + fresh
}
const outerPost = (post, fresh) => {
const punct = post.trimEnd()
return fresh.endsWith(punct) ? fresh + post.slice(punct.length) : fresh + post
}
// doc.replace('foo', (m)=>{})
const replaceByFn = function (main, fn, keep) {
main.forEach(m => {
const out = fn(m)
const out = fn(withoutEnds(m))
m.replaceWith(out, keep)
})
return main
@@ -58,13 +80,23 @@ fns.replaceWith = function (input, keep = {}) {
ptrs = ptrs.map(ptr => ptr.slice(0, 3))
// original.freeze()
let oldTags = (original.docs[0] || []).map(term => Array.from(term.tags))
const originalPre = original.docs[0][0].pre
const originalPost = original.docs[0].at(-1).post
const originalEnds = original.docs.map(words => [words[0].pre, words.at(-1).post])
// slide this in
if (typeof input === 'string') {
input = this.fromText(input).compute('id')
}
const fresh = input.docs[0]
main.insertAfter(input)
// keep the punctuation around each match, outside the replacement's own
if (fresh) {
main.docs.forEach((words, i) => {
const [pre, post] = originalEnds[i]
words.at(-fresh.length).pre = outerPre(pre, fresh[0].pre.trimStart())
if (post) {
words.at(-1).post = outerPost(post, fresh.at(-1).post.trimEnd())
}
})
}
// are we replacing part of a contraction?
if (original.has('@hasContraction') && main.contractions) {
const more = main.grow('@hasContraction+')
@@ -84,17 +116,6 @@ fns.replaceWith = function (input, keep = {}) {
}
}
// try to keep some pre-punctuation
if (originalPre && main.docs[0]) {
main.docs[0][0].pre = originalPre
}
// try to keep any post-punctuation
if (originalPost && main.docs[0]) {
const lastOne = main.docs[0].at(-1)
if (!lastOne.post.trim()) {
lastOne.post = originalPost
}
}
// what should we return?
const m = main.toView(ptrs).compute(['index', 'freeze', 'lexicon'])
if (m.world.compute.preTagger) {
+40
View File
@@ -225,3 +225,43 @@ test('replace-with-pre-post', function (t) {
t.end()
})
test('replace-edge-punctuation', function (t) {
let arr = [
['"hello world" he said', '"[x]" he said'],
['he said "hello world" today', 'he said "[x]" today'],
['he said "hello world"', 'he said "[x]"'],
['(hello world) he said', '([x]) he said'],
['he said (hello world) today', 'he said ([x]) today'],
['he said hello world.', 'he said [x].'],
['"hello world" and "hello world" too', '"[x]" and "[x]" too'],
]
arr.forEach(a => {
const doc = nlp(a[0])
doc.replace('hello world', '[x]')
t.equal(doc.text(), a[1], here + 'string: ' + a[0])
})
arr = [
['"hello world" he said', '"HELLO WORLD" he said'],
['he said "hello world" today', 'he said "HELLO WORLD" today'],
['he said "hello world"', 'he said "HELLO WORLD"'],
['(hello world) he said', '(HELLO WORLD) he said'],
['hello world.', 'HELLO WORLD.'],
['"hello world" and "hello world" too', '"HELLO WORLD" and "HELLO WORLD" too'],
]
arr.forEach(a => {
const doc = nlp(a[0])
doc.replace('hello world', m => m.text().toUpperCase())
t.equal(doc.text(), a[1], here + 'function: ' + a[0])
})
let doc = nlp('"hello world" he said')
doc.replace('hello world', m => '[' + m.text() + ']')
t.equal(doc.text(), '"[hello world]" he said', here + 'function with brackets')
doc = nlp('he said "hello" today')
doc.replace('hello', '"hi"')
t.equal(doc.text(), 'he said "hi" today', here + 'quotes already in replacement')
t.end()
})