Application lttoolbox

From Apertium
Jump to navigation Jump to search

Vous pouvez utiliser lttoolbox comme bibliothèque en C++ avec #include <lttoolbox/fst_processor.h>, et avec un peu plus de travail, vous pouvez aussi l'utiliser depuis Python.

Utilisation comme bibliothèque en C++

Pour cet exemple, vous aurez besoin de deux fichiers : test.dix

<dictionary>
  <alphabet/>
  <sdefs>
    <sdef n="n"/>
    <sdef n="pl"/>
  </sdefs>
  <section id="main" type="standard">
    <e><p><l>cars</l><r>car<s n="n"/><s n="pl"/></r></p></e>
  </section>
</dictionary>

et test.cc.

#include <stdio.h>
#include <string>
#include <iostream>

#include <lttoolbox/fst_processor.h>
#include <lttoolbox/lt_locale.h>
#include <lttoolbox/ltstr.h>

using namespace std;

FSTProcessor fstp;

int main(int argc, char **argv)
{
	if(argc < 2) { 
		wcout << L"Veuillez spécifier un transducteur" << endl;
		exit(-1);
	}

        LtLocale::tryToSetLocale();
	FILE *t_rl = fopen(argv[1], "r");

	fstp.load(t_rl);
	fclose(t_rl);
	fstp.initBiltrans();

	wstring input = L"^car<n><pl>$";
	wstring trad = fstp.biltrans(input);

	wcout << entrée << L" --> " << trad << endl;

	return 0;
}

Compilez les deux fichiers :

$ lt-comp rl test.dix test_rl.bin
main@standard 6 5

$ g++ -Wall test.cc -o test -llttoolbox3

Puis testez :

$ ./test test_rl.bin 
^car<n><pl>$ --> ^cars$

Utilisation comme module depuis Python

Cet exemple est basé sur la manière dont libvoikko (a traduire ?) utilise lttoolbox pour l'analyse. On fait une bibliothèque C++ qui expose n'importe quelle fonction lttoolbox dont on a besoin pour Python, et une petite classe Python qui rend l'appel de ces fonctions un peu plus pythonique.[1]

Écrivez les fichiers qui suivent :

libltpy.h:

#include <lttoolbox/fst_processor.h>

class Analyser {
	public:
		Analyser(const std::string & directoryName) throw(exception);
		wstring analyse(wstring const &word);
	private:
		FSTProcessor fst;
};

libltpy.cc:

#include "libltpy.hpp"
#include <lttoolbox/fst_processor.h>
#include <lttoolbox/lt_locale.h>

using namespace std;

Analyser::Analyser(const string & analyserpath) throw(exception) {
	FILE * file = fopen(analyserpath.c_str(), "r");
	if (!file) {
		cerr << "Impossible d'ouvrir le fichier analyseur " << analyserpath << endl;
		throw exception();
	}
	fst.load(file);
	fclose(file);
	fst.setCaseSensitiveMode(false);
	fst.setDictionaryCaseMode(true);
	fst.initBiltrans();
}
    
wstring Analyser::analyse(wstring const &word) {
	pair <wstring,int> analysis = fst.biltransWithQueue(word, false);
	// Le 'faux' signifie que les ^ ou $ ne sont pas nécessaires en entrée/sortie
	if (analysis.second == 0) {
		return analysis.first;
	}
	else {
		// une correspondance partielle :
		return L"@"+word;
	}
}

extern "C" wstring * analyse(Analyser * a, const wchar_t * word) {
	// Il semble que Python peut seulement envoyer des wchar_t*, mais on a besoin d'une wstring
	size_t wlen = wcslen(word);
	if (wlen == 0) {
		// éviter un bug dans biltransWithQueue:
		return 0;
	}
	wstring inputString = L"";
	for (size_t i = 0; i < wlen; i++) {
		inputString.append(1, word[i]);
	}
	wstring * out = new wstring(a->analyse(inputString));
	return out;
}

EXTERN void free_analyses(wstring * analyses) {
	delete analyses;
}

extern "C" Analyser * init(const char ** error, const char * path) {
	LtLocale::tryToSetLocale();
	Analyser * a = 0;
	try {
		a = new Analyser(path);
		
	}
	catch (exception & e) {
		delete a;
		a = 0;
		*error = e.what();
		return 0;
	}
	*error = 0;
	return a;
}

extern "C" void terminate(Analyser * a) {
	delete a;
}

lt.py:

# -*- coding: utf-8 -*-
from ctypes import byref, CDLL, c_char_p, c_wchar_p, c_void_p, POINTER

class FST(object):
	def __init__(self, libpath, fstpath):
		self.__lib = CDLL(libpath)

		self.__lib.init.argtypes = [POINTER(c_char_p), c_char_p]
		self.__lib.init.restype = c_void_p

		self.__lib.terminate.argtypes = [c_void_p]
		self.__lib.terminate.restype = None

		self.__lib.analyse.argtypes = [c_void_p, c_wchar_p]
		self.__lib.analyse.restype = POINTER(c_wchar_p)

		error = c_char_p()
		self.__handle = self.__lib.init(byref(error), fstpath)
		if error.value != None:
			self.__handle = 0
			raise Exception(u"Erreur d'initialisation de fst : " + unicode(error.value, "UTF-8"))
	
	def __del__(self):
		if (self.__handle != 0):
			self.__handle = 0
			class DummyLib:
				def __getattr__(obj, name):
					raise Exception("Tentative d'utilisation de la bibliothèque après l'appel de terminate()")
			self.__lib = DummyLib()
		
	def analyse(self, word):
		ana_p = self.__lib.analyse(self.__handle, word)
		if ana_p:
			analysis = ana_p.contents.value
		else:
			analysis = ""
		self.__lib.free_analyses(ana_p)
		return analysis

Compilez les fichiers C++ afin d'obtenir un fichier comme libltpy.so. Les commandes qui suivent on travaillé pour moi :

g++ -DGCC_VISIBILITY=1 -g -O2 -fvisibility=hidden -I/usr/local/include/lttoolbox-3.2 -I/usr/local/lib/lttoolbox-3.2/include -c libltpy.cpp -fPIC -DPIC -o libltpy.o

g++  -fPIC -DPIC -shared  libltpy.o -Wl,-rpath -Wl,/usr/local/lib -Wl,-rpath -Wl,/usr/local/lib -L/usr/local/lib /usr/local/lib/liblttoolbox3.so   -Wl,-soname -Wl,libltpy.so.0 -o libltpy.so

Quoi qu'il en soit, si vous utilisez des outils automatiques, vous avez pratiquement juste besoin de ce qui suit dans votre Makefile.am :

lib_LTLIBRARIES = libltpy.la
libltpy_la_SOURCES = libltpy.cpp
libltpy_la_LDFLAGS = $(LTTOOLBOX_LIBS)

ensuite autogen.sh et make libltpy.la (voir #Exemples sur des mots réels).

Alors utilisez-le comme :

import lt
fst = lt.FST("libltpy.so", "analyser.bin")
print fst.analyse(u"cars") # devrait afficher car<n><pl>

Exemples sur des mots réels

Notes

  1. Une alternative pourrait être d'inclure Python.h dans le code C++, et définir une classe Python complète à partir de là, à la manière dont pylibtextcat/py3libtextcat crée une classe Python pour la bibliothèque C.