I hope this info is useful. There was a comment in a README regarding the parsing of arrays/QBXML...I'm not exactly clear.
Anyway, here's a snippet to convert the file found in the QBSDK download called 'qbxmlopsxx.xml', where the xx is a version number. You're probably familiar with it. This script converts all of the QBXML requests into nested arrays, which is what I believe the API uses.
PEAR has a class XML_Serializer that makes this relatively painless. It might be useful to this project. If you use it to generate just one array, it is very quick in spite of the qbxmlopsxx.xml file being > 2 Mb.
I've also used templates in the past (Smarty), as well as XSLT (which is very tedious) to generate QBXML. The template approach would be easily modified for Drupal. The QB parser can be rather picky about your QBXML, but with a template approach, one can control the output precisely.
ini_set('display_errors', 1);
require 'XML/Unserializer.php';
//replace with the path to your qbxmlopsxx.xml file
$file = file_get_contents('../Desktop/qbxmlops70.xml');
//remove whitespace and new lines
$string = preg_replace('/\s{2,}/', '', $file);
$string = preg_replace('/\n/', '', $string);
$xml = DOMDocument::loadXML($string);
//get just the request QBXML fragments
$xpath = new DOMXPath($xml);
$entries = $xpath->query('//QBXMLMsgsRq/*');
$us = new XML_Unserializer();
foreach ($entries as $entry) {
//Creating a string of QBXML containing only 1 message, e.g. 'AccountAddRq';
//there's got to be a better way to do this...
$doc = new DOMDocument;
$root = $doc->createElement('QBXML', '');
$doc->appendChild($root);
$msg = $doc->createElement('QBXMLMsgsRq');
$root->appendChild($msg);
$node = $doc->importNode($entry, true);
$msg->appendChild($node);
$string = $doc->saveXML();
if ($us->unserialize($string)) {
$data = $us->getUnserializedData();
print_r($data);
}
}
Example output (only 1 QBXML message shown):
Array
(
[QBXMLMsgsRq] => Array
(
[AccountQueryRq] => Array
(
[ListID] => IDTYPE
[FullName] => STRTYPE
[MaxReturned] => INTTYPE
[ActiveStatus] => ENUMTYPE
[FromModifiedDate] => DATETIMETYPE
[ToModifiedDate] => DATETIMETYPE
[NameFilter] => Array
(
[MatchCriterion] => ENUMTYPE
[Name] => STRTYPE
)
[NameRangeFilter] => Array
(
[FromName] => STRTYPE
[ToName] => STRTYPE
)
[AccountType] => ENUMTYPE
[IncludeRetElement] => STRTYPE
[OwnerID] => GUIDTYPE
)
)
)
Comments
Comment #1
mitz commentedIn case it wasn't clear, I'm not asking that this be included, just thought it might help.
Comment #2
allie mickaWish I'd known!
Prior to this post, I had written a script which does just this, and saves the result to files that are included in this module. The latest commit includes files which contain var_export()-ed versions of the XSD files you speak of, in order to list available queries, record types, and data types. I have used this in other modules ( e.g. qb_data ), to show which queries may be executed, and identify the fields that are available for mapping.
Going forward, we can also use this data to validate fields ( such as limiting Drupal's field values to those seemingly-arbitrary 41 character limits if they're going to be imported to applicable QB records. )
See the includes/xml directory for these files.
I'm not using PEAR - simply DOM. And there are still some quirks, which I'd love some help with. But I'm closing this issue for now!
Comment #3
mitz commentedAllie,
What kinds of quirks? How can I help?
I've looked over the files your script refers to. My concern is that the qbxml.7.0.inc file has nearly 65K lines of code in a single function, and it seems like a lot to load to extract the small portion of the array needed.
In the past, I've thought about putting this validation and array-building data into a MySQL table, or possibly extracting the needed qbXML directly from the qbXML source files as needed, then building up the qbXML response from there. Maybe a combination of those methods. Does that make sense?
BTW, I'm setting up a test environment to include CVS. This (qb) is a feature that I've long sought. I'll be happy to help test, provide input, write docs and help with coding chores as you deem appropriate.
mitz
Comment #4
allie mickaHi Mitz,
Thanks for your interest and your help! I'm a little thumbs-down on plunking all of this into the database rather than using the versioned files. I do hear you on the overall bulk, however!
My reasoning (copout?) is that these files won't be used very frequently. Right now, they're exclusively being used in an administrative interface to see what fields might be available for data mapping. However, it will become prudent to validate nodes and users with mapped fields, and to do a better job of validating requests.
The primary contributor to bulk here is the fact that my script expanded all of the referenced items into a duplicative über array. For example, everyplace a postaladdress macro type is used, its fields are enumerated out. This makes things handy but it also makes them giant.
Perhaps breaking down the data types is the place to start?
Comment #5
mitz commentedOK, so if it's used in the admin part, I can see that logic. But where/how does the validation take place when the qbXML is being put together (sorry, I haven't dug that deep into your code yet -- I'll will examine it more thoroughly tomorrow). For the admin part, the validation would be nice to know, but for building the actual qbXML messages, it's critical.
So this is where the MySQL comes in...reduce the number of (as far as I can tell) identical arrays. How else can we store it without unnecessarily using up memory? I thought about a class or a function, and that would certainly cut down the lines of code, but we would still need to include it whenever we needed to reference it, so would we really be saving anything?
Templates???
Maybe I'm making too big a deal out of it...
mitz
Comment #6
mitz commentedQuestion: which QB version are you targeting (for now)? Desktop? That's been my primary focus. I've found that QBOE doesn't respond well when hosting a site on a shared server. Have you had any experience with that?
I've come around to your point of view. The source files qbxml70.xsd (for validation) and qbxmlops70.xml (for structure) seem to me to be the most pertinent. In the past, I've only focused on the Rq messages, because I was only interested in building up qbXML. I think (for now), we can assume that WC will send us valid responses.
I'm thinking:
1. grab the XML fragment from qbxmlops for the operation in question,
2. step through each element to validate (using qbxmlxx.xsd) and insert the data
I looked for the function _qb_data_request (w/ drupal_alter in qb_data.wc.inc) and couldn't find it, so I'm assuming it hasn't been written yet...could the above be the basis for that?
Thoughts?
Comment #7
allie mickaThe README in the xml directory explains why I'm not shipping the xsd files directly. Basically, I'm not sure that it's kosher with Intuit or Drupal if I redistribute those files. Meanwhile, it's nigh impossible to lay hands on them, so I'm not going to put users through paces of signing up, downloading SDK's and rooting around for xsd files that they can make available to this module.
Thus, we're stuck with looking for some sort of representation of the data in Drupal-consumable form, without actually having the xsd files.
To support multiple versions and to make it a little future-proof, each file is auto-generated. Drupal will only load the one that's pertinent to the selected API version. I'll go one further and make the unsubstantiated claim that even a large array in "raw" form will be more efficient than loading an XML file and attempting to parse and validate it on each request.
That said, there's certainly a case for slimming down the resulting 1.5M files. As I say, each instance of a Type (e.g. addresses, complex types, or enumerated types such as account details, etc, is fully expanded on each if its uses. This wasn't a bad thing when I wrote it because the array elements were created by reference. But the var_export baked those references in.
Comment #8
mitz commentedWell, I understand not shipping the files directly, but it isn't unheard of to require the end user to copy libraries into Drupal (e.g. 'print' module). I was under the impression that the SDK was a prerequisite to WC. But you're probably right, if the array was slimmed down, it would be faster.
I spent some quality time with my debugger and your qbxml.php script yesterday. That's a nice piece of coding.