1 Haziran 2016 Çarşamba

Ölü XML den Diri XML çıkarmak :)

VitrinGez'de yoğun bir şekilde XML işliyoruz. Ancak firmalardan çoğu zaman düzgün çalışan XML alamıyoruz. Koskoca firmaya XML'i düzeltir misin de diyemiyorsunuz!

Biz bazı firmalardan XML alamayınca siteyi crawl ediyoruz. Bunun içinde scrapy kullanıyoruz. Scrapy kullanırken bozuk XML lerde dahi çalıştığını farkettim. O zaman kaynak kodu inceledim.

https://github.com/scrapy/scrapy/blob/master/scrapy/utils/iterators.py#L18

def xmliter(obj, nodename):
    """Return a iterator of Selector's over all nodes of a XML document,
       given the name of the node to iterate. Useful for parsing XML feeds.
    obj can be:
    - a Response object
    - a unicode string
    - a string encoded as utf-8
    """
    nodename_patt = re.escape(nodename)

    HEADER_START_RE = re.compile(r'^(.*?)<\s*%s(?:\s|>)' % nodename_patt, re.S)
    HEADER_END_RE = re.compile(r'<\s*/%s\s*>' % nodename_patt, re.S)
    text = _body_or_str(obj)

    header_start = re.search(HEADER_START_RE, text)
    header_start = header_start.group(1).strip() if header_start else ''
    header_end = re_rsearch(HEADER_END_RE, text)
    header_end = text[header_end[1]:].strip() if header_end else ''

    r = re.compile(r'<%(np)s[\s>].*?</%(np)s>' % {'np': nodename_patt}, re.DOTALL)
    for match in r.finditer(text):
        nodetext = header_start + match.group() + header_end
        yield Selector(text=nodetext, type='xml').xpath('//' + nodename)[0]
Yukarıda gördüğünüz üzere yapılan iş aslında çok basit. Önce xml'i küçük parçalara ayıyor. Sonra her birini bağımsız birer xml elemanı gibi kontrol ediyor. Mesela aşağıdaki gibi bir XML yapımız olsun. Dikkat ederseniz ID 5 olan kısımda etiketi bozuk oluşturdum. Büyük bir XML dosyamız olsa küçücük bir kısım yüzünden bütün XML çöp oluyor. Kodları gist'e koydum. Buradan inceleyebilirsiniz. https://gist.github.com/volkan/0d3af41be13a628201b1cee82246d095

$xml = '

';

echo fixXML($xml, 'product');

function fixXML($xml, $nodeName) {

        $patternTag = sprintf('/<%s[\s>].*?<\/%s>/s', $nodeName, $nodeName);
        preg_match_all($patternTag, $xml, $matches);

        $start = '';
        $end = '';
        $list = [];
        $wrongItem = [];
        $doc = new \DOMDocument();
        foreach ($matches[0] as $key => $item) {
            try {
                $tmpXML = $start . $item . $end;
                $status = @$doc->loadXML($tmpXML);
                if ($status == false) {
                 throw new \Exception;
                }
                $list[] = $item;
            } catch (\Exception $e) {
                $wrongItem[] = $item;
            }
        }

        $xml = $start . implode("\n", $list) . $end;

        return $xml;
}

Hiç yorum yok: