1

Here is what I have so far, I want to add date/time logs to the xml file.

   <?php

       // Load the XML file we want to write to
          $visitors  = simplexml_load_file("data.xml");

       // Set e-mail xml search
          $search_id = htmlentities($_POST['email']);
          $visitors = $visitors->xpath("visitor[email='$search_id']");

       // If we have a search result, email already exists in xml file
          if(isset($visitors[0])){
           $visitor = $visitors[0];
           $email = (string) $visitor->email;

           // ********** Need to add date/time for each visit here.
           // So, I guess I need to search for e-mail address, then append to it.
           // Help required here... and again below to confirm formatting for multiple
           // dates. 

          } else {
           $xml = simplexml_load_file("data.xml");
           $sxe = new SimpleXMLElement($xml->asXML());
           $search_id = preg_replace('/[^(\x20-\x7F)]*/','', $search_id);

            $visitor = $sxe->addChild("visitor");
            $visitor->addChild("email", $search_id);

            // ******** Not sure this is the correct xml formatting.  
               $date = $visitor->addChild('date');
               $date->addChild("email", date("Y-m-d H:i:s"));

           $sxe->asXML("data.xml");
          } // if(isset($visitors[0])){

       } // if(isset($data)){ ?>

Which generates

<?xml version="1.0" encoding="utf-8"?>
<visitors>
 <visitor>
  <email>jon.doe@aol.com</email>
  </visitor>
</visitors>

What I want to do is add a date log (incementing/appending) for each visit.

So the result would be (I am not sure my formatting is correct, which of course is not helping to matters). Don't worry about the PHP for the date/time.

<?xml version="1.0" encoding="utf-8"?>
<visitors>
 <visitor>
  <email>jon.doe@aol.com</email>
  <date>2012-11-01 11:00:00</date>
  <date>2012-11-02 14:00:00</date>
  </visitor>
</visitors>
Stuart
  • 690
  • 2
  • 9
  • 26
  • Related: [How to append to a XML file with PHP preferably with SimpleXML](http://stackoverflow.com/q/7098093/367456) – hakre Nov 03 '12 at 09:55

2 Answers2

3

I think you were on the right track here, the main thing is that your call to asXML('data.xml') is inside your ELSE block, so the file only gets written to file if it is being created from scratch (ie. The search_id is not found).

I made a few changes, mostly to make it easier to follow. More changes to come!:

<?php

// Load the XML file we want to write to
$xmlFile  = simplexml_load_file("data.xml");

// Set e-mail xml search
$search_id = 'jon.doe@aol.com';
$results = $xmlFile->xpath("visitor[email='$search_id']");

// If we have a search result, email already exists in xml file
if(isset($results[0]))
{
    $visitor = $results[0];
    $email = (string) $visitor->email;

    echo "Found Email: " . $email . "\n";
    echo "Date: " . $visitor->date . "\n";
    // ********** Need to add date/time for each visit here.
    // So, I guess I need to search for e-mail address, then append to it.
    // Help required here... and again below to confirm formatting for multiple
    // dates.
    echo "Adding new date";
    $visitor->addChild('date', date('Y-m-d H:i:s'));

    echo "New XML: " . $xmlFile->asXML();

    $xmlFile->asXML('data.xml');

} else {
    $sxe = new SimpleXMLElement($xmlFile->asXML());
    $search_id = preg_replace('/[^(\x20-\x7F)]*/','', $search_id);

    $visitor = $sxe->addChild("visitor");
    $visitor->addChild("email", $search_id);

    // ******** Not sure this is the correct xml formatting.
    $date = $visitor->addChild('date', date('Y-m-d H:i:s'));

    $sxe->asXML("data.xml");

}
?>

After running this script a few times, I got:

<?xml version="1.0"?>
<visitors>
        <visitor>
                <email>jon.doe@aol.com</email>
                <date>2012-11-03 02:13:28</date>
                <date>2012-11-03 02:20:20</date>
                <date>2012-11-03 02:22:07</date>
                <date>2012-11-03 02:22:10</date>
                <date>2012-11-03 02:22:13</date>
        </visitor>
</visitors>

Which I think is what you want? Does this help?

EDIT:

Regarding your mentioning of whether or not it's the correct XML format. I suppose there's no exclusive right answer. I would say that this format is fine, provided that you remember to separate each individual email address / set of dates in its own visitor tags.

To do that, you'd probably want something like so (untested):

$newVisitor = $xmlFile->addChild('visitor');
$newVisitor->addChild('email', $newEmail);
$newVisotor->addChild('date', $newDate);
$xmlFile->asXML('data.xml');

EDIT 2:

One last thing that I've used in the past is the following code. It will format the XML much nicer, and will also prevent any corruption of the original XML file if you have any issues mid-save (or at least give you a backup). Personally, I try to make a habit of using a temporary file like this any time I have to write to file.

Of course, just add this, then replace your calls to:

  • $xmlFile->asXML('data.xml');
  • $sxe->asXML("data.xml");

With:

  • writeXML('data.xml', $xmlFile);
  • writeXML('data.xml', $sxe);

And it should work OK.

//Writes final version of the SimpleXML object to file.
//Includes writing to a temp file for safety.
//This way is safer because if the code crashes mid-write it won't garble the original file.
function writeXML($fileName, $xmlObject)
{
        //Sanity check before copy, prevent any visible errors:
        if (file_exists($fileName))
                copy($fileName, $fileName . '.backup');

        //Write to temp file first in-case something kills the write; don't want to corrupt the original.
        $tmpName = $fileName . '~$.' . microtime();
        $handle = fopen($tmpName, 'w');
        fwrite($handle, formatXML($xmlObject));
        fclose($handle);
        copy($tmpName, $fileName);
        unlink($tmpName);
}

//asXML does no indentation formatting, so we'll do it here instead. This is called by WriteXML so that we're writing formatted XML to file.
function formatXML($xmlObject)
{
        $dom = new DOMDocument('1.0');
        $dom->preserveWhiteSpace = false;
        $dom->formatOutput = true;
        $dom->loadXML($xmlObject->asXML());
        return $dom->saveXML();
}
Geekman
  • 599
  • 7
  • 18
  • Your are a hero... I was just starting to loop through with foreach... didn't think that was the right approach and clearly it wasn't! – Stuart Nov 02 '12 at 15:28
  • Let me know if there's anything else I can assist with here. I wanted to provide some code to properly format the XML when being written to file... But unfortunately it's not working yet. :( – Geekman Nov 02 '12 at 15:44
  • No worries. Just FYI. I've just added the code for formatting to my original answer. – Geekman Nov 02 '12 at 15:58
  • @Geekman: I added an answer because you asked about how to reduce the code further. I didn't edit yours because it's already that large. And just seeing you've got also a pretty print in there ;) – hakre Nov 03 '12 at 09:54
1

This is an addition to @Geekmans answer

Your code does not look too far of. However you can greatly improve it which is why add another answer here (I would have normally edited @Geekmans answer but it's already very large).

The point of improvement is in the program flow. If the email is not found, you don't need to create a new SimpleXMLElement, you only need to create a new <visitor> element. Then adding the <date> element as well as outputting the file is actually the same for both cases. This allows to remove the duplicate code:

// Specify the XML file we want to write to
$inFile = "data.xml";

// Set e-mail xml search and insert id
$search_id = 'jon.doe@aol.com';
$insert_id = preg_replace('/[^(\x20-\x7F)]*/', '', $search_id);

// Load the XML file we want to write to
$xmlFile = simplexml_load_file($inFile);
$results = $xmlFile->xpath("visitor[email='$search_id']");

// If we have a search result, email already exists in xml file
if ($results) {
    $visitor = $results[0];
    echo "Found Visitor: ", $visitor->email, "\n",
    "First Date: ", $visitor->date, "\n";
} else {
    echo "Adding new Visitor: ", $insert_id, "\n";
    $visitor = $xmlFile->addChild("visitor");
    $visitor->addChild("email", $insert_id);
}

echo "Adding new Date: ", $date = date('Y-m-d H:i:s'), "\n";
$visitor->addChild('date', $date);

echo "Changed XML: \n" . simplexml_pretty_print($xmlFile);
$xmlFile->asXML($inFile);


/**
 * @param SimpleXMLElement $simplexml
 * @link https://stackoverflow.com/a/798986/367456
 */
function simplexml_pretty_print(SimpleXMLElement $simplexml) {
    $dom                     = new DOMDocument();
    $dom->preserveWhiteSpace = false;
    $dom->formatOutput       = true;
    $dom->loadXML($simplexml->asXML());
    return $dom->saveXML();
}

As a bonus I added the simplexml_pretty_print function to have the output in a nicer way.

The output for jon.doe@aol.com:

Found Visitor: jon.doe@aol.com
First Date: 2012-11-01 11:00:00
Adding new Date: 2012-11-03 10:48:05
Changed XML: 
<?xml version="1.0" encoding="utf-8"?>
<visitors>
  <visitor>
    <email>jon.doe@aol.com</email>
    <date>2012-11-01 11:00:00</date>
    <date>2012-11-02 14:00:00</date>
    <date>2012-11-03 10:48:05</date>
  </visitor>
</visitors>

The output for info@example.com:

Adding new Visitor: info@example.com
Adding new Date: 2012-11-03 10:52:09
Changed XML: 
<?xml version="1.0" encoding="utf-8"?>
<visitors>
  <visitor>
    <email>jon.doe@aol.com</email>
    <date>2012-11-01 11:00:00</date>
    <date>2012-11-02 14:00:00</date>
  </visitor>
  <visitor>
    <email>info@example.com</email>
    <date>2012-11-03 10:52:09</date>
  </visitor>
</visitors>

Tip: Whenever you spot duplicate code, there often is a way to write it in a much simpler form.

Community
  • 1
  • 1
hakre
  • 193,403
  • 52
  • 435
  • 836