</> Cómo funciona
Función pura del español.
| parámetro | tipo | por defecto | qué hace |
|---|---|---|---|
palabra | string | — (obligatorio) | la palabra |
commons/upln.py — silabeador y acentuador algorítmicos, con silabear(), int_silaba_tonica() y acentuar().pr, tr, ll, ch…) y casos especiales (la h muda, la u muda de que/gui, psic-…).flowchart TD
MOD[("repositorio")]
is_vowel["is_vowel()"]
has_text["has_text()"]
get_position_first_vowel["get_position_first_vowel()"]
get_position_last_vowel["get_position_last_vowel()"]
get_num_consonantes_antes_de_vocal["get_num_consonantes_antes_de_vocal()"]
get_letters["get_letters()"]
_compara["_compara()"]
desacentuar_vocal["desacentuar_vocal()"]
desacentuar_palabra["desacentuar_palabra()"]
_cumple_condicion_y["_cumple_condicion_y()"]
_get_separador_vocales["_get_separador_vocales()"]
_get_condicion_q["_get_condicion_q()"]
_get_accion_grupos_separados["_get_accion_grupos_separados()"]
_get_numero_silabas["_get_numero_silabas()"]
_get_condicion_h["_get_condicion_h()"]
_get_condicion_vocales["_get_condicion_vocales()"]
_get_condicion_psicol["_get_condicion_psicol()"]
_get_condicion_vuv["_get_condicion_vuv()"]
silabear["silabear()"]
int_silaba_tonica["int_silaba_tonica()"]
numero_silabas["numero_silabas()"]
_compara --> desacentuar_palabra
_cumple_condicion_y --> has_text
_cumple_condicion_y --> is_vowel
_get_accion_grupos_separados --> get_num_consonantes_antes_de_vocal
_get_accion_grupos_separados --> has_text
_get_accion_grupos_separados --> is_vowel
_get_condicion_h --> _get_numero_silabas
_get_condicion_q --> _compara
_get_condicion_vuv --> is_vowel
_get_separador_vocales --> get_letters
_get_separador_vocales --> is_vowel
desacentuar_palabra --> desacentuar_vocal
get_num_consonantes_antes_de_vocal --> is_vowel
get_position_first_vowel --> is_vowel
get_position_last_vowel --> is_vowel
int_silaba_tonica --> has_text
int_silaba_tonica --> is_vowel
silabear --> _compara
silabear --> _cumple_condicion_y
silabear --> _get_accion_grupos_separados
silabear --> _get_condicion_h
silabear --> _get_condicion_psicol
silabear --> _get_condicion_q
silabear --> _get_condicion_vocales
silabear --> _get_condicion_vuv
silabear --> _get_separador_vocales
silabear --> get_position_first_vowel
silabear --> get_position_last_vowel
silabear --> has_text
silabear --> is_vowel
is_vowel -. datos .-> MOD
has_text -. datos .-> MOD
get_letters -. datos .-> MOD
desacentuar_vocal -. datos .-> MOD
_get_numero_silabas -. datos .-> MOD
_get_condicion_vocales -. datos .-> MOD
_get_condicion_psicol -. datos .-> MOD
numero_silabas -. datos .-> MOD
classDef red fill:#fde7f0,stroke:#c0397b,color:#7a1e4d;
class MOD red;# -*- coding: utf-8 -*-
"""
upln.py — Silabeador y acentuador algorítmicos del español.
Sin lookup y sin base de datos: PURO ALGORITMO. Divide en sílabas y coloca la tilde
aplicando las reglas del español (diptongos, hiatos, grupos consonánticos, tonicidad).
Método público principal:
silabear(palabra) -> "cam=pe=ón" (getPalabraSilabeada)
numero_silabas(silabeada) -> int
int_silaba_tonica(silabeada) -> 1=aguda, 2=llana, 3=esdrújula... (desde el final)
acentuar(palabra) -> coloca la tilde correcta (getAcentuacionPalabra)
acentuar_silabeada(sil, pos) -> idem a partir de silabeada+posición
desacentuar_palabra(palabra) -> quita tildes
"""
SEPARADOR = "="
# --- listas de vocales del español ---
VOC_ABIERTAS = set("a,á,A,Á,ä,Ä,e,é,E,É,ë,Ë,o,ó,O,Ó,ö,Ö".split(","))
VOC_ABIERTAS_SIN_ACENTUAR = set("a,A,e,E,o,O".split(","))
VOC_CERRADAS = set("i,í,I,Í,ï,Ï,u,ú,U,Ú,ü,Ü".split(","))
VOC_ABIERTAS_ACENTUADAS = set("á,Á,é,É,ó,Ó".split(","))
VOC_CERRADAS_ACENTUADAS = set("í,Í,ú,Ú".split(","))
VOC_ABIERTAS_CERRADAS_ACENTUADAS = VOC_ABIERTAS | VOC_CERRADAS_ACENTUADAS
VOC_ACENTUADAS = VOC_ABIERTAS_ACENTUADAS | VOC_CERRADAS_ACENTUADAS
CONSONANTES_ESPECIALES = set("pr,pl,br,bl,fr,fl,cr,cl,gr,gl,tr,dr,ll,ch,tx,tz,fl,rr,kl".split(","))
_VOWELS_ISVOWEL = set("aeiouáéíóúAEIOUÁÉÍÓÚäëïöüÄËÏÖÜ")
_CONSONANTS = set("bcdfghjklmnñpqrstvwxyzBCDFGHJKLMNÑPQRSTVWXYZ")
# ================= auxiliares de texto =================
def is_vowel(c):
return c in _VOWELS_ISVOWEL if c else False
def has_text(s):
return bool(s) and any(not ch.isspace() for ch in s)
def get_position_first_vowel(s):
for i, c in enumerate(s):
if is_vowel(c):
return i
return -1
def get_position_last_vowel(s):
for i in range(len(s) - 1, -1, -1):
if is_vowel(s[i]):
return i
return -1
def get_num_consonantes_antes_de_vocal(s):
s = s.replace("-", "")
r = 0
for c in s:
if not is_vowel(c):
r += 1
else:
break
return r
def get_letters(s, ini, fin):
"""Trozo de `s` entre `ini` y `fin`; si fin>len, devuelve todo el input."""
if ini > len(s):
return ""
if ini <= fin:
if fin > len(s):
return s
return s[ini:fin]
return ""
def _compara(a, b, accent_sensitive=False):
"""Compara dos caracteres: por defecto ignora tildes y mayúsculas; sensible a tilde si se pide."""
if a is None or b is None:
return False
if accent_sensitive:
return a.lower() == b.lower()
return desacentuar_palabra(a).lower() == desacentuar_palabra(b).lower()
# ================= acentuar / desacentuar vocales =================
_ACENTUAR = {"a": "á", "e": "é", "i": "í", "o": "ó", "u": "ú",
"A": "Á", "E": "É", "I": "Í", "O": "Ó", "U": "Ú"}
_DESACENTUAR = {"á": "a", "é": "e", "í": "i", "ó": "o", "ú": "u",
"Á": "A", "É": "E", "Í": "I", "Ó": "O", "Ú": "U"}
def desacentuar_vocal(v):
return _DESACENTUAR.get(v, v)
def desacentuar_palabra(palabra):
return "".join(desacentuar_vocal(c) for c in palabra)
# ================= silabeo =================
def _cumple_condicion_y(palabra):
if not has_text(palabra):
return False
result = False
if len(palabra) > 1:
i = 0
while (not result) and i < len(palabra):
if palabra[i].lower() == "y":
if i == 0:
result = not is_vowel(palabra[i + 1])
elif i == len(palabra) - 1:
result = not is_vowel(palabra[i - 1])
else:
result = (not is_vowel(palabra[i - 1])) and (not is_vowel(palabra[i + 1]))
i += 1
return result
_CASOS_RAROS = set("ii,uu,ihi,uhu,ihí,íhi,úhu,uhú,ií,íi,úu,uú".split(","))
def _get_separador_vocales(palabra, posicion):
result = 1
cont_abcer = 0
cont_cerradas = 0
i = posicion
caso2 = get_letters(palabra, posicion, posicion + 2)
caso3 = get_letters(palabra, posicion, posicion + 3)
if not (caso2 in _CASOS_RAROS or caso3 in _CASOS_RAROS):
if palabra[posicion] in VOC_CERRADAS:
cont_cerradas = 1
if palabra[posicion] in VOC_ABIERTAS_CERRADAS_ACENTUADAS:
cont_abcer = 1
while (len(palabra) > i + 1) and (
((cont_abcer == 0) or ((cont_abcer == 1) and palabra[i + 1] not in VOC_ABIERTAS_CERRADAS_ACENTUADAS)) and
((cont_cerradas < 2) or ((cont_cerradas == 2) and palabra[i + 1] not in VOC_CERRADAS)) and
(palabra[i + 1].lower() == "h" or is_vowel(palabra[i + 1]))
):
if palabra[i + 1] in VOC_CERRADAS:
cont_cerradas += 1
if palabra[i + 1] in VOC_ABIERTAS_CERRADAS_ACENTUADAS:
cont_abcer += 1
result += 1
i += 1
return result
def _get_condicion_q(palabra, posicion):
if len(palabra) > posicion + 2:
if _compara(palabra[posicion], "g", accent_sensitive=True):
if _compara(palabra[posicion + 1], "u", accent_sensitive=True):
if _compara(palabra[posicion + 2], "e") or _compara(palabra[posicion + 2], "i"):
return True
return False
def _get_accion_grupos_separados(grupo):
if not has_text(grupo):
raise ValueError("grupo vacío")
if is_vowel(grupo[0]):
return 1
n = get_num_consonantes_antes_de_vocal(grupo)
if n == 0:
return 1
if n == 1:
return 1
if n == 2:
return 1 if grupo[0:2] in CONSONANTES_ESPECIALES else 2
# 3 o más
if grupo[1:3] in CONSONANTES_ESPECIALES:
return 3
if grupo[0:2].lower() in ("ns", "bs", "ds"):
return 4
if grupo[1:2].lower() == "s":
return 4
return 5
def _get_numero_silabas(silabeada):
return len(silabeada.split(SEPARADOR))
def _get_condicion_h(palabra):
result = palabra
if "h" in palabra:
if _get_numero_silabas(result) > 1:
if "h" + SEPARADOR in result:
result = result.replace("h" + SEPARADOR, SEPARADOR + "h")
return result
def _get_condicion_vocales(palabra):
result = palabra
aux = palabra.replace(SEPARADOR, "")
replace = None
replacement = None
cambiar = False
for i in range(len(aux)):
cerradas = False
hache = False
j = i
if aux[j] in VOC_ABIERTAS:
replace = aux[j]
replacement = aux[j] + SEPARADOR
if j + 1 < len(aux):
if aux[j + 1] in VOC_CERRADAS:
cerradas = True
elif aux[j + 1].lower() == "h":
if j + 2 < len(aux):
if aux[j + 2] in VOC_CERRADAS:
j = j + 1
cerradas = True
hache = True
replace = replace + "h"
if cerradas:
replace = replace + aux[j + 1] + SEPARADOR
if hache:
replacement = replacement + "h" + aux[j + 1]
else:
replacement = replacement + aux[j + 1]
if j + 2 < len(aux):
if aux[j + 2] in VOC_ABIERTAS:
replace = replace + aux[j + 2]
replacement = replacement + aux[j + 2]
cambiar = True
break
if cambiar:
result = palabra.replace(replace, replacement, 1)
return result
def _get_condicion_psicol(palabra):
aux = palabra.replace(SEPARADOR, "").lower()
if "psicol" in aux:
return palabra.lower().replace("p" + SEPARADOR + "si", SEPARADOR + "psi")
return palabra
def _get_condicion_vuv(palabra):
result = palabra
aux = palabra.replace(SEPARADOR, "")
replace = None
replacement = None
cambiar = False
for i in range(len(aux)):
j = i
if is_vowel(aux[j]):
replace = aux[j]
replacement = aux[j] + SEPARADOR
if j + 1 < len(aux):
if aux[j + 1].lower() == "h":
replace = replace + "h"
replacement = replacement + "h"
j += 1
if j + 1 < len(aux):
if aux[j + 1].lower() == "u":
replace = replace + aux[j + 1]
replacement = replacement + aux[j + 1]
if j + 2 < len(aux):
if is_vowel(aux[j + 2]):
replace = replace + aux[j + 2]
replacement = replacement + aux[j + 2]
cambiar = True
break
if cambiar:
if replace in palabra:
result = palabra.replace(replace, replacement, 1)
else:
r2 = replace.replace("u", SEPARADOR + "u", 1)
r3 = replace.replace("u", "u" + SEPARADOR, 1)
if r2 in palabra:
result = palabra.replace(r2, replacement, 1)
elif r3 in palabra:
result = palabra.replace(r3, replacement, 1)
return result
def silabear(palabra):
"""Devuelve la palabra dividida en sílabas, con '=' entre ellas (cam=pe=ón)."""
if palabra is None:
raise ValueError("null")
if not has_text(palabra):
raise ValueError("sin texto")
palabra_original = palabra
if _cumple_condicion_y(palabra_original):
palabra = palabra.replace("y", "i")
result = []
i = 0
L = len(palabra)
while i < L:
if is_vowel(palabra[i]):
if i < L - 1:
n = _get_separador_vocales(palabra, i)
if n in (1, 2, 3, 4):
result.append(palabra[i:i + n])
result.append(SEPARADOR)
i += n
else:
result.append(palabra[i]); i += 1
else:
result.append(palabra[i]); i += 1
else:
if _compara(palabra[i], "q") or _get_condicion_q(palabra, i):
result.append(palabra[i])
if i + 1 < L:
result.append(palabra[i + 1]); i += 1
i += 1
else:
result.append(palabra[i]); i += 1
result = "".join(result)
if "qu" + SEPARADOR + "i" in result:
result = result.replace("qu" + SEPARADOR + "i", "qui")
elif "gu" + SEPARADOR + "i" in result:
result = result.replace("gu" + SEPARADOR + "i", "gui")
# borrar SEPARADOR que dejaría consonantes colgando al final
borrar = -1
i = len(result) - 1
while borrar == -1 and i > 0:
if is_vowel(result[i]):
break
else:
if result[i] == SEPARADOR:
borrar = i
i -= 1
if borrar != -1:
result = result[:borrar] + result[borrar + 1:]
# redistribución de consonantes entre sílabas
lResultados = result.split(SEPARADOR)
out = []
for j in range(len(lResultados)):
if j == 0:
out.append(lResultados[j])
if len(lResultados) > 1:
out.append(SEPARADOR)
else:
grupo = lResultados[j]
accion = _get_accion_grupos_separados(grupo)
if accion == 1:
out.append(lResultados[j])
if j != len(lResultados) - 1:
out.append(SEPARADOR)
elif accion in (2, 3):
out.pop() # quita el último SEPARADOR
out.append(grupo[0]); out.append(SEPARADOR); out.append(grupo[1:])
if j != len(lResultados) - 1:
out.append(SEPARADOR)
elif accion == 4:
out.pop()
out.append(grupo[0]); out.append(grupo[1]); out.append(SEPARADOR); out.append(grupo[2:])
if j != len(lResultados) - 1:
out.append(SEPARADOR)
elif accion == 5:
pos_ult = get_position_last_vowel(grupo)
if pos_ult != -1:
palabraAux = grupo[0:pos_ult]
buscar = palabraAux[len(palabraAux) - 2:len(palabraAux)]
if buscar in CONSONANTES_ESPECIALES:
out.pop()
out.append(palabraAux[0:len(palabraAux) - 2]); out.append(SEPARADOR)
out.append(buscar); out.append(grupo[pos_ult:])
if j != len(lResultados) - 1:
out.append(SEPARADOR)
else:
buscar1 = palabraAux[len(palabraAux) - 1:len(palabraAux)]
if buscar1.lower() == "h":
out.pop()
out.append(palabraAux[0:len(palabraAux) - 2]); out.append(SEPARADOR)
out.append(palabraAux[len(palabraAux) - 2:len(palabraAux)]); out.append(grupo[pos_ult:])
if j != len(lResultados) - 1:
out.append(SEPARADOR)
else:
posicion = get_position_first_vowel(grupo)
if posicion != -1:
out.pop()
out.append(grupo[0:posicion - 1]); out.append(SEPARADOR)
out.append(grupo[posicion - 1:])
if j != len(lResultados) - 1:
out.append(SEPARADOR)
else:
out.append("No contemplado")
elif accion == -1:
return "No contemplado"
palabra_resultado = "".join(out)
# restaurar la 'y' original donde se sustituyó por 'i'
result2 = []
j = 0
for i in range(len(palabra_original)):
if j < len(palabra_resultado) and palabra_resultado[j] == SEPARADOR:
result2.append(SEPARADOR); j += 1
if j < len(palabra_resultado) and palabra_resultado[j].lower() == palabra_original[i].lower():
result2.append(palabra_resultado[j])
else:
result2.append("y")
j += 1
aux = "".join(result2)
aux = _get_condicion_h(aux)
aux = _get_condicion_vocales(aux)
aux = _get_condicion_psicol(aux)
aux = _get_condicion_vuv(aux)
return aux
# ================= sílaba tónica =================
def int_silaba_tonica(silabeada):
if not has_text(silabeada):
raise ValueError("sin texto")
result = -1
listaSilabas = silabeada.split(SEPARADOR)
if len(listaSilabas) == 1:
result = 1
else:
i = len(listaSilabas) - 1
posicion = 1
while result == -1 and i >= 0:
pAux = listaSilabas[i]; i -= 1
for ch in pAux:
if ch in VOC_ACENTUADAS:
result = posicion
break
posicion += 1
if result == -1:
c = silabeada[-1]
if c.lower() == "s":
if len(silabeada) > 1 and not is_vowel(silabeada[-2]):
result = 1
else:
result = 2
elif is_vowel(c) or c.lower() == "n":
result = 2
if result == -1:
result = 1
return result
def numero_silabas(silabeada):
return len(silabeada.lower().split(SEPARADOR))
# ================= acentuación =================
if __name__ == "__main__":
# […pruebas del módulo (batería de palabras y comprobación)…]
Llámala con el código a la vista.
curl "https://api.wordkers.com/v1/acentuacion" \
-H "X-API-Key: TU_CLAVE"