Re: How does this work?



Sorrow wrote:
I was watching a javascript presentation and one of the slides had the
following code:

<code>

Since this is not a Web forum but a public Usenet newsgroup, such
pseudo-syntax neither is necessary nor do I consider it appropriate to
designate code segments (it could be part of HTML or XML after all -- would
you post a real `code' element inside a `code' pseudo-element then?).

Putting code in a separate paragraph and maybe indent it a little should
suffice.

String.prototype.supplant = function( o ) {
return this.replace(/{([^{}]*)}/g,
function( a, b ) {
var r = o[b];
return typeof r === 'string' ? r : a;

}
);
}
</code>

and the variable getting passed to supplant() in this particular example
looks like

<code>
var data = {
first: 'FirstName',
last: 'LastName',
blah: 'whatever'
}
</code>

What I don't quite understand is where the a and b variables come from that
get passed as arguments to the function that represents the second parameter
of the replace() method. Is there an implicit iteration through all the
key/value pairs of the object passed into supplant()?

Quite the opposite. What happens is that the value of properties is looked
up and returned conditionally using part of the matching substring as
property name.

Or is it something else altogether? If it is, what is it? Can someone
explain to me what's going on here?

As the name suggests and the algorithm confirms, this method supplants
(replaces) occurrences in a string value with property values from the
object referred to by the passed reference.

Let's say the string value is

s = "foo={first}";

and

s.supplant(data)

is called, then what happens with

// pretty-printed
String.prototype.supplant = function(o)
{
return this.replace(
/{([^{}]*)}/g,
function(a, b)
{
var r = o[b];
return (typeof r === 'string' ? r : a);
});
};

is that all occurrences (`g') in `s' that match the Regular Expression
/{([^{}]*)}/ will be replaced by what the second function-type argument
returns when called for each match. The signature of that function is
defined (in short) as follows:

The value of the first argument (a) is the substring that matched.

The value of the second argument is the match for the first paranthesed
expression in that substring, which would be `([^{}]*)' here.

(See ECMAScript Ed. 3 Final, 15.5.4.11 or
http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:String:replace#Specifying_a_function_as_a_parameter
for details.)

Now, that subexpression matches all substrings that consist of an arbitrary
number (including zero: `*') of characters that are neither `{' nor `}'
(`[^...]'). (Using character classes this way is a workaround in
less-supportive (RegExp, and here also script) engines for non-greedy
matching; the match extends as long as the delimiter characters don't match.
In more supportive engines, `\{.*?\}' or `\{.*+\}' could be used instead.)

In our example --

s = "first={first}"

-- that would mean that the first match would be `{first}' and the first
paranthesed subexpression match (b) would be `first'. Then comes

var r = o[b];

Since b == "first", that is

var r = o["first"];

which with

var data = {
first: 'FirstName',
last: 'LastName',
blah: 'whatever'
};

is resolved to

var r = "FirstName";

Then follows the statement and test

return (typeof r === 'string' ? r : a);

which means that if the property value retrieved before would be a string,
the property value would be returned, and so e.g. "{first}" in the string
would be replaced by "FirstName". Otherwise, the whole substring would be
returned, and so "{first}" would be replaced by itself (i.e. nothing would
be replaced, actually).

And so on for other possible matches due to the *g*lobal expression modifier.


So, as you can probably see now, it is actually a simple template engine
implementation that we have here.


It has several flaws:

It only allows for strings as property values, although other values could
be implicitly converted to string, too, without losing their meaning.

It performs unconditional read access to undefined properties (rather than
testing the type first which would lead to a number of warnings in the error
console of Gecko-based user agents.

This could be fixed as follows:

String.prototype.supplant = function(o)
{
return this.replace(
/{([^{}]*)}/g,
function(a, b) {
return (typeof o[b] != "undefined" ? o[b] : a);
});
};

Since I'd rather not have it match empty strings (although the empty string
is a valid property name), I would change it further to:

String.prototype.supplant = function(o)
{
return this.replace(
/{([^{}]+)}/g,
function(a, b) {
return (typeof o[b] != "undefined" ? o[b] : a);
});
};

I am also not particular fond of using the outermost `{' and `}' characters
this way. Usually outside of character classes these characters delimit
decimal quantifiers (`{x,}', `{,y}', or `{x,y}'). IIRC at least the RegExp
engine of one script engine chokes on those when not preceded by an
expression, therefore:

String.prototype.supplant = function(o)
{
return this.replace(
/\{([^{}]+)\}/g,
function(a, b) {
return (typeof o[b] != "undefined" ? o[b] : a);
});
};

And finally, there is the issue of prototype augmentation which generally
should be avoided because it breaks the `in' operation. That may be
negligible here, though.

function strSupplant(s, o)
{
return s.replace(
/\{([^{}]+)\}/g,
function(a, b) {
return (typeof o[b] != "undefined" ? o[b] : a);
});
};


HTH

PointedEars
--
realism: HTML 4.01 Strict
evangelism: XHTML 1.0 Strict
madness: XHTML 1.1 as application/xhtml+xml
-- Bjoern Hoehrmann
.



Relevant Pages

  • Re: "Reversed" LCS problem
    ... have to worry so much about defining "substring" versus "subsequence," ... say "the letters in the string must be next to each other, ... >> has the lowest number of common characters from the other strings, ... > No whitespaces, no sequences, only substrings. ...
    (comp.programming)
  • Re: Deleting substrings
    ... substring is present in the string or not. ... We start at the beginning of the string. ... We copy the characters from the point immediately after ... redundant scans of the host string. ...
    (comp.programming)
  • Re: split a string
    ... >I have a string of N characters. ... >more ‘*' characters, which are the delimiter for the substring. ...
    (alt.comp.lang.learn.c-cpp)
  • Re: read text file
    ... 20 characters long and I need to look for the item at position 31. ... I get the following error "input string is in the incorrect format" and this ... >> I need to use SubString because I need to get only certain dataitems, ... > What Line is throwing the exception? ...
    (microsoft.public.dotnet.languages.csharp)
  • Re: Prothon should not borrow Python strings!
    ... """It does not make sense to have a string without knowing what encoding ... same cul de sac as Python. ... Prothon_String_As_ASCII // raises error if there are high characters ... Python's split between byte strings and Unicode strings is ...
    (comp.lang.python)