Kai345: XPath -> Dieses Forum : Optimierung möglich?

[latex]Mae  govannen![/latex]

Kein wirkliches Problem, eher Versuch der Zugriff-Optimierung:

Ich bastele gerade an meinem Javascript für dieses Forum herum, genauer gesagt mit XPath. Zwar scheint soweit alles wie gewünscht zu funktionieren, aber es kommt mir etwas umständlich vor. Da ich von XPath wenig Ahnung habe, frage ich daher nach, ob sich die Ausdrücke verbessern/vereinfachen lassen.

Mit...

/* Anzahl ungelesene Nachrichten */  
var msg_root = document.getElementById('root');  
if (msg_root) {  
  var ungelesene = msg_root.getNodesByXPath("descendant::ul/descendant::li[not(contains(@class, 'visited'))]");  
  var ug_count = ungelesene.length;  
}

(Ich habe getNodesByXPath aus molilys Foren-Script verwendet)

...hole ich mir die ungelesenen Beiträge.

Nun blende ich einen Link ein, der auf den _ersten_ ungelesenen Beitrag im letzen (d.h. am tiefsten auf der Seite stehenden) Thread verweist. In ungelesene[ug_count-1] finde ich den letzten ungelesenen Beitrag, daher hole ich mir mit ancestor::li[contains(@class,'thread-start')] den Knoten dieses Threads und gehe von da wieder abwärts, um den ersten ungelesenen Beitrag zu ermitteln:

var t = ungelesene[ug_count - 1].getNodesByXPath("ancestor::li[contains(@class,'thread-start')]/descendant::li[not(contains(@class, 'visited'))]/descendant::span[contains(@class, 'posting')]/descendant::a");

und erhalte damit auch den gewünschten Wert.

Da ich wie erwähnt noch nicht viel gexpatht ;) habe und meist zu umständlich denke, frage ich mal dumm, ob ich mein Ziel einfacher erreichen kann.

TIA && Cü,

Kai

--
„It's 106 miles to Chicago, we got a full tank of gas, half a pack of cigarettes, it's dark, and we're wearing sunglasses“.
„Hit it!“
Selfzeugs
SelfCode: sh:( fo:| ch:? rl:( br:< n4:( ie:{ mo:| va:) js:| de:> zu:) fl:( ss:| ls:?
  1. Hallo!

    Nichts für ungut, aber dieses Forum verlinkt standardmäßig jQuery. Warum nicht deren Selektor-Engine (auch wenn es aufgrund der überalterten Version 1.2.1 noch kein Sizzle kennt) verwenden?

    Gruß, LX

    --
    RFC 1925, Satz 3: Mit ausreichendem Schub fliegen Schweine wunderbar. (...)
    1. Nichts für ungut, aber dieses Forum verlinkt standardmäßig jQuery. Warum nicht deren Selektor-Engine verwenden?

      Weil die scheißlahm im Vergleich zu querySelectorAll, getElementsByClassName und XPath ist.

      (auch wenn es aufgrund der überalterten Version 1.2.1 noch kein Sizzle kennt)

      Das kommt noch hinzu.

      Eine alte jQuery-Version wird hier eingesetzt, weil die neueren einen hässlichen Bug hatten, der den Browser beim Verlassen des Forums eingefroren hat. Dieser Bug ist nun fixed.

      Mathias

      1. [latex]Mae  govannen![/latex]

        Nichts für ungut, aber dieses Forum verlinkt standardmäßig jQuery. Warum nicht deren Selektor-Engine verwenden?
        Weil die scheißlahm im Vergleich zu querySelectorAll, getElementsByClassName und XPath ist.

        Unter anderem das, verbunden damit, daß ich dazu erst jQuerys Funktionen erlernen müste (da ich jQuery aber ansonsten definitiv nicht nutzen werde, bringt mir das nichts, im Gegensatz zu XPath), um bei dieser Gelegenheit eine Antwort an LX zu geben.

        [...} beim Verlassen des Forums [...]

        Wer macht denn *sowas*? Man bleibt 24Std im Forum!

        Cü,

        Kai

        --
        „It's 106 miles to Chicago, we got a full tank of gas, half a pack of cigarettes, it's dark, and we're wearing sunglasses“.
        „Hit it!“
        Selfzeugs
        SelfCode: sh:( fo:| ch:? rl:( br:< n4:( ie:{ mo:| va:) js:| de:> zu:) fl:( ss:| ls:?
        1. Unter anderem das, verbunden damit, daß ich dazu erst jQuerys Funktionen erlernen müste (...)

          Die Selektion von Nodes mit Sizzle wirst Du nur dann erlernen müssen, wenn Du keine Ahnung von CSS hast. So kompliziert ist das nun auch wieder nicht - und so viel langsamer als eine xpath-Selection, die auf den langsameren Browsern ohnehin nicht nativ unterstützt wird, ebenfall nicht.

          Gruß, LX

          --
          RFC 1925, Satz 3: Mit ausreichendem Schub fliegen Schweine wunderbar. (...)
          1. Unter anderem das, verbunden damit, daß ich dazu erst jQuerys Funktionen erlernen müste (...)

            Die Selektion von Nodes mit Sizzle wirst Du nur dann erlernen müssen, wenn Du keine Ahnung von CSS hast. So kompliziert ist das nun auch wieder nicht - und so viel langsamer als eine xpath-Selection, die auf den langsameren Browsern ohnehin nicht nativ unterstützt wird, ebenfall nicht.

            IE 8 kann glücklicherweise querySelectorAll. Damit können alle Browser in ihren neuesten Versionen QSA. Wenn man sich also ein Script schreibt, was nicht mit alten Browsern kompatibel sein muss (ich habe ja böserweise alle IEs in meinem Script ausgesperrt), dann kann man sehr einfache und performante Ansätze wählen.

            Ich verwende XPath nur noch zur Abwärtskompatibilität und für Sachen wie ancestor::, was ich sonst wie Sizzle und Co. über das DOM machen müsste. (Mag sein, dass letzteres schneller ist, ich habe es noch nicht ausgiebig getestet. Abfragen wie hasClass sind halt ultralahm, weil noch kein Browser die entsprechende HTML-5-API kennt.)

            Dass ich damals überhaupt XPath gewählt hatte hatte einfach den Grund, dass ich den Code nicht von einer 100-KB-Bibliothek abhängig machen wollte, es noch keine separaten Selector-Engines gab und XPath von allen Browsern außer IE unterstützt wurde.

            Mathias

  2. /* Anzahl ungelesene Nachrichten */

    Wenn du diese Info zuerst herausziehen willst, ergibt es Sinn, die Knotenliste dann auch weiterzuverwenden.

    Wie du vielleicht gesehen hast, frage ich zusätzlich noch ab, ob der Browser den Link bereits als visited markiert hat. Denn wenn man ein Posting gelesen hat, bekommt man die Forumshauptseite immer noch ohne class=visited ausgeliefert, weil stark gecacht wird und die Threadliste nur bei neuen Postings neu ausgeliefert wird.

    Nun blende ich einen Link ein, der auf den _ersten_ ungelesenen Beitrag im letzen (d.h. am tiefsten auf der Seite stehenden) Thread verweist. In ungelesene[ug_count-1] finde ich den letzten ungelesenen Beitrag, daher hole ich mir mit ancestor::li[contains(@class,'thread-start')] den Knoten dieses Threads und gehe von da wieder abwärts, um den ersten ungelesenen Beitrag zu ermitteln:

    Möglich ist das, ich würde verschiedene Methoden ausprobieren und die Performance testen.

    Z.B. manuelles DOM-Traversing: den Thread-Starter holen, dann von hinten durch ungelesen iterieren und bei jedem Knoten prüfen, ob er noch Kind vom Thread-Starter ist (mit contains() bzw. compareDocumentPosition()). Ich habe solche halb-XPath-halb-DOM-Lösungen noch nicht ausprobiert, es kann sein, dass sie schneller sind.

    Ein paar Beobachtungen:

    • Eine lange XPath-Abfrage, die etwas wiederholt, was man vorher schon abgefragt hat, kann immer noch schneller sein, als wenn man durch das erste Result-Set iteriert und eine oder mehrere Abfrage(n) von den bereits gefundenen Knoten aus startet.
    • XPath ist lahm. Ich bin dazu übergegangen, querySelector(All) zu verwenden, wo es geht. getElementsByClassName ist am schnellsten.

    var t = ungelesene[ug_count - 1].getNodesByXPath("ancestor::li[contains(@class,'thread-start')]/descendant::li[not(contains(@class, 'visited'))]/descendant::span[contains(@class, 'posting')]/descendant::a");

    Scheint mir überkomplex, weil auch das a-Element class=visited hat.

    Vielleicht ist das schneller:

    ancestor::li[contains(@class,'thread-start')]/descendant::a[contains(@class, 'title') and not(contains(@class, 'visited')) and position() = 0]

    (Bin aber auch kein XPath-Ass. Nur aus der Hüfte geschossen.)

    Mathias

    1. 你好 molily,

      Denn wenn man ein Posting gelesen hat, bekommt man die Forumshauptseite immer noch ohne class=visited ausgeliefert, weil stark gecacht wird und die Threadliste nur bei neuen Postings neu ausgeliefert wird.

      Das allerdings muss an einem Fehler in der Konfiguration liegen (ich rate jetzt mal: Benutzer-Rechte flasch gesetzt). Denn eigentlich™ wird bei einer visited-Markierung (also, der serverseitigen Markierung) ein Timestamp zurück gesetzt und damit das Last-Modified-Datum (für den Nutzer) auf den Zeitpunkt der Markierung gesetzt:

        
      /* {{{ module api function, mark a posting visited */  
      void *flt_visited_mark_visited_api(void *vmid) {  
        u_int64_t *mid = (u_int64_t *)vmid;  
        DBT key,data;  
        int ret,fd;  
        u_char buff[256];  
        size_t len;  
        char one[] = "1";  
        
        if(Cfg.VisitedFile) {  
          memset(&key,0,sizeof(key));  
          memset(&data,0,sizeof(data));  
        
          len = snprintf(buff,256,"%"PRIu64,*mid);  
        
          data.data = one;  
          data.size = sizeof(one);  
        
          key.data = buff;  
          key.size = len;  
        
          if((ret = Cfg.db->put(Cfg.db,NULL,&key,&data,0)) != 0) {  
            if(ret != DB_KEYEXIST) {  
              fprintf(stderr,"flt_visited: db->put() error: %s\n",db_strerror(ret));  
              return NULL;  
            }  
        
            return vmid;  
          }  
        
          /* reset last-modified timestamp */  
          snprintf(buff,256,"%s.tm",Cfg.VisitedFile);  
          remove(buff);  
          if((fd = open(buff,O_CREAT|O_TRUNC|O_WRONLY)) != -1) close(fd);  
        
          return vmid;  
        }  
        
        return NULL;  
      }  
      /* }}} */  
        
      /* {{{ flt_visited_validate */  
      #ifndef CF_SHARED_MEM  
      int flt_visited_validate(cf_hash_t *head,configuration_t *dc,configuration_t *vc,time_t last_modified,int sock)  
      #else  
      int flt_visited_validate(cf_hash_t *head,configuration_t *dc,configuration_t *vc,time_t last_modified,void *sock)  
      #endif  
      {  
        struct stat st;  
        char buff[256];  
        
        if(Cfg.VisitedFile) {  
          snprintf(buff,256,"%s.tm",Cfg.VisitedFile);  
          if(stat(buff,&st) == -1) return FLT_DECLINE;  
          if(st.st_mtime > last_modified) return FLT_EXIT;  
        }  
        
        return FLT_OK;  
      }  
      /* }}} */  
      
      

      Und den Tests nach, die ich eben gemacht habe, funktioniert das prinzipiell auch.

      再见,
       克里斯蒂安

      1. Denn wenn man ein Posting gelesen hat, bekommt man die Forumshauptseite immer noch ohne class=visited ausgeliefert, weil stark gecacht wird und die Threadliste nur bei neuen Postings neu ausgeliefert wird.

        Das ist anscheinend falsch: Wenn der Browser denn anfragt, bekommt er auch eine neue Version der Forumshauptseite, aber siehe unten.

        Denn eigentlich™ wird bei einer visited-Markierung (also, der serverseitigen Markierung) ein Timestamp zurück gesetzt und damit das Last-Modified-Datum (für den Nutzer) auf den Zeitpunkt der Markierung gesetzt:

        Okay, danke für den Hinweis. Dann liegt es vermutlich am Browser-Caching.
        Die Caching-Header weisen den Browser an, die Forumshauptseite erst nach einer Minute wieder zu holen (wenn ich das richtig sehe). Das macht mein Browser auch, d.h. wenn ich innerhalb einer Minute die Hauptseite abrufe, ein Posting lese und wieder die Hauptseite abrufe, dann wird beim dritten Schritt gar kein (nicht mal ein Conditional) Request abgesendet und ich bekomme die Hauptseite aus dem lokalen Cache. Da aber das JavaScript wieder ausgeführt wird, das Posting bereits :visited gefärbt ist, aber keine visited-Klasse hat, muss ich tricksen.

        Mathias

    2. [latex]Mae  govannen![/latex]

      Wie du vielleicht gesehen hast, frage ich zusätzlich noch ab, ob der Browser den Link bereits als visited markiert hat. Denn wenn man ein Posting gelesen hat, bekommt man die Forumshauptseite immer noch ohne class=visited ausgeliefert, weil stark gecacht wird und die Threadliste nur bei neuen Postings neu ausgeliefert wird.

      Hervorragend. Das war auch ein Problem, das mir die ganze Zeit Kopfzerbrechen bereitet hat. Gelöst, bevor ich überhaupt fragen konnte :)

      Z.B. manuelles DOM-Traversing: den Thread-Starter holen, dann von hinten durch ungelesen iterieren und bei jedem Knoten prüfen, ob er noch Kind vom Thread-Starter ist (mit contains() bzw. compareDocumentPosition()). Ich habe solche halb-XPath-halb-DOM-Lösungen noch nicht ausprobiert, es kann sein, dass sie schneller sind.

      Diese Möglichkeit werde ich später auf jeden Fall mal testen.

      var t = ungelesene[ug_count - 1].getNodesByXPath("ancestor::li[contains(@class,'thread-start')]/descendant::li[not(contains(@class, 'visited'))]/descendant::span[contains(@class, 'posting')]/descendant::a");

      Scheint mir überkomplex, weil auch das a-Element class=visited hat.

      Das ist völlig an mir vorüber gegangen. Das ändert einiges.

      var ungelesene = msg_root.getNodesByXPath("descendant::span[contains(@class, 'subject')]/child::a[not(contains(@class, 'visited'))]");

      und

      var ug_last = ungelesene[ug_count - 1].getNodesByXPath("ancestor::li[contains(@class,'thread-start')]/descendant::a[contains(@class, 'title') and not(contains(@class, 'visited'))]")[0];

      Mit deiner Variante mit position()=0 hatte ich leider (noch) nicht den gewünschten Erfolg, es war auch <del>spät</del><ins>früh></ins> (so gegen 5:40 am Morgen), aber ich werde aber auch diesbezüglich mit Variationen weitertesten.

      Aber die XPath/DOM-Methode dürfte vermutlich oft schneller sein, die meisten Threads bleiben ja recht überschaubar. Würde also nur dann zum Problem, wenn ausgerechnet ein sehr großer Thread mit vielen ungelesenen an letzter Stelle steht.

      Cü,

      Kai

      --
      „It's 106 miles to Chicago, we got a full tank of gas, half a pack of cigarettes,
      it's dark, and we're wearing sunglasses“.  „Hit it!“
      Foren-Stylesheet
      Selfzeugs
      SelfCode: sh:( fo:| ch:? rl:( br:< n4:( ie:{ mo:| va:) js:| de:> zu:) fl:( ss:| ls:?