Search code examples
phpsqlxpathdomxpath

translating sql-like syntax to xpath in PHP


I am writing a class to read content of an xml with mysql-like syntax (of course it not completly the same syntax and I ignore many things, just want to have some basics).

First of all, statements look like:

"SELECT name passwd FROM table WHERE id = 1 and description = 'desc'"

Some basic rules:

  • There has to be a whitespace between everything
  • not more than two comparisons after the "WHERE"

I haven't done anything but the method for the select statement. I explode the statement, sort it into an array and then try to translate it to DOMXpath

It works if there's no WHERE. But I'm struggling with the where-clauses (here what I have done so far:)

statement: "SELECT name pw FROM user WHERE id = '1' and description = 'test or test2'"

the array looks like:

array(4) {
  ["type"]=>
  string(6) "SELECT"
  ["searchFields"]=>
  array(2) {
    ["searchField0"]=>
    string(4) "name"
    ["searchField1"]=>
    string(2) "pw"
  }
  ["tableName"]=>
  string(4) "user"
  ["comparer"]=>
  array(2) {
    ["where0"]=>
    array(3) {
      ["field"]=>
      string(2) "id"
      ["operator"]=>
      string(1) "="
      ["value"]=>
      string(3) "'1'"
    }
    ["where1"]=>
    array(4) {
      ["splitTag"]=>
      string(3) "and"
      ["field"]=>
      string(11) "description"
      ["operator"]=>
      string(1) "="
      ["value"]=>
      string(15) "'test or test2'"
    }
  }
}

How I'm trying to convert the statement to Xpath with the following code:

for ($i = 0; $i < count($arrQuery['searchFields']); $i++) {
    $conditions = "";
    foreach ($arrQuery['comparer'] as $value) {
        switch (count($arrQuery['comparer'])) {
            case 1:
                $conditions .= '//parent::content[@name="'.$value['field'].'" and text()='.$value['value'].']';
                break;
            case 2:
                if (!isset($value['splitTag']) || $value['splitTag'] == "and") {
                    $conditions .= '//parent::content[@name="'.$value['field'].'" and text()='.$value['value'].']';
                    break;
                } else {
                    //$conditions .= 'content[@'.$value['field'].' and text()='.$value['value'].']//parent::*';
                }
                break;
        }
    }
    $xpathquery = '//datarange[@name="'.$arrQuery['tableName'].'"]'.$conditions.'//content[@name="'.$arrQuery['searchFields']['searchField'.$i].'"]';
    $nodeList = $this->xpath->query($xpathquery);
    foreach ($nodeList as $node) {
        $arrContent['searchField'.$i][] = $node->nodeValue;
    }
}

My first Point is: if the condition of the if-clause in case 2 is confirmed, the created xpath isn't working (might be problem with parent or my logic)

My second Point is: I still have no idea how to handle the case that the condition doesn't match and $value['splitTag'] is "or". If anyone has a hint how to solve that, I would be very thankfull.

/EDIT: Ok, thanks for the tip, here's an example of my xml:

<database>
  <datarange id="user">
    <dataset id="0">
      <content name="id">
        1
      </content>
      <content name="description">
        test or test2
      </content>
      <content name="name">
        Name
      </content>
      <content name="pw">
        Passwort
      </content>
    </dataset>
    <dataset id="1">
      <content name="name">
        Name2
      </content>
      <content name="pw">
        Passwort2
      </content>
    </dataset>
  </datarange>
  <datarange id="config">
    <dataset id="0">
      <content name="type">
       command
      </content>
      <content name="name">
       lab
      </content>
      <content name="type">
       request
      </content>
      <content name="target">
       target_name
      </content>
      <content name="desc">
        Address Book
      </content>
    </dataset>
  </datarange>
</database>

Solution

  • Given your input document and the query: SELECT name pw FROM user WHERE id = '1' and description = 'test or test2 you will have to build yourself the following XPath to get to the dataset node that has the values you need:

    //datarange[@id = 'user']/dataset
          [normalize-space(content[@name = 'id']) = '1']
          [normalize-space(content[@name = 'description']) = 'test or test2']
    

    This one will give you the dataset node that you can then run the following on:

    normalize-space(content[@name = 'name'])
    

    to get the name and:

    normalize-space(content[@name = 'pw'])
    

    Here's a simple XSLT to test it:

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
        <xsl:template match="/">
            <xsl:apply-templates select="//datarange[@id = 'user']/dataset
                    [normalize-space(content[@name = 'id']) = '1']
                    [normalize-space(content[@name = 'description']) = 'test or test2']"/>
        </xsl:template>
    
        <xsl:template match="dataset">
            name: <xsl:value-of select="normalize-space(content[@name = 'name'])"/>
            pwd: <xsl:value-of select="normalize-space(content[@name = 'pw'])"/>
        </xsl:template>
    
    </xsl:stylesheet>
    

    When applied to your input document it will produce:

    name: Name
    pwd: Passwort
    

    You can now factor it into your query SQL-like-to-XPath engine.

    One more thing though. If you can upgrade to XPath 2.0 you may want to try the conditional and quantified expressions to make your XPath more query-like. And who knows, maybe you won't need a SQL-like syntax to begin with. Plus there's XQuery.