Está en la página 1de 71

12 En este capítulo:

• Un interactivo
Corrector ortográfico
• Generando un
Índice formateado
• Detalles de repuesto de la
índice maestro
Prog ram

Con todas las funciones


Aplicaciones

Este capítulo presenta dos aplicaciones complejas que integran la mayoría de las
características del lenguaje de programación awk. El primer programa, el corrector
ortográfico, proporciona una interfaz interactiva para el programa de ortografía de UNIX.
La segunda aplicación, índice maestro, es un programa por lotes para generar un índice
para un libro o un conjunto de libros. Incluso si no está interesado en la aplicación en
particular, debe estudiar estos programas más grandes para tener una idea del alcance de
los problemas que un programa awk puede resolver.

Un corrector ortográfico interactivo


El programa de ortografía UNIX hace un trabajo adecuado para detectar errores
ortográficos en un documento. Para la mayoría de las personas, sin embargo, solo hace la
mitad del trabajo. No te ayuda a corregir las palabras mal escritas. Los usuarios nuevos
de hechizos se encuentran anotando las palabras mal escritas y luego usando el editor de
texto para cambiar el documento. Los usuarios más expertos crean un script sed para
realizar los cambios automáticamente.

El programa de corrección ortográfica ofrece otra forma: le muestra cada palabra que el
hechizo ha encontrado y le pregunta si desea corregir la palabra. Puede cambiar cada
aparición de la palabra después de ver la línea en la que aparece, o puede corregir el error
ortográfico a nivel mundial. También puede optar por agregar cualquier palabra que se
deletrea en un archivo de diccionario local.

Antes de describir el programa, demos una demostración de cómo funciona. El usuario


ingresa el corrector ortográfico, un script de shell que invoca awk, y el nombre del
archivo de documento.

$ corrector ortográfico ch00


¿Usar archivo local dict? (y / n) y
278
Un corrector ortográfico interactivo 279

Si no se especifica un archivo de diccionario en la línea de comando, y existe un archivo


llamado dict en el directorio actual, se le pregunta al usuario si se debe usar el diccionario
local. Spellcheck luego ejecuta Spell usando el diccionario local.
Ejecutando corrector ortográfico ...

Usando la lista de palabras “mal escritas” aparecidas por hechizo, el corrector ortográfico
le indica al usuario que las corrija. Antes de que se muestre la primera palabra, se muestra
una lista de respuestas que describe qué acciones son posibles.
Respuestas:
Cambiar cada ocurrencia,
Cambio global,
Añadir a Dict,
Ayuda,
Dejar
CR para ignorar:
1 - SparcStation encontrado (C / G / A / H / Q /): a

La primera palabra encontrada por hechizo es "SparcStation". Una respuesta de "a"


(seguida de un retorno de carro n) agrega esta palabra a una lista que se utilizará para
actualizar el diccionario. La segunda palabra es claramente un error ortográfico y se
ingresa una respuesta de "g" para hacer el cambio globalmente:

2 - Idioma encontrado (C / G / A / H / Q /): g


Cambiar globalmente a: idioma
¿Cambiar globalmente el idioma a idioma? (y / n): y
> y una descripción completa de su lenguaje de script.
1 líneas cambiadas. ¿Guardar cambios? (y / n) y

Después de solicitar al usuario que ingrese la ortografía correcta y de confirmar la


entrada, se realiza el cambio y se muestra cada línea afectada, precedida por un ">".
Luego se le pide al usuario que apruebe estos cambios antes de guardarlos. La tercera
palabra también se agrega al diccionario:

3 - Nawk encontrado (C / G / A / H / Q /): a

La cuarta palabra es un error ortográfico de "utilidades".


4 - Utilidades encontradas (C / G / A / H / Q /): c
Estas utilidades tienen muchas cosas en común, incluyendo
ˆˆˆˆˆˆˆˆˆˆ
Cambiar a: utilidades
¿Cambiar las utilidades a las utilidades? (y / n): y
Otras dos utilidades que se encuentran en el sistema UNIX
ˆˆˆˆˆˆˆˆˆˆ
¿Cambiar las utilidades a las utilidades? (y / n): y
> Estas utilidades tienen muchas cosas en común, incluidas> Otras dos
utilidades que se encuentran en el sistema UNIX 2 líneas cambiadas.
¿Guardar cambios? (y / n) y
280 Capítulo 12: Aplicaciones con todas las
funciones

El usuario ingresa "c" para cambiar cada ocurrencia. Esta respuesta permite al usuario ver
la línea que contiene la falta de ortografía y luego hacer el cambio. Después de que el
usuario haya realizado cada cambio, se muestran las líneas modificadas y se le pide al
usuario que confirme si guarda los cambios.

No está claro si la quinta palabra es una falta de ortografía o no, por lo que el usuario
ingresa "c" para ver la línea.
5 - Encontrado xvf (C / G / A / H / Q /): c
nombre de archivo tar xvf
ˆˆˆ
Cambiar a: VOLVER

Después de determinar que no es una falta de ortografía, el usuario ingresa un carro


retornado para ignorar la palabra. En general, la ortografía genera muchas palabras que
no son errores ortográficos, por lo que un carro devuelto significa ignorar la palabra.

Después de que se hayan procesado todas las palabras de la lista, o si el usuario se cierra
antes, se le solicitará que guarde los cambios realizados en el documento y el diccionario.

Guardar correcciones en ch00 (y / n)? y


¿Hacer cambios al diccionario (y / n)? y

Si el usuario responde "n", el archivo original y el diccionario no se modifican.

Ahora veamos el script spellcheck.awk, que se puede dividir en cuatro secciones:

• El procedimiento BEGIN, que procesa los argumentos de la línea de comandos y


ejecuta el comando de deletreo para crear una lista de palabras.
• El procedimiento principal, que lee una palabra a la vez de la lista y solicita al usuario
que realice una corrección.
• El procedimiento END, que guarda la copia de trabajo del archivo, sobrescribiendo el
original. También agrega palabras de la lista de excepciones al diccionario actual.

• Funciones de soporte, que se llaman para realizar cambios en el archivo.


Examinaremos cada una de estas secciones del programa.

Comenzar procedimiento
El procedimiento BEGIN para spellcheck.awk es grande. También es algo inusual.
# spellcheck.awk - corrector ortográfico interactivo
#
# AUTOR: Dale Dougherty
##
Un corrector ortográfico interactivo 281

# Uso: archivo nawk -f spellcheck.awk [+ dict]


# (Utilice el corrector ortográfico como nombre del programa de shell)
# SPELLDICT = "dict"
# SPELLFILE = "archivo"

# Las acciones BEGIN realizan las siguientes tareas:


# 1) procesar argumentos de línea de comandos
# 2) crear nombres de archivo temporales
# 3) ejecute el programa de ortografía para crear un archivo de lista de palabras
# 4) muestra la lista de respuestas de los usuarios

EMPEZAR {
# Procesar argumentos de línea de comandos
# Debe tener al menos dos argumentos: nawk y nombre de archivo si (ARGC> 1) {
# si hay más de dos argumentos, el segundo argumento es dict if (ARGC> 2) {
# prueba para ver si dict se especifica con "+"
# y asignar ARGV [1] a SPELLDICT
si (ARGV [1] ˜ /ˆ\+.*/)
SPELLDICT = ARGV [1]
más
SPELLDICT = "+" ARGV [1]
# asignar archivo ARGV [2] a SPELLFILE
SPELLFILE = ARGV [2]
# eliminar args para que awk no los abra como archivos eliminar ARGV [1]
eliminar ARGV [2]
}
# no más de dos args más {
# asignar archivo ARGV [1] a SPELLFILE
SPELLFILE = ARGV [1]
# prueba para ver si el archivo dict local existe si (! system ("test -r dict")) {
# si es así, pregunte si deberíamos usarlo
printf ("¿Usar archivo dict local? (s / n)")
getline responder <"-"
# si la respuesta es sí, use "dict"
if (respuesta ˜ / [aa] (es)? /) {
SPELLDICT = "+ dict"
}
}
}
} # fin del procesamiento de argumentos> 1
# si args no es> 1, imprima el uso del comando shell de otra manera {

imprimir "Uso: archivo ortográfico [+ dict]"


salida 1

}
# fin de procesamiento de argumentos de línea de comando
282 Capítulo 12: Aplicaciones con todas las
funciones

# crear nombres de archivo temporales, cada uno comienza con sp_ wordlist = "sp_wordlist"
spellsource = "sp_input"
spellout = "sp_out"

# copiar SPELLFILE al sistema de archivos de entrada temporal (fuente de hechizos "cp" SPELLFILE "")

# ahora ejecuta el programa de hechizo; salida enviada a la lista de palabras


imprima "Ejecutando corrector
ortográfico ..." if (SPELLDICT)
SPELLCMD = "hechizo" SPELLDICT ""
más
SPELLCMD = sistema "hechizo" (fuente de
hechizos SPELLCMD ">" lista de palabras)

# prueba la lista de palabras para ver si aparecen las palabras mal escritas
if (system (lista de palabras "test -s")) {
# si la lista de palabras está vacía (o el comando de deletreo falló), salga de print "No se
encontraron palabras mal escritas". sistema ("rm" spellsource "" lista de palabras)
salida
}

# asigne un archivo de lista de palabras a ARGV [1] para que awk lo lea. ARGV [1] = lista de palabras

# mostrar lista de respuestas del usuario


responseList = "Respuestas: \ n \ tCambie cada aparición,"
responseList = responseList "\ n \ tGlobal change,"
responseList = responseList "\ n \ tAgregar a Dict"
responseList = responseList "\ n \ tAyuda"
responseList = responseList "\ n \ tQuit"
responseList = responseList "\ n \ tCR para ignorar:"
printf ("% s", respuestaLista)

} # fin del procedimiento BEGIN

La primera parte del procedimiento BEGIN procesa los argumentos de la línea de


comandos. Comprueba que ARGC es mayor que uno para que el programa continúe. Es
decir, además de "nawk", se debe especificar un nombre de archivo. Este archivo
especifica el documento que analizará la ortografía. Se puede especificar un nombre de
archivo de diccionario opcional como segundo argumento. El script de corrección
ortográfica sigue la interfaz de la línea de comando de la ortografía, aunque ninguna de
las oscuras opciones de ortografía puede invocarse desde la línea de comando de la
corrección ortográfica. Si no se especifica un diccionario, el script ejecuta un comando de
prueba para ver si el archivo dict existe. Si lo hace, el mensaje le pide al usuario que
apruebe su uso como archivo de diccionario.

Una vez que hemos procesado los argumentos, los eliminamos de la matriz ARGV. Esto
es para evitar que se interpreten como argumentos de nombre de archivo.
Un corrector ortográfico interactivo 283

La segunda parte del procedimiento BEGIN configura algunos archivos temporales,


porque no queremos trabajar directamente con el archivo original. Al final del programa,
el usuario tendrá la opción de guardar o descartar el trabajo realizado en los archivos de
tempo-rary. Todos los archivos temporales comienzan con "sp_" y se eliminan antes de
salir del programa.

La tercera parte del procedimiento ejecuta un hechizo y crea una lista de palabras.
Probamos para ver que este archivo existe y que hay algo en él antes de continuar. Si por
alguna razón el programa de ortografía falla, o no se encuentran palabras mal escritas, el
archivo de la lista de palabras estará vacío. Si este archivo existe, asignamos el nombre
del archivo como el segundo elemento en la matriz ARGV. Esta es una forma inusual
pero válida de proporcionar el nombre del archivo de entrada que awk procesará. ¡Tenga
en cuenta que este archivo no existía cuando se invocó awk! El nombre del archivo del
documento, que se especificó en la línea de comando, ya no está en la matriz ARGV. No
leeremos el archivo del documento utilizando el bucle de entrada principal de awk. En
cambio, un ciclo while lee el archivo para encontrar y corregir palabras mal escritas.

La última tarea en el procedimiento BEGIN es definir y mostrar una lista de respuestas


que el usuario puede ingresar cuando se muestra una palabra mal escrita. Esta lista se
muestra una vez al comienzo del programa, así como cuando el usuario ingresa "Ayuda"
en el indicador principal. Poner esta lista en una variable nos permite acceder a ella desde
diferentes puntos del programa, si es necesario, sin mantener duplicados. La asignación
de responseList podría hacerse de manera más simple, pero la cadena larga no se podría
imprimir en este libro. (No se puede romper una cadena en dos líneas).

Procedimiento principal
El procedimiento principal es bastante pequeño, simplemente muestra una palabra mal
escrita y le pide al usuario que ingrese una respuesta apropiada. Este procedimiento se
ejecuta para cada palabra mal escrita.

Una razón por la cual este procedimiento es breve es porque la acción central —
corrección de una palabra mal escrita— es manejada por dos funciones más grandes
definidas por el usuario, que veremos en la última sección.

# procedimiento principal, ejecutado para cada línea en la lista de palabras.


# El propósito es mostrar palabras mal escritas y avisar al usuario
# para la acción apropiada

{
# asignar palabra a la falta de ortografía incorrecta = $ 1 respuesta = 1 ++ palabra
284 Capítulo 12: Aplicaciones con todas las
funciones

# escribe errores ortográficos y solicita respuesta


while (respuesta! ˜ / (ˆ [cCgGaAhHqQ]) | ˆ $ /) {
printf ("\ n% d - Encontrado% s (C / G / A / H / Q /):", palabra, falta de ortografía)
respuesta getline <"-"
}
# ahora procese la respuesta del usuario
# CR: el retorno de carro ignora la palabra actual
# Ayuda
if (respuesta ˜ / [Hh] (elp)? /) {
# Muestra la lista de respuestas y vuelve a preguntar. printf ("% s", respuestaLista)
printf ("\ n% d - Se encontró% s (C / G / A / Q /):", palabra, falta de
ortografía) respuesta getline <"-"
}
# Dejar
if (respuesta ˜ / [Qq] (uit)? /) salir
# Añadir al diccionario
if (respuesta ˜ / [Aa] (dd)? /) {
dict [++ dictEntry] = falta de ortografía
}
# Cambiar cada ocurrencia
if (respuesta ˜ / [cC] (hange)? /) {
# lee cada línea del archivo que estamos corrigiendo newspelling = ""; cambios = "" while
((getline <fuente de hechizos)> 0) {
# función de llamada para mostrar la línea con una palabra mal escrita
# y solicitar al usuario que haga cada corrección
make_change ($ 0)
# todas las líneas van al archivo de salida temporal print> deletreo
}
# todas las líneas han sido leídas
# cerrar la entrada temporal y el archivo de salida temporal cerrar (deletreo) cerrar (fuente de
hechizos)

# si se hizo un cambio
if (cambios) {
# mostrar líneas cambiadas
para (j = 1; j <= cambios; ++ j)
imprimir cambiadoLíneas [j]
printf ("% d líneas cambiadas", cambios)
# función para confirmar antes de guardar los cambios confirm_changes ()
}
}
# Cambiar globalmente
if (respuesta ˜ / [gG] (lobal)? /) {
# llamar a la función para solicitar la corrección
# y muestra cada línea que se cambia.
# Solicite al usuario que apruebe todos los cambios antes de guardar. make_global_change ()
}
} # fin del procedimiento principal
Un corrector ortográfico interactivo 285

El primer campo de cada línea de entrada de la lista de palabras contiene la palabra mal
escrita y se asigna a la falta de ortografía. Construimos un ciclo while dentro del cual
mostramos la palabra mal escrita al usuario y solicitamos una respuesta. Mire
detenidamente la expresión regular que prueba el valor de la respuesta:
while (respuesta! ˜ / (ˆ [cCgGaAhHqQ]) | ˆ $ /)

El usuario solo puede salir de este ciclo ingresando cualquiera de las letras especificadas
o ingresando un retorno de carro n, una línea vacía. El uso de expresiones regulares para
probar la entrada del usuario ayuda enormemente a escribir un programa simple pero
flexible. El usuario puede ingresar una sola letra “c” en minúscula o mayúscula o una
palabra que comience con “c” como “Cambiar”.

El resto del procedimiento principal consiste en declaraciones condicionales que evalúan


una respuesta específica y realizan una acción correspondiente. La primera respuesta es
"ayuda", que muestra nuevamente la lista de respuestas y luego vuelve a mostrar el
mensaje.

La siguiente respuesta es "dejar de fumar". La acción asociada con salir es salir, que
abandona el procedimiento principal y va al procedimiento FIN.

Si el usuario ingresa "agregar", la palabra mal escrita se coloca en la matriz dict y se


agregará como una excepción en un diccionario local.

Las respuestas "Cambio" y "Global" hacen que comience el verdadero trabajo del
programa. Es importante entender cómo difieren. Cuando el usuario ingresa “c” o
“cambio”, se muestra la primera aparición de la palabra mal escrita en el documento.
Luego se le solicita al usuario que realice el cambio. Esto sucede para cada aparición en
el documento. Cuando el usuario ingresa "g" o "global", se le solicita que realice el
cambio de inmediato, y todos los cambios se realizan a la vez sin que el usuario confirme
cada uno. Este trabajo es manejado en gran parte por dos funciones, make_change () y
make_global_change (), que veremos en la última sección. Estas son todas las respuestas
válidas, excepto una. Un carro devuelto significa ignorar la palabra mal escrita y obtener
la siguiente palabra en la lista. Esta es la acción predeterminada del bucle de entrada
principal,

Procedimiento FINAL
El procedimiento END, por supuesto, se alcanza en una de las siguientes circunstancias:
• El comando de hechizo falló o no apareció ningún error ortográfico.
• La lista de palabras mal escritas está agotada.
• El usuario ha ingresado "salir" en un mensaje.
El propósito del procedimiento END es permitir al usuario confirmar cualquier cambio
permanente en el documento o el diccionario.
286 Capítulo 12: Aplicaciones con todas las
funciones

# El procedimiento END hace que los cambios sean permanentes.


# Sobrescribe el archivo original y agrega palabras
# al diccionario
# También elimina los archivos temporales.

FIN {
# si llegamos aquí después de leer solo un registro,
# no se hicieron cambios, así que salga.
si (NR <= 1) sale
# el usuario debe confirmar guardar las correcciones en el archivo
while (saveAnswer! ˜ / ([aa] (es)?) | ([nN] o?) /) {
printf "¿Guardar correcciones en% s (y / n)?", SPELLFILE
getline saveAnswer <"-"
}
# si la respuesta es sí, entonces el archivo de entrada temporal de mv a SPELLFILE
# guardar viejo SPELLFILE, por si acaso
if (saveAnswer ˜ / ˆ [aa] /) {
sistema ("cp" SPELLFILE "" SPELLFILE ".orig")
sistema ("mv" fuente de hechizos "" SPELLFILE)
}
# si la respuesta es no, entonces rm archivo de entrada temporal if (saveAnswer ˜ / ˆ [nN] /)
sistema (fuente de hechizos "rm")

# si se han agregado palabras a la matriz del diccionario, entonces solicite


# para confirmar guardar en el diccionario actual.
if (dictEntry) {
printf "¿Hacer cambios al diccionario (y / n)?"
respuesta getline <"-"
if (respuesta ˜ / ˆ [aa] /) {
# si no hay un diccionario definido, entonces use "dict" if (! SPELLDICT)
SPELLDICT = "dict"

# recorrer la matriz y agregar palabras al sub diccionario (/ ˆ \ + /, "", SPELLDICT)


para (artículo en dict)
print dict [item] >> SPELLDICT
cerrar (SPELLDICT)
# ordenar archivo de diccionario
sistema ("sort" SPELLDICT "> tmp_dict")
sistema ("mv" "tmp_dict" SPELLDICT)
}
}
# eliminar lista de palabras
sistema ("rm sp_wordlist")
} # fin del procedimiento END

El procedimiento FIN comienza con una declaración condicional que prueba que el
número de registros es menor o igual a 1. Esto ocurre cuando el programa de ortografía
no genera una lista de palabras o cuando el usuario ingresa "salir" después de ver solo el
primer grabar. Si es así, se cierra el procedimiento END ya que no hay trabajo que
guardar.
Un corrector ortográfico interactivo 287

A continuación, creamos un ciclo while para preguntarle al usuario sobre cómo guardar
los cambios realizados en el documento. Requiere que el usuario responda "y" o "n" a la
solicitud. Si la respuesta es "y", el archivo de entrada temporal reemplaza el archivo de
documento original. Si la respuesta es "n", se elimina el archivo temporal. No se aceptan
otras respuestas.

A continuación, probamos para ver si la matriz dict tiene algo en ella. Sus elementos son
las palabras que se agregarán al diccionario. Si el usuario aprueba agregarlos al
diccionario, estas palabras se agregan al diccionario actual, como se definió
anteriormente, o si no, a un archivo dict local. Debido a que el diccionario debe ordenarse
para que se lea por hechizo, se ejecuta un comando sor t con la salida enviada a un
archivo temporal que luego se copia sobre el archivo original.

Funciones de soporte
Existen tres funciones de soporte, dos de las cuales son grandes y hacen la mayor parte
del trabajo de hacer cambios en el documento. La tercera función respalda ese trabajo al
confirmar que el usuario desea guardar los cambios realizados.

Cuando el usuario desea "Cambiar cada aparición" en el documento, el procedimiento


principal tiene un ciclo while que lee el documento una línea a la vez. (Esta línea se
convierte en $ 0.) Llama a la función make_change () para ver si la línea contiene la
palabra mal escrita. Si lo hace, se muestra la línea y se le solicita al usuario que ingrese la
ortografía correcta de la palabra.

# make_change: solicita al usuario que corrija la falta de ortografía


# para la línea de entrada actual. Se llama a sí mismo # para encontrar
otras ocurrencias en cadena.

# stringToChange - inicialmente $ 0; luego subcadena sin igual de $ 0


# len: longitud desde el principio de $ 0 hasta el final de la cadena coincidente
# Asume que la falta de ortografía está definida.

función make_change (stringToChange, len, # línea de parámetros,


OKmakechange, printtring, carets) # locales
{
# coincidir con la falta de ortografía en stringToChange; de lo contrario, no haga nada si (match
(stringToChange, error ortográfico)) {
# Mostrar línea coincidente
cadena de impresión = $ 0
gsub (/ \ t /, "", cadena de impresión)
imprimir cadena de impresión
carets = "ˆ"
para (i = 1; i <LONGITUD; ++ i)
carets = carets "ˆ"
si (len)
FMT = "%" len + RSTART + RLENGTH-2 "s \ n"
más
FMT = "%" RSTART + RLENGTH-1 "s \ n"
printf (FMT, cuidados)
288 Capítulo 12: Aplicaciones con todas las
funciones

# Solicitar al usuario la corrección, si aún no está definido si (! Newspelling) {


printf "Cambiar a:" getline
newspelling <"-"
}
# Un retorno de carro cae
# Si el usuario ingresa la corrección, confirme
while (newspelling &&! OKmakechange) {
printf ("¿Cambiar% s a% s? (s / n):", error ortográfico, ortografía)
getline OKmakechange <"-"
madechg = ""
# respuesta de prueba
if (OKmakechange ˜ / [aa] (es)? /) {
# hacer cambio (solo primera vez)
madechg = sub (falta de ortografía, ortografía, stringToChange)
}
más si (OKmakechange ˜ / [nN] o? /) {
# ofrece la oportunidad de volver a ingresar la corrección de impresión
"Cambiar a:"
getline newspelling <"-"
OKmakechange = ""
}
} # final del ciclo while

# if len, estamos trabajando con una subcadena de $ 0 if (len) {

# ensamblarlo
línea = substr ($ 0,1, len-1) $ 0 =
línea stringToChange
}
más {
$ 0 = stringToChange si
(madechg) ++ cambia
}

# poner la línea modificada en la matriz para mostrar


si (madechg)
changedLines [cambios] = ">" $ 0

# crear una subcadena para que podamos intentar hacer coincidir otras ocurrencias len + = RSTART +
RLENGTH
part1 = substr ($ 0, 1, len-1) part2 =
substr ($ 0, len)
# se llama a sí mismo para ver si se encuentra la falta de ortografía en la parte restante make_change
(part2, len)

} # fin de if

} # final de make_change ()

Si la palabra mal escrita no se encuentra en la línea de entrada actual, no se hace nada. Si


se encuentra, esta función muestra la línea que contiene la falta de ortografía y le
pregunta al usuario
Un corrector ortográfico interactivo 289

si debe ser corregido Debajo de la pantalla de la línea actual hay una fila de carros que
indica la palabra mal escrita.
Otras dos utilidades que se encuentran en el sistema UNIX
ˆˆˆˆˆˆˆˆˆˆ

La línea de entrada actual se copia a la cadena de impresión porque es necesario cambiar


la línea para fines de visualización. Si la línea contiene pestañas, cada pestaña de esta
copia de la línea se reemplaza temporalmente por un solo espacio. Esto resuelve un
problema de alinear los cantos cuando las pestañas estaban presentes. (Una pestaña
cuenta como un solo carácter al determinar la longitud de una línea, pero en realidad
ocupa un espacio mayor cuando se muestra, generalmente de cinco a ocho caracteres de
largo).

Después de mostrar la línea, la función solicita al usuario que ingrese una corrección.
Luego sigue mostrando lo que el usuario ha ingresado y solicita confirmación. Si se
aprueba la corrección, se llama a la función sub () para realizar el cambio. Si no se
aprueba, el usuario tiene otra oportunidad de ingresar la palabra correcta.

Recuerde que la función sub () solo cambia la primera aparición en una línea. La función
gsub () cambia todas las ocurrencias en una línea, pero queremos permitir que el usuario
confirme cada cambio. Por lo tanto, debemos tratar de hacer coincidir la palabra mal
escrita con la parte restante de la línea. Y tenemos que poder hacer coincidir la siguiente
aparición, independientemente de si la primera ocurrencia se modificó o no.

Para hacer esto, make_change () está diseñado como una función recursiva; se llama a sí
mismo para buscar ocurrencias adicionales en la misma línea. En otras palabras, la
primera vez que se llama a make_change (), mira todos los $ 0 y coincide con la primera
palabra mal escrita en esa línea. Luego divide la línea en dos partes: la primera parte
contiene los caracteres hasta el final de la primera aparición y la segunda parte contiene
los caracteres que siguen inmediatamente al final de la línea. Luego se llama a sí mismo
para tratar de hacer coincidir la palabra mal escrita en la segunda parte. Cuando se llama
recursivamente, la función toma dos argumentos.

make_change (part2, len)

La primera es la cadena que se cambiará, que inicialmente es $ 0 cuando se llama desde


el procedimiento principal, pero cada vez después es la parte restante de $ 0. El segundo
argumento es len o la longitud de la primera parte, que usamos para extraer la subcadena
y volver a ensamblar las dos partes al final.
La función make_change () también recopila una matriz de líneas que fueron cambiadas.
# poner la línea modificada en la matriz para mostrar si (madechg)
changedLines [cambios] = ">" $ 0
290 Capítulo 12: Aplicaciones con todas las
funciones

La variable madechg tendrá un valor si la función sub () fue exitosa. $ 0 (las dos partes se
han unido) se asignan a un elemento de la matriz. Cuando se han leído todas las líneas del
documento, el procedimiento principal realiza un bucle a través de esta matriz para
mostrar todas las líneas modificadas. Luego llama a la función confir m_changes () para
preguntar si estos cambios deben guardarse. Copia el archivo de salida temporal sobre el
archivo de entrada temporal, manteniendo intactas las correcciones realizadas para la
palabra mal escrita actual.

Si un usuario decide hacer un "Cambio global", se llama a la función


make_global_change () para hacerlo. Esta función es similar a la función make_change
(), pero es simple porque podemos hacer el cambio globalmente en cada línea.
# make_global_change -
# solicitar al usuario que corrija la falta de ortografía
# para todas las líneas a nivel mundial.
# No tiene argumentos
# Asume que la falta de ortografía está definida.

función make_global_change ( redacción de noticias, OKmakechange, cambios)


{
# solicitar al usuario que corrija la palabra mal escrita printf "Cambie globalmente a:" getline newspelling
<"-"

# el retorno del carro cae


# si hay una respuesta, confirme
while (newspelling &&! OKmakechange) {
printf ("¿Cambiar globalmente% s a% s? (y / n):", error ortográfico,
ortografía)

getline OKmakechange <"-"


# prueba de respuesta y hacer cambios
if (OKmakechange ˜ / [aa] (es)? /) {
# abrir archivo, leer todas las líneas
while ((getline <fuente de hechizos)> 0) {
# Si se encuentra una coincidencia, realice el cambio con gsub
# e imprima cada línea cambiada.
if ($ 0 ˜ ortografía incorrecta) {
madechg = gsub (falta de ortografía, ortografía)
imprimir ">", $ 0
cambios + = 1 # contador para cambios de línea

}
# escribir todas las líneas en el archivo de salida temporal imprimir>
deletrear
} # final del ciclo while para leer el archivo

# cerrar archivos temporales


cerrar (deletreo)
cerrar (fuente de hechizos)
# informar el número de cambios
printf ("% d líneas cambiadas", cambios)
Un corrector ortográfico interactivo 291

# función para confirmar antes de guardar los cambios confirm_changes ()


} # fin de if (OKmakechange ˜ y)

# si no se confirma la corrección, solicite una nueva palabra


si no (OKmakechange ˜ / [nN] o? /) {printf
"Cambie globalmente a:" getline
newspelling <"-"
OKmakechange = ""
}

} # fin del ciclo while para solicitar al usuario la corrección} # end of

make_global_change ()

Esta función solicita al usuario que ingrese una corrección. Se configura un bucle while
para leer todas las líneas del documento y aplicar la función gsub () para realizar los
cambios. La diferencia principal es que todos los cambios se realizan a la vez: no se le
pide al usuario que los confirme. Cuando se han leído todas las líneas, la función muestra
las líneas que se modificaron y llama a m_changes () para confirmar que el usuario
aplique este lote de cambios antes de guardarlos.

La función confirma m_changes () es una rutina llamada para obtener la aprobación de


los cambios realizados cuando se llama a la función make_change () o
make_global_change ().
# confirm_changes -
# confirmar antes de guardar los cambios

function confirm_changes (savechanges) {


# solicitar confirmar los cambios guardados mientras (! savechanges) {
printf ("¿Guardar cambios? (y / n)")
getline savechanges <"-"
}
# si se confirma, mv salida a entrada
if (savechanges ˜ / [aa] (es)? /)
sistema (fuente de hechizos "mv" hechizo "")
}

La razón para crear esta función es evitar la duplicación de código. Su propósito es


simplemente requerir que el usuario reconozca los cambios antes de reemplazar la
versión anterior del archivo de documento (fuente de hechizos) con la nueva versión
(deletreo).

El corrector ortográfico Script Script


Para facilitar la invocación de esta secuencia de comandos awk, creamos la secuencia de
comandos del corrector ortográfico (digamos que tres veces más rápido). Contiene las
siguientes líneas:
AWKLIB = / usr / local / awklib
nawk -f $ AWKLIB / spellcheck.awk $ *
292 Capítulo 12: Aplicaciones con todas las
funciones

Este script configura una variable de shell AWKLIB que especifica la ubicación del
script spellcheck.awk. El símbolo "$ *" se expande a todos los parámetros de la línea de
comandos siguiendo el nombre del script. Estos parámetros están disponibles para awk.

Una de las cosas interesantes sobre este corrector ortográfico es lo poco que se hace en el
**
script de shell. Todo el trabajo se realiza en el lenguaje de programación awk, incluida
la ejecución de 10 comandos UNIX. Estamos utilizando una sintaxis consistente y las
mismas estructuras al hacerlo todo en awk. Cuando tiene que hacer parte de su trabajo en
el shell y algunos en awk, puede ser confuso. Por ejemplo, debe recordar las diferencias
en la sintaxis de if condicionales y cómo hacer referencia a las variables. Las versiones
modernas de awk proporcionan una verdadera alternativa al shell para ejecutar comandos
e interactuar con un usuario. La lista completa de spellcheck.awk se encuentra en el
Apéndice C, Suplemento para el Capítulo 12.

Generando un índice enmarañado


El proceso de generar un índice generalmente implica tres pasos:
• Codifique las entradas de índice en el documento.
• Para mat el documento, produciendo entradas de índice con números de página.
• Procese las entradas de índice para ordenarlas, combinando entradas que difieran solo
en el número de página y luego prepare el índice formateado.

Este proceso sigue siendo prácticamente el mismo si se utiliza trof f, otro lote codificado
para asuntos o un formateador WYSIWYG como FrameMaker, aunque los pasos no
están tan claramente separados con este último. Sin embargo, describiré cómo usamos
trof f para generar un índice como el de este libro. Codificamos el índice usando las
siguientes macros:

Macro Descripción
.XX Produce entradas de índice general.
.XN Crea referencias cruzadas "ver" o "ver también".
.XB Crea una entrada de página en negrita que indica la referencia principal.
.XS Comienza el rango de páginas para la entrada.
.XE Finaliza el rango de páginas para la entrada.

Estas macros toman un solo argumento entre comillas, que puede tener uno de varios
para ms, indicando claves primarias, secundarias o terciarias:
"primario [: secundario [; terciario ]] "
* Procesamiento de texto UNIX (Dougherty y O'Reilly, Howard W. Sams, 1987) presenta un corrector ortográfico
basado en sed que depende en gran medida del caparazón. Es interesante comparar las dos versiones.
Generando un índice enmarañado 293

Se utilizan dos puntos como separador entre las claves primaria y secundaria. Para
soportar una convención de codificación anterior, la primera coma se interpreta como el
separador si no se utilizan dos puntos. Un punto y coma indica la presencia de una clave
terciaria. El número de página siempre está asociado con la última clave.
Su e es una entrada con solo una clave primaria:
.XX "XView"

Las siguientes dos entradas especifican una clave secundaria:


.XX "XView: nombres reservados"
.XX "XView, paquetes"

Las entradas más complejas contienen claves terciarias:


.XX "XView: objetos; lista"
.XX "XView: objetos; jerarquía de"

Finalmente, hay dos tipos de referencias cruzadas:


.XN "recuperación de errores: (ver manejo de errores)"
.XX "mh mailer: (ver también xmh mailer)"

La entrada "ver" refiere a una persona a otra entrada de índice. El "ver también" se usa
normalmente cuando hay entradas para, en este caso, "correo electrónico mh", pero hay
información relevante catalogada con otro nombre. Solo las entradas "ver" no tienen
números de página asociados a ellas.

Cuando el documento es procesado por trof f, se producen las siguientes entradas de


índice:

XView 42
XView: nombres reservados43
XView, paquetes 43
XView: objetos; lista de 43
XView: objetos; jerarquía de 44
XView, paquetes 45
recuperación de errores: (Ver manejo de errores)
mh mailer: (ver también xmh mailer) 46

Estas entradas sirven como entrada para el programa de indexación. Cada entrada
(excepto las entradas "ver") consta de la clave y un número de página. En otras palabras,
la entrada se divide en dos partes y la primera parte, la clave, también se puede dividir en
tres partes. Cuando estas entradas son procesadas por el programa de indexación y la
salida es para matizada, las entradas para "XView" se combinan de la siguiente manera:
XView, 42
objetos; jerarquía de, 44;
lista de, 43
paquetes, 43,45
nombres reservados, 43
294 Capítulo 12: Aplicaciones con todas las
funciones

Para lograr esto, el programa de indexación debe:


• Ordenar el índice por clave y número de página.
• Combinar entradas que difieren solo en el número de página.
• Combinar entradas que tengan las mismas claves primarias y / o secundarias.
• Busque números de página consecutivos y combine como un rango.
• Prepare el índice en un formato para mostrar en pantalla o para imprimir.

Esto es lo que hace el programa de índice si está procesando las entradas de índice para
un solo libro. También le permite crear un índice maestro, un índice general para un
conjunto de volúmenes. Para hacer eso, una secuencia de comandos awk agrega un
número romano o una abreviatura después del número de página. Cada archivo contiene
las entradas para un libro en particular y esas entradas se identifican de forma única. Si
optamos por usar números romanos para identificar el volumen, las entradas anteriores se
cambiarían a:
XView 42: yo
XView: nombres reservados43: I
XView: objetos; lista de 43: I

Con entradas de varios volúmenes, el índice final que se genera podría verse así:
XView, I: 42; II: 55,69,75
objetos; jerarquía de, I: 44;
lista de, I: 43; II: 56
paquetes, I: 43,45
nombres reservados, I: 43

Por ahora, solo es importante reconocer que la entrada de índice utilizada como entrada
para el programa awk puede tener un número de página o un número de página seguido
de un identificador de volumen.

El programa de índice maestro


* *
Debido a la longitud y complejidad de esta aplicación de indexación, nuestra
descripción presenta la estructura más amplia del programa. Use los comentarios en el
programa mismo para comprender lo que sucede en el programa línea por línea.

* Los orígenes de este programa de indexación se remontan a una copia de un programa de indexación escrito en
awk por Steve Talbott. Aprendí este programa al desarmarlo e hice algunos cambios para admitir la numeración de
páginas consecutivas además de la numeración de páginas de sección. Ese fue el programa que describí en UNIX
Text Processing. Conociendo ese programa, escribí un programa de indexación que podría tratar con entradas de
índice producidas por Microsoft Word y generar un índice usando la numeración de páginas de sección. Más tarde,
necesitábamos un índice maestro para varios libros de nuestra serie X Window System. Aproveché la oportunidad
para repensar nuestro programa de indexación y reescribirlo usando nawk, para que sea compatible con los índices de
un solo libro y de varios libros. El lenguaje de programación AWK contiene un ejemplo de un programa de índice
que es más pequeño que el que se muestra aquí y podría ser un lugar para comenzar si lo encuentra demasiado
complicado. Sin embargo, no trata con llaves. Ese programa de indexación es una versión simplificada del descrito en
el Informe técnico 128 de Bell Labs Computing Science, Herramientas para imprimir índices, octubre de 1986, por
Brian Kernighan y Jon Bentley. [DD]
Generando un índice enmarañado 295

Después de las descripciones de cada uno de los módulos del programa, una sección final
discute algunos detalles restantes. En su mayor parte, estos son fragmentos de código que
se ocupan de problemas esenciales relacionados con las entradas que tuvieron que
* *
resolverse en el camino. El índice maestro del script de shell permite al usuario
especificar varias opciones de línea de comandos diferentes para especificar qué tipo de
índice hacer e invoca los programas awk necesarios para hacer el trabajo. Las
operaciones del programa de índice maestro se pueden dividir en cinco programas o
módulos separados que forman una sola tubería.
input.idx | ordenar | pagenums.idx | combine.idx | format.idx

Todos menos uno de los programas están escritos con awk. Para ordenar las entradas,
confiamos en sor t, una utilidad estándar de UNIX. Aquí hay un breve resumen de lo que
hace cada uno de estos programas:

input.idx
Estandariza el formato de las entradas y las rota.
sor t
Ordena las entradas por clave, volumen y número de página.
pa genums.idx
Fusiona entradas con la misma clave, creando una lista de números de página.
combine.idx
Combina números de página consecutivos en un rango.
format.idx
Prepara el índice formateado para la pantalla o el procesamiento por trof f.
Discutiremos cada uno de estos pasos en una sección separada.

Entrada de estandarización
Este script input.idx busca diferentes tipos de entradas y las estandariza para un
procesamiento más fácil por programas posteriores. Además, gira automáticamente las
entradas de índice que contienen una tilde (˜). (Consulte la sección "Rotación de dos
partes" más adelante en este capítulo).

La entrada al programa input.idx consta de dos campos separados por tabuladores, como
se describió anteriormente. El programa produce registros de salida con tres campos
separados por dos puntos. El primer campo contiene la clave primaria; el segundo campo
contiene las claves secundarias y terciarias, si están definidas; y el tercer campo contiene
el número de página.
* Este script de shell y la documentación para el programa se presentan en el Apéndice C. Es posible que desee
leer primero la documentación para una comprensión básica del uso del programa.
296 Capítulo 12: Aplicaciones con todas las
funciones

Ella es el código para el programa input.idx:


#! / work / bin / nawk -f
# ------------------------------------------------
# input.idx: estandariza la entrada antes de ordenar
# Autor: Dale Dougherty
# Versión 1.1 10/07/90
##
# la entrada es la pestaña "entrada" "número_página"
# ------------------------------------------------
COMIENZA {FS = "\ t"; OFS = ""}

N. ° 1 Entradas coincidentes que necesitan rotación que contienen una sola tilde
# $ 1 ˜ / ˜ [ˆ˜] / # regexp no funciona y no sé por qué $ 1 ˜ / ˜ / && $ 1! ˜ / ˜˜ / {
# dividir el primer campo en una matriz llamada subcampo
n = split ($ 1, subcampo, "˜")
si (n == 2) {
# imprime la entrada sin "˜" y luego gira
printf ("% s% s ::% s \ n", subcampo [1], subcampo [2], $ 2)
printf ("% s:% s:% s \ n", subcampo [2], subcampo [1], $ 2)
}
siguiente
} # Fin de 1

# 2 entradas coincidentes que contienen dos tildes


$ 1 ˜ / ˜˜ / {
# reemplace ˜˜ con ˜ gsub (/ ˜˜ /, "˜", $ 1)
} # Fin de 2

# 3 Entradas coincidentes que usan "::" para literal ":".


$ 1 ˜ / :: / {
# sustituya el valor octal por "::" gsub (/ :: /, "\\ 72", $ 1)
} # Fin de 3

# 4 limpiar entradas
{
# busque el segundo colon, que podría usarse en lugar de ";" if (sub (/:.*:/, "&;", $ 1)) {
sub (/:; /, ";", $ 1)
}
# elimine el espacio en blanco si lo hay después de los dos puntos.
sub (/: * /, ":", $ 1)
# Si se usa una coma como delimitador, conviértalo a dos puntos. if ($ 1! ˜ /: /) {

# En ver también y ver, intente poner el delimitador antes de "(" if ($ 1 ˜ / \ ([sS] ee /) {


if (sub (/, *. * \ (/, ": &", $ 1))
sub (/ :, * /, ":", $ 1)
más
sub (/ * \ (/, ":(", $ 1)
}
Generando un índice enmarañado 297

de lo contrario {# de lo contrario, solo busque la coma


sub (/, * /, ":", $ 1)
}
}
más {
# agregado para insertar punto y coma en "Ver" if ($ 1 ˜ /: [ˆ;] + * \ ([sS] ee /)
sub (/ * \ (/, "; (", $ 1)
}
} # Fin de 4

# 5 partido Ver Alsos y arreglar para ordenar al final


$ 1 ˜ / * \ ([Ss] ee + [Aa] lso / {
# agregue "˜zz" para ordenar al final
sub (/ \ ([Ss] ee + [Aa] lso /, "˜zz (ver también", $ 1)
if ($ 1 ˜ /: [ˆ;] + * ˜zz /) {
sub (/ * ˜zz /, "; ˜zz", $ 1)
}
# si no hay número de página si ($ 2 == "") {
print $ 0 ":"
siguiente
}
más {
# salida dos entradas:
# print Vea también la entrada sin número de página print $ 1 ":"
# eliminar Ver también
sub (/ * ˜zz \ (ver también. * $ /, "", $ 1)
sub (/; /, "", $ 1)
# imprimir como entrada normal si ($ 1 ˜ /: /)
imprimir $ 1 ":" $ 2
más
imprimir $ 1 "::" $ 2
siguiente
}
} # Fin de 5

# 6 Procesar entradas sin número de página (Ver entradas)


(NF == 1 || $ 2 == "" || $ 1 ˜ / \ ([sS] ee /) {
# si una entrada "Ver"
if ($ 1 ˜ / \ ([sS] ee /) {
si ($ 1 ˜ /: /)
imprimir $ 1 ":"
más
imprimir $ 1 ":"
siguiente
}
sino {# si no es una entrada Ver, generar error
impresora ("Sin número de página")
siguiente
}
} # Fin de 6
298 Capítulo 12: Aplicaciones con todas las
funciones

# 7 Si el colon se usa como delimitador


$ 1˜ /: / {
# entrada de salida: impresión de página $ 1 ":" $ 2 siguiente

} # Fin de 7

# 8 Entradas coincidentes con solo claves primarias.


{
imprimir $ 1 "::" $ 2
} # Fin de 8

# funciones de apoyo
#
# impresorar - mensaje de error de impresión y registro actual
# Arg: mensaje a mostrar

función de impresora (mensaje) {


# mensaje de impresión, número de registro y registro printf ("ERROR:% s (% d)% s \ n",
mensaje, NR, $ 0)> "/ dev / tty"
}

Este script consta de una serie de reglas de coincidencia de patrones para reconocer
diferentes tipos de entrada. Tenga en cuenta que una entrada puede coincidir con más de
una regla a menos que la acción asociada con una regla llame a la siguiente instrucción.

Cuando describamos este script, nos referiremos a las reglas por número. La regla 1 gira
las entradas que contienen una tilde y produce dos registros de salida. La función split ()
crea una matriz llamada subcampo que contiene las dos partes de la entrada de com-
pound. Las dos partes se imprimen en su orden original y luego se intercambian para
crear un segundo registro de salida en el que la clave secundaria se convierte en una clave
principal.

Debido a que estamos usando la tilde como un carácter especial, debemos proporcionar
alguna forma de ingresar realmente una tilde. Hemos implementado la convención de que
dos tildes consecutivas se traducen en una sola tilde. La regla 2 trata ese caso, pero
observe que el patrón para la regla 1 se asegura de que la primera tilde que coincida no
**
sea seguida por otra tilde.

El orden de las reglas 1 y 2 en el script es significativo. No podemos reemplazar “˜˜” con


“˜” hasta después del procedimiento para rotar la entrada.

* En la primera edición, Dale escribió: “Para obtener crédito adicional, envíeme un correo electrónico si puede
descubrir por qué la expresión regular comentada justo antes de la regla 1 no funciona. Usé la expresión compuesta
como último recurso ". Me da vergüenza admitir que esto también me dejó perplejo. Cuando Henry Spencer encendió
la luz, fue cegador: “La razón por la cual la expresión regular comentada no funciona es que no hace lo que
pensó el autor. Busca tilde seguido de un carácter que no sea tilde. . . ¡pero la segunda tilde de una
combinación ˜˜ generalmente es seguida por una no tilde! Usar / [ˆ˜] ˜ [ˆ˜] / probablemente funcionaría ”.
Conecté esta expresión regular al programa, y funcionó bien. [ARKANSAS]
Generando un índice enmarañado 299

La regla 3 hace un trabajo similar al de la regla 2; permite que "::" se use para generar un
":" literal en el índice. Sin embargo, dado que usamos los dos puntos como delimitador
de entrada durante toda la entrada al programa, no podemos permitir que aparezca en una
entrada como salida final hasta el final. Por lo tanto, reemplazamos la secuencia "::" con
el valor ASCII del colon en octal. (El programa format.idx revertirá el reemplazo).

Comenzando con la regla 4, intentamos reconocer varias formas de codificar entradas, lo


que brinda al usuario más flexibilidad. Sin embargo, para facilitar la escritura de los
programas restantes, debemos reducir esta variedad a algunas formas básicas.

En la sintaxis "básica", las claves primaria y secundaria están separadas por dos puntos.
Las teclas secundaria y terciaria están separadas por un punto y coma. No obstante, el
programa también reconoce un segundo colon, en lugar de un punto y coma, como
delimitador entre las teclas secundaria y terciaria. También reconoce que si no se
especifican dos puntos como delimitador, entonces se puede usar una coma como
delimitador entre las teclas primaria y secundaria. (En parte, esto se hizo para ser
compatible con un programa anterior que usaba la coma como delimitador). La función
sub () busca la primera coma en la línea y la cambia a dos puntos. Esta regla también
trata de estandarizar la sintaxis de las entradas "ver" y "ver también". Para las entradas
que están delimitadas por dos puntos, la regla 4 elimina espacios después de los dos
puntos. Todo el trabajo se realiza utilizando la función sub ().

La regla 5 trata de las entradas "ver también". Anteponemos la cadena arbitraria "˜zz" a
las entradas "ver también" para que se ordenen al final de la lista de claves secundarias.
El script pa genums.idx, más adelante en la tubería, eliminará "˜zz" después de que se
hayan ordenado las entradas.

La regla 6 coincide con las entradas que no especifican un número de página. La única
entrada válida sin un número de página contiene una referencia "ver". Esta regla genera
entradas "ver" con ":" al final para indicar un tercer campo vacío. Todas las demás
entradas generan un error o mensaje a través de la función pr interr (). Esta función
notifica al usuario que una entrada particular no tiene un número de página y no se
incluirá en la salida. Este es un método para estandarizar la entrada: mostrar lo que no
puede interpretar correctamente. Sin embargo, es fundamental notificar al usuario para
que pueda corregir la entrada.

La regla 7 genera entradas que contienen el delimitador de dos puntos. Su acción se usa a
continuación para evitar alcanzar la regla 8.
Finalmente, la regla 8 coincide con las entradas que contienen solo una clave primaria.
En otras palabras, no hay delimitador. Generamos "::" para indicar un segundo campo
vacío.
300 Capítulo 12: Aplicaciones con todas las
funciones

Ella es una parte del contenido de nuestro archivo de prueba. Lo usaremos para generar
ejemplos en esta sección.
$ prueba de gato
XView: programas; inicialización 45
XV_INIT_ARGS˜macro 46
Xv_object˜type49
Xv_singlecolor˜type 80
gráficos: (ver también imagen del servidor)
gráficos, modelo XView83
Sistema X Window: eventos 84
gráficos, CANVAS_X_PAINT_WINDOW 86
Sistema X Window, ID de X Window para el kit de herramientas
de paint window 87 (Ver Sistema X Window). gráficos: (ver
también imagen del servidor)

Xlib, repintando lienzo 88


Archivo Xlib.h˜header 89

Cuando ejecutamos este archivo a través de input.idx, produce:


$ prueba input.idx
XView: programas; inicialización: 45
XV_INIT_ARGS macro :: 46
macro: XV_INIT_ARGS: 46
Tipo de objeto Xv :: 49
tipo: Xv_object: 49
Xv_singlecolor type :: 80
tipo: Xv_singlecolor: 80
gráficos: ˜zz (ver también imagen del servidor):
gráficos: modelo XView: 83
Sistema X Window: eventos: 84
gráficos: CANVAS_X_PAINT_WINDOW: 86
Sistema X Window: X ID de ventana para ventana de pintura:
87 gráficos: ˜zz (ver también imagen del servidor): Xlib:
repintado de lienzo: 88 Archivo de encabezado Xlib.h :: 89

archivo de encabezado: Xlib.h: 89

Cada entrada ahora consta de tres campos separados por dos puntos. En la salida de
muestra, puede encontrar ejemplos de entradas con solo una clave primaria, aquellas con
claves primarias y secundarias, y aquellas con claves primarias, secundarias y terciarias.
También puede encontrar ejemplos de entradas rotadas, entradas duplicadas y entradas
"ver también".

La única diferencia en la salida para las entradas multivolumen es que cada entrada
tendría un cuarto campo que contiene el identificador de volumen.

Sor ting the Entries


Ahora la salida producida por input.idx está lista para ser ordenada. La forma más fácil
de ordenar las entradas es usar el programa de programación estándar de UNIX en lugar
de escribir un script personalizado. Además de ordenar las entradas, queremos eliminar
cualquier duplicado y para esta tarea usamos el programa uniq.
Generando un índice enmarañado 301

Ella es la línea de comando que usamos:


ordenar -bdf -t: +0 -1 +1 -2 +3 -4 + 2n -3n | uniq

Como puede ver, utilizamos varias opciones con el comando sor t. La primera opción, -b,
especifica que los espacios iniciales se ignoren. La opción -d especifica un tipo de
diccionario en el que se ignoran los símbolos y caracteres especiales. -f especifica que las
letras mayúsculas y minúsculas se deben plegar juntas; en otras palabras, deben ser
tratados como el mismo personaje para propósitos de este tipo. El siguiente argumento es
quizás el más importante: -t: le dice al programa que use dos puntos como delimitador de
campo para ordenar las claves. Las opciones "+" que siguen especifican el número de
campos que se omiten desde el principio de la línea. Por lo tanto, para especificar el
primer campo como la clave de clasificación principal, usamos "+0". Del mismo modo,
las opciones "-" especifican el final de una clave de clasificación. La especificación "-1"
indica que la clave de ordenación primaria termina en el primer campo o al comienzo del
segundo campo. El segundo campo de clasificación es la clave secundaria. El cuarto
campo ("+3") si existe, contiene el número de volumen. La última clave para ordenar es
el número de página; esto requiere un orden numérico (si no dijimos a sor t que esta clave
consiste en números, entonces el número 1 iría seguido de 10, en lugar de 2). Observe
que ordenamos los números de página después de ordenar los números de volumen. Por
lo tanto, todos los números de página para el Volumen I se ordenan en orden antes de los
números de página para el Volumen II. Finalmente, canalizamos la salida a uniq para
eliminar entradas idénticas. Al procesar la salida de input.idx, el comando sor t produce:
entonces el número 1 sería seguido por 10, en lugar de 2). Observe que ordenamos los
números de página después de ordenar los números de volumen. Por lo tanto, todos los
números de página para el Volumen I se ordenan en orden antes de los números de
página para el Volumen II. Finalmente, canalizamos la salida a uniq para eliminar
entradas idénticas. Al procesar la salida de input.idx, el comando sor t produce: entonces
el número 1 sería seguido por 10, en lugar de 2). Observe que ordenamos los números de
página después de ordenar los números de volumen. Por lo tanto, todos los números de
página para el Volumen I se ordenan en orden antes de los números de página para el
Volumen II. Finalmente, canalizamos la salida a uniq para eliminar entradas idénticas. Al
procesar la salida de input.idx, el comando sor t produce:

gráficos: CANVAS_X_PAINT_WINDOW: 86
gráficos: modelo XView: 83
gráficos: ˜zz (ver también imagen del servidor):
archivo de encabezado: Xlib.h: 89
macro: XV_INIT_ARGS: 46
kit de herramientas: (Ver sistema X Window) .:
tipo: Xv_object: 49
tipo: Xv_singlecolor: 80
Sistema X Window: eventos: 84
Sistema X Window: X ID de ventana para ventana de pintura: 87
Xlib: repintar lienzo: 88
Archivo de encabezado Xlib.h :: 89
XView: programas; inicialización: 45
XV_INIT_ARGS macro :: 46
Tipo de objeto Xv :: 49
Xv_singlecolor type :: 80

Número de página de manejo s


El programa pa genums.idx busca entradas que difieren solo en el número de página y
crea una lista de números de página para una sola entrada. La entrada a este programa son
cuatro campos separados por dos puntos:
PRIMARIO: SECUNDARIO: PÁGINA: VOLUMEN
302 Capítulo 12: Aplicaciones con todas las
funciones

El cuarto es opcional. Por ahora, consideramos solo el índice de un solo libro, en el que
no hay números de volumen. Recuerde que las entradas ahora están ordenadas.

El corazón de este programa compara la entrada actual con la anterior y determina qué
salida. Los condicionales que implementan la comparación se pueden extraer y expresar
en pseudocódigo, de la siguiente manera:
PRIMARIO = $ 1
SECUNDARIO = $ 2
PÁGINA = $ 3
if (PRIMARIO == prevPRIMARIO)
if (SECUNDARIO == prevSECONDARY)
Imprimir página
más
imprimir PRIMARIO: SECUNDARIO: PÁGINA
más
imprimir PRIMARIO: SECUNDARIO: PÁGINA
prevPRIMARY = PRIMARY
prevSECONDARY = SECUNDARY

Veamos cómo este código maneja una serie de entradas, comenzando con:
XView :: 18

La clave primaria no coincide con la clave primaria anterior; la línea se emite como es:
XView :: 18

La siguiente entrada es:


XView: acerca de: 3

Cuando comparamos la clave principal de esta entrada con la anterior, son las mismas.
Cuando comparamos claves secundarias, difieren; sacamos el registro como está:
XView: acerca de: 3

La siguiente entrada es:


XView: acerca de: 7

Debido a que las claves primaria y secundaria coinciden con las claves de la entrada
anterior, simplemente mostramos el número de página. (La función pr intf se usa en lugar
de pr int para que no haya una nueva línea automática). Este número de página se agrega
a la entrada anterior para que se vea así:
XView: aproximadamente: 3,7

La siguiente entrada también coincide con ambas teclas:


XView: aproximadamente: 10
Generando un índice enmarañado 303

Nuevamente, solo se muestra el número de página para que la entrada ahora se vea así:
XView: aproximadamente: 3,7,10

De esta manera, tres entradas que difieren solo en el número de página se combinan en
una sola entrada.

El script completo agrega una prueba adicional para ver si el identificador de volumen
coincide.
Ella es el script completo pa genums.idx:
#! / work / bin / nawk -f
# ------------------------------------------------
# pagenums.idx - recolecta páginas para entradas comunes
# Autor: Dale Dougherty
# Versión 1.1 10/07/90
##
# la entrada debe ser PRIMARIA: SECUNDARIA: PÁGINA: VOLUMEN
# ------------------------------------------------

COMIENZA {FS = ":"; OFS = ""}

# rutina principal: se aplica a todas las líneas de entrada


{
# asignar campos a variables
PRIMARIO = $ 1
SECUNDARIO = $ 2
PÁGINA = $ 3
VOLUMEN = $ 4

# compruebe si hay un ver también y recójalo en una matriz si (SECUNDARIO ˜ / \ ([Ss] ee + [Aa]
lso /) {
# crear copia tmp y eliminar "˜zz" de la copia tmpSecondary = SECUNDARIO
sub (/ ˜zz \ ([Ss] ee + [Aa] lso * /, "", tmpSecondary) sub (/ \)
* /, "", tmpSecondary)
# eliminar clave secundaria junto con "˜zz"
sub (/ˆ.*˜zz \ ([Ss] ee + [Aa] lso * /, "", SECUNDARIO)
sub (/ \) * /, "", SECUNDARIO)
# asignar al siguiente elemento de seeAlsoList seeAlsoList [++ eachSeeAlso] = SECONDARY
";" prevPrimary = PRIMARY
# asignar copia a la clave secundaria anterior prevSecondary = tmpSecondary next

} # prueba final para ver también

# Condicionales para comparar claves del registro actual con las anteriores
# grabar. Si las claves primaria y secundaria son iguales, solo
# Se imprime el número de página.

# prueba para ver si cada tecla PRIMARIA coincide con la tecla anterior
if (PRIMARIO == prevPrimary) {
304 Capítulo 12: Aplicaciones con todas las
funciones

# prueba para ver si cada tecla SECUNDARIA coincide con la tecla anterior if (SECONDARY
== prevSecondary)
# prueba para ver si VOLUME coincide;
# imprimir solo VOLUMEN: PÁGINA
if (VOLUME == prevVolume)
printf (",% s", PAGE)
más {
printf (";")
volpage (VOLUMEN, PÁGINA)
}
más{
# if array of See Alsos, imprímalos ahora if (eachSeeAlso) outputSeeAlso (2)
# imprimir PRIMARIO: SECUNDARIO: VOLUMEN: PÁGINA
printf ("\ n% s:% s:", PRIMARIO, SECUNDARIO)
volpage (VOLUMEN, PÁGINA)
}
} # fin de la prueba para PRIMARY == prev
else {# PRIMARY! = prev
# si tenemos una matriz de See Alsos, imprímalos ahora if (eachSeeAlso)
outputSeeAlso (1)
si (NR! = 1)
printf ("\ n") if
(NF == 1) {
printf ("% s:", $ 0)
}
más {
printf ("% s:% s:", PRIMARY, SECONDARY)
volpage (VOLUME, PAGE)
}
}
prevPrimary = PRIMARY
prevSecondary = SECUNDARIO
prevVolume = VOLUME

} # fin de la rutina principal

# al final, imprima nueva línea END {


# en caso de que la última entrada tenga "ver también"
if (eachSeeAlso) outputSeeAlso (1)
printf ("\ n")
}

# función outputSeeAlso: enumera elementos de la función seeAlsoList outputSeeAlso (LEVEL) {


# NIVEL: indica qué tecla debemos generar si (NIVEL == 1)
printf ("\ n% s: (Ver también", anteriorPrimario)
más {
sub (/;.*$/, "", prevSecondary)
printf ("\ n% s:% s; (Vea también", prevPrimary, prevSecondary)
}
Generando un índice enmarañado 305

sub (/; $ /, ".):", seeAlsoList [eachSeeAlso])


para (i = 1; i <= eachSeeAlso; ++ i)
printf ("% s", veaAlsoList [i])
eachSeeAlso = 0
}

# función volpage: determina si se imprime o no la información de volumen


# dos args: volumen y página

función volpage (v, p)


{
# si VOLUME está vacío, imprima PÁGINA solo si (v == "")
printf ("% s", p)
más
# de lo contrario, imprima VOLUMEˆPAGE
printf ("% sˆ% s", v, p)
}

Recuerde, en primer lugar, que la entrada al programa está ordenada por sus claves. Los
números de página también están en orden, de modo que una entrada para “gráficos” en
la página 7 aparece en la entrada anterior a la de la página 10. De manera similar, las
entradas para el Volumen I aparecen en la entrada antes del Volumen II. Por lo tanto, este
programa no necesita ordenar; simplemente compara las teclas y, si son las mismas,
agrega el número de página a una lista. De esta forma, las entradas se reducen.

Este script también maneja las entradas "ver también". Como los registros ahora están
ordenados, podemos eliminar la secuencia de clasificación especial "˜zz". También
manejamos el caso donde podríamos encontrar entradas consecutivas de "ver también".
No queremos generar:
Toolkit (ver también Xt) (Ver también XView) (Ver también Motivo).

En cambio, nos gustaría combinarlos en una lista para que aparezcan como:
Kit de herramientas (ver también Xt; XView; Motif)

Para hacer eso, creamos una matriz llamada seeAlsoList. Desde SECUNDARIO,
eliminamos los paréntesis, la clave secundaria si existe y el "ver también" y luego lo
asignamos a un elemento de seeAlsoList. Hacemos una copia de SECUNDARY con la
clave secundaria y la asignamos a prevSecondar y para hacer comparaciones con la
siguiente entrada.

Se llama a la función outputSeeAlso () para leer todos los elementos de la matriz e


imprimirlos. La función volpa ge () también es simple y determina si necesitamos o no
generar un número de volumen. Ambas funciones se invocan desde más de un lugar en el
código, por lo que la razón principal para definirlas como funciones es reducir la
duplicación.

Su e es un ejemplo de lo que produce para un índice de un solo libro:


Sistema X Window: Xlib: 6
Estructura XFontStruct :: 317
Xlib :: 6
306 Capítulo 12: Aplicaciones con todas las
funciones

Xlib: repintar lienzo: 88


Archivo de encabezado Xlib.h :: 89,294
Tipo Xv_Font :: 310
XView :: 18
XView: aproximadamente: 3,7,10
XView: como sistema orientado a objetos: 17

Ella es un ejemplo de lo que produce para un índice maestro:


nombres reservados: tabla de: Iˆ43
Xt: ejemplo de interfaz de programación: Iˆ44,65
Xt: objetos; lista de: Iˆ43,58; IIˆ40
Xt: paquetes: Iˆ43,61; IIˆ42
Xt: programas; inicialización: Iˆ45
Xt: nombres reservados: Iˆ43,58
Xt: prefijos reservados: Iˆ43,58
Xt: tipos: Iˆ43,54,61

El "ˆ" se utiliza como delimitador temporal entre el número de volumen y la lista de


números de página.

Fusionar entradas con las mismas claves


El programa pa genums.idx redujo las entradas que eran iguales excepto por el número de
página. Ahora procesaremos las entradas que comparten la misma clave primaria.
También queremos buscar números de página consecutivos y combinarlos en rangos.

El combine.idx es bastante similar al script pa genums.idx, haciendo otra pasada a través


del índice, comparando entradas con la misma clave primaria. El siguiente pseudocódigo
resume esta comparación. (Para facilitar esta discusión, omitiremos las claves terciarias y
mostraremos cómo comparar las claves primarias y secundarias). Después de que pa
genums.idx procese las entradas, no existen dos entradas que compartan las mismas
claves primarias y secundarias. Por lo tanto, no tenemos que comparar claves
secundarias.

PRIMARIO = $ 1
SECUNDARIO = $ 2
PÁGINA = $ 3
if (PRIMARIO == prevPRIMARIO)
imprimir: SECUNDARIO:
más
imprimir PRIMARIO: SECUNDARIO
prevPRIMARY = PRIMARY
prevSECONDARY = SECUNDARY

Si las claves primarias coinciden, solo mostramos la clave secundaria. Por ejemplo, si hay
tres entradas:
XView: 18
XView: aproximadamente: 3, 7, 10
XView: como sistema orientado a objetos: 17
Generando un índice enmarañado 307

saldrán como:
XView: 18
: sobre: 3, 7, 10
: como sistema orientado a objetos: 17

Dejamos caer la clave primaria cuando es la misma. El código real es un poco más difícil
porque hay claves terciarias. Tenemos que probar las claves primarias y secundarias para
ver si son únicas o iguales, pero no tenemos que probar las claves terciarias. (Solo
necesitamos saber que están allí).

Sin duda notó que el pseudocódigo anterior no genera números de página. La segunda
función de este script es examinar los números de página y combinar una lista de
números consecutivos. Los números de página son una lista separada por comas que se
puede cargar en una matriz, utilizando la función split ().

Para ver si los números son consecutivos, recorremos la matriz comparando cada
elemento con 1 + el elemento anterior.
cada página [j-1] +1 == cada página [j]

En otras palabras, si agregar 1 al elemento anterior produce el elemento actual, entonces


son consecutivos. El elemento anterior se convierte en el primer número de página del
rango y el elemento actual se convierte en la última página del rango. Esto se realiza
dentro de un ciclo while hasta que el condicional no sea verdadero y los números de
página no sean consecutivos. Luego sacamos el primer número de página y el último
número de página separados por un guión:

23-25

El código real parece más complicado que esto porque se llama desde una función que
debe reconocer pares de volumen y número de página. Primero tiene que dividir el
volumen de la lista de números de página y luego puede llamar a la función
(rangeOfPages ()) para procesar la lista de números.
Su e es la lista completa de combine.idx:
#! / work / bin / nawk -f
# ------------------------------------------------
# combine.idx - fusionar claves con la misma clave PRIMARIA
# y combinar números de página consecutivos
# Autor: Dale Dougherty
# Versión 1.1 10/07/90
##
# la entrada debe ser PRIMARIA: SECUNDARIA: PAGELISTA
# ------------------------------------------------

COMIENZA {FS = ":"; OFS = ""}


308 Capítulo 12: Aplicaciones con todas las
funciones

# rutina principal: se aplica a todas las líneas de entrada


# Compara las claves y combina los duplicados.
{
# asignar primer campo

# dividir el segundo campo, obteniendo claves SEC y TERT. sizeOfArray = split ($ 2, array, ";")
SECUNDARY = array [1]

TERCIARIO = matriz [2]


# probar que existe la clave terciaria
if (sizeOfArray> 1) {
# la clave terciaria existe isTertiary = 1
# dos casos donde ";" podría aparecer
# verifique la tecla SEC para ver la lista de "ver también"
if (SECUNDARIO ˜ / \ ([sS] ee también /) {
SECUNDARIO = $ 2
isTertiary = 0
}
# marque la tecla TERT para "ver también"
if (TERCIARIO ˜ / \ ([sS] ee también /) {
TERCIARIO = substr ($ 2, (índice ($ 2, ";") + 1))
}
}
sino # clave terciaria no existe
isTertiary = 0
# asignar tercer campo PAGELIST = $ 3

# Condicional para comparar la clave principal de esta entrada con esa


# de entrada anterior. Luego compare las claves secundarias. Esta
# determina qué claves no duplicadas generar.
if (PRIMARIO == prevPrimary) {
if (isTertiary && SECONDARY == prevSecondary)
printf ("; \ n ::% s", TERCIARIO)
más
si (es terciario)
printf ("\ n:% s;% s", SECUNDARIO, TERCIARIO)
más
printf ("\ n:% s", SECUNDARIO)
}
más {
si (NR! = 1)
printf ("\ n")
if ($ 2! = "")
printf ("% s:% s", PRIMARIO, $ 2)
más
printf ("% s", PRIMARIO)

prevPrimary = PRIMARY
}

prevSecondary = SECUNDARIO
} # fin del procedimiento principal
Generando un índice enmarañado 309

# rutina para las entradas "Ver" (solo clave primaria) NF == 1 {printf ("\ n")}

# rutina para todas las demás entradas


# Maneja la salida del número de página.

NF> 1 {
si (PAGELISTA)
# llama a la función numrange () para buscar
# Números de página consecutivos.
printf (":% s", rango de números (PAGELIST))
más
if (! isTertiary || (TERCIARIO && SECUNDARIO)) printf (":")

} # final de NF> 1

# El procedimiento END genera una nueva línea END {printf ("\ n")}

# Funciones de apoyo

# rango de números - leer la lista de VolumenˆPágina números, separar Volumen


# de la página para cada volumen y rango de llamadas de páginas
# para combinar números de página consecutivos en la lista.
# PÁGINA = volúmenes separados por punto y coma; volumen y página
# separado por.

función numrange (PAGE, { listOfPages, sizeOfArray)

# Divide la lista por volumen.


sizeOfArray = split (PAGE, howManyVolumes, ";")
# Verifique si hay más de 1 volumen. if (sizeOfArray> 1) {

# si hay más de 1 volumen, repita la lista para (i = 1; i <= sizeOfArray; ++ i) {


# para cada elemento VolumeˆPage, separe Volume
# y llame a la función rangeOfPages en la página para
# separar los números de página y comparar para encontrar
# numeros consecutivos.
if (split (howManyVolumes [i], volPage, "ˆ") == 2)
listOfPages = volPage [1] "ˆ" rangeOfPages
(volPage [2])

# recopilar salida en listOfPages if (i == 1)


resultado = listOfPages
más
resultado = resultado ";" listOfPages
} # final para bucle
}
más {# no más de 1 volumen

# verifique el índice de volumen único con número de volumen


# Si es así, separe el número de volumen.
310 Capítulo 12: Aplicaciones con todas las
funciones

# Ambos llaman a rangeOfPages en la lista de números de página. if (split (PAGE, volPage, "ˆ")
== 2)
# if VolumeˆPage, separe el volumen y luego llame a rangeOfPages listOfPages =
volPage [1] "ˆ" rangeOfPages (volPage [2])
más # No hay número de volumen involucrado
listOfPages = rangeOfPages (volPage [1])
resultado = listOfPages
} # fin de otra cosa

devolver resultado # VolumenˆLista de páginas

} # Fin de la función de rango numérico

# rangeOfPages: leer la lista de números de página separados por comas,


# cargarlos en una matriz y comparar cada uno
# al siguiente, buscando números consecutivos.
# PAGENUMBERS = lista de números de página separados por comas

función rangeOfPages (PAGENUMBERS, pagesAll, sizeOfArray, pages,


listOfPages, d, p, j) {
# espacio de primer plano en rangos generados por troff gsub (/ - /, ", -", PAGENUMBERS)

# dividir la lista en cada conjunto de páginas.


sizeOfArray = split (PAGENUMBERS, cada página, ",")
# if más de 1 número de página if (sizeOfArray> 1) {
# para cada número de página, compárelo con el número anterior + 1 p = 0 # el indicador indica
la asignación a las páginas
# para el bucle comienza a las 2
para (j = 2; j-1 <= sizeOfArray; ++ j) {
# comience guardando la primera página en secuencia (primera página)
# y bucle hasta que encontremos la última página (última página) firstpage =
eachpage [j-1]
d = 0 # indicador indica números consecutivos encontrados
# bucle mientras los números de página son consecutivos
while ((cada página [j-1] +1) == cada página [j] ||
cada página [j] ˜ / - /) {
# eliminar "-" del rango generado por troff if (eachpage [j] ˜ / - /) {
sub (/ - /, "", cada página [j])
}
lastpage = cada página [j]
# contadores de incremento
++ d
++ j
} # final del ciclo while
# use valores de primera página y última página para hacer rango. si (d> = 1) {

# hay un rango
páginas = primera página "-" última página
}
sino # no hay rango; solo leer páginas de
primera página = primera página
Generando un índice enmarañado 311

# asignar rango a páginasTodo si (p == 0) {


pagesAll = páginas p
=1
}
más {
pagesAll = pagesAll "," páginas
}
} # final del ciclo for

# asigne pagesAll a listOfPages listOfPages = pagesAll

} # fin de sizeOfArray> 1

sino # solo una página


listOfPages = PAGENUMBERS

# agregar espacio después de la coma gsub (/, /, ",", listOfPages)


# regresar lista cambiada de números de página return listOfPages
} # Fin de la función rangeOfPages

Este script consta de procedimientos mínimos de INICIO y FIN. La rutina principal hace
el trabajo de comparar claves primarias y secundarias. La primera parte de esta rutina
asigna los campos a las variables. El segundo campo contiene las claves secundarias y
terciarias y usamos split () para separarlas. Luego probamos que hay una clave terciaria y
establecemos el indicador isTer tiary en 1 o 0.

La siguiente parte del procedimiento principal contiene las expresiones condicionales que
buscan claves idénticas. Como dijimos en nuestra discusión sobre el pseudocódigo para
esta parte del programa, pa genums.idx ya ha eliminado las entradas con claves
completamente idénticas.

Los condicionales de este procedimiento determinan qué claves generar en función de si


cada una es única o no. Si la clave primaria es única, se genera junto con el resto de la
entrada. Si la clave primaria coincide con la clave anterior, comparamos las claves
secundarias. Si la clave secundaria es única, entonces se emite, junto con el resto de la
entrada. Si la clave primaria coincide con la clave primaria anterior, y la clave secundaria
coincide con la clave secundaria anterior, entonces la clave terciaria debe ser única.
Luego solo sacamos la clave terciaria, dejando en blanco las claves primaria y
secundaria.

Las diferencias para ms se muestran a continuación:


primario
primario secundario
:secundario
312 Capítulo 12: Aplicaciones con todas las
funciones

: secundario: terciario
::terciario
primaria: secundaria: terciaria

El procedimiento principal es seguido por dos rutinas adicionales. El primero de ellos se


ejecuta solo cuando NF es igual a uno. Se trata del primero de los formularios en la lista
anterior. Es decir, no hay un número de página, por lo que debemos generar una nueva
línea para finalizar la entrada.

El segundo procedimiento trata con todas las entradas que tienen números de página. Este
es el procedimiento donde llamamos a una función para separar la lista de números de
página y buscar páginas consecutivas. Llama a la función numrange (), cuyo objetivo
principal es tratar con un índice multivolumen donde podría verse una lista de números
de página:
Iˆ35,55; IIˆ200

Esta función llama a split () usando un delimitador de punto y coma para separar cada
volumen. Luego llamamos a split () usando un delimitador "ˆ" para separar el número de
volumen de la lista de números de página. Una vez que tenemos la lista de páginas,
llamamos a una segunda función rangeOfPages () para buscar números consecutivos. En
un índice de un solo libro, como el ejemplo que se muestra en este capítulo, la función
numrange () realmente no hace más que llamar a rangeOfPages (). Discutimos la carne de
la función rangeOfPages () anteriormente. Se crea la matriz eachpa ge y se usa un bucle
while para recorrer la matriz y comparar un elemento con el anterior. Esta función
devuelve la lista de páginas.
La salida de muestra de este programa sigue:
Xlib: 6
: repintar lienzo: 88
Archivo de encabezado Xlib.h: 89, 294
Xv_ Tipo de fuente: 310
XView: 18
: sobre: 3, 7, 10
: como sistema orientado a objetos: 17
: compilando programas: 41
: el concepto de ventanas difiere de X: 25
: tipos de datos; tabla de: 20
: ejemplo de interfaz de programación: 44
: cuadros y subtramas: 26
: funciones genéricas: 21
: Objeto genérico: 18, 24
: bibliotecas: 42
: notificación: 10, 35
: objetos: 23-24;
: tabla de: 20;
: lista de: 43
: paquetes: 18, 43: modelo del
programador: 17-23: interfaz de
programación: 41
Generando un índice enmarañado 313

: programas; inicialización: 45
: nombres reservados: 43
: prefijos reservados: 43
: estructura de aplicaciones: 41
: subventanas: 28
: tipos: 43
: objetos de ventana: 25

En particular, observe la entrada para "objetos" en "XView". Este es un ejemplo de una


clave secundaria con múltiples claves terciarias. También es un ejemplo de una entrada
con un rango de páginas consecutivas.

Formateando el índice
Las secuencias de comandos anteriores han realizado casi todo el procesamiento, dejando
la lista de entradas en buen estado. El script format.idx, probablemente el más fácil de los
scripts, lee la lista de entradas y genera un informe en dos diferen- tes para esteras, una
para visualizar en una pantalla de terminal y otra para enviar a trof f para imprimir en un
láser. impresora. Quizás la única dificultad es que sacamos las entradas agrupadas por
cada letra del alfabeto.

Un argumento de línea de comandos establece la variable FMT que determina cuál de los
dos formatos de salida se utilizará.
Su e es la lista completa de format.idx:
#! / work / bin / nawk -f
# ------------------------------------------------
# format.idx - preparar índice formateado
# Autor: Dale Dougherty
# Versión 1.1 10/07/90
##
# la entrada debe ser PRIMARIA: SECUNDARIA: PÁGINA: VOLUMEN
# Args: formato FMT = 0 (predeterminado) para la pantalla
# FMT = 1 salida con macros troff
# MACDIR = nombre de ruta del archivo de macro de troff de índice
# ------------------------------------------------
EMPEZAR { FS = ":"
upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
lower = "abcdefghijklmnopqrstuvwxyz"
}

# Salida de macros iniciales si troff FMT NR == 1 && FMT == 1 {


si (MACDIR)
printf (".so% s / indexmacs \ n", MACDIR)
más
printf (".so indexmacs \ n")
printf (".Se \" \ "\" Index \ "\ n")
printf (".XC \ n")
} # final de NR == 1
314 Capítulo 12: Aplicaciones con todas las
funciones

# rutina principal: se aplica a todas las líneas


# determinar qué campos generar
{
# convertir colon octal a colon "literal"
# haga sub para cada campo, no $ 0, para que los campos no se analicen gsub (/ \\ 72 /, ":", $ 1)

gsub (/ \\ 72 /, ":", $ 2)
gsub (/ \\ 72 /, ":", $ 3)

# asignar campo a variables


PRIMARIO = $ 1
SECUNDARIO = $ 2
TERCIARIO = ""
PÁGINA = $ 3
si (NF == 2) {
SECUNDARIO = ""
PÁGINA = $ 2
}
# Busque campos vacíos para determinar qué generar si (! PRIMARIO) {
si (! SECUNDARIO) {
TERCIARIO = $ 3
PÁGINA = $ 4
si (FMT == 1)
printf (".XF 3 \"% s ", TERCIARIO)
más
printf ("% s", TERCIARIO)
}
más
si (FMT == 1)
printf (".XF 2 \"% s ", SECUNDARIO)
más
printf (" % s ", SECUNDARIO)
}
más {# si existe una entrada principal
# extraer el primer carácter de la entrada primaria firstChar = substr ($ 1, 1, 1)
# ver si está en cadena inferior.
char = index (lower, firstChar)
# char es un índice de letras mayúsculas o minúsculas if (char == 0) {
# si no se encuentra char, vea si es superior char = index (upper, firstChar) if (char ==
0)

char = prevChar
}
# if new char, entonces comience el grupo para la nueva letra del alfabeto if (char! =
prevChar) {
si (FMT == 1)
printf (". XF A \"% s \ "\ n", substr (upper, char, 1))
más
printf ("\ n \ t \ t% s \ n", substr (upper, char, 1))
prevChar = char
}
Generando un índice enmarañado 315

# ahora genera entrada primaria y secundaria si (FMT == 1)


si (SECUNDARIO)
printf (".XF 1 \"% s \ "\"% s ", PRIMARIO, SECUNDARIO)
más
printf (".XF 1 \"% s \ "\" ", PRIMARIO)
más
si (SECUNDARIO)
printf ("% s,% s", PRIMARIO, SECUNDARIO)
más
printf ("% s", PRIMARIO)
}

# si es el número de página, llame a pageChg para reemplazar "ˆ" con ":"


# para listas de páginas de varios volúmenes.
si (PÁGINA) {
if (FMT == 1) {
# agregado para omitir la coma después de la entrada en negrita si (!
SECUNDARIO &&! TERCIARIO)
printf ("% s \" ", pageChg (PÁGINA))
más
printf (",% s \" ", pageChg (PÁGINA))
}
más
printf (",% s", pageChg (PÁGINA))
}
más si (FMT == 1)
printf ("\" ")

printf ("\ n")

} # Fin de la rutina principal

# Función de apoyo

# pageChg - convierte "ˆ" a ":" en la lista de volumenˆpágina


# Arg: lista de páginas - lista de números

función pageChg (lista de páginas) {


gsub (/ \ ˆ /, ":", lista de páginas)
if (FMT == 1) {
gsub (/ [1-9] + \ * /, "\\ fB & \\ P", lista de páginas)
gsub (/ \ * /, "", lista de páginas)
}
volver a la lista de páginas
} # Fin de la función pageChg

El procedimiento BEGIN define el separador de campo y las cadenas superior e inferior.


El siguiente procedimiento es uno que genera el nombre del archivo que contiene las
definiciones de macro de índice trof f. El nombre del directorio macro puede establecerse
desde la línea de comando como segundo argumento.
316 Capítulo 12: Aplicaciones con todas las
funciones

El procedimiento principal comienza convirtiendo los dos puntos "ocultos" en dos puntos
literales. Tenga en cuenta que aplicamos la función gsub () a cada campo en lugar de a la
línea completa porque al hacer esto último se volvería a evaluar la línea y se alteraría el
orden actual de los campos.

Luego asignamos los campos a las variables y luego probamos para ver si el campo está
vacío. Si la clave primaria no está definida, entonces vemos si la clave secundaria está
definida. Si es así, lo sacamos. Si no es así, sacamos una clave terciaria. Si se define la
clave primaria, extraemos su primer carácter y luego vemos si la encontramos en la
cadena inferior.

firstChar = substr ($ 1, 1, 1)

char = index (lower, firstChar)

La variable char mantiene la posición de la letra en la cadena. Si este número es mayor


que o igual a 1, entonces también tenemos un índice en la cadena superior. Comparamos
cada entrada y aunque char y prevChar son iguales, la letra actual del alfabeto no cambia.
Una vez que difieren, primero verificamos la letra en la cadena superior. Si char es una
nueva letra, mostramos una cadena centrada que identifica esa letra del alfabeto.

Luego nos fijamos en la salida de las entradas primaria y secundaria. Finalmente, se


genera la lista de números de página, después de llamar a la función pa geChg () para
reemplazar la "ˆ" en las referencias de páginas de volumen con dos puntos.
La salida de pantalla de muestra producida por format.idx se muestra a continuación:
X
Protocolo X, 6
Sistema X Window, eventos, 84
extensibilidad, 9
comunicaciones intercliente, 9
resumen, 3
protocolo, 6
rol de administrador de ventanas, 9
relación servidor y cliente, 5
jerarquía de software, 6
kits de herramientas, 7
X ID de ventana para ventana de pintura, 87
Xlib, 6
Estructura XFontStruct, 317
Xlib, 6
repintar lienzo, 88
Archivo de encabezado Xlib.h, 89, 294
Xv_Tipo tipo, 310
XView, 18
aproximadamente, 3, 7, 10
como sistema orientado a objetos, 17
compilación de programas, 41
Generando un índice enmarañado 317

el concepto de ventanas difiere de X, 25


tipos de datos; tabla de 20
ejemplo de interfaz de programación, 44
marcos y subtramas, 26
funciones genéricas, 21
Objeto genérico, 18, 24

A continuación se muestra un ejemplo de salida f trof producida por format.idx:


.XF A "X"
.XF 1 "Protocolo X" "6"
.XF 1 "Sistema X Window" "eventos, 84"
.XF 2 "extensibilidad, 9"
.XF 2 "comunicaciones intercliente, 9"
.XF 2 "resumen, 3"
.XF 2 "protocolo, 6"
.XF 2 "función de administrador de ventanas, 9"
.XF 2 "relación servidor y cliente, 5"
.XF 2 "jerarquía de software, 6"
.XF Juegos de herramientas de 2 ", 7"
.XF 2 "X ID de ventana para ventana de pintura, 87"
.XF 2 "Xlib, 6"
.XF 1 "Estructura XFontStruct" "317"
.XF 1 "Xlib" "6"
.XF 2 "lienzo repintado, 88"
.XF 1 "Archivo de encabezado Xlib.h" "89, 294"
.XF 1 "Xv_Font tipo" "310"
.XF 1 "XView" "18"
.XF 2 "aproximadamente, 3, 7, 10"
.XF 2 "como sistema orientado a objetos, 17"

Este resultado debe formatearse con trof f para producir una versión impresa del índice.
El índice de este libro se realizó originalmente utilizando el programa de índice maestro.

El script de shell de índice maestro


El script de shell de índice maestro es el pegamento que mantiene todos estos scripts
juntos y los invoca con las opciones adecuadas basadas en la línea de comando del
usuario. Por ejemplo, el usuario ingresa:
$ índice maestro -s -m volumen1 volumen2

para especificar que se cree un índice maestro a partir de los archivos volumen1 y
volumen2 y que la salida se envíe a la pantalla.
El script de shell de índice maestro se presenta en el Apéndice C con la documentación.
318 Capítulo 12: Aplicaciones con todas las
funciones

Detalles de repuesto del


masterindex Prog ram

Esta sección presenta algunos detalles interesantes del programa de índice maestro que de
otro modo podrían pasar desapercibidos. El propósito de esta sección es extraer algunos
fragmentos interesantes del programa y mostrar cómo resuelven un problema en
particular.

Cómo ocultar un personaje especial


Nuestro primer fragmento es del script input.idx, cuyo trabajo es estandarizar las entradas
de índice antes de que se ordenen. Este programa toma como entrada un registro que
consta de dos campos separados por tabuladores: la entrada de índice y su número de
página. Se usan dos puntos como parte de la sintaxis para indicar las partes de una
entrada de índice.

Debido a que el programa usa dos puntos como un carácter especial, debemos
proporcionar una manera de pasar dos puntos a través del programa. Para hacer esto,
permitimos que el indexador especifique dos dos puntos consecutivos en la entrada. Sin
embargo, no podemos simplemente convertir la secuencia a dos puntos literales porque el
resto de los módulos del programa llamados por el índice maestro leen tres campos
separados por dos puntos. La solución es convertir el colon a su valor octal usando la
función gsub ().
# <de input.idx
# convertir dos puntos literales a valor octal $ 1 ˜ / :: / {

# sustituya el valor octal por "::" gsub (/ :: /, "\\ 72", $ 1)

"\\ 72" representa el valor octal de un colon. (Puede encontrar este valor escaneando una
tabla de equivalentes hexadecimales y octales en el archivo / usr / pub / ascii.) En el
último módulo de programa, usamos gsub () para convertir el valor octal de nuevo a dos
puntos. Aquí está el código de format.idx.

# <de format.idx
# convertir colon octal a colon "literal"
# haga sub para cada campo, no $ 0, para que los campos no se analicen gsub (/ \\ 72 /, ":", $ 1)

gsub (/ \\ 72 /, ":", $ 2) gsub


(/ \\ 72 /, ":", $ 3)

Lo primero que observa es que hacemos esta sustitución para cada uno de los tres campos
por separado, en lugar de tener un comando de sustitución que funciona con $ 0. La razón
de esto es que los campos de entrada están separados por dos puntos. Cuando awk
escanea una línea de entrada, divide la línea en campos. Si cambia el contenido de $ 0 en
cualquier punto de la secuencia de comandos, awk volverá a evaluar el valor de $ 0 y
analizará la línea en los campos nuevamente. Por lo tanto, si tiene tres campos antes de
realizar la sustitución, y el
Detalles de repuesto del programa masterindex 319

la sustitución hace un cambio, agregando dos puntos a $ 0, luego awk reconocerá cuatro
campos. Al hacer la sustitución de cada campo, evitamos que la línea se analice
nuevamente en los campos.

Rotación de dos partes


Arriba hablamos sobre la sintaxis de dos puntos para separar las claves primaria y
secundaria. Con algunos tipos de entradas, también tiene sentido clasificar el elemento
bajo su clave secundaria. Por ejemplo, podríamos tener un grupo de declaraciones de
programa o comandos de usuario, como "comando sed". El indexador puede crear dos
entradas: una para "comando sed" y otra para "comando: sed". Para facilitar la
codificación de este tipo de entrada, implementamos una convención de codificación que
utiliza un carácter tilde (˜) para marcar las dos partes de esta entrada de modo que la
primera y la segunda parte puedan intercambiarse para crear la segunda entrada
**
automáticamente. Por lo tanto, codificando la siguiente entrada de índice
.XX "sed˜command"

pr produce dos entradas:


comando sed 43
comando: sed 43

Su e es el código que rota las entradas.


# <de input.idx
# Haga coincidir las entradas que deben rotarse que contengan una sola tilde $ 1 ˜ / ˜ / && $ 1! ˜ / ˜˜ / {
# dividir el primer campo en una matriz llamada subcampo
n = split ($ 1, subcampo, "˜")
si (n == 2) {
# imprime la entrada sin "˜" y luego gira
printf ("% s% s ::% s \ n", subcampo [1], subcampo [2], $ 2)
printf ("% s:% s:% s \ n", subcampo [2], subcampo [1], $ 2)
}
siguiente
}

La regla de coincidencia de patrones coincide con cualquier entrada que contenga una
tilde pero no dos tildes consecutivas, lo que indica una tilde literal. El procedimiento
utiliza la función split () para dividir el primer campo en dos "subcampos". Esto nos da
dos subcadenas, una antes y otra después de la tilde. Se emite la entrada original y luego
se emite la entrada girada, ambas utilizando la instrucción pr intf.

* La idea de rotar las entradas de índice se derivó del lenguaje de programación AWK. Sin embargo, una entrada
se gira automáticamente donde se encuentra un espacio en blanco; la tilde se usa para evitar una rotación al "llenar" el
espacio. En lugar de que la rotación sea la acción predeterminada, utilizamos una convención de codificación
diferente, donde la tilde indica dónde debe ocurrir la rotación.
320 Capítulo 12: Aplicaciones con todas las
funciones

Debido a que la tilde se usa como un carácter especial, usamos dos tildes consecutivas
para representar una tilde literal en la entrada. El siguiente código aparece en el programa
después del código que intercambia las dos partes de una entrada.
# <de input.idx
# Haga coincidir las entradas que contienen dos tildes $ 1 ˜ / ˜˜ / {

# reemplazar con
gsub (/ ˜˜ /, "˜", $ 1)
}

A diferencia de los dos puntos, que conservan un significado especial en todo el


programa de índice maestro, la tilde no tiene importancia después de este módulo, por lo
que simplemente podemos generar una tilde literal.

Encontrar un reemplazo
El siguiente fragmento también proviene de input.idx. El problema era buscar dos puntos
separados por texto y cambiar el segundo punto a punto y coma. Si la línea de entrada
contiene

clase: clase inicializar: (ver también métodos)

entonces el resultado es:


clase: clase inicializar; (ver también métodos)

El problema es bastante simple de formular: queremos cambiar el segundo colon, no el


primero. Es bastante fácil de resolver en sed debido a la capacidad de seleccionar y
recuperar una parte de lo que coincide en la sección de reemplazo (usando \ (... \) Para
rodear la parte para que coincida y \ 1 para recuperar la primera parte) . Al carecer de la
misma habilidad en awk, tienes que ser más inteligente. Su única solución posible:
# <de input.idx
# reemplace el segundo colon con punto y coma if (sub (/:.*:/, "&;", $ 1))
sub (/:; /, ";", $ 1)

La primera sustitución coincide con el lapso completo entre dos puntos. Hace un
reemplazo con lo que coincide (&) seguido de un punto y coma. Esta sustitución se
produce dentro de una expresión condicional que evalúa el valor devuelto de la función
sub (). Recuerde, esta función devuelve ns 1 si se realiza una sustitución; no devuelve n la
cadena resultante. En otras palabras, si hacemos la primera sustitución, entonces hacemos
la segunda. La segunda sustitución reemplaza ":;" con ";". Debido a que no podemos
hacer el reemplazo directamente, lo hacemos indirectamente al hacer que el segundo
colon parezca distinto.
Detalles de repuesto del programa masterindex 321

Una función para informar errores


El propósito del programa input.idx es permitir variaciones (o menos inconsistencias) en
la codificación de las entradas de índice. Al reducir estas variaciones a una básica para m,
los otros programas se hacen más fáciles de escribir.

El otro lado es que si el programa input.idx no puede aceptar una entrada, debe
informarlo al usuario y descartar la entrada para que no afecte a los otros programas. El
programa input.idx tiene una función utilizada para informar errores llamada pr interr (),
como se muestra a continuación:

función de impresora (mensaje) {


# mensaje de impresión, número de registro y registro printf ("ERROR:% s (% d)% s \ n",
mensaje, NR, $ 0)> "/ dev / tty"
}

Esta función facilita la notificación de errores de manera estándar. Toma como


argumento un mensaje, que generalmente es una cadena que describe el error. Emite este
mensaje junto con el número de registro y el registro en sí. La salida se dirige al terminal
del usuario "/ dev / tty". Esta es una buena práctica, ya que la salida estándar del
programa puede ser, como en este caso, dirigida a una tubería o un archivo. También
podríamos enviar el mensaje de error al error estándar, así:
imprima "ERROR:" mensaje "(" NR ")" $ 0 | "gato 1> y 2"

Esto abre una tubería a cat, con la salida estándar de cat redirigida al error estándar o. Si
está utilizando gawk, mawk o Bell Labs awk, podría decir:
printf ("ERROR:% s (% d)% s \ n", mensaje, NR, $ 0)> "/ dev / stderr"

En el programa, la función pr interr () se llama de la siguiente manera:


impresora ("Sin número de página")

Cuando se produce este error, el usuario ve el siguiente mensaje de error:


ERROR: Sin gestión de geometría de número de página (612): set_values_almost

Manejo Ver también entradas


Un tipo de entrada de índice es un "ver también". Como una referencia de "ver", remite al
lector a otra entrada. Sin embargo, una entrada "ver también" también puede tener un
número de página. En otras palabras, esta entrada contiene información propia pero
remite al lector a otro lugar para obtener información adicional. Aquí hay algunas
entradas de muestra.
procedimiento de error34
procedimiento de error (ver también XtAppSetErrorMsgHandler) 35
procedimiento de error (ver también XtAppErrorMsg)
322 Capítulo 12: Aplicaciones con todas las
funciones

La primera entrada de esta muestra tiene un número de página, mientras que la última no.
Cuando el programa input.idx encuentra una entrada "ver también", verifica si se
proporciona un número de página ($ 2). Si hay uno, genera dos registros, el primero de
los cuales es la entrada sin el número de página y el segundo es una entrada y un número
de página sin la referencia "ver también".
# <input.idx
# si no hay número de página
if ($ 2 == "") {
imprimir $ 0 ":"
siguiente
}
más {
# salida dos entradas:
# print Vea también la entrada sin número de página print $ 1 ":"
# eliminar Ver también
sub (/ * ˜zz \ (ver también. * $ /, "", $ 1)
sub (/; /, "", $ 1)
# imprimir como entrada normal si ($ 1 ˜ /: /)
imprimir $ 1 ":" $ 2
más
imprimir $ 1 "::" $ 2
siguiente
}

El siguiente problema a resolver fue cómo ordenar las entradas en el orden correcto. El
programa de sordos, usando las opciones que le dimos, clasificó las teclas secundarias
para las entradas "ver también" juntas bajo "s". (La opción -d hace que se ignore el
paréntesis). Para cambiar el orden de clasificación, modificamos la clave de clasificación
agregando la secuencia "˜zz" al frente.

# <input.idx
# agregue "˜zz" para ordenar al final
sub (/ \ ([Ss] ee [Aa] lso /, "˜zz (ver también", $ 1)

La tilde no se interpreta por el tipo, pero nos ayuda a identificar la cadena más tarde
cuando la eliminamos. Agregar "˜zz" nos asegura la clasificación al final de la lista de
claves secundarias o terciarias.

El script pa genums.idx elimina la cadena de clasificación de las entradas "ver también".


Sin embargo, como describimos anteriormente, buscamos una serie de entradas "ver
también" para la misma clave y creamos una lista. Por lo tanto, también eliminamos lo
que es igual para todas las entradas y colocamos la referencia en sí misma en una matriz:
# <pagenums.idx
# eliminar clave secundaria junto con "˜zz"
sub (/ˆ.*˜zz \ ([Ss] ee + [Aa] lso * /, "", SECUNDARIO)
sub (/ \) * /, "", SECUNDARIO)
Detalles de repuesto del programa masterindex 323

# asignar al siguiente elemento de seeAlsoList seeAlsoList [++ eachSeeAlso] = SECONDARY ";"

Hay una función que genera la lista de entradas "ver también", separando cada una de
ellas con un punto y coma. Por lo tanto, la salida de la entrada "ver también" de pa
genums.idx se ve así:

procedimiento de error: (vea también XtAppErrorMsg; XtAppSetErrorHandler).

Alterar formas nativas de sor t


En este programa, elegimos no admitir solicitudes trof f de fuente y tamaño de punto en
entradas de índice. Si desea admitir secuencias de escape especiales, una forma de
hacerlo se muestra en El lenguaje de programación AWK. Para cada registro, tome el
primer campo y añádalo al registro como la clave de clasificación. Ahora que hay un
duplicado del primer campo, elimine las secuencias de escape de la clave de
clasificación. Una vez que las entradas están ordenadas, puede eliminar la clave de
clasificación. Este proceso evita que las secuencias de escape molesten al orden.

Otra forma más es hacer algo similar a lo que hicimos para las entradas "ver también".
Debido a que los caracteres especiales se ignoran en el orden, podríamos usar el
programa input.idx pr para convertir una secuencia de cambio de fuente trof f como “\
fB” a “˜˜˜” y “\ fI” a “˜˜ ˜˜ ”o cualquier secuencia de escape conveniente. Esto llevaría la
secuencia a través del programa de sordos sin alterar el orden. (Esta técnica fue utilizada
por Steve Talbott en su guión de indexación original).

El único problema adicional que debe reconocerse en ambos casos es que dos entradas
para el mismo término, una con información de fuente y otra sin ella, se tratarán como
entradas diferentes cuando se compara una con la otra.

También podría gustarte