What's New in Windows Script 5.5

 

Peter Torr
Microsoft Corporation

Updated July 14, 2000

Contents

Regular Expressions
Formatting Functions
Miscellaneous
What About VBScript?
More Information and Feedback

With the final release of Microsoft Windows Script 5.5, Microsoft continues its commitment to deliver the leading ECMA-262 (ECMAScript) implementation. Version 5.5 of Windows Script is compliant with version 3 of ECMAScript, with only minor differences that are necessary for compatibility with existing script code. The changes for ECMAScript version 3 primarily involved extending the built-in objects, rather than radical improvements in the language itself. Here's a quick rundown on a few of the new features you can find in JScript® and Visual Basic® Scripting Edition (VBScript) 5.5. Enjoy!

Regular Expressions

Regular expressions are incredibly powerful tools for searching and manipulating all kinds of textual data. JScript has included support for regular expressions since version 3—but this time around, we have added new features to both the pattern syntax and the replace method.

Using Replacement Functions

Previously, if you wanted to replace text within a string, you had two choices:

  • If the text you wanted to replace was static, you could perform a single global replacement using a regular expression in one line of code. Very nice.
  • If the text you wanted to replace was "dynamic," you had to use indexOf or charCodeAt calls inside of a loop and manually build up the replacement string. Yuck.

In JScript 5.5, you have the best of both worlds with replacement functions. You can perform a neat, one-line global search-and-replace operation using your favourite regular expression, but pass it a Function object instead of a replacement string. Because the function is called every time the regular expression finds a match, it can make "smart" decisions and perform computations before returning a replacement string.

Now that sounds a little complex, so here's a concrete example. Hopefully, many of you will have heard of something called "ROT13 encoding." Basically, ROT13 is a weak form of text encoding, designed to hide the content of a message from casual users. ROT13 can be used, for instance, to hide "spoilers" within a film review on the news:rec.arts.movies newsgroup. If you have Outlook Express installed on your computer, you can use the "Unscramble (ROT13)" menu item from the Message menu when reading newsgroups to see how this would work.

ROT13 gets its name from the fact that it "rotates" all the alphabetical characters in the input text by 13 places. Of course, since there are 26 letters in the alphabet, if you ROT13 encode a message twice, you get back to where you started. To build your own ROT13 encoder in JScript 5.0, you would have had to use the old charCodeAt trick, checking every character in the string, and building up the replacement character by character. Here's how it might look:

// Old-style function to ROT 13 encode upper-case letters
function OldRot13(s)
{
   var sResult = "";
   var i = 0;
   var d = 0;

   // Check every character in the string
   for (i = 0; i < s.length; i++)
   {
      // Get the next character
      d = s.charCodeAt(i);

      // Is it an upper-case character?
      if ((d >= 65) && (d <= 90))
      {
         // Increment it
         d += 13;

         // Rotate any over-flows
         if (d > 90)
         {
            d = 64 + (d - 90);
         }
      }

      // Add the character to the string
      sResult += String.fromCharCode(d);
   }

   // Return the result
   return sResult;
}

Notice how the actual logic to do the encoding—adding 13 and then rotating any overflows—is cluttered up by having the looping code around the outside? Sure, we could add another function to encapsulate the per-character rotation code, but we've still got that ugly looping code hanging around. Here's how we might use it:

var s = "A SECRET MESSAGE! ";

// Encode it:
var sEncoded = OldRot13(s);
window.alert(sEncoded);

// Decode it:
s = OldRot13(sEncoded);
window.alert(s);

The output of this code is the encoded string "N FRPERG ZRFFNTR!" followed by the original string, "A SECRET MESSAGE!".

So far, so good: We've got an ugly encoding function, but using it is no problem. Imagine, though, that we wanted to encode something other than just upper-case letters; for example, what if we wanted to encode only those strings that matched a certain pattern? We'd have to hack the OldRot13 function to do this. Not so good.

Enter JScript 5.5 to save the day. Using the new function replacements, you can implement the same functionality as OldRot13 in JScript 5.5 as follows:

// Simple function to ROT 13 encode upper-case letters
function Rot13(sMatchedString)
{
   // Get the character code
   var d = sMatchedString.charCodeAt(0);

   // Increment it
   d += 13;

   // Rotate any over-flows
   if (d > 90)
   {
      d = 64 + (d - 90);
   }

   return String.fromCharCode(d);
}

And a sample way to use it (giving the same results as above):

// Encode only the upper-case letters
var re = /[A-Z]/g;

var s = "A SECRET MESSAGE!";

// Encode it:
var sEncoded = s.replace(re, Rot13);
window.alert(sEncoded);

// Decode it:
s = sEncoded.replace(re, Rot13);
window.alert(s);

Notice several differences here. First, there's about half as much code, as we didn't have to worry about all the looping logic; the global replace function did that for us. Second, we specify the type of characters to search for using a regular expression, so it is easy to change. If we want to encode only the vowels, we can change the first line:

// Encode only the upper-case vowels
var re = /[AEIOU]/g;

Voilà! It works, displaying "N SRCRRT MRSSNGR!" (Note, however, that the decoding phase no longer works, because the regular expression still only looks for vowels, and we encoded all the vowels out of the string!)

Syntax of the Replacement Function

In the example above, only one parameter was passed to the replacement function—sMatchedString—but this function can actually take a variable number of arguments, as follows:

replaceFunc(matchedString [, subMatch1 [, ...]] , matchPos, source)
Parameter Value
matchedString The entire substring string that matched the regular expression.
subMatch1 The first captured submatch, if any
... Additional captured submatches, if any
matchPos The position within the source string that the match was found
source The entire source string being searched

The replacement function will always be passed at least three parameters—matchedString, matchPos, and source—but it will also be passed additional parameters if there are capturing submatches in the regular expression. Submatches are introduced using parentheses. For example, the following code shows how submatches are passed to the replacement function:

// Look for a word, followed by a decimal number
var re = /(\w+)\s*(\d+\.\d+)/g;

// Sample string to search
var s = "JScript 5.5 and VBScript 5.5";

s = s.replace(re, showParams);
window.alert(s);

function showParams(matchedString, subMatch1, subMatch2, matchPos, source)
{
   var s = "Found a match!" +
      "\n\tMatch = " + matchedString +
      "\n\tSub-match 1 = " + subMatch1 +
      "\n\tSub-match 2 = " + subMatch2 +
      "\n\tMatch position = " + matchPos +
      "\n\tSource string = " + source;

   window.alert(s);

   return (subMatch1 + " is great");
}

The code above produces the following three alert dialogs:

Found a match!
        Match = JScript 5.5
        Sub-match 1 = JScript
        Sub-match 2 = 5.5
        Match position = 0
        Source string = JScript 5.5 and VBScript 5.5

Found a match!
        Match = VBScript 5.5
        Sub-match 1 = VBScript
        Sub-match 2 = 5.5
        Match position = 16
        Source string = JScript 5.5 and VBScript 5.5

JScript is great! and VBScript is great!

Dollar Variables

But the buck doesn't stop there! Not only do you get replacement functions, but we've added a host of new replacement tokens for the replace string. These are pretty much borrowed from Perl 5, so some of you may find them familiar. These tokens can be used in the replacement string of a call to String.prototype.replace, but they can't be used inside a replacement function. C'est la vie.

Token Meaning
$$ The literal character '$'
$& The entire matched string
$` The substring preceding the matched substring
$' The substring following the matched substring
$nn The nnth captured submatch
$+ The last matched substring
$_ The entire input string

Formatting Functions

One of the most common questions on the JScript newsgroups is "How do I format a number with two decimal places?" Using VBScript's FormatNumber function, this has been easy—but those using JScript have been forced to use esoteric calculations involving Math.ceil and Math.pow to fulfill this simple desire. Again, JScript 5.5 fixes this problem with three new methods of the Number prototype object: toFixed(fractionDigits), toExponential(fractionDigits), and toPrecision(precision). The first two methods format a number with fractionDigits digits after the decimal place; toFixed uses normal decimal notation, and toExponential uses exponential notation. The last method, toPrecision, formats the number with precision digits of precision, using exponential notation if necessary.

The following simple script illustrates these methods, and the improved toLocaleString (see below):

var x = 1234.56789;

window.alert(x.toString());
window.alert(x.toLocaleString());
window.alert(x.toFixed(3));
window.alert(x.toExponential(3));
window.alert(x.toPrecision(3));

On my computer, this produces the following results (yours may differ for the second line if you have a different number format setup in Control Panel's Regional Settings).

1234.56789
1,234.57
1234.568
1.235e+3
1.23e+3

toLocaleString

Another common request we receive is for the ability to format dates correctly. The string produced by Date.prototype.toString is—for historical reasons—somewhat less than ideal for displaying dates to the user. As such, many script authors revert to writing their own routines to do this.

With the release of JScript 5.5, we have updated toLocaleString for Date, Number, and Array objects so that they all retrieve formatting information from the user's Control Panel settings. For Date objects, the return value is formatted with the user's Long Date format; for Number objects and values, the decimal and thousands separators are used; and for Array objects, the list separator is used to concatenate the individual elements.

While this means that users can finally get to see dates, numbers, and lists in their preferred format, it also means that you cannot rely on the return values of these functions for calculations. You should use toLocaleString only to present information to your users—never as part of your internal computations.

Miscellaneous

We've added a lot of other functionality to JScript for this release—but unfortunately, I cannot list all the new features here. A quick (but incomplete) rundown of some of the remaining features follows:

  • The new in operator checks if an object (or its prototype chain) contains the named property:
if (prop in obj) // obj (or its prototype) has a property named prop
  • New methods of the Object prototype object:
obj.hasOwnProperty(prop) // obj has a property named prop
obj.propertyIsEnumerable(prop) // prop is enumerable in for..in loops
obj.isPrototypeOf(proto) // obj has proto in its prototype chain
  • URI encoding and decoding functions (now you can stop using escape and unescape):
encodeURI(text) // encodes a string as a full URI
encodeURIComponent(text) // encodes a string as a URI component
decodeURI(uri) // decodes a full URI
decodeURIComponent(uricomponent) // decodes a URI component
  • New or improved methods for Array objects (pop, push, shift, unshift, and more)
  • finally blocks for exception handling (actually, these existed in JScript 5.0, but they were undocumented)

What About VBScript?

We haven't forgotten VBScript users with this release. Up until now, VBScript's support for regular expressions has not been as complete as JScript's, but all that changes with VBScript 5.5. VBScript (and the VBScript.RegExp COM component) now has all the features of JScript regular expressions, including submatches and replacement functions.

Submatches in VBScript

VBScript 5.5 now supports capturing parentheses (submatches), just as JScript does. This new functionality is implemented by having a SubMatches collection for each Match object returned by the Execute method. Here's a simple example that shows how to get the components of an e-mail address. (Note that this isn't a complete regular expression for matching e-mail addresses.)

Dim s, re, matches, match
s = "Please send feedback to msscript@microsoft.com"
Set re = New RegExp

' Simple pattern to match an e-mail address, capturing the three parts
re.Pattern = "(\w+)@(\w+)\.(\w+)"
Set matches = re.Execute(s)

' There is only one match, and the collection starts at 0
Set match = matches(0)

' Show the whole match
window.alert("Complete match is " & match)

' Show how many submatches were in the first match
window.alert("Number of captures is " & match.SubMatches.Count)

' Show each submatch
window.alert("Name is " & match.SubMatches(0))
window.alert("Organisation is " & match.SubMatches(1))
window.alert("Root domain is " & match.SubMatches(2))

This results in the following messages being displayed:

Complete match is msscript@microsoft.com
Number of captures is 3
Name is msscript
Organization is microsoft
Root domain is com

In VBScript 5.5, both the MatchCollection and the SubMatches collections are zero-based. We have also made Item the default property of the MatchCollection and SubMatch objects.

Replacement Functions in VBScript

Replacement functions in VBScript work much the same as in JScript. Simply pass the return of the GetRef function instead of the replacement string:

' Simple replacement function. Note that in VBScript you must include
' all the parameters to the function, even if they are not used.
Function Test(match, pos, source)
   Test = "matched '" & match & "' at position " & pos & "..."
End Function

Dim s, re

' Setup a simple test
s = "Hello there"
Set re = New RegExp
re.Pattern = "\w+"
re.Global = True

' Call the Replace method and display the result
s = re.Replace(s, GetRef("Test"))
window.alert(s)

This code displays the string "matched 'Hello' at position 0... matched 'there' at position 6... ".

Using the replacement function in Visual Basic (not VBScript) is a little more involved, because Visual Basic doesn't support the GetRef function. Instead, you need to create a Class module with a public default function, and pass a new instance of that object to the Replace method. You can make a function the default member of a class in Visual Basic 6.0 by choosing Procedure Attributes from the Tools menu, clicking the Advanced button, and changing the Procudure ID control to "(Default)". The name of the function does not matter, because the RegExp object will simply call whatever method happens to be registered as the default. See the Visual Basic documentation for more information on how to create classes and set their attributes.

More Information and Feedback

Updated documentation for Windows Script 5.5 is now available on the Windows Script Technologies Web site.

Please direct any feedback you have for this release to the Windows Script team at msscript@microsoft.com or on our public newsgroup, news://msnews.microsoft.com/microsoft.public.scripting.jscript. A complete (but brief) list of the new features in Windows Script 5.5 can be found on the JScript newsgroup, which is archived on Deja.com at http://x40.deja.com/getdoc.xp?AN=555513203.

We hope you find the new features in Windows Script 5.5 useful!

 

Scripting Clinic

Peter Torr is an Australian Program Manager for Windows Script at Microsoft, and spends his entire day listening to German trance music. In his spare time, he watches movies, cringes at "American English," watches movies, complains about the food here, and watches movies.