Die PCRE-Bibliothek wurde von Philipp Hazel ursprünglich für das
Exim-Projekt entwickelt, wird aber
inzwischen von vielen Softwareprojekten verwendet, darunter die Sprachen PHP
und Python. Die Bibliothek ist frei, auch für die Verwendung in kommerzieller
Software. Einige Informationen findet man auf der
PCRE-Heimseite, die allerdings erstaunlich
sparsam gehalten ist.
Wer nach weiteren Informationen zu den regulären Ausdrücken sucht, ist dort
allerdings völlig falsch - die gibt es natürlich in der Perl-Dokumentation!
PCRE heisst ja schließlich 'Perl Compatible Regular Expressions', und bis auf
ein paar kleine Abweichungen ist PCRE zu Perl völlig kompatibel. Ich selbst
arbeite mit
ActivePerl - aber man muss ja kein
Perl haben, um mit PCRE zu arbeiten. Wer aber einen Blick in die Handbücher
und Tutorials zu den regulären Ausdrücken werfen will, muss bei ActiveState
schon das ganze Paket (ca. 50 MB Nettogröße, die aber ca. 150 MB auf der
Platte belegen) herunterladen. Wer sich das nicht antun will, sollte
allerdings mit Gugels Hilfe auch andere Tutorials usw. finden, z.B. die Seite
regular-expressions.info.
Wer sich mal eine etwas komplexere Lisp-Anwendung zu Gemüte führen möchte, die
massiven Gebrauch der PCRE-Funktionalität macht, der kann sich mein
Tippex-Programm anschauen, das bei
Cadwiesel zum Download bereitliegt. Bei
diesem Programm geht es darum, dass (als Vorbereitung für das Plotten)
bestimmte Korrekturen an Maßtexten vorgenommen werden. So zeigt AutoCAD
beispielsweise bei einer Maßtoleranz mit dem Bereich +0 -1 den Wert +0
oben hinter der Maßzahl an - nach deutschen Normen sollte diese 0 weggelassen
werden und nur die Minustoleranz unten angezeigt werden. Dieser und andere
Schönheitsfehler können mit 'Tippex' behoben werden. Das Programm ist leicht
zu erweitern bzw. zu modifizieren, wenn man andere Fehler beheben möchte: man
muss nur neue regular expressions eintragen - am Code selbst muss man gar
nichts ändern. Allerdings, das sei nicht verschwiegen: Die regulären Ausdrücke
können monströse Ausmaße annehmen;-)
Ich habe versucht, den Ball möglichst flach zu halten. Meine ganze Leistung
besteht darin, dass ich a) den Quelltext der Datei "pcre2006.arx" geschrieben
habe, und b) die Funktion (pcre-sr) in Lisp programmiert habe. Nur zwei
Funktionen aus der Bibliothek (pcre_compile() und pcre_exec()) habe ich
überhaupt verwendet. Damit die Bibliothek aus Lisp heraus aufgerufen werden
kann, musste ich die RX-Schnittstelle (ehemals ADS) in AutoCAD verwenden, es
handelt sich also nicht um eine ObjectARX-Anwendung.
Die Lisp-Implementation von (pcre-sr) ist rudimentär - Steuerzeichen werden
ignoriert. In Perl steht z.B. das Zeichen '$' im Ersetzungsmuster für einen
einzufügenden Zeilenumbruch. (pcre-sr) ignoriert das aber, das Dollarzeichen
bleibt ein Dollarzeichen. Einen Zeilenumbruch im Ersetzen-Ausdruck muss man
in AutoLisp also mit '\n' erzeugen.
Für alle, die es irgendwie interessieren sollte, hier der Code der DLL
(pcre2006.arx) in C:
#include <stdio.h>
#include <adslib.h>
#include <rxregsvc.h>
#include <rxdefs.h>
#include <aced.h>
#include <acdbads.h>
#include "pcre.h"
#define OVECCOUNT 30 /* should be a multiple of 3 */
#define ELEMENTS(array) (sizeof(array)/sizeof((array)[0]))
struct func_entry { char *func_name; int (*func) (struct resbuf *); };
int pcre_match (struct resbuf *rb);
static struct func_entry func_table[] = { {"pcre-match", pcre_match},
};
/* Declarations of other local functions */
void main (int, char **);
int dofun (void);
int funcload (void);
/* ACRXENTRYPOINT -- This function replaces main() for an ARX program. */
extern "C" AcRx::AppRetCode acrxEntryPoint(AcRx::AppMsgCode msg, void* appId){
switch(msg) {
case AcRx::kInitAppMsg:
acrxDynamicLinker->unlockApplication(appId);
acrxDynamicLinker->registerAppMDIAware(appId);
break;
case AcRx::kInvkSubrMsg:
dofun();
break;
case AcRx::kLoadDwgMsg:
funcload();
}
return AcRx::kRetOK;
}
/* FUNCLOAD -- Define this application's external functions. Return
RTERROR on error, else RTNORM. */
static int funcload(){
int i;
for (i = 0; i < ELEMENTS(func_table); i++) {
if (!acedDefun(func_table[i].func_name, (short)i))
return RTERROR;
}
return RTNORM;
}
/* DOFUN -- Execute external function (called upon an RQSUBR request).
Return value from the function executed, RTNORM or RTERROR. */
static int dofun()
{
struct resbuf *rb;
int val;
/* Get the function code and check that it's within range.
(It can't fail to be, but paranoia doesn't hurt.) */
if ((val = acedGetFunCode()) < 0 || val >= ELEMENTS(func_table)) {
acdbFail("Received nonexistent function code.");
return RTERROR;
}
/* Fetch the arguments, if any. */
rb = acedGetArgs();
/* Call the handler and return its success-failure status. */
val = (*func_table[val].func)(rb);
acutRelRb(rb);
return val;
}
/*-----------------------------------------------------------------------*/
static int pcre_match(struct resbuf *rb){
char* str;
char* pat;
char* flagstr = NULL;
int flags = 0;
struct resbuf* resultlist;
struct resbuf* rover;
pcre *re;
const char *error;
int erroffset;
int ovector[OVECCOUNT];
int rc, i;
char* failstr = NULL;
if((rb == NULL)||(rb->rbnext == NULL)){
acdbFail("Too few arguments");
return RTERROR;
}
if((rb->restype != RTSTR) || (rb->rbnext->restype != RTSTR)) {
acdbFail("Wrong argument type");
return RTERROR;
}
if(rb->rbnext->rbnext){
if(rb->rbnext->rbnext->restype != RTSTR){
acdbFail("Wrong argument type");
return RTERROR;
}else{
flagstr = rb->rbnext->rbnext->resval.rstring;
while(flagstr[0]){
switch(flagstr[0]){
case 'i':
flags |= PCRE_CASELESS;
break;
case 'x':
flags |= PCRE_EXTENDED;
break;
case 'm':
flags |= PCRE_MULTILINE;
break;
case 's':
flags |= PCRE_DOTALL;
break;
default:
break;
}
flagstr++;
}
}
}
pat = rb->resval.rstring;
str = rb->rbnext->resval.rstring;
re = pcre_compile(
pat, /* the pattern */
flags, /* options */
&error, /* for error message */
&erroffset, /* for error offset */
NULL /* use default character tables */
);
/* Compilation failed: print the error message and exit */
if (re == NULL){
sprintf(failstr, "PCRE compilation failed at offset %d: %s\n", erroffset, error);
acdbFail(failstr);
return RTERROR;
}
/* Compilation succeeded: match the subject in the second argument */
rc = pcre_exec(
re, /* the compiled pattern */
NULL, /* no extra data - we didn't study the pattern */
str, /* the subject string */
(int)strlen(str), /* the length of the subject */
0, /* start at offset 0 in the subject */
0, /* default options */
ovector, /* output vector for substring information */
OVECCOUNT /* number of elements in the output vector */
);
/* Matching failed: handle error cases */
if (rc < 0){
switch(rc){
case PCRE_ERROR_NOMATCH:
acedRetNil();
return RTNORM;
break;
/* Handle other special cases if you like */
default:
acedRetInt(rc);
return RTNORM;
break;
}
}
/* Match succeded */
resultlist = rover = acutNewRb(RTLB);
for(i = 0; i < rc; i++){
rover->rbnext = acutNewRb(RTLB);
rover = rover->rbnext;
rover->rbnext = acutNewRb(RTLONG);
rover = rover->rbnext;
rover->resval.rint = ovector[2*i];
rover->rbnext = acutNewRb(RTLONG);
rover = rover->rbnext;
rover->resval.rint = ovector[2*i+1] - ovector[2*i];
rover->rbnext = acutNewRb(RTLE);
rover = rover->rbnext;
}
rover->rbnext = acutNewRb(RTLE);
rover = rover->rbnext;
rover->rbnext = NULL;
acedRetList(resultlist);
return RTNORM;
}