Recentemente precisei fazer uma busca por nomes duplicados e também nome similares que foram escritos errados em um arquivo csv.
Para resolver esse problema usei uma macro em python com um algorítimo tipo metaphone, mas em uma versão português brasileiro. Vou compartilhar aqui no forum.
Usei o módulo disponível em: (Créditos ao Autor)
mas precisei fazer algumas alterações no código:
#foi portado do python 3.10 para o python 3.8 que é utilizado atualmente pelo libreoffice 7.5
#foi alterado o código das vogais, para coincidir nomes com essa caracteristica: [espaço Letra D espaço vogal] ex: Joana D Arc; Estrela D Alva …
esse algorítimo pymetaphone-br usa um módulo de terceiro (Unidecode) que precisa ser instalado com o zaz-pip (extensão do libreoffice para instalar módulos oriundos do repositório pypi)
import uno
import itertools
from unidecode import unidecode #https://pypi.org/project/Unidecode/
def main_comparacao_fonetica(*args):
doc = XSCRIPTCONTEXT.getDocument()
sheets = doc.Sheets
plan_main = sheets[0]
ultima_linha_preenchida = get_last_used_row(plan_main, 0, 0, (1+2+4))
#cria uma matriz (lista de listas, no caso tupla de tuplas) com os nomes que estão no intervalo "A2:A44"
tag_conteudo = f'''A2:A{ultima_linha_preenchida}'''
range_conteudo = plan_main[tag_conteudo]
matriz_nomes = range_conteudo.DataArray
#cria uma matriz com os códigos gerados pelo metaphone
matriz_metaphone = []
for nome in matriz_nomes:
lista_linha = []
lista_linha.append(br_metaphone(nome[0]))
#matriz_metaphone.append(tuple(lista_linha)) #aceita tanto lista como tupla
matriz_metaphone.append(lista_linha)
#escreve a matriz_metaphone no intervalo de destino "B2:B44"
#foi somado 1, pois os dados serão inseridos apartir da linha B2, se fosse B5 somaria 4
tag_metaphone = f'''B2:B{len(matriz_metaphone)+1}'''
range_metaphone = plan_main[tag_metaphone]
#range_metaphone.DataArray = tuple(matriz_metaphone) #aceita tanto lista como tupla
range_metaphone.DataArray = matriz_metaphone
return
def get_last_used_row(oSheet, refCol, startRow, flags):
"""Retorna a última linha preenchida por dados na coluna de referência (refCol)
Flags de tipos de conteúdo:
https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet_1_1CellFlags.html
exemplo flags=7 -> 1 + 2 + 4:
VALUE = 1
DATETIME = 2
STRING = 4
Args:
oSheet (objeto planilha): doc.Sheets['Planilha1'] ; doc.Sheets[0] ...
refCol (integer): coluna de referência para pesquisar o intervalo com os dados, ex: coluna A: 0
startRow (integer): primeira linha do intervalo onde estão os dados, ex: linha 1: 0
flags (integer): somatório das Flags de tipos de conteúdo das celúlas
Returns:
integer: número da última linha preenchida na coluna refCol
"""
oCursor = oSheet.createCursor()
oCursor.gotoEndOfUsedArea(False)
#número da última linha
LastRow = oCursor.RangeAddress.EndRow
#oSheet[linha_inicial:(linha_final + 1), coluna_inicial:(coluna_final + 1)].AbsoluteName
#print(oSheet[0:(999+1), 0:(1+1)].AbsoluteName) #b1:b1000
#print(oSheet[startRow:(LastRow+1), refCol:(refCol+1)].AbsoluteName)
#slice de lista == getCellRangeByPosition
current_selection_range = oSheet[startRow:(LastRow+1), refCol:(refCol+1)].queryContentCells(flags).RangeAddresses[0]
#última linha preenchida de acordo com a flag indicada
LastUsedRow = current_selection_range.EndRow + 1
return LastUsedRow
#####################################
#Funções do Algorítimo Metaphone português brasileiro
#####################################
#https://pypi.org/project/pymetaphone-br/#files
#foi portado do python 3.10 para o python 3.8 que é utilizado atualmente pelo libreoffice 7.5
#foi alterado o código das vogais, para coincidir nomes com essa caracteristica: [espaço Letra D espaço vogal] ex: Joana D Arc; Estrela D Alva ...
def make_upper_clean(word: str):
original_word = word.upper()
word = unidecode(original_word)
# O Unidecode tira a Ç que precisamos para o nosso algoritmos
pos_cedilha = [i for i, letter in enumerate(original_word) if letter == "Ç"]
for i in pos_cedilha:
word = word[:i] + "Ç" + word[i + 1 :]
# Replace Y with I
word = word.replace("Y", "I")
# Remove duplicate consecutive characters if is not R or S without regex
word_pieces = []
for i, g in itertools.groupby(word):
if i not in ["R", "S"]:
word_pieces.append(i)
else:
word_pieces.extend(list(g))
return "".join(word_pieces)
def is_vowel(char):
return char in ["A", "E", "I", "O", "U"]
def br_metaphone(word):
if word is None or word == "":
return word
original = make_upper_clean(word)
length = len(original)
# Iterate each character
metaphone = []
last_char = " "
current = 0
while current < length:
current_char = original[current]
ahead_char = original[current + 1] if current + 1 < length else " "
ahead2_char = original[current + 2] if current + 2 < length else " "
last2_char = original[current - 2] if current - 2 >= 0 else " "
last3_char = original[current - 3] if current - 3 >= 0 else " "
if current_char in ["A" , "E" , "I" , "O" , "U"]:
#if last_char.isspace():
if last3_char.isspace() and last2_char == 'D' and last_char.isspace(): #-> ' D Vogal' : joana d arc - joana darc, joana d'arc
pass
else:
if last_char.isspace():
metaphone.append(current_char)
elif current_char == "L":
if ahead_char == "H":
metaphone.append("1")
elif is_vowel(ahead_char) or last_char.isspace():
metaphone.append("L")
elif current_char in ["T" , "P"]:
if ahead_char == "H":
if current_char == "P":
metaphone.append("F")
else:
metaphone.append("T")
current += 1
metaphone.append(current_char)
elif current_char in ["B" , "D" , "F" , "J" , "K" , "M" , "V"]:
metaphone.append(current_char)
elif current_char == "G":
if ahead_char == "H":
if not is_vowel(ahead2_char):
metaphone.append("G")
if ahead_char in ["H", "E", "I"]:
metaphone.append("J")
else:
metaphone.append("G")
elif current_char == "R":
if last_char.isspace() or ahead_char.isspace():
metaphone.append("2")
elif ahead_char == "R":
metaphone.append("2")
current += 1
elif is_vowel(last_char) and is_vowel(ahead_char):
metaphone.append("R")
current += 1
else:
metaphone.append("R")
elif current_char == "Z":
if ahead_char.isspace():
metaphone.append("S")
else:
metaphone.append("Z")
elif current_char == "N":
if ahead_char.isspace():
metaphone.append("M")
elif ahead_char == "H":
metaphone.append("3")
current += 1
elif last_char != "N":
metaphone.append("N")
elif current_char == "S":
if ahead_char == "S":
metaphone.append("S")
last_char = ahead_char
current += 1
elif ahead_char == "H":
metaphone.append("X")
current += 1
elif is_vowel(last_char) and is_vowel(ahead_char):
metaphone.append("Z")
elif ahead_char == "C":
if ahead2_char in ["E", "I"]:
metaphone.append("S")
current += 2
elif ahead2_char in ["A", "O", "U"]:
metaphone.append("SK")
current += 2
elif ahead2_char == "H":
metaphone.append("X")
current += 2
else:
metaphone.append("S")
current += 1
else:
metaphone.append("S")
elif current_char == "X":
if ahead_char.isspace():
metaphone.append("X")
elif last_char == "E":
if is_vowel(ahead_char):
if last2_char.isspace():
metaphone.append("Z")
else:
if ahead_char in ["E", "I"]:
metaphone.append("X")
current += 1
else:
metaphone.append("KS")
current += 1
elif ahead_char == "C":
metaphone.append("S")
current += 1
elif ahead_char in ["P", "T"]:
metaphone.append("S")
else:
metaphone.append("KS")
elif is_vowel(last_char):
if last2_char in ["A", "E", "I", "O", "U", "C", "K", "G", "L", "R", "X"]:
metaphone.append("X")
else:
metaphone.append("KS")
else:
metaphone.append("X")
elif current_char == "C":
if ahead_char in ["E", "I"]:
metaphone.append("S")
elif ahead_char == "H":
if ahead2_char == "R":
metaphone.append("K")
else:
metaphone.append("X")
current += 1
elif (ahead_char == "Q") or (ahead_char == "K"):
pass
else:
metaphone.append("K")
elif current_char == "H":
if last_char.isspace():
if is_vowel(ahead_char):
metaphone.append(ahead_char)
current += 1
elif current_char == "Q":
metaphone.append("K")
elif current_char == "W":
if is_vowel(ahead_char):
metaphone.append("V")
elif current_char == "Ç":
metaphone.append("S")
last_char = current_char
current += 1
return "".join(metaphone)
#####################################
Não é um código que funciona em 100% dos casos, mas já ajuda bastante na buscas por nomes repetidos ou com erros de digitação.
Ex:
David Barros
Davi Barros
Deivid Barros
consegue identificar David Barros e Deivid Barros. Porém não identifica Davi Barros. Esses nomes poderiam ser apenas uma pessoa com três cadastros diferentes.
Mas para confirmar com certeza qualquer duplicidade casdastral sempre temos que ter no mínimo três paramentros para confirmar se são pessoas distintas ou iguais. Ex: Nome, Data de Nascimento, Nome da mãe
no arquivo de exemplo vou usar apenas o nome da pessoa para demonstração.
Na coluna A temos os nomes, na coluna B temos o código metaphone.
Na coluna B apliquei uma formatação condicional que procura por duplicidades, com estilo ERRO (fundo vermelho)
Depois basta filtrar a coluna B pela cor de fundo vermelha que serão exibidos os nomes similares/repetidos identificados pelo metaphone
Obs: Para usar macros em python recomendo essas extensões:
Apso para embutir as macros nos arquivos do libreoffice
https://gitlab.com/jmzambon/apso/-/raw/master/apso.oxt
ZAZPip para instalar os módulos do repositório PyPi
https://git.cuates.net/elmau/zaz-pip/raw/branch/master/extension/ZAZPip_v0.10.2.oxt
como usar a extensão ZAZPip
tutorial em espanhol sobre macros em python no libreoffice, feitos pelo Mauricio Baeza
Arquivo de exemplo:
teste comparação metaphone.ods (27,7,KB)