替换字符串中的标记

Posted

技术标签:

【中文标题】替换字符串中的标记【英文标题】:Replacing tokens inside string 【发布时间】:2021-03-30 19:39:31 【问题描述】:

我想要做的是用类似语法[[VULKAN_SDK]] 在字符串中替换所有标记。我想获取括号的内容(在本例中为VULKAN_SDK),并通过传递括号的内容将整个令牌替换为我从getenv 获得的值。示例 [[VULKAN_SDK]]C:\VulkanSDK\1.2.148.1(系统环境变量)替换。 如果环境变量不存在,则不应替换令牌。 这是我的代码:

void replaceSystemEnviromentTokens(std::string& var) 
  static const char* tokenOpening = "[[";
  static const char* tokenClosure = "]]";
  auto getEnvVal = [](const char* var)  
    char * val = std::getenv(var);
    return val == NULL ? std::string() : std::string(val);
  ;
  
  std::size_t temp = var.find(tokenOpening, 0);
  while(temp != std::string::npos) 
    const std::size_t beginning = temp;
    const std::size_t end = var.find(tokenClosure, beginning);
    temp = var.find(tokenOpening, beginning + 1);
    
    if(end == std::string::npos) 
      break;
    
    if(temp != std::string::npos)
      if(end > temp)
        continue;
    
    const std::size_t tokenSize = end - (beginning + strlen(tokenOpening));
    const std::string token = var.substr(beginning + strlen(tokenOpening), tokenSize);
    const std::string translatedToken = getEnvVal(token.c_str());
    
    if(!translatedToken.empty()) 
      const std::size_t currentSize = var.length();
      var.replace(beginning, end, translatedToken);
      if(temp != std::string::npos) 
        temp += currentSize - var.length();
        if(temp > var.length())
          temp = std::string::npos;
      
    
  

测试:

int main(int argc, char** argv) 
  std::string test = "this should be converted: [[VULKAN_SDK]] this should not be converted: VULKAN_SDK]] this: [[VULKAN_SDK]]";
  replaceSystemEnviromentTokens(test);
  std::cout << test << std::endl;

当前行为:this should be converted: C:\VulkanSDK\1.2.148.1erted: VULKAN_SDK]] this: [[VULKAN_SDK]]

预期行为:this should be converted: C:\VulkanSDK\1.2.148.1 this should not be converted: VULKAN_SDK]] this: C:\VulkanSDK\1.2.148.1

【问题讨论】:

为什么不用c++17内置的replace()函数呢? 会有test = "this should be replaced [[vul[kan]]"?这样的字符串在这种情况下会发生什么?我想问的是,'['']' 字符会像任何其他普通字符一样出现在字符串中吗? test = "abc [[vul[[kan]] xyz"test = "abc [[[kan]] xyz"? 或者如果test = "abc [[vul [[jjk]] hkj [[hjh]] [[nj [[ hjgk[[ ]]ghjg[[gfgh]] ]] ]] [[kan]] xyz" 【参考方案1】:

使用 C++17 中内置的 replace() 函数,您可以用最少的代码完成上述任务。

如果给定字符串 test = "abc [[vul [[hjh]] [[nj [[ hg[[gfgh]] ]]m ]]xyz""abc [[vul[[kan]] xyz" 或类似类型,那么您可以将其视为使用堆栈解决的 balanced parenthesis 问题的变体。

void replaceSystemEnviromentTokens(std::string& var) 
    static const char* tokenOpening = "[[";
    static const char* tokenClosure = "]]";
        auto getEnvVal = [](const char* var) 
        char * val = std::getenv(var);
        return val == NULL ? std::string() : std::string(val);
    ;
    std::stack<int> s;
    for(int i =0; i<var.size();i++)
        if(var[i]=='[' && (i+1)<var.size() && var[i+1]=='[')
            s.push(i);
            i++;
        
        if(var[i]==']' && (i+1)<var.size() && var[i+1]==']')
            if(!s.empty())
                // This means we have encountered a [[ before as well
                // And using the stack, we would get the most recent [[
                int a = s.top();
                s.pop();
                // A corner case of [[]] can be checked here i.e empty string
                // The case of [[ ]] can also be checked but for now it is omitted
                int beg = a + strlen(tokenOpening);
                const std::size_t tokenSize = i - beg;
                if(tokenSize > 0)
                    // Non-empty token enclosed between [[____]]
                    const std::string token = var.substr(beg, tokenSize);
                    const std::string translatedToken = getEnvVal(token.c_str());
                    if(!translatedToken.empty()) 
                        var.replace(a, tokenSize + strlen(tokenOpening) + strlen(tokenClosure), translatedToken);
                        /*
                        Here you need to decide what to initialize i with here
                        If the translatedToken itself would bring more [[___]] pairs
                        and you would want to replace their tokens as well
                        then i = a-1, if not then i = a + translatedToken.size()-1
                        */
                        i = a + translatedToken.size()-1;
                    
                
            
        
    

【讨论】:

【参考方案2】:

编辑:risingStark 发现原始版本无法正常工作!这是一个看起来肯定比原来的更丑的修复,但至少它有效。我真的很讨厌从迭代器开始的需要,但我不能很快找到更好的方法。在这里试试这个:https://ideone.com/Hcb4BM

我想用正则表达式来做这件事,但我花了很长时间才明白它们在 C++ 中是如何工作的。不管怎样,结果是这样的:

void replaceSystemEnviromentTokens(std::string& var)

    static std::string tokenOpening = "\\[\\[";
    static std::string tokenClosure = "\\]\\]";
    auto getEnvVal = [](const std::string& var) 
        char* val = getenv(var.c_str());
        return std::string(val ? val : "");
    ;

    std::regex re(tokenOpening + "(\\S+)" + tokenClosure);
    std::sregex_iterator it(var.begin(),var.end(),re);
    std::sregex_iterator end;
    while (it != end) 
        auto& m = *it;
        auto value = getEnvVal(m.str(1));
        if (!value.empty()) 
            std::regex re_replace(tokenOpening + m.str(1) + tokenClosure);
            var = regex_replace(var, re_replace, value);
            it = std::sregex_iterator(var.begin(), var.end(), re);
        
        else 
            ++it;
        
    

我不太确定它是否比公认的答案更好,但至少现在我知道如何使用 &lt;regex&gt;... 我认为必须有更好的方法来处理为每个令牌,但我看不到任何解决问题的方法。

【讨论】:

它不工作。它进入一个无限循环。你试过运行一次吗? @risingStark 哎呀,你是对的!如果您有一个未在环境中定义的令牌,它将继续尝试替换它......叹息......我的测试是ideone.com/BoG8dB。不幸的是,我错过了那个案例。 @risingStark 查看新版本。

以上是关于替换字符串中的标记的主要内容,如果未能解决你的问题,请参考以下文章

如何替换 Java 字符串中的一组标记?

替换字符串中的 html 标记,但保留文本并用自定义标记重新换行

从 SQL Server 中的字符串替换 CSS 标记

JavaScript RegEx:在两个“标记”之间替换字符串中的文本

替换Python字符串中的自定义“HTML”标记

替换 HTML 字符串中 HTML 标记属性的引号