This shows you the differences between two versions of the page.
— |
notas_sobre_malete [15/05/2009 00:00] (current) |
||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ===== Notas sobre Malete, mayo 2005 ===== | ||
+ | Malete en Linux | ||
+ | |||
+ | Obtenemos el archivo malete.1.0.2.tgz en el sitio http://malete.org. | ||
+ | |||
+ | Extraemos su contenido: | ||
+ | |||
+ | tar xzvf malete.1.0.2.tgz | ||
+ | |||
+ | LGPL.txt | ||
+ | Makefile | ||
+ | Mingw.mk | ||
+ | README | ||
+ | Version | ||
+ | doc/.index | ||
+ | doc/CDS.txt | ||
+ | doc/CharSet.txt | ||
+ | doc/CmdLine.txt | ||
+ | doc/Converting.txt | ||
+ | doc/Diff09.txt | ||
+ | doc/DownLoad.txt | ||
+ | doc/FileFormats.txt | ||
+ | doc/IIF.txt | ||
+ | doc/MOM.txt | ||
+ | doc/MetaData.txt | ||
+ | doc/MultiProcess.txt | ||
+ | doc/OverView.txt | ||
+ | doc/Protocol.txt | ||
+ | doc/Query.txt | ||
+ | doc/RecStruct.txt | ||
+ | doc/Selene.txt | ||
+ | doc/Status.txt | ||
+ | doc/TagUse.txt | ||
+ | doc/Tcl.txt | ||
+ | mlt/Makefile | ||
+ | mlt/cds.c | ||
+ | mlt/cdx.c | ||
+ | mlt/core.h | ||
+ | mlt/db.c | ||
+ | mlt/dbo.c | ||
+ | mlt/hash.c | ||
+ | mlt/iif.c | ||
+ | mlt/malete.c | ||
+ | mlt/pw.c | ||
+ | mlt/pw.h | ||
+ | mlt/qdx.c | ||
+ | mlt/rdx.c | ||
+ | mlt/run | ||
+ | mlt/srv.c | ||
+ | mlt/sys.c | ||
+ | mlt/tool.h | ||
+ | mlt/uti.c | ||
+ | php/Isis.php | ||
+ | php/Isis/Db.php | ||
+ | php/Isis/Http.php | ||
+ | php/Isis/Rec.php | ||
+ | php/Isis/Server.php | ||
+ | php/demo.php | ||
+ | php/killidx.php | ||
+ | php/killqry.php | ||
+ | php/unicode.php | ||
+ | test/cds.m0d | ||
+ | test/cds.mqt | ||
+ | test/cds.mrd | ||
+ | test/unicode.m0d | ||
+ | test/unicode.mqt | ||
+ | test/unicode.mrd | ||
+ | |||
+ | |||
+ | Vemos que se crean cuatro directorios: doc, mlt, php y test. | ||
+ | |||
+ | En 'doc' encontramos la documentación, en forma de archivos de texto con una | ||
+ | sintaxis al estilo wiki, a partir de los cuales se genera la versión HTML que está | ||
+ | en el sitio web (malete.org). | ||
+ | |||
+ | En 'mlt' se encuentra el código fuente de Malete (archivos *.c y *.h), y el Makefile. | ||
+ | Veamos la descripción de cada archivo: | ||
+ | |||
+ | cds.c malete CDS/ISIS conversion | ||
+ | cdx.c charset collation | ||
+ | core.h full interface of the Malete core | ||
+ | db.c implementation of general db access functions ***? | ||
+ | dbo.c the db object | ||
+ | hash.c the hashtable | ||
+ | iif.c malete IIF(ISO2709/Z39.2)/MARC conversion | ||
+ | Makefile -> Ver en particular la sección "demos and tests" | ||
+ | malete.c malete main | ||
+ | pw.c Malete patchwork functions | ||
+ | pw.h interface of the Malete patchwork | ||
+ | qdx.c the btree | ||
+ | rdx.c implementation of general db access functions ***? | ||
+ | run run demo server on test/cds (ver código más abajo) | ||
+ | srv.c malete server | ||
+ | sys.c I/O support for the openisis library | ||
+ | tool.h interface of the Malete tools | ||
+ | uti.c utilities | ||
+ | |||
+ | Este es el contenido del archivo 'run': | ||
+ | |||
+ | #!/bin/sh | ||
+ | # | ||
+ | # run demo server on test/cds | ||
+ | # | ||
+ | # $Id: run,v 1.1 2005/01/24 18:04:00 kripke Exp $ | ||
+ | | ||
+ | if [ ! -f ../test/cds.mqd ]; then | ||
+ | echo "loading cds index ..." | ||
+ | ../bin/malete qload -vi cds -f../test/ -wa <../test/cds.mqt | ||
+ | fi | ||
+ | | ||
+ | if [ x != "x$TEST" ]; then | ||
+ | ../bin/malete server -vd cds -f../test/ -w <<EOF | ||
+ | cds.Q ${*:-PLANT WATER} | ||
+ | EOF | ||
+ | fi | ||
+ | | ||
+ | ../bin/malete server -vd -S cds -f../test/ -w | ||
+ | |||
+ | |||
+ | En 'php' se encuentran un par de demos y una biblioteca PHP que implementa funciones | ||
+ | de lectura, escritura, consulta (query) e indización. Tenemos allí: | ||
+ | |||
+ | demo.php demo for the Isis package | ||
+ | unicode.php unicode demo for the Isis package | ||
+ | |||
+ | Dentro del subdirectorio 'Isis' están los 4 archivos que constituyen la biblioteca: | ||
+ | |||
+ | Db.php class Isis_Db (This class represents a "database") | ||
+ | Http.php class Isis_Http (This class represents the connection to a HTTP server) | ||
+ | Rec.php class Isis_Rec (An ISIS(/IIF/Z39.2/ISO2709)-style record in pure PHP) | ||
+ | Server.php class Isis_Server (This class represents the connection to an Isis server) | ||
+ | |||
+ | |||
+ | Finalmente, tenemos el directorio 'test', donde encontramos dos bases de datos: cds | ||
+ | (la tradicional base distribuida por Unesco), y unicode (una pequeña base | ||
+ | para probar el uso de Unicode en Malete). Cada base de datos está compuesta por | ||
+ | tres archivos: | ||
+ | |||
+ | *.m0d malete options (record 0) file (collation y qué más?) | ||
+ | *.mqt malete query terms | ||
+ | *.mrd malete record data | ||
+ | |||
+ | Es importante notar que se trata de archivos de *texto*. | ||
+ | |||
+ | En el directorio principal está el archivo README, que contiene unas instrucciones | ||
+ | para que podamos comenzar a interactuar con Malete: | ||
+ | |||
+ | make -C mlt server | ||
+ | | ||
+ | then check out php/demo.php | ||
+ | or telnet localhost 2042 | ||
+ | and type (be careful to use a tab after the Q with queries!): | ||
+ | test.Q | ||
+ | | ||
+ | test.Q | ||
+ | | ||
+ | test.Q water | ||
+ | | ||
+ | test.Q plant water? | ||
+ | | ||
+ | test.Q plant + dev$ | ||
+ | | ||
+ | test.Q | ||
+ | |||
+ | |||
+ | Sigamos entonces esas instrucciones, y veamos qué sucede. Comenzamos con el | ||
+ | comando | ||
+ | |||
+ | make -C mlt server | ||
+ | |||
+ | Esto invoca al programa make, y le pasa dos argumentos: la opción '-C mlt' le indica | ||
+ | que debe leer el Makefile del directorio mlt, y el target 'server' ...? | ||
+ | |||
+ | * Qué vemos en la pantalla: ver archivos log-error y log-out | ||
+ | |||
+ | El mensaje final es: listening on '*:2042' | ||
+ | |||
+ | * Qué nuevos archivos se han creado | ||
+ | |||
+ | Nuevo directorio 'bin': | ||
+ | cds.o db.o hash.o malete pw.o rdx.o sys.o | ||
+ | cdx.o dbo.o iif.o malete.o qdx.o srv.o uti.o | ||
+ | |||
+ | bin/malete es el ejecutable (279052 bytes) | ||
+ | |||
+ | Dentro de mlt, 1 archivo: | ||
+ | spanish.mcx compiled collation file | ||
+ | |||
+ | (por qué spanish? por qué dentro de mlt?) | ||
+ | |||
+ | Dentro de test, 5 archivos: | ||
+ | test.m0d | ||
+ | test.mrd | ||
+ | test.mrx | ||
+ | test.mqd | ||
+ | test.mqx | ||
+ | |||
+ | Esta nueva base, llamada test, es una copia de la base cds que vino con malete. Más | ||
+ | precisamente, los archivos test.m0d y test.mrd son copia, respectivamente, de | ||
+ | cds.m0d y cds.mrd, mientras que | ||
+ | |||
+ | test.mqd se genera a partir de cds.mqt (malete qload) | ||
+ | test.mrx se genera a partir de test.mrd (malete dbinfo) ??? | ||
+ | test.mqx se genera a partir de test.mqd y test.m0d (malete dbinfo) ??? | ||
+ | |||
+ | |||
+ | Vamos a ver qué sucede al establecer una conexión vía telnet. Para esto abrimos | ||
+ | otra consola: | ||
+ | |||
+ | [fernando@localhost openisis]$ telnet localhost 2042 | ||
+ | Trying 127.0.0.1... | ||
+ | Connected to localhost. | ||
+ | Escape character is '^]'. | ||
+ | |||
+ | El servidor Malete está listo para escuchar nuestros mensajes. Empecemos con "test.Q", | ||
+ | seguido por un doble enter: | ||
+ | |||
+ | test.Q | ||
+ | | ||
+ | # 151 0 0 | ||
+ | -9 1@0 | ||
+ | 44 Methodology of plant eco-physiology: proceedings of the Montpellier Symposium | ||
+ | 50 Incl. bibl. | ||
+ | 69 Paper on: <plant physiology><plant transpiration><measurement and instruments> | ||
+ | 24 Techniques for the measurement of transpiration of individual plants | ||
+ | 26 aParis bUnesco c-1965 | ||
+ | 30 ap. 211-224 billus. | ||
+ | 70 Magalhaes, A.C. | ||
+ | 70 Franco, C.M. | ||
+ | -8 2@336 | ||
+ | 44 Methodology of plant eco-physiology: proceedings of the Montpellier Symposium | ||
+ | 50 Incl. bibl. | ||
+ | 69 Paper on: <plant evapotranspiration> | ||
+ | 24 <The> Controlled climate in the plant chamber and its influence upon assimilation and transpiration | ||
+ | 26 c1965 | ||
+ | 30 ap. 225-232 billus. | ||
+ | 70 Bosian, G. | ||
+ | |||
+ | [esos son los primeros 2 registros; siguen 18 más] | ||
+ | |||
+ | Cuando repetimos el mensaje "test.Q" (seguido, como en todos los casos, por doble | ||
+ | enter), obtenemos los siguientes 20 registros de la base test: | ||
+ | |||
+ | test.Q | ||
+ | | ||
+ | # 131 0 0 | ||
+ | -8 21@6320 | ||
+ | 24 <The> Determination of the evaporation from the plant cover and the surface of the soil by relating lysimeter and soil moisture measurements to potential evaporation | ||
+ | 26 c1965 | ||
+ | 30 ap. 461-465 billus. | ||
+ | 44 Methodology of plant eco-physiology: proceedings of the Montpellier Symposium | ||
+ | 50 Incl. bibl. | ||
+ | 69 Paper on: <evaporation><soil moisture><plant physiology><plant ecology><measurement and instruments> | ||
+ | 70 Klausing, O. | ||
+ | -9 22@6741 | ||
+ | 44 Methodology of plant eco-physiology: proceedings of the Montpellier Symposium | ||
+ | 50 Incl. bibl. | ||
+ | 69 Paper on: <water balance><water yield><forests><plant ecology><rain><run-off><Israel> | ||
+ | 24 Water yields of forest, Maquis and grass covers in semi-arid regions: a literature review | ||
+ | 26 c1965 | ||
+ | 30 ap. 467-477 billus. | ||
+ | 70 Shachori, A.Y. | ||
+ | 70 Michaeli, A. | ||
+ | |||
+ | [siguen hasta el registro 40] | ||
+ | |||
+ | Al usar una consulta vacía, estamos recuperando todos los registros de la base (véase Protocol.txt) | ||
+ | |||
+ | |||
+ | Probemos ahora con "test.Q water" (atención: luego de la Q hay un TAB): | ||
+ | |||
+ | test.Q water | ||
+ | | ||
+ | # 14 1 0 | ||
+ | 0 4 | ||
+ | 0 5 | ||
+ | 0 10 | ||
+ | 0 11 | ||
+ | 0 12 | ||
+ | 0 13 | ||
+ | 0 14 | ||
+ | 0 16 | ||
+ | 0 22 | ||
+ | 0 24 | ||
+ | 0 25 | ||
+ | 0 43 | ||
+ | 0 52 | ||
+ | 0 57 | ||
+ | |||
+ | Lo que hemos hecho es una búsqueda por el término "water", y lo que vemos es una | ||
+ | lista de los 14 record-id (los MFN de cds-isis) correspondientes. | ||
+ | |||
+ | Pasemos a la siguiente consulta: | ||
+ | |||
+ | test.Q plant water? | ||
+ | | ||
+ | # 2 2 0 | ||
+ | -9 5@1372 | ||
+ | 44 Methodology of plant eco-physiology: proceedings of the Montpellier Symposium | ||
+ | 50 Incl. bibl. | ||
+ | 69 <plant physiology><soil moisture><plant transpiration><evapotranspiration><measurement and instruments> | ||
+ | 24 Anti-transpirants as a research tool for the study of the effects of water stress on plant behaviour | ||
+ | 26 c1965 | ||
+ | 30 ap. 269-274 billus. | ||
+ | 70 Gale, J. | ||
+ | 70 Poljakoff-Mayber, A. | ||
+ | -8 25@7420 | ||
+ | 24 <The> Heat, water and carbon dioxide budget of plant cover: methods and measurements | ||
+ | 26 c1965 | ||
+ | 30 ap. 495-512 billus. | ||
+ | 44 Methodology of plant eco-physiology: proceedings of the Montpellier Symposium | ||
+ | 50 Incl. bibl. | ||
+ | 69 Paper on: <energy balance><water balance><carbon dioxide><plant physiology><heat transfer><measurement and instruments> | ||
+ | 70 Baumgartner, Albert | ||
+ | |||
+ | |||
+ | Hemos realizado la consulta "plant water?", y hemos recuperado los registros 5 y 25. | ||
+ | Esta consulta consta de dos partes: una búsqueda (plant water) y un filtro (en este | ||
+ | caso, vacío). | ||
+ | |||
+ | Obtuvimos los registros, y no solamente los identificadores. Véase Protocol.txt: | ||
+ | |||
+ | If no filter is specified (i.e. no '?'), only record ids are returned. | ||
+ | |||
+ | |||
+ | |||
+ | Siguiente consulta, de acuerdo con la sugerencia del README: | ||
+ | |||
+ | test.Q plant + dev$ | ||
+ | | ||
+ | # 26 3 0 | ||
+ | 0 2 | ||
+ | 0 3 | ||
+ | 0 5 | ||
+ | 0 6 | ||
+ | 0 8 | ||
+ | 0 21 | ||
+ | 0 25 | ||
+ | 0 27 | ||
+ | 0 86 | ||
+ | 0 87 | ||
+ | 0 100 | ||
+ | 0 111 | ||
+ | 0 114 | ||
+ | 0 118 | ||
+ | 0 124 | ||
+ | 0 127 | ||
+ | 0 128 | ||
+ | 0 130 | ||
+ | 0 131 | ||
+ | 0 132 | ||
+ | |||
+ | Como hay 26 registros y sólo recibimos los identificadores de los primeros 20, | ||
+ | enviamos un nuevo mensaje "test.Q": | ||
+ | |||
+ | test.Q | ||
+ | | ||
+ | # 6 3 0 | ||
+ | 0 133 | ||
+ | 0 134 | ||
+ | 0 141 | ||
+ | 0 147 | ||
+ | 0 149 | ||
+ | 0 150 | ||
+ | |||
+ | |||
+ | A todo esto, ¿qué ha estado sucediendo en la consola inicial, aquella donde habíamos | ||
+ | puesto en marcha el servidor Malete? Luego del mensaje listening on '*:2042', apareció | ||
+ | todo esto: | ||
+ | |||
+ | listening on '*:2042' | ||
+ | got 8 bytes | ||
+ | got tag -1 | ||
+ | detected crlf | ||
+ | got 2 bytes | ||
+ | oStruct 'test.Q' | ||
+ | facMain 'test' | ||
+ | oDBo 'Q' | ||
+ | 20 of 0 results off 0 | ||
+ | got 8 bytes | ||
+ | got tag -1 | ||
+ | detected crlf | ||
+ | got 2 bytes | ||
+ | oStruct 'test.Q' | ||
+ | oDBo 'Q' | ||
+ | 20 of 0 results off 20 | ||
+ | got 14 bytes | ||
+ | got tag -1 | ||
+ | detected crlf | ||
+ | got 2 bytes | ||
+ | oStruct 'test.Q water' | ||
+ | oDBo 'Q water' | ||
+ | parsing 'water' as search | ||
+ | pos 0 lev 1: w | ||
+ | c0 = "water"/0 | ||
+ | successfully parsed 5 bytes cost 1 | ||
+ | creating 17 | ||
+ | keypos 0027 | ||
+ | keypos 8010 | ||
+ | scb '2f162c1b2a' 15 | ||
+ | got 15 | ||
+ | 20 of 14 results off 0 | ||
+ | got 21 bytes | ||
+ | got tag -1 | ||
+ | detected crlf | ||
+ | got 2 bytes | ||
+ | oStruct 'test.Q plant water?' | ||
+ | oDBo 'Q plant water?' | ||
+ | parsing 'plant water?' as search | ||
+ | pos 0 lev 1: p | ||
+ | pos 6 lev 1: w | ||
+ | * on lev 1 | ||
+ | pos 6 lev 2: w | ||
+ | pos 11 lev 2: ? | ||
+ | c0 * | ||
+ | c0 = "plant"/0 | ||
+ | m0 = "water"/0 | ||
+ | successfully parsed 11 bytes cost 1 | ||
+ | creating 4 | ||
+ | creating 17 | ||
+ | table 2 check 97 108-108 off 1308 35 | ||
+ | keypos 001b | ||
+ | keypos 8006 | ||
+ | scb '282216252c' 8 | ||
+ | got 8 | ||
+ | marking 17 + on 4 | ||
+ | keypos 0027 | ||
+ | keypos 8010 | ||
+ | scb '2f162c1b2a' 15 | ||
+ | matching res len 15 against flt len 8 cnd 4 set 1 mrk 1 dim 0 | ||
+ | marking | ||
+ | marking | ||
+ | marked 2 | ||
+ | reducing a->len 8 | ||
+ | 1st 0 were good | ||
+ | ... to a->len 2 | ||
+ | parsing '' as filter | ||
+ | c0 null | ||
+ | successfully parsed 0 bytes cost 0 | ||
+ | 20 of 2 results off 0 | ||
+ | got 21 bytes | ||
+ | got tag -1 | ||
+ | detected crlf | ||
+ | got 2 bytes | ||
+ | oStruct 'test.Q plant + dev$' | ||
+ | oDBo 'Q plant + dev$' | ||
+ | parsing 'plant + dev$' as search | ||
+ | pos 0 lev 1: p | ||
+ | pos 6 lev 1: + | ||
+ | + on lev 1 | ||
+ | pos 8 lev 2: d | ||
+ | c0 + | ||
+ | c0 = "plant"/0 | ||
+ | c0 % "dev"/0 | ||
+ | successfully parsed 12 bytes cost 1 | ||
+ | creating 2 | ||
+ | creating 17 | ||
+ | table 2 check 97 108-108 off 1308 35 | ||
+ | keypos 001b | ||
+ | keypos 8006 | ||
+ | scb '282216252c' 8 | ||
+ | got 8 | ||
+ | creating 18 | ||
+ | keypos 0009 | ||
+ | keypos 000a | ||
+ | scb '1a1b2e1b2227281b1a0218272d252c2a1f1b2b' 1 | ||
+ | scb '1a1b2e1b2227281f251d' 4 | ||
+ | matching res len 4 against flt len 1 cnd 4 set 0 mrk 0 dim 0 | ||
+ | merging 1+4 dim 0 | ||
+ | copying 3 bs to 3 slots | ||
+ | merged 1+4 brk 0 untouched 4 b 3 | ||
+ | scb '1a1b2e1b2227281f251d0218272d252c2a1f1b2b' 8 | ||
+ | matching res len 8 against flt len 5 cnd 4 set 0 mrk 0 dim 0 | ||
+ | merging 5+5 dim 0 | ||
+ | copying 5 bs to 5 slots | ||
+ | merged 5+5 brk 0 untouched 6 b 5 | ||
+ | scb '1a1b2e1b222728241b252c' 9 | ||
+ | matching res len 9 against flt len 10 cnd 4 set 0 mrk 0 dim 0 | ||
+ | merging 10+6 dim 0 | ||
+ | merged 10+6 brk 0 untouched 1 b 0 | ||
+ | scb '1a1b2e1b222728241b252c0228221625251f251d' 2 | ||
+ | matching res len 2 against flt len 16 cnd 4 set 0 mrk 0 dim 0 | ||
+ | merging 16+2 dim 0 | ||
+ | merged 16+2 brk 0 untouched 5 b 0 | ||
+ | scb '1a1b2e1b222728241b252c022b2c2a162c1b1d1f1b2b' 2 | ||
+ | matching res len 2 against flt len 18 cnd 4 set 0 mrk 0 dim 0 | ||
+ | merging 18+1 dim 0 | ||
+ | copying 1 bs to 1 slots | ||
+ | merged 18+1 brk 0 untouched 2 b 1 | ||
+ | scb '1a1b2e1b222728281b241b252c' 1 | ||
+ | matching res len 1 against flt len 19 cnd 4 set 0 mrk 0 dim 0 | ||
+ | merging 19+1 dim 0 | ||
+ | merged 19+1 brk 0 untouched 7 b 0 | ||
+ | loop ends on key '1a1b2f'(3) -1 3 | ||
+ | got 20 | ||
+ | matching res len 20 against flt len 8 cnd 4 set 0 mrk 0 dim 0 | ||
+ | merging 8+20 dim 0 | ||
+ | merged 8+20 brk 0 untouched 8 b 0 | ||
+ | got 28 | ||
+ | 20 of 26 results off 0 | ||
+ | got 8 bytes | ||
+ | got tag -1 | ||
+ | detected crlf | ||
+ | got 2 bytes | ||
+ | oStruct 'test.Q' | ||
+ | oDBo 'Q' | ||
+ | 20 of 26 results off 20 | ||
+ | |||
+ | |||
+ | OK. So what? | ||
+ | |||
+ | |||
+ | Si reiteramos el mensaje "test.Q", ahora que ya hemos agotado los resultados para la | ||
+ | última consulta, obtenemos: | ||
+ | |||
+ | test.Q | ||
+ | | ||
+ | # 0 3 0 | ||
+ | |||
+ | |||
+ | |||
+ | Repetimos la consulta "plant water", esta vez sin el signo de interrogación final: | ||
+ | |||
+ | test.Q plant water | ||
+ | | ||
+ | # 2 4 0 | ||
+ | 0 5 | ||
+ | 0 25 | ||
+ | |||
+ | (en lugar de obtener los 2 registros, como antes, ahora sólo vemos la lista de | ||
+ | identificadores). | ||
+ | |||
+ | |||
+ | |||
+ | Hagamos entonces un repaso de lo visto hasta aquí. | ||
+ | |||
+ | Hemos estado usando un único tipo de mensaje: Q (query message, mensaje de consulta). | ||
+ | La forma de este mensaje (para nuestra base test) es: | ||
+ | |||
+ | test.Q[*TAB*query] | ||
+ | | ||
+ | test.Q | ||
+ | | ||
+ | test.Q query | ||
+ | |||
+ | query es una expresión en el lenguaje de consulta de Malete (ver Query.txt). | ||
+ | |||
+ | La fila de números que encabeza la respuesta a un mensaje Q tiene el significado | ||
+ | siguiente (ver Protocol.txt): | ||
+ | |||
+ | * número estimado de registros restantes, incluyendo los que acaban de ser leídos | ||
+ | * número de la consulta, por el cual se la puede referenciar | ||
+ | * truncation record id | ||
+ | |||
+ | |||
+ | |||
+ | ==== Pasemos ahora a la prueba con PHP. ==== | ||
+ | |||
+ | |||
+ | Para esto, copiamos el directorio 'php' a /var/www/html/openisis/ (/var/www/html | ||
+ | es el DocumentRoot de nuestro servidor Apache). Nos aseguramos de que Apache está | ||
+ | funcionando, e ingresamos con un navegador a | ||
+ | |||
+ | http://127.0.0.1/openisis/php/demo.php | ||
+ | |||
+ | (el servidor Malete ha seguido funcionando ininterrumpidamente). | ||
+ | |||
+ | |||
+ | La página que vemos consta de dos partes: la primera muestra el resultado de | ||
+ | diversas manipulaciones sobre registros "virtuales" creados dentro del mismo | ||
+ | script php; la segunda (a partir de "server") muestra la interacción con el | ||
+ | servidor Malete, usando los mensajes terms (T), query (Q), read (R), write (W) e | ||
+ | index (X) sobre registros de la base test. | ||
+ | |||
+ | ATENCION: en Protocol.txt dice "The standard messages a database should recognize are", | ||
+ | y allí no se menciona 'terms'! | ||
+ | |||
+ | Luego de acceder a esa página, vemos que los archivos test.mrd y test.mqd han | ||
+ | sido modificados. El primero, a causa de las operaciones de escritura sobre el | ||
+ | registro 42; el segundo a causa de la operación de indización sobre el mismo | ||
+ | registro 42. | ||
+ | |||
+ | ATENCION: para poder hacer esta comparación, deberíamos haber conservado una copia | ||
+ | de test.mrd en su estado inicial. | ||
+ | Si examinamos el contenido de test.mrd, notaremos que se han agregado dos registros | ||
+ | al final. El primero, comienza con el encabezado: | ||
+ | |||
+ | W 42@13953 | ||
+ | |||
+ | El segundo, es una copia del primero, pero sin el encabezado. Esto corresponde a lo | ||
+ | que vemos en la página como "writing 42" y "writing 42 as new record". Cuando se | ||
+ | crea un nuevo registro (con el mismo contenido del registro 42), el registro | ||
+ | creado recibe el identificador 152. | ||
+ | |||
+ | PREGUNTA: ¿quién es el usuario con permiso de escritura sobre esos archivos? Por lo | ||
+ | que se ve, es el usuario que ha ejecutado el servidor Malete. | ||
+ | |||
+ | |||
+ | Volvemos por un momento a la consola donde tenemos nuestra sesión de telnet: | ||
+ | |||
+ | test.Q one .. author | ||
+ | | ||
+ | # 1 8 0 | ||
+ | 0 152 | ||
+ | |||
+ | El número de consulta ha pasado a 8 -- los números 5, 6, 7 corresponden a las tres | ||
+ | consultas realizadas desde demo.php. (Según Protocol.txt, "These numbers are per | ||
+ | database".) | ||
+ | |||
+ | |||
+ | [No sé por dónde continuar el análisis de demo.php.] | ||
+ | |||
+ | Algunas preguntas: | ||
+ | |||
+ | * cómo sabe el servidor malete que deseamos trabajar con la base test/test? | ||
+ | |||
+ | La respuesta parece estar en Makefile: | ||
+ | |||
+ | server: $(MALETE) testdb | ||
+ | $(RUN) $(MALETE) server -vd -S test -f../test/ | ||
+ | |||
+ | * entonces, ¿cómo hacemos para activar el servidor y poder acceder a más de una base? | ||
+ | |||
+ | Respuesta: no lo sé. | ||
+ | |||
+ | |||
+ | * por qué no anda "test.Q #n" ? | ||
+ | |||
+ | |||
+ | ==== Cómo importar una base CDS/ISIS ==== | ||
+ | |||
+ | |||
+ | Paso 1: copio los 8 archivos de la base biblio (mst, xrf, cnt, ifp, l01, l02, n01, n02) | ||
+ | en el directorio test. | ||
+ | |||
+ | Paso 2: ejecuto malete con la opción cdsimp: | ||
+ | |||
+ | [fernando@localhost openisis]$ bin/malete cdsimp -No850 biblio -ftest/ | ||
+ | mrx too short | ||
+ | rec 14 pos 109348 locked | ||
+ | rec 16 pos 107120 locked | ||
+ | rec 17 pos 103668 locked | ||
+ | rec 18 pos 101952 locked | ||
+ | rec 19 pos 104716 locked | ||
+ | rec 21 pos 110112 locked | ||
+ | rec 22 pos 113100 locked | ||
+ | rec 23 pos 102766 locked | ||
+ | |||
+ | Malete crea los cuatro archivos biblio.mrx, biblio.mrd, biblio.mqx, biblio.mqd. | ||
+ | |||
+ | ATENCION: La opción -No850 se omite cuando la base original tiene la codificación | ||
+ | de MS-DOS. | ||
+ | |||
+ | |||
+ | Y ahora quiero poder consultar la base biblio mediante malete. ¿Qué hago? | ||
+ | |||
+ | |||
+ | ---- | ||
+ | |||
+ | En el directorio mlt hay un archivo ejecutable, 'run'. | ||
+ | |||
+ | [fernando@localhost openisis]$ cd mlt | ||
+ | [fernando@localhost mlt]$ ./run | ||
+ | loading cds index ... | ||
+ | using path ../test/cds | ||
+ | reading 435 bytes options from '../test/cds.m0d' | ||
+ | [...líneas omitidas...] | ||
+ | listening on '*:2042' | ||
+ | |||
+ | |||
+ | La base disponible ahora es cds. Probamos el mensaje T (terms): | ||
+ | |||
+ | cds.T ar as | ||
+ | | ||
+ | # 0 | ||
+ | 0 4 arab countries | ||
+ | 0 3 arabes | ||
+ | 0 2 architecture | ||
+ | 0 4 area | ||
+ | 0 3 areas | ||
+ | 0 1 arid | ||
+ | 0 1 armaments | ||
+ | 0 1 arms control | ||
+ | 0 1 arms sales | ||
+ | 0 1 art books | ||
+ | 0 1 art history | ||
+ | 0 1 artistic creation | ||
+ | 0 1 arts | ||
+ | |||
+ | |||
+ | Restringimos los términos al tag 24: | ||
+ | |||
+ | cds.T ar as 24 | ||
+ | | ||
+ | # 0 | ||
+ | 0 3 arabes | ||
+ | 0 3 area | ||
+ | 0 3 areas | ||
+ | 0 1 arid | ||
+ | 0 1 armaments | ||
+ | |||
+ | |||
+ | ATENCION: esto parece ser un error: | ||
+ | |||
+ | cds.T area areas | ||
+ | | ||
+ | # 0 | ||
+ | |||
+ | Es decir, no devuelve ningún término, pero de acuerdo con Protocol.txt, | ||
+ | 'T*TAB*from*TAB*to' | ||
+ | Selects terms greater or equal the first parameter and less than the second. | ||
+ | (BTW, debiera decir "greater or equal than".) | ||
+ | |||
+ | El término 'area' es mayor o igual que 'area' y menor que 'areas', por lo tanto | ||
+ | debiera aparecer como resultado. | ||
+ | |||
+ | La misma prueba, con otros pares de términos consecutivos, da el resultado correcto. | ||
+ | Otros fracasos: west/western, model/models, press/pressure | ||
+ | |||
+ | ---- | ||
+ | |||
+ | Ubicar otro archivo donde tenía ya algunas notas sobre Malete 1.0.2 (mail?) | ||
+ | |||
+ | ---- | ||
+ | |||
+ | Es posible evitar la sesión de Telnet, usando por ejemplo: | ||
+ | |||
+ | bin/malete server cds -ftest/ | ||
+ | | ||
+ | bin/malete server biblio -ftest/ | ||
+ | |||
+ | |||
+ | Este comando permite "activar" más de una base: | ||
+ | |||
+ | bin/malete server biblio -ftest/ cds -ftest/ test -ftest/ | ||
+ | |||
+ | |||
+ | PREGUNTA: ¿por qué los terms en biblio están en mayúsculas, y los de cds y test en | ||
+ | minúsculas? Encima... T y Q son sensibles a esta diferencia! Curiosamente, los | ||
+ | términos en cds.mqt (que es de donde son tomados para crear test.mqd) están en | ||
+ | mayúsculas. | ||
+ | |||
+ | |||
+ | |||
+ | ==== Prueba con el script unicode.php ==== | ||
+ | |||
+ | |||
+ | Ejecutamos el servidor: | ||
+ | |||
+ | bin/malete server -S unicode -ftest/ | ||
+ | |||
+ | Genera los archivos unicode.mrx, unicode.mqd, unicode.mqx, unicode-demo.mcx. | ||
+ | |||
+ | En el archivo /var/www/html/openisis/php/unicode.php tenemos que hacer esta | ||
+ | modificación (en forms y links): | ||
+ | |||
+ | /php/unicode => unicode.php | ||
+ | |||
+ | Este script permite: | ||
+ | |||
+ | a) ingresar datos en la base unicode, usando el formulario superior | ||
+ | (ciudad, país, río). | ||
+ | |||
+ | b) realizar búsquedas en la base, usando el formulario inferior, y ver un listado | ||
+ | de todos los registros y de todos los términos del índice. | ||
+ | |||
+ | Inicialmente, la lista de búsquedas está vacía (lo cual es natural), pero también | ||
+ | está vacía la lista de términos... lo cual es extraño, pues el diccionario no está | ||
+ | vacío! | ||
+ | |||
+ | Comenzamos con una búsqueda: wien -> no produce nada | ||
+ | |||
+ | Bien, entonces probemos generando (cargando) el unicode.mqd "a mano": | ||
+ | |||
+ | bin/malete qload -vi unicode -ftest/ <test/unicode.mqt | ||
+ | |||
+ | OK, con eso se resuelve el problema. Entramos a | ||
+ | |||
+ | http://127.0.0.1/openisis/php/unicode.php?bitte=terms | ||
+ | |||
+ | y nos muestra 40 términos del diccionario. Y cada término es un link que funciona! | ||
+ | No entiendo por qué "Zürich" aparece en el índice como "zuerich", "Köln" como | ||
+ | "koeln", "Österreich" como "oesterreich". | ||
+ | |||
+ | Si buscamos "zürich" o "zuerich" recuperamos el registro 4, correspondiente a Zürich. | ||
+ | |||
+ | Si buscamos "Lódz", recupera el registro 9. | ||
+ | |||
+ | Si accedemos a | ||
+ | |||
+ | http://127.0.0.1/openisis/php/unicode.php?bitte=suchen | ||
+ | |||
+ | vemos un listado de los registros presentes en la base (inicialmente 14). | ||
+ | |||
+ | Ahora ingresamos un nuevo registro en la base de datos: | ||
+ | |||
+ | Ciudad Juárez / México | ||
+ | |||
+ | Así hemos creado el registro 15. Pero ya encontramos un nuevo problema: en lugar de | ||
+ | los dos términos (juárez, méxico) que esperábamos encontrar en el diccionario, | ||
+ | aparecen estos cuatro: ju, rez, m, xico. | ||
+ | |||
+ | Sin embargo, si creamos un registro con la palabra "Córdoba", el término "cordoba" | ||
+ | aparece en el diccionario. | ||
+ | |||
+ | Esto se explica si estudiamos el archivo unicode.m0d, que contiene una línea | ||
+ | |||
+ | 4 M o ó | ||
+ | |||
+ | (esto es, la "ó" es mapeada a la "o"), pero nada dice acerca de otras vocales como | ||
+ | "á" o "é". | ||
+ | |||
+ | Si modificamos el archivo .m0d, debemos volver a cargar el índice (qdump + qload). | ||
+ | |||
+ | |||
+ | |||
+ | ==== Importación de registros ISO/MARC ==== | ||
+ | |||
+ | |||
+ | Tomamos un registro MARC (mr.mrc), al que renombramos como mr.iif, y lo ubicamos | ||
+ | en el directorio test. Ejecutamos: | ||
+ | |||
+ | bin/malete iifimp mr -ftest/ | ||
+ | |||
+ | y vemos que se generan los 4 archivos mr.m[rq][dx]. Examinamos mr.mrd y encontramos | ||
+ | allí el registro MARC tal como esperábamos, y además tenemos un primer campo donde | ||
+ | se almacenan los 8 bytes "importantes" del leader: | ||
+ | |||
+ | Leader original: 02688cas 2200589 a 4500 | ||
+ | |||
+ | Leader en mr.mrd: | ||
+ | |||
+ | W 1 cas a | ||
+ | |||
+ | (esto es: c a s blank blank blank a blank, que corresponde a las posiciones 05, 06, | ||
+ | 07, 08, 09, 17, 18, 19) | ||
+ | |||
+ | No es necesario que el archivo marc tenga la extensión .iif: | ||
+ | |||
+ | bin/malete iifimp -Ftest/mr.mrc mr -ftest/ | ||
+ | |||
+ | Debe notarse que esto produce una importación (o append) sobre la base mr. | ||
+ | |||
+ | Probamos ahora con un archivo que contiene 50 registros MARC: | ||
+ | |||
+ | bin/malete iifimp -Ftest/bahia-1-50.mrc bahia -ftest/ | ||
+ | |||
+ | ATENCION: en estas pruebas estamos usando registros MARC de LC (codificación: Latin1). | ||
+ | Aun así, ya detectamos un problema: | ||
+ | |||
+ | 255 aScale 1:500,000 c(W 64°30Ž--W 55°30Ž/S 32°00Ž--S 42°00Ž). | ||
+ | |||
+ | (Habrá que verificar el registro original.) | ||
+ | |||
+ | Probamos ahora con algunos (855) registros de OCLC: | ||
+ | |||
+ | bin/malete iifimp -Ftest/oclc.mrc oclc -ftest/ | ||
+ | |||
+ | Y allí aparece el problema de la codificación: "Problemas de geometrâia analâitica" | ||
+ | |||
+ | |||
+ | |||
+ | ==== Pruebas con bases que no existen ==== | ||
+ | |||
+ | El envío de un mensaje a una base que no existe provoca que la base sea creada. | ||
+ | Pero si desde otra consola borramos la base, entonces al siguiente mensaje que | ||
+ | enviemos la base no será recreada. | ||
+ | |||
+ | Otro ejemplo: entro a demo.php, que intenta acceder a la base test; como la base | ||
+ | no existe, la crea; pero luego corrijo la situación borrando los archivos creados | ||
+ | y reemplazándolos por links simbólicos a la base test. Vuelvo a entrar a demo.php, | ||
+ | pero sigue viendo la base vacía que creó un rato antes! Recién luego de reiniciar el | ||
+ | servidor malete se corrige la situación. | ||
+ | |||
+ | malete dbinfo xxx -> crea la base xxx | ||
+ | |||
+ | |||
+ | |||
+ | ==== Plan para un tutorial básico de Malete ==== | ||
+ | |||
+ | * creo que conviene comenzar por los archivos que componen una base | ||
+ | |||
+ | * aclarar que vamos a "dialogar" a bajo nivel con el servidor malete, para aprender cómo funciona el protocolo. Hacer una analogía con HTTP (telnet host 80). En la práctica, ese diálogo estará a cargo de las aplicaciones que usemos/desarrollemos (e.g. Isis.php). | ||
+ | |||
+ | * una sesión sencilla con la base test: mensajes R, Q, T | ||
+ | |||
+ | * estudio del mensaje R | ||
+ | |||
+ | * estudio del mensaje T | ||
+ | |||
+ | * estudio del mensaje Q. Sintaxis de las consultas | ||
+ | |||
+ | * estudio del mensaje W | ||
+ | |||
+ | * estudio del mensaje X | ||
+ | |||
+ | * el archivo .mrd. El concepto de screenshot | ||
+ | |||
+ | * el archivo .m0d. Metadata. Collation | ||
+ | |||
+ | * otras opciones aparte del server: | ||
+ | * cdsimp | ||
+ | * cdsexp | ||
+ | * iifimp - importación de MARC, importación de ISO (isis), leader | ||
+ | * iifexp | ||
+ | * dbinfo | ||
+ | * qdump | ||
+ | * qload | ||
+ | * rdump | ||
+ | * rload | ||
+ | |||
+ | * trabajo con bases múltiples; links simbólicos | ||
+ | |||
+ | * registros incrustados (embedded) ? | ||
+ | |||
+ | * permisos sobre las bases? | ||
+ | |||
+ | * principales diferencias con cds/isis: términos largos en el diccionario; reemplazo de isisuc.tab & isisac.tab por .m0d; FST?; archivos que componen una base de datos; lenguaje de búsquedas. | ||
+ | |||
+ | * capacidades, limitaciones (tamaños de registros, archivos, claves,...) | ||
+ | |||
+ | * recodificación (Cp850, iso-8859-1, utf-8,...) | ||
+ | |||
+ | * case sensitivity of terms/queries...? | ||
+ | |||
+ | * interacción via web: demo.php (crear demos más detallados) | ||
+ | |||
+ | * para investigar: cómo tener herramientas de línea de comando que proporcionen funcionalidad similar a la de los cisis? | ||
+ | * i2id, id2i: no es necesaria (ver mrd) | ||
+ | * mx: gizmo, iso, seq, dict | ||
+ | * msrt | ||
+ | * retag | ||
+ | * etc | ||
+ | |||
+ | * compatibilidad entre plataformas: bases "dos" y bases "linux". | ||
+ | |||
+ | |||
+ | ATENCION ATENCION [WINDOWS] el servidor no escribe los cambios en la base en forma inmediata. | ||
+ | Hago una prueba de escritura desde PHP, y veo que el mrd no se modifica. A continuación bajo el servidor (CTRL+C), y entonces sí aparece el cambio. Pero si necesito trabajar con el archivo mrd (e.g. para hacer un backup), cómo logro que esté al día, sin bajar el servidor? | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | ==== Dudas y correcciones para Klaus ==== | ||
+ | |||
+ | Descripción de archivos en el directorio mlt: | ||
+ | db.c implementation of general db access functions | ||
+ | rdx.c implementation of general db access functions | ||
+ | |||
+ | |||
+ | ¿Por qué los archivos .mcx se generan en el directorio desde el que se ejecuta el comando malete? Por ejemplo, al ejecutar | ||
+ | |||
+ | bin/malete server -S unicode -ftest/ | ||
+ | |||
+ | se genera unicode-demo.mcx en el directorio actual. ¿No debiera ir también al directorio test? En CharSet.txt dice que se graba en el directorio actual. | ||
+ | |||
+ | |||
+ | Archivo IIF.txt, hablando de generar IIF desde WinIsis: "Make sure, that every field starts with two indicator characters" debiera excluir a los campos 00x. | ||
+ | |||
+ | Importación de registros MARC con la codificación MARC-8 (e.g., de OCLC). | ||
+ | |||
+ | Alguna idea acerca de CQL (Common Query Language)? | ||
+ | |||
+ | Alguna posibilidad de implementar una función de proximidad tipo agrep? | ||
+ | |||
+ | |||
+ | == CharSet.txt == | ||
+ | |||
+ | * multibyte entitities => multibyte entities | ||
+ | * completey => completely | ||
+ | * are mapped to first one => are mapped to the first one | ||
+ | * sideeffect => side effect ? | ||
+ | * beginn => begin | ||
+ | * c.f. => cf. | ||
+ | * with it's own tertiary => with its own tertiary | ||
+ | * it's secondary variant => its secondary variant | ||
+ | * it's lowercase equivalent => its lowercase equivalent | ||
+ | * Since Malete support full application controlled indexing => supports | ||
+ | * is the name yo your database => is the name of your database | ||
+ | |||
+ | == MetaData.txt == | ||
+ | |||
+ | * as detailled => as detailed | ||
+ | |||
+ | == demo.php == | ||
+ | |||
+ | * </br> => <br> | ||
+ | |||
+ | CH y LL en español (collation) | ||
+ | |||
+ | Si modifico el archivo m0d (tabla de collation), ¿qué debo hacer para generar el mcx correspondiente? ¿Borrar el viejo y reiniciar el servidor? | ||
+ | |||
+ | Si tengo el servidor andando con una base, y quiero acceder a una segunda base, ¿debo reiniciar el servidor? ¿Inicio otro en otro puerto? | ||
+ | |||
+ | No comprendo la diferencia entre A (alias) y M (mapeo) en el archivo m0d, para el caso de las vocales acentuadas. En el ejemplo del CharSet.txt, "A","á" y "Á" aparecen como alias de "a", y "E", "é", "É" aparecen como alias de "e". Pero en unicode.m0d, la "ó" aparece *mapeada* a la "o". | ||
+ | |||
+ | Klaus: en el menú lateral del sitio web faltan links a algunos documentos. | ||
+ | |||
+ | |||
+ | |||
+ | ==== Pruebas con Malete 1.0.2 - Domingo 15 de mayo 2005 ==== | ||
+ | |||
+ | |||
+ | Para que el servidor funcione en Windows: | ||
+ | |||
+ | malete server -S | ||
+ | malete server -S2042 | ||
+ | |||
+ | Demo: | ||
+ | http://localhost/openisis/php/demo.php | ||
+ | |||
+ | |||
+ | Para poder hacer búsquedas, es necesario crear antes el diccionario | ||
+ | para la base test/cds: | ||
+ | |||
+ | ..\malete qload cds <cds.mqt | ||
+ | |||
+ | Y en demo.php: | ||
+ | |||
+ | $db = new Isis_Db($fdt, 'test/cds', new Isis_Server()); | ||
+ | |||
+ | ---- | ||
+ | |||
+ | El archivo Converting.txt dice: | ||
+ | |||
+ | Create a textual representation of the index by | ||
+ | $ | ||
+ | malete qdump cds 2>cds.mqt | ||
+ | $ | ||
+ | |||
+ | El comando debiera ser: malete qdump cds >cds.mqt | ||
+ | |||
+ | ---- | ||
+ | |||
+ | * Importación de una base tradicional: | ||
+ | |||
+ | >malete cdsimp biblio | ||
+ | |||
+ | * Info sobre la base: | ||
+ | |||
+ | >malete dbinfo biblio | ||
+ | db biblio path biblio has 205 records | ||
+ | mrx type 0x01: 4 pos + 3 len + 1 fields = 8 bytes | ||
+ | mqd type 0x01: blocksize 1024, 8 byte pointer values, 247 byte uncompressed keys | ||
+ | mqd pointers 0x8b: 3 record id + 2 tag + 3 pos bytes tag first | ||
+ | |||
+ | * Genera versión txt de la base: | ||
+ | |||
+ | >malete rdump biblio > biblio.txt | ||
+ | |||
+ | * Genera versión txt del diccionario: | ||
+ | |||
+ | >malete qdump biblio >biblio.mqt | ||
+ | |||
+ | * Importación de un archivo .iso | ||
+ | |||
+ | >malete iifimp test1 (busca un archivo test1.iif) | ||
+ | |||
+ | ATENCION: los subcampos aparecen delimitados con '^', y no con TAB. | ||
+ | |||
+ | ---- | ||
+ | |||
+ | Probamos detalladamente con G:\httpd\bases\cds, para poder reportar problemas. | ||
+ | |||
+ | malete cdsimp g:\httpd\bases\cds\cds | ||
+ | Genera error: | ||
+ | could not truncate 0: La operaci¾n solicitada no se puede realizar en un archivo | ||
+ | con una secci¾n asignada a usuario abierta. | ||
+ | |||
+ | Crea 4 archivos: cds.m[rq][dx] | ||
+ | cds.mrd es el único en "formato texto". | ||
+ | En cds.mrd se observan estos cambios: | ||
+ | * delimitador de subcampos: ^ -> TAB | ||
+ | * codificación: Cp850 -> Latin 1 | ||
+ | Sólo aparecen los campos, sin ningún metadato. | ||
+ | ATENCION: cds.mrd contiene el texto "W 24" antes del registro 24, y solamente allí sucede ese fenómeno. | ||
+ | |||
+ | ¿Es necesario que exista un archivo invertido para la base de origen? | ||
+ | |||
+ | |||
+ | Versión txt del diccionario: | ||
+ | G:\tmp\malete>malete qdump cds >cds.mqt | ||
+ | mrx older than data <--- ERROR? | ||
+ | |||
+ | Genera cds.mqt, y escribe cds.mrx | ||
+ | |||
+ | |||
+ | Dump del archivo maestro: | ||
+ | G:\tmp\malete>malete rdump cds >cds.dump | ||
+ | |||
+ | cds.dump comienza con "W 1@0". Sigue con: | ||
+ | "W 2@336" | ||
+ | "W 3@624" | ||
+ | ... | ||
+ | "W 151@57504" | ||
+ | Fuera de eso, es igual a cds.mrd. | ||
+ | Delimitador de subcampos: TAB | ||
+ | |||
+ | |||
+ | Exportación IIF (ISO): | ||
+ | G:\tmp\malete>malete iifexp cds | ||
+ | |||
+ | Genera cds.iif, un archivo iso2709 (sin saltos de línea) | ||
+ | |||
+ | ---- | ||
+ | |||
+ | cds.m0d (collation) para español, está desactualizado (ch, ll) | ||
+ | |||
+ | From CharSet.txt: | ||
+ | "Creating a collation definition from existing ISISAC.TAB and ISISUC.TAB | ||
+ | is a straightforward exercise left to the reader." | ||
+ | |||
+ | |||
+ | |||
+ | {{tag>malete}} |