CODESYS V3.5 SP14 Patch 2 XmlResultFormatter 05a4fdda-1086-47b5-ad10-7c2bac568355 a429e267-db3b-4872-871e-1ca509b971e2 XmlPac(); IF NOT DoneGeneratingXml THEN xUnitFileComposer.Format(NumberOfTestSuites := NumberOfTestSuitesFinished, NumberOfTestCases := NumberOfTestCases, NumberOfSuccessfulTestCases := NumberOfSuccessfulTestCases, NumberOfFailedTestCases := NumberOfFailedTestCases, Busy => BusyGeneratingXml); DoneGeneratingXml := NOT BusyGeneratingXml; END_IF; 917c49f4-3643-42fe-b791-ac2edc0df35b Default file access mode Is buffer Initialised? Buffer : ARRAY [0..GVL_Param_XmlControl.MaxFileSize-1] OF BYTE; Or I_TestResultFormatter This function block reports the results from the tests into a xUnit compatible XmlFile The total number of test suites The total number of test cases (for all test function blocks) The total number of test cases that had all ASSERTS successful The total number of test cases that had at least one ASSERT failed Responsible for formatting the xml file skeleton <testsuites> => the aggregated result OF all xunit testfiles <testsuite> => the output from a single TestSuite <properties> => the defined properties at test execution <property> => name/value pair for a single property ... </properties> <error></error> => optional information, in place of a test case - normally if the tests in the suite could not be found etc. <testcase> => the results from executing a test method <system-out> => data written to System.out during the test run <system-err> => data written to System.err during the test run <skipped/> => test was skipped <failure> => test failed <error> => test encountered an error </testcase> ... </testsuite> ... </testsuites> Busy := TRUE; NumberOfTestsInCurrentSuite := 3; IF NumberOfFailedTestCases > 0 THEN LOGSTR(msgCtrlMask := UDINT_TO_DWORD(CmpLog.LogClass.LOG_INFO), msgFmtStr := '%s', strArg := '| ========= Export Xml Report ========='); (* <?xml version="1.0" encoding="UTF-8"?> *) Xml.writeDocumentHeader(Header := '<?xml version="1.0" encoding="UTF-8"?>'); xml.NewTag('testsuites'); FOR SuiteCounter := 1 TO NumberOfTestSuites BY 1 DO (* <testsuite> *) xml.NewTag('testsuite'); (* name="name" *) //TestSuiteInstancePath := 'TestInstancePath' TestSuiteName := 'TestSuiteName'; Xml.NewParameter('name', TestSuiteName); (* <testsuite errors="errors"> *) //NumberOfFailedTestsInCurrentSuite := UINT_TO_STRING(NumberOfFailedTestCases); Xml.NewParameter('errors', UINT_TO_STRING(NumberOfFailedTestsInCurrentSuite)); (* <testsuite errors="errors" tests="tests">*) Xml.NewParameter('tests', UINT_TO_STRING(NumberOfTestsInCurrentSuite)); (* close testsuite *) Xml.CloseTag(); FOR TestCounter := 1 TO NumberOfTestsInCurrentSuite BY 1 DO //IF GVL_CfUnit.TestSuiteAddresses[SuiteCounter]^.Tests[TestCounter].IsFailed() THEN IF TRUE THEN (* get testcase name *) //TestCaseName := GVL_CfUnit.TestSuiteAddresses[SuiteCounter]^.Tests[TestCounter].GetName(); TestCaseName := 'TestCaseName'; Xml.NewTag('testcase'); Xml.NewParameter('name', TestCaseName); (* close testcase *) Xml.CloseTag(); Xml.NewTag('failure'); //AssertResult := GVL_CfUnit.TestSuiteAddresses[SuiteCounter]^.AssertResults.AssertResults[TestCounter]; //Xml.NewParameter('message', AssertResult.Message ); Xml.NewParameter('message', 'AssertResultMessage' ); Xml.NewTagData('Assertion failed'); (* close failure *) Xml.CloseTag(); END_IF END_FOR END_FOR (* Close testsuites *) Xml.CloseTag(); (* Open, save and close the file *) OpenSaveAndClose(); END_IF Busy := FALSE; TRUE: the retain variables are initialized (reset warm / reset cold) TRUE: the instance will be copied to the copy code afterward (online change) FB_Init is always available implicitly and it is used primarily for initialization. The return value is not evaluated. For a specific influence, you can also declare the methods explicitly and provide additional code there with the standard initialization code. You can evaluate the return value. // Set Filepath szFilename := GVL_Param_XmlControl.FilePath; // Set buffer and flag Xml.SetBuffer(pString := ADR(Buffer), iSizeOf := SIZEOF(Buffer)); xBufferInitialised := TRUE; // write buffer to a file File.Open(Filename := szFilename, FileAccessMode := stFileAccessMode); File.Save(pString := ADR(Buffer), xml.Length ); File.Close(); Xml.ClearBuffer(); 6597c0b3-0890-4d5d-8d3a-2d0d0b2355db Closes the current file IF FileHandle <> SysFile.SysTypes.RTS_INVALID_HANDLE THEN Close := SysFile.SysFileClose(hFile := FileHandle); END_IF File name can contain an absolute or relative path to the file. Path entries must be separated with a Slash (/) Opens a file FileHandle := SysFile.SysFileOpen(szFile := Filename, am := FileAccessMode, pResult := ADR(Open)); Call with ADR(); Call with SIZEOF(); IF FileHandle <> SysFile.SysTypes.RTS_INVALID_HANDLE THEN FileSize := SysFile.SysFileread(hFile := FileHandle, pbyBuffer := pString, ulSize := Size, pResult := ADR(Read)); END_IF Call with ADR(); Call with SIZEOF(); Saves the contents of the buffer into a file Be sure to call Open() before calling Save() IF FileHandle <> SysFile.SysTypes.RTS_INVALID_HANDLE THEN SysFile.SysFileWrite(hFile := FileHandle, pbyBuffer := pString, ulSize := Size, pResult := ADR(Save)); END_IF c9bec8fc-2de3-45f4-b839-e6c4276a6b5a This functionblock acts as a stream buffer for use with FB_XmlControl Clears the buffer and sets the length to 0 IF (pStrBuf = 0) OR (udiBufsize = 0) THEN RETURN; END_IF FOR i := 0 TO (udiBufsize-1) DO pStrBuf[i] := 0; END_FOR udiLength := 0; Copies a string from the character buffer udiLoop := 0; pByteCopy := ADR(Copy); pByteBuffer := pStrBuf + udiStart - 1; WHILE(udiLoop < SIZEOF(Copy)) AND udiStart - 1 + udiLoop < udiLength AND udiStart + udiLoop < udiEnd DO pByteCopy^ := pByteBuffer^; udiLoop := udiLoop + 1; pByteCopy := ADR(Copy) + udiLoop; pByteBuffer := pStrBuf + udiStart + udiLoop -1; END_WHILE; IF udiLoop = SIZEOF(Copy) THEN XmlError := E_XmlError.ErrorStringLen; ELSIF udiStart - 1 + udiLoop = udiLength THEN XmlError := E_XmlError.ErrorMaxBufferLen; ELSE XmlError := E_XmlError.OK; END_IF; pByteCopy^ := 0; udiCopyLen := udiLoop; iLoop := 0; pByteCut := ADR(CutOff); pByteBuffer := pStrBuf + udiStartPos - 1; WHILE pByteBuffer^ <> 0 AND(iLoop < SIZEOF(CutOff)) AND udiStartPos -1 + iLoop < udiLength DO pByteCut^ := pByteBuffer^; iLoop := iLoop + 1; pByteCut := ADR(CutOff) + iLoop; pByteBuffer := pStrBuf + udiStartPos - 1 + iLoop; END_WHILE; IF pByteBuffer^ = 0 THEN XmlError := E_XmlError.OK; ELSIF iLoop = SIZEOF(CutOff) THEN XmlError := E_XmlError.ErrorStringLen; ELSIF udiStartPos -1 + iLoop = udiLength THEN XmlError := E_XmlError.ErrorMaxBufferLen; END_IF; pByteCut^ := 0; udiLength := udiStartPos -1; pByteBuffer := pStrBuf + udiStartPos - 1; pByteBuffer^ := 0; udiCutLen := iLoop; Find a searchstring in the buffer and returns its position. It's possible to add a preffered startposition within buffer udiLoop := 0; iSearch := 0; pBuffer := pStrBuf + udiStartPos; pSearch := ADR( sSearchString); WHILE(pSearch^ <> 0 ) AND udiLoop + udiStartPos < udiLength DO IF pBuffer^ <> pSearch^ THEN udiLoop := udiLoop + 1; pBuffer := pStrBuf + udiStartPos + udiLoop; pSearch := ADR( sSearchString); iSearch := 0; ELSE iSearch := iSearch +1; pBuffer := pStrBuf + udiStartPos + udiLoop + iSearch; pSearch := ADR( sSearchString ) + iSearch; END_IF; END_WHILE; Find := udiLoop + 1 + udiStartPos; udiLoop := 0; udiSearch := 0; pBuffer := pStrBuf + udiLength; pSearch := ADR(sSearchString); WHILE(pSearch^ <> 0 ) AND udiLoop < udiLength DO IF pBuffer^ <> pSearch^ THEN udiLoop := udiLoop + 1; pBuffer := pStrBuf + udiLength - udiLoop; pSearch := ADR( sSearchString); udiSearch := 0; ELSE udiSearch := udiSearch + 1; pBuffer := pStrBuf + udiLength - udiLoop + udiSearch; pSearch := ADR( sSearchString ) + udiSearch; END_IF; END_WHILE; FindBack := udiLength - udiLoop + 1; Set buffer adress (ADR ...) Set buffer size (SIZEOF ...) Sets the Buffer IF (pbyAdr = 0) OR (udiSize = 0) THEN Set := FALSE; RETURN; END_IF; udiBufSize := udiSize; pStrBuf := pbyAdr; Set := TRUE; Appends a string to the buffer pByteIn := ADR(Append); pByteBuffer := pStrBuf + udiLength; WHILE pByteIn^ <> 0 AND (udiLength < udiBufSize ) DO pByteBuffer^ := pByteIn^; udiLength := udiLength + 1; pByteIn := pByteIn + 1; pByteBuffer := pByteBuffer + 1; END_WHILE; pByteBuffer := pStrBuf + udiLength; (* String End *) pByteBuffer^ := 0; (* null terminated string *) Read current Buffersize BufferSize := udiBufSize; Length := udiLength; udiLength := Length; Prepends a string to the buffer //save current buffer to temporary buffer pByteBuffer := pStrBuf; FOR i := 0 TO (udiBufsize-1) DO TempBuf[i] := pByteBuffer[i]; END_FOR pByteIn := ADR(Prepend); pByteBuffer := pStrBuf; WHILE pByteIn^ <> 0 AND (udiLength < udiBufSize ) DO pByteBuffer^ := pByteIn^; udiLength := udiLength + 1; pByteIn := pByteIn + 1; pByteBuffer := pByteBuffer + 1; END_WHILE; pTempBuf := ADR(TempBuf); WHILE pTempBuf^ <> 0 AND (udiLength < udiBufSize ) DO pByteBuffer^ := pTempBuf^; pTempBuf := pTempBuf + 1; pByteBuffer := pByteBuffer + 1; END_WHILE; pByteBuffer := pStrBuf + udiLength; (* String End *) pByteBuffer^ := 0; (* nul terminated string *) fbccbed5-94ee-455b-a9d5-e8b750ed732e Organizes parsing and composing of XML data. Data can be treated as STRING or char array. Filebuffersize is default 4096 bytes and can be set via Gvl_Param_XmlControl Usage example: 1. Initiate VAR XML : FB_XMLControl; Buffer: STRING(GVL_Param_XmlControl.udiMaxFileSize); //or Buffer: ARRAY [0..GVL_Param_XmlControl.udiMaxFileSize] OF BYTE; END_VAR XML.pBuffer: = ADR (buffer); XML.LenBuffer: = SIZEOF (buffer); Add your own preferred fileheader like so: XML: <?xml version="1.0" encoding="UTF-8"?> XML.WriteDocumentHeader( '<?xml version="1.0" encoding="UTF-8"?>'); 2. Compose a tag with a parameters: XML: <MyTag ParaName = "11" /> XML.newTag(sTagName: = 'MyTag'); XML.newPara(sName: = 'ParaName', sPara: = sValue); XML.CloseTag(); 3. Add tag value: XML: <MyTag> MyText </ MyTag> XML.newTag(sName := 'MyTag'); XML.newTagData(sTagData :='MyText'); XML.CloseTag(); 4. Jump to the beginning of the XML data XML.toStartBuffer(); 5. Add a comment: XML: <!-- MyComment --> XML.newComment(sTagName: = 'MyComment'); 6. Returns the next tag from the current position in buffer XML.NextTag(); 7. Output the parameter of the tag XML.NextPara(sPara: = LastValue); Feedback: sPara returns the value found (string) Creates a new Tag: XML: <MyTag> XML.NewTag(Name: = 'MyTag'); IF xTagOpen THEN Buffer.Append := TAG_CLOSE; END_IF; Buffer.Append := TAG_OPEN; Buffer.Append := Name; xTagOpen := TRUE; TagList.Append := FORWARD_SLASH; TagList.Append := Name; Clears the contents of the entire buffer udiSearchPos := 0; TagListSeek.Length := 0; Buffer.Length := 0; sTagsSeek := ''; sTag := ''; Closes a Tag: XML: <MyTag />' Method: XML.CloseTag(); IF xTagOpen THEN Buffer.Append := END_TAG_CLOSE; iSelect := TagList.FindBack(sSearchString := FORWARD_SLASH); CloseTag := TagList.CutOff(udiStartPos:= iSelect); xTagOpen := FALSE; ELSE iSelect := TagList.FindBack(sSearchString := FORWARD_SLASH); CloseTag := TagList.CutOff(udiStartPos:= iSelect); Buffer.Append := TAG_OPEN; Buffer.Append := CloseTag; Buffer.Append := TAG_CLOSE; END_IF Adds a comment; XML: <!-- MyComment --> XML.NewComment(Comment: = 'MyComment'); IF xTagOpen THEN Buffer.Append := TAG_CLOSE; xTagOpen := FALSE; END_IF; Buffer.Append := OPEN_COMMENT; Buffer.Append := Comment; Buffer.Append := CLOSE_COMMENT; Must be called after opening a new tag XML.NewParameter(Name: = 'ParaName', Value: = 'Value'); Buffer.Append := SPACE; Buffer.Append := Name; Buffer.Append := EQUALS; Buffer.Append := QUOTE; Buffer.Append := Value; Buffer.Append := QUOTE; Buffer.Append := TAG_CLOSE; Buffer.Append := Data; xTagOpen := FALSE; iSelectStart := Buffer.Find(sSearchString := TAG_OPEN, udiStartPos := udiSearchPos); iSelectEnd := Buffer.Find(sSearchString := TAG_CLOSE, udiStartPos := iSelectStart); iSelectStartPara := Buffer.Find(sSearchString := SPACE, udiStartPos := iSelectStart); iSelectTagClose := Buffer.Find(sSearchString := FORWARD_SLASH, udiStartPos := iSelectStart); IF iSelectStart < Buffer.Length AND iSelectEnd < Buffer.Length THEN udiSearchPos := iSelectEnd; sTag := Buffer.Copy( udiStart:= iSelectStart + 1, udiEnd:= iSelectEnd); IF iSelectEnd < iSelectStartPara THEN NextTag := Buffer.Copy( udiStart:= iSelectStart + 1, udiEnd:= iSelectEnd); ELSE NextTag := Buffer.Copy( udiStart:= iSelectStart + 1, udiEnd:= iSelectStartPara); END_IF; IF iSelectTagClose = iSelectStart + 1 THEN iSelect := TagListSeek.FindBack(sSearchString := FORWARD_SLASH); TagListSeek.CutOff(udiStartPos:= iSelect); ELSIF iSelectTagClose > iSelectStart AND iSelectTagClose < iSelectEnd THEN TagListSeek.Append := FORWARD_SLASH; TagListSeek.Append := NextTag; iSelect := TagListSeek.FindBack(sSearchString := FORWARD_SLASH); TagListSeek.CutOff(udiStartPos:= iSelect); ELSE TagListSeek.Append := FORWARD_SLASH; TagListSeek.Append := NextTag; END_IF; ELSE udiSearchPos := Buffer.Length; NextTag := ''; END_IF; Jump to the beginning of the XML data XML.ToStartBuffer(); udiSearchPos := 0; TagListSeek.Length := 0; sTagsSeek := ''; sTag := ''; Add your own preffered fileheader like: XML: <?xml version="1.0" encoding="UTF-8"?> XML.WriteDocumentHeader('<?xml version="1.0" encoding="UTF-8"?>'); //Buffer.Prepend := Concat(Header, CR_LF); Buffer.Prepend := Header; iSelectEndPara := Buffer.Find(sSearchString := EQUALS, udiStartPos := iSelectStartPara); IF iSelectStartPara < iSelectEnd AND iSelectEndPara < iSelectEnd THEN Name := Buffer.Copy( udiStart := iSelectStartPara + 1, udiEnd := iSelectEndPara); iSelectStartValue := Buffer.Find(sSearchString := QUOTE, udiStartPos := iSelectEndPara); iSelectEndValue := Buffer.Find(sSearchString := QUOTE, udiStartPos := iSelectStartValue); Parameter := Buffer.Copy( udiStart := iSelectStartValue + 1, udiEnd := iSelectEndValue); iSelectStartPara := iSelectEndValue + 1; ELSE iSelectEndValue := Buffer.Find(sSearchString := TAG_OPEN, udiStartPos := udiSearchPos); Name := Buffer.Copy( udiStart := udiSearchPos + 1, udiEnd := iSelectEndValue); END_IF; NextParameter := Name; Buffer.Set(pbyAdr := pString, udiSize := iSizeOf); TagList.Set(pbyAdr := ADR(sTags), udiSize := SIZEOF(sTags)); TagListSeek.Set(pbyAdr := ADR(sTagsSeek), udiSize := SIZEOF(sTagsSeek)); Tag.Set(pbyAdr := ADR(sTag), udiSize := SIZEOF(sTag)); Length := Buffer.Length; f48b7de2-5c07-4763-a8be-3d8ab3c1f58c 7e465938-43b3-4ac5-bae5-c19fce2bd2bf Maximum filesize in bytes (32 kb default) Filepath for testresult.xml, e.g.: c:/jenkins/workspace/XmlResultTester/testresult.xml 69588962-bd3e-4958-957e-7bfb94609af1 The total number of test suites The total number of test cases (for all test function blocks) The total number of test cases that had all ASSERTS successful The total number of test cases that had at least one ASSERT failed Responsible for formatting the xml file skeleton <testsuites> => the aggregated result OF all xunit testfiles <testsuite> => the output from a single TestSuite <properties> => the defined properties at test execution <property> => name/value pair for a single property ... </properties> <error></error> => optional information, in place of a test case - normally if the tests in the suite could not be found etc. <testcase> => the results from executing a test method <system-out> => data written to System.out during the test run <system-err> => data written to System.err during the test run <skipped/> => test was skipped <failure> => test failed <error> => test encountered an error </testcase> ... </testsuite> ... </testsuites> Imported from XML parsing and composing library (xml-pac) 0.3.1.0 Imported from XML parsing and composing library (xml-pac) 0.3.1.0