Validation, Formatting, and Regular Expressions: Chapter 14 - Flex 4 Cookbook
by Joshua Noble, Todd Anderson, Marco Casario, Garth BraithwaiteThis 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.
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.
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.
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>You want to create a custom formatter that will accept any appropriate string and return it with the correct formatting.
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 );
}
}
}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.
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>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.
Use a NumberValidator to check
the radio buttons, and a custom Validator to validate the combo box.
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>You want to create and display multiple validation error results
regardless of whether the user has the TextInput or another control in
focus.
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.
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>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.
You need a regular expression that will match the major credit card types: Visa, MasterCard, American Express, and Discover.
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.
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.
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.
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 (-).
You want to use regular expressions with explicit characters to match patterns in text—for example, words containing only vowels.
Use brackets ([ and ]) to hold all the characters that you would
like to match the pattern—for
example, [aeiou] to match all
vowels.
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.
You want to use regular expressions to match character types (integers, characters, spaces, or the negations of these) in your patterns.
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:
\dmatches a decimal digit. This is the equivalent of
[0-9].\Dmatches any character other than a digit. This is the equivalent of
[^0-9].\bmatches at the position between a word character and a nonword character. If the first or last character in the string is a word character,
\balso matches the start or end of the string.\Bmatches at the position between two word characters. Also matches the position between two nonword characters.
\fmatches a form-feed character.
\nmatches the newline character.
\rmatches the return character.
\smatches any whitespace character (a space, tab, newline, or return character).
\Smatches any character other than a whitespace character.
\tmatches the tab character.
\unnnnmatches the Unicode character with the character code specified by the hexadecimal number nnnn. For example,
\u263ais the smiley character.\vmatches a vertical-feed character.
\wmatches a word character (A–Z, a–z, 0–9, or _). Note that
\wdoes not match non-English characters, such as é, ñ, or ç.\Wmatches any character other than a word character.
\xnnmatches 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 thes(dotall) flag is set. For more information, see thes(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.
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 the grouping syntax—either the period (.) expression or the plus (+) expression—to match different groups
various numbers of times.
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 falseYou 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.
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+?/;
You want to match a pattern and then use that match to check the next potential match—for example, matching pairs of HTML tags.
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 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.
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.
