...

View Full Version : PHP help to save, search and edit a text file



cdn2005
08-12-2008, 08:01 PM
Can I please get some help for the following. I have a very basic form, but I like to save the data from the form to a text file (or csv file) and then be able to edit the data later on, if necessary. (No, I do not have access to MySQL, hence I have to save the data to a text file.)

Secondly, on another form I like to have a drop down menu with the keywords as the select and based on the key field seelected, I like to display about that item on that page. Before displaying them, I like to spilt the data from each field and apply some formatting as well, as shown below.

So basically three things:
- Save data to a text file
- Edit data of selected item
- Display data of selected item


Sample data that will be entered from the form:
FR230E|Joe Smith|Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.|Philadelphia Arizona Ohio California
<repeated with similar data>

Sample output page:
ID Number: FR230E (this will be used as the key filed)
Name: Joe Smith
Description: Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.
Cities Covered:


Philadelphia
Arizona
Ohio
California

Fou-Lu
08-12-2008, 08:36 PM
What is the trouble you're having with this? You need to remember that we are here to help, but not do.
My suggestion is to create an object representing this. A struct would be better, but since PHP doesn't support those an object would be just fine. Read the data in one line at a time, and populate a collection of you're objects; there are several functions dedicated to reading and can be found here (http://ca3.php.net/manual/en/ref.filesystem.php). Either tokenize or split you're data on you're delimiter and store each inside their respective property. When done, save all back to the file.

If you really want to take the dirty way out, you can look into using serialize and unserialize. These will produce a lot of overhead, but will make reading and writing a piece of cake. Another option is to use XML files which you can logically treat similar to a database (I have an XML File driver I wrote to work with my Storage factory).
Look into some stuff, try a few things first. Come back when you're having difficulty with actual code.

cdn2005
08-12-2008, 09:17 PM
Thank you for the feedback. Yes, I understand your point. I thought it might be better for someone to suggest a good solution as opposed to my mediocre solution. However, I do have the code to save the data to a text file.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Untitled Document</title>
</head>

<body>

<form action="./mytest.php" method="POST" name="form1" id="form1">
Course ID <br />
<input type="text" name="courseID" /><br />
Course Name<br />
<input type="text" size="90" name="courseName" /><br />
Course Vendor <br />
<input type="text" name="courseVendor" /><br /><br />

Course Type <br />
<input type="radio" name="courseType" value="Instructor Led"/>Instructor Led
<input type="radio" name="courseType" value="eLearning"/>eLearning<br /><br />

Objective <br />
<textarea class="textarea" rows="10" cols="90" name="objective" /></textarea><br />
Course Description<br/>
<textarea class="textarea" rows="10" cols="90" name="description"></textarea><br />
Key Topics Covered<br/><textarea class="textarea" rows="10" cols="90" name="keytopics"></textarea><br />

<input type="submit" name="submit" value="Submit" />
</form>
</body>
</html>

and the PHP...

<html>
<head>
<?php
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
//if ($_POST['submit']=='OK') {
//if (count(array_diff(array('name', 'email', 'dept', 'managerName', 'memail', 'location'), array_keys($_POST))) == 0) # POSTed
// {
$courseID = $_POST['courseID'] ;
$courseName = $_POST['courseName'] ;
$courseVendor = $_POST['courseVendor'] ;
$courseType = $_POST['courseType'] ;
$objective = $_POST['objective'] ;
$description = $_POST['description'] ;
$keytopics = $_POST['keytopics'] ;
#echo $name;
$f=fopen("./courseDetails.txt","a");
//fwrite($f,"****************************************\r\n\r\n");
fwrite($f,"$courseID|$courseName|$courseVendor|$courseType|$objective|$description|$keytopics\r\n");
fclose($f);
}
?>
<title>Thank You</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
</head>

Here is some sample data:

XML305E|Inroduction to C|Vendor A|Instructor Led|After completing this comprehensive training, you will have the necessary skills to:
- Write C COde|This comprehensive course, on C programming. You will learn the basics.|Module 1: What is C
Module 2: C Language Concepts
Module 3: Arrays and Strings
Lab 1: Write \"Hello World!\"
Module 4: Introduction to Functions
Lab 2: Functions and Procedures
Module 5: Operators and Expressions
Module 6:

CPLUS305E|Inroduction to C|Vendor CPLUS|Instructor Led|After completing this comprehensive training, you will have the necessary skills to:
- Write C++ COde|This comprehensive course, on C programming. You will learn the basics.|Module 1: What is C++
Module 2: C Language Concepts
Module 3: Arrays and Strings
Lab 1: Write \"Hello World!\"
Module 4: Introduction to Functions
Lab 2: Functions and Procedures
Module 5: Operators and Expressions
Module 6:

XML350E|Inroduction to XML|Vendor XML|Instructor Led|After completing this comprehensive training, you will have the necessary skills to:
- Write XML COde & HTML|This comprehensive course, on XML programming. You will learn the basics of XML Coding.|Module 1: What is XML
Module 2: XML Language Concepts
Module 3: Arrays and Strings
Lab 10: Write \"Hello World!\"
Module 4: Introduction to Functions
Lab 20: Functions and Procedures
Module 5: Operators and Expressions


- The part I need help now is on how to add an Edit button to the data
- How to select one item based on the ID. Example, if I just want to edit the information for XML350E, how do I do that.
- Likewise, how do I pick data based on an ID selected and display the data with some html formatting applied to the last field value. In this case, that would be the topics section, so that I can display them as an unordered list, as shown in my original message. Hope you can help.

I heard a lot about serialize/deserialize, but without seeing any actual working code, I am having a hard time figuring out how to use that for my application here. I would be happy with an XML solution as well, if you have one that you could share.

Thank you.

Fou-Lu
08-13-2008, 01:00 AM
This is where C would be easy, read from a file and dump into a struct of a specified size. PHP doesn't allow this though, which is kinda a pain.
The biggest problem is that you allow newline entries inside of you're text area, and this will be a pain when trying to determine if you've finished the current record or of you're still inside of it. An option would be to use a record delimiter that differs from the pipebars, but will always be a pain no matter what if an entry includes the delimiter and has not been replaced.
My recommendation would be to use XML with DOMDocument if you have it available. Combine this with XPath it will make it really easy to find records:


<?xml version="1.0" encoding="UTF-8"?>
<classes>
<record>
<courseCode><![CDATA[XML305E]]></courseCode>
<courseName><![CDATA[Introduction to C]]></courseName>
<courseVender><![CDATA[Vendor A]]></courseVender>
<courseType><![CDATA[Instructor Led]]></courseType>
<courseObjective><![CDATA[After completing this comprehensive training, you will have the necessary skills to:
- Write C COde]]></courseObjective>
<courseDescription><![CDATA[This comprehensive course, on C programming. You will learn the basics.]]></courseDescription>
<courseKeyTopics><![CDATA[This comprehensive course, on C programming. You will learn the basics.|Module 1: What is C
Module 2: C Language Concepts
Module 3: Arrays and Strings
Lab 1: Write \"Hello World!\"
Module 4: Introduction to Functions
Lab 2: Functions and Procedures
Module 5: Operators and Expressions
Module 6: ]]></courseKeyTopics>
</record>
<!-- ... -->
</classes>


Now, here comes the fun. Construct a new dom document with xpath:


$dom = new DOMDocument('file.xml');
$xpath = new DOMXPath($dom);
// To find records, we write a query:
$sQry = '//record[courseCode=XML305E]';
$records = $xpath->evaluate($sQry);



This will go on and on, and sadly I won't have enough characters to show you how to do this. There are insertions, selections, deletions and modifications to attend to, which means the methods you'll need are DOMDocument::createElement, DOMDocument::createCDATASection, DOMDocument::removeChild, DOMXPath::evaluate, DOMNodeList::item, etc. It gets quite complex, but mimics a database quite well.

Serialization in PHP is super easy:


$array = array(1 => 'My Element @ 1');
$sArray = serialize($array); // Will be something like: a:1{i:1(1),s:14('My Element @ 1')}
$arrayRestuct = unserialize($array); // Put it back into an array.

The problem with this is you need to read and write an entire array every time to the truncated file.

Coming from a C perspective, I'd put a small header at the front of every record indicating how many bytes the entire record is. I would probably force maximum sizes on the ID and names, some of the shorter fields so they can be stored as a sized array instead of as a char *. This will allow you to fread only a certain number of bytes, and then split each of you're properties through explode or tokenizing. This will get around the record separation problem, since now you couldn't care less what its deliminated by.

As you can see there are several ways of doing this. Personally I'm pro XML files for the simplicity of mimicking the DB. In any case, it will take a little bit of research from you're end to determine the best route. I don't consider any solution to be 'Mediocre', I consider every solution to be a step towards a specified goal. No matter what you create, you can always improve it - there is no such thing as the perfect program.

cdn2005
08-13-2008, 01:23 AM
Wow, thanks for the reply. You are clearly a pro XML! Unfortunately, with my limited expertise in PHP or XML programming, I think it will be a huge mountain for me to tackle to try and do all that you have mentioned.

I thought PHP was supposed to powerful in handling data for web based applications!

Just to try and understand your code, i am assuming that I have to wrap the data with all those tag like <courseCode> etc before I save it to the text file.

I have no idea how to create DOMDocument::createElement etc. I will do soe research, but from my level it would be like learning a brand new language.

If you get some spare time and if you feel like finishing up what you started, tha would be great. If not, I will keep trying diff things.

Fou-Lu
08-13-2008, 02:10 AM
PHP is the best route for web based languages IMO. File handling is also superb, but even I haven't done a lot of file handling with PHP in a number of years. Everything is the way of the DB now, so its kinda a setback when you've got to do file stuff (like relearning how to do it lol).
Lets do a quick DOMDocument tutorial. I'll warn you up front, DOMDocument is a PHP5 exclusive class and can be forcibly disabled when compiled. I find its quite common in PHP5+ servers though.


<?php
// Create a domDocument:
$dom = new DomDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
$dom->preserveWhiteSpace = false;

// Ok, lets work as if we're on a fresh file:
$root = $dom->createElement('classes');
$dom->appendChild($root); // Place the classes element into the dom
$record = $dom->createElement('record'); // Create a record element.


// Create you're entry fields and append them to record:
$classID = $dom->createElement('classID', $dom->createCDATASection('XML305E'));
$record->appendChild($classID);
$className = $dom->createElement('className', $dom->createCDATASection('Introduction to C'));
$record->appendChild($className);
$courseVender = $dom->createElement('courseVender', $dom->createCDATASection('Vendor A'));
$record->appendChild($courseVender);
$courseType = $dom->createElement('courseType', $dom->createCDATASection('Instructor Led'));
$record->appendChild($courseType);
$courseObjective = $dom->createElement('courseObjective', $dom->createCDATASection('After completing this comprehensive training, you will have the necessary skills to:
- Write C COde'));
$record->appendChild($courseObjective);
$courseDescription = $dom->createElement('courseDescription', $dom->createCDATASection('This comprehensive course, on C programming. You will learn the basics.'));
$record->appendChild($courseDescription);
$courseKeyTopics = $dom->createElement('courseKeyTopics', $dom->createCDATASection('Module 1: What is C
Module 2: C Language Concepts
Module 3: Arrays and Strings
Lab 1: Write \"Hello World!\"
Module 4: Introduction to Functions
Lab 2: Functions and Procedures
Module 5: Operators and Expressions
Module 6:'));
$record->appendChild($courseKeyTopics);

// Now, apply record to the root. This could have been done first thing, but I like to keep it in order:
$root->appendChild($record);

// Thats it, lets save the data to a file:
$dom->save('classes.xml');

This is untested, but should provide the same results in the actual file. It looks way worse than it is, but it is really quite simple. Each item is created as an element and given the data in a cdatasection block. These newly created elements are then attached to a record (also an element), which in turn is attached to the root. Finally the file is saved.
The DOM is huge, you can get to the api here: http://ca3.php.net/manual/en/book.dom.php
With the correct mixtures of xpath and dom you can literally manipulate the files exactly as if it were a database (much like my driver files). When I've completed it I'll probably post it in the snippets, but its not fully tested yet and I haven't converted all of the conditional syntax (IN, CONTAINS, etc, no joins though, what a nightmare that would be >.<).
Practice on the DOM. It will come to you in no time flat. A great place to go for the XML information, including its structure and XPath is www.w3schools.org . They are by no means complete but have a great starters. I'll give you a heads up, learn XML ASAP. XML will be leading communications between languages and I'd suspect that it will gain more and more popularity. DTD is also a requirement, and Schema if you get the time (focus on DTD first, its easier). Finally XPath and XQuery, specifically XPath - I haven't found a language yet (though there may be now) that supports XQuery, so XPath seems to be the better to learn. XQuery is far simpilier than XPath but at least you'll learn the hard stuff :P

cdn2005
08-13-2008, 03:39 PM
Thanks for the reply. You must be a hard core programmer. This stuff is just flowing out without any problems.

Your script is very straight forward and easy to follow. But there is more to it than meets the eye. I am tempted to take the XML method for my problem, but I am afraid that I am getting way over my head with this stuff.

Anyway, I tried your code and got this error message
Warning: DOMDocument::createElement() expects parameter 2 to be string, object given in C:\wamp\www\testSite\DOM Examples\saveFile.php on line 14

and the error is repeated for every line containg:

$classID = $dom->createElement('classID', $dom->createCDATASection('XML305E'));

I am not sure if PHP 5 is supported in our system. I wonder if the warning has to do with that.

Fou-Lu
08-13-2008, 08:18 PM
No, you're right I messed up. I tried this after and createElement cannot be given a cdatasection at the same time. Easy fix:


$element = $dom->createElement('myelem');
$data = $dom->createCDATASection('My Data for elem.');
$element->appendChild($data);

CDATA needs to be appended to element. My bad, sorry about that. An alternative *probably* exists to this format. CDATASection is likely a typeof Node, meaning it has an attribute called nodeValue or data on it which returns a string result. This would let you combine the two. I'd need to reflect the cdata result to see what it is exactly to confirm this though.

PHP5 and DOMDocument are both supported on you're system from the system error. It is warning you that it expects different datatypes than what has been provided, and thats ok. If you didn't support it you're error would either be referring to no class definition for DOMDocument or an undefined method on object of unknown type DOMDocument.

I'm almost done my file driver for this as well, just working out a few bugs and optimizing it. Haven't tried it with 1000 record yet, but with 10 its about 8 times faster than SQL access.

cdn2005
08-14-2008, 05:34 AM
well that helped to move forward.
Now there is no warning, no erros, but the data didn't get saved in to the classes.xml file.

All I have in that file is

<?xml version="1.0" encoding="UTF-8"?>
<classes>
<record/>
</classes>

I changed everything in my code to...

$element = $dom->createElement('classID');
$data = $dom->createCDATASection('XML305E');
$element->appendChild($data);
$element = $dom->createElement('className');
$data = $dom->createCDATASection('Introduction to C');
$element->appendChild($data);
and repeated for all elements...

I thought the script would save the data at the end!

Your file driver, is it something that you can share, either privately or through the forum?

Fou-Lu
08-14-2008, 09:00 AM
You're record element is empty.
Since you are re-using you're $element variable, you need to make sure you're applying the data at the end of every call:


// Not sure what you called it, but we'll call it $record:
$record = $dom->createElement('record');
$element = $dom->createElement('classID');
$data = $dom->createCDATASection('XML305E');
$element->appendChild($data);
// This is what I'm referring to, at this level $element has been reassigned, so we
// will put this into the $record element right away:
$record->appendChild($element);

$element = $dom->createElement('className');
$data = $dom->createCDATASection('Introduction to C');
$element->appendChild($data);
$record->appendChild($element);

Each of you're elements should be appended to the $record. Don't forget to ensure that $dom->save('yourefile.xml'); is done after all of you're changes are performed. My money is in that you have that at the bottom, but the actual $record doesn't have any data in it. If you've used the variable $element for everything, it will be a quick copy and paste :)
Looks like you're coming along pretty well. DOM is so intensive, I've used it for about 2 years now and I still have so many troubles with it >.<. It does get a lot easier with practice though.

The file driver is almost ready, I went back to work on it the other day. Since its part of a storage factory it requires specific functions to be implemented to work properly (storage factories let you use many different types of storages including files and databases without having to change the code making use of them). I'll likely be posting it by the weekend, I just need to add two more conditions for it to be usable. I'm leaving off schema structures and relationship cascades until version 2. Version 1 will just be the select, insert, update, and deletion functions.

cdn2005
08-14-2008, 07:17 PM
Wow... that part is working now. That feels good, but equally scared because I know now I got the taste of something good and I can' turn back, but I am getting in to deeper waters ...

I now tried to modify the script so that it will take the data from my original html form. So I added the following lines, but it is not happy.


if ($_SERVER['REQUEST_METHOD'] == 'POST') {
//if ($_POST['submit']=='OK') {
// {
$courseID = $_POST["courseID"] ;
$courseName = $_POST["courseName"] ;
$courseVendor = $_POST["courseVendor"] ;
$courseType = $_POST["courseType"] ;
$objective = $_POST["courseObjective"] ;
$description = $_POST["courseDescription"] ;
$keytopics = $_POST["courseKeyTopics"] ;

}

and then the script was changed to :

$element = $dom->createElement('courseID');
$data = $dom->createCDATASection('$courseID');
$element->appendChild($data);
$record->appendChild($element);

$element = $dom->createElement('courseName');
$data = $dom->createCDATASection('$courseName');
$element->appendChild($data);
$record->appendChild($element); and so on...

I am sure I should be using a loop for this, but that is something for later... Anyways, this didn't do anything. (I changed the form field names in both form and here)

Secondly I also came across a tutorial: http://www.phpfreaks.com/tutorial/handling-xml-data/page2

In that example, the data is stored as
<?xml version="1.0"?>
<books>
<book isbn="978-1594489501">
<title>A Thousand Splendid Suns</title>
<author>Khaled Hosseini</author>
<publisher>Riverhead Hardcover</publisher>
<amazon_price>14.27</amazon_price>
</book>
<book isbn="978-1594489587">
<title>The Brief Wondrous Life of Oscar Wao</title>
<author>Junot Diaz</author>
<publisher>Riverhead Hardcover</publisher>
<amazon_price>14.97</amazon_price>
</book>
<book isbn="978-0545010221">
<title>Harry Potter and the Deathly Hallows</title>
<author>J. K. Rowling</author>
<publisher>Arthur A. Levine Books</publisher>
<amazon_price>19.24</amazon_price>
</book>
</books>


In your example, yo uare storing data as:

<?xml version="1.0" encoding="UTF-8"?>
<classes>
<record>
<classID><![CDATA[XML305E]]></classID>
<className><![CDATA[Introduction to C]]></className>
<courseVendor><![CDATA[Vendor A]]></courseVendor>
<courseType><![CDATA[Instructor Led]]></courseType>
<courseObjective><![CDATA[After completing this comprehensive training, you
will have the necessary skills to: - Write C COde]]></courseObjective>
<courseDescription><![CDATA[This comprehensive course, on C programming. You
will learn the basics.]]></courseDescription>
<courseKeyTopics><![CDATA[Module 1: What is C
Module 2: C Language Concepts
Module 3: Arrays and Strings
Lab 1: Write \"Hello World!\"
Module 4: Introduction to Functions
Lab 2: Functions and Procedures
Module 5: Operators and Expressions
Module 6:]]></courseKeyTopics>
</record>
</classes>


What is the difference? Which method is better? The <classes> in your example, is that a keyword or could I have used <courses> instead?

Fou-Lu
08-14-2008, 09:14 PM
Lots of questions in one :)
Classes is not reserved. In XML, the only tag you cannot use is <xml> unless its a namespace style tag. Any other tag name that matches criteria (I'm taking a stab at it based on language variables) must start with a string, can contain numbers, chars, and underscores, but no other special characters. XML is so flexible since its a data descriptive language. You can name you're tags whatever you want. You're root can be called <myUberRoot> and it won't matter.
CDATA blocks are not necessary. Without them is more optimized. However, since I plan on using mine to store HTML and code data, CDATA will allow me to get around storing special character representations for this code. In other words, I'm too lazy to store replaced data and then reparse it on load. CDATA is character data, while no cdata represents parsable character data. So, if I have this:


<?xml version="1.0"?>
<root>
<code><html><head><title>Page Title</title></head><body></body></html></code>
</root>

The tags within the <code> block will be treated as a part of the XML document. Using <![CDATA[<html><head><title>Page Title</title></head><body></body></html>]]> tells the XML file this is a string, and not to parse the interior code as xml.

As for you're actual insertion data, the problem is with these:
$data = $dom->createCDATASection('$courseID');
Wraping a variable with single quotations in PHP treats it as a non-parsable string. So '$courseID' literally means $courseID. What you want is one of these:
$data = $dom->createCDATASection($courseID); or $data = $dom->createCDATASection("$courseID");, with emphasis on the first since it is faster to process.

Don't worry about turning back. Even if you never use the DOM class again, what you are learning is PHP in an Object Oriented or OO style usage. This is important since PHP is shifting from procedural to Object Oriented and its important to learn the concepts early. All objects function in a similar way to how you have it here. When I've got the driver uploaded, it will be a lot easier, something more like this:


$storage = Storage::getInstance('XML', './storageDir');
$aValues = array(
'courseID' => $courseID,
'courseName' => $courseName,
'courseVender' => $courseVender,
'courseType' => $courseType,
'courseObjective' => $objective,
'courseDescription' => $description,
'courseKeyTopics' => $keyTopics,
);
$storage->insert($aValues, 'classes');

Retrieval, deletion and alterations will be just as similar. The purpose is to closely relate to a database for simplicity. This will allow me to create objects which do not care about storage engine and still handle all the necessary functionality.

cdn2005
08-15-2008, 06:23 AM
Thank you for the reply. Sorry for the too many qustions.
I hope you won't give up on me now.

Okay now changed the code as follows:

$storage = Storage::getInstance('XML', './storageDir');
$aValues = array(
'courseID' => $courseID,
'courseName' => $courseName,
'courseVender' => $courseVender,
'courseType' => $courseType,
'courseObjective' => $objective,
'courseDescription' => $description,
'courseKeyTopics' => $keyTopics,
);
$storage->insert($aValues, 'classes');

$element = $dom->createElement('courseID');
$data = $dom->createCDATASection($courseID);
$element->appendChild($data);
$record->appendChild($element);

$element = $dom->createElement('courseName');
$data = $dom->createCDATASection($courseName);
$element->appendChild($data);
$record->appendChild($element);

And I get the error
Fatal error: Class 'Storage' not found in /www/hsweb/training/DOM_Examples/saveFile.php on line 11

I feel like walking blind folded now... stepping in to the unknown... one step at a time...

How does it know that it has to capture the $_POST data?

And is ./StorageDir an directory that I have to create?

I added this line, but didn't help:

$storage = $dom->createElement('storage');


Please remember that I do not have a lot of exposure to XML programming, so if you could add more comments to your code, it would be extremely helpful at this stage. I really appreciate your help and patience. This would be a real joy to see this code working. Wow... an XML code!

Fou-Lu
08-16-2008, 05:00 AM
That code is how my driver works - this code:


$storage = Storage::getInstance('XML', './storageDir');
$aValues = array(
'courseID' => $courseID,
'courseName' => $courseName,
'courseVender' => $courseVender,
'courseType' => $courseType,
'courseObjective' => $objective,
'courseDescription' => $description,
'courseKeyTopics' => $keyTopics,
);
$storage->insert($aValues, 'classes');

Will produce the exact same results as what you are doing right now. Storage does not exist as a built in PHP class, so this will have to wait for a driver. It was a demonstration to show the simplification to what you're producing right now. I'm struggling with a LIKE syntax on it, so I'll need to check in with the XML forum after to make a posting about that. It was simply to show a comparison, what you will be able to do with a driver versus actually writing the code out. They function identically, but the driver is generic so it handles whatever data you throw at it.

cdn2005
08-16-2008, 05:07 AM
So I suppose what you are saying is that I could replace all of the entries like


$element = $dom->createElement('courseName');
$data = $dom->createCDATASection($courseName);
$element->appendChild($data);
$record->appendChild($element);

$element = $dom->createElement('courseVendor');
$data = $dom->createCDATASection($courseVendor);
$element->appendChild($data);
$record->appendChild($element);

with something simple like this, if we had a storage class available.


$storage = Storage::getInstance('XML', './storageDir');
$aValues = array(
'courseID' => $courseID,
'courseName' => $courseName,
'courseVender' => $courseVender,
'courseType' => $courseType,
'courseObjective' => $objective,
'courseDescription' => $description,
'courseKeyTopics' => $keyTopics,
);
$storage->insert($aValues, 'classes');


So i suppose there was no point in me struggling to get ehat code working, since I was already missing the storage class. Sorry, I didn't realize that it was class... my mistake!

I tried adding this line, but didn't help:

$root->appendChild($storage);
$dom->save('classes.xml')
So I will wait for your driver to be ready. can I assume that you would share the driver with me as well.

cdn2005
08-16-2008, 05:34 AM
Please ignore the previous message... upon closer reading of your previous message, it became more clear to me now. So I made the required changes to read thte data from $_POST for my purpose and it is now saving the data to the file.

But one minor problem, it keeps overwriting the same record. When I run the script a second time, it doesn't create a new record, it just writes over the old record.

I think I have to append to the file instead of straight save. Hmmm....

Fou-Lu
08-16-2008, 06:07 AM
Thats correct. Its actually overwriting the entire file, not just the single record.
To append to an XML dom, you need to load and retrieve the DOM Root Element.
I'm pretty certain its $dom->documentElement, but I'll check to confirm for you (just checked, it is documentElement, I'm used to xpath usage).
Here is the basic process. You load the xml file, grab the document root element, and continue writing.


<?php
$dom = new DOMDocument('1.0', 'UTF-8');
$filename = 'yourfile.xml';
$root = $dom->createElement('classes');
if ($dom->load($filename)) // May trigger/throw error, supress with @ if necessary
{
// DocumentElement is a non-standard DOM accessor. Its easy to use though :)
$root = $dom->documentElement;
}


// You're processing goes here, with the createElements:
$record = $dom->createElement('record');
$element = $dom->createElement('courseID');
$data = $dom->createCDATASection($courseID);
$element->appendChild($data);
$record->appendChild($element);

$element = $dom->createElement('courseName');
$data = $dom->createCDATASection($courseName);
$element->appendChild($data);
$record->appendChild($element);

// And so forth...
$root->appendChild($record);
$dom->save($filename);
?>

With this you should be able to append a child to a root regardless of if the file has been created or if its been loaded.

cdn2005
08-16-2008, 06:16 AM
Yes, I read similar article elsewhere also. So, basically what needs to happen is that I have to read my document in to memory and then append new record to it. Hmm... that seems odd... wouldn't it make things run slow, if my file is really really huge with thousands of records?

I suppose it would be the same idea to edit an existing record as well, which is the next task I have to tackle.

Fou-Lu
08-16-2008, 06:40 AM
To be honest, I've always thought so myself. I think I mentioned somewhere in this thread that I haven't tested with 1000's of records yet, but so far the files are beating the database by way of speed. I'll also test the memory usage after. My understanding is that files can be handled in two ways, one is by loading the entire file into memory, and the other is loading just pointers to the file and location into the memory. This takes longer to process overall but doesn't consume tons of memory.
I have yet to develop any applications which could actually top off these files to a point they would actually crush themselves.
I'll post back when I've run some memory tests on this to see if its fully loaded or only partially loaded into memory.


Oh yes. Editing you're records is a little trickier. For that, we'll be needing DOMXPath to easily target you're desired record. I'll show you that as well, its fairly simple

cdn2005
08-16-2008, 06:48 AM
Yes, the surprising part is thatwe can not simply append to a file using a command just as we could with regular file commands, like:

$f=fopen("./courseDetails.txt","a");

With today's computer speeds and memory availability, reading an entire file may not cause any noticeable delay for average sized files, but the method just seems counter productive. ??

Fou-Lu
08-16-2008, 06:52 AM
Agreed. XML is more complex than a standard file though. Programming is all about weighing and sacrificing. XML is easy to store, retrieve, alter, insert and delete records from. But the price is it takes more memory. File pointing is faster and consumes less memory but requires far more processing instruction to manipulate.
This is why we like databases :)

cdn2005
08-18-2008, 03:33 PM
Hope u had a nice weekend. I tried diff things over the weekend to get it it to append data to the file, but with my limited knowledge I was only making things worse.

As suggested, I loaded the XML, grabbed the root and wrote the new data... it will work for one set of new data, but when I tried to write a third record, it complains... because I think it can only still read in the first record and it sees the line after the first record as a error.

How is your File Driver coming along. Is there enough code that I can take a look at or familiarize with?

Fou-Lu
08-18-2008, 10:28 PM
Yeah, its almost done. Just finishing up with the conditional commands, specifically adding the NOT parameter. Also considering adding some meta-data to handle the primary surrogate keys. I also need to change the sorting algorithm, its currently an O(n^2), so I will change it to either logrithmic if I can (methinks its not possible with how this is handled), or a O((n-1) / 2) to make it more efficient.
Heres the driver so far. This won't work by itself, but you can at least see what it is:


<?php

$preXMLFileDriverWD = getcwd();
chdir(dirname(__FILE__));
require_once '../Storage.class.php';
require_once '../../Exceptions/IllegalArgumentException.class.php';
chdir($preXMLFileDriverWD);
unset($preXMLFileDriverWD);

/*
* We will be using DOMDocument and DOMXPath since I'm too lazy to develop
* my own DOM, so we need to ensure these classes are available.
*/
if (!class_exists('DOMDocument'))
{
throw new NoClassDefFoundError('Class DOMDocument cannot be found!');
}

if (!class_exists('DOMXPath'))
{
throw new NoClassDefFoundError('Class DOMXPath cannot be found!');
}

/**
* Create storage driver for XML Files. Relies on DOMDocument and DOMXPath
* system objects to function.
* Currently does not support a schema/dtd, table style meta-data (surrogates,
* nulls, defaults, etc). These will be implemented in version 2 of this
* driver class.
*
* @author Kevin Simpson
* @copyright 2008
* @version 1.7
*/
final class XMLDriver extends Storage
{
protected $sStorageLoc;
protected $iNextSurrogate;

/**
* Get the location of the storage directory
*
* @return String The storage directory location
*/
public function getStorageLoc()
{
return $this->sStorageLoc;
}

/**
* Set the storage location directory
*
* @param String $storageLoc The location of the storage
*/
public function setStorageLoc($storageLoc)
{
if (!is_dir($storageLoc))
{
throw new IllegalArgumentException(sprintf("Storage location '%s'" .
" is not a valid location!", $storageLoc));
}
$this->sStorageLoc = realpath($storageLoc);
}

/**
* Generate an accurate path to the storage files
*
* @param String $file The name of the file
* @return String The path to the file
*/
private function genStorageFile($file)
{
return rtrim($this->sStorageLoc, ' / ') . '/' . $file . '.xml';
}

/**
* Normalize the WHERE condition to handle for XPath. We will support only
* IN, BETWEEN and LIKE.
*
* @param string $condition The condition to normalize
* @return string The normalized XPath string.
*/
private function normalizeWhere($condition)
{
if (strpos($condition, 'LIKE') !== false)
{
$match = array();
if (!preg_match("/^([a-z0-9]+) LIKE (.*)$/i", $condition, $match))
{
$this->halt(0, sprintf("There is an error in you're " .
"syntax near %s", $condition));
}

$field = $match[1];
$param = trim($match[2], "\"' ");
if (preg_match('/^%(.*)%$/', $param))
{
// For LIKE %a% comparison
$condition = 'contains(' . $field . ', \'' .
trim($param, '%') . '\')';
}
if (preg_match('/^%([^%]*)$/', $param)) // Match end
{
// For LIKE a% Comparison
// There is no ends-with in xpath 1.0, so lets figure out
// something different to make this work...
$paramLength = strlen($param);
$condition = 'contains(substring(' . $field .
', string-length(' . $field . ') - ' . $paramLength .
'), \'' . trim($param, '%') . '\')';
}
if (preg_match('/^([^%]*)%$/', $param))
{
// For LIKE %a Comparison
$condition = 'starts-with(' . $field . ', \'' .
rtrim($param, '%') . '\')';
}
}
if (strpos($condition, 'IN') !== false)
{
$match = array();
if (!preg_match("/^([a-z0-9]+) IN \((['|\"].+['|\"])\)$/i", $condition, $match))
{
$this->halt(0, sprintf("There is an error in you're " .
"syntax near %s", $condition));
}
$condition = '';
foreach (explode(', ', $match[2]) AS $cond)
{
$condition .= $match[1] . '=' . $cond . ' or ';
}
$condition = rtrim($condition, ' or ');
}
if (strpos($condition, 'BETWEEN') !== false)
{
$match = array();
if (!preg_match('/^([a-z0-9]+) BETWEEN (.*) AND (.*)$/i',
$condition, $match))
{
$this->halt(0, sprintf("There is an error in you're " .
"syntax near %s", $condition));
}

$condition = '';
$field = trim($match[1], "\"'");
$cond1 = trim($match[2], "\"'");
$cond2 = trim($match[3], "\"'");
$condition = $field . '>=\'' . $cond1 . '\' and ' . $field .
' <= \'' . $cond2 . '\'';
}
return $condition;
}

/**
* Swap one node for another.
*
* @param DOMNode $childA The first node to swap
* @param DOMNode $childB The second node to swap
*/
private function swap(DOMNode $childA, DOMNode $childB)
{
$aFields = $childA->childNodes;
$bFields = $childB->childNodes;

$tmpAFields = $childA->cloneNode(true)->childNodes;
$tmpBFields = $childB->cloneNode(true)->childNodes;


for ($i = 0; $i < $aFields->length; $i++)
{
$childA->replaceChild($tmpBFields->item($i)->cloneNode(true),
$aFields->item($i));
}

for ($i = 0; $i < $bFields->length; $i++)
{
$childB->replaceChild($tmpAFields->item($i)->cloneNode(true),
$bFields->item($i));
}
}

/**
* Perform sort to order result sets.
*
* @param DOMNodeList $resource The resultset resource
* @param string $order The field to order on
* @param int $direction The direction to order by
*/
private function sortResults(DOMNodeList &$resource, $order, $direction)
{
for ($i = 0; $i < $resource->length; $i++)
{
for ($j = $resource->length - 1; $j > $i; $j--)
{
$childA = null;
$childB = null;
foreach ($resource->item($i)->childNodes AS $firstChild)
{
if (strcasecmp($firstChild->nodeName, $order) == 0)
{
$childA = $firstChild;
break;
}
}
foreach ($resource->item($j)->childNodes AS $secondChild)
{
if (strcasecmp($secondChild->nodeName, $order) == 0)
{
$childB = $secondChild;
break;
}
}

if ($childA === null || $childB === null)
{
throw new IllegalArgumentException('Unknown attribute ' .
$order . ' in ' . __METHOD__);
}
if (strcasecmp($childA->nodeValue, $childB->nodeValue) < 0
&& $direction == self::DIR_DESC)
{
$this->swap($resource->item($i), $resource->item($j));
}
if (strcasecmp($childA->nodeValue, $childB->nodeValue) > 0 &&
$direction == self::DIR_ASC)
{
$this->swap($resource->item($j), $resource->item($i));
}
}
}
}

/***************************************************************************
* IFactory Methods
**************************************************************************/

/**
* @see IFactory::getInstance
*/
public static function getInstance()
{
$argv = func_get_args();
$class = str_replace('Driver', '', __CLASS__);
$parent = get_parent_class(__CLASS__);
array_unshift($argv, $class);
return call_user_func_array(array($parent, 'getInstance'), $argv);
}

/***************************************************************************
* IMStorage Methods
**************************************************************************/
/**
* @see IMStorage::setConfigs
*/
public function setConfigs()
{
$argv = func_get_args();
$argc = count($argv);
if ($argc != 1)
{
throw new Exception(__CLASS__ . ' requires a storage location!');
}
$this->setStorageLoc($argv[0]);
}

/**
* @see IMStorage::open
*/
public function open()
{
if (!class_exists('DOMDocument'))
{
throw new NoClassDefFoundError('DOMDocument was not found!');
}
$this->obHandle = new DOMDocument('1.0', 'UTF-8');
$this->obHandle->formatOutput = true;
$this->obHandle->preserveWhiteSpace = false;
$this->isOpen = true;
}

/**
* @see IMStorage::close
*/
public function close()
{
if ($this->isOpen())
{
$this->obHandle = null;
$this->isOpen = false;
}
}

/**
* @see IMStorage::select
*/
public function select(array $fields, $from, $condition = null,
$order = null, $direction = self::DIR_ASC)
{
$sysOpen = false;
$fromFile = $this->genStorageFile($from);
if (!file_exists($fromFile))
{
$this->halt(0, sprintf("File %s does not exist!", $from));
}
$condition = $this->normalizeWhere($condition);

if (!$this->isOpen())
{
$this->open();
$sysOpen = true;
}

$start = microtime(true);
$this->obHandle->load($fromFile);
$xpath = new DOMXPath($this->obHandle);
$query = '//row';
if ($condition !== null)
{
$query .= sprintf("[%s]", $condition);
}
$result = $xpath->query($query);

if ($order !== null)
{
try
{
$this->sortResults($result, $order, $direction);
}
catch (IllegalArgumentException $ex)
{
$this->halt($ex->getCode(), $ex->getMessage());
}
}

if (count($fields) > 1 || (count($fields) == 1 && $fields[0] != '*'))
{
for ($i = 0; $i < $result->length; $i++)
{
$aToRemove = array();
$childNodes = $result->item($i)->childNodes;

// Ok, we can't just remove from here, we'll need
// reference a child to a new array, and remove it after
// this iteration. Seems a little silly.
foreach ($childNodes AS $child)
{
if (!in_array($child->nodeName, $fields))
{
$aToRemove[] = $child;
//$cpyResult->item($i)->removeChild($child);
}
}

foreach ($aToRemove AS $remove)
{
$result->item($i)->removeChild($remove);
}
}
}

$this->iQryTime += microtime(true) - $start;
$this->iQryCount++;
if ($sysOpen)
{
$this->close();
}
return $result;
}

/**
* @see IMStorage::insert
*/
public function insert(array $fields, $to)
{
$toFile = $this->genStorageFile($to);
$sysOpen = false;
if (!file_exists($toFile))
{
$this->halt(0,sprintf("File %s does not exist!", $to));
}
if (!$this->isOpen())
{
$this->open();
$sysOpen = true;
}
$this->obHandle->load($toFile);
$doc = $this->obHandle->documentElement;
if ($doc->hasAttribute('nextSurrogate'))
{
$this->iNextSurrogate = $doc->getAttribute('nextSurrogate');
$doc->setAttribute('nextSurrogate', ($this->iNextSurrogate + 1));
}

$row = $this->obHandle->createElement('row');
foreach ($fields AS $key => $val)
{
$item = $this->obHandle->createElement($key);
$data = $this->sanitize($val);
$item->appendChild($data);
$row->appendChild($item);
}
$doc->appendChild($row);
$this->obHandle->save($toFile);
if ($sysOpen)
{
$this->close();
}

}

/**
* @see IMStorage::update
*/
public function update(array $fields, $to, $condition = null)
{
$toFile = $this->genStorageFile($to);
$sysOpen = false;
if (!file_exists($toFile))
{
$this->halt(0, sprintf("File %s does not exist!", $to));
}
if (!$this->isOpen())
{
$this->open();
$sysOpen = true;
}
$start = microtime(true);
$selList = $this->select(array('*'), $to, $condition);

for ($i = 0; $i < $selList->length; $i++)
{
foreach ($fields AS $fldKey => $fldVal)
{
$newElem = $this->obHandle->createElement($fldKey);
$newData = $this->sanitize($fldVal);
$newElem->appendChild($newData);
$oldElem = $selList->item($i)->getElementsByTagName($fldKey);
$selList->item($i)->replaceChild($newElem, $oldElem->item(0));
}
}

$this->obHandle->save($toFile);
$this->iQryTime += microtime(true) - $start;
$this->iQryCount++;
if ($sysOpen)
{
$this->close();
}
}

/**
* @see IMStorage::delete
*/
public function delete($from, $condition = null)
{
$fromFile = $this->genStorageFile($from);
$sysOpen = false;
if (!file_exists($fromFile))
{
$this->halt(0,sprintf("File %s does not exist!", $from));
}

if (!$this->isOpen())
{
$this->open();
$sysOpen = true;
}

$start = microtime(true);
$this->obHandle->load($fromFile);

$selList = $this->select(array('*'), $from, $condition);
for ($i = 0; $i < $selList->length; $i++)
{
$parent = $selList->item($i)->parentNode;
$parent->removeChild($selList->item($i));
}


$this->obHandle->save($fromFile);
$this->iQryTime += microtime(true) - $start;
$this->iQryCount++;
if ($sysOpen)
{
$this->close();
}
}

/**
* @see IMStorage::sanitize
*/
public function sanitize($value)
{
$cdata = $this->obHandle->createCDATASection($value);
return $cdata;
}

/**
* @see IMStorage::countResults
*/
public function countResults($resObj)
{
return $resObj->length;
}

/**
* @see IMStorage::fetchArray
*/
public function fetchArray($resource, $type = self::TYPE_BOTH)
{
static $iLoc = 0;
static $obLastResource = null;
if ($resource !== $obLastResource)
{
$obLastResource = $resource;
$iLoc = 0;
}
$aResult = array();
$aItem = $resource->item($iLoc++);
if ($aItem == null)
{
$iLoc = 0;
$obLastResource = null;
$aResult = false;
}
else
{
foreach ($aItem->childNodes AS $val)
{
switch($type)
{
case self::TYPE_NUM:
$aResult[] = $val->nodeValue;
break;
case self::TYPE_ASSOC:
$aResult[$val->nodeName] = $val->nodeValue;
break;
case self::TYPE_BOTH:
$aResult[] = $val->nodeValue;
$aResult[$val->nodeName] = $val->nodeValue;
break;
}
}
}

return $aResult;
}

/**
* @see IMStorage::fetchRow
*/
public function fetchRow($resource)
{
return $this->fetchArray($resource, self::TYPE_NUM);
}

/**
* @see IMStorage::fetchAssoc
*/
public function fetchAssoc($resource)
{
return $this->fetchArray($resource, self::TYPE_ASSOC);
}

/**
* @see IMStorage::fetchObject
*/
public function fetchObject($resource)
{
static $iLoc = 0;
static $obLastResource = null;
if ($resource !== $obLastResource)
{
$obLastResource = $resource;
$iLoc = 0;
}
$objResult = new stdClass();
$aItem = $resource->item($iLoc++);
if ($aItem == null)
{
$iLoc = 0;
$objResult = false;
}
else
{
foreach ($aItem->childNodes AS $children)
{
$objResult->{$children->nodeName} = $children->nodeValue;
}
}

return $objResult;
}
}
?>

cdn2005
08-19-2008, 12:47 AM
That's great. I will start playing with it a bit. I think, if I can get those include files, that would help. But no rush, if you are busy working on it.

Meanwhile, I played with my old code.
I have this now, and get the following error:

$dom = new DOMDocument('1.0', 'UTF-8');
$filename = 'classes.xml';
$root = $dom->createElement('classes');
$dom->formatOutput = true;
$dom->preserveWhiteSpace = false;
if ($dom->loadXML($filename)) // May trigger/throw error, supress with @ if necessary
{
// DocumentElement is a non-standard DOM accessor. Its easy to use though :)
//$root = $dom->documentElement;
$root = $dom->getElementsByTagName('classes')->item(0);
}

// Ok, lets work as if we're on a fresh file:
//$root = $dom->createElement('classes');
$dom->appendChild($root); // Place the classes element into the dom
$record = $dom->createElement('record'); // Create a record element.
$storage = $dom->createElement('storage');


error:


Warning: DOMDocument::loadXML() [function.loadXML]: Start tag expected, '<' not found in Entity, line: 1 in /www/hdsweb/silicontraining/DOM_Examples/xml_saveFile.php on line 6

cdn2005
08-19-2008, 12:52 AM
IGNORE....IGNORE.... Ignore the last message.... I just removed XML from loadXML and it is WORKING now.... WOW!

Reached another milestone... Now I can write more than one record to the file.

Now I have to figure out how to display one record based on a keyvalue like CourseID, how to edit one item etc. Oh, man!

Fou-Lu
08-19-2008, 05:14 AM
Look into the select method on the driver. It shows how to retrieve records. I've changed the normalizeCondition method to be more efficient, and I'm implementing a quicksort to control the ordering to increase the efficiency of the sorting. I also moved the $condition = $this->normalizeWhere call in the select to a part of the $condition !== null block (otherwise it seeks a condition and causes a formatting error).

cdn2005
08-19-2008, 02:44 PM
Okay... I will start trying few things. Meanwhile, can you also send me the following two files so that I can start playing with your file Driver code, and any updates on the driver itself, if you made changes. Looking forward to seeing that code working.


require_once '../Storage.class.php';
require_once '../../Exceptions/IllegalArgumentException.class.php';

Can you please include the code after you have changed the normalizeCondition method and after adding the quicksort method, as mentioned in your last reply.

cdn2005
08-26-2008, 07:30 AM
Hi there... how is things? how is ur work on the File driver coming along? any chance I could get those require_once files indicated above or any new updates on the scripts itself.

Fou-Lu
08-26-2008, 06:11 PM
Its close enough for this version. Just extract the files to you're root http directory.
No advanced querying and only single conditionals are handled. Do not allow straight user choice for conditionals as its still open to xpath injections. Example file is included showing how to instantiate the storage and insert, update, delete and select. It does handle BETWEEN, LIKE and IN for its conditionals, including NOT. Since this version does not support schema comparisons, it technically means you can dynamically switch you're tables, which I don't really recommend doing. You must create you're own xml files for you're table representations including the correct format, otherwise they will not parse on load. Just copy and paste the test.xml file for this purpose.
You can use this for personal use. You may not use this for monetary gain. That said, enjoy the easy XML handling.

cdn2005
08-26-2008, 09:36 PM
Thank you so much for the files. No, I certainly won't be using it for any monetary gain. With my level of knowledge in XML, it would be suicidal for me to offer any such capability.

Pardon my ignorance, I downloaded the files and tried the example.php. Well, it shows the following items on the screen:

Selecting all records...
Array ( [id] => 1 [text] => This is a test for 1 )
Array ( [id] => 2 [text] => This is a test for 2 )
Array ( [id] => 3 [text] => This is a test for 3 )
Array ( [id] => 4 [text] => This is a test for 4 )

Selecting all records (DESC by ID)...
Array ( [id] => 4 [text] => This is a test for 4 )
Array ( [id] => 3 [text] => This is a test for 3 )

It looks to me, that it is showing the different selections, updates etc that I can perform. I am struggling to figure out how to tie this with my online form to enter data, and have a button to edit the contents, delete records, and display selected record etc.

It would be helpful, if you can give me a one liner description for all the files in your storage.zip, so that I can see the links or how it works together. For convenience I will list all the files here:


./fileDriver_01.php = this is original script that you send me. It does not seem to doing anything when I try to run it now.
./Logs
./Logs/makezipshappy.txt
./OO
./OO/Abstracts
./OO/Abstracts/Object.class.php
./OO/Exceptions
./OO/Exceptions/CustomException.class.php
./OO/Exceptions/IllegalArgumentException.class.php
./OO/Exceptions/NoClassDefFoundError.class.php
./OO/Exceptions/StorageRequiredException.class.php
./OO/Interfaces
./OO/Interfaces/IFactory.class.php
./OO/Interfaces/IMStorage.class.php
./OO/Singleton.class.php
./OO/Storage
./OO/Storage/Drivers
./OO/Storage/Drivers/XMLDriver.class.php
./OO/Storage/Storage.class.php
./Storage
./Storage/test.xml
./classes.xml
./xml_enterCourseData.html
./xml_saveFile.php



Also attached is the directory structure as I have, including my original online form.

Fou-Lu
08-26-2008, 11:38 PM
I don't really have time to write a userguide for this atm, so I'll just try to explain it. I'll try to explain it with how it would work with you're code as well.

The Storage is used as an abstraction layer. I specifically use it for n-tier style programming, relating one object to one database table (or xml file for the XML Driver). This is why it only performs simplistic selections instead of complex querying. The idea is now my n-tier objects don't care where they come from or how they are stored, the drivers handle all of those details.

Now, selections are simple. All you need to do is tell it what you want to select as an array, or * for all. Also tell it where you want it to come from (in the case of the XMLDriver, it refers to a file). You can select each item and display it to the user.
Here is how you instantiate it:


try
{
$obStorage = Storage::getInstance('XML', '/path/to/storage');
}
catch(Exception $ex)
{
die($ex->getMessage());
}

Here is how to select records. Optional parameters are conditions, ordering field and direction:


// Selections can take array('*') for all fields
$aFields = array(
'courseID',
'courseName',
);
$from = 'classes';
$obQuery = $obStorage->select($aFields, $from);

To iterate you're records, you have a recordset pointer, similar to SQL handling. Available options are fetchRow, fetchAssoc, fetchArray (with optional types row, assoc and both, defaults to both), fetchObject.


while ($record = $obStorage->fetchAssoc($obQuery))
{
// Do whatever with each individual record.
}

Insertions are also very easy:


$aFields = array(
'courseID' => $courseID,
'courseName' => $courseName,
...
);
$to = 'classes';
$obStorage->insert($aFields, $to);

Updates ($obStorage->update(...)) are identical to insertions, but also handle a condition. If no condition is supplied, it updates all records. Deletions ($obStorage->delete(...)) require a location to delete from (classes) and an optional condition. If no condition is given, deletes all records.

Since I use these in an object context it is very simple for me to insert/update. Since I encapsulate a record as an object I can determine if it has been loaded or not and a simple call to a save method allows it to dynamically choose which to perform. The idea for how I would do what you have would be something like (pseudo code style):


if form posted then:
if editID exists then:
updateRecord WHERE courseID = editID with values
else
insertRecord with values
endif;
endif;

if get action is add:
show add record form
endif;
if get action is edit:
// Exactly the same as add, but also have a hidden field for the value
get record from storage
show edit record form
endif;
if get action is list:
get all records and display links
endif;

Think simplistically. You no longer need to create DOMDocument handling of you're own, the driver takes are of that. You're primary file (minus the html code) is only going to be about 25 lines of code or so.

cdn2005
08-27-2008, 07:02 PM
Thank you for your reply again. I tried to modify my code to work with this engine. In your example.php, you are actually using data from a table that was created on the fly. I am trying to instead use data from my classes.xml file, and not sure exactly how to go about it. Thus far I have the following code. How do I properly open the existing classes.xml file, load the data (all or selected based on keyword) and add new record or manipulate existing.

The code is not giving me any errors, but it is clearly not seeing classes.xml

fd_saveFile.php

<?php
$preObjectWD = getcwd();
chdir(dirname(__FILE__));
require_once './OO/Storage/Storage.class.php';
print ($preObjectWD);

$filename = 'classes.xml';
$table = 'classes';
$obStorage = null;
try
{
$obStorage = Storage::getInstance('XML', './Storage');
$sel = $obStorage->select(array('*'), $table);
while ($record = $obStorage->fetchAssoc($sel))
{
print_r($record);
print "<br />\n";
}

}
catch(Exception $ex)
{
die($ex->getMessage());
}
?>

And then I modified xml_enterCourseData.html as follows:

<form action="fd_saveFile.php" method="POST" name="form1" id="form1">


I can almost see the light at the end of the tunnel, but I think I will need a few more hints from you to make it there. :)

Fou-Lu
08-27-2008, 07:32 PM
I should have mentioned this in my last post. You're xml format is like so:


<?xml version="1.0" encoding="UTF-8"?>
<classes>
<record>...
</classes>

The engine stores as 'row' instead of a 'record'. The root is really irrelevant since it performs lookups based on any existent of record. You can either change it in the driver itself to look for //record, or you can rename record to row with a search/replace. That will let you're selections work. Part of this is because I do not support schemas which can be used for force specifications for each entry.

You're classes.xml file needs to be located in the directory stated during the XMLDriver construction. $obStorage = Storage::getInstance('XML', './Storage'); places the ./Storage directory as the location for these files. Move classes.xml into ./Storage.

Save file I assume is to update/insert records correct? In this case you don't need to select all of you're records. What you do is like so:


<?php

require_once './OO/Storage/Storage.class.php';
$table = 'classes';
$obStorage = null;

try
{
$obStorage = Storage::getInstance('XML', './Storage');
}
catch(Exception $ex)
{
die($ex->getMessage());
}

if (isset($_POST['submit'])) // Or whatever you do to check for POST
{
// Extract each of you're values and perform you're validation on them.
// ...
// I would have a hidden field passed if its to be edited. Something like:
$originalCourseID = $_POST['originalCourseID'];
$aFields = array(
'courseID' => $courseID,
'courseName' => $courseName,
...
);
$chkRecord = $obStorage->select(array('*'), $table, 'courseID = \'' . $originalCourseID . '\'');
if ($obStorage->countResults($chkRecord) == 0)
{
// Insert record
$obStorage->insert($aFields, $table);
}
else
{
$obStorage->update($aFields, $table, 'courseID = \'' . $originalCourseID . '\'');
}
}

HTML would be something like:


<form action="fd_saveFile.php" method="POST" id="form1">
<fieldset>
<!-- If its an editing record: -->
<input type="hidden" name="originalCourseID" value="$theEditingID" />
<label for="txtCourseID">Course ID:</label>
<input type="text" name="txtCourseID" id="txtCourseID" value="$populateCourseID" />
.....

As I mentioned, this driver version is XPath injectionable, specifically through the conditions (any input records are considered character data only, so they cannot be parsed by the XPath processor). So, you may want to consider doing a check on the $originalCourseID and removing any existance of non-alphanumeric characters, a simple preg for [a-z0-9] or ctype_alnum can check for these.

cdn2005
08-29-2008, 07:09 PM
I made some scripts to handle few things that I started off at the beginning. Nothing to boast about, and I have stumbled up on couple of things. Something probably very obvious to you.

How do I display the data in a row without the array id etc. I wrote a script that will display this: [Edit?] [Delete?] are buttons


Display all available Course IDs...
Array ( [courseID] => Course 01 ) [Edit?] [Delete?]
Array ( [courseID] => Course 02 ) [Edit?] [Delete?]
Array ( [courseID] => Course 03 ) [Edit?] [Delete?]
Array ( [courseID] => Course 04 ) [Edit?] [Delete?]
Array ( [courseID] => Course 05 ) [Edit?] [Delete?]


... but like to display it as

Display all available Course IDs...
Course 01 [Edit?] [Delete?]
Course 02 [Edit?] [Delete?]
Course 03 [Edit?] [Delete?]
Course 04 [Edit?] [Delete?]
Course 05 [Edit?] [Delete?]


Secondly, the $courseID is not getting passed to the fd_processor.php script from the form. What is missing?

Here is the code:
fd_editSelect.php

<?php
require_once './OO/Storage/Storage.class.php';
$table = 'classes';
$obStorage = null;

try
{
$obStorage = Storage::getInstance('XML', './Storage');
}
catch(Exception $ex)
{
die($ex->getMessage());
}


?>
<form action="fd_processor.php" method="POST" name="form1" id="form1">
<?php
print("<br />\nDisplay all available Course IDs...<br />\n");
$sel = $obStorage->select(array('courseID'), $table);
while ($record = $obStorage->fetchAssoc($sel))
{
print_r($record);
?>
<input type="hidden" name="editCourseID" value="<?php echo $record; ?>">
<input type="submit" name="edit" value="Edit?">
<input type="submit" name="delete" value="Delete?">

<?php
print "<br />\n";
}
?>
</form>

fd_processor.php

<?php

// check which button was clicked
// perform calculation
$courseID = $_POST['originalcourseID'] ;
if ($_POST['delete'])
{
echo "Do you want to delete?"; }
else if ($_POST['edit']) {
echo "Ready to edit" . $courseID; }

?>

Fou-Lu
08-30-2008, 07:59 AM
The display part is easy. I haven't looked at the script part yet, but if it ever shows 'Array' with actual data, you're print_r'ing the data. Instead, access it by its index: $record['courseID'].

Now, I'm actually looking at the code, lol. The above is exactly what you need to use. $record is a resultset row that you've chosen to fetch as an associative array (just an FYI, there is also fetchRow, fetchObject, and fetchArray available, though generally fetchAssoc is what you'll likely want to use). So, the same applies to the above indexing:


<input type="hidden" name="editCourseID" value="<?php echo $record; ?>" />
// Change to:
<input type="hidden" name="editCourseID" value="<?php echo $record['courseID'];?>" />


Remember, since this is designed as an abstract way of collecting you're data, you can provide 'querylike' lookups. So, if you wanted to display something different you can always alter you're search parameters (infact, with the XML Driver it is more efficient to select all fields, but only with the XMLDriver, not the SQLDriver).


<?php
// ...
// Lets fetch the ID and name
$sel = $obStorage->select(array('courseID', 'courseName'), $table);
while ($record = $obStorage->fetchAssoc($sel))
{
print $record['courseName'];
?>
<input type="hidden" name="editCourseID" value="<?php echo $record; ?>" />
<input type="submit" name="edit" value="Edit?" />
<input type="submit" name="delete" value="Delete?" />
<?php
}

This will show the course name instead, but still map the courseID to the editCourseID input - generally ID's (especially numeric ones) are used to identify an item, related to the databases Primary Key in general.

Hope that helps!

cdn2005
09-10-2008, 02:46 PM
Hi, I was doing something else for the last few days, but now trying to get back at this and see if I can do what I was trying to accomplish at the beginning. Meanwhile, I want to really thank you for your support and your patience in staying through this exercise. Not many would do that. You have a real heart to help and I appreciate that. You have helped a lot more than what I had anticipated at the beginning. Thank you so much. I will keep you posted with my progress.

Now I am trying to create a page for editing existing data. At some point I would like some help on some of the commands that you have used like:

$obStorage = null;
try
{
$obStorage = Storage::getInstance('XML', './Storage');


$sel = $obStorage->select(array('*'), $table, 'id NOT IN (\'1\', \'4\')');
... what is the role of $obStorage, the reason for providing ('XML', './Storage') etc.

and how do you prepopulate a form with data from existing rows, instead of using

while ($record = $obStorage->fetchAssoc($sel))
{
print_r($record);
print "<br />\n";
}

My Attempt has been to prepopulate the edit field as follows, but I am sure what I have is wrong:

fd_processor.php

<?php
require_once './OO/Storage/Storage.class.php';
$table = 'classes';
$obStorage = null;


try
{
$obStorage = Storage::getInstance('XML', './Storage');
}
catch(Exception $ex)
{
die($ex->getMessage());
}
?>
<form action="fd_saveFile.php" method="POST" name="form1" id="form1">
<?php
if ($_POST['delete'])
{
echo "Are you sure you want to delete " . $_POST['editCourseID']; }
else if ($_POST['edit']) {
echo "You can now edit " . $_POST['editCourseID'];
$sel = $obStorage->select(array('editCourseID'), $table); //select items by CourseID
while ($record = $obStorage->fetchAssoc($sel))
{
?>
Course ID <br />
<input type="text" name="editCourseID" value="<?php $editCourseID ?>" /><br />
Course Name<br />
<input type="text" size="90" name="courseName" value="<?php $courseName?>" /><br />
Course Vendor <br />
<input type="text" name="courseVendor" value="<?php $courseVendor?>" /><br /><br />
Course Type <br />
<input type="submit" name="submit" value="Submit" />

<?php
}
?>


I think the big question is how do you display the values ONLY from a selected record.

Some documentation as time permits would be greatly helpful as well.

Fou-Lu
09-10-2008, 08:39 PM
XML states the the driver it requires is the XML driver, and the ./Storage is the location to where the files are stored. I use other storage methods, so I have drivers for different databases and other file handling techniques. It works the same above the abstraction layer, but below it the drivers all function differently to perform their required tasks. The new ones use array's for configuration information instead of strings. This lets me change these values in a config file instead of inside the actual storage instantiation.

What you are looking for is the condition, and ignoring the loop. Its the third parameter of the select statement. From what you have, it looks like the data was sent to you from the code that I had posted in the post prior to it, so I'll keep going off of that.


<?php
require_once './OO/Storage/Storage.class.php';
$table = 'classes';
$obStorage = null;
$sCondition = null;

try
{
$obStorage = Storage::getInstance('XML', './Storage');
}
catch(Exception $ex)
{
die($ex->getMessage());
}

// Now, we have an ID, we need to know what to do with it first. Lets see if its for deletion
if (isset($_POST['editCourseID']) && !empty($_POST['editCourseID']))
{
// This version does not have a generic cleaner, so we'll just addslashes it for now.
$sCondition = 'courseID = \'' . addslashes($_POST['editCourseID']) . '\'';
}
if (isset($_POST['delete']) && isset($_POST['editCourseID']))
{
// We are deleting it.
$obStorage->delete($table, $sCondition);
}
else if (isset($_POST['edit']))
{
$aResults = array(
'courseID' => null,
'courseName' => null,
'courseVendor' => null,
'courseType' => null,
'courseObjective' => null,
'courseDescription' => null,
'courseKeyTopics' => null,
);
if (isset($_POST['editCourseID']) && !empty($_POST['editCourseID']))
{
$obCourseInfo = $obStorage->select(array('*'), $table, $sCondition);
if ($obStorage->countResults($obCourseInfo) == 1)
{
$aResults = $obStorage->fetchAssoc($obCourseInfo);
}
}
$file = __FILE__;
?>
<form method="post" action="<?php echo $file;?>">
<fieldset>
<?php
if (isset($aResults['courseID']) && !empty($aResults['courseID']))
{
printf("\t\t<input type=\"hidden\" name=\"editCourseID\" value=\"%s\" />\n", $aResults['courseID']);
}
?>
<label for="txtCourseName">Course Name:</label>
<input type="text" name="txtCourseName" id="txtCourseName" value="<?php echo $aResults['courseName']; ?>" />
<!-- Insert the rest of you're fields into here -->
</fieldset>
<div>
<input type="submit" name="doUpdate" value="Update" />
<input type="reset" name="reset" value="Reset" />
</div>
</form>

<?php
}
else if (isset($_POST['doUpdate']))
{
// We have finished editing this record. Lets update it.
$aFields = array(
'courseID' => $_POST['txtCourseID'],
'courseName' => $_POST['txtCourseName'],
'courseVendor' => $_POST['txtCourseVendor'],
'courseType' => $_POST['txtCourseType'],
'courseObjective' => $_POST['txtCourseObjective'],
'courseDescription' => $_POST['txtCourseDescription'],
'courseKeyTopics' => $_POST['txtCourseKeyTopics']
);
if (!empty($sCondition))
{
$obStorage->update($aFields, $table, $sCondition);
}
else
{
$obStorage->insert($aFields, $table);
}
}
?>


Hmm, probably should have used a switch. Meh.
Anyway, edit can also be used as an add, so you can add a new button on you're list for addrecords and add it as a part of the check in the edit: if (isset($_POST['edit']) || isset($_POST['add'])) for example.

Remember that this version isn't capable of creating surrogate keys, so you need to make sure you have you're own courseID available. The insert/update commands would likely want to do some checking on you're input, making sure things are not empty and whatnots.
I haven't tested this either, nor have I put it into an ide to check its syntax, but looks about right. Make a duplicate of the xml file before you use it. As well, the field names may not match, so you may need to edit those to match yours. Those are the $aResults and $aFields variables.
Let me know if it doesn't work.

cdn2005
09-10-2008, 10:14 PM
Thanks again. It is $aFields for me. Is it just an arbitrary name that you happened to pick?

I almost got the code working. Couple of issues, when I choose "edit" the fd_processor.php containing the above code u provided kicks in and displays all the fields, but it does not show the old data in the fields to make changes to it.

Secondly when I enter an existing course ID and the rest of the data, my expectation is that it would update/overwrite the data int he storage file, but instead it is actually creating a new entry. Example, if I have a
courseID:"1234" with its
courseName: "Course1234" and then if in the edit field I enter "1234" for courseID and "New Course 1234" for courseName, it doesn't go and update the values for 1234 in the datafile, it actually creates a new entry and I end up having two entries.

courseID:"1234"
courseName: "Course1234"
courseID:"1234"
courseName: "New Course 1234"

Attached is the modified fd_processor.php called by fd_editSelect.php

Fou-Lu
09-10-2008, 11:03 PM
Yeah, thats the behaviour for it. Sounds like it can't find the courseID passed by the editCourseID. If the editCourseID exists, it should populate it and keep the data within it. Technically this lets you edit the primary key as well. It relies on the existance of editCourseID to determine if it should be populating the record with an insert or an update.

Post you're related XML file and I'll debug it to see whats up.

cdn2005
09-11-2008, 07:46 AM
Yes, I was looking to see if there is nay issues in the vway values are passed between scripts. XML file attached.

Also noticed that, with fd_editSelect.php, it always passes the value of the first item in the list, which in my case is "Course 01", regardless of which one I choose to "edit" or "delete".

I am not quite clear on how the while loop processes the daa to understand exactly what is wrong.


while ($record = $obStorage->fetchAssoc($sel))
{
print_r($record);
?>
<input type="submit" name="edit" value="Edit?">
<input type="submit" name="delete" value="Delete?">
<input type="hidden" name="editCourseID" value="<?php echo $record['courseID'];?>" />
<?php

Fou-Lu
09-11-2008, 09:40 AM
I'm sleepy, so I'll look into this tomorrow morning!

Fou-Lu
09-11-2008, 07:56 PM
Ok, I'll fix it up. The main problem is simply that the hidden field is not passed correctly. I could swear that forms were linear, but I must be mistaken. I have something that works but its very ugly so I'll need to change it up a bit :P

cdn2005
09-11-2008, 08:11 PM
Ok. It looks clean to me as well, but I am sure if it the print_r command that is meessing things up. Is there any other method to display each item so that it just displays the values instead of the field name and value.

That is, instead of displaying


Array ( [courseID] => Course 01 ) Edit? Delete?
Array ( [courseID] => Course 01 ) Edit? Delete?
Array ( [courseID] => Course 01 ) Edit? Delete?

..it should just show:


Course 01 -> Edit? Delete?
Course 01 -> Edit? Delete?
Course 01 -> Edit? Delete?

Fou-Lu
09-11-2008, 09:08 PM
Yeah, I meant the changes that I've made are ugly.
I think I'm going to create a seperate entry form for each of the inputs. I mentioned that I was pretty sure the forms were linear (so the hidden value would only make it up to the button selected), but I was wrong. The other option is to still keep a single form but pass the id through as a part of the input:


<input type="submit" name="edit[course 1]" value="Edit?" />

For example. This works but makes it harder to extract the value to edit/delete.

The print_r is a recursive print - its built to expand objects and arrays. I'll change it so it looks nicer. Its a great debugging tool, but useless for production (it takes longer to process than a print/printf).
Anyway, I'm off to fix a client's computer, but I'll finish this when I return.

Fou-Lu
09-12-2008, 09:48 PM
Mmkay, this is a little cleaner. My awesome CSS skills too, lol :D
Oh, I found an error with the driver as well, but I don't have time to fix it. If you try to sort it on an empty recordset it will terminate the app. So atm, you'll need to take the natural order thats from the xml file.

cdn2005
09-13-2008, 07:35 AM
Wow.... this is pretty impressive stuff! By the way, is this all OO programming or XML. If I were to take a course on XML, would that help in doing stuff like this?

I am consistently getting one type of error:

Warning: Cannot modify header information - headers already sent by (output started at /www/hdsweb/silicontraining/fileDriver/fd_processor.php:9) in /www/hdsweb/silicontraining/fileDriver/fd_processor.php on line 135 when I try to delete an item.

Likewise, when I edit a field, I get the edit fields where I can make changes, but when I click on Update, I get eh following message:

Warning: Cannot modify header information - headers already sent by (output started at /www/hdsweb/silicontraining/fileDriver/fd_processor.php:9) in /www/hdsweb/silicontraining/fileDriver/fd_processor.php on line 218

Both are pointing to this command:

header('Location: fd_editSelect.php?sMsg=' . urlencode($sMsg));

Also, what I had was aFields not aRecords, would that matter and should I change them all to one or the other?

Fou-Lu
09-13-2008, 10:01 AM
XML course won't help you too much. Just go to w3schools.com and look into XML there. You will learn a lot about it (and about DTD and Schema which the driver is not yet capable of handling correctly).
These last files are procedural, not OO, but the driver is OO.
I know what I did already to give you header errors. After I made it so it would work I added the output to the top of the page to make it compliant:


<!DOCTYPE...
<html ...>
<head>
...

Since thats output, it flushes it which queues it up for the browser. By this point you can no longer send headers. Thats a duh on my part.
Fix it in one of two ways. Change the direct output into storing it as a string and outputing it at the end of the file, or you can be lazy and enable output buffering.
At the very start of the processor page (won't need it in the other, there are no header() calls), simply add:


<?php
ob_start();
?>
<!DOCTYPE...

At the end you can technically add an ob_end_flush(), but its implicitly called (I'm about 90&#37; sure its implicitly called) anyway so it doesn't really matter. Thats the easiest way.
Just thought some messages would be nice :D
Works otherwise though right?


Oh yeah, did I mention before that I found a bug? Don't use an ordering since if you remove all records it tosses an exception at you. I won't bother fixing this atm, but its in the select command.
I just noticed as well that there is a debug info in the $_POST['doUpdate']. Remove the print_r($_POST), or you will still receive the header errors.
I think this is an older copy of this, I know I tested both of these and I don't have output buffering on my test environment enabled. Let me know if its broken.

Yes and you asked about the $aFields and $aRecords.
You can just leave them, they are self-sustained. You probably won't need to make any changes to what you have.

cdn2005
09-13-2008, 06:28 PM
All I did was added the "ob_start();" to top of the fd_processor.php and there was no more warnings. Hmmm.... I am not sure what just happened, but it works! I can't find where that ob_start(); function is coming from :-(

Now, I am trying to see if I can make the Course items display as a drop down list instead of each course per line, which could get very long if there are 100s of courses.

So I modified the fd_ediSelct.php as follows, but clearly, again I am off on the wrong track. The idea is that the list of courses will be displayed in a drop down list, user picks the course they want to edit or delete and proceed to fd_processor.php.

Is there anyway to sort the courseID before displaying them in the list?


<form action="fd_processor.php" method="post" id="frm_<?php echo str_replace(' ', '_', $record['courseID']); ?>">
<fieldset>
<legend><?php echo $record['courseID'] ?></legend>
<select name="editCourseID" size="1">
<option><?php
while ($record = $obStorage->fetchAssoc($sel))
{
echo $record['courseID']; ?></option><?php echo $record['courseID'];
}
?>
</select>
<input type="submit" name="edit" value="Edit?" />
<input type="submit" name="delete" value="Delete?" />
</fieldset>
</form>

Fou-Lu
09-13-2008, 09:01 PM
ob_start() is a built in function. It stands for output buffering start. The easiest way to think of it is that it 'holds' all the data to send to the client until the end of the script and then pushes it all at the same time. Generally, I don't recommend using it, but right now I'm too lazy to alter it. It allows you to send any header, set cookies, and start sessions any time you want without conflicting with flushed output.
What you have is along the right track:


<select name="editCourseID" size="1">
<?php
while ($record = $obStorage->fetchAssoc($sel))
{
printf("<option value=\"%s\">%s</option>", $record['courseID'], $record['courseID']);
}
?>
</select>

You want to contain the entirety of an option within each record. This assumes $sel is the resultset from $obStorage->select.

You can sort it. As I said, there is a bug in the xml driver that will terminate you're program if there is no results, but I have no desire to fix that atm.


public function select(array $aFields, $sTable, $sCondition = null, $sOrder = null, $eDirection = self::DIR_ASC);

Is the prototype for the select function. To use it, you would do something like:


$aFields = array('courseID', 'courseName');
$sTable = 'classes';
$sel = $obStorage->select($aFields, $sTable, null, 'courseID', Storage::DIR_ASC);

Since I forgot to do a try/catch block in the selection menu for sorting on an empty set, it won't work if you have no results. I'll fix it later.
Another option is to capture all of you're results in an array and sort the array:


$aRecords = array();
$sel = $obStorage->select($aFields, $sTable);
while ($record = $sel->fetchAssoc($sel))
{
$aRecords[$record['courseID']] = $record;
}
ksort($aRecords);

print "<select name=\"editCourseID\">\n";
foreach ($aRecords AS $courseID => $record)
{
printf("<option value=\"%s\">%s</option>", $courseID, $record['courseName']);
}

for example.

cdn2005
09-14-2008, 07:11 PM
I was trying to implement your last option of sorting the arrays first. Looks like a parameter is missing from the function call for select. i get the following error:


Fatal error: Argument 1 passed to XMLDriver::select() must not be null, called in /www/hdsweb/silicontraining/fileDriver/fd_editSelect.php on line 60 and defined in /www/hdsweb/silicontraining/fileDriver/OO/Storage/Drivers/XMLDriver.class.php on line 407

Looking at the code, this is how it should be called.

public function select(array $fields, $from, $condition = null,
$order = null, $direction = self::DIR_ASC)

But yet at another spot you have called it as follows and it works fine there!

$sel = $obStorage->select($aFields);

Here is my modified code. I am not sure if it has to do with the while loop:

Last part of fd_editSelect.php

<form action="fd_processor.php" method="post" id="frm_<?php echo str_replace(' ', '_', $record['courseID']); ?>">
<fieldset>
<legend><?php echo $record['courseID'] ?></legend>
<?php
$aRecords = array();
$sel = $obStorage->select($aFields);
while ($record = $sel->fetchAssoc($sel))
{
$aRecords[$record['courseID']] = $record;
}
ksort($aRecords);
print "<select name=\"editCourseID\">\n";
foreach ($aRecords AS $courseID => $record)
{
printf("<option value=\"%s\">%s</option>", $courseID, $record['courseName']);
}
?>
<input type="submit" name="edit" value="Edit?" />
<input type="submit" name="delete" value="Delete?" />
</fieldset>
</form>

Fou-Lu
09-14-2008, 10:22 PM
Select is missing its $table, the location where you're looking up information.


<?php
$sel = $obStorage->select(array('courseID'), $table);
if ($obStorage->countResults($sel) <= 0)
{
print "<div><p>No Records.</p></div>\n";
}

while ($record = $obStorage->fetchAssoc($sel))
{
?>
<form action="fd_processor.php" method="post" id="frm_<?php echo str_replace(' ', '_', $record['courseID']); ?>">
<fieldset>
<legend><?php echo $record['courseID'] ?></legend>
<input type="hidden" name="editCourseID" value="<?php echo $record['courseID']; ?>" />
<input type="submit" name="edit" value="Edit?" />
<input type="submit" name="delete" value="Delete?" />
</fieldset>
</form>

<?php
}
?>

Was the original, and now you want to swap it into a select. So you'd go like so:


<?php
// Up to this above block
$sel = $obStorage->select(array('courseID', 'courseName'), $table);
if ($obStorage->countResults($sel) <= 0)
{
print "<div><p>No Records.</p></div>\n";
}
else
{
$aRecords = array();
while ($record = $obStorage->fetchAssoc($sel))
{
$aRecords[$record['courseID']] = $record;
}
?>
<form action="fd_processor.php" method="post" id="frm_<?php echo str_replace(' ', '_', $record['courseID']); ?>">
<fieldset>
<label for="editCourseID">Select CourseID:</label>
<select name="editCourseID" id="editCourseID">
<?php
if (is_array($aRecords))
{
ksort($aRecords);
foreach ($aRecords AS $record)
{
printf("\t\t\t\t\t<option value=\"%s\">%s</option>\n", $record['courseID'], $record['courseName']);
}
}
?>
</select>
</fieldset>
<div>
<input type="submit" name="edit" value="Edit?" />
<input type="submit" name="delete" value="Delete?" />
</div>
<?php
}


Try that. Hmm, it may just be easier for me to fix the bug in the ordering, this is a lot of work for something that the driver is capable of doing.

Fou-Lu
09-14-2008, 10:45 PM
Ok, normally I hate double posting if I can avoid it, but I wanted you to see the solution above as well.
I've altered my driver as well, so I can't give it to you since it will no longer work with the original - the getinstance method was altered to accept string indexed arrays instead of flat strings. Instead, I'll tell you where to do the alteration.


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>List Courses</title>
</head>
<body>
<?php
//Remember, you may need to change the location to storage.
require_once './OO/Storage/Storage.class.php';
$table = 'classes';
$obStorage = null;

try
{
// Remember, you may need to change the location of the docs!
$obStorage = Storage::getInstance('XML', './Storage');
}
catch(Exception $ex)
{
die($ex->getMessage());
}
if (isset($_GET['sMsg']))
{
printf("<div id=\"sMsg\"><p>&#37;s</p></div>\n", urldecode($_GET['sMsg']));
}
?>
<form action="fd_processor.php" method="post" id="frmMass">
<fieldset>
<legend>All Set Manipulation</legend>
<input type="submit" name="add" value="Add Record" />
<input type="submit" name="delete" value="Delete All Records" />
</fieldset>
</form>
<?php
$sel = $obStorage->select(array('courseID', 'courseName'), $table, null, 'courseID', Storage::DIR_ASC);
if ($obStorage->countResults($sel) <= 0)
{
print "<div><p>No Records.</p></div>\n";
}
else
{
?>
<form action="fd_processor.php" method="post" id="frm_editCourses">
<fieldset>
<legend>Courses</legend>
<label for="editCourseID">Select Course:</label>
<select name="editCourseID" id="editCourseID">
<?php

while ($record = $obStorage->fetchAssoc($sel))
{
printf("\t\t\t\t<option value=\"%s\">%s</option>\n",
$record['courseID'], $record['courseName']);
}
?>
</select>
<input type="submit" name="edit" value="Edit?" />
<input type="submit" name="delete" value="Delete?" />
</fieldset>
</form>
<?php
}
?>
</body>
</html>


Now, in the xmldriver.class.php, you'll want to get into the select function. Find this part, its about half way through it (line 438):


if ($order !== null)
{
try
{
$this->sortResults($result, $order, $direction);
}
catch (IllegalArgumentException $ex)
{
$this->halt($ex->getCode(), $ex->getMessage());
}
}

Change the if statement to: if ($order !== null && $result->length > 0)
This will let you utilize the order thats built into the function.


This can be generated into one form, but I can't be bothered to alter it and create a delete all when its not necessary.
Looks pretty cool otherwise though!

cdn2005
09-29-2008, 09:34 PM
How are you? Any idea what happened to our 5th page? That page is missing or I couldn't get to it. The following last message from you is nowhere to be seen!!
My be that page will reappear when I post this.



Lol.
I'll look into it and get back to you. I'm a little busy at work atm, so it may be a couple of days. The header is simply due to the output buffering, I should have written the doctype and stuff after the storage construction.
I'll post back.

By the way, did you had any time to look at the code further?

Fou-Lu
09-30-2008, 08:04 PM
DB rollback to fix problems. Kinda a pain >.<
Yeah I did get this done, but I couldn't remember the name of the thread, I'm glad you posted again lol.
Try this out, its not very optimized since its just an edit on the current files, but it looks like it does the trick. I don't think you'll need to worry about output buffering anymore either.
I'm not certain how exactly you wanted it, but I put in a multiple selection menu with a confirmation for deletion using checkboxes (you'll need to edit the css to make it align better). I didn't want to do it with the delete all, but I did it anyway. The problem with the checkboxes is if you have a lot of records then it will result in a long page, otherwise you'll need to store the checked items and pass them through a post for pagination which is a pain.
Anyway, let me know if thats what you're looking at. If you wanted to, a JS addition to popup confirmations and change the fields would be possible, though I did mess up on the names to allow that so I'll need to go over it and change it later. I'll probably do the js up next round.

cdn2005
09-30-2008, 09:53 PM
That looks great. I had to make couple of minor corrections:

In the fd-global.php, it didn't like the getinstance(array...

$obStorage = Storage::getInstance('XML', './Storage');
//$obStorage = Storage::getInstance(array('driver' => 'XML', './Storage'));

and I also added ob_start() in processor.php

Yes, with the Delete All, we will have the issue you mentioned, if we have lot of records. I was thinking, we could use a simple counter to show the first 20 records, then select check boxes and delete the ones selected, and in the refreshed page it will show the next 20, which will be the first 20 minus records deleted.

So if the user deleted 5 records, then the next set of 20 records now start at record 16.

cdn2005
10-01-2008, 05:08 PM
On a side note, I noticed that you have embedded all your html in PHP language. While it looks very clean, I can't imagine it being very easy to edit or create. Creating html using wysiwyg or even simple text editor is fairly straight forward and most people are used to doing that. But embedding every single line in print statements with escape character for every quotes etc ... like this
printf("\t\t\t<input type=\"radio\" name=\"courseType\" id=\"ctIL\" value=\"Instructor Led\"%s />\n",
(DEFAULT_RADIO == 'Instructor Led') ? ' checked="checked"' : ''); looks a bit tedious to code and debug, and makes it that much more difficult if the page has a lot of html code.

So, my question is did you use any application that would convert the html to PHP code or did you actually hand code it? Is there any programs that would do that automatically?

Fou-Lu
10-01-2008, 07:20 PM
Yeah sorry about the getinstance, my Storage is slightly different in that it uses arrays, but a simple change to the string would work. I'm a little surprised that the output buffering was still required, I'll need to check my configurations.
I hand coded that. I actually choose printf's since they are pretty easy to alter and gives a cleaner view to what the output html will be. I also like to avoid breaking my PHP code out when I can, it just looks 'hacky' when you do. I normally template things, but this would actually require a number of them to put together properly, and we don't have a template handler on it. There are probably programs to do this, but I came from notepad and still do a lot of things manually (no IDE's existed in the FII days).

Doing the limiting will be easier said than done. Since limits are not a standard in data handling, I hadn't included them in. I'll look into it when I've got some time and see whether it can be done. You can also highlight the fields you want to delete and choose delete from there. That one also has the same radio button confirmation to it, but will be limited to the ones you've selected.

cdn2005
10-01-2008, 07:25 PM
Yeah, seeing your programming skills I kinda thought that you are the type of hard core programmer who would use notepad for coding :-)

I didn't quite understand what you said about highlighting fields. Is that feature already available or you are saying that it is something that can be implemented?

Fou-Lu
10-01-2008, 08:16 PM
Lol, its not that bad, I did a .swf movie in notepad once, and compiled it after. It didn't have any graphics to it, but I was pretty impressed nonetheless. I won't do that again though... maybe I'll try it with an mp3 one of these days :D. Using C is actually pretty easy and lets you do these kinds of things since it has extreme memory control on it, all you need is to compare it (I compared my .swf to another prebuilt .swf to trace through the memory and how its handled and moved). The only reason I did this though was to see if you can compile it with the free studio plugins from macromedia, and sure enough you could. I was maybe 14 or 15 at the time so I couldn't afford the $700 for the actual Macromedia flash studio.

That features already implemented, which is why the selection box was extended to show more records. Just click on an item, hold shift and click on another one for selecting a range, or hold ctrl when selecting items to highlight non-ranged multiples. Both the deletion and edit menus handle multiple records, and I put an anchor jump into the edit page that lets you jump to a specific record too. I figure this feature is really more along the lines you were looking for, check it out and let me know.


Yes, I'll change it up a bit after too, since I changed the field names for deletions. Once I change them back to editCourseID, we'll be able to write a Javascript handler to popup a confirmation box instead if you wanted to do it that way.

cdn2005
10-01-2008, 10:23 PM
A .swf file using Notepad. lol... you are certainly born to be a programmer :-)

I have another question on another topic unrelated to this issue. Perhaps I should start a new thread. Do you have any perl scripts that would parse through a report and filter out warnings that were already in the previous report. Or optionally, if I provide a file containing an exception list of warnings (cut and pasted from the first report and using regexp wild characters to group similar type messages), then it would remove all those from the new log file. The log file is fairly large and I can not suppress all messages based on an error code. There are some errors that I know are benign, so I want to filter them out from the new log file.

If you have some scripts along those lines, I will open a new thread, otherwise let's stick to the original thread. Sorry to bug you with a new question. But I thought I will ask anyway, just in case you already have something that'll do just that.

Fou-Lu
10-03-2008, 07:24 AM
:)

I probably do, on my dead Tux machine :(
PERL is the perfect choice for what you need to do, just post a new thread in the Perl forum, if I get a chance I'll try to make it for you. Just show them what you're format is and they can probably put it together in a few minutes (if its simple).
If you need it done in PHP, we could also look at doing that.

cdn2005
10-03-2008, 07:37 AM
Okay.. I will post a new thread. I am also playing with perl to make one, but not quite there yet. But if you have anything using PHP, that would be just as good, because then I can look at the code and generate a perl version (or use it as is from a webpage). I know the algorithm, but not sure about exactly which commands to use in perl, that is perl has so many options like hash, exists etc.

I posted a new thread:
http://www.codingforums.com/showthread.php?p=736410#post736410

cdn2005
10-04-2008, 01:34 AM
I think I got couple of simple solutions, which seems to do the trick. Amazing power of Unix commands. I don't know how good those commands are when dealign with large files with varying entries.


comm -3 -1 excepionlist newreport > cleanreport
or
egrep -vf excepionlist newreport > cleanreport

Interesting...

cdn2005
10-06-2008, 03:19 AM
This is addictive!
I was trying to see if I could create a drop down list for users to just view the data as they select each record item. I got the structure down in a new file fd_showDetails.php. But I am not sure how to refresh the screen wen the user makes a selection. So, I can certainly use the touch of your mighty hands again for this new module that I generated.

My "intention" was to make a page, when the page is initially opened, would simply list the drop down menu item. As the user makes a selection from the drop down list, the page would automatically refresh the page with some data from that record.

I am confused about how to handle line 53, 57, function editForm (should really be called displayForm) etc.

cdn2005
10-08-2008, 03:00 PM
Any chance for you to take a look at this one issue. I tried different things, but it's just not falling in to place. I think it is something simple and has to do with calling the function during the "onchange" event of the drop down menu, but I just cant put my finger on it. Any help would be great.

Fou-Lu
10-08-2008, 08:33 PM
Sorry, I've been busy at work and haven't had much chance to visit - I missed this since I must of hit the page showing it as old :P
I'll look into it after work today, since I'm just on lunch and don't have access to a webserver. What I can tell you right off the bat is that what you're trying with the current code is impossible. You're mixing JS and PHP together, but JS is not capable of directly calling a PHP function. The options are AJAX (Recommended), or to prepopulate all of the data for JS to use (not recommended). AJAX is trickier to setup, but works way better IMO in the longrun. The JS prepopulation will take more client memory, but its far easier to implement.

cdn2005
10-08-2008, 09:10 PM
Thanks for the reply. I thought you may have lost the thread.

Interesting, I didn't know JS couldn't call PHP. I have seen AJAX being used, where, in a case like this it may be possible to have a section scroll down (accordion style) with data of the selected record right below the drop down list. But I don't knwo how difficult that is, I haven't really understood the concept behind setting up an AJAX section yet.

Fou-Lu
10-12-2008, 03:10 AM
Yeah, remember that http is a stateless protocol. So, you request a php page, the server processes it, and sends the output to the client. Once this has happened, it is impossible to interact directly with PHP functions (and also why PHP is not compatible directly with JS).
I'm working at integrating the current script work into a single page, and using ajax to serve it out (as well as non-js). So far, it works in IE7+, FF 2+, and Opera 9 for sure, and definitely not IE6.
I'm down to the last step. Seems that the JS is replacing a space with an underscore, and I have no idea why. Since it works without the JS, that indicates its the JS thats causing it, so I'll need to scan through it to see what it is. This is a problem though, since if we have a course ID with a space in it, we can't properly idenfity it, and we can't just replace the underscore (since it may actually be in the id).
I'll post back after, I dragged up an AJAX framework I did a few years ago. I actually updated it sometime last year.

Fou-Lu
10-12-2008, 04:36 AM
Mmkay, try this out. It doesn't have an accordian on it, but it contains all of the other features, and should work with or without JS on.

Oh yes, I should mention as well. I'm no JS expert, but I think I got it working correctly :P

cdn2005
10-14-2008, 08:12 PM
Wow... that is way cool....! that is neat.... wow... the code looks so minimal for what it is doing. that is cool... suffice to say that I am bit lost or should I say more lost now... I am going to try and make a new page for a simpler function based on the procedures that you used here.

That's neat.... Is there any language that you don't know! Hope you had a good thanks giving weekend! :-)

Fou-Lu
10-15-2008, 02:36 AM
Lol, my thanks giving was last weekend here in Canada! Thanks for the thought hough :P
Like I said, this will work on any modern browser. It doesn't work in IE6, but I'm pretty certain is because I haven't done any editing to the document to get it from the right place - it also seems unhappy serving it out as an xml document without a xsl sheet, so it would also have to detect the browser in PHP to determine its content-type. Meh.
It should work if you turn you're JS off as well, it will be pretty much the same as the original one. All I did was cut and paste the old code into the new script, referenced an old ajax framework, and added the js for this particular form. The trickiest part was figuring out how to get which button was clicked - its been forever since I've done JS so I had to search the net for that one. Easy find though :D
I just realized something else... I think I've done this entire thing now haven't I? Lol, next time I'll need to charge you ;). If you've got this on a public site, can you give me a url so I can link it into my portfolio?
As before, no business rules have been enforced EXCEPT that you cannot reuse the primary key. This is an easy addon though.

cdn2005
10-15-2008, 05:09 AM
:-) For sure... but at the moment, I don't have any projects to use it on a public site. However, having seen the capabilities, I am tempted to find an application to make use of this engine. Yes, you have done quite a bit of the solution. I can not thank you enough for your patience and interest in resolving issues. This was a real learning experience for me. Of course, my original goal was to be able to write data to a file and read data back and edit it. Certainly this does it in a much fancier way, and in the processor, I kinda got overwhelmed and sidetracked by the cleverness and power of XML and now bit of Ajax as well. I still haven't really completely my original task, which I will work on using code from this sample.

You are right, it completely barfs on IE6, big time. It just spills the whole code on to the screen and that's it. Very strange. I am really disappointed at the way IE6 is developed, they just won't support what everybody else supports? Why is the code behaving like that? Anything I can do to make it work or is it a lot of work to make it happy on all common browsers. Without IE6, our code becomes pretty obsolete for any real purpose.

For the time being, I may have to go back to the previous version to make it happy for doing what I set out to do.

Fou-Lu
10-15-2008, 09:03 AM
You actually need to support IE6? I'll look into it - I know I've successfully tricked IE6 into running an application/xhtml+xml document as text/html before, and still kept all of the useful features :D
I'll see if I have anything onsite that I've used before, otherwise I'll see if I can't find a solution.
The reason why it doesn't work is becaus the mimetype is application/xhtml+xml. This is a requirement for running the dom correctly through the js and xhtml strict documnts, but legacy browsers do not support anything above text/html. There is no such thing as an 'all around' solution when it comes to js though, browsers still handle it however they see fit. Kinda a pain actually.
Like I said, I'll see if I can't get this running on IE6. Good thing I still have an IE6 (win98) workstation. Love it.

cdn2005
10-15-2008, 10:01 PM
It would certainly be helpful to have IE6 support. Yeah, it is interesting that Microsoft tools are progressively getting worse and even with its limitations, it seems that their older OS looks far more stable than the new ones, although when we only had those old Win98 and WinXp we didn't realize how good we had it!

I tried the fd_course.php on IE6 and the browser simply displays the code as is without any complains or warning. Too bad that they don't have any patches to support mimetype application/xhtml+xml. That would've been a better solution. IE6 is really not that old, and apparently still used by many. So, I wonder how these Ajax XML applications are used in major sites where they need to ensure support for legacy browsers!

Fou-Lu
10-16-2008, 08:23 AM
Its done by not correctly supporting xhtml standards. XHTML dictates that the mimetype must be of text/xml, application/xml or application/xhtml+xml. Since none of these are understood by older (by that I mean like 3 years or older lol) browsers, you need to find a way to trick it into taking it as text/html.
Still in the works. I figured out how to trick it into serving out as text/html while still being text/xml, so thats good. But, because I did I now can't identify document.getElementById in html - so IE6 still doesn't know what to do with it :P. The ajax is working best I can tell, it just cannot be shown.
I'll figure it out, either by forcing an entity reference for the id field (to actually tell it that 'id' is meant to be the unique 'id' that html normally dictates), or I'll iterate it as tagname bases. I'm pretty sure the trouble I'm having right now is case-sensitivity based - I recall that IE6 will divert all items to uppercase if I'm not mistaken. So that may be where my trouble is.

cdn2005
10-21-2008, 09:55 PM
I am surprised that there is really no support for this issue from Microsoft. IE6 is still heavily used and most of those new sites developed by Google, don't they all use Ajax/XML heavily? So how do they all run without any issues.

If the solution not straight forward, we could go back to Javascript or some other more traditional method. Or simply have the information appear on a pop up window. I can use a feature where the courses are listed in a drop down list and when the user select a course, the information of that course gets displayed on that page (no editing required), which is what we ave now, but instead of Ajax, if we could use Javascript to simply display the information on a popup window, that will be good enough. I just didn't know how to bring up the information "onchange" in the dropdown list.



EZ Archive Ads Plugin for vBulletin Copyright 2006 Computer Help Forum