Archive for March, 2006

03/23 Tokens, Attributes, and pseudo XML parsing in Erlang

I have a very specific need, and I have this wild idea I can solve it with Erlang. Grosso modo, the need is that I have to parse a huge XML file, but I mean something fiercely huge. I haven’t seen it yet – my client has it – but even my very very specific, can break if you move a comma, I am reading this file as plain text parser, which I wrote for this client and this file format, takes forever to parse the file. Attempts to read that file, or similar, albeit a tad smaller ones, with expat et altri was entertaining but fruitless.

So I broke out the big guns. I have three computers at home – well four, but my wife’s old TiBook 15″ is sick – and will prolly have one or two more soon, thanks to recent/current contract work. And one at least will be dual core, or better* so it should be interesting.
* I haven’t decided yet whether to buy a double core or better G5 – since most of my commercial apps are PPC, and OSS stuff can be recompiled and sped up with -mcpu=g5 – or to buy an Intel. We’ll see.

Those two of you who read my posts on Erlang and survived know that I have been playing with concurrency, distributed computing [ridiculously easy to set up in Erlang, but I suspect there are pitfalls lurking right across the corner to bite me…], looping through lists and tuples [of distributed nodes, wink wink], and complaining about string manipulations [for all I know Erlang has a superb set of string manips, but people on #erlang agreed that it’s so so…]. Now, I *need* decent string manips if I am to fake-parse XML. So far, I can start nodes on remote machines, read lines from a file and send these lines to the nodes in a round-robin way, to be processed. Meaning, to obtain the content of the XML – in this case the attributes – and store them somewhere. The “store somewhere” part, I’ll leave for later, we can’t win all battles in one day. Just the word mnesia makes me shiver… For the time being, the parsed output will go to the node’s dictionary. Good enough.

Now, I started working on this with another XML file that has a similar format: the opml file produced by Technorati. It looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<!– OPML generated by Technorati Blog Finder –>
<opml version="1.0">
<head>
<ownerName>Technorati</ownerName>
<title>5 blogs tagged Erlang sorted by authority</title>
</head>
<body>
<outline text="3pBlog Mickaël Rémond Performance, Process, P… " description="Mickael Remond’s thoughts on cluster, robust, scalable and distributed computing" htmlUrl="http://www.3pblog.net" xmlUrl="http://www.3pblog.net/rss.php" type="rss"/>
<outline text="Life’s too short to brag about microcosm a… " htmlUrl="http://sungnyemun.org/wordpress" xmlUrl="http://sungnyemun.org/wordpress/wp-rss2.php" type="rss"/>
[…]
</body>
</opml>

The idea is to extract the 5 attributes – not all five may be present, so it’s actually a good idea to preset the dictionary with the five keys and empty values – and return a tuple, list, dictionary, whatever. Read the file until you hit <body> and then start reading lines until </body>. Ok, can do. Then, for each line, I read the “header” [<outline ], and then hope for the best and pray that the tag/attribute pairs are properly formatted – no reason to doubt, they’re machine-produced, right? So I need a function to retrieve the tag [which I called token], and then the attribute. This until I hit “/>”, the end of the line, as far as the “parser” is concerned. I know, many things could break, but if the format is consistent… we should be safe!

Here goes:

-module(extract).
-compile(export_all).

doit(S) -> % that’s the function we’ll call
% reset the dictionary
put(text,""),
put(type,""),
put(htmlUrl,""),
put(description,""),
put(xmlUrl,""),
proceed(S).

proceed("/>") -> % This is the closing tag, we’re done.
{{text, get(text)}, {type, get(type)}, {htmlUrl, get(htmlUrl)}, {description, get(description)}, {xmlUrl, get(xmlUrl)}};
proceed([$< |T]) -> % opening tag, read the header
{S1,Head}=extract_header(T),
proceed(S1);
proceed([32|T]) ->
proceed(T);
proceed(S1) -> % any other possibility
{S2,Tk}=extract_token(S1),
{S3,Attr}=extract_attribute(S2),
Atom=list_to_atom(Tk),
put(Atom,Attr),
proceed(S3).

extract_header(L) ->
extract_header(L,[],0).
% the last argument, 0 and then 1 is just a state marker
extract_header([H|T],Acc,0) ->
case H of
32 -> extract_header(T,Acc,1);
Other -> extract_header(T,[H,Acc],0)
end;
extract_header([H|T],Acc,1) ->
case lists:member(H, "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890 -_") of
true -> {[H|T],lists:reverse(lists:flatten(Acc))};
% Return header + remainder
Other -> extract_header(T,Acc,1)
end.

extract_token(L) ->
extract_token(L,[]).
extract_token([H|T],Acc) ->
case lists:member(H, "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890 -_") of
true ->
case Acc of
[] -> extract_token(T,H);
Other -> extract_token(T,[H,Acc])
end;
false -> {[H|T],lists:reverse(lists:flatten(Acc))}
end;
extract_token([],Acc) ->
{">",lists:reverse(lists:flatten(Acc))}.

extract_attribute(L) ->
extract_attribute(L,[],0).
extract_attribute([H|T],Acc,0) ->
case H of
34 -> extract_attribute(T,Acc,1); % Open "
Other -> extract_attribute(T,Acc,0)
end;
extract_attribute([H|T],Acc,1) ->
% " open
case H of
34 -> {T,lists:reverse(lists:flatten(Acc))}; % Close ". Return Acc
Other -> extract_attribute(T,[H,Acc],1)
end.

70> S2="<outline text=\"3pBlog Micka\303\253l R\303\251mond Performance, Process, P… \" description=\"Mickael Remond’s thoughts on cluster, robust, scalable and distributed computing\" htmlUrl=\"http://www.3pblog.net\" xmlUrl=\"http://www.3pblog.net/rss.php\" type=\"rss\"/>".
71> extract:doit(S2).
{{text,"3pBlog Micka\303\253l R\303\251mond Performance, Process, P… "},
{type,"rss"},
{htmlUrl,"http://www.3pblog.net"},
{description,"Mickael Remond’s thoughts on cluster, robust, scalable and distributed computing"},
{xmlUrl,"http://www.3pblog.net/rss.php"}}

Sweet, no? :-)

I am sure this is clumsy. But.It.Works. Now on to bring all the pieces together… And learn about mnesia and stuff.

Erlang

03/22 Chroniques coréennes

Ami lecteur, tu n’es pas sans savoir que je suis passionné par la Corée, même si cette passion s’agrémente d’un grain de sel de bonne taille… Prenons le simple exemple du calendrier, chose qui devrait être ô combien objective. La Corée se targue d’une longue Histoire, ce dont les Etats Pas Si Unis Que Cela D’Amérique seraient bien en peine de, une Histoire longue de 5.000 ans, voire d’un demi dix-millénaire. Eh oui, dans les pays de civilisation crypto-chinoise, 10.000 est une unité simple (萬 ou 万), à l’instar de dix (十), cent (百) ou mille (千); 100.000 se dit 十萬 (dix fois 10.000), un million 百萬 (cent fois 10.000), et ainsi de suite. Et donc, les Coréens du pays de la fraicheur matinale – et du matin calme, mais cela, c’est surtout parce qu’ils ne sont pas encore remis de leurs libations nocturnes de la veille – se targuent d’une Histoire 半萬年iène, ie d’une demi dix-millénaire. C’est ainsi, mais ce n’est pas ça qui gêne : depuis 20 ans que je compte mes sous à leur façon, je suis habitué, quoique à 1.200 won l’euro, je suis convaincu que faire un Général de Gaulle à leur monnaie ne ferait pas de mal. Enlevez-moi deux ou trois zéros !

Je m’égare. Leur imagination débordante leur fait dire – après tout, nous avons tous nos légendes – que le fondateur de ce qui est, bon gré, mal gré, la Corée d’aujourd’hui, le ci-devant Tangun, est né 2.333 années avant Jean-Christophe. Ce produit des ébats enflammés d’une ourse et d’un tigre, ou est-ce le contraire ?, mais néanmoins humain – car si le fil de fer barbelé est le rejeton du ver de terre et du hérisson, le premier Coréen est le rejeton de deux animaux velus et sauvages, ce qui a bien changé aujourd’hui – est né donc il y a 4.339 ans.

Pouf Pouf, comme disait le grand Desproges.

Sans vouloir critiquer, il nous manque un demi-millénaire bien tassé, avec monnaie rendue. 661 ans, à vue de nez. Nous voici confrontés à un premier exemple – et rassure-toi, cher lecteur, pas le dernier – de cette culture du 代充 代充, taech’ung taech’ung, autrement dit de l’à-peu-près, si fièrement répandue dans la péninsule. Certains diraient que je chipote, mais il y a 661 ans, la Corée de l’époque, 高麗 Koryŏ, se remettait tout juste d’un siècle d’invasion mongole. En fait, je ne vais pas entretenir le suspense, elle ne s’en remettra pas, et une nouvelle dynastie, encore plus puritaine et coincée que les déjantés du Mayflower, prendra le pas… Je ne sais pas quelle tournure la Corée aura quand elle aura vraiment (et encore, à condition de croire à la légende) 5.000 ans d’histoire, mais nous pouvons parier sur “très différente” sans trop se mouiller…

Pas convaincu ? Un autre exemple issu de la même usine ! L’âge. Ahhh ! L’âge, tout un programme… Je suis né, vieux con que je suis, pendant l’été 1967. Une amie à moi est née en janvier de cette même année. Quel âge avons nous, aujourd’hui, Jeudi 22 Mars 2006 — 05:38 du moins en Corée, décalage horaire GMT+9 oblige ?
Hmmm…?
Moi 40 ans, elle 41.

Pas sympa ça… Eh oui, car ces braves Coréens n’ont pas le même âge que nous. Hormis le décalage horaire sus-mentionné, qui, grâce au plan anti-gaspi, varie entre 7 et 8 heures avec la France de mon enfance, ces braves gens vous collent d’office une année de rab1 pour bons et loyaux services pré-natals2. Entre neuf mois et un an, il y a une marge vite franchie, similaire à 4.339 et 5.000. Que nenni, mon tout bon, que nenni… On pourrait s’attendre, vu leur propension à tout faire vite et mal, à ce que les enfants made in Korea soient conçus en 3 minutes d’allers-retours furtifs – apparemment pas seulement une légende – et 6 ou 7 mois de gestation, bikoze on est pressés (ce qui aurait sans doute plu à l’Homme pressé de Paul Morand). Or, non. Ils ont droit à 10 mois de gestation, certificat du docteur clefs en mains.

Le règne animal ne cessera de me surprendre. La logique derrière cela est que lorsque Mama annonce à Papa qu’il est un homme, un vrai (apparemment après avoir attendu pendant trois mois ses ragnagnas… je rêve !), on fait reculer le calendrier d’un mois, au cycle menstruel précédent. D’où les dix mois, dont un aussi fictif qu’un billet de trois euros.

Donc, un enfant qui naît, il n’a pas une heure, un jour, une semaine, mais un an plus une heure, un jour, une semaine ? Ce serait trop simple. L’addition ne commencera qu’après le premier anniversaire… Le gamin passe d’un an à trois en l’espace de 12 mois. Balaize…

Ami lecteur, crois-tu vraiment qu’ils se sont arrêtés à des broutilles pareilles ? Haha ! Ce serait douter de leurs facultés imaginatives — le soju3 aide vraiment… Ces gens utilisent deux calendriers. Moi aussi, sur mon bureau, j’ai deux calendriers, mais c’est pour voir deux mois d’un coup. Ici, ce n’est pas le cas. En plus de notre bon vieux calendrier Grégorien, ils z’utilisent le calendrier chinois, rebaptisé lunaire pour faire oublier qu’il n’est pas made in Korea. Et c’est là que le rock’n'roll commence vraiment. Reprenons l’exemple des deux personnes nées en 1967. La personne née en janvier 1967 est, selon le calendrier sino-lunaire, pendant la douxième lune de l’an 4.299 de ce calendrier4, finalement franchement chinois, ce que les Coréens, dans un syncrétisme à faire pâlir d’envie les chrétiens réincarnationistes, vous recalculeront en décembre 1966 lunaire. Donc si vous êtes né en “1966″ vous avez un an de plus, non ? Je veux dire, deux ans de plus, puisqu’ils vous en rajoutent un de toute façon…

C’est pas chouette la Corée ?

Notes

1Cela explique que les Coréens croient dur comme fer que j’ai bientôt 40 ans, les sots… Mais pas pourquoi cette amie, née en janvier, a 41 ans.
2 Les anti-avortements applaudiront bêtement.
3 Sorte de dégraisse-boyaux vendu pas cher, car produit en très large quantité et avec des matières premières garanties chimiques. En vente libre, apparemment.
4 Les “siècles” y sont de 60 ans, 5 fois 12 ans (les 五行 cinq éléments – 木火土金水, bois, feu, terre, métal et eau – et les 十二支, 12 rameaux terrestres, autrement connus sous le nom de “signes” : les douzes animaux du zodiaque chinois – 子丑寅卯辰巳午未申酉戌亥, rat, bœuf, tigre, lièvre, dragon, serpent, cheval, mouton, singe, coq, chien et porc. Ces braves z’animaux – en fait les caractères chinois ci-dessus ne signifient pas rat, bœuf, tigre, etc, mais sont associés aux animaux – servent aussi pour indiquer “l’heure”, par groupe de deux heures :
de 23 heures à 1 heure pour 子, l’heure du chien, à 21-23 heures pour 亥, l’heure du porc, ce qui ne surprendra personne…

03/22 Collage of crap

Via Antti, this little gem in the endless flow of Engrish crapvalcade our [ex-]host country knows so well to provide for our amusement and befuddlement:


Collage.of.Huminity

Collagen is Humility?
That’s coming from the [supposedly] numero uno uni in Korea. Yucksers…

03/15 More looping

I expanded on the previous post, and I have a much better example. First, I realized that the variables for current node, current turn, number of turns could be skipped and passed in code. The only one that I feel can be kept is the Pid of the receiver, which is not needed until the end.
Update: Scratch the code I posted yesterday, it wasn’t working properly… Instead I have this, which works… What I did is split the code into three modules, and let them be… :-) I create as many “process” processes as there are lines – the ones that [are supposed to] do the actual parsing – and they quit when finished; the Receiver process closes when the number of lines stored equates the total number of lines. Goody!

-module(essai).
-export([start/0]).
start() ->
ToBeProcessed=[“line 1″,“line 2″,“line 3″,“line 4″,“line 5″,“line 6″,“line 7″,“line 8″],
T=list_to_tuple(ToBeProcessed),
NumLines=size(T),

S=list_to_tuple(nodes()),

LogFile=“Tagada.log”,
Desc=file:open(LogFile,write),
file:write_file(LogFile,“Liste~n”),

Receiver=spawn(rcv,receiver,[LogFile,0,NumLines]),
io:format(“Receiver started as ~p, with params ~p, 0 and ~p~n”,[Receiver, LogFile, NumLines]),

unroll(S,T,1,0,NumLines).

unroll(S,T,Pos,Turn,NumLines) when Pos > size(S) ->
unroll(S,T,1,Turn,NumLines);
unroll(S,T,Pos,Turn,NumLines) ->
case Turn of
NumLines -> done;
_ ->
NextTurn=Turn+1,
io:format(“Creating a new process with value ~p~n”,[element(NextTurn,T)]),
spawn(process,extract,[element(NextTurn,T),get(rcv)]),
unroll(S,T,Pos + 1, NextTurn, NumLines)
end.

-module(process).
-export([extract/2]).

extract(Line,Rcv) ->
io:format(” * Bong! ~p. Pinging ~p (Receiver).~n”,[Line, Rcv]),
Rcv ! {self(), Line}.

-module(rcv).
-export([receiver/3]).

receiver(File, NumberOfLines, TotalLines) when NumberOfLines == TotalLines ->
io:format(“receiver is done here…~n”,[]),
file:close(File);
receiver(File, NumberOfLines, TotalLines) ->
receive
{SomePid,LineToProcess} ->
io:format(“msg in [~p]: ~p ~p~n”,[NumberOfLines + 1,LineToProcess,File]),
receiver(File,NumberOfLines + 1, TotalLines)
end.

Hmmm. Not too bad. I’ll have to make a real life test now…

Erlang

03/15 Looping several times through a list/tuple

-module(essai).
-export([start/0]).

start() ->
S={1,2,3},
put(currentElement,1),
unroll(S).

unroll(S) ->
unroll(S,get(currentElement)).

unroll(S,Pos) when Pos > size(S) ->
done;
unroll(S,Pos) ->
io:format("Element: ~p~n",[element(Pos,S)]),
put(currentElement, Pos + 1),
unroll(S).

Crude but functional. Now imagine that I want to loop several times through that Tuple, I would only have to add two other keys, say numberOfTurns and currentTurn, and watch that. A good example for such a behaviour would be for instance sharing some workload among remote machines. The tuple there would be the list of nodes converted to a tuple: S = list_to_tuple( nodes() ). And instead of printing the elements, I could spawn functions on the nodes, passing some parameters to process.

Which is exactly what I am aiming for…

Erlang