Hide quickref panel
Common
() enclose whole plan
| item separator
x any digit
x. repeat 0 or more times
[25-7] any of 2,5,6 or 7
 
 
 
Other
<03:613> 03 becomes 613
, dial tone
! number barring
S0 immediate dial
<:@gw0> via gateway 0
 
 
 
Example
(000S0<:@gw0>|
<#9:>xx.<­ :@gw0>|
<0:61>xxxxxxxxx|
<­ :613>xxxxxxxx|
<:03>[23567­ ]xxxxxxx<:@gw2>)
 
 
Dial Plans

Dial Plan Parser
Select a phone number to the left to see the number that would be dialed.
Explanation

Intro

The dialplan parser was inspired by a post on the Whirlpool forums. It is intended to allow you to check your dial plans and help you see why they are not working as you expected. The swanky effects you see are made possible through the use of AJAX thanks to the great work from the Rico team from Sabre Airline Solutions.

The dialplan system is barely alpha stage at present and any assistance in improving the code is welcome. No responsibility taken - use at your own risk, if it breaks you get to keep both pieces, don't blame me if your phone fails to dial 000 when you need it too etc. etc.

Usage

Paste your dial plan in the text box at the top then click on a phone number in the list box at the left. You may also enter a phone number in the text box (bottom left), when you are ready to check it press enter. Information is returned showing the dial plan used and the number to be dialled. The gateway selected is also shown as well as the number of hits or possible matches in your dial plan. On the right hand side is a display of the "raw" dial plan items as well as a "Search" version with which is the raw item with all the substitution and special action characters removed. This allows you to see what the dial plan is looking to match more easily. The dial plan item that matched is shown in bold, the one actually used has its item number in bold too.

Dial Plan Explanation

The following explanation was done by bogongmoth on the Whirlpool forums;
(If bogongmoth or anyone else has issues with me using this then please contact me and I will rewrite)

Dial plans can be a bit tricky to master at first, but offer great flexibility when you understand them. Here is an explanation of the sipura spa dial plan structure, and sample dial plans.

Common notations () opening and closing brackets - the entire dial plan string is enclosed in brackets. | each dial plan is separated from the next by the pipe |, but the first and last part of the dial plan only have an opening and closing bracket respectively.

The dial plan can include specific individual keys 0 to 9 (inclusive), * and # x any numeric digit from 0 to 9 . repetition eg x. means any numeric digit (x) repeated 0 or more times.

Other things: Substitution: replace 03 with 613 by writing <03:613>

Outside line: inset a comma , between digits for an outside line dial tone eg 9,03xxxxxxxx means dial 9 then you will get an outside dial tone until you press 0.

Barring numbers: put a ! at the end of a sequence to prevent it being dialed.

Immediate dial S0 - (capital S zero) immediately dial the numbers after you've matched the dial plan sequence. For example: 000S0

Gateways Spa 3k only: dial out through a gateway by adding <:@gw1> to the dial plan for gateway 1; or <:@gw0> for gateway 0 (PSTN) etc

Limited choice 1 If your dial plan contains [579]xxx then it will match with any four digit sequence starting with either 5, 7 or 9

Limited choice 2 If your dial plan contains [5-9]xxx then it will match with any four digit sequence starting with either 5, 6, 7, 8 or 9

Simple dial plan:
(000S0<:@gw0>|<#9:>xx.<­ :@gw0>|<0:61>xxxxxxxxx|<­ :613>xxxxxxxx|<:03>[23567­ ]xxxxxxx<:@gw2>)

Explanation: 000S0<:@gw0> This will dial 000 by the PSTN gateway (Spa3k only) immediately after you've pressed the third zero.

<#9:>xx.<:@gw0> You dial #9 then any phone number and it will dial the phone number through the PSTN gateway (over the POTS line) - Spa3k only

<0:61>xxxxxxxxx You dial, for example, 02 9678 5555 and the unit will convert this to 612 9678 5555 and dial out over Line 1 (voip)

<:613>xxxxxxxx You dial, for example, 9678 5555 and the unit will convert this to 613 9678 5555 and dial out over Line 1 (voip).

<:03>[23567]xxxxxxx<:@gw2>­ (Spa3k only) You dial, for example 5678 5678 and the unit will convert this to 03 5678 5678 and dial out through gateway 2 (which you will have set up with another provider). This sequence will only match when you dial a number that starts with either 2,3,5,6 or 7.

A more complex dial plan;

(000S0<:@gw0>|<#9:>xx.<­ :@gw0>|<0:61>[23][89­ ]xxxxxxx|<:613>[89]xxxxxxx|<­ 026:6126>[12]xxxxxxx|<073:6173>­ xxxxxxx|<08:618>[67]xxxxxxx|<­ 088:6188>[1-4]xxxxxx|<089:6189>­ [2-4]xxxxxx|<04:614>xxxxxxxx|<­ 0011:>xx.|<1800:611800>xx.|<­ 13:6113>xx.|89[89]060xx.|<:03>­ [23567]xxxxxxx<:@gw2>|0[2378­ ][2-7]xxxxxxx<:@gw2>|<#2:>­ xx.<:<@gw2>|<060:0661>­ ­ xxxxxxxxx|<06:06613>xxxxxxxx|<­ 090:0961>xxxxxxxxx|<09:09613>­ xxxxxxxx|<090011:>xx.|<#1­ 13:0516113>xx.<:@gw1>|<­ #10:05161>xx­ xxxxxxxS0<:@gw1|<#1:051613>­ ­ [5-9]xxxxxxS0<:@gw1)

Source
Here is the PHP code that parses the number you click on using the Dial Plan you are testing. If you have any suggestions or enhancments then send your code to voip27@supremeit.com:
<?php
/**
 * Dial plan parser
 *
 * @package    DIALPLAN
 * @author     Andrew Braund <voip@supremeit.com>
 * @copyright  2005 Andrew Braund
 * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
 * @version    0.1 alpha
 * @link       http://supremeit.com/voip/doDialPlan.php
 * @todo       sipura/PAP2 differences etc
 * @todo       number barring
 * @todo       S:4 (...) to set timeouts in PAP2
 * @todo       dial tone (,)
 */
class DIALPLAN {

     /**
     * Set the debug flag so we echo some extra info useful during development
     * If set to false, nothing will be echoed
     * @var false|true
     */
    var $debug = false;

    /**
     * Set the showPreg flag  show the preg string we use in the returned allSearch
     * If set to false, allSearch strings will only contain the search
     * @var false|true
     */
    var $showPreg = true;

     /**
     * A couple of parameters, maximum length that we allow the phone number to be dialed
     * and maximum length of a dial plan. 2047 limit is for Linksys PAP2
     * 
     * @var int
     */
    var $numberMaxLength = 12;
    var $dialplanMaxLength = 2047;

    /**
     * Set error string
     * If set to false, no error else error string
     * @var false|string
     * @access public
     */
    var $errorStr = true;

    /**
     * An array holding all dial plans in their raw format 
     *
     * key is just an numeric index
     *
     * row format is array('000S0','xxxxxx','<:61>03xxxxxxx')
     *
     * @var object
     * @access private
     */    
    var $_allRaw;

    /**
     * An array holding all dial plans in their search format 
     *
     * key is just an numeric index
     *
     * row format is array('000S0','xxxxxx','<:61>03xxxxxxx')
     *
     * @var object
     * @access private
     */    
    var $_allSearch;

    /**
     * An array holding the index to each dial plan that was "hit" during parsing
     *
     *
     * row format is array('3','4','6')
     *
     * @var array
     * @access private
     */    
    var $_hit= array();

    /**
     * An integer holding the index to each dial plan that is to be used
     *
     * @var int
     * @access private
     */    
    var $_used=-1;

    /**
     * An string holding the new number
     *
     * @var int
     * @access private
     */    
    var $_newNumber=-1;

    /**
     * Get the new phone number to dial
     *
     * @param none
     * @return string  the number
     * @access public
     */
   function getNewNumber(){
     return $this->_newNumber;
   }

    /**
     * Get a list of all hits
     *
     * @param none
     * @return array  the list
     * @access public
     */
   function getHits(){
     return $this->_hit;
   }

    /**
     * Get the dial plan that is to be used
     *
     * @param none
     * @return int 
     * @access public
     */
   function getUsed(){
     return $this->_used;
   }

    /**
     * Get a list of all dial plan items in raw format
     *
     * list format is array('000S0','xxxxxx','<:61>03xxxxxxx')
     *
     * @param none
     * @return array  the list
     * @access public
     */
   function getAllRaw(){
     return $this->_allRaw;
   }

    /**
     * Get a list of all dial plan items in search format
     * this allow you to see what the parser is looking for
     * more easily as all the substitutions and special tags have been removed
     *
     * list format is array('000','xxxxxx','03xxxxxxx');
     *
     * @param none
     * @return array  the list 
     * @access public
     */
   function getAllSearch(){
     return $this->_allSearch;
   }


function doDialPlan($dialPlan='(1|2|3|4|5)',$number='1800111222') {
  $rtnAry['gw'] = '';
  $this->_used=-1;
  $this->errorStr=false;

  // get rid of any whitespace
  //$dialPlan = preg_replace('/ |\n|\r|(\r\n)/m', '', $dialPlan);
  $dialPlan = preg_replace('/\s/', '', $dialPlan);
  // break out dial plan into individual items
  //$dialPlan = trim($dialPlan);
  $uncleanDialPlan = $dialPlan;
  $dialPlan = preg_replace('/[^0-9Sgwx*!,.#:<>|@\-\(\)\[\]]/', '', $dialPlan);
  $uncleanNumber = $number;
  
  if (strlen($number) > $this->numberMaxLength) {
    $this->errorStr = 'Phone number length was greater than '.$this->numberMaxLength.' characters';
  } else   if ($uncleanDialPlan != $dialPlan) {
    $this->errorStr = 'Dial plan may only contain the characters 0-9xgwS-*&lt;&gt;@#:.';
  } else if (strlen($dialPlan) > $this->dialplanMaxLength ) {
    $this->errorStr = 'Dial plan length was greater than '.$this->dialplanMaxLength.' characters';
  } else if (preg_replace('/[^0-9*#]/', '', $number) != $uncleanNumber) {
    $this->errorStr = 'Number may only contain the characters 0-9*#';
  } else if (!(substr($dialPlan,0,1) == '(' && substr($dialPlan,-1,1) == ')')) {
    $this->errorStr = 'Dial plan must be enclosed in ()<br />';
    $this->errorStr .= htmlentities(substr($dialPlan,0,10));
    $this->errorStr .= ' ... '.htmlentities(substr($dialPlan,-10,11));
  } else { // not a basic error

    $dialPlan = substr($dialPlan,1,strlen($dialPlan)-2); // nuke ()
    $dialPlanAry = explode('|',$dialPlan);
    if ($this->debug ) {
      echo 'dialPlan = '.$dialPlan.' number = '.$number."<br />\n";
      echo '<table border="0" cellspacing="2" cellpadding="0"><tr>'."\n";
      echo '<th>key</th><th>val</th>';
      echo '<th>searchStr</th><th>preg</th><th>number</th>';
      echo "<th>hit</th><th>replaceStr</th><th>removeStr</th><th>New #</th></tr>\n";
    }
    $rtnAry['gw'] = 'GW1'; // default gateway unless set below
    $gw='not set';
    $i = 0;
    foreach ($dialPlanAry as $key=>$val) {
      if ($this->debug) echo '<tr><td>'.$key.'</td><td>'.$val."</td>\n";
      $searchStr = $val;
      $searchStr = str_replace('*','\*',$searchStr);
      $searchStr = preg_replace('/<([^:]*):[^>]*>/', '${1}', $searchStr);
      // nuke any immediate dial flags (S0)
      $searchStr = preg_replace('/S0/', '', $searchStr);
      // convert repeat digit x. to a regex
      $pregStr = preg_replace('/(.)\./','[${1}]*',$searchStr);
      // convert dialplan to a regex
      $pregStr = preg_replace('/x/','.',$pregStr);
      if ($this->debug) echo '<td> '.$searchStr.'</td><td>'.$pregStr.'</td><td>'.$number.'</td>';
      // replacement
      $count = 0;
      $replaceStr = preg_replace('/^<([^:]*):([^>]*)>(.*)/', '${2}', $val);
      $removeStr = preg_replace('/^<([^:]*):([^>]*)>(.*)/', '${1}', $val);
      if($removeStr == $val) { // didn't actually replace anything
        $removeStr = '';
      }
      if($replaceStr == $val) { // didn't actually replace anything
        $newNumber = $number;
        $replaceStr = '';
      } else {
        $newNumber = preg_replace('/^'.$removeStr.'(.*)/',$replaceStr.'${1}',$number);
      }
      if (preg_match('/^'.$pregStr.'/',$number)) {
        if ($this->debug) echo '<td>'.$i."</td>\n";
        $this->_hit[] = $i; // we record a list of all the search strings we hit
        if ($this->_used == -1) { // for first one hit
          $this->_used = $i;
          $this->_newNumber = $newNumber; // update class var only for used string
          // get the gateway from the search string we have just hit
          $gw = preg_replace('/.*:@(gw.)>/', '${1}', $val);
          if ($gw != $val) {
            $rtnAry['gw'] = $gw;
          }
        }
        if ($this->debug) echo '<td>'.$replaceStr.'</td><td>'.$removeStr."\n";
        if ($this->debug) echo '<td>'.$newNumber."</td>\n";
      } else {
        if ($this->debug) echo '<td colspan="3">&nbsp;</td>'."\n ";
      }

      if ($this->showPreg) {
        $this->_allSearch[] = $searchStr.' '.$pregStr;// .' gw='.$gw;
      } else {
        $this->_allSearch[] = $searchStr;
      }
      $i++;
      if ($this->debug) echo "</tr>\n ";
    }
    $this->errorStr = false;
    //$rtnAry['allRaw'] = $dialPlanAry;
    $this->_allRaw = $dialPlanAry;
  }
  if ($this->debug) echo "</table><br />\n ";
  return $rtnAry;
  }

}
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
?>

some simple unit tests (please suggest some more - code would be great!);

<?php
include 'doDialPlan.php';
require_once 'PHPUnit.php';

class DialPlanTest extends PHPUnit_TestCase {
var $dp;

function DialPlanTest($name) {
  $this->PHPUnit_TestCase($name);
}

function setUp() {
  $this->dp = new DIALPLAN();
}

function testGetAllRawBasic() {
  $this->dp->doDialPlan('(<:61>xxxx| xxxxx| <2:613>2xxxx | 23233 |33)','23432342');
  $dpAry = $this->dp->getAllRaw();
  if(!is_array($dpAry)) {
    $errormsg = 'dpAry was  not an array! count';
  } else {
    $errormsg = 'entries in raw array';
  }
  $this->assertEquals(5, count($dpAry),$errormsg);
}

function testGetAllRaw() {
  $this->dp->doDialPlan('(<:61>xxxx| xxxxx| <2:613>2xxxx | 23233 |33)','23432342');
  $errormsg = 'values in raw array';
  $expected = array('<:61>xxxx', 'xxxxx','<2:613>2xxxx','23233','33');
  $this->assertEquals($expected, $this->dp->getAllRaw(), $errormsg);
}

function testGetAllSearch() {
  $this->dp->showPreg = false; // don't add the preg version for this test
  $this->dp->doDialPlan('(<:61>xxxx| xxxxx| <2:613>2xxxx | 23233 |33)','23432342');
  $errormsg = 'values in search array';
  $expected = array('xxxx','xxxxx','22xxxx','23233','33');
  $this->assertEquals($expected, $this->dp->getAllSearch(), $errormsg);
}

function testHits() {
  // $this->dp->debug = true;
  $this->dp->showPreg = false; // don't add the preg version for this test
  $this->dp->doDialPlan('(<:61>xxxx| xxxxx| <2:613>2xxxx |xx.| 239xx |33)','23999');
  $dpAry = $this->dp->getHits();
  if(!is_array($dpAry)) {
    $errormsg = 'dpAry was  not an array! count';
  } else {
    $errormsg = 'values in array';
  }
  $expected = array(0,1,3,4);
  if($expected != $dpAry) {
    foreach($dpAry as $key=>$val){
      echo $key.' '.$val."<br />\n";
    }
  }
  $this->assertEquals($expected, $dpAry, $errormsg);
}

function testUsed() {
  // $this->dp->debug = true;
  $this->dp->showPreg = false; // don't add the preg version for this test
  $this->dp->doDialPlan('(<:61>xxxx| xxxxx| <2:613>2xxxx | 239xx |33)','23999');
  // echo ' used = '.$this->dp->getUsed().'<br />';  
  $this->assertEquals(0, $this->dp->getUsed());
}

function testErrorStr() {
  // $this->dp->debug = true;
  $this->dp->showPreg = false; // don't add the preg version for this test
  $this->dp->doDialPlan('(<:61>xxxx| xxxxx| <2:613>2xxxx | 239xx |33)','23u9999');
  $errorStr = $this->dp->errorStr;
  // echo 'errorStr is '.$errorStr."<br />\n";
  if($errorStr === false) {
    $errorStr = 'non';
    $errormsg = 'thought number was good!';
  } else if($errorStr === true) {
    $errorStr = 'non';
    $errormsg = 'default value of true!!';
  } else {
    $errormsg = 'a string at least';
  }
  // echo ' used = '.$this->dp->getUsed().'<br />';  
  $this->assertRegExp('/.*contain.*/', $errorStr, $errormsg);
}

function testReplace() {
  $this->dp->debug = true;
  $dialPlan = '( *xx | 0 | <0011:> xxxx x. | <:618> [8]xxx xxxx ';
  $dialPlan .= '| <0:61> [2345679] xxxx xxxx | 61 x xxxx xxxx';
  $dialPlan .= '| <1800:611800> 1800xxx xxx | <130:61130> x xxx xxx ';
  $dialPlan .= '| <13:6113> xx xx | 1900 xxx xxx! )';
  $this->dp->doDialPlan($dialPlan,'8229876');
  $this->dp->doDialPlan('(<:61>xxxx| xxxxx|<2:613>2xxxx |33)','2222');
  $this->dp->doDialPlan('(<:61>xxxx| xxxxx|<2:613>2xxxx |33)','2222');
  $this->dp->doDialPlan('(<:61>xxxx| xxxxx|<2:613>2xxxx |33)','2229876');
  $this->dp->doDialPlan('(<8:61>xxxx| xxxxx|<2:613>2xxxx |33)','8229876');
  // echo ' used = '.$this->dp->getUsed().'<br />';  
  $this->assertEquals(0, $this->dp->getUsed());
}

}

$suite  = new PHPUnit_TestSuite('DialPlanTest');
$result = PHPUnit::run($suite);
print $result->toHTML();
?>