carica/xpath-functions

Provides XPath 2/3 like functions for PHPs ext/xslt

dev-master 2022-11-23 18:02 UTC

This package is auto-updated.

Last update: 2024-12-23 22:20:34 UTC


README

This is project tries to add Xpath 2/3 functions to PHPs XSLTProcessor. A complete implementation isn't possible - some of the syntax is not available. But let's see how much can be done.

If you have a function that you would like to have added please open an issue.

How it works:

  • Extends the XSLTProcessor with Carica\XpathFunctions\XSLTProcessor
  • Implements a callback for the XSLTProcessor to call specific PHP functions
  • Adds a stream wrapper to load XSLT templates that wrap callbacks to PHP as Xpath functions using EXSLT or implement the function directly.

Install

composer require carica/xpath-functions

Usage

  1. Define the namespace for the function
  2. Import a module into your XSLT
  3. Call the Xpath function

Step 2 is the difference to XSLT 2/3. You need to import the module template with the functions you would like to use.

Compromises

Xpath/XSLT 1.0 does not of the extensive type system of their successors. So most of the functions return more basic types. Arrays and maps are emulated with XDM nodes, sequences as XDM arrays.

Examples

Use String Comparsion

// import extended XSLTProcessor
use Carica\XpathFunctions\XSLTProcessor;

$xslt = <<<'XSLT'
<?xml version="1.0"?>
<xsl:stylesheet 
  version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:fn="http://www.w3.org/2005/xpath-functions"
  exclude-result-prefixes="fn">
  <!-- ^- define the function namespace -->
  
  <!-- import the string comparsion module -->
  <xsl:import href="xpath-functions://Strings/Comparsion"/>
  <xsl:output indent="yes" method="xml"/>
                
  <xsl:template match="/names">
    <html lang="en">
      <body>
        <div>
          <header>Exactly</header>
          <xsl:for-each select="name[fn:compare(., 'André') = 0]">
            <span><xsl:value-of select="."/></span>
          </xsl:for-each>
        </div>
        <div>
          <header>Case Insensitive, Ignore Accents</header>
          <xsl:variable name="collation">http://www.w3.org/2013/collation/UCA?strength=primary</xsl:variable>
          <xsl:for-each select="name[fn:compare(., 'andre', $collation) = 0]">
            <span><xsl:value-of select="."/></span>
          </xsl:for-each>
        </div>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>
XSLT;

$xml = <<<'XML'
<?xml version="1.0"?>
<names>
  <name>Andreas</name>
  <name>Andre</name>
  <name>André</name>
  <name>Andrè</name>
</names>
XML;

$stylesheet = new DOMDocument();
$stylesheet->loadXML($xslt);
$input = new DOMDocument();
$input->loadXML($xml);

$processor = new XSLTProcessor();
$processor->importStylesheet($stylesheet);

echo $processor->transformToXml($input);

Output:

<?xml version="1.0"?>
<html lang="en">
  <body>
    <div>
      <header>Exactly</header>
      <span>André</span>
    </div>
    <div>
      <header>Case Insensitive, Ignore Accents</header>
      <span>Andre</span>
      <span>André</span>
      <span>Andrè</span>
    </div>
  </body>
</html>

Wrap Parts Of Text Nodes Using RegExp

// import extended XSLTProcessor
use Carica\XpathFunctions\XSLTProcessor;

$xslt = <<<'XSLT'
<?xml version="1.0"?>
<xsl:stylesheet 
  version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:fn="http://www.w3.org/2005/xpath-functions"
  exclude-result-prefixes="fn">
  
  <!-- import RegExp functions -->
  <xsl:import href="xpath-functions://Strings/RegExp"/>
                
  <xsl:template match="speak//text()">
    <!-- use RegExp function -->
    <xsl:for-each select="fn:analyze-string(., '\d+')/*">
      <xsl:choose>
        <xsl:when test="local-name() = 'match'">
          <say-as interpret-as="characters"><xsl:value-of select="."/></say-as>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="."/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each>
  </xsl:template>
  
  <xsl:template match="*">
    <xsl:element name="{name()}">
      <xsl:copy-of select="@*"/>
      <xsl:apply-templates/>
    </xsl:element>
   </xsl:template>
</xsl:stylesheet>
XSLT;

$xml = <<<'XML'
<?xml version="1.0"?>
<speak>The test number is 123456789, and some further block of text.</speak>
XML;

$stylesheet = new DOMDocument();
$stylesheet->loadXML($xslt);
$input = new DOMDocument();
$input->loadXML($xml);

$processor = new XSLTProcessor();
$processor->importStylesheet($stylesheet);

echo $processor->transformToXml($input);

Output:

<?xml version="1.0"?>
<speak>The test number is <say-as interpret-as="characters">123456789</say-as>, and some further block of text.</speak>

Implemented Functions