VALIDATION XML

weneedweneed Member Posts: 82
I need validate XML file schema using XSD file.
I'm using Business Central SaaS version and cannot use dotnet.
Searching I've found this article but on SaaS not work.
Someone can help me?

Best Answer

  • dreezdreez Member Posts: 72
    edited 2022-03-14 Answer ✓
    Try using TryValidateAgainstSchema method from "Xml Validation" Codeunit and let us know what is the problem that you cannot deal with, so we might be able to help.

Answers

  • RockWithNAVRockWithNAV Member Posts: 1,139
    You can try to work with newly XML data types for XML added.
  • weneedweneed Member Posts: 82
    You can try to work with newly XML data types for XML added.
    Unfortunately it seems that some types of variables are missing to report the same code..
  • dreezdreez Member Posts: 72
    edited 2022-03-14 Answer ✓
    Try using TryValidateAgainstSchema method from "Xml Validation" Codeunit and let us know what is the problem that you cannot deal with, so we might be able to help.
  • weneedweneed Member Posts: 82
    dreez wrote: »
    Try using TryValidateAgainstSchema method from "Xml Validation" Codeunit and let us know what is the problem that you cannot deal with, so we might be able to help.
    Thanks a lot. I did not know this codeunit
  • weneedweneed Member Posts: 82
    Unfortunately I still have a problem. It seems that in my case the validation of the file does not depend on a single xsd file but on multiple files recalled from the main xsd file. Only one xsd file can be passed as an argument when calling the TryValidateAgainstSchema function. In the onprem version we have no difficulty as we can pass the path that contains the files. With the onCloud target this is not possible
  • dreezdreez Member Posts: 72
    edited 2022-03-15
    That is true, you cannot specify more than one schema, unfortunately.
    Look into this module on github: XMLValidation

    I think that you could easily extend that by implementing new external procedure "AddSchema" and I think that XmlDoc DotNet can handle it.

    You would need to open the Pull Request in this repository and someone from Microsoft team would have to review it. After that it would be available OnCloud too.
  • weneedweneed Member Posts: 82
    Thank you. I take this opportunity to ask you if there is a function that deletes empty nodes in an XML. I took a look but I don't seem to find anything
  • dreezdreez Member Posts: 72
    edited 2022-03-15
    I am not aware of ready-made function, but I would try something like the code below. Didn't test it though, but I bet you get the idea.
    procedure RemoveEmptyNodes(var XDoc: XmlDocument)
    var
        XNode: XmlNode;
        XElement: XmlElement;
    begin
        foreach XNode in XDoc.GetDescendantElements() do begin
             XElement := XNode.AsXmlElement();
             if not (XElement.HasElements() or XElement.HasAttributes() or not XElement.IsEmpty()) then
                 XElement.Remove();
        end;
    end;
    
  • weneedweneed Member Posts: 82
    dreez wrote: »
    I am not aware of ready-made function, but I would try something like the code below. Didn't test it though, but I bet you get the idea.
    procedure RemoveEmptyNodes(var XDoc: XmlDocument)
    var
        XNode: XmlNode;
        XElement: XmlElement;
    begin
        foreach XNode in XDoc.GetDescendantElements() do begin
             XElement := XNode.AsXmlElement();
             if not (XElement.HasElements() or XElement.HasAttributes() or not XElement.IsEmpty()) then
                 XElement.Remove();
        end;
    end;
    

    I tried to use your code but I have some problems. Fails to instruction xmlDoc.WriteTo(OutStream);. The error is of a technical nature without description:
     trigger OnAction()
                    var
                        TempBlob: Codeunit "Temp Blob";
                        Filename: Text[250];
                        fileMgt: Codeunit "File Management";
                        InStream: InStream;
                        OutStream: OutStream;
                        InStream2: InStream;
                        xmlDoc: XmlDocument;
                        XNode: XmlNode;
                        XElement: XmlElement;
                    begin
                        fileName := fileMgt.BLOBImportWithFilter(tempBlob, 'Choose XML', fileName, 'XML Files (*.XML)|*.XML', 'xml');
                        if fileName <> '' then begin
                            tempBlob.CreateInStream(InStream);
                            TempBlob.CreateOutStream(OutStream);
                            if XmlDocument.ReadFrom(InStream, xmlDoc) then begin
                                foreach XNode in xmlDoc.GetDescendantElements() do begin
                                    XElement := XNode.AsXmlElement();
                                    if (not XElement.HasElements()) or (NOT XElement.HasAttributes()) or (XElement.IsEmpty()) then begin
                                        XElement.Remove();
                                        xmlDoc.WriteTo(OutStream);
                                    end
                                end
                            end;
                            Filename := 'XmlFileTest.xml';
                            DownloadFromStream(InStream, '', '', '', Filename);
                        end;
                    end;
    
  • dreezdreez Member Posts: 72
    edited 2022-03-15
    The problem is that you are executing the following line
    xmlDoc.WriteTo(OutStream);
    
    as many times as many XmlElements there is in the XmlDocument.

    Try moving it right after this loop ends.


  • weneedweneed Member Posts: 82
    dreez wrote: »
    The problem is that you are executing the following line
    xmlDoc.WriteTo(OutStream);
    
    as many times as many XmlElements there is in the XmlDocument.

    Try moving it right after this loop ends.


    I've tried but same error..
  • dreezdreez Member Posts: 72
    edited 2022-03-15
    Your if statement is wrong. In that form you should change or to and operators.

    I just noticed that XmlElement.IsEmpty() doesn't behave as expected. It is always true.

    Try this instead:
    if not (XElement.HasElements() or XElement.HasAttributes() or (XElement.InnerText() <> '')) then
        XElement.Remove();
    

    It should be working.
  • weneedweneed Member Posts: 82
    My problem however occurs when I write the changes to the file using.
    xmlDoc.WriteTo(OutStream);
    
  • dreezdreez Member Posts: 72
    Please show me your code again, because it seems to work for me.
  • weneedweneed Member Posts: 82
    dreez wrote: »
    Please show me your code again, because it seems to work for me.
    trigger OnAction()
                    var
                        TempBlob: Codeunit "Temp Blob";
                        Filename: Text[250];
                        fileMgt: Codeunit "File Management";
                        InStream: InStream;
                        OutStream: OutStream;
                        InStream2: InStream;
                        xmlDoc: XmlDocument;
                        XNode: XmlNode;
                        XElement: XmlElement;
                    begin
                        fileName := fileMgt.BLOBImportWithFilter(tempBlob, 'Choose XML', fileName, 'XML Files (*.XML)|*.XML', 'xml');
                        if fileName <> '' then begin
                            tempBlob.CreateInStream(InStream);
                            TempBlob.CreateOutStream(OutStream);
                            if XmlDocument.ReadFrom(InStream, xmlDoc) then begin
                                foreach XNode in xmlDoc.GetDescendantElements() do begin
                                    XElement := XNode.AsXmlElement();
                                    if (not XElement.HasElements()) or (NOT XElement.HasAttributes()) or (XElement.IsEmpty()) then begin
                                        XElement.Remove();
                                    end
                                end
                            end;
                            xmlDoc.WriteTo(OutStream);
                            Filename := 'XmlFileTest.xml';
                            DownloadFromStream(InStream, '', '', '', Filename);
                        end;
                    end;
    
  • dreezdreez Member Posts: 72
    edited 2022-03-15
    As I said, your IF statement is wrong, so you are removing elements that cannot be removed, because the XML file would be broken.

    Please replace this line with the line from my previous comment, which is
    if not (XElement.HasElements() or XElement.HasAttributes() or (XElement.InnerText() <> '')) then
        XElement.Remove();
    
  • weneedweneed Member Posts: 82
    Ok. Error was skipped but empty elements remain3n3dy8d23u3d.png
  • dreezdreez Member Posts: 72
    It is because there is something weird with XmlElement.IsEmpty().

    I thought that checking the XmlElement.InnerText() will do the thing, but maybe not all the cases?
  • dreezdreez Member Posts: 72
    weneed wrote: »
    Ok. Error was skipped but empty elements remain3n3dy8d23u3d.png

    I've just checked and that example works fine for me. Are you sure we're on the same page here?
    local procedure ImportXMLAndRemoveEmptyNodes()
    var
        FileManagement: Codeunit "File Management";
        TempBlob: Codeunit "Temp Blob";
        Filename: Text;
        InStream: InStream;
        OutStream: OutStream;
        XDocument: XmlDocument;
    begin
        Filename := FileManagement.BLOBImportWithFilter(TempBlob, 'Choose XML', FileName, 'XML Files (*.XML)|*.XML', 'xml');
        if FileName <> '' then begin
            TempBlob.CreateInStream(InStream);
            TempBlob.CreateOutStream(OutStream);
            if XmlDocument.ReadFrom(InStream, XDocument) then
                RemoveEmptyNodes(XDocument);
            XDocument.WriteTo(OutStream);
        end;
        Filename := 'XmlFileTest.xml';
        DownloadFromStream(InStream, '', '', '', Filename);
    end;
    
    local procedure RemoveEmptyNodes(var XDoc: XmlDocument)
    var
        XNode: XmlNode;
        XElement: XmlElement;
    begin
        foreach XNode in XDoc.GetDescendantElements() do begin
            XElement := XNode.AsXmlElement();
            if not (XElement.HasElements() or XElement.HasAttributes() or (XElement.InnerText() <> '')) then
                XElement.Remove();
         end;
    end;
    
    I am executing procedure ImportXMLAndRemoveEmptyNodes() OnAction trigger.

    Have you changed the condition
    not XmlElement.IsEmpty()
    
    to
    XmlElement.InnerText <> 0
    
    ?
  • weneedweneed Member Posts: 82
    Hi Dreez,
    I have reported exactly the code above and the result is that the <Fax /> node is not deleted from the file although it is empty.
  • weneedweneed Member Posts: 82
    I've solved problem.
    This is my code:
    trigger OnAction()
                    var
                        FileManagement: Codeunit "File Management";
                        TempBlob: Codeunit "Temp Blob";
                        Filename: Text;
                        InStream: InStream;
                        OutStream: OutStream;
                        XDocument: XmlDocument;
                    begin
                        Filename := FileManagement.BLOBImportWithFilter(TempBlob, 'Choose XML', FileName, 'XML Files (*.XML)|*.XML', 'xml');
                        if FileName <> '' then begin
                            TempBlob.CreateInStream(InStream);
                            if XmlDocument.ReadFrom(InStream, XDocument) then begin
                                RemoveEmptyNodes(XDocument);
                                Clear(TempBlob);
                                TempBlob.CreateInStream(InStream);
                                TempBlob.CreateOutStream(OutStream);
                                XDocument.WriteTo(OutStream);
                                Filename := 'XmlFileTest.xml';
                                DownloadFromStream(InStream, '', '', '', Filename);
                            end
                        end;
    
                    end;
    
        local procedure RemoveEmptyNodes(var XDoc: XmlDocument)
        var
            XNode: XmlNode;
            XElement: XmlElement;
            Valore: Text;
            cr: Char;
            lf: char;
        begin
            cr := 13;
            lf := 10;
            foreach XNode in XDoc.GetDescendantElements() do begin
                XElement := XNode.AsXmlElement();
               
                Valore := delchr(XElement.InnerText(), '=', ' ');
                valore := DELCHR(valore, '=', FORMAT(cr));
                valore := DELCHR(valore, '=', FORMAT(lf));
                if Valore = '' then
                    XElement.Remove();
            end;
        end;
    
  • weneedweneed Member Posts: 82
    So now I've a strange result. XML File processed remove empty node ma leave carriage return. This is an example:

    <?xml version="1.0" encoding="UTF-16" standalone="no"?>

    <Root>
    <Customer>
    <No>01121212</No>
    <Name>Spotsmeyer's Furnishings</Name>
    <city>Miami</city>


    </Customer>
    <Customer>
    <No>01445544</No>
    <Name>Progressive Home Furnishings</Name>
    <city>Chicago</city>

    <condizioni>
    <code>3060</code>
    <description>30, 60 giorni</description>
    </condizioni>
    </Customer>
    </Root>

    There is a way to remove also end of line (cr char)?
  • CrunchCrunch Member Posts: 38
    Following this, as I also had this issue with IsEmpty.
    I went through the nodelist backwards to also remove a potential parent, whose child elements have all been removed. Otherwise you can still end up with empty Elements.
  • weneedweneed Member Posts: 82
    How did you do it? can you show me how to build the code?
Sign In or Register to comment.