Csswg-drafts: [css-values] String concatentation

Created on 28 Sep 2016  ·  28Comments  ·  Source: w3c/csswg-drafts

As discussed with @tabatkins during TPAC last week.

Now with variables (custom properties), string concatenation is coming up in more and more use cases. Constructing URLs from variables for instance (see #541) is useless without concatenation.
Also, building SVG paths from variables.

Tab suggested a concat() function. I wonder if we can reuse the syntax from content which already allows for concatenation in a simple and elegant way, and special case the few cases where existing widely used property values accept <string>+ (e.g. quotes) if they are sufficiently few and concatenation isn't needed there. Sure, it's weird if concatenation doesn't work in a few places, but I think the usability benefits of doing concatenation in the same simple way that authors are already used to, are worth it. Otherwise, CSS will end up like LISP with all the parentheses (imagine things like concat(var(--text), calc(1% * var(--foo))), which will be pretty common).

Related specs:

css-values-5

Most helpful comment

I'm intrigued by the idea of automatic concatenation of consecutive string tokens, but I'm worried that this might derail the general possibility of concatenation.

I personally don't think one extra layer of function notation will create an overwhelming number of parentheses. But maybe I've just been programming too long.

One thing I've mentioned elsewhere (and Lea seems to be assuming it, as well) is the idea that a concatenation function could do double-duty as a string coercion function, forcing any number or other non-string variables inside it into string version. This is very important for building SVG path data from variables. I'm worried that if you combine this with the default concatenation option you'd really make a mess of parsing.

So my vote is for a concat() or cat() or string() function. And sooner rather than later.

All 28 comments

I'm intrigued by the idea of automatic concatenation of consecutive string tokens, but I'm worried that this might derail the general possibility of concatenation.

I personally don't think one extra layer of function notation will create an overwhelming number of parentheses. But maybe I've just been programming too long.

One thing I've mentioned elsewhere (and Lea seems to be assuming it, as well) is the idea that a concatenation function could do double-duty as a string coercion function, forcing any number or other non-string variables inside it into string version. This is very important for building SVG path data from variables. I'm worried that if you combine this with the default concatenation option you'd really make a mess of parsing.

So my vote is for a concat() or cat() or string() function. And sooner rather than later.

@tabatkins @fantasai Any thoughts? This is standing in the way of many cool CSS variable use cases, and once we agree on a syntax, not particularly hard to implement.
I got reminded of this because I was trying to use CSS variables in d: path(); yesterday, and quickly realized it was impossible :/.

I'm strongly against the proposal to just make adjacent <string> values auto-concat; it clashes with existing grammars, restricts our ability to design reasonable grammars in the future, and this ability in 'content' isn't well-known anyway.

I'm supportive of concat() or string(). Having it auto-coerce things also seems fine to me, with the understanding that it'll use some well-defined notion of serialization, rather than necessarily preserving the exact input from the author.

(That is, string(1.0) might resolve to "1".)

@tabatkins Awesome, can we make it happen?

Regarding the name, I'd suggest text(). string() assumes knowledge of programming which doesn't necessarily apply to CSS authors, and concat() looks weird with only one argument. text() is also shorter.

+1 to text() for being understandable and flexible.

Also +1 to Tab's suggestion that string serialization of numbers and lengths be independent of the formatting of the initial value. If there is demand in future, separate number formatting options could be considered, based on the number formatting options in counter().

There is string() function here: https://www.w3.org/TR/css-gcpm-3/#using-named-strings.

Some printing tools have already implemented the behaviour from that working draft. I believe it's not really a good idea to have some new string() meaning. But it will be awesome to have a concatenating function named like text() or concat().

The CSS Working Group just discussed String Concatenation, and agreed to the following:

  • RESOLVED: work on astring-coercion-and-concatenation function

The full IRC log of that discussion
<fantasai> Topic: String Concatenation

<astearns> github: https://github.com/w3c/csswg-drafts/issues/542

<fantasai> leaverou: There's a bunch of string value accepting proeprties in CSS, and can't use variables in there

<fantasai> leaverou: paths

<fantasai> leaverou: Lots of places where concatenation would be useful

<fantasai> leaverou: and don't have a way to do it

<fantasai> leaverou: Didn't recall any objections to the propsoal, just questions about what it's called

<fantasai> leaverou: I don't care, we just need a way to do it

<AmeliaBR> q+

<fantasai> chris: ppl using preprocessors take string concatenation for granted, it's simple there

<fantasai> chris: they're astounded when they find it's not available in raw CSS

<fantasai> leaverou: Primarily useful in URLs, but also useful in other places like paths

<fantasai> TabAtkins: I agree with the need for a function here, prefer concat() but fine with anything else

<chris> q?

<fantasai> leaverou: We've also had suggestions for a function that converts things to strings,

<xfq> ack AmeliaBR

<fantasai> leaverou: text() is generic enough to do both

<fantasai> string() sgtm

<fantasai> AmeliaBR: Problem with that is that for string coercion you often also want number formatting

<fantasai> AmeliaBR: so that might need to be a separate function

<fantasai> AmeliaBR: butwould need to be partof the system if you are going to make useful path dat from numeric variables

<fantasai> AmeliaBR: need to concatenate not just letters but numbers from variables

<fantasai> AmeliaBR: calc expressions

<dbaron> q+ to ask what the inputs to this function can be, and where it can be used

<fantasai> AmeliaBR: etc.

<fantasai> TabAtkins: What are the use cases for coercion? Debugging ovviousl, anything else?

<fantasai> leaverou: ...

<fantasai> Am Big in dataviz

<leaverou> s/.../generated content to display a variable value in the UI/

<leaverou> q+

<fantasai_> AmeliaBR: e.g. bar graphs, want to use value in drawing but also labelling

<fantasai_> AmeliaBR: don't want to duplicate content

<fantasai_> TabAtkins: I really don't want to get into string formatting

<fantasai_> TabAtkins: otherwise do see the value in do see value in displaying 50% on bar chart while 50% also used to size the bar chart

<fantasai_> AmeliaBR: So if we want to leave the generic number formatting issue for later, that's fine

<fantasai_> AmeliaBR: So long as we still have idea that basic concat function can take non-string values and simply print them out

<fantasai_> AmeliaBR: so you can use them for path data

<fantasai_> AmeliaBR: In path data you generally want to preserve maximum precision anyway

<fantasai_> leaverou: example ... interpolation

<fantasai_> leaverou: Don't want to define how concat() interpolates with each other

<xfq> ack db

<Zakim> dbaron, you wanted to ask what the inputs to this function can be, and where it can be used

<fantasai> dbaron: Reading the proposal wasn't clear to me what the inputs of this function is and what the output is in terms of types

<fantasai> dbaron: What CSS value types can be used in here?

<fantasai> TabAtkins: output tpe is tring

<fantasai> TabAtkins: input type is any thing, standard serialization of the token

<fantasai> dbaron: There will be a spec defining standard serialization of a token?

<leaverou> s/example ... interpolation/yes, that simplifies interpolation as well, if types of arguments were not coerced we'd need to define interpolation between two concat() calls

<fantasai> TabAtkins: Need that for variables anyway

<fantasai> TabAtkins: If it doesn't exist yet, some othe rspec is implicitly relying on it and it needs to be defined

<fantasai> astearns: Wrt used, it's anywhere with a string?

<fantasai> fremy: Exception for url?

<fantasai> TabAtkins: No, works in url()

<xfq> ack lea

<fantasai> TabAtkins: url() can take a string

<TabAtkins> s/take a string/take only a literal string, there's another issue for generic <string> input/

<fantasai> astearns: I'm hearing lots of nods on having this thing. Any implementer interest?

<fremy> (to clarify what tab said, url() can take a string but not a <string>)

<fantasai> astearns: From silence sounds like, yes this would be a good thing, no it's not a priority

<fantasai> emilio: Expectation is that you can do like sth(var(--whatev))?

<fantasai> emilio: Then how can you define it to be any token?

<fantasai> TabAtkins: var() is processed at a higher level than any value

<fantasai> fremy: If you want a string, you do text("string")

<TabAtkins> (It's specific as taking a <string> in the syntax, but in Syntax we handle url() in special ways that prevent it from taking string-producing functions.)

<fantasai> emilio: If it takes any token

<fantasai> fremy: If it's not a string, then you convert

<fantasai> TabAtkins: I think Emilio got it

<fantasai> astearns: OK, naming. What do we call the thing?

<fantasai> AmeliaBR: Lea suggested text(), but there's already a text() function in paged content

<fantasai> fantasai: How about string? was in Lea's original proposal

<fantasai> leaverou: sounds fine

<fantasai> chris: sounds fine

<bkardell_> lol

<fantasai> AmeliaBR: Sorry, confused things. It's the string() function that exists, text() does not

<fantasai> AmeliaBR: and already implemented

<chris> I care more that it exists, than what precisely it is called

<xfq> https://drafts.csswg.org/css-gcpm-3/#using-named-strings

<tantek> I for one an all for the cat() function, I bet it would poll well on Twitter 😺

<bkardell_> lol

<fantasai> astearns: Any objections to text()?

<chris> Lets go with text

<fantasai> dydz: Prefer concat(), but ...

<Rossen> combine()

<fantasai> AmeliaBR: Want names that are understandable by non-programmers in CSS

<fantasai> AmeliaBR: Also, we tend not to truncate identifiers in CSS

<chris> concat is not immediately obvious to non programmers either. both cat and concat are abbreviations

<fantasai> TabAtkins: We are not using cat(), tantek!

<tantek> 😿

<fantasai> bkardell_: I don't have a better suggestion, but text() is not very clear to me

<TabAtkins> text(50%)

<Rossen> +1 to bkardell_

<fantasai> bkardell_: There are so many ways that I coudl interpret "text"

<tantek> +1 to bkardell_

<fantasai> flatten() :P

<fantasai> chris: I think it's pretty clear

<fantasai> myles__: We should think about whether concatenation or coercion is more common use case

<TabAtkins> url(text("http://example.com", var(--foo)))

<florian> echo() ?

<tantek> printf()

<florian> printf()

<fantasai> AmeliaBR: text() is clear for coercion, maybe less clear for concatenation

<TabAtkins> No unixisms!

<TabAtkins> y'all weirdos!

<chris> and text(var(--foo)) with one param is clearer than concat(var(--foo))

<Rossen> stringify()

<Rossen> toString()

<TabAtkins> to-string()

<chris> DOTtoString

<dbaron> hopefully everybody has forgotten XPath by now?

<tantek> Time for a Twiter survey!

<fantasai> iank_: Spreadsheets use concatenate and concat

<fantasai> TabAtkins: It's terrible and hard to spell

<fantasai> TabAtkins: These are all great names, except for all the ones that are bad.

<chris> https://developer.mozilla.org/en-US/docs/Web/XPath/Functions/concat

<fantasai> TabAtkins: Let's table this and put together a non-binding Twitter poll

<fantasai> florian: Make sure you include an international audience

<fantasai> bkardell_: That works best if we all promote it so let us know

<fantasai> leaverou: Twitter doesn't have enough space for examples, and based on what examples you use can get different reactions

<fantasai> TabAtkins: I'll put together coercion example and concat example, and you can review them

<chris> https://stackoverflow.com/questions/9493732/difference-between-text-and-string

<fantasai> astearns: Sounds like we'll do this and decide the name later, would be interested in implementer interest

<fantasai> astearns: Any objections?

<fantasai> RESOLVED: work on astring-coercion-and-concatenation function

One thing I want to mention: what would be really nice if that future string concatenation could work with _anything_ that accepts strings, especially, grid-template-areas, to allow generating named areas from other variables, for example.

Of course, this should work also by allowing converting idents into strings, so those could be reused both inside grid-template-areas and other grid properties.

And this also brings me to another kinda related topic: _ident_ concatenation. Probably also related to converting _anything_ into _anything_, and related to the attr()'s second argument. All of this is probably off-topic for this issue, but my point is: if we'd want to think about the name for string concatenation, we should remember that we could potentially have other functions in the future doing similar things, and we should come with a name that could work best in the wider picture.

@kizu
I think the grid template use case would work with what we decided. If you use a non-string inside the function, it gets converted to a string based on its serialized value. The serialization of an ident is its character sequence. And it would definitely apply in any function that accepts a string data type.

Converting a string to a non-string (string to ident, string to parsed number or length) would be a separate issue. But I agree that adding a general parse function for that case is tied in with the unimplemented second parameter to attr.

Yes, this function's type will be <string>; any place that accepts a <string> should accept it.

Playing devil's advocate for a moment... calc() already accepts seven different literal types. Why couldn't string be the eighth?

Playing devil's advocate for a moment... calc() already accepts seven different literal types. Why couldn't string be the eighth?

Because calc() is obviously for calculating something and deals with numeric values.

Sebastian

@BigBadaboom And overloading calc() to do string concatenation would mean having to do calc("" + 50%) to get type coercion. And even if JS devs are used to that, it's still ugly.

string() already produces strings, and is understandable to css authors. I would rather not have string() produce a string one way, and text() produces a string another way. I would rather just extend string(). It seems doable. If the argument is an identifier that matches a string-set, then parse it as existing string(). Otherwise, coerce space-separated arguments into a string. Then allow this string to be used anywhere that strings are allowed.

I don’t buy the argument that CSS authors don’t know what “string” means. If that was the case, we shouldn’t have used it for GCPM. We have it there now, it exists, and it can be the single tool that authors reach for when they need a string.

It more CSS-y without the commas anyway.

Minutes from the meeting where this was discussed suggested this was going to a twitter poll on naming - did that go anywhere?

The name summary seems to be concat, cat, text, stringify, combine, to-string - I'm excluding string as that's already in css-gcpm (and I see no advantaged in trying to merge this functionality into that). I would like to add join to that list (athough only because tab says we're not allowed "cat", despite 50 years of the One True Operating System saying otherwise).

We're certainly going to implement this (as -bfo-join(<string> [<ws> <string>]*) until a name is finalised) - it ties in with something else we're working on, it's neat, and it's about 20 lines of code.

Iʼm all for a name that is easily understood by non-programmers. Unfortunately, most candidates already have some established meaning (and thus user expectations) in one programming language or the other.

join, for instance, is known as a function to concatenate the items of an array, list or map into a string, but with a custom separator string between them. chain and link sound similar to me, but have other drawbacks, although they are not abbreviations like concat and, way worse, cat.

I like text and now wish calc had been math. Itʼs better than type, write, line and log, which have not been suggested yet, but appear in other languages for similar functions, as does print which does not work well for CSS.

A special kind of _stringify_ functions is _slugify_ for generating _slugs_ (short titles, URL-friendly). I think itʼs too narrow-focused.

join, for instance, is known as a function to concatenate the items of an array, list or map into a string, but with a custom separator string between them. chain and link sound similar to me, but have other drawbacks, although they are not abbreviations like concat and, way worse, cat.

concat is a standard abbreviation, already used in the web platform for a similar purpose (String.prototype.concat). Seems like the choice I would go with.

@noamr concat() only makes sense when you have 2+ arguments. For a single argument, concat(foo) looks awfully confusing.

@noamr concat() only makes sense when you have 2+ arguments. For a single argument, concat(foo) looks awfully confusing.

Hrm you're right.
My second favorite was to-string.

So:

  • concat cat and combine may be strange for a single string, though on Linux for example cat is often used for a single string.
  • text has a different meaning than string, e.g. innerText vs. innerHTML
  • stringify is usually meant for non strings, e.g. an object -> JSON

So IMO to-string makes the most sense.

Suggesting an alternative approach here.
Maybe general-purpose string concatenation is too low-level for CSS.

Instead, I'm suggesting to treat every string-requiring CSS property differently.

  • For paths, allow constructing paths without strings (see here: https://github.com/w3c/csswg-drafts/issues/5674#issuecomment-719067583)
  • For urls, allow the url function to take more than one argument and have that function concat them.
  • For some of the other props requiring strings, like content, allow string concatenation in the same way that it's done today, by having several arguments (e.g. content: "(" attr(title) ")", or font-family: "Times-New-Roman", var(--fallback))

The reason for this is a sense that strings inside CSS may create a "language within a language" for each property, (which is already my sense with e.g. path strings).

Not sure how innerText demonstrates the difference between "text" and "string", since there's no innerString. Also note that innerText was never the output of a rigorous consensus-driven process, but a Microsoft extension that was later standardized to pave the cowpaths.

Do note that in spreadsheets, it's a text() function that converts to a string.

I don't think adding string concatenation on a case by case basis is a good idea at all. It makes each individual property more complicated, it makes the language less consistent (which properties support concatenation and which don't?) and adds overhead to learning each property, since each property does concatenation differently (lack of internal consistency). Also it would mean that authors need to wait for the full standards lifecycle (proposal + draft + implementation + test) for every single concatenation use case that we did not foresee early on.

Also note that font-family doesn't support concatenation. If you have a variable with "Times" and another one with " New Roman", you cannot turn them into a single font family by just placing them next to each other. The only property that currently supports actual concatenation is content.

Yeah, I'm strongly against special-casing string concatenation per usage site, and I've already given reason why I do not want to go down the "putting them next to each other implies concatenation" route (it ties our hands wrt grammar design later).

Was the introduction of new syntax already considered for this? Many languages use an operator for string concatination like + or &. Would that be possible for CSS? I mean something like

"string1" + 1.0 + "string2"

Note that this is independent of the discussion about automatic type conversion. A function could still be required for converting different types into a string, i.e.

"string1" + string(1.0) + "string2"

Sebastian

Not sure how innerText demonstrates the difference between "text" and "string", since there's no innerString. Also note that innerText was never the output of a rigorous consensus-driven process, but a Microsoft extension that was later standardized to pave the cowpaths.

Do note that in spreadsheets, it's a text() function that converts to a string.
In spreadsheets and in other places, yes. In the web, my connotation of the word 'text' is more about text that is accessible to users (like innerText and text nodes) or about encoding/decoding, while string is usually neutral about its content.

I don't totally object to text, but saying that e.g. JSON.stringify and Object.toString are closer to what this function does than TextEncoder, createTextNode and innerText.

I don't think adding string concatenation on a case by case basis is a good idea at all. It makes each individual property more complicated, it makes the language less consistent (which properties support concatenation and which don't?) and adds overhead to learning each property, since each property does concatenation differently (lack of internal consistency). Also it would mean that authors need to wait for the full standards lifecycle (proposal + draft + implementation + test) for every single concatenation use case that we did not foresee early on.>

I'm not suggesting to add concatenation on a case by case basis, but rather attempt to solve the problems that require concatenation in a case-by-base basis, without using strings if possible (e.g. what we're doing with paths).

Also note that font-family doesn't support concatenation. If you have a variable with "Times" and another one with " New Roman", you cannot turn them into a single font family by just placing them next to each other. The only property that currently supports actual concatenation is content.

This is where I think this feature might become more powerful than we realize, and can change CSS and the web in unpredictable ways.

For example:

  • you could start to be able to write full SVGs with CSS custom properties and inheritance and concatenate them in a data: URI.

  • If you have a font used somewhere, you wouldn't be able to statically analyze which stylesheet it came from, as it might be a concatenation of many CSS properties, inheritances and calcs.

  • I can see how people would start creating CSS uglifiers, that turn readable CSS into CSS that you can't understand with a whole bunch of variable-concatenated URLs, background SVGs, font-families, and clip-paths.

I'm not against any of the above, but I think that we should give them a consideration. This feature enables a lot more than meets the eye.

I believe concatenating data URI SVGs with custom properties is one of the primary use cases, not "more than meets the eye".

I believe concatenating data URI SVGs with custom properties is one of the primary use cases, not "more than meets the eye".

It wasn't an obvious one to me and wasn't in the description of the issue.

Dynamically constructing full SVGs from CSS and uglifying are use-cases that can IMO fundamentally change the way CSS is used, reminiscent of eval in Javascript. It doesn't make it an undesirable feature but I'm not sure the implications were given full consideration.

Was this page helpful?
0 / 5 - 0 ratings