Validation, Formatting, and Regular Expressions: Chapter 14 - Flex 4 Cookbook

by Joshua Noble, Todd Anderson, Marco Casario, Garth Braithwaite
Flex 4 Cookbook

This excerpt is from Flex 4 Cookbook. This highly practical book contains hundreds of tested recipes for developing interactive Rich Internet Applications. You'll find everything from Flex basics to solutions for working with visual components and data access, as well as tips on application development, unit testing, and Adobe AIR. Each recipe features a discussion of how and why it works, and many of them offer sample code that you can put to use immediately.

buy button

Chapter 14. Validation, Formatting, and Regular Expressions

Validation, formatting, and regular expressions may seem a somewhat strange grouping at first glance, but they tend to be used for similar things in the everyday experience of developers: parsing the format of strings to detect a certain pattern, altering strings into a certain format if specific patterns are or are not encountered, and returning error messages to users if necessary properties are not encountered. That is, all three are useful for dealing with the sorts of data that we need from third parties or users that may not always be supplied in the format required by our applications—things like phone numbers, capitalized names, currencies, zip codes, and ISBN numbers. The Flex Framework provides two powerful tools to integrate this type of parsing and formatting with the UI elements of the Framework in the Validator and Formatter classes. Beneath both of these is the regular expression or RegExp object introduced in ActionScript 3. This is a venerable and powerful programming tool, used by nearly all, and loved and loathed in equal measure for its incredible power and difficult syntax.

The Validator is an event dispatcher object that checks a field within any Flex control to ensure that the value submitted falls within its set parameters. These parameters can indicate a certain format, whether a field is required, or the length of a field. Validation can be implemented simply by setting the source property of the Validator to the control where the user input will occur and indicating the property that the Validator should check. If a validation error occurs, the Validator will dispatch the error event to the control, and the control will display a custom error message that has been set in the Validator. There are many predefined validators in the Flex Framework (e.g., for credit cards, phone numbers, email, and social security numbers), but this chapter focuses primarily on building custom validators and integrating validators and validation events into controls.

The Formatter class has a simple but highly important job: accepting any value and altering it to fit a prescribed format. This can mean changing nine sequential digits into a properly formatted phone number such as (555) 555-5555, formatting a date correctly, or formatting zip codes for different countries. The Formatter class itself defines a single method of importance to us: format(). This is the method that takes the input and returns the proper string.

Both of these classes, at their roots, perform the type of string manipulation that can be done with a regular expression, though they do not tend to use regular expressions in their base classes. Regular expressions are certainly one of the most powerful, elegant, and difficult tools available in most modern programming languages. They let a programmer create complex sets of rules that will be executed on any chosen string. Almost all major programming languages have a built-in regular expression engine that, while varying somewhat in its features, maintains the same syntax, making the regular expression a useful tool to add to your repertoire.

The ActionScript implementation of the regular expression is the RegExp class, which defines two primary methods: the test() method, which returns a true or false value depending on whether the RegExp is matched anywhere in the string, and the exec() method, which returns an array of all matches along with the location in the string where the first match is encountered. A regular expression can also be tested by using the match(), search(), and replace() methods of the String class. Of these, I find that the methods in the String class tend to be most useful, because they allow manipulation of the characters using the regular expression. Regular expressions are a vast topic, and whole books are devoted to their proper use, so this chapter covers only some of their more specific aspects and provides solutions to common problems, rather than attempting to illustrate a general set of use cases.

Use Validators and Formatters with TextInput Controls

Problem

You need to validate and then format multiple TextInput and TextArea controls.

Solution

For each type of input—date, phone number, currency—use a Validator to ensure that the input is appropriate and then use a Formatter control to format the text of the TextInput appropriately.

Discussion

To use validators and formatters together in a component, simply create multiple validators for each of the needed types of validation. When the focusOut event occurs on a TextInput control, call the validate() method on the proper validator. To bind the validator to the correct TextInput, set the TextInput as the source of the validator and the text as the property of the TextInput that we want to validate:

<mx:NumberValidator id="numValidator" source="{inputCurrency}" property="text"/>

The formatter is called after the data has been validated. The base Formatter class accepts a formatting string consisting of hash marks that will be replaced by the digits or characters of the string that is being formatted. For a phone number, for example, the formatting string is as follows:

(###) ###-####

You can set up a phone number formatter as shown here:

<mx:PhoneFormatter id="phoneFormatter" formatString="(###) ###-####"
                   validPatternChars="#-() "/>

To use this formatter, call the format() method and pass the text property of the desired TextInput:

inputPhone.text = phoneFormatter.format(inputPhone.text);

A complete code listing implementing our validator and formatter follows. In practice, this probably would not be the best user experience, but note that in each of its example methods, if the result is not valid, the application clears the user-entered text and displays an error message:

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               xmlns:mx="library://ns.adobe.com/flex/halo"
               minWidth="1024" minHeight="768">
    <fx:Declarations>
        <mx:DateFormatter id="dateFormatter" formatString="day: DD, month: MM,
                          year: YYYY"/>
        <mx:DateValidator id="dateVal" inputFormat="mm/dd/yyyy"/>
        <mx:PhoneNumberValidator id="phoneValidator" property="text"
                                 source="{inputPhone}"/>
        <mx:PhoneFormatter id="phoneFormatter" formatString="(###) ###-####"
                           validPatternChars="#-() "/>
        <mx:CurrencyFormatter id="currencyFormatter" currencySymbol="£"
                              thousandsSeparatorFrom="." decimalSeparatorFrom=","/>
        <mx:NumberValidator id="numValidator" property="text"/>
    </fx:Declarations>

    <fx:Script>
        <![CDATA[

            import mx.events.ValidationResultEvent;
            private var vResult:ValidationResultEvent;
            // event handler to validate and format input
            private function dateFormat():void
            {
                vResult = dateVal.validate(inputDate.text);
                if (vResult.type==ValidationResultEvent.VALID) {
                    inputDate.text = dateFormatter.format(inputDate.text);
                } else {
                    inputDate.text= "";
                }
            }

            private function phoneFormat():void {
                vResult = phoneValidator.validate();
                if (vResult.type==ValidationResultEvent.VALID) {
                    inputPhone.text = phoneFormatter.format(inputPhone.text);
                } else {
                    inputPhone.text= "";
                }
            }

            private function currencyFormat():void {
                vResult = numValidator.validate(inputCurrency.text);
                if (vResult.type==ValidationResultEvent.VALID) {
                    inputCurrency.text =
                            currencyFormatter.format(inputCurrency.text);
                } else {
                    inputCurrency.text= "";
                }
            }

        ]]>
    </fx:Script>


    <s:VGroup>
        <s:HGroup>
            <s:Label text="Currency Input"/>
            <s:TextInput id="inputCurrency" focusOut="currencyFormat()"
                         width="300"/>
        </s:HGroup>

        <s:HGroup>
            <s:Label text="Phone Number Input"/>
            <s:TextInput id="inputPhone" focusOut="phoneFormat()" width="300"/>
        </s:HGroup>

        <s:HGroup>
            <s:Label text="Date Input"/>
            <s:TextInput id="inputDate" focusOut="dateFormat();" width="300"/>
        </s:HGroup>

    </s:VGroup>
</s:Application>

Create a Custom Formatter

Problem

You want to create a custom formatter that will accept any appropriate string and return it with the correct formatting.

Solution

Extend the Formatter class and override the format() method.

Discussion

In the format() method of the Formatter, you’ll create a SwitchSymbolFormatter instance and pass to its formatValue() method a string of hash marks representing the characters you want replaced with your original string. For example, if provided the format ###-### and the source 123456, the formatValue() method will return 123-456. You’ll then return this value from the format() method of your custom formatter.

The Formatter class uses a string of hash marks that will be replaced by all the characters in the string passed to the format() method. Replacing those characters is simply a matter of looping through the string and, character by character, building out the properly formatted string and then replacing the original:

package oreilly.cookbook {
    import mx.formatters.Formatter;
    import mx.formatters.SwitchSymbolFormatter;

    public class ISBNFormatter extends Formatter {

    public var formatString : String = "####-##-####";

        public function ISBNFormatter() {
           super();
        }

        override public function format(value:Object):String {
            // we need to check the length of the string
            // ISBN can be 10 or 13 characters
            if( ! (value.toString().length == 10 ||
                    value.toString().length == 13) ) {
                error="Invalid String Length";
                return ""
            }

            // count the number of hash marks passed into our format string
            var numCharCnt:int = 0;
            for( var i:int = 0; i<formatString.length; i++ ) {
                if( formatString.charAt(i) == "#" ) {
                    numCharCnt++;
                }
            }

            // if we don't have the right number of items in our format string
            // time to return an error
            if( ! (numCharCnt == 10 || numCharCnt == 13)  ) {
                error="Invalid Format String";
                return ""
            }

            // if the formatString and value are valid, format the number
            var dataFormatter:SwitchSymbolFormatter = new SwitchSymbolFormatter();
            return dataFormatter.formatValue( formatString, value );
        }

    }
}

Use Regular Expressions to Create an International Zip Code Validator

Problem

You need to validate all the South American postal code formats for countries that use them.

Solution

Create a series of regular expressions using groups to represent each country that has a postal code to be validated. Create a custom Validator class that can be passed a country value, and based on that value, apply the correct RegExp in the doValidation() method. If the value passed to the doValidation() method matches the RegExp, or the country selected doesn’t have a RegExp for its postal code, return true; otherwise, return false.

Discussion

Using regular expressions in custom validators lets you create far more versatile validation methods than would be possible without them. Without regular expressions, the Validator is restricted to a single string that it can validate. Using more than one regular expression in a validator enables you to create a class that can validate multiple string formats.

This code sample sets up a hash table of different countries’ postal codes. When the user selects a country and passes it into the validator, the correct regular expression is chosen from the hash:

        private var countryHash:Object = {"Argentina":/[a-zA-Z]\d{4}[a-zA-Z]{3}/,
                "Brazil":/\d{5}-\d{3}/, "Mexico":/\d{5}/, "Bolivia":/\d{4}/,
                "Chile":/\d{7}/, "Paraguay":/\d{4}/,"Uruguay":/\d{5}/};

The country property of the validator is used in the doValidation() method of the Validator class that the example overrides:

                // ensure that we have a country set
                if(countryHash[_country] != null) {
                    // read from our hash table and get the correct RegExp
                    var regEx:RegExp = countryHash[_country];
                    if(regEx.test(value as String)) {
                        return results;
                    } else {
                        // if the postal code doesn't validate, return an error
                        var err:ValidationResult = new ValidationResult(true, "",
                                "", "Please Enter A Correct Postal Code");
                        results.push(err);
                    }
               } else {
                   return results;
               }

The complete code listing for the validator is shown here:

package oreilly.cookbook {
    import mx.validators.ValidationResult;
    import mx.validators.Validator;

    public class SouthAmericanValidator extends Validator {
        // store all of our countries and their postal codes in a hash table
        private var countryHash:Object = {"Argentina":/[a-zA-Z]\d{4}[a-zA-Z]{3}/,
                "Brazil":/\d{5}-\d{3}/, "Mexico":/\d{5}/, "Bolivia":/\d{4}/,
                "Chile":/\d{7}/, "Paraguay":/\d{4}/, "Uruguay":/\d{5}/};

        private var results:Array;
        private var _country:String;

        public function SouthAmericanValidator() {
            super();
        }

        public function set country(str:String):void {
            _country = str;
            trace(_country);
        }

        // define the doValidation() method
        override protected function doValidation(value:Object):Array {
            // clear results Array
            results = [];

            // if we don't have a country set, we return an error
            if(_country == "") {
                var err:ValidationResult = new ValidationResult(true, "", "",
                        "Please Select a Country");
                results.push(err);
                return results;
            } else {
                // if it's a country that doesn't have a zip code, we return
                // w/o an error
                if(countryHash[_country] != null) {
                    // read from our hash table and get the correct RegExp
                    var regEx:RegExp = countryHash[_country];
                    if(regEx.test(value as String)) {
                        return results;
                    } else {
                        // if the postal code doesn't validate, return an error
                        var err:ValidationResult = new ValidationResult(true, "",
                                "", "Please Enter  A Correct Postal Code");
                        results.push(err);
                    }
                } else {
                    return results;
                }
            }
            return results;
        }

    }
}

To implement the custom validator, ensure that the country property is set before calling its doValidation() method. In this example, a ComboBox component is used to set the country property of the SouthAmericanValidator object:

<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               xmlns:mx="library://ns.adobe.com/flex/halo"
               xmlns:cookbook="oreilly.cookbook.*">
    <cookbook:SouthAmericanValidator property="text" source="{zip}" required="true"
                                     id="validator" invalid="showInvalid(event)"/>
    <fx:Script>
        <![CDATA[
            import mx.events.ValidationResultEvent;

            private function showInvalid(event:ValidationResultEvent):void {
                trace( " event " + event.message );
                zip.errorString = event.message;
            }

        ]]>
    </fx:Script>
    <s:HGroup>
        <mx:ComboBox dataProvider="{new ArrayList(['Argentina', 'Brazil', 'Mexico',
                                    'Bolivia', 'Ecuador', 'Colombia',
                                    'Chile','Paraguay','Uruguay'])}"
                     id="cb"
                     change="validator.country = cb.selectedItem as String"/>
        <s:Label text="Enter zip "/>
        <s:TextInput id="zip"/>
    </s:HGroup>
</s:Group>

Validate Combo Boxes and Groups of Radio Buttons

Problem

You need to validate groups of radio buttons and combo boxes to ensure that one of the radio buttons in the group is selected and that the combo box prompt is not selected.

Solution

Use a NumberValidator to check the radio buttons, and a custom Validator to validate the combo box.

Discussion

To return a ValidationResultEvent for a group of radio buttons, use a NumberValidator to check that the selectedIndex of the RadioButtonGroup is not −1, which would indicate that no radio button is selected. To validate a combo box, create a custom validator and check that the value of the ComboBox’s selectedItem property is not null and is not either the custom prompt that was supplied or an invalid value.

The code for the custom ComboBox validator is quite straightforward and is commented and shown here:

package oreilly.cookbook.flex4 {
    import mx.validators.ValidationResult;
    import mx.validators.Validator;

    public class ComboValidator extends Validator {
        // this is the error message that is returned if an item in the
        // ComboBox is not selected
        public var error:String;
        // if the developer sets a manual prompt, but pushes something into the
        // array of the ComboBox (I've seen it many times for different reasons)
        // we want to check that against what the selected item in the CB is
        public var prompt:String;

        public function ComboValidator() {
            super();
        }
        // here we check for either a null value or the possibility that
        // the developer has added a custom prompt to the ComboBox, in which
        // case we want to return an error
        override protected function doValidation(value:Object):Array {
            var results:Array = [];
            if(value as String == prompt || value == null) {
                var res:ValidationResult = new ValidationResult(true, "", "",
                                                                error);
                results.push(res);
            }
            return results;
        }
    }
}

One strategy for performing multiple validations is to use an array: you add to the array all of the component’s validators that need to be called, and then use the public static Validator.validateAll() method to validate all the validators in the array. This technique is particularly valuable when multiple fields need to be validated at the same time. If any of the validators return errors, all those errors are joined together and displayed in an Alert control. The following example demonstrates performing multiple validations, including validation of a radio button selection:

<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
         xmlns:s="library://ns.adobe.com/flex/spark"
         xmlns:mx="library://ns.adobe.com/flex/halo"
         width="600" height="400" xmlns:cookbook="oreilly.cookbook.flex4.*"
         creationComplete="init()">

    <fx:Declarations>
        <mx:StringValidator id="rbgValidator" source="{rbg}"
                            property="selectedValue"
                            error="Please Select a Radio Button"/>
        <mx:NumberValidator id="toggleValidator" source="{toggleButton}"
                            property="selected Index" allowNegative="false" />
        <cookbook:ComboValidator id="comboValidator" error="Please Select A State"
                                 prompt="{stateCB.prompt}" source="{stateCB}"
                                 property="selectedItem"/>

        <mx:RadioButtonGroup id="rbg"/>
    </fx:Declarations>
    <fx:Script>
        <![CDATA[

            import mx.events.ValidationResultEvent;
            import mx.validators.Validator;
            import mx.controls.Alert;

            [Bindable]
            private var validatorArr:Array;
            // make an array of all the validators that we'll check with one
            // method later
            private function init():void {
                validatorArr = new Array();
                // push all the validators into the same array
                validatorArr.push(rbgValidator);
                validatorArr.push(toggleValidator);
                validatorArr.push(comboValidator);
            }
            // validate all the items in the validator array and show an alert
            // if there are any errors
            private function validateForm():void {
                // the validateAll method will validate all the validators
                // in an array
                // passed to the validateAll method
                var validatorErrorArray:Array =
                        Validator.validateAll(validatorArr);
                var isValidForm:Boolean = validatorErrorArray.length == 0;
                if (!isValidForm) {
                    var err:ValidationResultEvent;
                    var errorMessageArray:Array = [];
                    for each (err in validatorErrorArray) {
                        errorMessageArray.push(err.message);
                    }
                    Alert.show(errorMessageArray.join("\n"), "Invalid form...",
                               Alert.OK);
                }
            }

        ]]>
    </fx:Script>
    <s:VGroup id="form">
        <mx:ComboBox id="stateCB" dataProvider="{someDataProvider}"
                     prompt="Select A State"/>
        <s:HGroup>
            <s:RadioButton group="{rbg}" label="first" id="first"/>
            <s:RadioButton group="{rbg}" id="second" label="second"/>
            <s:RadioButton id="third" label="third" group="{rbg}"/>
        </s:HGroup>
        <s:ButtonBar id="toggleButton">
            <fx:dataProvider>
                <fx:ArrayList>
                    <fx:Number>1</fx:Number>
                    <fx:Number>2</fx:Number>
                    <fx:Number>3</fx:Number>
                </fx:ArrayList>
            </fx:dataProvider>
        </s:ButtonBar>
    </s:VGroup>
    <s:Button label="validate" click="validateForm()"/>
</s:Group>

Show Validation Errors by Using ToolTips in a Form

Problem

You want to create and display multiple validation error results regardless of whether the user has the TextInput or another control in focus.

Solution

Use the ToolTipManager to create a new ToolTip class and position it over the control where the error occurred. Create a Style object and assign it to the ToolTip to give it a red background and an appropriate font color.

Discussion

The error tip that displays when a validator returns an error is simply a ToolTip component. You can use a style to represent all the necessary visual information for the ToolTip: backgroundColor, fontColor, fontType, and so forth. Use the setStyle() method of the ToolTip to apply this style to the new tooltips created for each validation error. For example:

errorTip.setStyle("styleName", "errorToolTip");

To display multiple tooltips, position them by using the stage positions of the relevant controls. For example:

var pt:Point = this.stage.getBounds(err.currentTarget.source);
var yPos:Number = pt.y * −1;
var xPos:Number = pt.x * −1;
// now create the error tip
var errorTip:ToolTip = ToolTipManager.createToolTip(err.message,
        xPos + err.currentTarget.source.width, yPos) as ToolTip;

When the form validates, all the tooltips are removed using the ToolTipManager destroyToolTip() method, which loops through each ToolTip added:

<s:VGroup xmlns:fx="http://ns.adobe.com/mxml/2009"
          xmlns:s="library://ns.adobe.com/flex/spark"
          xmlns:mx="library://ns.adobe.com/flex/halo"
          width="500" height="400"
          xmlns:cookbook="oreilly.cookbook.flex4.*"
          creationComplete="init();">
    <fx:Style>
        /* here's the CSS class that we'll use to give our tooltip the appearance
        of an error message */
        .errorToolTip {
            color: #FFFFFF;
            fontSize: 9;
            fontWeight: "bold";
            shadowColor: #000000;
            borderColor: #CE2929;
            borderStyle: "errorTipRight";
            paddingBottom: 4;
            paddingLeft: 4;
            paddingRight: 4;
            paddingTop: 4;
        }

    </fx:Style>
    <fx:Script>
        <![CDATA[
            import mx.controls.ToolTip;
            import mx.managers.ToolTipManager;
            import mx.events.ValidationResultEvent;
            import mx.validators.Validator;
            import mx.controls.Alert;

            [Bindable]
            private var validatorArr:Array;

            private var allErrorTips:Array;

            private function init():void {
                validatorArr = new Array();
                validatorArr.push(comboValidator1);
                validatorArr.push(comboValidator2);
            }

Here’s where the actual validation occurs:

            private function validateForm():void {
                // if we have error tips already, we want to remove them
                if(!allErrorTips) {
                    allErrorTips = new Array();
                } else {
                    for(var i:int = 0; i<allErrorTips.length; i++) {
                        // remove the tooltip
                        ToolTipManager.destroyToolTip(allErrorTips[i]);
                    }
                    // empty our array
                    allErrorTips.length = 0;
                }
                var validatorErrorArray:Array =
                        Validator.validateAll(validatorArr);

If nothing has been pushed into the validatorErrorArray, you know that no validation errors have been thrown. Otherwise, you’ll want to go about creating the error tips and placing them:

                var isValidForm:Boolean = validatorErrorArray.length == 0;
                if (!isValidForm) {
                    var err:ValidationResultEvent;
                    for each (err in validatorErrorArray) {
                        // Use the target's x and y positions to set position
                        // of error tip. We want their actual stage positions
                        // in case there's some layout management going on, so
                        // we use the getBounds() method.

Because the ErrorEvent’s target property is the control or component that threw the event, use that property to place the error tip:

                        var pt:Rectangle =
                                stage.getBounds(err.currentTarget.source);
                        var yPos:Number = pt.y * −1;
                        var xPos:Number = pt.x * −1;
                        // now create the error tip
                        var errorTip:ToolTip =
                                ToolTipManager.createToolTip(err.message,
                                xPos + err.currentTarget.source.width, yPos)
                                as ToolTip;
                        // apply the errorTip class selector
                        errorTip.setStyle("styleName", "errorToolTip");
                        // store the error tip so we can remove it later when
                        // the user revalidates
                        allErrorTips.push(errorTip);
                    }
                }
            }
        ]]>
    </fx:Script>
    <!-- our two validators -->
    <cookbook:ComboValidator id="comboValidator1"
                             error="Please Select A State"
                             prompt="{stateCB1.prompt}" source="{stateCB1}"
                             property="selectedItem"/>
    <cookbook:ComboValidator id="comboValidator2"
                             error="Please Select A State"
                             prompt="{stateCB2.prompt}" source="{stateCB2}"
                             property="selectedItem"/>
    <s:VGroup id="form">
        <s:ComboBox id="stateCB1" dataProvider="{someDataProvider}"
                    prompt="Select A State"/>
        <s:ComboBox id="stateCB2" dataProvider="{someDataProvider}"
                    prompt="Select A State"/>
    </s:VGroup >
    <s:Button label="validate" click="validateForm()"/>
</s:VGroup>

Use Regular Expressions for Locating Email Addresses

Problem

You need to identify any email addresses entered or encountered in text.

Solution

Create a regular expression to match the name@host.com email address format and use the global flag (g) to indicate that the expression can match multiple times.

Discussion

The necessary regular expression looks like this:

var reg:RegExp = /\w+?@\w+?\.\w{3}/g;

To match all the email addresses in a large block of text, use the String match() method, which returns an array of all matches. The match() method accepts either a string or a regular expression to search for.

Use Regular Expressions for Matching Credit Card Numbers

Problem

You need a regular expression that will match the major credit card types: Visa, MasterCard, American Express, and Discover.

Solution

Create a regular expression that uses the initial digits of each major credit card type and match the expected number of digits for each type.

Discussion

The numbers of each major credit card type start with the same identifying digits, and you can use this fact to create a single regular expression for determining whether a card number is valid. All MasterCard card numbers start with 5, all Visa card numbers start with 4, all American Express card numbers start with 30, and all Discover card numbers start with 6011. The expression you need is as follows:

(5[1-5]\d{14})|(4\d{12}(\d{3})?)|(3[47]\d{13})|(6011\d{14})

For MasterCard, the expression (5[1–5]\d{14}) matches only valid numbers without any spaces in them. It’s generally a good idea to clear the spaces out of any credit card numbers before sending them on to a processing service. The next segments of the expression match Visa cards, then American Express cards, and finally Discover cards. The alternation flag (|) in between the regular expression’s four parts indicates that you can match any one of the four valid card patterns to return a match.

Use Regular Expressions for Validating ISBNs

Problem

You want to create a regular expression to validate International Standard Book Numbers (ISBNs).

Solution

Create a pattern that allows for the use of hyphens, the possibility that the ISBN number is 10 or 13 digits long, and that the number may or may not end with a X.

Discussion

The regular expression shown here uses the caret (^) and dollar sign ($) markers to indicate that the pattern must be the only item in the string. These symbols could be removed to match all ISBN numbers within a block of text as well:

private var isbnReg:RegExp = /^(?=.{13}$)\d{1,5}([- ])\d{1,7}\1\d{1,6}\1(\d|X)$/;
private function testISBN():void {
    var s:String ="ISBN 1-56389-016-X";
    trace(s.match(isbnReg));
}

The caret indicates that the pattern must occur at the beginning of a line, and the dollar sign indicates that whatever directly precedes it must be the end of the line. Between these two symbols, you have groups of integers, optionally separated by hyphens (-).

Create Regular Expressions by Using Explicit Character Classes

Problem

You want to use regular expressions with explicit characters to match patterns in text—for example, words containing only vowels.

Solution

Use brackets ([ and ]) to hold all the characters that you would like to match the pattern—for example, [aeiou] to match all vowels.

Discussion

To match patterns in a block of text, you can use multiple character flags in a regular expression to signal the various character classes that you may wish to match. Here are a few common flags:

[ ] (square brackets)

Defines a character class, which defines possible matches for a single character; for example, /[aeiou]/ matches any one of the specified characters.

- (hyphen)

Within character classes, use the hyphen to designate a range of characters; for example, /[A-Z0-9]/ matches uppercase A through Z or 0 through 9.

/ (backslash)

Within character classes, insert a backslash to escape the ] and characters; for example, /[+\−]\d+/ matches either + or before one or more digits. Within character classes, other characters, which are normally metacharacters, are treated as normal characters (not metacharacters), without the need for a backslash: /[$£]/ matches either $ or £. For more information, see the Flex documentation on character classes.

| (pipe)

Used for alternation, to match either the part on the left side or the part on the right side; for example, /abc|xyz/ matches either abc or xyz.

To match only odd numbers, you would write this:

var reg:RegExp = /[13579]/;

To match only vowels, you would use this:

var vowels:RegExp = /[aeiou]/;

To not match vowels, you would use this:

var notVowels:RegExp = /[^aeiou]/;

Note that the caret in the preceding example means not only within the square brackets. Outside the square brackets, the caret indicates that the string must occur at the beginning of a line.

Use Character Types in Regular Expressions

Problem

You want to use regular expressions to match character types (integers, characters, spaces, or the negations of these) in your patterns.

Solution

Use the character type flags.

Discussion

Using the character class is by far the easiest and most efficient way to match characters when creating patterns to test against. To perform those tests, use character type flags. These consist of a backslash to tell the regular expression engine that the following characters are a character type (as opposed to a character to be matched), followed by the desired character class specification. Many of these character types also have negations. Here are some examples:

\d

matches a decimal digit. This is the equivalent of [0-9].

\D

matches any character other than a digit. This is the equivalent of [^0-9].

\b

matches at the position between a word character and a nonword character. If the first or last character in the string is a word character, \b also matches the start or end of the string.

\B

matches at the position between two word characters. Also matches the position between two nonword characters.

\f

matches a form-feed character.

\n

matches the newline character.

\r

matches the return character.

\s

matches any whitespace character (a space, tab, newline, or return character).

\S

matches any character other than a whitespace character.

\t

matches the tab character.

\unnnn

matches the Unicode character with the character code specified by the hexadecimal number nnnn. For example, \u263a is the smiley character.

\v

matches a vertical-feed character.

\w

matches a word character (A–Z, a–z, 0–9, or _). Note that \w does not match non-English characters, such as é, ñ, or ç.

\W

matches any character other than a word character.

\xnn

matches the character with the specified ASCII value, as defined by the hexadecimal number nn.

\ (backslash)

escapes the special metacharacter meaning of special characters.

. (dot)

matches any single character. A dot matches a newline character (\n) only if the s (dotall) flag is set. For more information, see the s (dotall) flag in the Flex documentation.

A few quick examples show the usage of these metacharacters. To match a 1 followed by two letters, use the following:

/1\w\w/;

To match a 1 followed by two nonletters, use this:

/1\W\W/;

To match five consecutive numbers, you could use this:

/\d\d\d\d\d/;

although a far easier way to do this is shown here:

/\d{5}/;

To match two numbers separated by a space:

/\d\b\d/;

To match three numbers separated by any character:

/\d.\d.\d/;

The metacharacters allow you to create expressions that will match certain patterns of any integer, alphabetic character, or blank space, as well as matching the negation of all of these. This lets you create much more powerful and terse regular expressions.

Match Valid IP Addresses by Using Subexpressions

Problem

You want to find multiple valid IP addresses in a block of text.

Solution

Use subexpressions to create valid matches for each three-digit number in the IP address.

Discussion

Using what you learned in the section called “Use Character Types in Regular Expressions”, you can match between one and three numbers of an IP address by using the \d flag:

\d{1,3}

If you want to match three sets of one and four numbers, use this:

(\d{1,4}){3}

Just as \d{3} matches 333, when you create a subexpression, you can match that subexpression. The subexpression is a distinct element that can be treated like any other pattern. So for the IP address, you want to match four sets of three numbers separated by periods. Think about this as three sets of three numbers with periods and then one set of three numbers. Doing so leads to a far more efficient expression, such as the following:

(\d{1,3}\.){3}\d{1,3}

This approach will bring you closer, but it won’t work completely because it also matches a string like 838.381.28.999, which is not a valid IP address. What you need is something that takes into account that the maximum value for one of the three-digit numbers in the IP address is 255. Using subexpressions, you can create just such a regular expression:

(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))\.){3}((\d{1,2})|(1\d{2})|
(2[0-4]\d)|(25[0-5]))

Take a closer look at this section:

(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))\.){3}

Translating this into English, you see two digits that are either 1 or 2, (\d{1,2}), or a 1 followed by two other numbers (1\d{2}), or a 2 followed by two of anything between 0 and 4 (2[0-4]\d), or 2 and 5 followed by anything between 0 and 5 (25[0-5]). Any one of these is then followed by a period.

Finally, you wind up with something like this:

((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))

This is exactly the same as the previous pattern, with one exception: the exclusion of the period at the end. An IP address (for example, 192.168.0.1) doesn’t contain a final period.

The subexpression syntax functions as follows:

  • {n} indicates at least n times.

  • {n,m} indicates at least n but no more than m times.

Use Regular Expressions for Different Types of Matches

Problem

You want to match a pattern described with a regular expression a certain number of times.

Solution

Use the grouping syntax—either the period (.) expression or the plus (+) expression—to match different groups various numbers of times.

Discussion

As you saw in the section called “Match Valid IP Addresses by Using Subexpressions”, the braces syntax allows you to indicate the number of times that a subexpression should be matched and whether the results should be returned. Suppose, for example, you want to match all characters between 0 and 4 in two strings:

var firstString:String = "12430";
var secondString:String = "603323";

Consider all the types of matches that you could execute on these two strings. The modifiers you could use are as follows:

  • ?? matches zero or one time only.

  • *? matches zero or more times.

  • +? matches one or more times.

Remember that matching and returning matches are quite different. If you want to find out whether the two example strings contain only characters between 0 and 4, for example, you can use the RegExp test() method, which returns a Boolean true or false value. If you want to include all characters in the String that match until a nonmatch is found, use the String match() method. If you want to include all characters in the String that match regardless of any nonmatches, use the global flag on the regular expression (/[0-4]+g/) together with the String match() method.

For example, /[abc]+/ matches abbbca or abba and returns abc from abcss.

\w+@\w+\.\w+ matches anything resembling an email address. Note that the period is escaped, meaning it is simply a period and not read as part of the regular expression’s syntax. The use of the + symbol indicates that any number of characters can be found; these characters must be followed by the at sign (@), which in turn can be followed by any number of additional characters.

This code snippet demonstrates various quantifiers and comments their results:

var atLeastOne:RegExp = /[0-4]+/g;
var zeroOrOne:RegExp = /[0-4]*/g;
var atLeastOne2:RegExp = /[0-4]+?/g;
var zeroOrOne2:RegExp = /[0-4]*?/g;
var firstString:String = "12430";
var secondString:String = "663323";

firstString.match(atLeastOne));   // returns "1243"
secondString.match(atLeastOne));  // returns "3323" because we want as many
                                  // characters as will match
firstString.match(zeroOrOne));    // returns "1243" because the first few
                                  // characters match
secondString.match(zeroOrOne));   // returns "" because the first few characters
                                  // don't match, so we stop looking
firstString.match(atLeastOne2));  // returns "1,2,4,3" because all we need is
                                  // one match
secondString.match(atLeastOne2)); // returns "3,3,2,3"
firstString.match(zeroOrOne2));   // returns ""
secondString.match(zeroOrOne2));  // returns ""
zeroOrOne2.test(firstString));    // returns true
zeroOrOne2.test(secondString));   // returns false

Match Ends or Beginnings of Lines with Regular Expressions

Problem

You want to match patterns that occur only at the beginning or the end of a string, or patterns that exist on the line of a string with either nothing in front of or nothing behind them.

Solution

Use the caret (^) and dollar sign ($) markers in your regular expression.

Discussion

When matching patterns on discrete lines or at a line’s start or end, place the caret marker at the beginning of your regular expressions to indicate that your pattern must occur at the beginning of a line, and place the dollar sign at the end of your pattern to indicate that the end of the line must follow your pattern.

For example, to match .jpg or .jpeg with any length of a filename, but only where the name is encountered on a line with nothing else around it, use the following:

/^.+?\.jpe?g$/i

To match only words that occur at the end of a line in a text field, use this:

/\w+?$/;

And to match words that occur only at the beginning of a line, use this:

/^\w+?/;

Use Back-References

Problem

You want to match a pattern and then use that match to check the next potential match—for example, matching pairs of HTML tags.

Solution

Use back-references in your regular expression to check each match against the most recent match.

Discussion

The Flash Player regular expression engine can store up to 99 back-references (i.e., a list of the 99 previous matches). The flag \1 always indicates the most recent match, \2 indicates the second most recent match, and so on. Likewise, in the String replace() method, which uses the matches from another regular expression, the most recent match is indicated by $1.

To ensure that pairs of HTML tags match (for example, that <h2> is followed by </h2>), this example uses the back-reference \1 to indicate the most recent match:

private var headerBackreference:RegExp = /<H([1-6])>.*?<\/H\1>/g;
private function init():void {
    var s:String = "<BODY> <H2>Valid Chocolate</H2> <H2>Valid Vanilla</H2>
            <H2>This is not valid HTML</H3></BODY>";
    var a:Array = s.match(headerBackreference);
    if(a != null) {
        for(var i:int = 0; i<a.length; i++) {
            trace(a[i]);
        }
    }
}

You could also use back-references to wrap all valid URLs with an <a> tag to make hyperlinks. The way of indicating the back-reference here is slightly different. Here’s the code:

private var domainName:RegExp = /(ftp|http|https|file):\/\/[\S]+(\b|$)/gim;
private function matchDomain():void {
    var s:String = "Hello my domain is http://www.bar.com, but I also like
            http://foo.net as well as www.baz.org";
    var replacedString = (s.replace(domainName, '<a href="$&">$&</a>').
            replace(/([^\/])(www[\S]+(\b|$))/gim,'$1<a href="http://$2">$2</a>'));
}

The first match is made by matching a valid URL:

/(ftp|http|https|file):\/\/[\S]+(\b|$)/gim;

Next, all the valid URLs are wrapped in <a> tags:

s.replace(domainName, '<a href="$&">$&</a>')

At this point you will have:

Hello my domain is <a href="http://www.bar.com">http://www.bar.com</a>,
but I also like <a href="http://foo.net">http://foo.net</a> as well as www.baz.org

which is not quite right. Because the original RegExp looked for strings starting with ftp, http, https, or file, www.baz.org isn’t matched. The second replace() statement looks through the string for any instance of www that is not prefaced by a /, which would have already matched:

replace(/([^\/])(www[\S]+(\b|$))/gim,'$1<a href="http://$2">$2</a>'))

The $1 and $2 here indicate the first and second matches within the pattern, the second being the actual URL name that you want.

Use a Look-Ahead or Look-Behind

Problem

You want to match patterns that are not preceded or followed by certain other characters.

Solution

Use a negative look-ahead, (?!) or negative look-behind, (?<!), to indicate any characters that cannot be in front of or behind your match. Use a positive look-ahead (?=) or positive look-behind (?<=) to indicate characters that should be located in front of or behind your match.

Discussion

A positive look-behind indicates a pattern whose presence in front of an expression will be used in determining a match, but that you do not want included in the match itself. For example, to match all numbers that follow a dollar sign, but not the dollar sign itself, in the following string:

400 boxes at $100 per unit and 300 boxes at $50 per unit.

you could use a regular expression with a positive look-behind:

/(?<=\$)\d+/

If you want to match all characters that do not have a dollar sign in front of them, you can instead use a negative look-behind:

/\b(?<!\$)\d+\b/

Note that the negative look-behind replaces the = of the positive look-behind with a ! symbol to indicate that in order for the string to match, the dollar sign must not be present.

To match only the prices from a string, you can use a positive look-ahead:

private var lookBehindPrice:RegExp = /(?<=[\$|₠])[0-9.]+/g;

private function matchPrice():void {
    var s:String = "dfsf24ds: ₠23.45 ds2e4d: $5.31 CFMX1: $899.00 d3923: ₠69";
    trace(s.match(this.lookBehindPrice));
}

Similarly, to match variable declarations in a string you can use positive look-aheads as shown here:

private var lookBehindVariables:RegExp = /(?<=var )[0-9_a-zA-Z]+/g;
private function matchVars():void {
    var s:String = " private var lookAheadVariables:RegExp = /blah/
            private var str:String = 'foo'";
    trace(s.match(lookBehindVariables));
}

If you want, for example, to match all strings with the value pic that do not have .jpg behind them, you can instead use a negative look-ahead:

var reg:RegExp = /pic(?!\.jpg)/;

If you enjoyed this excerpt, buy a copy of Flex 4 Cookbook.