Page 1 of 1

Please!!! help with XML within PHP

Posted: Wed Mar 12, 2003 3:57 pm
by WearyWillie
I am a proficient programmer in PHP but I am not yet familiar with XML. I have signed up to use the API's from the U.S. Postal Service and they have given all necessary info to access them. They transfer using XML. I need help in setting up functions that I can send all necessary parameters to and get back the postal rate. Ideally I'd like to find someone that already has a msimilar functrion but I would settle for some help in creating my own.

Any help anyone can give will be GREATLY appreciated,

Thanks,

Buddy

Posted: Wed Mar 12, 2003 4:12 pm
by daven
Not designed for PostalService stuff, but SauSurePay (http://surepay.sauen.com) is a great class that creates and sends XML documents. If nothing else, it is a good place to begin. You'd have to change the functions around a bit to have it give the correct info, but it works quite well.

All thanks to Stoker for writing it.

Posted: Wed Mar 12, 2003 6:54 pm
by Stoker
SauSurepay is good for the Legacy surepay gateway :) And some of its functions could probably be used for xml stuff..

However, I have recently worked on some code for the USPS rating service as well, it also includes a a function for the UPS API (XML)..

Keep in mind that this is work in progress, containing lots of dirty-fixes and unclean code, variable assignements and such, as well as no error control other than a couple silly global vars.. Basically, this was my first line by line writeup to make it work, some day I'll rewrite it to more of a production state, more efficient and less short-letter confusing and silly assignements of various things.. but, bottom line is that it works as is :)

This forum doesnt support attachements so I just post it all here

Code: Select all

 
<?php
 
 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 
   SauShip Developer Code Piece
   Version 0.2.1    2003-03-03
 
   (C) Copyright 2002-2003 Sauen.Com and Stoker (Jon T Stokkeland)
   Sauen.Com, 302 East State Street, Salamanca, New York, 14779  sauship@sauen.com
   This Code is Open/Free to use with the PHP, BSD, MPL or GPL License at your liking.
 
   !WARNING!
   This is just a bunch of code that happens to be useful if you are doing 
   UPS/USPS API integrations, it is not a solution of any kind and can only be 
   implemented and tested with the UPS/USPS systems by developers who have
   developers access login/keys and have read and agreed to their terms.
   Be aware of that UPS and USPS have very strict licensing and software packaging
   rules, so you can not include this code in any product for sale or open source
   application, you can only implement it on your own as the end user, or a developer
   or consultant working on custom integrations directly for the end user which has
   read and understood the terms from UPS/USPS.
 
   This function-collection also includes code written by and Copyright by Hans Anderson 2003
   Two XML functions, useful stuff: http://www.hansanderson.com/php/xml/xmlize.inc.txt
   
   BTW: When doing USPS stuff, make sure you read the documentation very carefully,
   their test server is very limited (idiotic) and will only respond correctly on the predefined
   canned tests, change any value and you get an error, stupidity if you ask me, 
   or simply incompetent personel that created the system for USPS.. Also, their
   servers does NOT accept anything else than GET, they never heard of POST I guess..
   The production server is quite a bit more forgiving, allows more correct XML etc.
 
   .. Will create some docs some day .. 
   
  # Requires PHP with curl (and ssl for UPS) The USPS part can easily be rewritten
  # to not use curl.. The UPS part could use command line curl if you need to.
   
  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
 
  # All functions and global variables are prefixed with sauship_ (as a namespace).
  # I did not create this as a class, mainly because PHP is not very efficient at OO code.
  # If you would like a single-class version of this I can certainly create that for you :)
 
  # Use this to debug
  define('DEBUG',0);
 
  ### Public Functions
  
  
  #########################
  # UPS
 
  function sauship_ups_quote (
    $username,           # Yoyr My.UPS.Com username
    $password,           # Your My.UPS.Com password
    $xml_access_code,    # Obtain one from UPS
    $weight,             # Actual Weight in pounds (LB)
    $from_zip,
    $from_country,       # 2-Letter Country code
    $to_zip,
    $to_country,
    $package_type,       # UPS code (See their docs) '01' = UPS letter, '02' = package, '03' = UPS tube
    $pickup_type,    # UPS code (See their docs) '01' = daily pickup, '03' = customer counter
    $to_residential = 0,     # Commercial address if not specified
    $from_residential = 0,   # Commercial address if not specified
    $insured_value = 0,    # Insurance value (if any)
    $ship_service = ''   # Specify UPS service code or empty/0/false for all possible options (shop)
   )
  {
    
  $ups_xml_url = 'https://wwwcie.ups.com/ups.app/xml/Rate';   // Use for Testing
  #  $ups_xml_url = 'https://www.ups.com/ups.app/xml/Rate';    // Use for production
 
    $r_xpci_version = '1.0001';  # content: request/TransactionReference/XpciVersion
    $timeout = 30;
    
    $currencycode = 'USD';
 
    $requestoption = empty($ship_service) ? 'Shop' : 'Rate';
    $shipservice = empty($ship_service) ? '' : sauship_xmltag('Service',0,sauship_xmltag('Code',0,$ship_service,1));
    $accessrequest = @sauship_accessrequest ($username, $password, $xml_access_code);
    $to_residential = empty($to_residential) ? '' : sauship_xmltag('ResidentialAddress',0);
    $from_residential = empty($from_residential) ? '' : sauship_xmltag('ResidentialAddress',0);
    
    if (!empty($insured_value)) {
        $insured_value = sauship_xmltag('PackageServiceOption',0,
            sauship_xmltag('InsuredValue',0,
                sauship_xmltag('CurrencyCode',0,$currencycode)
               .sauship_xmltag('MonetaryValue',0,$$insured_value)
            )
        );
    } else {
       $insured_value = '';
    }
 
    // This kind of messy looking, probably would have been much cleaner and more efficient to
    // just spell it all out and fill in variables.. well, some day I may split this up for
    // use with other requests etc..
    
    $request =  '<?xml version="1.0"?>'."\n"
    . sauship_xmltag('RatingServiceSelectionRequest',array('xml:lang'=>'en-US'),
        sauship_xmltag('Request',0, 
          sauship_xmltag('TransactionReference',0,
             sauship_xmltag('CustomerContext',0,'get quote')
        .sauship_xmltag('XpciVersion',0,$r_xpci_version)
          )
          .sauship_xmltag('RequestAction',0,'Rate')
          .sauship_xmltag('RequestOption',0,$requestoption)
            )
        .sauship_xmltag('PickupType',0,
          sauship_xmltag('Code',0,$pickup_type,1)
        )
        .sauship_xmltag('Shipment',0,
          sauship_xmltag('Shipper',0,
            sauship_xmltag('Address',0,
               sauship_xmltag('PostalCode',0,$from_zip,1)
              .sauship_xmltag('CountryCode',0,$from_country,1)
              .$from_residential
            )
          )
          .sauship_xmltag('ShipTo',0,
            sauship_xmltag('Address',0,
               sauship_xmltag('PostalCode',0,$to_zip,1)
              .sauship_xmltag('CountryCode',0,$to_country,1)
              .$to_residential
            )
          )
          .$shipservice
          .sauship_xmltag('Package',0,
            sauship_xmltag('PackagingType',0,
              sauship_xmltag('Code',0,$package_type,1)
        )
            .sauship_xmltag('PackageWeight',0,
              sauship_xmltag('Weight',0,$weight,1)
            )
            .$insured_value
          )
        )
       );
 
    if (DEBUG) echo "<pre>DEBUG:\n\nRequest:\n".htmlspecialchars($accessrequest . "\n\n". $request);
 
    $curlsession = curl_init();
    curl_setopt ($curlsession, CURLOPT_URL, $ups_xml_url);
    curl_setopt ($curlsession, CURLOPT_POST, 1);
    curl_setopt ($curlsession, CURLOPT_POSTFIELDS, 
        ''.$accessrequest."\n". $request);
 
        /* This is really odd by UPS, first of all their XML uses uppercase fieldnames,
           which may not be wrong but is not recommended.
           Secondly, as seen right here the posted data is posted as-is without any 
           standard POST prefix, I would have expected the identification to be in one
           variable and the request in another, and url encoded, but nopes, nothing 
           like that at all.. amateurs or for some reason?  */
 
    curl_setopt ($curlsession, CURLOPT_TIMEOUT, $timeout);
    curl_setopt ($curlsession, CURLOPT_HEADER, 0);
    curl_setopt ($curlsession, CURLOPT_SSLVERSION, 3);
    curl_setopt ($curlsession, CURLOPT_RETURNTRANSFER, 1);
 
    $response = curl_exec ($curlsession); 
    $curlinfo = curl_getinfo($curlsession);
    if (!$response) {
      $curl_error = curl_error($curlsession);
      $curl_errno = curl_errno($curlsession);
      $error = 'Curl error: '.$curl_errno.' '.$curl_error."\nConnection details:\n";
      if (is_array($curlinfo)) {
         while ($row = each($curlinfo)) {
              $curl_info .= '  ' . $row[0] . ' => ' . $row[1] . "\n";
         }
      } else {
        $curl_info .= '  '.$curlinfo. ' ::: The request: '.serialize($request);
      }
      $GLOBALS['sauship_error'] = $curlinfo;  // Use this to debug
      return 0;
    } 
    curl_close($curlsession);
 
    if (DEBUG) echo "\n\nResponse:\n".htmlspecialchars($response)."\n\n-----\n\n";
 
    unset ($request);
    $xmld = sauship_xmlize($response);
    if (DEBUG) var_dump($xmld);
    if (!$xmld) {
      $GLOBALS['sauship_error'] = 'Xmlize failed, nothing returned. Response: '.serialize($response); // Use this to debug
      return 0;
    }
 
    // If there where no rates found you can use this var to debug it - comment out if you don't want it..
    $GLOBALS['sauship_response'] = $response;
    unset ($response);
    
    $ratex = $xmld['RatingServiceSelectionResponse']['#']['RatedShipment'];
    if (!is_array($ratex)) {
      if ((int) $xmld['RatingServiceSelectionResponse']['#']['Response'][0]['#']['Error'][0]['#']['ErrorCode'][0]['#'] === 111210 ) 
            $GLOBALS['sauship_wrongzip'] = 1;
      else $GLOBALS['sauship_norate'] = 1;  
      $GLOBALS['sauship_error'] = 'Rateportion missing: '.serialize($response)."\n\nXmlized: ".serialize($xmld); // Use this to debug
 
      // This could occur if your weight was way high and no rating was available, therefor I set this global var u can auto-debug with
      
      return 0;
    }
    unset($xmld);
  
    $rates = array();
    foreach ($ratex as $rate) {
      $rates[(string) $rate['#']['Service'][0]['#']['Code'][0]['#']] =
         $rate['#']['TotalCharges'][0]['#']['MonetaryValue'][0]['#'];
    }   
 
    
    if (DEBUG) { echo "\nParse result:\n"; print_r($rates); echo "\n--\n"; }
 
    return $rates;
  }
 
  
  
  
  #############################
  # USPS
 
  function sauship_usps_domestic_quote (
    $usps_server_url,     # The server post URL provided to you by USPS
    $username,             # Yoyr USPS user
    $password,              # Your USPS password/access code
    $weight,                 # Actual Weight in pounds (LB)
    $from_zip,
    $to_zip,
    $container='None',      #  0-109(n) | EP13(x) | EP14 | EP14F | None
    $psize='Regular',         #  Regular | Large | Oversize
    $machinable='True',     # Used for Parcel only
    $service=array('Priority','Express','Parcel')   # Array with many for multi-quote or single-string
   )
  {
      
    # Very little error control for now
    
    $timeout = 30;
    if (!is_array($service)) $svc = array($service);
    else $svc = &$service;
    
    $p = '';
    $i = 0;
    $pounds = (int) floor($weight);
    $ounces = (int) ceil(((float)$weight - (float) $pounds) * 16); // Round up to nearest ounce
    foreach ($svc as $s) {
        if ($s === 'Parcel') $m = sauship_xmltag('Machinable',0,$machinable,1)."\n";
        else $m = '';
        $p .= "\n".
          sauship_xmltag('Package',array('ID'=> $i++),
            sauship_xmltag('Service',0,$s,1)."\n"
            .sauship_xmltag('ZipOrigination',0,$to_zip,1)."\n"
            .sauship_xmltag('ZipDestination',0,$from_zip,1)."\n"
            .sauship_xmltag('Pounds',0,$pounds,0)."\n"
            .sauship_xmltag('Ounces',0,$ounces,0)."\n"
            .sauship_xmltag('Container',0,$container,1)."\n"
            .sauship_xmltag('Size',0,$psize,1)."\n"
            .$m
           ,0);          
    }
 
    
          $x ='<?xml version="1.0"?>'."\n".
               sauship_xmltag('RateRequest',array(
          'USERID'     => htmlspecialchars($username),
          'PASSWORD' => htmlspecialchars($password)
         ),
         $p,
         0
     );
    $request = $usps_server_url.'?API=Rate&XML='.urlencode($x);
     
    if (DEBUG) echo "<pre>DEBUG:\n\nRequest:\n".htmlspecialchars($x) ."\n\n".
                            htmlspecialchars($request)."\n\n";
   
    # Do some fopen() or file() here instead if you don't have curl
 
    $curlsession = curl_init();
    @curl_setopt ($curlsession, CURLOPT_URL, $request);
    curl_setopt ($curlsession, CURLOPT_TIMEOUT, $timeout);
    curl_setopt ($curlsession, CURLOPT_HEADER, 0);
    curl_setopt ($curlsession, CURLOPT_RETURNTRANSFER, 1);
    $response = @curl_exec ($curlsession); 
    $curlinfo = curl_getinfo($curlsession);
    if (!$response) {
      $curl_error = curl_error($curlsession);
      $curl_errno = curl_errno($curlsession);
      $error = 'Curl error: '.$curl_errno.' '.$curl_error."\nConnection details:\n";
      if (is_array($curlinfo)) {
         while ($row = each($curlinfo)) {
              $curl_info .= '  ' . $row[0] . ' => ' . $row[1] . "\n";
         }
      } else {
        $curl_info .= '  '.$curlinfo. ' ::: The request: '.serialize($request);
      }
      $GLOBALS['sauship_error'] = $curlinfo;  // Use this to debug
      return 0;
    } 
    curl_close($curlsession);
 
    if (DEBUG) echo "\n\nResponse:\n".htmlspecialchars($response)."\n\n-----\n\n";
 
    unset ($request);
    $xmld = sauship_xmlize($response);
    if (!$xmld) {
      $GLOBALS['sauship_error'] = 'Xmlize failed, nothing returned. Response: '.serialize($response); // Use this to debug
      return 0;
    }
    unset ($response);
    
    if (DEBUG) var_dump($xmld);
 
    // If there where no rates found you can use this var to debug it - comment out if you don't want it..
    $GLOBALS['sauship_response'] = $response;
 
    $ratex = $xmld['RateResponse']['#']['Package'];
 
    if (!is_array($ratex)) {
      $GLOBALS['sauship_error'] = 'Rateportion missing: '.serialize($response)."\n\nXmlized: ".serialize($xmld); // Use this to debug
 
      // This could occur if your weight was way high and no rating was available, therefor I set this global var u can auto-debug with
      $GLOBALS['sauship_norate'] = 1;
 
      return 0;
    }
    unset($xmld);
    
    foreach ($ratex as $cherr) if ($cherr['#']['Error']) {
        $err1 = (int) $cherr['#']['Error'][0]['#']['Number'][0]['#'];
        if ($err1 === -2147219498 ||
            $err1 === -2147219453 ) $GLOBALS['sauship_wrongzip'] = 1;
        else $GLOBALS['sauship_norate'] = 1;
         // This could happen if there is an error in just one part, the rest may be ok..
        $GLOBALS['sauship_error'] = 'Errorcode in response: '.serialize($ratex);
        return 0;
    }
  
    $rates = array();
    foreach ($ratex as $rate) {
      $rates[(string) $rate['#']['Service'][0]['#']] =  $rate['#']['Postage'][0]['#'];
    }
 
    if (DEBUG) { echo "\nParse result:\n"; print_r($rates); echo "\n--\n"; }
 
    return $rates;
  }
  
  
  
  
  
  
 
 
  ###############################################################################################
  ###
  ### Internal functions
  ###
 
 
     // This function was written by and is Copyright by Hans Anderson 2003 - http://www.hansanderson.com/php/xml/xmlize.inc.txt - PHP License
     function sauship_xmlize($data) {
        $data = trim($data);
        $vals = $index = $array = array();
        $parser = xml_parser_create();
        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
        xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
        xml_parse_into_struct($parser, $data, $vals, $index);
        xml_parser_free($parser);
        $i = 0; 
        $tagname = $vals[$i]['tag'];
        if ( isset ($vals[$i]['attributes'] ) ) {
            $array[$tagname]['@'] = $vals[$i]['attributes'];
        } else {
            $array[$tagname]['@'] = array();
        }
        $array[$tagname]["#"] = sauship_xml_depth($vals, $i);
        return $array;
     }
 
     // This function was written by and is Copyright by Hans Anderson 2003 - http://www.hansanderson.com/php/xml/xmlize.inc.txt - PHP License
     function sauship_xml_depth($vals, &$i) { 
         $children = array(); 
         if ( isset($vals[$i]['value']) ) {
             array_push($children, $vals[$i]['value']);
         }
         while (++$i < count($vals)) { 
             switch ($vals[$i]['type']) { 
 
                case 'open': 
                     if ( isset ( $vals[$i]['tag'] ) ) {
                         $tagname = $vals[$i]['tag'];
                     } else {
                         $tagname = '';
                     }
                     if ( isset ( $children[$tagname] ) ) {
                         $size = sizeof($children[$tagname]);
                     } else {
                         $size = 0;
                     }
                     if ( isset ( $vals[$i]['attributes'] ) ) {
                         $children[$tagname][$size]['@'] = $vals[$i]["attributes"];
                     }
                     $children[$tagname][$size]['#'] = sauship_xml_depth($vals, $i);
                 break; 
 
                 case 'cdata':
                     array_push($children, $vals[$i]['value']); 
                 break; 
 
                 case 'complete': 
                     $tagname = $vals[$i]['tag'];
 
                     if( isset ($children[$tagname]) ) {
                         $size = sizeof($children[$tagname]);
                     } else {
                         $size = 0;
                     }
                     if( isset ( $vals[$i]['value'] ) ) {
                         $children[$tagname][$size]["#"] = $vals[$i]['value'];
                     } else {
                         $children[$tagname][$size]["#"] = '';
                     }
                     if ( isset ($vals[$i]['attributes']) ) {
                         $children[$tagname][$size]['@'] = $vals[$i]['attributes'];
                     }          
                 break; 
     
                 case 'close':
                     return $children; 
                 break;
             } 
         } 
     return $children;
     }
 
 
 
     # Create the AccessRequest 'document' (UPS)
 
     function sauship_accessrequest ( $username, $password, $xml_access_code ) {
        return '<?xml version="1.0"?>'."\n"
          . @sauship_xmltag('AccessRequest',array('xml:lang'=>'en-US'),
               @sauship_xmltag('AccessLicenseNumber',0,$xml_access_code,1)
             . @sauship_xmltag('UserId',0,$username,1)
             . @sauship_xmltag('Password',0,$password,1)
         );
     }
 
 
     # Create XML tag
 
     function sauship_xmltag (
       $tagname,                 # string
       $params = array(),        # Associative array
       $data ='',                # string
       $convert_entities = 0     # konvert parameter values and data to legal entities
      )
     {
         $result = "<$tagname";
         if ($params) {
           while (list ($key, $val) = each ($params)) {
             if ($convert_entities) {
                $val = str_replace('&','&',$val);
                $val = str_replace('"','"',$val);
                $val = str_replace('''','&apos;',$val);
                $val = str_replace('>','>',$val);
                $val = str_replace('<','<',$val);
         }
             $result .= ' '.$key.'="'.addslashes($val).'"';
           }
         }       
         if (trim($data) !== '') { 
           if ($convert_entities) {
              $data = str_replace('&','&',$data);
              $data = str_replace('"','"',$data);
              $data = str_replace('''','&apos;',$data);
              $data = str_replace('>','>',$data);
              $data = str_replace('<','<',$data);
       }
           $result .= '>'.$data.'</'.$tagname.'>'; 
        }
        else $result .= " />"; 
        return $result;
     }
 
 
 
     function sauship_ups_service_code ($code = '') {
 
    static $c = array (
     '01' => 'Next Day Air',
     '02' => '2nd Day Air',
     '03' => 'Ground',
     '07' => 'Worldwide Express',
     '08' => 'Worldwide Expedited',
     '11' => 'Standard',
     '12' => '3-Day Select',
     '13' => 'Next Day Air Saver',
     '14' => 'Next Day Air Early AM',
     '54' => 'Worldwide Express Plus',
     '59' => '2nd Day Air AM',
     '65' => 'Express Saver'
    );
 
    if (empty($code)) return $c;
    $code = sprintf('%02d',((int) $code));
    if (empty($c[$code])) return;
    return $c[$code];
 
     }
     
 
     function sauship_usps_service_code ($code = '') {
 
    static $u = array (
     'Express' => 'Express mail',
     'First Class ' => 'First Class mail',
     'Priority' => 'Priority mail',
     'Parcel' => 'Parcel Post',
     'BPM' => 'BPM - Bound Printed Matter',
     'Library' => 'Library',
     'Media' => 'Media',
    );
 
    if (empty($code)) return $u;
    if (empty($u[$code])) return;
    return $u[$code];
    
     }
 
?>
 
 
some of this is actually from the SauSurepay class, akthough afterwards I think that for this simple purpose it would be more efficient (and cleaner) using str_replace in a predefined variable instead of all the tag-functions nested like that.. first time pre-prototype usually doesnt end up as good at it should be :)

PS! USPS test server is crap, some retards set up that system (A bill gates box), it only accepts the canned requests exactly as posted in the SDK/guides.. In my opinion, a test server should do the exact same services as a production server..

Posted: Thu Mar 13, 2003 5:05 pm
by WearyWillie
Thanks Jon,

I do not have curl support on my server so I am trying to use file(). My code does not work. I guess I still do not have enough knowledge of XML. Here is my code:

<?php

$ratelist = file ("Http://testing.shippingapis.com/Shippin ... ateRequest USERID="************" PASSWORD="************"><Package ID="0"><Service> EXPRESS</Service><ZipOrigination>20770</ZipOrigination><ZipDestination>20852</ZipDestination><Pounds>10</Pounds><Ounces>0</Ounces><Container>None</Container><Size>REGULAR</Size><Machinable></Machinable></Package></RateRequest>");
foreach ($ratelist as $rate) { print "$rate<br>\n"; }

?>

As you can see by its simplicity I was just trying to see if it would connect. I did use my actuaal username and password but masked here for security. I inadvertantly created a new topic when trying to reply here. I apologize to all concerned. Also I aplogize to Stoker for getting your name wrong in the other post, I miss read it.

Thanks again for your help so far. Any help on this would be greatly appreciated.

Thanks,

Buddy

Posted: Thu Mar 13, 2003 7:17 pm
by Stoker
You have to use urlencode on the xml data, file returns an array, if you have php > 4.3.0 you could use file_get_contents instead..

using file you could do something like

$request = $server . 'API=Rate&XML='.urlencode($x));
$response = join ('',file($request));