El siguiente código implementa tus especificaciones, concretando un poco más los casos que estaban poco especificados. En concreto, lo que implementa es:
- Las secuencias de espacios al inicio de una línea se cambian por tabuladores, de modo que cada 4 espacios genera un tabulador (y los espacios sobrantes se conservan)
- Las secuencias de espacios entre palabras se cambian a un solo espacio. Se considera "palabra" cualquier secuencia de símbolos que no sea un "blanco" (espacio, tabulador o retorno de carro)
- Tras un signo de puntuación, si aparece pegado algo que no sea un "blanco", se debe insertar un espacio. Se consideran signos de puntuación cualquier combinación de ".,;:" Por tanto "..." también sería un signo de puntuación (uno solo) de modo que "Digo...esto" debe ser convertido en "Digo... esto", y no en "Digo. . . esto"
La siguiente función hace todo esto. En realidad no lo he probado con montones de casos, sino sólo con el ejemplo que después pondré. Si das con algún caso en que no se comporte como esperaba, plantéalo en un comentario.
import re
def correcciones(lineas):
resultado = []
for linea in lineas:
# Reemplazar cada 4 espacios al inicio de línea por 1 TAB
tabs = 0
while linea.startswith(" "):
tabs+=1
linea = linea[4:]
linea = "\t"*tabs + linea
# Reemplazar varios espacios entre palabras por uno solo
linea = re.sub(r"(\S) {2,}", r"\1 ", linea)
# Añadir espacio tras signo de puntuación no final
linea = re.sub(r"([:;.,]+)(\S)", r"\1 \2", linea)
resultado.append(linea)
return resultado
Caso de prueba (tiene de todo un poco):
txt = [
"Hola que pasa,\n",
" Aqui hay cuatro espacios al inicio.Y muchos entre palabras.\n",
" Aqui hay ocho al principio.",
" Y aqui diez...Y mas texto."
]
print(correcciones(txt))
Sale:
['Hola que pasa,\n',
'\tAqui hay cuatro espacios al inicio. Y muchos entre palabras.\n',
'\t\tAqui hay ocho al principio.',
'\t\t Y aqui diez... Y mas texto.']
Que parece estar correcto.
Como ves en el código, el problema del tabulador al inicio de la línea lo resolví "a pelo", iterando quitando espacios de 4 en 4 hasta que no hubiera más. El resto de los casos los resolví con expresiones regulares. Si tienes interés puedo explicar cómo funcionan. No pude usar una expresión regular también para lo del tabulador por culpa del requisito "al principio de la línea". Si la conversión 4 espacios -> tabulador se tuviera que hacer en cualquier lugar, se podría haber hecho también con expresiones regulares (re.sub(r" {4}", "\t", linea)
)
Actualización
Explicación de la expresión regular. Una expresión regular es una cadena formada por una serie de símbolos que tienen un significado especial. La expresión regular representa un cierto patrón a buscar dentro de una cadena dada (por ejemplo: "una secuencia de cuatro dígitos", o "Una palabra que comienza por mayúscula y tiene seis letras", etc.) Las expresiones regulares se construyen usando un lenguaje concreto. No es este lugar para explicar al completo ese lenguaje. Puedes mirar la documentación de python al respecto o esta pregunta-resumen en Stack Overflow en Español.
En este apartado simplemente voy a explicar las expresiones regulares que he usado en mi solución.
La primera es la que aparece en:
linea = re.sub(r"(\S) {2,}", r"\1 ", linea)
re.sub()
es una función que espera como primer parámetro una expresión regular, que enseguida explicaré, que encajará con ciertas partes de una cadena, como segundo parámetro una cadena de sustitución que se usará para sustituir las partes en las que la expresión regular haya encajado, y como tercer parámetro la cadena a la que hay que aplicárselo, en este caso la línea que estamos procesando.
La expresión regular en Python se escribe como una cadena entre comillas, pero con una r
delante de la primera comilla (esto es porque en las expresiones regulares muy a menudo aparece la barra inversa \
, que en una cadena normal tiene un significado especial, y en una cadena raw, marcada con la r
fuera, no tiene significado especial para python). En nuestro caso la expresión regular es:
(\S) {2,}
Esto significa lo siguiente (carácter a carácter)
(
la apertura de un parentesis indica que comienza un "grupo de captura". Lo que haya dentro de los paréntesis será "capturado" cuando la expresión regular encaje con algo de lo que había en la línea. Esa "captura" estará disponible para usarse en la cadena de sustitución, donde se ponga \1
.
- El carácter
\
es especial e indica que para interpretarlo hay que seguir mirando el siguiente carácter.
- La secuencia
\S
es un símbolo que dentro de una expresión regular significa "cualquier carácter que no sea un blanco". Los blancos son espacios, tabuladores, retornos de carro y otros caracteres unicode con el mismo significado (separadores invisibles). Por tanto un "no-blanco" es cualquier otro signo visible (una letra, un número, un signo de puntuacion, etc.)
)
cierra el grupo de captura.
es un espacio en blanco
{2,}
es un cuantificador que afecta al carácter anterior y que indica que debe aparecer repetido un mínimo de 2 veces en este caso y un máximo sin especificar. (Se puede poner un máximo tras la coma si se quiere indicar un máximo de repeticiones)
Por tanto toda la expresión regular podría traducirse a lenguaje llano como:
Cualquier caracter no-blanco seguido de dos o más espacios (siendo el caracter en cuestión capturado)
Así, por ejemplo, en la cadena "Esto es una prueba"
, ese patrón encajaría en varios sitios. El primero sería en la o
de Esto, pues esa o
es un carácter no-blanco seguido de dos o más espacios. La expresión encajaría con las subcadenas marcadas en las siguiente figura:
Esto es una prueba
^^^^^^ ^^^ ^^^^^
En el primer match tendría seis caracteres: o
(una o y cinco espacios) y el grupo de captura asociado a ese match sería la o
.
El siguiente parámetro de re.sub()
vale r"\1 "
, que significa:
Sustituir el match que se haya obtenido por una cadena formada por el contenido del grupo de captura y un espacio.
Por tanto "o "
será sustituido por "o "
(la o
sería el \1
). Análogamente "s "
será sustituido por "s "
, etc.
De este modo se sustituyen varios espacios por uno solo, pero sólo si esos espacios iban después de algo que no era espacio.
La siguiente expresión regular aparece en la línea:
linea = re.sub(r"([:;.,]+)(\S)", r"\1 \2", linea)
Ya hemos explicado qué hace re.sub()
, expliquemos ahora la expresión regular usada en este caso, que tiene en esta ocasión dos grupos de captura:
([:;.,]+)(\S)
(
se abre el primer grupo de captura
[
el corchete indica un "conjunto". La expresión es como un "or" y encajará si uno de los elementos dentro de los corchetes aparece en la cadena.
:,.,
son los posibles caracteres a encajar, los signos de puntuación especificados en la pregunta
]
se cierra el conjunto. El conjunto completo encajará con un solo carácter. Por ejemplo, si la cadena contiene "Hola, mundo"
, ese conjunto encajaría con la coma.
+
este signo indica que el carácter anterior puede aparecer más de una vez. Equivale a haber puesto {1,}
. Por tanto ahora si aparecieran varios signos de puntuación seguidos, encajarían toda la secuencia. Por ejemplo en la cadena "Hola,.; mundo"
, encajaría la parte ,.;
)
se acaba el grupo de captura, que contendría por tanto la secuencia de signos hallada.
(
se incicia un segundo grupo de captura
\S
que contiene un solo caracter, no-blanco
)
y se acaba el segundo grupo de captura.
Por tanto toda la expresión regular puede leerse como:
Una secuencia de uno o más signos del conjunto :.;,
seguido inmediatamente después de un carácter no-blanco.
Por ejemplo, en la cadena "Hola, mundo"
no se producirían coincidencias pues aunque aparece una coma, después va un blanco en lugar de un no-blanco. En cambio en la cadena "Hola,mundo"
sí habría una coincidencia, pues aparece la coma y detrás un carácter no blanco que es la m
. En ese match el primer grupo de captura contendría la coma, y el segundo la m
.
Cuando se encuentra un match el texto encajado se cambia por este otro: r"\1 \2"
, es decir, se mete un espacio separando el signo de puntuación de la letra que le seguía.
La página web https://regex101.com/ es muy útil para cuando estás aprendiendo expresiones regulares. Te permite escribir una, y te la "traduce" en el panel derecho, a la vez que te permite comprobar si encaja o no con cualquier texto de prueba que quieras darle.