Options

How to add DOCTYPE to xml file using xmlport in Version 2009

April_ChenApril_Chen Member Posts: 2
Hello all,

One of my client require a xml file with the following declaration:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE cXML SYSTEM "http://xml.cXML.org/schemas/cXML/1.2.009/InvoiceDetail.dtd"&gt;

The first declaration is generated automatically when I run the xmlport I created but not the second one. Does anyone know how? Your help would be greatly appreciated.

Thank you very much.

April

Comments

  • Options
    ara3nara3n Member Posts: 9,255
    I suggest to use the following method.

    Run you XMLPORT and export it to a stream. Load that to a MSXMLDOM and call a function that will add any additional Namespace or DOCTYPE


    Here is implementation of this. In my example
    XML is a blob field and I'm creating a stream that I'm loading into a MSXMLDOM
    CALCFIELDS(XML);
    
    XML.CREATEINSTREAM(instr);
     
    MSDOM.async := FALSE;
    MSDOM.load(instr);
    
    ChangeXMLFile(MSDOM);
    
    MSDOM.save('C:\xmlfilewithDocType.xml');
    
    

    ChangeXMLFile is the function that will add namespace or DOCTYPE or any other structure that is not be done by xmlport



    //MSDOM2 is passed by reference.
    //XMLNode is of type 'Microsoft XML, v6.0'.IXMLDOMNode
    //Separator is of type text
    //Ch is of type char
    ChangeXMLFile(VAR MSDOM2 : Automation "'Microsoft XML, v6.0'.DOMDocument")
    IF ISCLEAR(MSDOM) THEN BEGIN
      CREATE(MSDOM);
      MSDOM.async := FALSE;
    END;
    
    Ch10 := 10;
    CH13 := 13;
    CH9 := 9;
    Separator := FORMAT(CH13) + FORMAT(Ch10) + FORMAT(CH9);
    
    MSDOM.loadXML('<?xml version="1.0" encoding="utf-8"?>' + Separator
     + '<!DOCTYPE cXML SYSTEM "http://xml.cXML.org/schemas/cXML/1.2.009/InvoiceDetail.dtd">' + Separator);
    
    XMLNode := MSDOM.documentElement.firstChild;
    XMLNode2 := MSDOM2.documentElement.firstChild;
    XMLNode.appendChild(XMLNode2);
    
    MSDOM2 := MSDOM;
    
    Ahmed Rashed Amini
    Independent Consultant/Developer


    blog: https://dynamicsuser.net/nav/b/ara3n
  • Options
    SonGoten13SonGoten13 Member Posts: 44
    I had the same problem than April but could make ara3n's solution work (maybe because i was not using nav2009 ?).
    So i came up with an other workaround.
    fnk_doIt(par_re_Item : Record Item)
    
    // Create file
    fi_XMLFile.CREATE(te_XMLFilePath + te_XMLFileName);
    
    // Create OutStream
    re_TempBlob.Blob.CREATEOUTSTREAM(os_OutStream);
    
    // Run XML-Port and write in OutStream
    xp_Item.SETTABLEVIEW(par_re_Item);
    xp_Item.SETDESTINATION(os_OutStream);
    xp_Item.EXPORT;
    
    // Load it into a Blob
    re_TempBlob.Blob.CREATEINSTREAM(is_InStream);
    
    // TODO: look for the first representation of a linebreak(or something else), instead of reading a hardcoded number of chars
    // Read and write line 1
    IN_NumberOfChars := 56;   // Number of chars from line 1
    FOR i:=1 TO IN_NumberOfChars*2 DO BEGIN   // *2 because XML-Port generates 4 byte blocks and a Char has 2 bytes
      is_InStream.READ(CH_char);
      fi_XMLFile.WRITE(CH_char);
    END;
    
    // Make a line break
    // Xml-port makes a line break like this: 0D 00 0A 00 (Hexadezimal)
    // The write function writes minimal 4 bytes, so you can't write those chars(0D,0A,00) one by one
    // 655373 stands for 00 0A 00 0D; it starts writing with the last byte
    fi_XMLFile.WRITE(655373);
    
    // Make your extra line
    TE_InsertText := '<!DOCTYPE Transaction PUBLIC BlaBlaBla';
    FOR i:=1 TO STRLEN(TE_InsertText) DO BEGIN
      fi_XMLFile.WRITE(FORMAT(TE_InsertText[i]));
    END;
    
    // Read and write rest
    WHILE NOT is_InStream.EOS DO BEGIN
      is_InStream.READ(CH_char);
      fi_XMLFile.WRITE(CH_char);
    END;
    
    // Close file
    fi_XMLFile.CLOSE;
    

    Variables:
    Name	DataType	Subtype	Length
    re_TempBlob	Record	TempBlob	
    xp_Item	XMLport	Item	
    te_InsertText	Text		1024
    te_XMLFileName	Text		30
    te_XMLFilePath	Text		900
    ch_char	Char		
    i	Integer		
    in_NumberOfChars	Integer		
    fi_XMLFile	File		
    is_InStream	InStream		
    os_OutStream	OutStream			
    

    Not really a "nice" way to do it, but it worked for me.

    greetings
  • Options
    Joe_MathisJoe_Mathis Member Posts: 173
    Hi All,

    ( Using NAV2009 SP1 build 6.00.29626 Classic Client SQL Database)

    Trying to work with ara3n's solution, but coming up with an error that the Automation has not been instantiated, that I have to create it or assign it. This occurs in the ChangeXMLFile function on the XMLNode assignment.

    If I comment out the ChangeXMLFile function call. It creates the XML file as expected just without the <!DOCTYPE...> node.

    I tried it with the "XMLNode := MSDOM.documentElement.firstChild;" first, and that created an error, so then I tried "XMLNode := MSDOM.documentElement();" which moved the error down to the "XMLNode.appendChild(XMLNode2);" line.

    What is missing?

    Thanks for your help.
        VAR
          XMLDOMDocument: Automation :'Microsoft XML, v6.0'.DOMDocument;
          MSDOM: Automation :'Microsoft XML, v6.0'.DOMDocument;
          OutboundInvoice: Record 112;
          TempBlob: Record 99008535;
          TempOutstream: OutStream;
          TempInstream: InStream;
          OutboundInvoiceXML: XMLport 50001;
          XMLNode: Automation :'Microsoft XML, v6.0'.IXMLDOMNode;
          XMLNode2: Automation :'Microsoft XML, v6.0'.IXMLDOMNode;
          Separator: Text[30];
          Ch10: Char;
          Ch13: Char;
          Ch9: Char;
    
        PROCEDURE AddDoctype();
        
          IF ISCLEAR(XMLDOMDocument) THEN BEGIN
            CREATE(XMLDOMDocument);
            XMLDOMDocument.async := FALSE;
          END;
    
          OutboundInvoice.SETCURRENTKEY("Sell-to Customer No.","External Document No.");
          OutboundInvoice.SETRANGE(OutboundInvoice."No.",'PI103815');
          IF NOT OutboundInvoice.FIND('-') THEN
            ERROR('No Invoice Found');
    
          TempBlob.Blob.CREATEOUTSTREAM(TempOutstream);
    
          OutboundInvoice.SETRANGE(OutboundInvoice."No.",'PI103815');
          OutboundInvoiceXML.SETTABLEVIEW(OutboundInvoice);
          OutboundInvoiceXML.SETDESTINATION(TempOutstream);
    
          IF OutboundInvoiceXML.EXPORT THEN BEGIN
            TempBlob.Blob.CREATEINSTREAM(TempInstream);
            XMLDOMDocument.load(TempInstream);
            ChangeXMLFile(XMLDOMDocument);
            XMLDOMDocument.save('C:\xmlfilewithDocType.xml');
            MESSAGE('Xml Export completed! C:\xmlfilewithDocType.xml Created');
          END ELSE
            ERROR('Error exporting');
       
        PROCEDURE ChangeXMLFile(VAR MSDOM2: Automation:'Microsoft XML, v6.0'.DOMDocument");
    
          IF ISCLEAR(MSDOM) THEN BEGIN
            CREATE(MSDOM);
            MSDOM.async := FALSE;
          END;
    
          Ch10 := 10;
          Ch13 := 13;
          Ch9 := 9;
          Separator := FORMAT(Ch13) + FORMAT(Ch10) + FORMAT(Ch9);
    
          MSDOM.loadXML('<?xml version="1.0" encoding="utf-8" standalone="no" ?>' + Separator
          + '<!DOCTYPE cXML SYSTEM "http://xml.cXML.org/schemas/cXML/1.2.009/InvoiceDetail.dtd">' + Separator);
    
          //XMLNode := MSDOM.documentElement.firstChild;
          //XMLNode2 := MSDOM2.documentElement.firstChild;
    
          XMLNode := MSDOM.documentElement();
          XMLNode2 :=MSDOM2.documentElement();
    
          XMLNode.appendChild(XMLNode2);
    
          MSDOM2 := MSDOM;
    
  • Options
    ara3nara3n Member Posts: 9,255
    Try and add before

    MSDOM..documentElement.firstChild;


    MSDOM.SAVE('c:\test1.xml');

    MSDOM2.SAVE('c:\test2.xml');

    And make sure they have valid xml files.
    Ahmed Rashed Amini
    Independent Consultant/Developer


    blog: https://dynamicsuser.net/nav/b/ara3n
  • Options
    Joe_MathisJoe_Mathis Member Posts: 173
    MSDOM2 is valid, MSDOM is not.

    I assume that this line is not working?
    MSDOM.loadXML('<?xml version="1.0" encoding="utf-8" standalone="no" ?>' + Separator
          + '<!DOCTYPE cXML SYSTEM "http://xml.cXML.org/schemas/cXML/1.2.009/InvoiceDetail.dtd">' + Separator);
    

    if I change this to a text instead, and stream it into a file, it creates an XML file, but I still can't open it to get the nodes and move them into the XML port generated one.
    insertText := '<?xml version="1.0" encoding="utf-8" standalone="no" ?>' + Separator
          + '<!DOCTYPE cXML SYSTEM "http://xml.cXML.org/schemas/cXML/1.2.009/InvoiceDetail.dtd">' + Separator;
         
    XMLFile.CREATE('C:\Temp.XML');
    XMLFile.CREATEOUTSTREAM(writeStream);
    writeStream.WRITETEXT(insertText);
    XMLFile.CLOSE;
    

    I am sure I am missing something small.
  • Options
    ara3nara3n Member Posts: 9,255
    When created the second MSDOM, you need to add a root element.
    MSDOM.loadXML('<?xml version="1.0" encoding="utf-8" standalone="no" ?>' + Separator
          + '<!DOCTYPE cXML SYSTEM "http://xml.cXML.org/schemas/cXML/1.2.009/InvoiceDetail.dtd">' + Separator + '<ROOT></ROOT>');
    <ROOT></ROOT>
    
    Ahmed Rashed Amini
    Independent Consultant/Developer


    blog: https://dynamicsuser.net/nav/b/ara3n
  • Options
    Joe_MathisJoe_Mathis Member Posts: 173
    The problem is with the MSDOM.loadXML command.

    Apparently when using an external DTD, it can't validate when parsing because it isn't a whole definition.
    Adding "MSDOM.validateOnParse(FALSE);" before the "MSDOM.loadXML(..." will allow it to actually load.

    So I am able to get the file to save from the MSDOM with the "<!DOCTYPE" inserted. Still getting an error on the XMLNode though.

    Current Code:
    //ChangeXMLFile(VAR MSDOM2 : Automation "'Microsoft XML, v6.0'.DOMDocument")
    IF ISCLEAR(MSDOM) THEN BEGIN
      CREATE(MSDOM);
      MSDOM.async := FALSE;
    END;
    
    Ch10 := 10;
    Ch13 := 13;
    Ch9 := 9;
    Separator := FORMAT(Ch13) + FORMAT(Ch10) + FORMAT(Ch9);
    
    MSDOM.validateOnParse(FALSE);
    Loaded := MSDOM.loadXML('<?xml version="1.0" encoding="utf-8" standalone="no" ?>' + Separator
    + '<!DOCTYPE cXML SYSTEM "http://xml.cXML.org/schemas/cXML/1.2.009/InvoiceDetail.dtd">' + Separator
    + '<ROOT></ROOT>');
    
    //MSDOM.save('C:\Test1.xml');
    //MSDOM2.save('C:\Test2.xml');
    
    XMLNode := MSDOM.documentElement.firstChild;
    XMLNode2 := MSDOM2.documentElement.firstChild;
    
    //XMLNode := MSDOM.documentElement();
    //XMLNode2 :=MSDOM2.documentElement();
    
    XMLNode.appendChild(XMLNode2);
    
    MSDOM2 := MSDOM;
    

    Once again, something small being overlooked...
  • Options
    Joe_MathisJoe_Mathis Member Posts: 173
    Thanks for your help ara3n. Got it figured out. :D

    Basically have to copy all the child nodes into the DOM that is created with the corrected header.
    You cannot move/append the <!DOCTYPE> node (it's read only :? ) which is why it was failing.

    Here is the working code:
        VAR
          XMLDOMDocument : Automation :'Microsoft XML, v6.0'.DOMDocument;
          OutboundInvoice : Record 112;
          TempBlob : Record 99008535;
          TempOutstream : OutStream;
          TempInstream : InStream;
          OutboundInvoiceXML : XMLport 50001;
          MSDOM : Automation :'Microsoft XML, v6.0'.DOMDocument;
          XMLNode : Automation :'Microsoft XML, v6.0'.IXMLDOMNode;
          XMLNode2 : Automation :'Microsoft XML, v6.0'.IXMLDOMNode;
          Separator : Text[30];
          Ch10 : Char;
          Ch13 : Char;
          Ch9 : Char;
    
        PROCEDURE AddDocType();
    
          IF ISCLEAR(XMLDOMDocument) THEN BEGIN
            CREATE(XMLDOMDocument);
            XMLDOMDocument.async := FALSE;
          END;
    
          OutboundInvoice.SETCURRENTKEY("Sell-to Customer No.","External Document No.");
          OutboundInvoice.SETRANGE(OutboundInvoice."No.",'PI103815');
          IF NOT OutboundInvoice.FIND('-') THEN
            ERROR('No Invoice Found');
    
          TempBlob.Blob.CREATEOUTSTREAM(TempOutstream);
    
          OutboundInvoice.SETRANGE(OutboundInvoice."No.",'PI103815');
          OutboundInvoiceXML.SETTABLEVIEW(OutboundInvoice);
          OutboundInvoiceXML.SETDESTINATION(TempOutstream);
    
          IF OutboundInvoiceXML.EXPORT THEN BEGIN
            TempBlob.Blob.CREATEINSTREAM(TempInstream);
            XMLDOMDocument.load(TempInstream);
            ChangeXMLFile(XMLDOMDocument);
            XMLDOMDocument.save('C:\xmlfilewithDocType.xml');
            MESSAGE('Xml Export completed! C:\xmlfilewithDocType.xml Created');
          END ELSE
            ERROR('Error exporting');
    
    
        PROCEDURE ChangeXMLFile(VAR MSDOM2 : Automation :'Microsoft XML, v6.0'.DOMDocument);
    
          IF ISCLEAR(MSDOM) THEN BEGIN
            CREATE(MSDOM);
            MSDOM.async := FALSE;
          END;
    
          Ch10 := 10;
          Ch13 := 13;
          Ch9 := 9;
          Separator := FORMAT(Ch13) + FORMAT(Ch10) + FORMAT(Ch9);
    
          //Cancel validation because loadXML will try to send to parser and it will fail because of external DTD
          MSDOM.validateOnParse(FALSE);
    
          //Insert the header you want/need to use.  It seems you must have root or something like it.
          //You can add a boolean in front of this command to verify that it loaded.
          MSDOM.loadXML('<!DOCTYPE cXML SYSTEM "http://xml.cXML.org/schemas/cXML/1.2.009/InvoiceDetail.dtd">' + Separator
            + '<root></root>');
    
          //If you need to replace the <root> with your first element use this.
          //My first element I wanted to keep was called <cXML>.
          XMLNode := MSDOM.selectSingleNode('root');
          XMLNode2 := MSDOM2.selectSingleNode('cXML');
          MSDOM.replaceChild(XMLNode2,XMLNode);
    
          //If you wanted to keep the <root> use this instead.
          //XMLNode2 := MSDOM2.selectSingleNode('cXML');
          //XMLNode := XMLNode2.cloneNode(TRUE);
          //MSDOM.documentElement.appendChild(XMLNode);
    
          //These were made for testing...
          //MSDOM.save('C:\Test1.xml');
          //MSDOM2.save('C:\Test2.xml');
    
          MSDOM2 := MSDOM;
    

    April, hopefully this works for you too and you can mark this as SOLVED.
    Might not be a bad thing to add into the "How-to" Section. :mrgreen:

    Now to move the file from the services tier to the client... plenty of how-to's on that though.
  • Options
    merlyn2000merlyn2000 Member Posts: 2
    Hi Joe

    As you found out before , You end up with two files and various problems with the xml nodes within the function.

    Where you able to resolve this or did you have to write something different?
  • Options
    Joe_MathisJoe_Mathis Member Posts: 173
    Hi,

    I didn't notice your notification until today. I checked to see if you had posted after your call.

    I did use this code and was successful making a XML file and changing the header. I only ended up with one useful file though. I run through the ChangeXMLFile procedure before saving.

    What are you doing differently?
  • Options
    merlyn2000merlyn2000 Member Posts: 2
    It is all working fine actually and client has gone live.

    :D
Sign In or Register to comment.