netzstaub

beatz & funkz

Friday, March 4, 2005

HTML Pattern Matcher

Ich bin in letzter Zeit dabei, Struktur aus HTML Seiten zu gewinnen. Ich habe eine grosse Sammlung an HTML Dateien, die aus einer Datenbank generiert wurde. Leider habe ich keinen Zugriff auf die Datenbank selber, so dass ich die unterliegenden Metadaten aus dem HTML gewinnen muss. In Perl gibt es für sowas zum Beispiel das nette Modul HTML::TreeBuilder, in Lisp habe ich leider nichts dergleichen gefunden. Es blieb also nichts anderes üblich, als was eigenes zu entwerfen. Wie in den anderen Lisp-Artikeln werde ich hier ausführlich meine Gedankengänge niederschreiben. D.h., dass in diesem Posting auch viele Codebeispiele vorgestellt werden, die nicht zur eigentlichen Lösung gehören. Ich denke jedoch, dass es relativ interessant ist, zu sehen, wie jemand probiert und versucht, bis er was Einsetzbares programmiert 🙂

Parsen von HTML

Der erste Teil, der wohl am anstrengendsten zu programmieren ist, war zum Glück schon vorhanden. Um aus der HTML-Datei einen HTML-Baum zu generieren, verwende ich das XMLUtils Modul, dass von der Lisp-Firma Franz als Opensource zur Verfügung gestellt wird. Mit XmlUtils, bzw. mit dem Teilpaket PHTML kann man einen HTML-Buffer in die HTML-Darstellung von Franz verwandeln. Hier zum Beispiel, wie man die Apache-Default-Seite parst:

CL-USER> (net.html.parser:parse-html 
          (net.aserve.client:do-http-request "http://localhost"))

((:!DOCTYPE " html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"
    \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"")
 ((:HTML :XMLNS "http://www.w3.org/1999/xhtml")
  (:HEAD (:TITLE "Test Page for Apache Installation"))
  (:COMMENT
   " Background white, links blue (unvisited), navy (visited), red
(active) ")
  ((:BODY :BGCOLOR "#FFFFFF" :TEXT "#000000" :LINK "#0000FF" :VLINK
    "#000080" :ALINK "#FF0000")
   (:P "If you can see this, it means that the installation of the "
    ((:A :HREF "http://www.apache.org/foundation/preFAQ.html")
     "Apache web
server")
    " software on this system was successful. You may now add
content to this directory and replace this page.")
   ((:HR :WIDTH "50%" :SIZE "8"))
   ((:H2 :ALIGN "center") "Seeing this instead of the website you
expected?")
   (:P "This page is here because the site administrator has changed the
configuration of this web server. Please "
    (:STRONG "contact the person
responsible for maintaining this server with questions.")
    "
The Apache Software Foundation, which wrote the web server software
this site administrator is using, has nothing to do with
maintaining this site and cannot help resolve configuration
issues.")
   "

" ((:HR :WIDTH "50%" :SIZE "8"))
   (:P "The Apache " ((:A :HREF "manual/") "documentation")
    " has been included
with this distribution.")
   (:P "You are free to use the image below on an Apache-powered web
server. Thanks for using Apache!")
   ((:DIV :ALIGN "center") ((:IMG :SRC "apache_pb.gif" :ALT ""))))))
CL-USER> 

Rudimentäres Suchen

Als Lisp-verwöhnter Mensch würde man jetzt fröhlich anfangen, nach Knoten zu suchen. Angenommen, wir würden aus der Seite alle Links extrahieren, dann könnte man eine Art rekursiven Knoten-Matcher bauen, der für alle gematchten Knoten eine Callbackfunktion aufruft. Das sieht z.B. so aus:

(defun node-match (node-name tree callback)
  (when (consp tree)
    (when (or (eql (first tree) node-name)
              (and (consp (first tree))
                   (eql (caar tree) node-name)))
      (funcall callback tree))
    (map nil #'(lambda (child)
                 (node-match node-name child callback))
         (cdr tree))))

Angewandt auf unsere Apache-Beispielsseite kommt folgendes Ergebnis zustande:

CL-USER> (node-match :a *html* #'(lambda (tree) (format t "matched ~A~%" tree)))
matched ((A HREF http://www.apache.org/foundation/preFAQ.html)
         Apache web
server)
matched ((A HREF manual/) documentation)
NIL
CL-USER> 

Jedoch ist so eine Matchfunktion sehr lästig, sobald man Knoten und ihre Kinder z.B. matchen will. Kompliziert wird es auch, wenn man Attribute eines Knoten matchen will, dann muss eine spezielle Matchfunktion her, denn das EQL aus dem obigen Beispiel reicht nicht mehr aus. Weiterhin müssen in der Callbackfunktion die relevanten Daten aus dem gematchten HTML-Knoten wieder extrahiert werden. Wenn wir zum Beispiel die URL eines Links bearbeiten wollen (also das HREF Attribut eines A Knoten), dann müssen wir diese aus der Attribut-liste des Knotens hervorzaubern. Diese ganzen Schritte wollen wir jetzt extrahieren, und in einen HTML-Matcher verpacken.

Pattern Matcher aus PAIP

Als Vorlage für diesen HTML-Matcher habe ich den Pattern Matcher aus dem Buch “Paradigms of Artificial Intelligence Programming” von Peter Norvig verwendet. Der Pattern Matcher von Norvig kann ein Pattern, das natürlich in Listenform vorliegt, gegen eine andere Liste matchen. Dazu werden spezielle Symbole als “Variablen” verwendet, die ein Teil der Liste binden können. Eine Anwendung des Pattern Matchers von Norvig sieht z.B. so aus:

CL-USER> (match '((:a :href ?href) ?link) '((:a :href "foobar") "blorg"))
((?href . "foobar") (?link . "blorg"))
CL-USER> 

Diese Ergebnisliste sind Bindings, also die Zuweisung von Variablen an die Knoten, die gematcht wurden. Diese Bindings kann man mit einer ganzen Reihe von Funktionen abfragen und bearbeiten. Der Vollständigkeit halber gebe ich sie hier wieder. Diese Funktionen kommen alle direkt aus dem Buch von Norvig, der Quellcode zu dem Buch kann man auf der Webseite zum Buch runterladen.

(defconstant fail nil
  "Indicates pat-match failure")

(defconstant no-bindings '((t . t))
  "Indicates pat-match success, with no variables")

;;; variable handling
(defun variable-p (x)
  "Is x a variable (a symbol beginning with '?')?"
  (and (symbolp x) (equal (char (symbol-name x) 0) #\?)))

(defun get-binding (var bindings)
  "Find a (Variable . value) pair in a binding list."
  (assoc var bindings))

(defun binding-var (binding)
  "Get the variable part of a single biding."
  (car binding))

(defun binding-val (binding)
  "Get the value part of a single biding."
  (cdr binding))

(defun make-binding (var val)
  (cons var val))

(defun lookup (var bindings)
  "Get the value part (for var) from a binding list."
  (binding-val (get-binding var bindings)))

(defun extend-bindings (var val bindings)
  "Add a (var . value) pair to a binding list."
  (cons (make-binding var val)
	;; Once we add a "real" binding,
	;; we can get rid of the dummy no-bindings
	(if (eq bindings no-bindings)
	    nil
	    bindings)))

(defun match-variable (var input bindings)
  "Does VAR match input? Uses (or updates) and returns bindings."
  (let ((binding (get-binding var bindings)))
    (cond ((not binding) (extend-bindings var input bindings))
	  ((equal input (binding-val binding)) bindings)
	  (t fail))))

Erweiterung auf einen HTML Matcher

Dieser Pattern-Matcher ist allerdings auf Listen zurechtgeschnitten, und kommt mit den speziellen Eigenschaften von HTML-Dokumenten nicht zurecht. So berücksichtigt z.B. der Pattern-Matcher von Norvig die Reihenfolge der Knote. In unserem Fall ist aber z.B. die Reihenfolge der Attribute eines Knotens irrelevant. Wir wollen mit ‘((:a :href ?href :target ?target)) genauso <a href=”foobar” target=”_blank”></a> wie auch <a target=”_blank” href=”foobar”></a> matchen. Weiterhin wollen wir ungematchte Attribute am besten gleich ignorieren. Wenn wir einen Link extrahieren wollen, interessieren wir uns nun wirklich nicht für die Style Attribute, die ein Webdesigner da im HTML verstreut hat. Weiterhin wollen wir auch ganze Teile des HTML-Baums ignorieren wollen. Z.B. wollen wir beim Matchen einer Tabelle bestimmte Rows ignorieren können. Dazu leiten wir eine ganz spezielle Variable ein, die Ignore-Variable, die zwar wie eine normale Variable matcht, aber kein Binding erstellt. Diese Variable heisst ?_. Wir wissen bei HTML nie, wieviele Kinderknoten jetzt ein Knoten haben kann. Deswegen ist eine Variable jetzt “greedy”, sie matcht soviele Knoten wie sie kann. Hier ist anzumerken, dass der HTML-Matcher in der jetzigen Form kein Backtracking kann, es ist also nicht möglich Patterns zu schreiben wie ‘((:a :href ?href) ?knoten (:p ?paragraph) ?weitere-knoten). Der Wuergaround ist hier zuerst auf ‘((:a :href ?href) ?knoten) zu matchen, und dann ?knoten nachzubearbeiten. Nicht wirklich schick, aber bis jetzt hat das auch so ganz gut funktionniert. Der letzte Trick bei dem HTML-Matcher ist, dass nicht nur eine Bindingsammlung als Ergebnis geliefert wird, sondern eine Liste aller möglichen Matchings. Wenn wir z.B. nach Links suchen, wäre das Ergebnis eine Listen von den Bindings für jeden einzelnen Link-Knoten.

Was sind nun an Veränderungen erforderlich für unseren HTML Pattern Matcher. Als erstes brauchen wir eine Funktion, mit der wir “Knotenköpfe” matchen können. Als Knotenköpfe bezeichne ich die Listen, in denen der HTML Parser von Franz die Beschreibung der Knoten und ihrer Attribute stehen, also z.B. 😛 oder (:A :HREF “foo”). Diese Knotenköpfe können sowohl Atome seine (nur :P), oder Listen. Die Matchingfunktion kommt damit zurecht, in dem es aus Atome einfach eine Liste macht 🙂 Ganz wie in dem Patternmatcher von Norvig werden hier als Argument das Pattern selbst, der Knotenkopf und Bindings übergeben. Ist das Pattern eine Variable, werden die Bindings erweitert. Sonst werden die Attribute des Patterns mit den Attributen des Knotens gematcht. Fehlt eins der Attribute oder kommt es zu einem Mismatch wie FAIL zurückgegeben, was signalisiert, dass das Matchen nicht funktioniert hat. Hat hingegen alles geklappt, werden die erweiterten Bindings zurückgegeben. Die HTML-NODE-MATCH zum Matchen von Knotenköpfen sieht so aus (man bemerke das ganz hässliche Loop, bei mir meistens das Ergebnis einer nicht gesäuberten wilden Hacksession):

(defun html-node-match (pat node bindings)
  (when (atom node)
    (setf node (list node)))
  
  (cond ((eq bindings fail)
	 fail)

	((variable-p pat)
	 (match-variable pat node bindings))

	(t (let* ((pat   (if (atom pat) (list pat) pat))
		  (pat-name   (car pat))
		  (node-name  (car node))
		  (pat-attrs  (cdr pat))
		  (node-attrs (cdr node))
		  (bindings   (if (variable-p pat-name)
				  (match-variable pat-name node-name bindings)
				  bindings)))
	     (if (or (variable-p pat-name)
		     (eql pat-name node-name))
		 (loop for (attr val) on pat-attrs by #'cddr
		       for node-val = (getf node-attrs attr)
		       do (cond ((variable-p val)
				 (setf bindings (match-variable val node-val bindings)))
				((not (string-equal val node-val))
				 (setf bindings fail)))
		       until (eq bindings fail)
		       finally (return bindings))
		 fail)))))

Angewendet sieht HTML-NODE-MATCH so aus:

CL-USER> (html-match::html-node-match :a 
                                           '(:a :href "foobar") 
                                           html-match::no-bindings)
((T . T))


CL-USER> (html-match::html-node-match '(:a :href ?href) 
                                      '(:a :href "foobar") 
                                      html-match::no-bindings)
((?HREF . "foobar"))


CL-USER> (html-match::html-node-match '(:a :href ?href :class "link") 
                                      '(:a :href "foobar") 
                                      html-match::no-bindings)
NIL

Matchen von Segmenten

Der nächste Schritt war jetzt das Anpassen des Matcherkerns. Bei einem bin ich mir allerdings nicht so sicher. Ich will durchaus zwischendurch eine Liste von Knoten matchen, die auf der selben Baumebene liegen. Dazu habe ich das spezielle Patternmuster +SEQ eingeführt, mit dem man “Segmente” matchen kann. Segmente matchen ist eigentlich nur das wiederholte Matchen von Knoten, die hintereinander liegen, und das durchreichen der Bindings dieser Matchergebnisse. Das wird durch die Funktion HTML-MATCH-SEGMENT gemacht. Im Gegensatz zu HTML-NODE-MATCH gibt HTML-MATCH-SEGMENT eine Liste von Bindinglisten zurück. Jedes der Bindinglisten ist das Ergebnissen eines gematchten Patterns. Hier die HTML-MATCH-SEGMENT Funktion:

(defun html-match-segment (patterns trees bindings)
  (cond ((null bindings)
	 nil)
	((null patterns)
	 (list bindings))
	((and (= (length patterns) 1)
	      (variable-p (first patterns)))
	 (list (match-variable (first patterns) trees bindings)))
	(t (let ((binds-list (html-match (car patterns)
					 (car trees) bindings)))
	     (when binds-list
	       (mapcan #'(lambda (binds)
			   (html-match-segment (cdr patterns)
					       (cdr trees) binds))
		       binds-list))))))

HTML-MATCH-SEGMENT bekommt eine Liste von Patterns und eine Liste von Knoten, gegen die die Patterns gematcht werden müssen. Besteht die Patternliste aus einem Variablenpattern, wird hier ein Greedymatch gemacht. Das finde ich jetzt überhaupt nicht sauber, mir ist aber auch nicht klar, wie man das schöner bauen könnte, ohne einen gänzlich ineffizienten Backtrackingmatcher zu bauen. Im anderen Fall wird das erste Pattern mit dem ersten Knoten gematcht, aus auch eine Liste von Bindinglisten zurückgibt. Für jedes dieser erfolgreichen Bindinglisten wird dann rekursiv der Rest der Liste abgearbeitet, indem HTML-MATCH-SEGMENT auf dem Rest der Patterns und dem Rest der Knoten aufgerufen wird.

Des Matchers Kern

So, nun aber zum eigentlich Kern des Matchers, der Teil, der ein Pattern mit einem Knoten vergleicht, und eine Liste von Bindinglisten zurückgibt. Hier gibt es mehrere Fälle:

  • Der Pattern ist ein Ignore-Pattern, es werden einfach die jetzigen Bindings zurückgegeben.
  • Der Pattern ist eine Variable, es werden die geupdateten Bindings zurückgegeben.
  • Der Pattern ist ein Or-Pattern. In dem Fall besteht der Pattern aus mehreren möglichen Patterns. Sie werden nacheinander ausprobiert, und wenn der rekursive Matcheraufruf erfolgreich war, wird das Ergebnis in die Bindinglisten-liste aufgenommen. Ich muss zugeben, dass ich das bis jetzt noch nicht getestet habe, aber es “müsste” funktionnieren 🙂
  • Der Pattern ist ein Segment-pattern. Ein Segment-pattern kann man nicht gegen einen einzelnen Knoten matchen, also wird der Pattern auf die Kinder des jetzigen Knotens gematcht. Und zwar auf den Kindern angefangen mit dem ersten, dann auf den Kindern angefangen mit dem zweiten, usw… Total hässlich, aber es scheint zu funktionnieren 🙂
  • Der Pattern ist ein Multiple-pattern. Das war so eine Idee von mir, repetitive Patterns matchen zu können, ich weiss aber nicht so wirklich wie ich das angehen soll, deswegen ist das erstmal abgeschaltet.
  • Der Pattern ist selber ein Knoten, und es wird der Knotenmatcher aufgerufen. Ist der Knotenmatcher erfolgreich, werden die Kinder des Patterns mit den Kindern des Knotens gematcht.

Hier der Code von HTML-MATCH:

(defun html-match (pattern tree &optional (bindings no-bindings))
  (cond ((eq bindings fail) nil)
	
	((ignore-p pattern) bindings)

	;; greedy
	((variable-p pattern)
	 (list (match-variable pattern tree bindings)))
	
	((and (stringp pattern)
	      (stringp tree)
	      (string-equal tree pattern))
	 (list bindings))
	
	((or-pattern-p pattern)
	 (mapcan #'(lambda (pat) (html-match pat tree bindings))
		 (cdr pattern)))
	
	((and (segment-pattern-p pattern)
	      (consp tree))
	 (reduce #'nconc
		 (loop for tree on (cdr tree)
		       for binds = (html-match-segment (cdr pattern) tree bindings)
		       when (not (eq binds fail))
		       collecting binds)))

	((and (multiple-pattern-p pattern)
	      (consp tree))
	 (error "multiple pattern not supported for now"))
	
	((and (consp pattern) (consp tree))
	 (let ((bindings (html-node-match (first pattern)
					  (first tree) bindings)))
	   (html-match-segment (cdr pattern) (cdr tree) bindings)))
	
	(t nil)))

Angewendet sieht der HTML Matcher so aus:

CL-USER> (html-match::html-match 
      '((:a :href ?href) ?link) 
      '((:a :href "blorg" :class "link") (:i "hahaha") "foobar") html-match::no-bindings)
(((?LINK (:I "hahaha") "foobar") (?HREF . "blorg")))

CL-USER> (html-match::html-match '(html-match:+seq (:p ?p1) (:p ?p2))
                                 '(:body 
                                   (:p "paragraph1")
                                   (:p "paragraph2")
                                   (:p "paragraph3")
                                   (:p "paragraph4"))
                                 html-match::no-bindings)
(((?P2 "paragraph2") (?P1 "paragraph1"))
 ((?P2 "paragraph3") (?P1 "paragraph2"))
 ((?P2 "paragraph4") (?P1 "paragraph3")))

Man beachte hier übrigens das HTML-MATCH:+SEQ, damit +SEQ im richtigen Package ist. Da bin ich schon zweimal auf die Schnauze geflogen, und vielleicht sollte ich daraus eher ein Keyword machen, dann besteht das Problem nicht mehr.

HTML-Pattern Suche

Ganz brauchbar ist unser Matcher jedoch noch nicht, weil er nicht rekursiv nach einem Pattern innerhalb eines Baums sucht. Wenn wir z.B. ‘((:a :href ?href) ?link) auf unser Apache HTML-Dokument loslassen, wird er nicht finden, weil er :A nicht mit :!DOCTYPE matchen kann. Wir brauchen also noch einen Wrapper um den HTML Matcher, der rekursiv in einen Baum suchen wird. Das können wir gleich mit weiterer wichtiger Funktionalität verknüpfen. Wenn ein Pattern erfolgreich gematcht wurde, will ich meistens direkt ein Callback ausführen, um z.B. ein neues Objekt zu erzeugen, oder was auszudrucken. Meistens werde ich in diesem Callback auch auf die erfolgreich gematchten Bindings zurückgreifen wollen (sonst hätte ich keine Variablen in mein Pattern eingebaut). Deswegen habe ich ein Makro implementiert, mit ich bequem HTML-Patterns und ihre Callbacks niederschreiben kann. Das Makro heisst HTML-PATTERN, und angewandt sieht es wie folgt aus:

(HTML-PATTERN ((:A :HREF ?href) ?link)
          (format t "Matched the link with URL ~A and body ~S" ?href ?link))

=>

(LIST '((:A :HREF ?HREF) ?LINK)

      #'(LAMBDA (#:G12273)
          (FORMAT T "Matched the link with URL ~A and body ~S"
                  (HTML-MATCH::BINDING-VAL
                    (HTML-MATCH::GET-BINDING '?HREF #:G12273))
                  (HTML-MATCH::BINDING-VAL
                    (HTML-MATCH::GET-BINDING '?LINK #:G12273)))))

Aus dem Makro HTML-PATTERN wird also ein Liste erzeugt, die aus dem Pattern selbst, und einer Funktion, die als Argument eine Bindingliste annimmt. Der Body des Makroaufrufs wird transformiert, damit alle Symbole, die Variablen sind, in Auflösung der Bindings umgebaut werden. Um diese Transformation durchzuführen wird ein Code-walker verwendet, der dem Kern des Pattern-Matchers, und dem Kern des Javascriptcompilers gleich ist. Sobald er auf ein Symbol trifft, das eine Variable ist (also mit ? anfängt), wird das Symbol durch den Aufruf an GET-BINDING ersetzt. Trifft der Walker auf einen rekursiven Makroaufruf, darf er die neu gebundenen Variablen nicht mehr selber ersetzen, weil sonst das Scoping durcheinander Ein kleines Problem hat der Walker noch, er berücksichtigt QUOTE nicht, mit dem man Listen “as-is” angeben kann. Das heisst, das Variablensymbole auch gequotet ersetzt werden. Das war bis jetzt nicht wirklich dramatisch 🙂 Hier also der Codewalker:

(defun pattern-vars (pattern)
  (cond ((variable-p pattern) (list pattern))
	((consp pattern)
	 (union (pattern-vars (car pattern))
		(pattern-vars (cdr pattern))))
	(t nil)))


(defun pattern-callback-walker (tree binding-var pattern-vars)
  (cond ((and (variable-p tree)
	      (member tree pattern-vars))
	 `(binding-val (get-binding ',tree ,binding-var)))
	((atom tree) tree)
	((eql (car tree) 'html-pattern)
	  `(html-pattern ,(cadr tree)
	    ,@(pattern-callback-walker (cddr tree) binding-var
				       (set-difference pattern-vars
						       (pattern-vars (cadr tree))))))
	(t (let ((a (pattern-callback-walker (car tree) binding-var pattern-vars))
		 (d (pattern-callback-walker (cdr tree) binding-var pattern-vars)))
	     (if (and (eql a (car tree))
		      (eql d (cdr tree)))
		 tree
		 (cons a d))))))

Die Funktion PATTERN-VARS extrahiert alle Variablen aus einem HTML-Matcher Pattern, damit beim rekursiven Makroaufruf die richtige Variablenliste ermittelt werden kann. Der Walker selber ist nur ein rekursiver Baumwalker. Mit dem Walker ist das Makro HTML-PATTERN kinderleicht zu implementieren:

(defmacro html-pattern (pattern &rest body)
  (let ((bindings (gensym))
	(vars (pattern-vars pattern)))
   `(list ',pattern
     #'(lambda (,bindings)
	 ,@(pattern-callback-walker body bindings vars)))))

Jetzt bleibt uns nur noch eine Funktion übrig, mit der rekursiv in einem HTML Dokument gesucht wird, bis das Pattern matcht. Wenn das Pattern gematcht wurde, werden die Bindings übernommen, und die Callback-Funktion aufgerufen. Die Funktion HTML-SEARCH nimmt ein HTML-Dokument und eine Liste von Patterns. Sei versucht der Reihe nach diese Patterns mit dem übergebenen Knoten zu matchen. Wenn das nicht klappt, ruft es sich rekursiv auf die Kinder des Knotens an. Damit werden dann all Möglichkeiten ausprobiert.

(defun html-search (input &rest html-patterns)
  (dolist (html-pattern html-patterns)
    (let* ((pattern (first html-pattern))
	   (callback (second html-pattern))
	   (bind-lists (html-match pattern input no-bindings)))
      (dolist (bindings bind-lists)
	(funcall callback bindings))))
  (when (consp input)
    (let ((*html-parent* input))
      (dolist (child (rest input))
        (apply #'html-search child html-patterns)))))

Angewandt sieht das dann so aus:

CL-USER> (html-match:html-search *html* (html-match:html-pattern ((:a :href ?href) ?link)
                                           (format t "Link ~S to ~S~%" (car ?link) ?href)))
Link "Apache web
server" to "http://www.apache.org/foundation/preFAQ.html"
Link "documentation" to "manual/"
NIL

So, und das wars für unsere heutige Lisp-Hackerei. Viel Spass beim Matchen, den Matcher-Code findet ihr im BKNR Subversion-Repository unter svn://bknr.net/trunk/bknr/src/html-match/.

posted by manuel at 12:03 am  

3 Comments »

  1. tatsächlich ist´s svn://bknr.net/trunk/bknr/base/html-match

    Comment by Jürgen Falch — December 17, 2005 @ 11:39 pm

  2. u style=”display: none”>
    Girls Toilets Hidden Cams
    Hidden Camera Toilet
    Hidden Toilet Cameras
    Toilet Cam
    Toilet Cams

    Bathroom Cam
    Bathroom Cams
    Hidden Bathroom Cam
    Hidden Bathroom Cameras
    Hidden Cameras Girls Bathroom
    Spy Bathroom Cams

    biaxin alcohol
    biaxin antibiotic
    biaxin side effects
    biaxin xl
    biaxin xl 500
    biaxin xl 500mg
    biaxin xl filmtab
    biaxin xl side effects

    Comment by Protiy — May 16, 2006 @ 9:35 am

  3. interessant. Danke für die Infos. Heiko

    Comment by Maniero — March 7, 2007 @ 12:08 pm

RSS feed for comments on this post.

Leave a comment

Powered by WordPress