Xml belgesi ayrıştırılıyor. XML verileri ayrıştırılıyor. Ayrıştırıcı örneği oluşturma

XML ayrıştırma, esasen bir XML belgesinde gezinmek ve ilgili verileri döndürmek anlamına gelir. Giderek artan sayıda web hizmeti JSON biçiminde veri döndürse de çoğu hala XML kullanıyor; bu nedenle, mevcut API'lerin tümünü kullanmak istiyorsanız XML ayrıştırmada uzmanlaşmak önemlidir.

Uzantıyı kullanma SimpleXML PHP 5.0'da eklenen PHP'de XML ile çalışmak çok kolay ve basittir. Bu yazıda size bunu nasıl yapacağınızı göstereceğim.

Kullanım temelleri

Aşağıdaki örnekle başlayalım diller.xml:


>

> 1972>
> Dennis Ritchie >
>

> 1995>
> Rasmus Lerdorf >
>

> 1995>
> James Gosling >
>
>

Bu XML belgesi, her dil hakkında bazı bilgiler içeren programlama dillerinin bir listesini içerir: tanıtıldığı yıl ve yaratıcısının adı.

İlk adım XML'i aşağıdaki işlevleri kullanarak yüklemektir: simplexml_load_file(), veya simplexml_load_string(). İşlevlerin adından da anlaşılacağı gibi, ilki XML'i bir dosyadan, ikincisi ise XML'i bir dizeden yükleyecektir.

Her iki işlev de tüm DOM ağacını belleğe okur ve bir nesne döndürür SimpleXMLElement. Yukarıdaki örnekte nesne $languages ​​değişkeninde saklanmaktadır. Fonksiyonları kullanabilirsiniz var_dump() veya baskı_r()İsterseniz döndürülen nesneyle ilgili ayrıntıları almak için.

SimpleXMLElement Nesnesi
[lang] => Dizi
[ 0 ] => SimpleXMLElement Nesnesi
[@attributes] => Dizi
[isim] => C
[ortaya çıktı] => 1972
[yaratıcı] => Dennis Ritchie
[ 1 ] => SimpleXMLElement Nesnesi
[@attributes] => Dizi
[isim] => PHP
[ortaya çıktı] => 1995
[yaratıcı] => Rasmus Lerdorf
[ 2 ] => SimpleXMLElement Nesnesi
[@attributes] => Dizi
[isim] => Java
[ortaya çıktı] => 1995
[yaratıcı] => James Gosling
)
)

Bu XML bir kök öğe içeriyor Dillerİçinde üç element bulunan uzun. Her dizi öğesi bir öğeye karşılık gelir uzun XML belgesinde.

Operatörünü kullanarak bir nesnenin özelliklerine erişebilirsiniz. -> . Örneğin, $languages->lang size ilk öğeyle eşleşen bir SimpleXMLElement nesnesi döndürecektir. uzun. Bu nesne iki özellik içerir: ortaya çıktı ve yaratıcı.

$languages ​​​​-> lang [ 0 ] -> belirdi ;
$languages ​​​​-> lang [ 0 ] -> yaratıcı ;

Dillerin bir listesini görüntülemek ve özelliklerini göstermek, aşağıdaki gibi standart bir döngü kullanılarak çok kolay bir şekilde yapılabilir: her biri için.

foreach ($languages ​​​​-> lang as $lang ) (
printf(
"" ,
$dil ["isim" ],
$lang -> göründü,
$lang -> yaratıcı
) ;
}

Dil adını almak için öğenin dil özelliği adına nasıl eriştiğime dikkat edin. Bu şekilde SimpleXMLElement nesnesi olarak temsil edilen bir öğenin herhangi bir niteliğine erişebilirsiniz.

Ad Alanlarıyla Çalışmak

Çeşitli web hizmetlerinin XML'i ile çalışırken, öğe ad alanlarıyla birden çok kez karşılaşacaksınız. Haydi değiştirelim diller.xml ad alanı kullanmanın bir örneğini göstermek için:



xmlns:dc =>

> 1972>
> Dennis Ritchie >
>

> 1995>
> Rasmus Lerdorf >
>

> 1995>
> James Gosling >
>
>

Şimdi eleman yaratıcı ad alanına sığar doğru akım http://purl.org/dc/elements/1.1/ adresini işaret ediyor. Önceki kodumuzu kullanarak dil oluşturucuları yazdırmaya çalışırsanız işe yaramayacaktır. Öğe ad alanlarını okumak için aşağıdaki yaklaşımlardan birini kullanmanız gerekir.

İlk yaklaşım, öğe ad alanına erişirken URI adlarını doğrudan kodda kullanmaktır. Aşağıdaki örnek bunun nasıl yapıldığını göstermektedir:

$dc = $languages ​​​​-> lang [ 1 ] - > çocuklar( "http://purl.org/dc/elements/1.1/") ;
echo $dc -> yaratıcı ;

Yöntem çocuklar() bir ad alanı alır ve bir önekle başlayan alt öğeleri döndürür. İki bağımsız değişken alır; bunlardan ilki XML ad alanıdır, ikincisi ise varsayılan olarak isteğe bağlı bir bağımsız değişkendir. YANLIŞ. İkinci bağımsız değişken TRUE olarak ayarlanırsa ad alanı önek olarak değerlendirilecektir. FALSE ise, ad alanı bir URL ad alanı olarak değerlendirilecektir.

İkinci yaklaşım, URI adlarını belgeden okumak ve öğe ad alanına erişirken bunları kullanmaktır. Bu aslında öğelere erişmenin daha iyi bir yoludur çünkü URI'ye sabit kodlanmış olmanıza gerek yoktur.

$namespaces = $languages ​​​​-> getNamespaces (true) ;
$dc = $languages ​​​​-> lang [ 1 ] -> çocuklar ( ($namespaces [ "dc" ] ) ;

echo $dc -> yaratıcı ;

Yöntem GetNamespaces() bir dizi önek adı ve bunlarla ilişkili URI'leri döndürür. Varsayılan olarak ek bir parametre kabul eder YANLIŞ. gibi ayarlarsanız doğru, bu yöntem üst ve alt düğümlerde kullanılan adları döndürür. Aksi halde yalnızca üst düğümde kullanılan ad alanlarını bulur.

Artık aşağıdaki gibi dillerin listesini yineleyebilirsiniz:

$languages ​​= simplexml_load_file ("languages.xml" ) ;
$ns = $languages ​​-> getNamespaces (true ) ;

foreach ($languages ​​​​-> lang as $lang ) (
$dc = $dil -> çocuklar ($ns [ "dc" ] ) ;
printf(
"

%s %d'de göründü ve %s tarafından oluşturuldu.

" ,
$dil ["isim" ],
$lang -> göründü,
$dc -> yaratıcı
) ;
}

Pratik örnek - YouTube'dan bir video kanalını ayrıştırma

Şu adresten RSS beslemesi alan bir örneğe bakalım: Youtube kanalı ve içindeki tüm videoların bağlantılarını görüntüler. Bunu yapmak için lütfen aşağıdaki adresle iletişime geçin:

http://gdata.youtube.com/feeds/api/users/xxx/uploads

URL, belirli bir kanaldaki en son videoların listesini XML biçiminde döndürür. XML'i ayrıştıracağız ve her video için aşağıdaki bilgileri alacağız:

  • Videoya bağlantı
  • Minyatür
  • İsim

XML'i arayıp yükleyerek başlayacağız:

$kanal = "Kanal_adı";
$url = "http://gdata.youtube.com/feeds/api/users/". $kanal. "/yüklenenler";
$xml = file_get_contents($url);

$feed = simplexml_load_string ($xml) ;
$ns = $feed -> getNameSpaces ( true ) ;

XML feed'ine bakarsanız, orada birçok öğenin bulunduğunu görebilirsiniz. varlık, her biri mağaza detaylı bilgi Kanaldaki belirli bir video hakkında. Ancak yalnızca görsel küçük resimlerini, video URL'sini ve başlığını kullanırız. Bu üç element elementin torunlarıdır grup, bu da bir çocuk giriş:

>

>



Başlık… >

>

>

Tüm unsurların üzerinden geçeceğiz giriş ve her biri için gerekli bilgileri çıkaracağız. dikkat oyuncu küçük resim Ve başlık medya ad alanındadır. Bu nedenle önceki örnekte olduğu gibi ilerlememiz gerekiyor. İsimleri belgeden alırız ve elementlere erişirken isim alanını kullanırız.

foreach ($feed -> $giriş olarak giriş) (
$grup = $giriş -> çocuklar ($ns [ "medya" ] ) ;
$grup = $grup -> grup ;
$thumbnail_attrs = $grup -> küçük resim [ 1 ] -> nitelikler () ;
$resim = $thumbnail_attrs [ "url" ] ;
$oyuncu = $grup -> oyuncu -> özellikler () ;
$bağlantı = $oynatıcı [ "url" ] ;
$başlık = $grup -> başlık ;
printf( "

" ,
$oyuncu, $resim, $başlık);
}

Çözüm

Artık nasıl kullanılacağını bildiğinize göre SimpleXML XML verilerini ayrıştırmak için farklı XML akışlarını farklı API'lerle ayrıştırarak becerilerinizi geliştirebilirsiniz. Ancak SimpleXML'in DOM'un tamamını belleğe okuduğunu dikkate almak önemlidir; bu nedenle, büyük bir veri kümesini ayrıştırıyorsanız belleğiniz tükenebilir. SimpleXML hakkında daha fazla bilgi edinmek için belgeleri okuyun.


Herhangi bir sorunuz varsa, bizim kullanmanızı öneririz

XML Genişletilebilir İşaretleme Dili, belgeleri makine tarafından okunabilir biçimde kodlamaya yönelik bir dizi kuraldır. XML, İnternet'te veri alışverişi için popüler bir formattır. Haber siteleri veya bloglar gibi içeriklerini sıklıkla güncelleyen siteler, harici programların içerik değişikliklerinden haberdar olması için genellikle bir XML beslemesi sağlar. XML verilerini göndermek ve ayrıştırmak, ağa bağlı uygulamalar için ortak bir görevdir. Bu derste XML belgelerinin nasıl ayrıştırılacağı ve verilerinin nasıl kullanılacağı açıklanmaktadır.

Ayrıştırıcı Seçmek

Kanal Analizi

Bir feed'i ayrıştırmanın ilk adımı, hangi veri alanlarıyla ilgilendiğinize karar vermektir. Ayrıştırıcı verilen alanları çıkarır ve geri kalan her şeyi yok sayar.

Örnek uygulamada incelenecek kanalın bir parçasını burada bulabilirsiniz. StackOverflow.com'daki her gönderi, bir yayında birkaç alt etiket içeren bir giriş etiketi olarak görünür:

android ile etiketlenen en yeni sorular ... ... http://stackoverflow.com/q/9439999 0 Veri dosyam nerede? uçurum2310 http://stackoverflow.com/users/1128925 2012-02-25T00:30:54Z 2012-02-25T00:30:54Z

Veri dosyası gerektiren bir Uygulamam var ...

... ...

Örnek uygulama, giriş etiketinden ve bunun başlık, bağlantı ve özet alt etiketlerinden verileri alır.

Ayrıştırıcı örneği oluşturma

Bir sonraki adım ayrıştırıcıyı başlatmak ve ayrıştırma işlemini başlatmaktır. Bu kod parçacığı, ayrıştırıcıyı ad alanlarını işlemeyecek ve sağlanan OutputStream'i girdi olarak kullanacak şekilde başlatır. Ayrıştırma işlemi nextTag() çağrısıyla başlar ve uygulamanın ilgilendiği verileri alıp işleyen readFeed() yöntemini çağırır:

Genel sınıf StackOverflowXmlParser ( // Ad alanlarını kullanmıyoruz. , false); parser.setInput(in, null); parser.nextTag(); return readFeed(parser) nihayet ( in.close(); ) ... )

Kanalı çıkar

ReadFeed() yöntemi, feed'in işlenmesine ilişkin asıl işi yapar. "Giriş" etiketiyle işaretlenen öğeler, kanalın yinelemeli işlenmesi için başlangıç ​​noktasıdır. Bir sonraki etiket giriş etiketi değilse atlanır. Tüm "besleme" yinelemeli olarak işlendikten sonra, readFeed(), beslemeden alınan girişleri (iç içe geçmiş veri öğeleri dahil) içeren bir Liste döndürür. Bu Liste daha sonra ayrıştırıcı tarafından döndürülür.

Özel Liste readFeed(XmlPullParser parser) XmlPullParserException, IOException'ı atar ( Liste girişleri = new ArrayList (); parser.require(XmlPullParser.START_TAG, ns, "feed"); while (parser.next() != XmlPullParser.END_TAG) ( if (parser.getEventType() != XmlPullParser.START_TAG) ( devam et; ) String name = parser.getName(); // Giriş etiketini arayarak başlar if (name.equals("entry")) ( entrys.add( readEntry(ayrıştırıcı)); else ( skip(ayrıştırıcı); ) ) girişleri döndürür;

XML ayrıştırma

XML akışını ayrıştırma adımları aşağıdaki gibidir:

Bu kod parçası, ayrıştırıcının girişi, başlığı, bağlantıyı ve özeti nasıl ayrıştırdığını gösterir.

Genel statik sınıf Giriş ( genel son Dize başlığı; genel son Dize bağlantısı; genel son Dize özeti; özel Giriş (Dize başlığı, Dize özeti, Dize bağlantısı) ( this.title = başlık; this.summary = özet; this.link = bağlantı ; ) ) // Bir girdinin içeriğini ayrıştırır. Bir başlık, özet veya bağlantı etiketiyle karşılaşırsa bunları işleme için ilgili "okuma" yöntemlerine // devreder. Aksi takdirde etiketi atlayın. özel Giriş readEntry(XmlPullParser ayrıştırıcı) XmlPullParserException, IOException'ı atar ( parser.require(XmlPullParser.START_TAG, ns, "entry"); Dize başlığı = null; Dize özeti = null; Dize bağlantısı = null; while (parser.next() ! = XmlPullParser.END_TAG) ( if (parser.getEventType() != XmlPullParser.START_TAG) ( devam et; ) String name = parser.getName(); if (name.equals("title")) ( title = readTitle(parser) ; ) else if (isim.equals("özet")) ( özet = readSummary(ayrıştırıcı); ) else if (isim.equals("bağlantı")) ( link = readLink(ayrıştırıcı); ) else ( skip(ayrıştırıcı) ; ) ) return new Entry(title, Summary, link) // Akıştaki başlık etiketlerini işler. özel String readTitle(XmlPullParser ayrıştırıcı) IOException'ı atar, XmlPullParserException ( parser.require(XmlPullParser.START_TAG, ns, "title"); String title = readText(parser); parser.require(XmlPullParser.END_TAG, ns, "title"); return title; ) // Feed'deki bağlantı etiketlerini işler. özel String readLink(XmlPullParser ayrıştırıcı) IOException'ı atar, XmlPullParserException ( String link = ""; parser.require(XmlPullParser.START_TAG, ns, "link"); String etiketi = parser.getName(); String relType = parser.getAttributeValue(null , "rel"); if (tag.equals("link")) ( if (relType.equals("alternate"))( link = parser.getAttributeValue(null, "href"); parser.nextTag(); ) ) parser.require(XmlPullParser.END_TAG, ns, "link"); // Akıştaki özet etiketlerini işler. özel String readSummary(XmlPullParser ayrıştırıcı) IOException'ı atar, XmlPullParserException ( parser.require(XmlPullParser.START_TAG, ns, "summary"); String özeti = readText(parser); parser.require(XmlPullParser.END_TAG, ns, "summary"); Özeti döndür; ) // Etiketlerin başlığı ve özeti için metin değerlerini çıkarır. özel String readText(XmlPullParser ayrıştırıcı) IOException'ı atar, XmlPullParserException ( String result = ""; if (parser.next() == XmlPullParser.TEXT) ( result = parser.getText(); parser.nextTag(); ) sonuç döndürür; ) ... )

İhtiyacınız olmayan öğeleri atlamak

Yukarıda açıklanan XML ayrıştırma adımlarından birinde ayrıştırıcı, ilgilenmediğimiz etiketleri atlar. skip() yönteminin ayrıştırıcı kodu aşağıdadır:

Özel void skip(XmlPullParser parser) XmlPullParserException, IOException'ı atar ( if (parser.getEventType() != XmlPullParser.START_TAG) ( throw new IllegalStateException(); ) int derinlik = 1; while (derinlik != 0) ( switch (parser. next()) ( case XmlPullParser.END_TAG: derinlik--; break; case XmlPullParser.START_TAG: derinlik++; break; ) ) )

İşte nasıl çalışıyor:

  • Geçerli olay START_TAG değilse yöntem bir istisna atar.
  • START_TAG'ı ve END_TAG'a kadar olan tüm etkinlikleri tüketir.
  • Orijinal START_TAG'den sonraki ilk etikette değil, doğru END_TAG'de durduğundan emin olmak için yuvalama derinliğini takip eder.

Dolayısıyla, geçerli öğenin iç içe geçmiş öğeleri varsa, ayrıştırıcı orijinal START_TAG ile karşılık gelen END_TAG arasındaki tüm olayları işleyene kadar derinlik değeri 0 olmayacaktır. Örneğin, analizörün nasıl geçtiğini düşünün iç içe geçmiş 2 öğesi olan bir öğe, Ve :

  • While döngüsünden ilk geçişte, analizcinin bundan sonra karşılaştığı bir sonraki etiket bu START_TAG için
  • While döngüsünden ikinci geçişte analizcinin karşılaştığı bir sonraki etiket END_TAG olur
  • While döngüsünden üçüncü geçişte analizcinin karşılaştığı bir sonraki etiket START_TAG'dir . Derinlik değeri 2'ye çıkarılır.
  • While döngüsünden dördüncü geçişte analizcinin karşılaştığı bir sonraki etiket END_TAG olur. Derinlik değeri 1'e düşürülür.
  • While döngüsünden beşinci ve son geçişte analizcinin karşılaştığı bir sonraki etiket END_TAG olur. Derinlik değeri 0'a düşürülür; öğe başarıyla atlandı.

XML Veri İşleme

Örnek uygulama, bir AsyncTask'ta bir XML akışını alır ve ayrıştırır. İşleme, ana kullanıcı arayüzü iş parçacığının dışında gerçekleşir. İşleme tamamlandığında uygulama, ana aktivitedeki (NetworkActivity) kullanıcı arayüzünü günceller.

Aşağıdaki kod parçasında loadPage() yöntemi şunları yapar:

  • Bir XML beslemesine işaret eden bir URL ile bir dize değişkenini başlatır.
  • Kullanıcı ayarları ve ağ bağlantısı izin veriyorsa, yeni DownloadXmlTask().execute(url) öğesini çağırır. Bu, yeni bir DownloadXmlTask ​​nesnesi (AsyncTask alt sınıfı) oluşturur ve kanalı indirip ayrıştıran ve kullanıcı arayüzünde görüntülenecek bir dize sonucu döndüren executive() yöntemini yürütür.
genel sınıf NetworkActivity, Etkinliği genişletir ( public static final String WIFI = "Wi-Fi"; public static final String ANY = "Herhangi"; özel statik final String URL = "http://stackoverflow.com/feeds/tag?tagnames=android&sort =en yeni"; // Wi-Fi bağlantısı olup olmadığı. özel statik boolean wifiConnected = false; // Mobil bağlantı olup olmadığı. özel statik boolean mobileConnected = false; // Ekranın yenilenip yenilenmeyeceği. public static boolean yenilemeDisplay = true; public static String sPref = null ... // XML akışını stackoverflow.com'dan indirmek için AsyncTask'ı kullanır. public void loadPage() ( if((sPref.equals(ANY)) && (wifiConnected || mobileConnected ) ) ( new DownloadXmlTask().execute(URL); ) else if ((sPref.equals(WIFI)) && (wifiConnected)) ( new DownloadXmlTask().execute(URL); ) else ( // hatayı göster ) )
  • doInBackground(), loadXmlFromNetwork() yöntemini çalıştırır. Kanal URL'sini parametre olarak iletir. loadXmlFromNetwork() yöntemi kanalı alır ve işler. İşlemeyi tamamladığında ortaya çıkan dizeyi geri iletir.
  • onPostExecute() döndürülen dizeyi alır ve kullanıcı arayüzünde görüntüler.
// stackoverflow.com'dan XML beslemesini indirmek için kullanılan AsyncTask'ın uygulanması. özel sınıf DownloadXmlTask ​​​​AsyncTask'ı genişletir ( @Override protected String doInBackground(String... urls) ( try ( return loadXmlFromNetwork(urls); ) catch (IOException e) ( return getResources().getString(R.string.connection_error); ) catch (XmlPullParserException e) ( return getResources().getString(R.string.xml_error); ) @Override protected void onPostExecute(String result) ( setContentView(R.layout.main); // HTML dizesini WebView WebView aracılığıyla kullanıcı arayüzünde görüntüler myWebView = ( WebView) findViewById(R.id.webview) myWebView.loadData(sonuç, "text/html", null)) )

Aşağıda DownloadXmlTask'tan çağrılan loadXmlFromNetwork() yöntemi bulunmaktadır. Aşağıdakileri yapar:

  1. StackOverflowXmlParser'ın bir örneğini oluşturur. Ayrıca, bu alanlar için XML akışından çıkarılan değerleri depolamak amacıyla Liste Girişi nesneleri ve başlık, url ve özet için değişkenler oluşturur.
  2. Kanalı indiren ve onu bir OutputStream olarak döndüren downloadUrl() öğesini çağırır.
  3. Bir OutputStream'i ayrıştırmak için StackOverflowXmlParser'ı kullanır. StackOverflowXmlParser, Liste girişlerini akıştaki verilerle doldurur.
  4. Liste girişlerini işler ve kanal verilerini HTML işaretlemesiyle birleştirir.
  5. onPostExecute() yöntemindeki ana aktivite olan AsyncTask'ın kullanıcı arayüzünde görüntülenen HTML dizesini döndürür.
// XML'i stackoverflow.com'dan yükler, ayrıştırır ve // ​​HTML işaretlemesiyle birleştirir. HTML dizesini döndürür. Private String loadXmlFromNetwork(String urlString) XmlPullParserException, IOException'ı atar (InputStream akışı = null; // Ayrıştırıcıyı örneklendirin StackOverflowXmlParser stackOverflowXmlParser = new StackOverflowXmlParser(); List girişler = boş; Dize başlığı = boş; Dize URL'si = boş; Dize özeti = null; Takvim rightNow = Calendar.getInstance(); DateFormat formatlayıcı = new SimpleDateFormat("MMM dd h:mmaa"); // Kullanıcının tercihi özet metni içerecek şekilde ayarlayıp ayarlamadığını kontrol eder SharedPreferences paylaşılanPrefs = PreferenceManager.getDefaultSharedPreferences(this); boolean pref = paylaşılanPrefs.getBoolean("summaryPref", false); StringBuilder htmlString = new StringBuilder(); htmlString.append("

" + getResources().getString(R.string.page_title) + "

"); htmlString.append(" " + getResources().getString(R.string.updated) + " " + formatter.format(rightNow.getTime()) + ""); try (stream = downloadUrl(urlString); entrys = stackOverflowXmlParser.parse(stream); // Uygulamanın kullanımı tamamlandıktan sonra // GirişStream'in kapatıldığından emin olur. ) son olarak ( if (stream != null) (stream.close(); ) // StackOverflowXmlParser, Entry nesnelerinin bir Listesini ("girişler" olarak adlandırılır) döndürür. // Her Entry nesnesi, XML akışındaki tek bir gönderiyi temsil eder. // Bu bölüm, her birini birleştirmek için giriş listesini işler. HTML işaretli giriş. // Her giriş, kullanıcı arayüzünde isteğe bağlı olarak (Giriş girişi: girişler) için bir metin özeti içeren bir bağlantı olarak görüntülenir ( htmlString.append("

" + giriş.başlık + "

"); // Kullanıcı tercihini özet metni içerecek şekilde ayarlarsa, // bunu ekrana ekler. if (pref) ( htmlString.append(entry.summary); ) ) return htmlString.toString(); ) // Bir URL'nin dize temsili verildiğinde, bir bağlantı kurar ve // ​​bir giriş akışı alır. ; conn.setReadTimeout(10000 /* milisaniye */); conn.setConnectTimeout(15000 /* milisaniye */); conn.setRequestMethod("GET"); conn.connect( ); );
Yazarı: Arseny Kapoulkine
Yayın tarihi: 21 Eylül 2012
Tercüme: A. Panin
Çeviri tarihi: 10 Kasım 2013

giriiş

XML, hiyerarşik olarak yapılandırılmış belgeleri insan tarafından okunabilir bir metin biçiminde kodlamak için bir dizi kural belirleyen standartlaştırılmış bir biçimlendirme dilidir. XML standardı yaygınlaştı ve hem çok kompakt basit belgeler (SOAP istekleri gibi) hem de karmaşık veri bağımlılıklarına (COLLADA) sahip çok gigabaytlık belgeler (OpenStreetMap projesi tarafından kullanılan) oluşturmak için kullanıldı. XML belgelerini işlemek için kullanıcılar genellikle özel bir kitaplığa ihtiyaç duyar: belgeyi metinsel bir belgeden dahili bir temsile dönüştüren bir XML belge ayrıştırıcısı uygulamalıdır. XML standardı, ayrıştırma hızı, kullanıcı tarafından okunabilirlik ve ayrıştırılacak kodun karmaşıklığı açısından bir ödündür; bu nedenle, XML belgelerini ayrıştırmak için hızlı bir sisteme sahip olmak, uygulama verilerini depolamak için format olarak tercih edilen XML seçimini etkileyebilir.

Bu bölümde, açıklanan ayrıştırma sisteminin performansını artırmayı amaçlayan ve yazarın C++ programlama dili olan pugixml'yi kullanarak son derece verimli bir ayrıştırma sistemi geliştirmesine olanak tanıyan çeşitli teknikler açıklanmaktadır. Bu teknikler XML belge ayrıştırma sistemi için kullanılmış olsa da, bunların çoğu diğer formatlardaki belge ayrıştırma sistemlerine ve hatta tamamen alakasız yazılım bileşenlerine bile uygulanabilir (örneğin, bellek yönetimi algoritmaları ilgisiz metin belgesi ayrıştırma sistemleri alanlarında yaygın olarak kullanılmaktadır). .

XML belgelerini ayrıştırmaya yönelik oldukça farklı yaklaşımlar bulunduğundan ve ayrıştırma sisteminin, XML belgeleriyle deneyimli olanların bile farkında olmadığı ek adımları gerçekleştirmesi gerektiğinden, uygulama ayrıntılarına girmeden önce ilk olarak eldeki görevi tanımlamak önemlidir.

XML ayrıştırma sistemlerinin modelleri

XML ayrıştırma sistemlerinin farklı modellerinin her biri belirli durumlarda en uygunudur ve bu modellerin her birinin kendi performans ve bellek tüketimi parametreleri vardır. Aşağıdaki modeller en yaygın kullanılanlardır:

  • SAX tabanlı ayrıştırma sistemlerini (XML için Basit API) kullanırken, kullanıcıya girdi olarak belge veri akışını bekleyen ve "açık etiket", "kapanış etiketi", "karakter" gibi çeşitli geri çağırma işlevleri sağlayan bir yazılım bileşeni sunulur. iç etiketi". Ayrıştırma sistemi, belge verilerini işlerken geri arama işlevlerini kullanır. Ayrıştırma için gereken bağlam, mevcut öğenin ağacının derinliği ile sınırlıdır; bu da bellek gereksinimlerinde önemli bir azalma anlamına gelir. Bu tür ayrıştırma sistemi, aynı anda belgenin yalnızca bir kısmı mevcut olduğunda akış halindeki belgeleri işlemek için kullanılabilir.
  • Çekme ayrıştırma, sürecin kendisi açısından SAX tabanlı ayrıştırmaya benzer; her seferinde bir belge öğesi işlenir, ancak ayrıştırma sürecini yönetme yöntemi değişir: SAX tabanlı bir ayrıştırma sisteminde, ayrıştırma işlemi, sistemin kendisi geri çağırma işlevlerini kullanırken, çekme ayrıştırma sırasında kullanıcı yineleyici benzeri bir nesne kullanarak ayrıştırma sürecini kontrol eder.
  • DOM tabanlı ayrıştırma sistemlerini (Belge Nesne Modeli) kullanırken, kullanıcı ayrıştırma sistemine tam bir belgeyi arabellek veya metin verileri akışı biçiminde iletir; ayrıştırma sistemi buna dayanarak tüm belgenin bellek içi nesne temsilini oluşturur. her biri belirli bir XML öğesi veya özniteliği için ayrı nesneler ve bir dizi geçerli işlem (örneğin, "bu düğümün tüm alt öğelerini al") kullanan belge öğeleri ağacı. Pugxml kütüphanesi bu modeli kullanır.

Ayrıştırma sistemi modelinin seçimi genellikle belgenin boyutuna ve yapısına bağlıdır. Pugixml DOM'a dayalı olarak ayrıştırdığı için şu belgeler için etkilidir:

  • hafızaya tam olarak sığabilecek kadar küçük olmaları,
  • Geçilmesi gereken düğümler arasındaki bağlantılardan oluşan karmaşık bir yapıya sahip olması veya
  • karmaşık belge dönüşümleri gerektirir.

Pugixml'de mimari çözümler

Pugixml Kütüphanesinin geliştirilmesi sırasında, hızlı ve hafif SAX tabanlı ayrıştırma sistemleri (Expat gibi) mevcut olmasına rağmen, tüm DOM tabanlı XML ayrıştırma sistemleri üretim için mevcut olduğundan, çoğunlukla DOM temsilini oluşturma sorununa odaklanıldı. pugixml oluşturulduğu sırada (2006) kullanılanlar ya çok hafif değildi ya da çok hızlı değildi. Buna dayanarak, puixml geliştirme sürecinin ana hedefi, XML belgelerinin DOM tabanlı manipülasyonunu gerçekleştirmek için çok hızlı, hafif bir kütüphane oluşturmaktır.


Bu makalenin yayınlanmasına yalnızca makalenin yazarının web sitesine bir bağlantı ile izin verilir

Bu yazıda büyük bir XML dosyasının nasıl ayrıştırılacağına dair bir örnek göstereceğim. Sunucunuz (barındırma) komut dosyasının çalışma süresinin artırılmasını yasaklamıyorsa, en az gigabayt ağırlığındaki bir XML dosyasını ayrıştırabilirsiniz; ben şahsen yalnızca 450 megabayt ağırlığındaki ozondan ayrıştırdım.

Büyük XML dosyalarını ayrıştırırken iki sorun ortaya çıkar:
1. Yeterli hafıza yok.
2. Komut dosyasının çalışması için yeterli ayrılan süre yok.

Zamanla ilgili ikinci sorun, sunucu bunu yasaklamazsa çözülebilir.
Ancak bellek sorununu çözmek zordur, kendi sunucunuzdan bahsetsek bile, 500 megabaytlık dosyaları taşımak çok kolay değildir ve barındırma ve VDS'de belleği artırmak kesinlikle mümkün değildir.

PHP'nin çeşitli yerleşik XML işleme seçenekleri vardır - SimpleXML, DOM, SAX.
Bu seçeneklerin tümü birçok makalede örneklerle ayrıntılı olarak açıklanmaktadır, ancak tüm örnekler tam bir XML belgesiyle çalışmayı göstermektedir.

İşte bir örnek: XML dosyasından bir nesne almak

Artık bu nesneyi işleyebilirsiniz, AMA...
Gördüğünüz gibi XML dosyasının tamamı belleğe okunur, ardından her şey bir nesneye ayrıştırılır.
Yani, tüm veriler belleğe gider ve yeterli miktarda ayrılmış bellek yoksa komut dosyası durur.

Bu seçenek büyük dosyaların işlenmesi için uygun değildir; dosyayı satır satır okumanız ve bu verileri tek tek işlemeniz gerekir.
Bu durumda, veriler işlenirken geçerlilik kontrolü de gerçekleştirilir; bu nedenle, örneğin geçersiz bir XML dosyası olması durumunda veritabanına girilen tüm verileri geri alabilmeniz veya iki geçiş gerçekleştirebilmeniz gerekir. dosya aracılığıyla önce geçerlilik için okuyun, ardından verileri işlemek için okuyun.

Büyük bir XML dosyasını ayrıştırmanın teorik bir örneğini burada bulabilirsiniz.
Bu komut dosyası bir dosyadan her defasında bir karakter okur, bu verileri bloklar halinde toplar ve XML ayrıştırıcısına gönderir.
Bu yaklaşım hafıza sorununu tamamen çözer ve yük oluşturmaz ancak zamanla sorunu ağırlaştırır. Sorunu zaman içinde nasıl çözmeye çalışılır, aşağıyı okuyun.

webi_xml ($dosya) işlevi
{

########
### veri işlevi

{
$veriyi yazdır;
}
############################################



{
$adını yazdır;
print_r($attrs);
}


## kapanış etiketi işlevi
function endElement ($çözümleyici, $isim)
{
$adını yazdır;
}
############################################

($xml_parser, "veri");

// dosyayı aç
$fp = fopen($dosya, "r");

$perviy_vxod = 1; $veri = "";



{

$simvol = fgetc ($fp); $veri .= $simvol ;


if($simvol != ">" ) ( devam et;)


Eko "

kırmak;
}

$veri = "";
}
fclose($fp);

Webi_xml("1.xml");

?>

Bu örnekte, her şeyi tek bir webi_xml() fonksiyonuna koydum ve en altta onun çağrısını görebilirsiniz.
Komut dosyasının kendisi üç ana işlevden oluşur:
1. startElement() etiketinin açılışını yakalayan bir işlev
2. Kapanış endElement() etiketini yakalayan bir işlev
3. Ve alma işlevi veri() .

1.xml dosyasının içeriğinin bir tarif olduğunu varsayalım.



< title >Basit ekmek
< ingredient amount = "3" unit = "стакан" >Un
< ingredient amount = "0.25" unit = "грамм" >Maya
< ingredient amount = "1.5" unit = "стакан" >Ilık su
< ingredient amount = "1" unit = "чайная ложка" >Tuz
< instructions >
< step > Tüm malzemeleri karıştırıp iyice yoğurun.
< step > Üzerini bir bezle örtüp sıcak bir odada bir saat bekletin..
< step > Tekrar yoğurun, fırın tepsisine yerleştirip fırına verin.
< step > Site sitesini ziyaret edin


Bir meydan okumayla başlıyoruz genel fonksiyon webi_xml("1.xml");
Daha sonra ayrıştırıcı bu fonksiyonda başlar ve tüm etiket adlarını büyük harfe dönüştürür, böylece tüm etiketler aynı harfe sahip olur.

$xml_parser = xml_parser_create();
xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, true);

Artık bir etiketin açılmasını, kapatılmasını ve verinin işlenmesini yakalamak için hangi fonksiyonların çalışacağını belirtiyoruz

xml_set_element_handler($xml_parser, "startElement", "endElement");
xml_set_character_data_handler($xml_parser, "veri");

Daha sonra belirtilen dosyanın açılması gelir; dosyada her seferinde bir karakter yinelenir ve her karakter, karakter bulunana kadar dize değişkenine eklenir. > .
Eğer bu dosyaya ilk erişimse, dosyanın başlangıcında gereksiz olan her şey, daha önce gelen her şey silinecektir. , bu XML'in başlaması gereken etikettir.
İlk kez bir string değişkeni bir string içerecek

Ve onu sökücüye gönder
xml_parse ($xml_parser, $veri, feof ($fp));
Veriler işlendikten sonra string değişkeni sıfırlanır ve verilerin bir string halinde toplanması yeniden başlar ve string ikinci kez oluşturulur.

Üçüncüde
</b><br>dördüncüde <br><b>Basit ekmek

Lütfen bir dize değişkeninin her zaman tamamlanmış bir etiketten oluşturulduğunu unutmayın. > ve hırsıza veri içeren açık ve kapalı bir etiket göndermeye gerek yoktur, örneğin
Basit ekmek
Bu işleyicinin kesintisiz bir etiket, en az bir açık etiket ve bir sonraki adımda kapalı bir etiket alması veya bir dosyanın hemen 1000 satırını alması önemlidir, önemli değil, asıl mesele etiketin olmasıdır. örneğin kırılmaz

le>Sade ekmek
Bu şekilde etiket yırtıldığı için işleyiciye veri göndermek imkansızdır.
İşleyiciye veri göndermek için kendi yönteminizi geliştirebilirsiniz, örneğin 1 megabayt veri toplayıp hızı artırmak için işleyiciye gönderebilirsiniz, yalnızca etiketlerin her zaman tamamlandığından ve verilerin yırtılabileceğinden emin olun.
Basit</b><br><b>ekmek

Böylece dilediğiniz gibi parçalar halinde gönderebilirsiniz. büyük dosya işleyiciye.

Şimdi bu verilerin nasıl işlendiğine ve nasıl elde edileceğine bakalım.

Açılış etiketleri fonksiyonuyla başlayalım startElement ($ayrıştırıcı, $isim, $attrs)
İşlemin çizgiye ulaştığını varsayalım
< ingredient amount = "3" unit = "стакан" >Un
O zaman fonksiyonun içinde $isim değişkeni şuna eşit olacaktır: bileşen yani açık etiketin adı (henüz etiketin kapatılmasına gelinmedi).
Ayrıca bu durumda, bu $attrs etiketinin bir dizi özelliği mevcut olacak ve bu, veri içerecektir miktar = "3" ve birim = "bardak".

Bundan sonra açık etiketin verileri fonksiyon tarafından işlendi. veri ($çözümleyici, $veri)
$data değişkeni açılış ve kapanış etiketleri arasındaki her şeyi içerecektir, bizim durumumuzda bu Muka metnidir

Ve stringimizin fonksiyon tarafından işlenmesi sona eriyor endElement ($çözümleyici, $isim)
Bu kapalı etiketin adıdır, bizim durumumuzda $name şuna eşit olacaktır: bileşen

Ve bundan sonra her şey yeniden yoluna girdi.

Yukarıdaki örnek yalnızca XML işleme ilkesini göstermektedir, ancak gerçek uygulama için bunun değiştirilmesi gerekir.
Tipik olarak, veri tabanına veri girmek için büyük XML'i ayrıştırmanız ve verileri doğru şekilde işlemeniz için, verilerin hangi açık etikete ait olduğunu, hangi düzeyde etiket iç içe geçtiğini ve yukarıdaki hiyerarşide hangi etiketlerin açık olduğunu bilmeniz gerekir. Bu bilgilerle dosyayı sorunsuz bir şekilde işleyebilirsiniz.
Bunu yapmak için açık etiketler, iç içe yerleştirme ve veriler hakkında bilgi toplayacak çeşitli global değişkenleri tanıtmanız gerekir.
İşte kullanabileceğiniz bir örnek

webi_xml ($dosya) işlevi
{
küresel $webi_derinlik ; // yuvalama derinliğini izlemek için sayaç
$webi_derinlik = 0;
genel $webi_tag_open ; // bir açık dizi içerecek şu an Etiketler
$webi_tag_open = dizi();
genel $webi_data_temp ; // bu dizi bir etiketin verilerini içerecektir

####################################################
### veri işlevi
işlev verileri ($çözümleyici, $veri)
{
küresel $webi_derinlik ;
genel $webi_tag_open ;
genel $webi_data_temp ;
// diziye iç içe geçmeyi ve şu anda açık olan etiketi gösteren verileri ekleyin
$webi_data_temp [ $webi_derinlik ][ $webi_tag_open [ $webi_derinlik ]][ "veri" ]= $veri ;
}
############################################

####################################################
### açılış etiketi işlevi
function startElement ($çözümleyici, $isim, $attrs)
{
küresel $webi_derinlik ;
genel $webi_tag_open ;
genel $webi_data_temp ;

// eğer iç içe geçme seviyesi artık sıfır değilse, o zaman bir etiket zaten açık demektir
// ve ondan gelen veriler zaten dizide bulunuyor, onu işleyebilirsiniz
if ($webi_length)
{




" ;

Yazdır "
" ;
print_r($webi_tag_open); // açık etiket dizisi
Yazdır "


" ;

// verileri işledikten sonra hafızada yer açmak için silin
unset($GLOBALS [ "webi_data_temp" ][ $webi_derinlik ]);
}

// şimdi bir sonraki etiket açıldı ve bir sonraki adımda daha fazla işlem gerçekleştirilecek
$webi_derinlik++; // iç içe geçmeyi arttır

$webi_tag_open [ $webi_derinlik ]= $isim ; // bilgi dizisine açık bir etiket ekleyin
$webi_data_temp [ $webi_derinlik ][ $isim ][ "öznitelikler" ]= $öznitelikler ; // şimdi etiket niteliklerini ekliyoruz

}
###############################################

#################################################
## kapanış etiketi işlevi
function endElement ($çözümleyici, $isim) (
küresel $webi_derinlik ;
genel $webi_tag_open ;
genel $webi_data_temp ;

// veri işleme burada başlar; örneğin veritabanına ekleme, dosyaya kaydetme vb.
// $webi_tag_open, iç içe geçme düzeyine göre bir açık etiketler zinciri içerir
// örneğin $webi_tag_open[$webi_length] şu anda bilgileri işlenmekte olan açık etiketin adını içerir
// $webi_length etiketinin iç içe geçme düzeyi
// $webi_data_temp[$webi_third][$webi_tag_open[$webi_length]]["attrs"] etiket nitelikleri dizisi
// $webi_data_temp[$webi_third][$webi_tag_open[$webi_length]]["data"] etiket verileri

"Veri" yazdırın. $webi_tag_open [ $webi_derinlik ]. "--" .($webi_data_temp [ $webi_derinlik ][ $webi_tag_open [ $webi_derinlik ]][ "veri" ]). "
" ;
print_r ($webi_data_temp [ $webi_derinlik ][ $webi_tag_open [ $webi_derinlik ]][ "öznitelikler" ]);
Yazdır "
" ;
print_r($webi_tag_open);
Yazdır "


" ;

Unset($GLOBALS [ "webi_data_temp" ]); // veriyi işledikten sonra etiket kapalı olduğundan dizinin tamamını veriyle birlikte sileriz
unset($GLOBALS [ "webi_tag_open" ][ $webi_derinlik ]); // kapandığından beri bu açık etiketle ilgili bilgileri sil

$webi_derinlik --; // iç içe geçmeyi azalt
}
############################################

$xml_parser = xml_parser_create();
xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, true);

// etiketleri açarken ve kapatırken hangi fonksiyonların çalışacağını belirtin
xml_set_element_handler($xml_parser, "startElement", "endElement");

// verilerle çalışmak için bir işlev belirtin
xml_set_character_data_handler($xml_parser, "veri");

// dosyayı aç
$fp = fopen($dosya, "r");

$perviy_vxod = 1; // dosyaya ilk girişi kontrol etmek için işaretle
$veri = ""; // burada dosyadan verileri parçalar halinde toplayıp xml ayrıştırıcıya gönderiyoruz

// dosyanın sonuna kadar döngü
while (! feof ($fp ) ve $fp )
{
$simvol = fgetc ($fp); // dosyadan bir karakter oku
$veri .= $simvol ; // gönderilecek veriye bu karakteri ekliyoruz

// karakter bitiş etiketi değilse, döngünün başına dönün ve verilere başka bir karakter ekleyin ve bitiş etiketi bulunana kadar bu şekilde devam edin
if($simvol != ">" ) ( devam et;)
// eğer kapanış etiketi bulunursa, şimdi toplanan bu verileri işlenmek üzere göndereceğiz

// bunun dosyaya ilk giriş olup olmadığını kontrol edin, ardından etiketten önceki her şeyi sileceğiz// bazen XML'in başlangıcından önce çöplerle karşılaşabileceğiniz için (beceriksiz editörler veya dosya başka bir sunucudaki bir komut dosyası tarafından alınmış)
if($perviy_vxod ) ( $veri = strstr ($veri , "

// şimdi verileri xml ayrıştırıcısına atıyoruz
if (! xml_parse ($xml_parser, $data, feof ($fp)))) (

// burada geçerlilik hatalarını işleyebilir ve alabilirsiniz...
// bir hatayla karşılaşıldığında ayrıştırma durdurulur
Eko "
XML Hatası: " . xml_error_string(xml_get_error_code($xml_parser));
echo "satırda". xml_get_current_line_number ($xml_parser);
kırmak;
}

// ayrıştırma sonrasında, döngünün bir sonraki adımı için toplanan verileri atın.
$veri = "";
}
fclose($fp);
xml_parser_free($xml_parser);
// global değişkenleri kaldırıyoruz
unset($GLOBALS [ "webi_derinlik" ]);
unset($GLOBALS [ "webi_tag_open" ]);
unset($GLOBALS [ "webi_data_temp" ]);

Webi_xml("1.xml");

?>

Örneğin tamamına yorumlar eşlik ediyor; şimdi test edin ve deneyin.
Verilerle çalışma işlevinde, verilerin yalnızca bir diziye eklenmediğini, bunun yerine " kullanılarak eklendiğini lütfen unutmayın. .=" çünkü veriler bütün olarak gelmeyebilir ve sadece bir atama yaparsanız, zaman zaman verileri parçalar halinde alırsınız.

Hepsi bu kadar, artık herhangi bir boyuttaki bir dosyayı işlerken yeterli bellek var, ancak komut dosyasının çalışma süresi çeşitli şekillerde artırılabilir.
Betiğin başına bir işlev ekleyin
set_time_limit(6000);
veya
ini_set ("max_execution_time", "6000");

Veya .htaccess dosyasına metin ekleyin
php_value max_execution_time 6000

Bu örnekler betiğin çalışma süresini 6000 saniyeye çıkaracaktır.
Süreyi bu şekilde ancak güvenli mod kapalıyken artırabilirsiniz.

Eğer php.ini'yi düzenleme erişiminiz varsa, kullanarak süreyi artırabilirsiniz.
max_execution_time = 6000

Örneğin, Masterhost barındırmada, bu makalenin yazıldığı sırada, komut dosyası süresinin artırılması yasaktır. güvenli mod, ancak eğer profesyonelseniz, masterhost üzerinde kendi php derlemenizi oluşturabilirsiniz, ancak bu makalede bu yok.