how XML extract tags, keys

General Help regarding HMG, Compilation, Linking, Samples

Moderator: Rathinagiri

User avatar
dragancesu
Posts: 931
Joined: Mon Jun 24, 2013 11:53 am
DBs Used: DBF, MySQL, Oracle
Location: Subotica, Serbia

how XML extract tags, keys

Post by dragancesu »

I have XML files, see structure but how (easy) extract some tags, keys or other data

examle data in attcahment
Attachments
efaktura.zip
(290.95 KiB) Downloaded 336 times
edk
Posts: 999
Joined: Thu Oct 16, 2014 11:35 am
Location: Poland

Re: how XML extract tags, keys

Post by edk »

dragancesu wrote: Tue Dec 21, 2021 11:19 am I have XML files, see structure but how (easy) extract some tags, keys or other data

examle data in attcahment
Take a look at this sample, it might be useful to you.
4dragancesu.7z
(218.73 KiB) Downloaded 209 times
User avatar
dragancesu
Posts: 931
Joined: Mon Jun 24, 2013 11:53 am
DBs Used: DBF, MySQL, Oracle
Location: Subotica, Serbia

Re: how XML extract tags, keys

Post by dragancesu »

@edk Thank you, nice

this is for eInvoice goverment project, xml download from site and task is load into invoice data structure

Is posible something like in attachment

1. step xml_vadi
2. step xml_fnc
and result is DBF with data from XML
Attachments
efaktura2.zip
(5.7 KiB) Downloaded 180 times
edk
Posts: 999
Joined: Thu Oct 16, 2014 11:35 am
Location: Poland

Re: how XML extract tags, keys

Post by edk »

dragancesu wrote: Wed Dec 22, 2021 8:22 am @edk Thank you, nice

this is for eInvoice goverment project, xml download from site and task is load into invoice data structure

Is posible something like in attachment

1. step xml_vadi
2. step xml_fnc
and result is DBF with data from XML
It seems to me that a better solution will be to directly process the xml file and save the read values directly to the target databases. Here is an example:

Code: Select all

#require "hbmxml.hbc"

#include "hmg.ch"

#define cTab 	Chr ( 9 )

Function Main

Local cXmlString, cXmlFile, aXML, i, cPath, cKey, xValue, hAttrib, lShowMessage

//To Make DO CASE ... sequence only
Local aPath, Attrib_Key

cXmlFile := GetFile( {{'XML File', '*.xml'}}, 'Select XML', hb_CWD(), .F. , .T. ) 

IF EMPTY( cXmlFile )
	RETURN Nil
ENDIF

lShowMessage := MsgYesNo( "Show processed data?" )

cXmlString := FileStr( cXmlFile )                                                           

aXML := hb_XmlToArray( cXmlString, .T. /* .F. = encode root node also */  )

//To Make DO CASE ... sequence only
	aPath :={}
	STRFILE ("****** DO CASE for the " + hb_FNameNameExt ( cXmlFile ) + " file structure ********" + CRLF + "DO CASE" + CRLF, "Do_case.prg", .F. )
//

FOR i := 1 TO Len ( aXML )
	cPath   := aXML [ i ] [ 1 ]
	cKey    := aXML [ i ] [ 2 ]
	xValue  := aXML [ i ] [ 3 ]
	hAttrib := aXML [ i ] [ 4 ]
	
	
	// <----   here is xml data processing, e.g. writing to databases according to tags and possibly attributes
		
	
	IF lShowMessage
		/* In this example, I'm displaying the read values, the key and the path to the key along with any attributes */
		MsgInfo ('Key Path: "' + cPath + '"' + CRLF + CRLF + ;
			    'Key: "' + cKey + '"' + CRLF + CRLF + ;
			    "Value: " + Left ( xValue, 100 ) /* first 100 chars for test only */ + CRLF + CRLF +;
			    IF( Len( hAttrib ) > 0 , "Attribute(s): " + hb_JsonEncode ( hAttrib ), "" ), "Processing data from an XML file." )
	ENDIF
		
	/* Below I used the read XML file structure to generate a DO CASE ... sequence that I can use to process data from the XML file, e.g. to write values to the appropriate database fields. */
	//Make DO CASE ... sequence
		IF hb_AScan ( aPath, cPath + "/" + cKey, , , .T. ) = 0
			AAdd( aPath, cPath + "/" + cKey)
			STRFILE ( cTab + 'CASE cKey == "' + cKey + '" .AND. cPath == "' + cPath + '"' + CRLF + cTab + cTab + CRLF , "Do_case.prg", .T. )
			IF LEN( hAttrib ) > 0
				FOR EACH Attrib_Key IN hb_HKeys ( hAttrib )
					STRFILE ( cTab + cTab + 'IF hb_HHasKey( hAttrib, "' + Attrib_Key + '" )' + CRLF + ;
							cTab + cTab + cTab + 'xAttribValue := hb_HGet ( hAttrib, "' + Attrib_Key + '" )' + CRLF + CRLF + ; 
							cTab + cTab + 'ENDIF' + CRLF + CRLF , "Do_case.prg", .T. )			
			
				NEXT 
			ENDIF
		ENDIF
	//End Make DO CASE ... sequence
	
NEXT i

STRFILE ("END CASE", "Do_case.prg", .T. )

MsgInfo ('The file "Do_case.prg" has been created with the DO CASE sequence for the "' + hb_FNameNameExt ( cXmlFile ) + '" file structure.' )

Return

*****************************************************************************

STATIC FUNCTION hb_XMLtoArray( cXML, lOmitHeader )
LOCAL pXML
Private __aXML := {}
hb_default( @lOmitHeader, .T. )

pXML := mxmlLoadString( NIL, hb_utf8ToStr( cXML ), MXML_OPAQUE_CALLBACK )
IF !Empty( pXML )
	IF lOmitHeader
		hb_ArrayXML_getnodes( mxmlGetFirstChild( pXML ))
	ELSE
		hb_ArrayXML_getnodes( pXML )
	ENDIF
ENDIF

mxmlDelete( pXML )

RETURN __aXML

*****************************************************************************

STATIC FUNCTION hb_ArrayXML_getnodes( pNode )
LOCAL cKey, pChild, xResult, xValue, cPrevKey, hAttrib, pParrentNode, cPath

DO WHILE !Empty( pNode )

	IF mxmlGetType( pNode ) == MXML_ELEMENT
		cKey   := mxmlGetElement( pNode )
		pChild := mxmlGetFirstChild( pNode )
		xValue := hb_ArrayXML_getnodes( pChild )

		IF xValue == NIL
			xValue := mxmlGetOpaque( pNode )
			IF !( xValue == CRLF )
				cPath := ""
				pParrentNode := mxmlGetParent( pNode )
				DO WHILE !EMPTY ( pParrentNode )
					cPrevkey := mxmlGetElement( pParrentNode )
					IF Left ( cPrevkey, 4 ) <> "?xml"
						cPath := cPrevkey + IF( !Empty( cPath ), "/", "") + cPath 
					ENDIF
					pParrentNode := mxmlGetParent( pParrentNode )
				ENDDO
				hAttrib      := hb_mxmlGetAttrs( pNode )
				AADD( __aXML, { cPath, cKey, xValue, hAttrib } )
				xResult := __aXML
			ENDIF
		ENDIF
		
	ENDIF
	pNode := mxmlGetNextSibling( pNode )
ENDDO
RETURN xResult
The xml file is converted to an array, where the first element of the array is the path to the key, the second element is the key, the third element is the key value, and the fourth element is the optional key attributes.
In my example, the read paths, keys, values and attributes were used to generate a DO CASE sequence that I can use in .prg to process the read data from the XML file.

I think that this procedure can be used with any structure of XML files, but you need to pay attention to use an xml file when creating a DO CASE sequence, in which all possible keys described in the schema are contained in the structure, including the optional ones.
User avatar
dragancesu
Posts: 931
Joined: Mon Jun 24, 2013 11:53 am
DBs Used: DBF, MySQL, Oracle
Location: Subotica, Serbia

Re: how XML extract tags, keys

Post by dragancesu »

Thank you
edk
Posts: 999
Joined: Thu Oct 16, 2014 11:35 am
Location: Poland

Re: how XML extract tags, keys

Post by edk »

Added CDATA support:

Code: Select all

#require "hbmxml.hbc"

#include "hmg.ch"

#define cTab 	Chr ( 9 )

Function Main

Local cXmlString, cXmlFile, aXML, i, cPath, cKey, xValue, hAttrib, lShowMessage := .T.

//To Make DO CASE ... sequence only
Local aPath, Attrib_Key

cXmlFile := GetFile( {{'XML File', '*.xml'}}, 'Select XML', hb_CWD(), .F. , .T. ) 

IF EMPTY( cXmlFile )
	RETURN Nil
ENDIF

lShowMessage := MsgYesNo( "Show processed data?" )

cXmlString := FileStr( cXmlFile )                                                           

aXML := hb_XmlToArray( cXmlString, .T. /* .F. = encode root node also */  )

//To Make DO CASE ... sequence only
	aPath :={}
	STRFILE ("****** DO CASE for the " + hb_FNameNameExt ( cXmlFile ) + " file structure ********" + CRLF + "DO CASE" + CRLF, "Do_case.prg", .F. )
//

FOR i := 1 TO Len ( aXML )
	cPath   := aXML [ i ] [ 1 ]
	cKey    := aXML [ i ] [ 2 ]
	xValue  := aXML [ i ] [ 3 ]
	hAttrib := aXML [ i ] [ 4 ]
	
	
	// <----   here is xml data processing, e.g. writing to databases according to tags and possibly attributes
		
	
	IF lShowMessage
		/* In this example, I'm displaying the read values, the key and the path to the key along with any attributes */
		MsgInfo ('Key Path: "' + cPath + '"' + CRLF + CRLF + ;
			    'Key: "' + cKey + '"' + CRLF + CRLF + ;
			    "Value: " + Left ( xValue, 100 ) /* first 100 chars for test only */ + CRLF + CRLF +;
			    IF( Len( hAttrib ) > 0 , "Attribute(s): " + hb_JsonEncode ( hAttrib ), "" ), "Processing data from an XML file." )
	ENDIF
		
	/* Below I used the read XML file structure to generate a DO CASE ... sequence that I can use to process data from the XML file, e.g. to write values to the appropriate database fields. */
	//Make DO CASE ... sequence
		IF hb_AScan ( aPath, cPath + "/" + cKey, , , .T. ) = 0
			AAdd( aPath, cPath + "/" + cKey)
			STRFILE ( cTab + 'CASE cKey == "' + cKey + '" .AND. cPath == "' + cPath + '"' + CRLF + cTab + cTab + CRLF , "Do_case.prg", .T. )
			IF LEN( hAttrib ) > 0
				FOR EACH Attrib_Key IN hb_HKeys ( hAttrib )
					STRFILE ( cTab + cTab + 'IF hb_HHasKey( hAttrib, "' + Attrib_Key + '" )' + CRLF + ;
							cTab + cTab + cTab + 'xAttribValue := hb_HGet ( hAttrib, "' + Attrib_Key + '" )' + CRLF + CRLF + ; 
							cTab + cTab + 'ENDIF' + CRLF + CRLF , "Do_case.prg", .T. )			
			
				NEXT 
			ENDIF
		ENDIF
	//End Make DO CASE ... sequence
	
NEXT i

STRFILE ("END CASE", "Do_case.prg", .T. )

MsgInfo ('The file "Do_case.prg" has been created with the DO CASE sequence for the "' + hb_FNameNameExt ( cXmlFile ) + '" file structure.' )

Return

*****************************************************************************

STATIC FUNCTION hb_XMLtoArray( cXML, lOmitHeader )
LOCAL pXML
Private __aXML := {}
hb_default( @lOmitHeader, .T. )

pXML := mxmlLoadString( NIL, hb_utf8ToStr( cXML ), MXML_OPAQUE_CALLBACK )
IF !Empty( pXML )
	IF lOmitHeader
		hb_ArrayXML_getnodes( mxmlGetFirstChild( pXML ))
	ELSE
		hb_ArrayXML_getnodes( pXML )
	ENDIF
ENDIF

mxmlDelete( pXML )

RETURN __aXML

*****************************************************************************

STATIC FUNCTION hb_ArrayXML_getnodes( pNode )
LOCAL cKey, pChild, xResult, xValue, cPrevKey, hAttrib, pParrentNode, cPath

DO WHILE !Empty( pNode )

	IF mxmlGetType( pNode ) == MXML_ELEMENT
		cKey := mxmlGetElement( pNode )
		pChild := mxmlGetFirstChild( pNode )
		xValue := hb_ArrayXML_getnodes( pChild )

		IF xValue == NIL
			xValue := mxmlGetOpaque( pNode )
						
			IF !(xValue == CRLF)
				cPath := ""
				pParrentNode := mxmlGetParent( pNode )
				
				IF Left ( cKey, 8 ) == "![CDATA["
					xValue := hb_StrShrink ( mxmlGetCDATA( pNode ), 2 )
					cKey   := mxmlGetElement( mxmlGetParent( pNode ) )
					pParrentNode := mxmlGetParent( mxmlGetParent( pNode ) )
				ENDIF
				
				DO WHILE !EMPTY ( pParrentNode )
					cPrevkey := mxmlGetElement( pParrentNode )
					IF Left ( cPrevkey, 4 ) <> "?xml"
						cPath := cPrevkey + IF( !Empty( cPath ), "/", "") + cPath 
					ENDIF
					pParrentNode := mxmlGetParent( pParrentNode )
				ENDDO
				hAttrib      := hb_mxmlGetAttrs( pNode )
				AADD( __aXML, { cPath, cKey, xValue, hAttrib } )
				
				xResult := __aXML
			ENDIF
		ENDIF
		
	ENDIF
	pNode := mxmlGetNextSibling( pNode )
ENDDO
RETURN xResult


brunellopulix
Posts: 80
Joined: Sat Apr 24, 2010 10:17 am

Re: how XML extract tags, keys

Post by brunellopulix »

Hi

if you find it useful here a procedure that converts xml to Ini

FUNCTION _Xml_To_Ini3(cXml,cIni)
LOCAL aRay := {}
LOCAL aTemp := {}
LOCAL cString := hb_MemoRead(cXml)
LOCAL i
Local nhandle
*
cString := StrTran(cString,'<',CHR(10)+'<')
cString := StrTran(cString,'>','>'+CHR(10))
cString := StrTran(cString,CHR(13),'')
aTemp := hb_ATokens(cString,CHR(10))
*
nHandle := FCreate(cIni)
FOR i := 1 TO Len(aTemp)
IF Empty(aTemp)
If !Empty(aRay) .and. Left(aRay[1],2) != '</'
aRay[1] := StrTran(aRay[1],'<','[')
aRay[1] := StrTran(aRay[1],'>',']')
FWrite(nHandle,CRLF)
FWrite(nHandle,aRay[1]+CRLF)
Endif
aRay := {}
else
aadd(aRay,AllTrim(aTemp))
If Len(aRay) == 3
aRay[1] := StrTran(aRay[1],'<','')
aRay[1] := StrTran(aRay[1],'>','')
FWrite(nHandle,aRay[1]+'='+aRay[2]+CRLF)
aRay := {}
Endif
Endif
Next
*
FClose(nHandle)
execute file cIni
*
RETURN

Brunello Pulix
User avatar
andyglezl
Posts: 1461
Joined: Fri Oct 26, 2012 7:58 pm
Location: Guadalajara Jalisco, MX
Contact:

Re: how XML extract tags, keys

Post by andyglezl »

Hola EDK
Muchas gracias por tu aporte, le veo mucha utilidad.
He estado haciendo pruebas pero ya me perdí, y "necesito una una pequeña ayuda de mis amigos". :)
Los datos que están en Amarillo si los puedo obtener pero los que están en Azul no logro hacerlo.
Trate de hacerlo con el DO_CASE que genera tu ejemplo pero tampoco resultó.
Alguna idea ?
*-------------------------------------------------------------------------------------------------------------------------
Hi EDK
Thank you very much for your contribution, I see it very useful.
I've been doing tests but I'm lost, and "I need a little help from my friends." :)
I can get the data that are in Yellow but the ones that are in Blue I can't.
I tried to do it with the DO_CASE that your example generates but it didn't work either.
Any ideas ?


Pueden agrandar la imagen abriéndola en otra pestaña.
*---------------------------------------------------------------------
The image can be enlarged by opening it in another tab.
imagen_2021-12-28_150252.png
imagen_2021-12-28_150252.png (499.34 KiB) Viewed 2750 times
XMLDemo.zip
(5.63 KiB) Downloaded 150 times
Andrés González López
Desde Guadalajara, Jalisco. México.
edk
Posts: 999
Joined: Thu Oct 16, 2014 11:35 am
Location: Poland

Re: how XML extract tags, keys

Post by edk »

At first glance it looks like some nodes have Nil values but have attributes and child node. Those with Nil values are ignored, so we will have to treat them as if they had empty values. I'll try to prepare something tomorrow.
brunellopulix
Posts: 80
Joined: Sat Apr 24, 2010 10:17 am

Re: how XML extract tags, keys

Post by brunellopulix »

This is the attachment
3-KF-14.jpg
3-KF-14.jpg (175.99 KiB) Viewed 2715 times
Brunello Pulix
Post Reply