Ok, some background. I'm building a web-based media database as a little project, and it's coming along nicely. I use PHP as the backend, transforming SQL results into XML, masked from the client by content-negiotiation (i.e. "query.xml.php"). I then use XSLT as a client-side interface templating layer. This all works as expected and wonderfully. 😃

I decided that I should add support for server-side transformations for browsers that do not suppport XML/XSLT, as strictly speaking they should only need to support xhtml/html (browsers like konquerer for example). I thought this would be a trival wrap of the xml file with something like "query.html.php" which just does the transformation and spits out the result.

Not so.

I have all of the files modularised and accessed via relative paths, and I discovered that all sorts of bad things happened when I tried to access them locally. I realised this was because sablotron was accessing the raw php, not the processed output XML. This can be allievated for the initial XML file by passing the generated XML content as a variable to the xslt_process(), but this simply isn't possible for XML files loaded via XSLT's document() function.

My initial solution was to try and be a smartass and change all of the relative paths into absolute HTTP paths, the logic being that when they are served they will be processed correctly.

No such luck.

Sablotron only seems to support local files, even though PHP's fopen wrappers are enabled and working.

So, any ideas for further enquiry anyone? 😕

Oh yes, I should mention that I'm using PHP 4.3 with Apache 2 on Debian.

    Do you have a sample code that does not work that you can post and describe how it should work?

      Ok, no example is needed to show that local file access does not process the php to generate the XML (though of course that would be the ideal solution), so I'll move straight to attempting to use HTTP handlers to read files.

      <?php 
      
      // Allocate a new XSLT processor
      $xh = xslt_create();
      
      // Turn on XSL logging
      xslt_set_log($xh, true);
      xslt_set_log($xh, getcwd() . '/myfile.log');
      
      // Process the document, returning the result into the $result variable
      $result = xslt_process($xh, 'http://qwerki.jamie-thompson.co.uk/photos/fileA.xml', 'http://qwerki.jamie-thompson.co.uk/photos/transform.xsl');
      
      xslt_free($xh);
      
      print $result;
      
      ?>

      ...that should work, but it doesn't, I get:

      Warning: Sablotron error on line 1: XML parser error 4: not well-formed (invalid token) in /var/www/mrmedia/output.html.php on line 10

      It works fine in browsers however:
      Click here to view it working
      ...and here's the XSLT

      Just so you know, fileA.xml.php generates:
      <?xml version="1.0" encoding="iso-8859-1"?>
      <?xml-stylesheet type="text/xsl" href="transform.xsl"?>
      <test value="wibble"/>

      and fileB.xml.php generates:
      <?xml version="1.0" encoding="iso-8859-1"?>
      <random value="turnips"/>

      So there you have it. El-Sablotron dinnae like reading anything other than local files, even when PHP's fopen handlers are enabled. Which is unfortunate.

        so, you need to transform files located on a remote server using transforms which are also located on a remote server? is that the problem?

          Well, not stictly speaking, no. The whole accessing via HTTP is just a means to an end, that being getting PHP to process the PHP that generates the XML rather than just sending the raw PHP off to Sablotron, which obviously doesn't work.

          The problem is that I generate the majority of my XML files from php files, the full filenames of these masked by Apache's multiviews.

          I could concieveably do away with the multiviews, but the only way I can see to get PHP to process the files (thus outputting the XML, not the raw PHP), is to access them via HTTP.

          ...which it seems Sablotron doesn't support, even though PHP itself does.

            if you know how to run anothr php script from a php script, then you could capture the former script's output in to a string and pass the string to xslt_process.

              That indeed does work for a single xml file, but unfortunately, as additional xml files are loaded via XSL's document() function, there is no way to pass the processed XML output in.

              In the last hour or so I've managed to solve the problem to some extent though, using xslt_set_scheme_handlers to add support for http to Sablotron. This allows everythign to work as I'd planned, relative paths and all.

              I now have two interesting issues to work out, th emost significant is that I need to re-work how I use sessions as these obviously no longer work as the client is the webserver itself. The second, more minor point is a strnage inconsistency with XSL's template matching.

              Cheers for your thoughts though, this is proving to be quite an interesting problem.

                3 years later

                Holy thread resurrection Batman!

                It's kind of the the same issue though, so I feel somewhat justified in my little bit of necromancy.

                Anyway, I eventually got this all working perfectly not long after my last post here and this continued until yesterday, when I ran my Debian upgrades and they finally depreciated php4. So I decided rather than deal with running a hack install to continue to use php4, I'd bite the long-overdue bullet and update my code to php5.

                All the XML-DOM stuff and XSL seems easy enough to migrate to - bar one thing I just can't figure out.

                My solution that was working was hooking up custom handlers like thus:

                function extendedSablotronSchemeHandler($processor, $scheme, $rest)
                {
                	$url = $scheme . ':' . $rest;
                
                switch($scheme)
                {
                case 'http':	return sablotronHandlerHTTP($url);
                case 'php':	return sablotronHandlerPHP($rest);
                default:	return file_get_contents($url);
                }	
                }
                
                function sablotronHandlerHTTP($uri)
                {
                	$req =& new HTTP_Request($url);
                
                if (!PEAR::isError($req->sendRequest()))
                	return($req->getResponseBody());
                else
                	return '';
                }
                
                function sablotronHandlerPHP($path)
                {
                	if ($filename = selectBestMultiview($path))
                		return sandboxPHP($filename);
                	else
                		return '';
                }
                
                ...
                
                xslt_set_scheme_handlers($xh, array( 'get_all' => extendedSablotronSchemeHandler' ) );
                xslt_setopt($xh, xslt_getopt($xh) | XSLT_SABOPT_IGNORE_DOC_NOT_FOUND);

                Sadly, there doesn't seem to be a way to hook anything remotely similar into the new XSL libraries.

                Does anyone have any suggestions? ...I could do with some additional avenues of enquiry?

                  5 days later

                  Well, for the sake of someone else, I discovered PHP's streams, which are a far better solution for what I wanted than Sablotron's scheme handlers, as they work everywhere, not just with the XSLT module.

                  class PHPSBStream
                  {
                  ...
                  	function stream_open( $path, $mode, $options, &$opened_path )
                  	{
                  ...
                  		$this->actualFilename = selectBestMultiview( $this->filename );
                  ...
                  		$this->output = sandboxPHP( $this->actualFilename );
                  ...
                  	}
                  
                  ...
                  
                  function url_stat( $path, $flags )
                  {
                  	preg_match( '/:\/\/([^\?]*)/', $path, $res );
                  	if ( $filename = $res[ 1 ] )
                  		$actualFilename = selectBestMultiview( $filename );
                  
                  	return stat( $actualFilename );
                  }
                  }
                  
                  stream_wrapper_register( 'phpSB', 'PHPSBStream' )
                  	or die( 'Failed to register PHP sandbox protocol' );
                  
                    Write a Reply...