LESS replace()函数行为让你困惑了吗?

6

我想写一个LESS的mixin,使用字符串的一部分来形成类名。我希望能够使用replace()为变量提供值,然后在规则中使用该变量。下面是我编写的测试以验证它是否可行:

.foo(@xyz) {
  @pfx: replace(@xyz, "(.).*", "$1");

  .@{pfx}-foo { margin: 0; }
}

.foo(abcdefghijklmnop);

当我使用 lessc (1.7.4) (编辑 - 同样适用于 2.1.0) 运行它时,它 有点 有效,生成了这个:
.aaa-foo {
  margin: 0;
}

因此,replace() 函数正确地从我传入的字符串中取出第一个字符(“a”),但是它并没有...
.a-foo

它给了我

.aaa-foo

我查看了replace()函数的源代码,发现它非常简单,所以我完全不明白发生了什么。(我已经使用正则表达式"^(.).*$"尝试过,但结果相同。)

我稍微扩展了一下测试:

.foo(@xyz) {
  @pfx: replace(@xyz, "(.).*", "$1");

  .@{pfx}-foo { margin: 0; content: "@{pfx}"; }
}

.foo(abcdefghijklmnop);

这将使我获得:

.aaa-foo {
  margin: 0;
  content: "a";
}

这意味着正则表达式运行良好。

@AmitJoki 是的。如果我把它改成.foo(zebra),那么它会给我.zzz-foo - Pointy
@AmitJoki 我真的希望它是 .a-foo - 看起来 LESS 内部已经给变量赋了正确的值,但当它在特定的上下文中使用变量(在选择器中创建类名)时,它会重复该值。这很奇怪。 - Pointy
@AmitJoki 哈哈,是的,那是我尝试的第一件事情之一!不幸的是,它没有任何区别。 - Pointy
我在#2308中回答了。 - seven-phases-max
1
以防万一,这个问题已经在当前主分支中得到修复,所以很可能在其他人阅读此内容时,示例已经可以正常工作了(即Less v2.1.1或更高版本 :))。 - seven-phases-max
显示剩余2条评论
3个回答

2

显然有一些解决方法,但它的工作原理仍然是一个谜。

经过评估后的AST完全正确,看起来像这样:

Element { combinator: { value: '', emptyOrWhitespace: true },
  value: '.',
  index: 54,
  currentFileInfo: 
   { filename: 'input',
     relativeUrls: undefined,
     rootpath: '',
     currentDirectory: '',
     entryPath: '',
     rootFilename: 'input' } },
Element { combinator: { value: '', emptyOrWhitespace: true },
  value: 
   Quoted { escaped: undefined,
     value: 'a',
     quote: 'a',
     index: undefined,
     currentFileInfo: undefined },
  index: 55,
  currentFileInfo: 
   { filename: 'input',
     relativeUrls: undefined,
     rootpath: '',
     currentDirectory: '',
     entryPath: '',
     rootFilename: 'input' } },
Element { combinator: { value: '', emptyOrWhitespace: true },
  value: '-foo',
  index: 61,
  currentFileInfo: 
   { filename: 'input',
     relativeUrls: undefined,
     rootpath: '',
     currentDirectory: '',
     entryPath: '',
     rootFilename: 'input' } }

但是当生成CSS代码时,情况很快就变得糟糕了。
Quoted.prototype.genCSS = function (context, output) {
    if (!this.escaped) {
        output.add(this.quote, this.currentFileInfo, this.index);
    }
    output.add(this.value);
    if (!this.escaped) {
        output.add(this.quote);
    }
};

引用节点在未被转义时输出带引号的值。但是,如果该值的第一个字符为引号,如此情况下的 'a',则有所不同。

而当对replace应用e()时,less会在AST中放置匿名节点而非引用节点,因此不会输出引号。


哇。嗯,从Quoted()构造函数的样子来看,这就是我想象中发生的事情。replace()函数将返回值的引号设置为原始字符串的引号值(或空字符串)。 - Pointy
AST不正确。replace构造的Quote对象对于此示例无效。 - seven-phases-max
replace 功能正常。但是 Quoted 节点将在评估过程中自我评估。引号(尽管是 '')将被附加到内容中,并用于生成新的 Quoted 节点。请查看 https://github.com/less/less.js/blob/master/lib/less/tree/quoted.js#L45 - xiaoyi
@seven-phases-max 这个AST在评估后被转储,因此Quoted节点与replace()之后的节点不同。 - xiaoyi
@xiaoyi 嗯,不对哦,是 replace 错误地使用了 Quoted 构造函数(当输入值为 Keyword 类型时)而不是反过来的情况。 - seven-phases-max
显示剩余3条评论

2

试试这个:

@pfx: e(replace(@xyz, "^(.).*$", "$1"));

或者这个:
@pfx: replace(~"@{xyz}", "^(.).*$", "$1");

请参阅e函数的文档。

是的,我也是通过随机实验发现的。你知道这为什么有效,以及为什么右侧的content:属性中@{p}工作正常时不需要它吗? - Pointy
我猜这与字符串转换有关。但说实话,我无法确定真正的问题所在。 - haim770
嗯,看起来最新版本(2.1.0)的表现方式似乎是一样的。我提交了一个错误报告,想看看维护者会说些什么;我对这个工具并不是很精通。 - Pointy
可能会解释:http://www.bennadel.com/blog/2645-thinking-about-strings-quotes-tokens-and-tildes-in-less-css.htm - haim770
这个也是:https://github.com/less/less.js/blob/master/lib/less/functions/string.js#L17 - haim770
我同意该值被视为引用的说法,但是如果没有调用“e()”,则结果仍然不符合我的期望。 - Pointy

1
尝试这个。
.foo(@xyz) {
  @props: ~`"@{arguments}"`;

  @pfx: replace(@props, "^(.).*$", "$1");

  .@{pfx}-foo { margin: 0; }
}

.foo(abcdefghijklmnop);

编辑

回答评论时,您想要传递一个字符串进行替换。实际上,更简单的方法是使用.foo(~"abcdefghijklmnop"),这会强制less将参数视为字符串而不输出引号。


你明白这为什么有所不同吗?它确实有效,但我不知道原理。 - Pointy
是的,现在有点清楚了,但行为仍然似乎不太理想。我更愿意使用e()方法,因为我要传递的实际字符串是像marginpadding这样的东西,用~" "引用这些字符串看起来很丑。无论如何,感谢您的见解;LESS并不是我的最强技能 :) - Pointy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接