Converter xml de nota fiscal eletrônica em tibble

Bom dia.

Estou tentando transformar os arquivos de nota fiscal eletronica, que são xml, em dataframe ou tibble.

A ideia é partir da pasta ‘ponto_final’, onde estão milhares de xml, e transformar um a um em dataframe para depois empilhá-los e criar uma tibble de milhares de linhas e algumas colunas.

Primeiro crio uma lista de uma nfe específica. O exemplo abaixo é fictício porque não tenho dados públicos para usar.

uma_nfe ← as_list(read_xml(“C:/Users/mrutman/Documents/trabalhando XML/ponto_final/NFE_31108639445919.xml”))

Acreditei que com o script abaixo poderia fazer um dataframe de uma linha para depois usando o ‘map’ empilhar todos dem um dataframe de milhares de linhas.

O script não indicou erro mas formou um dataframe com as colunas desejadas mas todas com valor 0. Alguma sugestão para me ajudar?

Agradeço desde já a atenção. Abs.

df_uma_nfe ← tibble(
mod = uma_nfe$nfeProc$NFe$infNFe$ide$mod,
serie = uma_nfe$nfeProc$NFe$infNFe$ide$serie,
data_emissao = uma_nfe$nfeProc$NFe$infNFe$ide$dhEmi,
nro_nota_fiscal = uma_nfe$nfeProc$NFe$infNFe$ide$nNF,

cnpj_emitente = uma_nfe$nfeProc$NFe$infNFe$emit$CNPJ,
nome_emitente = uma_nfe$nfeProc$NFe$infNFe$emit$xNome,
uf_emitente = uma_nfe$nfeProc$NFe$infNFe$emit$enderEmit$UF,
ie_emitente = uma_nfe$nfeProc$NFe$infNFe$emit$IE,
ie_st_emitente = uma_nfe$nfeProc$NFe$infNFe$emit$IEST,

cnpj_destinatario = uma_nfe$nfeProc$NFe$infNFe$dest$CNPJ,
nome_destinatario = uma_nfe$nfeProc$NFe$infNFe$dest$xNome,
municipio_destinatario = uma_nfe$nfeProc$NFe$infNFe$dest$enderDest$xMun,
uf_destinatario = uma_nfe$nfeProc$NFe$infNFe$dest$enderDest$UF,
ie_destinatario = uma_nfe$nfeProc$NFe$infNFe$dest$IE,
email_destinatario = uma_nfe$nfeProc$NFe$infNFe$dest$email,

produto = uma_nfe$nfeProc$NFe$infNFe$det$prod$cProd,
ean = uma_nfe$nfeProc$NFe$infNFe$det$prod$cEAN,
descricao_produto = uma_nfe$nfeProc$NFe$infNFe$det$prod$xProd,
ncm = uma_nfe$nfeProc$NFe$infNFe$det$prod$NCM,
cfop = uma_nfe$nfeProc$NFe$infNFe$det$prod$CFOP,
quantidade = uma_nfe$nfeProc$NFe$infNFe$det$prod$qTrib,

base_de_calculo = uma_nfe$nfeProc$NFe$infNFe$det$imposto$ICMS$ICMS10$vBC,
aliquota_icms = uma_nfe$nfeProc$NFe$infNFe$det$imposto$ICMS$ICMS10$pICMS,
valor_icms = uma_nfe$nfeProc$NFe$infNFe$det$imposto$ICMS$ICMS10$vICMS,
base_de_calculo_st = uma_nfe$nfeProc$NFe$infNFe$det$imposto$ICMS$ICMS10$vBCST,
aliquota_icms_st = uma_nfe$nfeProc$NFe$infNFe$det$imposto$ICMS$ICMS10$pICMSST,
valor_icms_st = uma_nfe$nfeProc$NFe$infNFe$det$imposto$ICMS$ICMS10$vICMSST,

chave_de_acesso = uma_nfe$nfeProc$protNFe$infProt$chNFe
)

Oi Mário, tudo bem? Feliz 2022!

Eu fiz uma tarefa similar recentemente. Em vez de adaptar o seu código, eu adaptei o código que eu fiz (no meu caso está estruturado em um pacote privado, em um fluxo que busca isso semanalmente, transforma em um dataframe e é atualizado em um shiny).

Eu testei aqui a versão “resumida” e funcionou, veja se com você também dá certo, lembrando de editar o caminho onde estão os seus arquivos:

# FUNÇÕES ÚTEIS PARA PARSEAR A NOTA ------------------------------------
parsear_xpath <-
  function(x,  fim_xpath, inicio_xpath = '/nfeProc/NFe/infNFe/') {
    xml2::read_xml(x) |>
      xml2::xml_ns_strip() |>
      xml2::xml_find_all(xpath = paste0(inicio_xpath, fim_xpath)) |>
      purrr::map(xml2::as_list) |>
      purrr::map(unlist, recursive = TRUE) |>
      purrr::map_dfr(tibble::enframe, .id = "id") |>
      tidyr::pivot_wider(names_from = name,
                         values_from = value) |>
      janitor::clean_names() |>
      dplyr::select(-id)
  }

parsear_nota <- function(x) {
  infos_nota <- parsear_xpath(x, "ide")
  infos_cliente <- parsear_xpath(x, "dest")
  safe_parsear_xpath <-
    purrr::possibly(parsear_xpath, tibble::tibble(erro = "erro"))

  infos_faturamento <- safe_parsear_xpath(x, "cobr")

  if (infos_faturamento[1, 1] == "erro") {
    infos_faturamento <- safe_parsear_xpath(x, fim_xpath = "total") |>
      dplyr::transmute(faturamento = icms_tot_v_nf)
  } else {
    infos_faturamento  <- infos_faturamento |>
      dplyr::transmute(faturamento = fat_v_orig)
  }



  infos_produtos <- parsear_xpath(x, "det") |>
    dplyr::mutate(n_nf = infos_nota$n_nf, .before = tidyselect::everything())

  infos_nf <-
    dplyr::bind_cols(infos_nota, infos_cliente, infos_faturamento) |>
    dplyr::relocate(n_nf, .before = tidyselect::everything())

  dplyr::left_join(infos_produtos, infos_nf, by = "n_nf")
}

safe_parsear_nota <-
  purrr::possibly(parsear_nota, tibble::tibble(erro = "erro"))

# Código a partir daqui ----------


# listar arquivos que estão na pasta desejada,
# altere o caminho da pasta aqui!
arquivos_xml <- fs::dir_ls("ponto_final") # AQUI VOCÊ DEVE 
# INDICAR A PASTA ONDE ESTÃO AS NFE XML

# ler conteúdo das notas fiscais e salvar em uma lista
xml_nfe <- purrr::map(arquivos_xml, readr::read_file)


# parsear o conteúdo e transformar em uma tibble
conteudo_notas_fiscais <- xml_nfe |>
  tibble::enframe() |>
  dplyr::mutate(value = as.character(value),
                nota = purrr::map(value, safe_parsear_nota)) |>
  tidyr::unnest(cols = nota) |>
  dplyr::transmute(
    n_nf,
    faturamento_nf = readr::parse_number(faturamento) ,
    cliente_nome = x_nome,
    cliente_cnpj = cnpj,
    cliente_email = email,
    produto_codigo =  prod_c_prod,
    produto_nome = prod_x_prod,
    produto_unidade = prod_u_com,
    produto_valor_unitario =  readr::parse_number(prod_v_un_com),
    produto_quantidade = readr::parse_number(prod_q_com) ,
    produto_valor_total = readr::parse_number(prod_v_prod),
    data = readr::parse_datetime(dh_emi),
    dplyr::across(tidyselect::contains("erro"))
  )

dplyr::glimpse(conteudo_notas_fiscais)

# > dplyr::glimpse(conteudo_notas_fiscais)
# Rows: 21
# Columns: 12
# $ n_nf                   <chr> "2207", "2208", "2209", "2210", "2210…
# $ faturamento_nf         <dbl> REMOVI POR TER INFORMAÇÃO SENSÍVEL
# $ cliente_nome           <chr> REMOVI POR TER INFORMAÇÃO SENSÍVEL
# $ cliente_cnpj           <chr> REMOVI POR TER INFORMAÇÃO SENSÍVEL
# $ cliente_email          <chr> REMOVI POR TER INFORMAÇÃO SENSÍVEL
# $ produto_codigo         <chr> REMOVI POR TER INFORMAÇÃO SENSÍVEL
# $ produto_nome           <chr> REMOVI POR TER INFORMAÇÃO SENSÍVEL
# $ produto_unidade        <chr> "PC", "PC", "PC", "PC", "PC", "PC", "…
# $ produto_valor_unitario <dbl> REMOVI POR TER INFORMAÇÃO SENSÍVEL
# $ produto_quantidade     <dbl> REMOVI POR TER INFORMAÇÃO SENSÍVEL
# $ produto_valor_total    <dbl> REMOVI POR TER INFORMAÇÃO SENSÍVEL
# $ data                   <dttm> 2021-12-01 14:51:00, 2021-12-01 15:0…

Espero que seja útil! Abraços

Antes de tudo, feliz 2022 com muita saúde e alegrias.
Muuuito obrigado pelo código. Dei uma lida agora e vou começar adaptá-lo.
Depois te conto.
Abs.
Mário.

Bom dia Beatriz.

O script que encaminhou-me está acima de meus conhecimentos de xml e R, não acompanhei o raciocínio do código.

Acabei fazendo um que funcionou até aparecer o erro
“Error: Internal error in vec_proxy_assign_opts(): proxy of type list incompatible with value proxy of type NULL.”

Ficarei grato se puder sugerir melhorias no código abaixo.

library(tibble)
library(XML)
library(xml2)
library(dplyr)
library(readr)
library(janitor)
library(stringr)
library(tidyr)
library(purrr)


# 1. Listando todos os xml que estão na pasta ponto_final. --------------------------------------
lista_de_xml <- list.files(path = "C:/Users/mrutman/Documents/AFE-04 Petróleo/trabalhando XML/ponto_final",
           pattern = "*.xml", full.names = TRUE)


# Criando um vetor para retirar os xml com 'EVENTO', isto é, que contenha 'final/NFE'.
verdadeiro_falso <- lista_de_xml %>%
  str_detect("final/NFE")


# Os xmls sem 'EVENTO'.
xmls <- lista_de_xml[verdadeiro_falso]

# O xmls[1] é a primeira nota da lista, o xmls[2] a segunda e assim por diante.


# 2. Transformando uma Nfe em lista. -----------------------------------------
uma_nfe <- as_list(read_xml("C:/Users/mrutman/Documents/AFE-04 Petróleo/trabalhando XML/ponto_final/NFE_31180611086399000191550010000214121000700449.xml"))


# 3. Criando a função que faz uma tibble de uma linha. -------------------------

faz_tibble_de_uma_linha <- function(nota_fiscal) {
  # nota_fiscal é representada por um inteiro,
  # que é a posição do xml no objeto xmls.

  temp_01 <- as_list(read_xml(xmls[nota_fiscal]))

  # O que vem após o último $ é a coluna que me interessa.
  df_de_uma_nfe <- tibble(
    nota_fiscal = temp_01$nfeProc$NFe$infNFe$ide$nNF,
    data_emissao = temp_01$nfeProc$NFe$infNFe$ide$dhEmi,
    cnpj_emitente = temp_01$nfeProc$NFe$infNFe$emit$CNPJ,
    nome_emitente = temp_01$nfeProc$NFe$infNFe$emit$xNome,
    uf_emitente = temp_01$nfeProc$NFe$infNFe$emit$enderEmit$UF,
    cnpj_destinatario = temp_01$nfeProc$NFe$infNFe$dest$CNPJ,
    nome_destinatario = temp_01$nfeProc$NFe$infNFe$dest$xNome,
    uf_destinatario = temp_01$nfeProc$NFe$infNFe$dest$enderDest$UF,
    produto = temp_01$nfeProc$NFe$infNFe$det$prod$xProd,
    ncm = temp_01$nfeProc$NFe$infNFe$det$prod$NCM,
    cfop = temp_01$nfeProc$NFe$infNFe$det$prod$CFOP,
    quantidade = temp_01$nfeProc$NFe$infNFe$det$prod$qCom,
    valor_unitario = temp_01$nfeProc$NFe$infNFe$det$prod$vUnCom,
    total = temp_01$nfeProc$NFe$infNFe$det$prod$vProd,
    bc_st = temp_01$nfeProc$NFe$infNFe$total$ICMSTot$vBCST,
    st = temp_01$nfeProc$NFe$infNFe$total$ICMSTot$vST,
    valor_produto = temp_01$nfeProc$NFe$infNFe$total$ICMSTot$vProd,
    valor_nota_fiscal = temp_01$nfeProc$NFe$infNFe$total$ICMSTot$vNF,
    chave_de_acesso = temp_01$nfeProc$protNFe$infProt$chNFe
  )
}

# O tibble do 538º e do 1254º xml.
aa <- faz_tibble_de_uma_linha(538)
bb <- faz_tibble_de_uma_linha(1254)

# 5. Agora faz um tibble único empilhando todas as linhas. -------------------------------------------
ee <- map_df(1:length(xmls), faz_tibble_de_uma_linha) %>%
  bind_rows() %>%
  unnest(cols = c(nota_fiscal, data_emissao, cnpj_emitente, nome_emitente,
                  uf_emitente, cnpj_destinatario, nome_destinatario,
                  uf_destinatario, produto, ncm, cfop, quantidade,
                  valor_unitario, total, bc_st, st, valor_produto,
                  valor_nota_fiscal, chave_de_acesso))

Muito obrigado.

Oi Mário! O exemplo que você deu é de um código não reprodutível, o que dificulta adaptar ele. você tem exemplos dos dados usados? Abs

Bom dia Beatriz,
obrigado pela atenção, sei que você tem trocentas outras tarefas.
Não consegui fazer código reprodutível por conta de não poder usar as NFe que tenho.
Minha atual necessidade não é transformar qualquer .xml em tibble, mas somente os que são NFe, que são muito padronizados.
Atualmente estou usando o power query do Excel. Quando crio a tabela com as colunas que interessam, seus nomes indicam o xpath. Seguem abaixo os nomes das colunas.
se puder me dar apenas uma pista ou sugestão tavez já resolva.
abs.

Mário.

“NFe.infNFe.ide.natOp”
[3] “NFe.infNFe.ide.mod” “NFe.infNFe.ide.serie”
[5] “NFe.infNFe.ide.nNF” “NFe.infNFe.ide.dhEmi”
[7] “NFe.infNFe.emit.CNPJ” “NFe.infNFe.emit.xNome”
[9] “NFe.infNFe.dest.CNPJ” “NFe.infNFe.dest.xNome”
[11] “NFe.infNFe.dest.enderDest.UF” “NFe.infNFe.det.prod.xProd”
[13] “NFe.infNFe.det.prod.CFOP” “NFe.infNFe.det.prod.uCom”
[15] “NFe.infNFe.det.prod.qCom” “NFe.infNFe.det.prod.vUnCom”
[17] “NFe.infNFe.det.prod.vProd” “NFe.infNFe.det.prod.uTrib”
[19] “NFe.infNFe.det.prod.qTrib” “NFe.infNFe.det.prod.vUnTrib”
[21] “NFe.infNFe.det.prod.indTot” “NFe.infNFe.det.imposto.ICMS.ICMS00.vBC”
[23] “NFe.infNFe.det.imposto.ICMS.ICMS00.pICMS” “NFe.infNFe.det.imposto.ICMS.ICMS00.vICMS”
[25] “NFe.infNFe.det.infAdProd” “NFe.infNFe.det.Attribute:nItem”
[27] “NFe.infNFe.total.ICMSTot.vBC” “NFe.infNFe.total.ICMSTot.vICMS”
[29] “NFe.infNFe.total.ICMSTot.vBCST” “NFe.infNFe.total.ICMSTot.vST”