XPath Nedir? Nasıl Kullanılır?

XPath İle Temel İşlemler

R programlama dili ile ilgili son örneklerde XML ile çeşitli işlemler yapmış, Rcrawler ile bir web sitesini taramış ve taradığımız bir web sitesi için site haritaları oluşturmuştuk. Bu yazılar içerisinde bahsi geçen XPath kullanımına ayrıca değinmek faydalı olacaktır.

AA

R, PHP ve daha pek çok programlama dili aracılığı ile, gereksinimler doğrultusunda XML dosyalarını okumakta ve/veya eldeki verileri XML formatında kayıt etmekteyiz. Bu gibi durumlarda verinin ilgili alanlardan (düğüm ve/veya öznitelik) çekilmesi veya bu alanların oluşturulması için çeşitli fonksiyonlar / metotlar kullanılanılmakta. XML ve Temel Kavramlar başlıklı yazımda XML yapısına kısaca değinmeye çalışmıştım. XML yapısı ile ilgili bu yazıya göz atabilirsiniz.

XPath

XPath

XPath XML yapısı (HTML de buna dahil) içerisindeki veriyi edinme sürecinde sıklıkla kullanılan, sürekli geliştirilen bir sorgu (query) dili ve yazım-sözdizim-imla (sözdizimi) kılavuzudur1. W3C standardı olan XPath sayesinde XML dosyası içerisinde (node'lar arasında) yol belirteçleri (path expression) sayesinde hareket edebilmekteyiz2. Güncel durumda, 2017 yılında yayınlanan XPath 3.1 sürümüne sahiptir3, ancak XPath 1.0 hala en yaygın olarak kullanılan sürümdür. XPath'e daha basit bir alternatif olarak başka bir W3C standardı olan CSS Seçiciler örnek gösterilebilir.

Sözdizimi ve Semantik

Her dilde olduğu üzere, XPath ile işlemler gerçekleştirebilmek adına izlenmesi ve uyulması gereken bazı kurallar bulunmakta. Önceki yazılarla da ilişkili olması adına site hartası yapısını kullanacağım.

Tam Yazım Biçimi Kısaltılmış Biçim Açıklama
ancestor İlgili node'u kapsayan üst node'ları ifade eder ve geriye doğru işlem gerçekleştirir
ancestor-or-self İlgili nodun da belirtilen kapsayıcı ile ilişkilenmesi durumunda onu da dahil eder ve geriye doğru işlem gerçekleştirir
attribute @class Node'a ait özniteliği seçer. Diğer yazım biçimi attribute::class
child loc Alt seviyedeki node'u belirtir. Diğer yazım biçimi child::loc
descendant İlgili nodun alt seviyesindeki node'ları ifade eder
descendant-or-self // İlgili nodun alt seviyesindeki node'ları ve eşleşiyorsa kendisini de seçer. Diğer yazım biçimi /descendant-or-self::node()/
parent .. Kapsayıcı node'u ifade eder. Diğer yazım biçimi parent::node()
preceding Geriye doğru ilk node'u seçer
preceding-sibling Geriye doğru ilk node'u ve onun kardeşini seçer
self . İlgili node'un kendisini ifade eder. Diğer yazım biçimi self::node()

Aşağıda yer alan işlemleri herhangi bir XPath Tester ya da tarayıcınızın web geliştirici aracı ile test edebilirsiniz4.

<urlset>
    <url>
        <loc>https://domain.com</loc>
        <priority>1.00</priority>
        <lastmod>2021-03-26T11:40:09+03:00</lastmod>
        <changefreq>always</changefreq>
    </url>
    <url>
        <loc>https://domain.com/about</loc>
        <priority>1.00</priority>
        <lastmod>2021-03-26T11:40:09+03:00</lastmod>
        <changefreq>always</changefreq>
    </url>
</urlset>

XPath ile bir node (düğüm) sıralaması 1 (bir) ile başlar. Dolayısıyla, parent-child ve aynı seviyedeki node'lar (text node da dahil) arasındaki ilişkinin bu sıralamaya göre ele alınması gerekir. Node'lar arasındaki hiyerarşi / (slash) ile ifade edilir.

Yukarıdaki örnek XML içeriği içerisinde node içeriklerine ulaşmak için en temelden daha kapsamlı biçimlere doğru örneklendirmelere başlayalım. İlk olarak, kesin (absolute) bir hiyerarşi belirten yönteme bakalım.

/urlset/url/loc

Görüldüğü üzere kökten (root) itibaren her seviye ayrı bir şekidle belirtilmiş durumda. Node seviyesi ne kadar derinleşir ve karmaşıklaşırsa kesin tanımlarda o kadar hata yapma olasığı ortaya çıkacaktır. Bu sıralı sözdizimi bize https://domain.com ve https://domain.com/about değerlerini döndürecektir. Görüldüğü üzere hiyerarşik olarak tüm node tanımları belirtilmiş duruma. Ancak, kapsamlı yapılarda elbette bu sıralı yapının takip edilmesi mümkün ya da pratik olmayabilir. Bu durumda, yine hiyerarşik olarak genel ve/veya özel tanımlar yapılabilmekte. İlişkisel (relative) olan bu yapıda kesin bir yol belirtilmemektedir.

//loc

Bu tanım da yine bize aynı değerleri döndürecektir. XPath, sıralaması her ne olursa olsun loc isimli node'u tarar, bulur ve içeriğini döndürür. Özel karakterlerle de bu işlem gerçekleştirilebilir. Aşağıdaki örnek içerisinde yer alan * (asterisk) hiyararşi gözetmeksizin tarama yapılmasına izin verir.

//*/loc

Bu ifade ile aradaki node'lar kriter olarak alınmaksızın loc içerikleri döndürülür. //./loc da bize aynı sonucu döndürecektir. Ancak, burada yer alan . aslında self::node() ile aynı şekilde node'un kendisini temsil eder. Ayrıca, h3[.='See also'] biçiminde olduğu gibi text() yerine de kullanılabilmektedir. .. veya parent::node() ile de parent node'a erişebiliriz.

Sıralama belirtmek için ise [] ile de değer belirtmemiz yeterli olacaktır. Aşağıda hem sıra hem de .. içeren bir örnek kullanım yer almakta.

//../url[1]/loc

Yukarıdaki tanım bize sadece ilk url içerisindeki loc içeriğini verecektir. Şimdi bu sıra konusunu test format olarak ifade edilen tanımlayıcılarla biraz daha detaylandıralım.

//../url[1]/node()

Bu tanım bize ilk node tarafından kapsanan diğer node listesini verecektir. node() dışında text() ile textNode içeriğine, comment() ile de <!-- Comment --> şeklinde belirtilen yorumlara erişebiliriz. Şimdi birkaç konuyu bir arada ele alalım. Aşağıdaki 2 satırı ayrı ayrı olarak deneyelim.

(//url/*)[1]
//url/*[1]

Parantez içerisindeki tanım burada genel değil bir node'un kendisini işaret eder. Sonrasında gelen [1] ise bu node'un sırasını belirtmektedir. [2] olarak sırayı değiştirdiğimizde priority içeriğine ulaşırız. Parantezleri kaldırdığımızda ise ilgili sorguyla eşleyen tüm node'lar içerisinde belirtilen sıra değerleri dönecektir. Şimdi, bu sorguya bir de text() ekleyelim.

(//url/*/text())[1]
(//url/*[2]/text())[1]
//url/*/text()[1]
//url/*[2]/text()[1]

İlk tanım bize ilk node içeriği içerisinde yer alan loc değerini döndürürken, ikinci tanım yine bu node içerisindeki priority değerini döndürür. Üçüncü tanım tüm url node'ları arafından kapsanan node'ların değerlerini sunarken, son tanım tüm bu node'lar içerisindeki 2. node değerini döndürür.

Elbette sadece node'lar arasında değil, bir node'a ait özniteliklere (attribute) de erişebilmekteyiz. Bu işlem için @ veya attribute:: tanımlarını kullanabiliriz.

Yukarıdaki örnek XML yapısında bir attribute olmadığı için farklı bir örnek ekleyeceğim.

<html lang="en">
    <head>
        <title>Title</title>
    </head>
    <body class="body-class">
        <h1 id="mainTitle" class="title">Main Title</h1>
        <p lang="en" class="text p-1">Lorem ipsum dolor sit amet, <br /> consectetur <a href="https://google.com" class="link ">adipiscing</a> elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
        <p lang="tr" class="text p-2">Ut enim ad minim veniam, quis nostrud <a href="https://google.com" class="link" target="_blank" rel="noopener">exercitation ullamco</a> laboris nisi ut aliquip ex ea commodo consequat.</p>
        <p lang="fr" class="text p-3">Excepteur sint occaecat cupidatat non proident, <a href="https://google.com" class="link external" target="_blank" rel="noopener">exercitation</a> in culpa qui officia deserunt mollit anim id est laborum.</p>
    </body>
</html>

Şimdi yukarıdaki HTML örneği içerisinde yer alan text p-1 class'ına sahip paragrafa ulaşalım.

//body/*[@class='text p-1']
//body/p[@class='text p-1']

Şimdi bu tanıma aşağıdaki eklemeleri yaptığınızda nelerin değiştiğine bir bakın.

//body/p[@class='text p-1']/*
//body/p[@class='text p-1']/node()
//body/p[@class='text p-1']/text()
//body/p[@class='text p-1']/..
//body/p[contains(@class, 'text')]

İlgili tanımları daha da kompleks hale getirebiliriz ve hatta çeşitli koşullara da sözdizimi içerisinde yer verebiliriz.

//body/p[@lang='tr' and @class='text p-2']/a[@href, contains = 'google']/@target
//p/a[contains(text(), 'exercitation')]/parent::node()[@lang = 'tr']
//p/a[contains(., 'exercitation')]/parent::node()[@lang = 'tr']
//p[starts-with(., 'Ut')]/a[contains(text(), 'exercitation')]
//p/a[@class = 'link secure' and contains(text(), 'exercitation')]

XPath kullanımı ile ilgili örneklere operatörler ve concat(), length(), substring() gibi fonksiyonlar da dahil edilebilir5 6. Ancak, XPath ile ilgili örneklere JavaScript7 8 ve Selenium Driver9 bağlamında ayrıca değineceğim için şimdilik örnekleri sonlandırıyorum.