Sane XML builder



  • I'm currently writing a XML builder to do some cXML communication. If you don't know what cXML is, it's basically EDI but in XML format.
    I don't really have a problem with any of it, but i wanted to get some conformation that i'm not doing it the wrong way.

    What i'm currently making is a XML builder, that has multiple Objects for common XML blocks.
    So for instance i'll have a cXML_response object and a cXML_PunchOutSetupResponse object.

    All of these "building blocks" objects will inherit from cXML_constructor that defines some basic methods. The objects themselves, basically build there own pieces of XML via DomDocument.

    So then when i want to send a PunchOutSetupResponse i can do the following.

     // Define the basic response base.
    $cXML_response = new cXML_response();

    // Create the PunchoutSetupResponse
    $cXML_setupresponse = new cXML_PunchOutSetupResponse('some_url');

    // Add the PunchoutSetupResponse to the msg.
    $cXML_response->add_cXML_block($cXML_setupresponse->get_cXML());

    $cXML_msg = $cXML_response->get_cXML()->saveXML();

    And this works.

    However the Ariba manual code examples all contain a much simpler approach of just lots of different files, that contain a bit of code and most of the XML as just plain text.
    No re-using of anything basically.

    The internet also was pretty lacking for my google-fu on best practices of constructing XML files in a sane, dynamic way.

    So what i'm asking is basically if anyone can comment on if this is a sane way of doing it, and perhaps on how they would do it. 


     



  • The question is too vague, which is why it's not too surprising that google didn't tell you anything. It's about as meaningful as just asking "How do I write a sane program?" - you can talk about it in general, vague terms, but we're not going to get to anything specific. The problem of answering the question for your specific problem requires the full specification of the task, and is precisely equivalent to solving the problem; figuring that out is what a programmer does. Commenting on the sanity of your approach will require both the specification of the task and more detail about your solution - we'd need at least a sketch of the classes implementing this, not just a vague description. Oh, and what language is that supposed to be, PHP?



  • I wasn't being vague, i specified exactly what i was doing. Implementing cXML. And my question was if the way i was implementing the building of the XML was a good way to do that.
    But let me expand the example, to grant some better insight in how i'm currently trying to do that, for perhaps that part might have been unclear.

    cXML messages consists of certain xml blocks, that are shared among messages. For instance take the PunchOutSetupRequest

    <?xml version="1.0"?>
    <!DOCTYPE cXML SYSTEM "http://xml.cxml.org/schemas/cXML/1.2.014/cXML.dtd">
    <cXML xml:lang="en-US" payloadID="933694607118.1869318421@jlee" timestamp="2002-08-15T08:36:47-07:00">
        <Header>
            <From>
                <Credential domain="DUNS">
                    <Identity>65652314</Identity>
                </Credential>
            </From>
            <To>
                <Credential domain="DUNS">
                    <Identity>83528721</Identity>
                </Credential>
            </To>
            <Sender>
                <Credential domain="NetworkId">
                    <Identity>AN200001</Identity>
                    <SharedSecret>abracadabra</SharedSecret>
                </Credential>
                <UserAgent>Procurement System 2.0</UserAgent>
            </Sender>
        </Header>
        <Request>
            <PunchOutSetupRequest operation="create">
                <BuyerCookie>1CX3L4843PPZO</BuyerCookie>
                <Extrinsic name="UserEmail">jsmith</Extrinsic>
                <Extrinsic name="UniqueName">John_Smith</Extrinsic>
                <Extrinsic name="CostCenter">610</Extrinsic>
                <BrowserFormPost>
                    <URL>https://bigbuyer.com:2600/punchout?client=NAwl4Jo</URL>
                </BrowserFormPost>
                <SupplierSetup>
                    <URL>http://www.workchairs.com/punchout.asp</URL>
                </SupplierSetup>
                <ShipTo>
                    <Address addressID="1000467">
                        <Name xml:lang="en">1000467</Name>
                        <PostalAddress>
                            <DeliverTo>John Smith</DeliverTo>
                            <Street>123 Main Street</Street>
                            <City>Sunnyvale</City>
                            <State>CA</State>
                            <PostalCode>94089</PostalCode>
                            <Country isoCountryCode="US">United States</Country>
                        </PostalAddress>
                    </Address>
                </ShipTo>
                <SelectedItem>
                    <ItemID>
                        <SupplierPartID>5555</SupplierPartID>
                    </ItemID>
                </SelectedItem>
            </PunchOutSetupRequest>
        </Request>
    </cXML>

    The blocks "header", "ShipTo", "PostalAddress" are shared among many other messages that cXML has.

    So i figured that if i create XML builder classes for all the messages, and for the shared blocks of XML between those messages, i can make a nice system to generate those messages.

    A typical class would look like this:

    class cXML_PunchOutSetupResponse extends cXML_construct {
        private   $startpage_url;
        protected $cXML;
       
        public function cXML_PunchOutSetupResponse($url=false) {
           
            if ($url!=false) {
                $this->set_startpage_url($url);
            }
        }
       
        public function set_startpage_url($url) {
            $this->startpage_url = $url;
           
            $this->construct_cXML();
           
        }
       
        public function get_startpage_url($url) {
            return $this->startpage_url;
        }
       
        protected function construct_cXML() {
               
            $cXML = new DOMDocument();
           
            $cXML_PunchOutSetupResponse  = $cXML->createElement("PunchOutSetupResponse");
            $cXML_StartPage              = $cXML->createElement('StartPage');
            $cXML_URL                    = $cXML->createElement('URL',$this->startpage_url);
           
           
            $cXML_StartPage->appendChild($cXML_URL);
            $cXML_PunchOutSetupResponse->appendChild($cXML_StartPage);
            $cXML->appendChild($cXML_PunchOutSetupResponse);
           
            $this->cXML = $cXML;
        }
    }

    The abstract class that it extends, currently looks like this:

    abstract class cXML_construct {
        protected $cXML;
       
        public    function cXML_construct() {}
        protected function construct_cXML() {}
       
        public function get_cXML() {
            if ($this->cXML==false) {
                $this->construct_cXML();
            }
            return $this->cXML;
        }
    }

    Now another class to create the actual message where the XML from PunchOutSetupResponse needs to be inbedded in looks like this

    class cXML_response extends cXML_construct {
        protected $cXML;
       
        public function cXML_response() {}

        public function add_cXML_block($cXML_block) {
            $cXML_block_node = $cXML_block->firstChild;   
            $cXML            = $this->get_cXML();
           
           
            // Get the element to attach this block too.
            $Response_tag = $cXML->getElementsByTagName('Response');
           
            // import the node to import.
            $node = $cXML->importNode($cXML_block_node,true);
           
            // append the imported node to the child.
            $Response_tag->item(0)->appendChild($node);
           
            $this->cXML = $cXML;
           
        }
       
        protected function construct_cXML() {
            // DOM XML creation of cXML_response;
           
            $cXML = new DOMDocument('1.0');
           
            //DomDocument has a bug, that you can't set a DTD, so this is a workaround.
            $doctype_node = $cXML->createTextNode('_REPLACE_WITH_DOCTYPE_');
            $cXML->appendChild($doctype_node);
           
            $cXML_cXML     = $cXML->createElement("cXML");
            $cXML_Response = $cXML->createElement('Response');
            $cXML_Status   = $cXML->createElement('Status');
           
           
           
            $cXML_cXML->setAttribute('payloadID','933695160894');
            $cXML_cXML->setAttribute('timestamp',date('c'));
            $attr_payloadID = $cXML->createAttribute('payloadID');
           
           
            $cXML_Response->appendChild($cXML_Status);
            $cXML_cXML->appendChild($cXML_Response);
            $cXML->appendChild($cXML_cXML);
           
            $this->cXML = $cXML;
        }
     
    }

     

    So then when i call

    // Define the basic response base.
    $cXML_response      = new cXML_response();

    // Create the PunchoutSetupResponse
    $cXML_setupresponse = new cXML_PunchOutSetupResponse('some_url');

    // Add the PunchoutSetupResponse to the msg.
    $cXML_response->add_cXML_block($cXML_setupresponse->get_cXML());

    I get the following XML back.

    <?xml version="1.0"?>
    <!DOCTYPE cXML SYSTEM "http://xml.cxml.org/schemas/cXML/1.2.0.13/cXML.dtd">
    <cXML xml:lang="en-US" payloadID="933694406639" timestamp="2002-08-15T08:46:00-07:00">
    <Response>
        <Status code="200" text="success" ></Status>
        <PunchOutSetupResponse>
            <StartPage>
                <URL></URL>
            </StartPage>
        </PunchoutsteupResponse>
    </Response>
    </cXML>

    And the language is indeed PHP.

    These classes are far from final, and currently only serve as a proof of concept, to test this.



  • What are your requirements for throughput and load?  I know nothing about cXML, but I do know a fair bit about EDI (well, I did about 7 years ago).

    I see alot of overhead in your design, whereas you are creating many many objects just to get an XML output.  You might have better performance out of a single static class that returns snippets of your code...

    In your example, I see the system creating no fewer than 5 objects to retrieve a short snippet of xml:

    1 construct_cXML object
    1 DOMDocument
    3 XML Elements

    Take a step back and see if there is a simpler solution to the problem :)  If performance is not the #1 concern then your approach seems like a sane OO way to do it.



  • I don't know enough about cXML to comment meaningfully (and I'm not inclined to study it), but for the record, that's about the right level of detail for somebody to evaluate the sanity of your approach, if there's anybody around here with the background to do so.



  • @asuffield said:

    I don't know

    WTF

    ;) 



  • @RaspenJho said:

    What are your requirements for throughput and load?  I know nothing about cXML, but I do know a fair bit about EDI (well, I did about 7 years ago).
    I see alot of overhead in your design, whereas you are creating many many objects just to get an XML output.  You might have better performance out of a single static class that returns snippets of your code...
    In your example, I see the system creating no fewer than 5 objects to retrieve a short snippet of xml:

    1 construct_cXML object
    1 DOMDocument
    3 XML Elements

    Take a step back and see if there is a simpler solution to the problem :)  If performance is not the #1 concern then your approach seems like a sane OO way to do it.

    Performance is a concern, but not a big one.  

    This is made to integrate a webshop into procurement applications. And i believe the average number of orders a day is estimated in 10 thousands tops, i have no idea how the amount of orders will correlate to the amount of visitors via the procurement application though, but i would reckon it will be pretty high. And even then, there are already plans in place to be able to scale over multiple servers, so if it becomes the bottleneck we can throw hardware at the problem.

    After some testing of how it works. I refactored it a bit; Keeping in mind your point about the objects. Now i basically have a strategy pattern in place, with a stategy for each type of message. That message creates just 1 DomDocument Object, and passes around the correct Node to underlying Objects for them to attach XML to. I haven't run any real tests yet, but my gut tells me this is a pretty solid design.

    The reason i wanted to maintain maximum maintainability is because the cXML standard leaves quite a bit of room to implement your own "extra's" and such. So i need to be able to define different behaviour for different procurement applications, because there all bound to have there own quirky implementation. But for now that's in the future. But i still needed to consider it now, to be able to adapt quickly enough when that time comes.

    Perhaps tomorrow when i make the final adjustments/updates to my object diagram i'll upload a pic.

    Perhaps i should also consider not doing it by DOM though, and just keep pasting strings together. It wouldn't matter "much" code wise. It would just look a bit less clean, although a small static tool object to create xml tags might keep it looking clean. I dunno. I might try it tomorrow.



  • You might want to take a look at PHP's SimpleXML extension. It's quite awesome at building and parsing blocks of XML. Most functions that one'd regularly implement when wrapping the DOM are already there. I'm not sure how it fares against DOMXML performance-wise, but it's certainly a lot easier to use and manipulate.


Log in to reply