【问题标题】:String manipulation JavaScript - replace placeholders字符串操作 JavaScript - 替换占位符
【发布时间】:2021-09-26 04:11:30
【问题描述】:

我有一个很长的字符串,我必须以特定的方式对其进行操作。该字符串可以包含导致我的代码出现问题的其他子字符串。出于这个原因,对字符串进行任何操作之前,我将所有子字符串(由" 引入并以非转义" 结尾的任何内容)替换为以下格式的占位符:$0$1, $2, ..., $n。我确定主字符串本身不包含字符$,但子字符串之一(或更多)可能是例如"$0"

现在的问题是:在操作/格式化主字符串之后,我需要再次用它们的实际值替换所有占位符。

方便我将它们保存为这种格式:

// TypeScript
let substrings: { placeholderName: string; value: string }[];

但是这样做:

// JavaScript
let mainString1 = "main string $0 $1";
let mainString2 = "main string $0 $1";

let substrings = [
  { placeholderName: "$0", value: "test1 $1" },
  { placeholderName: "$1", value: "test2" }
];

for (const substr of substrings) {
  mainString1 = mainString1.replace(substr.placeholderName, substr.value);
  mainString2 = mainString2.replaceAll(substr.placeholderName, substr.value);
}

console.log(mainString1); // expected result: "main string test1 test2 $1"
console.log(mainString2); // expected result: "main string test1 test2 test2"

// wanted result: "main string test1 $1 test2"

不是一个选项,因为 子字符串 可能包含 $x,这将替换错误的内容(.replace().replaceAll())。

获取子字符串是使用正则表达式存档的,也许正则表达式在这里也可以提供帮助?虽然我无法控制子字符串中保存的内容...

【问题讨论】:

  • 您确定要main string test1 $1 test2 而不是main string test1 test2main string test1 $1
  • 是的,100% 确定..
  • 当然,我现在完全明白了。我问的时候是凌晨 4 点:D
  • 您可以控制占位符吗?他们会一直是$x吗?
  • 太好了,现在应该一切都好!

标签: javascript regex string replace jsonplaceholder


【解决方案1】:

如果您确定所有占位符都将遵循 $x 格式,我会使用带有回调的 .replace() 方法:

const result = mainString1.replace(
  /\$\d+/g,
  placeholder => substrings.find(
    substring => substring.placeholderName === placeholder
  )?.value ?? placeholder
);

// result is "main string test1 $1 test2"

【讨论】:

    【解决方案2】:

    关键是要选择一个在主串和子串中都不可能出现的占位符。我的诀窍是使用不可打印的字符作为占位符。我最喜欢的是NUL 字符(0x00),因为大多数其他人不会使用它,因为 C/C++ 认为它是字符串的结尾。然而,Javascript 足够强大,可以处理包含 NUL(编码为 un​​icode \0000)的字符串:

    let mainString1 = "main string \0-0 \0-1";
    let mainString2 = "main string \0-0 \0-1";
    
    let substrings = [
      { placeholderName: "\0-0", value: "test1 $1" },
      { placeholderName: "\0-1", value: "test2" }
    ];
    

    您的其余代码无需更改。

    请注意,我使用 - 字符来防止 javascript 将您的数字 01 解释为八进制 \0 的一部分。

    如果您像大多数程序员一样厌恶\0,那么您可以使用任何其他非打印字符,例如\1(标题开始)、007(使您的终端发出铃声的字符 -还有,詹姆斯邦德)等。

    【讨论】:

    • 关键是要实现正确的算法,而不是使用“不可能的占位符”。幼稚的方法。
    • @RoboRobok 对于文本,这将是一个相当正确的算法。除非你打算处理 JPEG
    • 我相信“永远不要相信你的意见”的方法胜过任何事情。不确定 OP 的数据来自哪里,但理论上我可以看到输入中存在 \0 字节。说真的,当有人认为某些内容不会出现在免费输入中时,我总是感到畏缩。
    • 我不能使用\1-0,因为我使用的是TypeScript(在严格的ofc模式下)并且它确实与消息octal escape sequences can't be used in untagged template literals or in strict mode code有关。 \0-0 也可以正常工作(尽管我不会使用它导致 NULL)
    • @slebetman 肯定的是,你的解决方案实际上很聪明,在现实生活中它引起问题的可能性几乎为零。但是,如果输入是原始的并且来自公众,它理论上可能会被黑客入侵。我可能有时对这些事情过于严格,我实际上发现你的想法与\0 一个有趣的发明。
    【解决方案3】:

    这可能不是最有效的代码。但是这里是我用 cmets 做的函数。

    注意:要小心,因为如果您将相同的占位符放在自身内部,它将创建一个无限循环。例如:

    { placeholderName: "$1", value: "test2 $1" }
    

    let mainString1 = "main string $0 $1";
    let mainString2 = "main string $0 $1";
    
    let substrings = [{
        placeholderName: "$0",
        value: "test1 $1"
      },
      {
        placeholderName: "$1",
        value: "test2"
      },
    ];
    
    function replacePlaceHolders(mainString, substrings) {
      let replacedString = mainString
    
      //We will find every placeHolder, the followin line wil return and array with all of them. Ex: ['$1', $n']
      let placeholders = replacedString.match(/\$[0-9]*/gm)
    
      //while there is some place holder to replace
      while (placeholders !== null && placeholders.length > 0) {
        //We will iterate for each placeholder
        placeholders.forEach(placeholder => {
          //extrac the value to replace
          let value = substrings.filter(x => x.placeholderName === placeholder)[0].value
          //replace it
          replacedString = replacedString.replace(placeholder, value)
        })
        //and finally see if there is any new placeHolder inserted in the replace. If there is something the loop will start again.
        placeholders = replacedString.match(/\$[0-9]*/gm)
      }
    
      return replacedString
    }
    
    console.log(replacePlaceHolders(mainString1, substrings))
    console.log(replacePlaceHolders(mainString2, substrings))

    编辑:

    好的...我想我现在明白了您的问题...您不希望替换值中的 placeHoldersLike 字符串。

    此版本的代码应该可以按预期工作,您不必担心这里的 infine 循环。但是,请注意您的占位符,“$”是正则表达式中的保留字符,您应该避免使用它们。我假设你所有的 placeHolders 都会像“$1”、“$2”等。如果不是,你应该编辑 regexPlaceholder 函数来包装和转义该字符。

    let mainString1 = "main string $0 $1";
    let mainString2 = "main string $0 $1 $2";
    
    let substrings = [
        { placeholderName: "$0", value: "$1 test1 $2 $1" },
        { placeholderName: "$1", value: "test2 $2" },
        { placeholderName: "$2", value: "test3" },
    
    ];
    
    function replacePlaceHolders(mainString, substrings) {
    
        //You will need to escape the $ characters or maybe even others depending of how you made your placeholders
        function regexPlaceholder(p) {
            return new RegExp('\\' + p, "gm")
        }
    
        let replacedString = mainString
      //We will find every placeHolder, the followin line wil return and array with all of them. Ex: ['$1', $n']
        let placeholders = replacedString.match(/\$[0-9]*/gm)
        //if there is any placeHolder to replace
        if (placeholders !== null && placeholders.length > 0) {
    
            //we will declare some variable to check if the values had something inside that can be 
            //mistaken for a placeHolder. 
            //We will store how many of them have we changed and replace them back at the end
            let replacedplaceholdersInValues = []
            let indexofReplacedValue = 0
    
            placeholders.forEach(placeholder => {
                //extrac the value to replace
                let value = substrings.filter(x => x.placeholderName === placeholder)[0].value
    
                //find if the value had a posible placeholder inside
                let placeholdersInValues = value.match(/\$[0-9]*/gm)
                if (placeholdersInValues !== null && placeholdersInValues.length > 0) {
                    placeholdersInValues.forEach(placeholdersInValue => {
                        //if there are, we will replace them with another mark, so our primary function wont change them
                        value = value.replace(regexPlaceholder(placeholdersInValue), "<markToReplace" + indexofReplacedValue + ">")
                        //and store every change to make a rollback later
                        replacedplaceholdersInValues.push({
                            placeholderName: placeholdersInValue,
                            value: "<markToReplace" + indexofReplacedValue + ">"
                        })
    
                    })
                    indexofReplacedValue++
                }
                //replace the actual placeholders
                replacedString = replacedString.replace(regexPlaceholder(placeholder), value)
            })
    
            //if there was some placeholderlike inside the values, we change them back to normal
            if (replacedplaceholdersInValues.length > 0) {
                replacedplaceholdersInValues.forEach(replaced => {
                    replacedString = replacedString.replace(replaced.value, replaced.placeholderName)
                })
            }
        }
    
        return replacedString
    }
    
    console.log(replacePlaceHolders(mainString1, substrings))
    console.log(replacePlaceHolders(mainString2, substrings))

    【讨论】:

    • 代码应该返回“main string test1 $1 test2”而不是“main string test1 test2 test2”
    • 但是您不希望 $1 标签再次被其值替换吗?也许我误解了你的问题。
    • 我想我已经用第二个版本解决了你想要的问题。 “值”中的占位符现在不会被替换
    • 是的,它确实适用于当前字符串,但我几乎无法控制子字符串,而且我知道有人会尝试破坏它,因此例如在子字符串中包含 "&lt;markToReplace1&gt;" 会还是打破它...但是可能没有好的解决方案...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-05-11
    • 2020-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多