ReGex para buscar TAG de XML

Projeto [x]Harbour - Compilador de código aberto compatível com o Clipper.

Moderador: Moderadores

Avatar do usuário
clodoaldomonteiro
Usuário Nível 4
Usuário Nível 4
Mensagens: 821
Registrado em: 30 Dez 2006 13:17
Localização: Teresina-PI
Contato:

ReGex para buscar TAG de XML

Mensagem por clodoaldomonteiro »

Olá!
No xHarbour tenho a necessidade de pegar todas as TAGs "lan:LancamentoContabilItem" de uma string XML usando a função hb_RegexAll() -> Array.

Código: Selecionar todos

...
                  cNode    := 'lan:LancamentoContabilItem'
                  cPattern := "<" + cNode + ">([^<]*)</" + cNode + ">"
                  aLancamentoContabilItem := HB_RegExAll( cPattern, cLancamentoXML )
...
String XML:

Código: Selecionar todos

   <lan:LancamentoContabil><!--Obs.DIA: RTPag->001527069326, CAP:416108|414381 -->
      <lan:numeroRegiOuLancamento>01027804</lan:numeroRegiOuLancamento>
      <lan:dataRegiOuLancamento>2024-01-24</lan:dataRegiOuLancamento>
      <lan:tipoLancContabil>1</lan:tipoLancContabil>
      <lan:tipoMoviContabil>2</lan:tipoMoviContabil>
      <lan:historicoRegiContabil>DIA Cod: 0126798, RTP[LCP 88.193] Pagamento Extra, Cód RTP: 001527-069326, Tipo: 15 - RPPS (Demais)</lan:historicoRegiContabil>
      <lan:codigoUnidOrcamentaria>000101</lan:codigoUnidOrcamentaria>
      <lan:LancamentoContabilItem><!--CAP:416108 -->
         <lan:tipoNatuLancamento>1</lan:tipoNatuLancamento>
         <lan:valorLancado>2067.26</lan:valorLancado>
         <lan:ContaCorrente>
            <cc:DisponibilidadeFinanceira>
               <cc:fonteRecurso>
                   <gen:ioc>1</gen:ioc>
                   <gen:tipoFontRecurso>500</gen:tipoFontRecurso>
                   <gen:tipoCompFontRecurso>9999</gen:tipoCompFontRecurso>
               </cc:fonteRecurso>
               <cc:atributoSupeFinanceiro xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
               <cc:codigoContContabil>821130200</cc:codigoContContabil>
            </cc:DisponibilidadeFinanceira>
         </lan:ContaCorrente>
      </lan:LancamentoContabilItem>
      <lan:LancamentoContabilItem><!--CAP:414381 -->
         <lan:tipoNatuLancamento>2</lan:tipoNatuLancamento>
         <lan:valorLancado>2067.26</lan:valorLancado>
         <lan:ContaCorrente>
            <cc:DisponibilidadeFinanceira>
               <cc:fonteRecurso>
                   <gen:ioc>1</gen:ioc>
                   <gen:tipoFontRecurso>500</gen:tipoFontRecurso>
                   <gen:tipoCompFontRecurso>9999</gen:tipoCompFontRecurso>
               </cc:fonteRecurso>
               <cc:atributoSupeFinanceiro xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
               <cc:codigoContContabil>821140200</cc:codigoContContabil>
            </cc:DisponibilidadeFinanceira>
         </lan:ContaCorrente>
      </lan:LancamentoContabilItem>
   </lan:LancamentoContabil>

</lan:LancamentosContabeis>

O erro acontece pq está retornando NIL.
Grato pela ajuda de todos
At. Clodoaldo Monteiro
Linguagens: Clipper / Harbour
Área de Atuação: Sistemas de gestão para Prefeituras Municipais
Fones: (86)3223-0653, 98859-0236
www.simplesinformatica.com.br
Avatar do usuário
Itamar M. Lins Jr.
Administrador
Administrador
Mensagens: 7928
Registrado em: 30 Mai 2007 11:31
Localização: Ilheus Bahia
Curtiu: 1 vez

ReGex para buscar TAG de XML

Mensagem por Itamar M. Lins Jr. »

Olá!
Acredito que hb_RegExALl() não é para essa finalidade.
Isso é para EXPRESSÕES REGULARES.
2024-11-14_11-14-39.png
No caso vc usa a função do José Quintas, para pegar as tags do XML. É mais fácil.

Saudações,
Itamar M. Lins Jr.
Saudações,
Itamar M. Lins Jr.
Avatar do usuário
JoséQuintas
Administrador
Administrador
Mensagens: 20267
Registrado em: 26 Fev 2007 11:59
Localização: São Paulo-SP

ReGex para buscar TAG de XML

Mensagem por JoséQuintas »

Pelas funções da sefazclass talvez fique mais tranquilo.
Vai usar basicamente 2 funções.

Como é o XML:

[campo]xxxx[/campo]

o mesmo nome marca início e fim, a diferença é que o fim tem a barra.
Isso se repete por todo o XML, com níveis.

XmlNode() é a que pega um determinado campo
E tem multipleNodeToArray(), que é pra pegar quando tem uma lista de nomes repetidos.

É ir pegando os blocos e separando o que interessa.

Por exemplo

Código: Selecionar todos

cXMLLancamento := XmlNode( cXml, "lan:LancamentoContabil" )
   cHistorico := XmlNode( cXmlLancamento, "lan:historicoRegiContabil" )
Também existe função que transforma XML em HASH, é outra alternativa.
José M. C. Quintas
Harbour 3.2, mingw, gtwvg mt, fivewin 25.04, multithread, dbfcdx, MySQL, ADOClass, PDFClass, SefazClass, (hwgui mt), (hmg3), (hmg extended), (oohg), PNotepad, ASP, stored procedure, stored function, Linux (Flagship/harbour 3.2)
"The world is full of kings and queens, who blind our eyes and steal our dreams Its Heaven and Hell"

https://github.com/JoseQuintas/
Avatar do usuário
clodoaldomonteiro
Usuário Nível 4
Usuário Nível 4
Mensagens: 821
Registrado em: 30 Dez 2006 13:17
Localização: Teresina-PI
Contato:

ReGex para buscar TAG de XML

Mensagem por clodoaldomonteiro »

A questão de acionar aqui os amigos, é que ao usar o método :selectSingleNode() em um XML grande (50MB), o processamento ficou muito lento, caiu de 2.000 /s para 200 /s, acho que por nesse método, poder usar XPath.
JoséQuintas escreveu:Pelas funções da sefazclass talvez fique mais tranquilo.
Vai usar basicamente 2 funções.

Como é o XML:

[campo]xxxx[/campo]

o mesmo nome marca início e fim, a diferença é que o fim tem a barra.
Isso se repete por todo o XML, com níveis.

XmlNode() é a que pega um determinado campo
E tem multipleNodeToArray(), que é pra pegar quando tem uma lista de nomes repetidos.
...

Código: Selecionar todos

cXMLLancamento := XmlNode( cXml, "lan:LancamentoContabil" )
   cHistorico := XmlNode( cXmlLancamento, "lan:historicoRegiContabil" )
Bom, aí recorri às funções do @Quintas, e já às uso faz tempo, mas na função multipleNodeToArray(), que pega os lançamentos de um Node (String com mais de 3MB) de lançamentos q tem mais 2.000 registros, o processo ficou muito lento, acho que por ser uma varredura numa string gigante.
Itamar M. Lins Jr. escreveu:Olá!
Acredito que hb_RegExALl() não é para essa finalidade.
Isso é para EXPRESSÕES REGULARES.
A função dá sim pra fazer o que falei acima, inclusive testando num serviço web, alguns regex, traz os resultados, mas vi que no Harbour tem alguma limitação com a biblioteca regex ou a string regex não está devida equacionada.
Exemplo:

Código: Selecionar todos

//Retorna em uma array todos os comentários da string XML.
            cPattern := "<!--(.*?)-->"
            aMatches := hb_REGEXALL( cPattern, cLancamentoXML )
Estou agora testando o método :getElementsByTagName() que á mais simples que :selectSingleNode(), e vendo se fica com a velocidade de processamento que imagino deva ter (vi num post aqui do @Rochinha).

Código: Selecionar todos

...
         objXml := TOLEAUTO():New( "Microsoft.XMLDOM" )
         objXml:async := .F.
         objXml:validateOnParse := .F.

         If objXml:load(mArq_) == .F.
            MsgError("Erro ao carregar o arquivo XML.")
            Break
         Endif

         nodes          := objXml:getElementsByTagName("lan:PrestacaoContas")
         nodePrestConta := nodes:item(0)
         
         mCodigoUnidGestora := xmlGetTextNode(nodePrestConta, "aux:codigoUnidGestora")
...
...
   //Retorna valor de :Text de um :Item Node.
Function xmlGetTextNode(oXml, cNodeName)
   Local oNodes, oNode, r := ''

   // Verifica se o XML é válido
   IF Empty(oXml)
      Return r
   ENDIF

   // Busca os nós pelo nome
   oNodes := oXml:getElementsByTagName(cNodeName)

   // Verifica se encontrou ao menos um nó
   IF oNodes:length > 0
      oNode := oNodes:Item(0)
      IF !Empty(oNode)
         r := oNode:text
      ENDIF
   ENDIF

   // Retorna o valor ou Empty se não encontrado e garantindo q ainda assim é o Text.
   Return r
Grato pela atenção de todos.
At. Clodoaldo Monteiro
Linguagens: Clipper / Harbour
Área de Atuação: Sistemas de gestão para Prefeituras Municipais
Fones: (86)3223-0653, 98859-0236
www.simplesinformatica.com.br
Avatar do usuário
JoséQuintas
Administrador
Administrador
Mensagens: 20267
Registrado em: 26 Fev 2007 11:59
Localização: São Paulo-SP

ReGex para buscar TAG de XML

Mensagem por JoséQuintas »

Já que a finalidade é desse XML grandão..... talvez isto na parte de lançamentos

Código: Selecionar todos

aList := hb_RegExSplit( "tagdelancamento", cXml  )
Mesmo que divida em XMLs inválidos, vai separar cada lançamento que continuará podendo ser tratado por XmlNode().
Se o problema de demora for nessa divisão, vai agilizar.
José M. C. Quintas
Harbour 3.2, mingw, gtwvg mt, fivewin 25.04, multithread, dbfcdx, MySQL, ADOClass, PDFClass, SefazClass, (hwgui mt), (hmg3), (hmg extended), (oohg), PNotepad, ASP, stored procedure, stored function, Linux (Flagship/harbour 3.2)
"The world is full of kings and queens, who blind our eyes and steal our dreams Its Heaven and Hell"

https://github.com/JoseQuintas/
Avatar do usuário
clodoaldomonteiro
Usuário Nível 4
Usuário Nível 4
Mensagens: 821
Registrado em: 30 Dez 2006 13:17
Localização: Teresina-PI
Contato:

ReGex para buscar TAG de XML

Mensagem por clodoaldomonteiro »

O primeiro parâmetro de HB_RegExSplit() é uma string regex, já até tentei usar ela, e hb_regexAll() seria melhor mesmo. O problema está em achar a string gerex correta, ou até ver se isso é uma limitação da biblioteca regex do harbour.
HB_RegExSplit()
Parses a string using a regular expression and fills an array.
Syntax
HB_RegExSplit( <cRegEx> , ;
<cString> , ;
[<lCaseSensitive>, ;
[<lNewLine>] , ;
[<nMaxMatches>] ) --> aSplitString

Arguments
<cRegEx>
This is a character string holding the search pattern as a literal regular expression. Alternatively, the return value of HB_RegExComp() can be used.
<cString>
This is the character string being searched for a match.
<lCaseSensitive>
This parameter defaults to .T. (true) so that a case sensitive search is performed. Passing .F. (false) results in a case insensitive search.
<lNewLine>
This parameter defaults to .F. (false).
<nMaxMatches>
This is a numeric value indicating the maximum number of matches to return. The default value is zero which returns all matches. Return
The function returns an array whose elements hold the parsing result. If no match is found, the return value is NIL.
Description
Function HB_RegExSplit() scans a character string for substrings matching a regular expression. All matched substrings are removed while the remaining strings are collected and returned in an array.
At. Clodoaldo Monteiro
Linguagens: Clipper / Harbour
Área de Atuação: Sistemas de gestão para Prefeituras Municipais
Fones: (86)3223-0653, 98859-0236
www.simplesinformatica.com.br
Avatar do usuário
JoséQuintas
Administrador
Administrador
Mensagens: 20267
Registrado em: 26 Fev 2007 11:59
Localização: São Paulo-SP

ReGex para buscar TAG de XML

Mensagem por JoséQuintas »

Eu uso pra dividir linhas.
Nunca reparei sobre expressão regular.

Código: Selecionar todos

aTxt := hb_RegExSplit( hb_Eol(), cText )
José M. C. Quintas
Harbour 3.2, mingw, gtwvg mt, fivewin 25.04, multithread, dbfcdx, MySQL, ADOClass, PDFClass, SefazClass, (hwgui mt), (hmg3), (hmg extended), (oohg), PNotepad, ASP, stored procedure, stored function, Linux (Flagship/harbour 3.2)
"The world is full of kings and queens, who blind our eyes and steal our dreams Its Heaven and Hell"

https://github.com/JoseQuintas/
Avatar do usuário
clodoaldomonteiro
Usuário Nível 4
Usuário Nível 4
Mensagens: 821
Registrado em: 30 Dez 2006 13:17
Localização: Teresina-PI
Contato:

ReGex para buscar TAG de XML

Mensagem por clodoaldomonteiro »

O processo de busca no XML "grandão" acelerou muito com XPath correto:
-> oLancamentoContabilItem := oLancamentoContabel:selectNodes( '//lan:LancamentoContabilItem' )
para
-> oLancamentoContabilItem := oLancamentoContabel:selectNodes( './lan:LancamentoContabilItem' )

No primeiro caso, forçava uma varredora em aproximadamente 49000 Nodes e no segundo, foca somente nos Itens de lançamento do Node Pai, que são 3400, ou seja, "//" é diferente de "./" (estudar sempre é bom) hehe.
O tempo reduziu de 27 segs para 13 segs.

Mas achei muito tempo ainda, pois cada "oItem" de "oLancamento" tem mais ou menos 1K de string XML, e pegando essa string "cItemXML := oIten:XML" e processando ela com a função do @Quintas ou com:

Código: Selecionar todos

hb_RegExAll("<lan:tipoNatuLancamento>(.*?)</lan:tipoNatuLancamento>", cItemXML)
... o tempo cai para 1 segundo, processando os 3400 registros de lançamentos Débito e Crédito.
É uma diferença bem considerável e pode ser que ainda esteja "patinando" no XPath dos métodos XML.

Para ajudar, fiz uma pequena função para deixar o código mais limpo.

Código: Selecionar todos

   //Retorna valor do primeiro Row de acordo com o regex
Function ReGexGetValue(cRegex, cString)
   Local r := '',aMatch

   aMatch := hb_RegExAll(cRegex, cString)

   If Len( aMatch ) > 0
      r := aMatch[1, 2]
   Endif

   Return r
   
Abraços.
At. Clodoaldo Monteiro
Linguagens: Clipper / Harbour
Área de Atuação: Sistemas de gestão para Prefeituras Municipais
Fones: (86)3223-0653, 98859-0236
www.simplesinformatica.com.br
Avatar do usuário
Itamar M. Lins Jr.
Administrador
Administrador
Mensagens: 7928
Registrado em: 30 Mai 2007 11:31
Localização: Ilheus Bahia
Curtiu: 1 vez

ReGex para buscar TAG de XML

Mensagem por Itamar M. Lins Jr. »

Olá!
Teve uma vez aqui que a solução que encontrei foi, ir diminuindo a Array depois de processado uma linha.
A medida que ia pegando os campos, ia cortando ela com adel...

Saudações,
Itamar M. Lins Jr.
Saudações,
Itamar M. Lins Jr.
Avatar do usuário
JoséQuintas
Administrador
Administrador
Mensagens: 20267
Registrado em: 26 Fev 2007 11:59
Localização: São Paulo-SP

ReGex para buscar TAG de XML

Mensagem por JoséQuintas »

E como vai trabalhar muito com string, pode pensar em passar por referência.
Isso usa a variável direta, sem ter que criar uma nova.
Isso é válido pra elementos de array, uso muito no dlgauto, mas não pra economizar memória, nem pra deixar mais rápido.

Código: Selecionar todos

Rotina( @aList[ 1 ] )
José M. C. Quintas
Harbour 3.2, mingw, gtwvg mt, fivewin 25.04, multithread, dbfcdx, MySQL, ADOClass, PDFClass, SefazClass, (hwgui mt), (hmg3), (hmg extended), (oohg), PNotepad, ASP, stored procedure, stored function, Linux (Flagship/harbour 3.2)
"The world is full of kings and queens, who blind our eyes and steal our dreams Its Heaven and Hell"

https://github.com/JoseQuintas/
Avatar do usuário
clodoaldomonteiro
Usuário Nível 4
Usuário Nível 4
Mensagens: 821
Registrado em: 30 Dez 2006 13:17
Localização: Teresina-PI
Contato:

ReGex para buscar TAG de XML

Mensagem por clodoaldomonteiro »

Itamar M. Lins Jr. escreveu:Olá!
Teve uma vez aqui que a solução que encontrei foi, ir diminuindo a Array depois de processado uma linha.
A medida que ia pegando os campos, ia cortando ela com adel...

Saudações,
Itamar M. Lins Jr.
Para busca em Array grandona (uma de 10000 rows), que é muito lento pq não é indexado, uso a função abaixo, que começa a busca do meio para as pontas e fica muito mais rápido que o velho Ascan(). Não me lembro que me passou ela.

Código: Selecionar todos

   //mlinha=buscabin(aArray, srchstr, col_pesq)
Function aScan2( a, mBuscab)
   Local low, high, mid, mtam_plv, x
   low := 1
   high:= Len(a)

   If high = 0
      Return(0)
   EndIf

   mid     := Int((low + high) / 2)
   mtam_plv:= Len(mbuscab)

   Do While SubStr(a[mid], 1, mtam_plv) # mBuscab .and. low <= high

      If mbuscab > SubStr(a[mid], 1, mtam_plv)
         low := mid + 1
      Else
         high := mid -1
         if high < 1
            return(0)
         EndIf
      EndIf
      mid := Int((low + high) / 2)

   EndDo

   If SubStr(a[mid], 1, mtam_plv) # mbuscab
      mid := 0
   Else
      For x := mid -1 to 1 Step -1
         if SubStr(a[x], 1, mtam_plv) = mbuscab
            mid := x
         Else
            exit
         EndIf
      Next
   EndIf
   
   Return(mid)
Abraços.
At. Clodoaldo Monteiro
Linguagens: Clipper / Harbour
Área de Atuação: Sistemas de gestão para Prefeituras Municipais
Fones: (86)3223-0653, 98859-0236
www.simplesinformatica.com.br
Avatar do usuário
clodoaldomonteiro
Usuário Nível 4
Usuário Nível 4
Mensagens: 821
Registrado em: 30 Dez 2006 13:17
Localização: Teresina-PI
Contato:

ReGex para buscar TAG de XML

Mensagem por clodoaldomonteiro »

Aqui a função ajustada para fazer busca numa array bidimensional.

Código: Selecionar todos

Function aScanB( aArray, mBuscab, nColumn )
   Local low, high, mid, mtam_plv, x
   Local cValue // Valor atual da coluna especificada para busca

   low := 1
   high := Len( aArray )

   If high = 0
      Return( 0 )
   EndIf

   mid := Int(( low + high ) / 2 )
   mtam_plv := Len( mBuscab )

   // Verificação adicional para garantir índice válido na coluna
   If ValType( aArray[1] ) != "A" .or. nColumn > Len( aArray[1] )
      Return( 0 )
   EndIf

   // Busca binária com base na coluna especificada
   Do While .T.
      cValue := SubStr( aArray[mid][nColumn], 1, mtam_plv )

      If cValue # mBuscab .and. low <= high
         If mBuscab > cValue
            low := mid + 1
         Else
            high := mid - 1
            If high < 1
               Return( 0 )
            EndIf
         EndIf
         mid := Int(( low + high ) / 2 )
      Else
         Exit
      EndIf
   EndDo

   // Verifica se o valor encontrado é o desejado
   If cValue # mBuscab
      mid := 0
   Else
      // Busca por ocorrências anteriores na mesma coluna
      For x := mid - 1 To 1 Step -1
         If SubStr( aArray[x][nColumn], 1, mtam_plv ) = mBuscab
            mid := x
         Else
            Exit
         EndIf
      Next
   EndIf

   Return( mid )
Processando um DBF de 48500 registros, com 3 relacionamentos, gerou uma matriz de 29700 registros, mais um tempo para gerar um XML, ficou em 52 segundos, e com Ascan() original fica em 14:20 minutos.

Abraços.
At. Clodoaldo Monteiro
Linguagens: Clipper / Harbour
Área de Atuação: Sistemas de gestão para Prefeituras Municipais
Fones: (86)3223-0653, 98859-0236
www.simplesinformatica.com.br
Avatar do usuário
JoséQuintas
Administrador
Administrador
Mensagens: 20267
Registrado em: 26 Fev 2007 11:59
Localização: São Paulo-SP

ReGex para buscar TAG de XML

Mensagem por JoséQuintas »

O detalhe é que é o mesmo que comparar LOCATE com SEEK.
hb_AScan() é pra pesquisa direta, esse algorítimo só funciona se estiver tudo ordenado pelo campo chave.
Com o arquivo em ordem, 10.000 registros, ao olhar o do meio, já podemos descartar 5.000
Se não estiver ordenado não serve.
José M. C. Quintas
Harbour 3.2, mingw, gtwvg mt, fivewin 25.04, multithread, dbfcdx, MySQL, ADOClass, PDFClass, SefazClass, (hwgui mt), (hmg3), (hmg extended), (oohg), PNotepad, ASP, stored procedure, stored function, Linux (Flagship/harbour 3.2)
"The world is full of kings and queens, who blind our eyes and steal our dreams Its Heaven and Hell"

https://github.com/JoseQuintas/
Responder