XPath-handledning

I den här handledningen kommer du att få en kort introduktion till XPath, ett frågespråk som kan användas för att välja godtyckliga delar av HTML-dokument i calibre. XPath är en allmänt använd standard, och att googla det kommer att ge massor av information. Den här handledningen fokuserar däremot på att använda XPath för e-bokrelaterade uppgifter som att hitta kapitelrubriker i ett ostrukturerad HTML-dokument.

Välja efter taggnamn

Den enklaste formen av val är att välja taggar efter namn. Anta till exempel att du vill välja alla <h2>-taggar i ett dokument. XPath-förfrågan till detta är helt enkelt:

//h:h2        (Selects all <h2> tags)

Prefixet // betyder sökning på alla nivåer i dokumentet. Anta nu att du vill söka efter <span>-taggar som finns inuti <a>-taggar. Det kan uppnås med:

//h:a/h:span    (Selects <span> tags inside <a> tags)

Om du vill söka efter taggar på en viss nivå i dokumentet, ändra prefixet:

/h:body/h:div/h:p (Selects <p> tags that are children of <div> tags that are
             children of the <body> tag)

Det här kommer endast att matcha <p>En mycket kort e-bok för att demonstrera användningen av XPath.</p> i Exempel e-bok men inte någon av de andra <p>-taggarna. Prefixet h: i exemplen ovan behövs för att matcha XHTML-taggar. Det här beror på att calibre internt representerar allt innehåll som XHTML. I XHTML har taggar ett namespace och h: är namespace-prefixet för HTML-taggar.

Anta nu att du vill välja både <h1>- och <h2>-taggar. För att göra det behöver vi en XPath-konstruktion som kallas predikat. Ett predikat är helt enkelt ett test som används för att välja taggar. Tester kan vara godtyckligt kraftfullt och som den här handledningen fortskrider, kommer du att se mer kraftfulla exempel. Ett predikat skapas genom att innesluta testuttrycket inom hakparenteser:

//*[name()='h1' or name()='h2']

Det finns flera nya funktioner i detta XPath-uttryck. Den första är användningen av tecknet *. Det betyder matcha alla taggar. Titta nu på testuttrycket name()='h1' eller name()= 'h2'. name() är ett exempel på en inbyggd funktion. Den utvärderar helt enkelt namnet på taggen. Så genom att använda det kan vi välja taggar vars namn är antingen h1 eller h2. Observera att funktionen name() ignorerar namespaces så det inte finns något behov av prefixet h:. XPath har flera användbara inbyggda funktioner. Några fler kommer att introduceras i den här guiden.

Välja efter attribut

För att välja taggar baserat på deras attribut, användning av predikat krävs:

//*[@style]              (Select all tags that have a style attribute)
//*[@class="chapter"]    (Select all tags that have class="chapter")
//h:h1[@class="bookTitle"] (Select all h1 tags that have class="bookTitle")

Här hänvisar operatören @ till attributen för taggen. Du kan använda en del av de XPath inbyggda funktioner att utföra mer avancerad matchning på attributvärden.

Välja efter tagginnehåll

Med hjälp av XPath kan du även välja taggar som baseras på text de innehåller. Det bästa sättet att göra detta är att använda kraften i reguljära uttryck via den inbyggda funktionen re:test():

//h:h2[re:test(., 'chapter|section', 'i')] (Selects <h2> tags that contain the words chapter or
                                          section)

Här hänvisar .-operatören till innehållet i taggen, precis som @-operatören avser dess attribut.

Exempel e-bok

<html>
    <head>
        <title>A very short e-book</title>
        <meta name="charset" value="utf-8" />
    </head>
    <body>
        <h1 class="bookTitle">A very short e-book</h1>
        <p style="text-align:right">Written by Kovid Goyal</p>
        <div class="introduction">
            <p>A very short e-book to demonstrate the use of XPath.</p>
        </div>

        <h2 class="chapter">Chapter One</h2>
        <p>This is a truly fascinating chapter.</p>

        <h2 class="chapter">Chapter Two</h2>
        <p>A worthy continuation of a fine tradition.</p>
    </body>
</html>

XPath inbyggda funktioner

name()

Namnet på den aktuella taggen.

contains()

contains(s1, s2) returnerar true om s1 innehåller s2.

re:test()

re:test(src, pattern, flags) returnerar true om strängen src matchar med det reguljära uttrycks mönster. En särskilt användbar flaggan är i, det gör matchning skiftlägesokänslig. En bra grund på syntaxen för reguljära uttryck finns på regexp syntax