Chapter 16. Regular Expressions

<feature><title>In This Chapter</title> </feature>

Regular expressions enable you to look for substrings that match a pattern, making them a powerful way to work with strings. For basic substring searches, you can use String class methods such as indexOf(). Methods such as indexOf() work when you know the exact substring you want to find. For example, if you want to determine whether a string contains the substring cog you can use the following code:

var string:String = "Gears and cogs";
var index:int = string.indexOf("cog");
trace("contains substring? " + (index != -1));

The preceding code determines whether string contains the letters “cog.” If the indexOf() method doesn’t return -1, then it means that it found an occurrence of the substring.

When writing sophisticated programs, it’s entirely likely that you’ll want to search for substrings in a more abstract fashion. For example, consider if you wanted to determine whether a string contains the substring cog or the substring log. In such a case, you could simply test for both substrings using indexOf(). Yet as the number of possible substrings for which you want to test increases, so too does the complexity of the code necessary to test for every substring using indexOf(). If you wanted to test for all substrings that start with a letter followed by og, then you have to test for 52 possible substrings. More complex substring possibilities could require testing for hundreds, thousands, millions, even billions of substrings. This is where regular expressions simplify things greatly.

Regular expressions are a way of testing for substrings by using patterns. Regular expressions use standard characters such as letters and numbers as well as special metacharacters and metasequences to form these patterns. In this chapter, we’ll look at how to build and work with regular expressions using ActionScript 3.0.

Note

Regular expressions are supported natively in Flash Player 9 with ActionScript 3.0.

Introducing the RegExp Class

ActionScript 3.0 uses the RegExp class to define regular expressions. There are two basic ways to construct a RegExp object. You can use the RegExp constructor or literal notation (that is, you can type it in directly as you would type a string or a number). Which you choose is mainly a matter of preference and usage. The constructor requires that you pass it at least one parameter specifying the regular expression pattern as a string. The following example constructs a RegExp object that matches any substring that contains between 4 and 8 lowercase alphabetic characters:

var pattern:RegExp = new RegExp("[a-z]{4,8}");

Note that when you want to define a regular expression pattern at runtime, you ought to use the constructor because it allows you to use a string value to define the expression. You can build this string using code, allowing you to create patterns appropriate for the situation at hand.

The literal notation surrounds the regular expression pattern by forward slashes (/). In the case of literal notation, the pattern is not a string and therefore is not surrounded by quotes. The following constructs a RegExp object that matches the same substrings as the preceding example. However, rather than using the constructor, the following example uses literal notation:

var pattern:RegExp = /[a-z]{4,8}/;

Note that when you want to add a backslash to a pattern that you build with the constructor, you’ll have to escape the backslash because the constructor requires that you specify the pattern as a string and the backslash character has special meaning within a string. Many regular expression metasequences use backslashes. For example, the d metasequence matches any digit. The following statement illustrates how to construct a RegExp object that matches any digit. Because the example uses the constructor, it’s necessary to escape the backslash.

var pattern:RegExp = new RegExp("\d");

Because the backslash character doesn’t have special meaning within regular expression literal notation, the following is the literal notation equivalent of the preceding example:

var pattern:RegExp = /d/;

However, because forward slashes have special meaning within regular expression literal notation, you must escape forward slashes. You can escape forward slashes with backslashes. The following matches the literal forward slash character:

var pattern:RegExp = ///;

Working with Regular Expressions

You can work with regular expressions by way of the RegExp methods or by way of the String methods that accept RegExp parameters. The methods are as follows.

  • RegExp.test()

  • RegExp.exec()

  • String.search()

  • String.replace()

  • String.match()

We’ll next look at using these methods in the following sections.

Boolean Testing Regular Expressions

The test() method is a method of the RegExp class, and it accepts a string parameter. The test() method returns true if the string parameter contains a substring that matches the regular expression pattern. Otherwise it returns false. The following example uses a regular expression that matches one or more lowercase characters. As you can see from the comments, the test() method returns true or false depending on whether or not the regular expression matches a substring in the string parameter.

var pattern:RegExp = /[a-z]+/;
var string:String = "abcd";
trace(pattern.test(string));  // true
string = "1234";
trace(pattern.test(string));  // false

Finding Matching Substring Indices

The search() method is a method of the String class, and it accepts a regular expression parameter. The search() method returns the index of the first matching substring. The following example locates the index of the first word that starts with the letter p:

var pattern:RegExp = /p[a-z]+/;
var string:String = "There is no path to peace. Peace is the path.";
trace(string.search(pattern));  // 12

The search() method always starts searching from the start of the string. The global flag and lastIndex property have no effect on search().

Retrieving Matching Substrings

The RegExp.exec() method and the String.match() method both enable you to retrieve matching substrings using regular expressions. However, the two methods operate in different ways.

The exec() method returns an array containing the substring matched. As discussed in the section, “Using Regular Expression Groups,” later in this chapter, the array returned by exec() can also contain group subpattern matches.

Even when the global flag is set, the exec() method only ever finds one match at a time. The exec() method sets the lastIndex property of the RegExp object from which it was called to the index immediately following the most recent matching substring. This arrangement allows you to continue to apply the exec() method to a string, and it finds the next matching substring each time. The exec() method uses the lastIndex property to determine that index from which to start the next search. When no more matching substrings are found, exec() returns null and resets lastIndex to 0. The following code illustrates the use of exec() as well as lastIndex. When you run the code, you can see that after null is returned, the lastIndex property is reset to 0. Following that, the method searches from the start of the string once again. The following example outputs each word within the string, one at a time, along with the index of the last character in the string that was previously examined:

var pattern:RegExp = /[a-z]+/ig;
var string:String = "There is no path to peace. Peace is the path.";
for(var i:uint = 0; i < 12; i++) {
    trace(pattern.exec(string));
    trace(pattern.lastIndex);
 }

The lastIndex property is a read-write property. That means you can set lastIndex so that it specifies the starting index from which exec() ought to start searching.

The match() method returns an array of all the substrings that match the pattern. Use the global flag to match all substrings. If you don’t set the global flag, the match() method returns an array with only the first matching substring. The following example illustrates the use of the match() method.

// This expression will match strings that follow the rules of email
// address values.
var pattern:RegExp = /(?:w|[_.-])+@(?:(?:w|-)+.)+w{2,4}/g;
var string:String = "emails: [email protected], [email protected], [email protected]";
trace(string.match(pattern));
// [email protected], [email protected], [email protected]

Replacing Substrings Using Regular Expressions

The replace() method is a String method that enables you to replace substrings using regular expressions. When using regular expressions with the replace() method, use the global flag if you want to replace all instances of a pattern or it will only replace the first instance. The following example replaces all email address with <email>@<domain>.com.

var pattern:RegExp = /((?:w|[_.-])+)@(?:((?:w|-)+).)+w{2,4}+/;
var string:String = "The following was posted by [email protected].";
trace(string.replace(pattern, "<email>@<domain>.com"));
// outputs: The following was posted by <email>@<domain>.com.

For more complex replacements, you can specify a function reference for the second parameter in place of the string. That function is then passed the following parameters:

  • The matching substring

  • Capturing groups (see “Using Regular Expression Groups”) (Note that if there are no capturing groups then these parameters are omitted.)

  • The index of the matching substring

  • The original string

The function should return a string. The string the function returns is what gets substituted.

The following example uses a replacement function:

package {

   import flash.display.Sprite;
   
   public class RegularExpressions extends Sprite {

   public function RegularExpressions() {
   // The ?: sequences make the groups non-capturing as discussed in 
   // the sections on groups in this chapter.
   var pattern:RegExp = /(?:w|[_.-])+@(?:(?:w|-)+.)+w{2,4}/;
   var string:String = "The following was posted by [email protected].";
   trace(string.replace(pattern, replacer));
   // Prints out: The following was posted by user AT domain DOT com
   }

   // The regular expression doesn't have any capturing groups, so the
   // function only expects three parameters.
   private function replacer(match:String, index:int, originalString:String):String {
   var string:String = match.replace("@", " AT ");
   string = string.replace(/./g, " DOT ");
   return string;
   }

   }

}

Using Regular Expression Flags

ActionScript lets you specify flags that affect how regular expressions work. Table 16.1 is a comprehensive list of those flags.

Table 16.1. Regular Expression Flags

Flag

Description

g

Global

i

Ignore case

m

Multiline

s

Dot matches newlines

x

Extended notation allows spaces in regular expression patterns

You can specify flags when you construct a RegExp object. When you use the constructor, you specify the flags as a second string parameter. The following example matches all substrings of 4 to 8 alphabetical characters:

var pattern:RegExp = new RegExp("[a-z]{4,8}", "ig");

You can specify the flags with literal notation by adding the flag characters following the second forward slash, as shown here:

var pattern:RegExp = /[a-z]{4,8}/ig;

The order in which you specify the flags makes no difference. Specifying igms is the same as specifying sgim.

The Global Flag

By default, regular expressions match only the first instance of a pattern in a string. Obviously, there are cases in which it’s beneficial to be able to match every instance of a pattern, not just the first instance. The global flag enables just that. Consider the following example. The pattern matches whole words. By default, it finds only one matching substring.

var pattern:RegExp = /w+/;
var string:String = "There is no path to peace. Peace is the path.";
trace(string.match(pattern).toString());  // There

The preceding code finds just the first substring because the global flag is not set. The following changes the preceding example simply by setting the global flag. Notice that it then finds every word.

var pattern:RegExp = /w+/g;
var string:String = "There is no path to peace. Peace is the path.";
trace(string.match(pattern).toString()); // There,is,no,path,to,peace,Peace,is,
the,path

Note

Both examples in this section return arrays. Because the first example does not have the global flag set, it returns only one element in the array.

The Ignore Case Flag

By default, ActionScript regular expressions make a distinction between lowercase and uppercase characters. For example, A and a are not considered the equivalent by default. The following code illustrates this. The pattern is supposed to match any continuous sequence of lowercase characters bordered by non-word characters (such as a space). Notice that the first matching substring it finds is the second word because the first word has an uppercase character.

var pattern:RegExp = /[a-z]+/;
var string:String = "There is no path to peace. Peace is the path.";
trace(string.match(pattern));  // is

Setting the i flag causes the regular expression to ignore the distinction between uppercase and lowercase characters. The following regular expression differs from the preceding code only by the i flag being set. The following regular expression matches the first word in the string.

var pattern:RegExp = /[a-z]+/i;
var string:String = "There is no path to peace. Peace is the path.";
trace(string.match(pattern));  // There

The Multiline Flag

The ^ character matches the start of a string, and the $ character matches the end of a string by default. Consider the following example in which the pattern looks for any sequence of lowercase and uppercase characters, spaces, and dots in which the substring is at the start of the string. Notice that the newline character ( ) delimits the substring, and even though the global flag is set, the regular expression finds only one substring.

var pattern:RegExp = /^[a-zA-Z .]+/g;
var string:String = "There is no path to peace.
Peace is the path.";
trace(string.match(pattern));  // There is no path to peace.

The multiline flag causes the ^ character to match both the start of the string and the start of the second line, and the $ character to match both the end of the string and the end of the first line. The following example is identical to the preceding code except that it also sets the multiline flag. Notice that it now matches both lines from the string.

var pattern:RegExp = /^[a-zA-Z .]+/mg;
var string:String = "There is no path to peace.
Peace is the path.";
trace(string.match(pattern));  // There is no path to peace.,Peace is the path.

The Dot Matches Newline Flag

The dot (.) character matches any character. However, it does not match the newline character by default. The following example illustrates the default behavior. The pattern matches one or more non-newline characters. Note that because the pattern does not match the newline character, it matches the substring only up until the newline character.

var pattern:RegExp = /.+/;
var string:String = "There is no path to peace.
Peace is the path.";
trace(string.match(pattern));  // There is no path to peace.

The s flag causes the dot to match every character including the newline character. The following example illustrates the difference with the s flag.

var pattern:RegExp = /.+/s;
var string:String = "There is no path to peace.
Peace is the path.";
trace(string.match(pattern));
/*
There is no path to peace.
Peace is the path.
*/

The Extended Flag

By default, spaces within patterns are interpreted literally. For complex regular expressions, that fact can make the patterns difficult to read by humans. For the purposes of legibility, you might want to add additional spaces to a pattern. If you want Flash Player to ignore additional spaces added for the purposes of legibility, you can set the x flag. Consider the following example that matches an email address:

var pattern:RegExp = /(w|[_.-])+@((w|-)+.)+w{2,4}+/;
var string:String = "[email protected]";
trace(pattern.test(string));  // true

The pattern is complex and perhaps difficult to read. You can make it more legible by adding spaces, as follows:

var pattern:RegExp = /    ( w | [_.-] )+    @    ( ( w | - )+ . )+   w{2,4}+    /;
var string:String = "[email protected]";
trace(pattern.test(string));  // false

In the preceding example, the x flag is not set, so the spaces are interpreted literally, and the test evaluates to false. The following example evaluates to true because the x flag is set so that the additional spaces are ignored.

var pattern:RegExp = /    ( w | [_.-] )+    @    ( ( w | - )+ . )+   w{2,4}+    /x;
var string:String = "[email protected]";
trace(pattern.test(string));  // true

If you want to match a literal space when the x flag is set, you can use a backslash to escape the space, as shown here:

var pattern:RegExp = /a b/x;
var string:String = "a b";
trace(pattern.test(string)); // true

Understanding Metacharacters and Metasequences

Regular expressions are composed of standard characters such as letters and numbers as well as special characters and sequences called metacharacters and metasequences. These metacharacters and metasequences are what enable regular expressions to match abstract patterns. For example, using the metasequence d, you can match any digit, which is more abstract than matching a specific digit.

The metacharacters used by regular expressions enable you to match specific parts of a string, group characters, and even perform logical operations. The list of metacharacters used by regular expressions is relatively short. The metacharacters are summarized in Table 16.2.

Table 16.2. Metacharacters Used in Regular Expressions

Metacharacter

Description

^

The start of the string or the start of a line when the m flag is set

$

The end of a string or the end of a line when the m flag is set

Escape a metacharacter or metasequence so it is interpreted literally

.

Any character; includes the newline character only when the s flag is set

*

Zero or more occurrences of the preceding item

+

One or more occurrences of the preceding item

?

Zero or one occurrences of the preceding item

()

A group

[]

A character class

|

Either the item on the left or the item on the right

The metasequences are sequences of characters that are interpreted in a specific manner by regular expressions. Table 16.3 summarizes the regular expression metasequences.

Table 16.3. Metasequences Used in Regular Expressions

Metasequence

Description

{n}

n occurrences of the preceding item

{n,}

n or more occurrences of the preceding item

{n,m}

Between n and m occurrences of the preceding item

A

The start of the string



The border between a word character (a-z, A-Z, 0-9, or _) and a non-word character including the start and end of a string

B

The border between two word characters or two non-word characters

d

Any digit

D

Any non-digit

Newline

Return

s

Any whitespace character

S

Any non-whitespace character

Tab

unnnn

The Unicode character represented by the character code nnnn

w

Any word character (a-z, A-Z, 0-9, or _)

W

Any non-word character

xnn

The character represented by the ASCII value nn

z

The end of the string including any final newline character



The end of the string excluding any final newline character

Using Character Classes

Character classes are denoted by square brackets ([]), and they enable you to specify a set of characters for one position within a regular expression. For example, the following regular expression uses a character class to match any substring that starts with b, followed by any vowel, and ending with a t.

var pattern:RegExp = /b[aeiou]t/g;
var string:String = "The bat lost the bet, but he didn't mind a bit.";
trace(string.match(pattern));  // bat,bet,but,bit

Most metacharacters and metasequences aren’t interpreted as such within a character class. For example {5} is interpreted literally as the digit 5 and the right and left curly brace characters when placed within a character class. The exceptions are the metasequences , , , unnnn, and xnn. In addition, the , ], and characters have special meaning within character classes.

The - (hyphen) character within a character class can indicate a range of characters. For example, the following code defines a regular expression that matches any lowercase alphabetical character:

var pattern:RegExp = /[a-z]/;

You can define valid ranges of uppercase and lowercase alphabetical characters, digits, and ASCII character codes. If you use a - character such that it does not define a valid range, then it will be interpreted literally. For example, the following defines a character class that matches all lowercase characters, digits, and the - character:

var pattern:RegExp = /[a-z-0-9]/;

The ] character closes a character class. If you want to match the literal ] character within a character class, you have to escape it. The backslash character () is the escape character. The following example matches all lowercase characters or the right square bracket character:

var pattern:RegExp = /[a-z]]/;

If you want to match the literal backslash character, you can escape it with a preceding backslash character. The following matches any lowercase character or the backslash character:

var pattern:RegExp = /[a-z\]/;

Working with Quantifiers

The metacharacters and metasequences *, +, ?, {n}, {n,}, and {n,m} are quantifiers. They allow you to specify repetitions within patterns. Quantifiers are applied to the item preceding them. An item can be a character, metasequence, character class, or group.

The following example uses the + operator to find all the substrings that consist of alphabetical characters:

var pattern:RegExp = /[a-z]+/ig;
var string:String = "There is no path to peace. Peace is the path.";
trace(string.match(pattern));  // There,is,no,path,to,peace,Peace,is,the,path

The following code matches only the words that are 4 or 5 characters:

var pattern:RegExp = /[a-z]{4,5}/ig;
var string:String = "There is no path to peace.
Peace is the path.";
trace(string.match(pattern));  // There,path,peace,Peace,path

Using Regular Expression Groups

Regular expression groups are denoted by parentheses. You can use groups for the following basic purposes:

  • Add quantifiers to more than one character

  • Add more control to logical or operations

  • Remember subpattern matches for subsequent use in the code

Quantifiers apply to the preceding item. The preceding item might be a character, metasequence, character code, or group. The following example uses a regular expression that matches substrings with an is followed by one or more s characters:

var pattern:RegExp = /iss+/g;
var string:String = "Mississippi";
trace(string.match(pattern)); // iss,iss

The following example matches all substrings composed of one or more iss sequences:

var pattern:RegExp = /(iss)+/g;
var string:String = "Mississippi";
trace(string.match(pattern));  // ississ

The | character normally matches the entire pattern on either side of the character. For example, the following code uses a regular expression that matches either re or ad:

var pattern:RegExp = /re|ad/g;
var string:String = "red is rad";
trace(string.match(pattern));  // re,ad

If parentheses are used, then the | operates on just the characters surrounded by the parentheses as shown in the following example:

var pattern:RegExp = /r(e|a)d/g;
var string:String = "red is rad";
trace(string.match(pattern));  // red,rad

Parentheses also enable you to use backreferences. Backreferences allow you to reference a grouped substring within the regular expression. You can reference each group numerically from 1 to 99. The following illustrates a backreference:

var pattern:RegExp = /(d) = 1/g;
var string:String = "1 = 1, 2 = 1 + 1, 3 = 1 + 1 + 1, 4 = 4";
trace(string.match(pattern));  // 1 = 1,4 = 4

In the preceding example, the 1 references the first group in the regular expression: the substring matched by (d). The following is a similar example. Notice that in this case, the pattern doesn’t match 2 = 2 because the grouped substring must consist of two digits:

var pattern:RegExp = /(dd) = 1/g;
var string:String = "20 = 20, 2 = 2, 3 = 1 + 1 + 1, 40 = 40";
trace(string.match(pattern));  // 20 = 20,40 = 40

The following example uses two backreferences:

var pattern:RegExp = /(d)(d) = 21/g;
var string:String = "42 = 24, 2 = 2, 3 = 1 + 1 + 1, 40 = 40";
trace(string.match(pattern));  // 42 = 24

You can use $1 through $99 as references to grouped substrings when using the String.replace() method. The following example illustrates how to use these references:

var pattern:RegExp = /([a-z]+) function ([a-zA-Z]+)():([a-zA-Z]+)/g;
var string:String = "public function example():void";
trace(string.replace(pattern, "The function called $2 is declared as $1 with a return type
Using Regular Expression Groups of $3"));

When you call the RegExp.exec() method, it returns an array with the current matching substring as well as any grouped substring.

var pattern:RegExp = /([a-z]+) function ([a-zA-Z]+)():([a-zA-Z]+)/g;
var string:String = "public function example():void { trace('example');}";
var substrings:Array = pattern.exec(string);
trace(substrings[0]);  // public function example():Void
trace(substrings[1]);  // public
trace(substrings[2]);  // example
trace(substrings[3]);  // void

You can also defined named groups using ?P<groupName> immediately following the opening parenthesis. In this case, RegExp.exec() returns an associative array where the names of the captured groups are keys of the array. The entire matched string is still returned in the first index. The following example is a rewrite of the preceding code such that it uses named groups:

var pattern:RegExp = /(?P<modifier>[a-z]+) function (?P<functionName>[a-zA-Z]+)():(
Using Regular Expression Groups?P<returnType>[a-zA-Z]+)/g;
var string:String = "public function example():void {trace('example');}";
var substrings:Array = pattern.exec(string);
trace(substrings[0]);
trace(substrings.modifier);
trace(substrings.functionName);
trace(substrings.returnType);

You can also instruct the regular expression not to capture a group. For example, you might want to use a group with a quantifier, but without capturing the group. In such cases, you can use ?: immediately following the opening parenthesis. The following example uses a standard capturing group. Notice that the array returned by exec() has two elements because it captures the subpattern.

var pattern:RegExp = /i(s|p){2}/;
var string:String = "Mississippi";
trace(pattern.exec(string));  // iss,s

The following code rewrites the preceding example such that it uses a non-capturing group. In this example, the array returned by exec() has just one element:

var pattern:RegExp = /i(?:s|p){2}/;
var string:String = "Mississippi";
trace(pattern.exec(string));  // iss

Lookahead groups are non-capturing groups that can be either positive (the subpattern must appear) or negative (the subpattern must not appear.) Positive lookahead groups are denoted by ?= following the opening parenthesis. A positive lookahead group says that the specified subpattern must appear in that position, but it will not be included in the match. Frequently, positive lookahead groups are used to match patterns that are followed by a specific pattern. For example, consider a string that contains filenames with file extensions. If you want to retrieve the filenames minus the file extensions from the string, you can use a positive lookahead group as in the following example:

var pattern:RegExp = /[a-z]+(?=.[a-z]+)/g;
var string:String = "Copy the program.exe and run.bat files. Move file.txt.";
trace(string.match(pattern));  // program,run,file

You can use positive lookahead groups for complex patterns that would be extremely difficult or impossible to match otherwise. Consider the example of an alphanumeric password that must be between 6 and 20 characters and must contain at least 2 digits as well as at least 1 lowercase and 1 uppercase character. The following example uses positive lookahead groups to accomplish that goal:

var pattern:RegExp = /(?=.*d.*d)(?=.*[a-z])(?=.*[A-Z])[a-zA-Z0-9]{6,20}/;
var string:String = "a1b2cd3e4";  // No uppercase
trace(pattern.test(string)); // false
string = "aBcdefg";  // No digits
trace(pattern.test(string));  // false
string = "a1B2cd3e4";
trace(pattern.test(string));  // true

Negative lookahead groups are denoted by ?!. Negative lookahead groups work just like positive lookahead groups, but they define subpatterns that must not appear. The following example uses a negative lookahead group to match all filenames (with file extensions) that don’t have the file extension .txt.

var pattern:RegExp = /[a-z]+(?!.txt).([a-z]+)/g;
var string:String = "Copy the program.exe and run.bat files. Move file.txt.";
trace(string.match(pattern));  // program.exe,run.bat

The following example rewrites the preceding regular expression slightly so that it matches all filenames except those that have file extensions of .txt or .bat:

var pattern:RegExp = /[a-z]+(?!.txt|.bat).([a-z]+)/g;
var string:String = "Copy the program.exe and run.bat files. Move file.txt.";
trace(string.match(pattern));  // program.exe

Building a Mad Libs Application Using Regular Expressions

In this example application, we’ll use regular expressions to build a Mad Lib application. Mad Libs are the fill-in-the-blank word games that prompt the user to specify words without knowing the context of the words. The words are then used to fill in the blanks of a story, often with humorous results.

The Mad Lib application we’ll build consists of the following elements:

  • MadLibTextElementData: A data model class for each text element, whether plain text or substitutable text.

  • MadLibInputItemData: A data model class for each of the word blanks that stores both the original value from the text file and the user-provided value that is substituted for the original. MadLibInputItemData is a subclass of MadLibTextElementData.

  • MadLibData: A data model class for the entire story. The data model consists of several arrays of MadLibInputItemData and MadLibTextElementData objects. The class loads the data from a text file; after the data has loaded, it dispatches an event to all listeners. MadLibData is a Singleton class (see Chapter 4, “Singleton Pattern,” for more information).

  • MadLibInputItem: A control that allows the user to input a word. MadLibInputItem objects use MadLibInputItemData objects as their data models.

  • FormScreen: A view class that renders the form of MabLibIinputItem controls for each of the word blanks from the MadLibData instance.

  • ResultScreen: A view class that renders the story with the user-substituted words.

  • MadLibs: The main class that renders a FormScreen and ResultScreen instance, and uses buttons to allow the user to toggle between the screens.

Additionally, the application has to load text from a file to use as the Mad Libs text. To start, create a file called madlibstory.txt in the deploy directory for the application. Then add the following text to the document.

There was once an old <type of building>, that stood in the middle of a deep gloomy 
wood, and in the <type of building> lived an old fairy. Now this fairy could take 
any shape she pleased. All the day long she flew about in the form of a/n <something 
that flies>, or crept about the country like a/n <something that moves on land>; but 
at night she always became an old woman again. When any young man came within a 
hundred paces of her castle, he became quite fixed, and could not move a step till 
she came and set him free; which she would not do till he had given her his word 
never to <something you like to do> again: but when any pretty maiden came within 
that space she was changed into a/n <something that goes in a cage>, and the fairy 
put her into a cage, and hung her up in a chamber in the castle. There were seven 
hundred of these cages hanging in the castle, and all with beautiful <something that 
goes in a cage> in them.

The words and phrases that appear within <> are the word blanks. The application uses regular expressions to substitute those values.

Creating the Data Model Classes

As described previously, the Mad Libs application uses several data model classes. The first class we’ll define is the data model class for each text element used in the application. A text element could be a substitutable or non-substitutable portion of the application. Define the com.peachpit.aas3wdp.madlibs.data.MadLibTextElementData class as follows:

package com.peachpit.aas3wdp.madlibs.data {
  
   import flash.events.EventDispatcher;
   import flash.events.Event;

   public class MadLibTextElementData extends EventDispatcher {

      public static const UPDATE:String = "update";

      private var _data:String;

      public function get data():String {
         return _data;
       }

      public function set data(value:String):void {
         _data = value;
         dispatchEvent(new Event(UPDATE));
      }

      public function MadLibTextElementData(value:String = "") {
         _data = value;
      }
     
   }
}

This class simply holds the string value for a text element, whether substitutable or non-substitutable.

Next, we’ll create the data model class that is specific to substitutable text. This class is called MadLibInputItemData, and it extends MadLibTextElementData. This class stores one additional piece of data: the label to use for the input. Define com.peachpit.aas3wdp.madlibs.data.MadLibInputItemData as follows:

package com.peachpit.aas3wdp.madlibs.data {

   import flash.events.EventDispatcher;
   import flash.events.Event;

   public class MadLibInputItemData extends MadLibTextElementData {

     private var _label:String;

     // The default label includes <>. Use a regular expression
     // to return the value between the <>.
     public function get labelFormatted():String {
        var pattern:RegExp = /[a-z ]+/i;
     return _label.match(pattern)[0];
     }

    public function get label():String {
       return _label;
     }

    public function set label(value:String):void {
       _label = value;
    }

    public function MadLibInputItemData(value:String) {
      _label = value;
    }

  }
}

Next we’ll create a class to serve as the data model for the entire Mad Libs application. This is the most complex of the data model classes. It should store collections of instances of the other data model classes. The class should then define an interface that allows access to those collections using iterators. Define com.peachpit.aas3wdp.madlibs.data.MadLibData as follows:

package com.peachpit.aas3wdp.madlibs.data {

   import com.peachpit.aas3wdp.collections.ICollection;
   import com.peachpit.aas3wdp.iterators.ArrayIterator;
   import com.peachpit.aas3wdp.iterators.IIterator;

   import flash.events.Event;
   import flash.events.EventDispatcher;
   import flash.net.URLLoader;
   import flash.net.URLRequest;

   public class MadLibData extends EventDispatcher {

      private var _items:Array;
      private var _textElements:Array;

      private static var _instance:MadLibData;

      public static const UPDATE:String = "update";
      public static const INPUT_ITEMS:String = "inputItems";
      public static const ALL_ITEMS:String = "allItems";

      public function MadLibData(enforcer:SingletonEnforcer) {
      }

      public static function getInstance():MadLibData {
          if(_instance == null) {
             _instance = new MadLibData(new SingletonEnforcer());
          }
          return _instance;
      }

     // Load the data from a specified location.
     public function load(file:String):void {
        var loader:URLLoader = new URLLoader();
        var request:URLRequest = new URLRequest(file);
        loader.addEventListener(Event.COMPLETE, onData);
        loader.load(request);
      }

      // Return an iterator of either all the text elements
      // (inclusive of the input items) or just the input items.
      public function iterator(type:String = ALL_ITEMS):IIterator {
        if(type == INPUT_ITEMS) {
          return new ArrayIterator(_items);
        }
        else {
          return new ArrayIterator(_textElements);
        }
     }
     // The onData() method is the listener that gets called
     // when the text data loads.
     private function onData(event:Event):void {

        // Retrieve the data from the URLLoader.
        var text:String = String(event.target.data);

        // Define a regular expression that will match all
        // the substitutable text.
        var expression:RegExp = /<[a-z0-9 ]+>/ig;

        // Match all the substitutable items.
        var items:Array = text.match(expression);

        // The _items array stores references to the
        // MadLibInputItemData objects. The _textElements
        // array stores references to all the text elements,
        // including the input items.
        _items = new Array();
        _textElements = new Array();

        // Make an array of all the text element text.
        var textElementsText:Array = text.split(expression);

        var index:uint = 0;
        var item:MadLibInputItemData;
        var newItem:Boolean;

        // Loop through all the matched items.
        for(var i:uint = 0; i < items.length; i++) {

           // With each iteration initially assume the
           // input item is new.
           newItem = true;

           // Create a new MadLibInputItemData object.
           item = new MadLibInputItemData(String(items[i]));

           // Loop through all the items already stored 
           // in the _items array. If the current item
           // label is equal to that of an existing
           // item in the array, then use the existing
           // item, and don't add the new item to the
           // _items array.
           for(var j:uint = 0; j < _items.length; j++) {
               if(item.label == _items[j].label) {
               item = _items[j];
               newItem = false;
               break;
              }
            }
            if(newItem) {
               _items.push(item);

               // Listen for UPDATE events from the
               // item.
               item.addEventListener(MadLibTextElementData.UPDATE, onUpdate);
             }

           // Add the text element for the non-
           // substitutable text.
           _textElements[index] = new MadLibTextElementData(String(textElementsText 
           [index]));

           // Add the text element for the
           // substitutable text.
           _textElements.splice(index + 1, 0, item);

           // Increment the index by 2 since each
           // iteration adds two items to the
           // _textElements array.
           index += 2;
         }

         // Notify listeners that the data model has updated.
         dispatchEvent(new Event(UPDATE));
       }
     
       private function onUpdate(event:Event):void {
          dispatchEvent(new Event(UPDATE));
       }

   }
}
class SingletonEnforcer {}

Creating the Input Control

Next we’ll create a class to use as an input control. The Mad Lib application consists of two screens: one that accepts user input and one that displays the results of the input combined with the story. The MadLibInputItem class defines the input control elements used on the form screen. This class uses MadLibInputItemData as a data model. Define com.peachpit.aas3wdp.madlibs.controls.MadLibInputItem as follows:

package com.peachpit.aas3wdp.madlibs.controls {

   import flash.display.Sprite;
   import flash.text.TextField;
   import flash.events.TextEvent;
   import com.peachpit.aas3wdp.madlibs.data.MadLibInputItemData;

   public class MadLibInputItem extends Sprite {

     private var _label:TextField;
     private var _value:TextField;
     private var _data:MadLibInputItemData;

     public function MadLibInputItem(data:MadLibInputItemData) {
       _data = data;

       // Add a text field to display the input label.
       _label = new TextField();
       _label.autoSize = "left";
       _label.text = data.labelFormatted;
       addChild(_label);

      // Add an input text field for the user value.
      _value = new TextField();
      _value.type = "input";
      _value.border = true;
      _value.background = true;
      _value.width = 200;
      _value.height = 20;
      _value.x = 200;
      addChild(_value);

      // Listen for TEXT_INPUT events.
      _value.addEventListener(TextEvent.TEXT_INPUT, onText);
    }

    // When the user updates the text, update the value stored
    // in the data model.
    private function onText(event:TextEvent):void {
       _data.data = event.target.text + event.text;
    }

  }
}

Creating the View Classes

Now we’ll create the two screens used by the application. Each uses MadLibsData as the data model. However, each displays the data in different ways. The first class, FormScreen, displays just the inputs for substitutable text. Define com.peachpit.aas3wdp.madlibs.views.screens.FormScreen as follows:

package com.peachpit.aas3wdp.madlibs.views.screens {

   import com.peachpit.aas3wdp.iterators.IIterator;
   import com.peachpit.aas3wdp.madlibs.controls.MadLibInputItem;
   import com.peachpit.aas3wdp.madlibs.data.MadLibData;
   import com.peachpit.aas3wdp.madlibs.data.MadLibInputItemData;

   import flash.display.Sprite;
   import flash.events.Event;

   public class FormScreen extends Sprite {
     
     public function FormScreen(data:MadLibData) {
        data.addEventListener(MadLibData.UPDATE, onUpdate);
     }

     private function onUpdate(event:Event):void {
        // If the screen hasn't already drawn itself, then
        // add input items for each of the elements from the
        // data model's INPUT_ITEMS iterator.
        if(numChildren == 0) {
             var data:MadLibData = MadLibData(event.target);
             var iterator:IIterator = data.iterator(MadLibData.INPUT_ITEMS);
             var item:MadLibInputItem;
             var y:Number = 0;
             while(iterator.hasNext()) {
                 item = new MadLibInputItem(MadLibInputItemData(iterator.next()));
                 item.y = y;
                 y += 25;
                 addChild(item);
            }
          }
        }
     }
 }

Now we’ll create the screen that displays the results of the user input. This screen displays both the non-substitutable text as well as the user input text in place of the substitutable text. Define com.peachpit.aas3wdp.madlibs.views.screens.ResultScreen as follows.

package com.peachpit.aas3wdp.madlibs.views.screens {
   
   import flash.display.Sprite;
   import com.peachpit.aas3wdp.madlibs.data.MadLibData;
   import com.peachpit.aas3wdp.madlibs.data.MadLibTextElementData;
   import com.peachpit.aas3wdp.iterators.IIterator;
   import flash.text.TextField;
   import flash.events.Event;

   public class ResultScreen extends Sprite {

     private var _text:TextField;

     public function ResultScreen(data:MadLibData) {
        data.addEventListener(MadLibData.UPDATE, onUpdate);

        // Add a text field to display the story.
        _text = new TextField();
        _text.width = 400;
        _text.height = 400;
        _text.multiline = true;
        _text.wordWrap = true;
        addChild(_text);
      }

    // When the data model dispatches an UPDATE event, update
    // the text correspondingly.
    private function onUpdate(event:Event):void {
       var data:MadLibData = MadLibData(event.target);
       _text.text = "";
       var iterator:IIterator = data.iterator(MadLibData.ALL_ITEMS);
       while(iterator.hasNext()) {
          _text.appendText(MadLibTextElementData(iterator.next()).data);
        }

      }

   }

}

Defining the Main Class

We have yet to create the main class that puts the application together. In this class, we create instances of the two screens and use buttons to toggle between the screens. You’ll need to ensure that the AAS3WDP library is in your project’s class path for this to work. Here’s the main class.

package {

  import flash.display.Sprite;
  import flash.events.Event;
  import flash.events.MouseEvent;
  import com.peachpit.aas3wdp.madlibs.data.MadLibData;
  import com.peachpit.aas3wdp.controls.BasicButton;
  import com.peachpit.aas3wdp.madlibs.views.screens.FormScreen;
  import com.peachpit.aas3wdp.madlibs.views.screens.ResultScreen;

  public class MadLibs extends Sprite {

    private var _data:MadLibData;
    private var _formScreen:FormScreen;
    private var _resultScreen:ResultScreen;
    private var _formButton:BasicButton;
    private var _resultButton:BasicButton;

    public function MadLibs() {

       // Tell the data model to load the data from the
       // text file.
       _data = MadLibData.getInstance();
       _data.load("madlibstory.txt");

       // Add the two screens. Only add the form screen to
       // the display list.
       _formScreen = new FormScreen(_data);
       _formScreen.y = 25;
       addChild(_formScreen);
       _resultScreen = new ResultScreen(_data);
       _resultScreen.y = 25;

       // Add buttons for toggling between the screens.
       _formButton = new BasicButton("Mad Lib Form");
       _formButton.addEventListener(MouseEvent.CLICK, onFormScreen);
       addChild(_formButton);
       _resultButton = new BasicButton("Story");
       _resultButton.addEventListener(MouseEvent.CLICK, onResultScreen);
       _resultButton.x = _formButton.width;
       addChild(_resultButton);
     }

    private function onResultScreen(event:Event):void {
       if (contains(_resultScreen)) return;
       removeChild(_formScreen);
       addChild(_resultScreen);
     }

    private function onFormScreen(event:Event):void {
       if (contains(_formScreen)) return;
       removeChild(_resultScreen);
       addChild(_formScreen);
     }

   }

}

When you test the application, you ought to be presented initially with the form screen with input controls for each of the substitutable elements from the story. After you’ve entered a value for each input control, click the story button to toggle to the ResultScreen view. Then you will see the story with the new words substituted for the original placeholders.

Because the application uses regular expressions to parse the text data, you can quite easily change the story and/or the substitutable elements. Regardless of how you edit the text in the madlibstory.txt file, the application will parse it and interpret any text in between the <> as substitutable text.

Summary

Regular expressions are a powerful way to find substrings that match a pattern. In this chapter, you’ve seen how to construct RegExp objects and use them to match substrings by using the RegExp and String methods that support regular expressions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.153.224