Парсинг XML в NSDictionary при помощи libxml

    В проекте для iPhone столкнулся с необходимостью парсинга большого количества xml ответов от сервера. Хотел бы поделиться своим решением парсинга XML в NSDictionary.

    XML вида (должен присутствовать какой-то корневой элемент, в примере result):
    <result success="true">
    <item>Value</item>
    <item attr="val" />
    <item code="item">value</item>
    </result>

    будет преобразован в:
    NSDictionary {
        "name" => "result",
        "attr" => NSDictionary {
            "success" => "true"
        },
        "child" => NSArray {
            0 => NSDictionary {
                "name" => "item",
                "value" => "value"
            },
            1 => NSDictionary {
                "name" => "item",
                "attr" => NSDictionary {
                    "code" => "item"
                }
            },
            2 => NSDictionary {
                "name" => "item",
                "attr" => NSDictionary {
                    "attr" => "val"
                },
                "value" =>  "value"
        }
    }
    


    Собственно сами методы:
    /* преобразование  xml в массив */
    - (NSDictionary *)xmlToDict {
        NSDictionary *resultDict = [NSDictionary dictionary];
        if (self.content != nil) {
            xmlDocPtr doc = xmlParseMemory([self.content bytes], [self.content length]);
            if (!doc) {
                // сообщение об ошибке
                NSLog(@"error");
            }
            else {
                xmlNode *root = NULL;
                root = xmlDocGetRootElement(doc);
                resultDict = [NSDictionary dictionaryWithDictionary:[self getNodeInfo:root]];
                xmlFree(root);
            }
        }
        return resultDict;
    }
    
    /* информация об xml объекте, рекурсия */
    -(NSDictionary *)getNodeInfo:(xmlNode *)node {
        NSMutableDictionary *itemDict = [[[NSMutableDictionary alloc] initWithCapacity:1] autorelease];
        
        xmlChar *value = NULL;
        xmlAttr *attribute = NULL;
        
        if (node && node->name && ![[NSString stringWithCString:(char *)node->name encoding:NSUTF8StringEncoding] isEqualToString:@"text"]) {
            /* имя объекта */
            value = (xmlChar*)node->name;
            [itemDict setObject:[NSString stringWithCString:(char *)value encoding:NSUTF8StringEncoding] forKey:@"name"];
            xmlFree(value);
        
            /* атрибуты объекта */
            attribute = node->properties;
            NSMutableDictionary *attrDict = [[NSMutableDictionary alloc] initWithCapacity:1];
            while(attribute && attribute->name && attribute->children)
            {
                value = xmlNodeListGetString(node->doc, attribute->children, 1);
                [attrDict setObject:[NSString stringWithCString:(char *)value encoding:NSUTF8StringEncoding]
                         forKey:[NSString stringWithCString:(char *)attribute->name encoding:NSUTF8StringEncoding]];
                xmlFree(value);
                attribute = attribute->next;
            }
            [itemDict setObject:attrDict forKey:@"attr"];
            [attrDict release];
        
            /* значение объекта */
            value = xmlNodeGetContent(node);
            [itemDict setObject:[NSString stringWithCString:(char*)value encoding:NSUTF8StringEncoding] forKey:@"value"];
            xmlFree(value);
            
            /* дочерние объекты */
            NSMutableArray *childArray = [[NSMutableArray alloc] initWithCapacity:1];
            xmlNode *child = NULL;
            for (child = node->children; child != NULL; child = child->next)
            {
                NSDictionary *childDict = [self getNodeInfo:child];
                if ([childDict count]) {
                    [childArray addObject:childDict];
                }
                
            }
            xmlFree(child);
            if ([childArray count])
                [itemDict setObject:childArray forKey:@"child"];
            [childArray release];
        }
        
        return (NSDictionary *)itemDict;
    }
    


    У меня в self.content хранится xml, полученный из интернета (откуда в принципе не важно) в NSData.
    Если нет корневого элемента в xml, то код немного придется переделать, каждый элемент парсить в NSDictionary и «складывать» в NSArray, но при такой структуре результата, мягко говоря, с результатом работать не удобно.
    Надеюсь, кому-то помог, а, возможно, от кого-то услышу критику и предложения.
    Есть проблема, libxml дает незначительные лики, скорее всего лики самой библиотеки.
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 10

      0
      Насколько альтернативных вариантов:

      Использовать аналог NSXMLDocument – KissXML

      Или через XSLT на сервере или iphone (последнее, несколько сложнее, но вполне решаемо) приводить XML к DTD PropertyList-1.0, и парсить штатными средствами.
        0
        +1 зы KissXML. Во-первых, его классы совместимы с маковскими NSXML… анлогами, что дает надежду на замену нативными средствами в будущем. А во-вторых, та самая структура результатов, о которой упоминул автор, дает возможность более удобной обработки. Так тривиальная и постоянно встречающаяся задача «вытащить все элементы item из result», решаемая вызовом единственного метода, при описанном в статье подход превращается у увлекаельное итерирование по всем элементам child, на сколько я понимаю.
          0
          предложите вариант при котором не потребуется итерированный проход по всем child?
          мне это видится в виде рекурсивного прохода по NSDictionary и метод в 5 строк, Вы что то другое можете предложить?
            0
            Навероное плохо выразился или недопонял суть статьи.

            Насколько я понял, Вами предлагается иметь на входе массив данных в формате XML, на выходе — словарь предложенной структуры. Я всего лишь заметил, что с таким выходом работать нелегко. Конечно под капотом потребуется итерация (хотя наверняка можно придумать, более быстрые варианты, чем обход всех детей в поисках нужных) по внутренним элементам, но суть моего предложения — поиметь удобные методы для работы, типа:

            [(DDXMLElement *) root elementsForName:@«item»];

            Всегда можно сказать: «надо всего лишь описать метод в 5 строк для выборки», а потом «дополнительная индексация прикручивается за 2 хода» и закончить «ничего не мешает объединить это все в единый класс». Я же говорю всего лишь о возможности сразу взять готовый набор классов и методов (с потенциальной возможностью выкинуть этот код безболезненно в будущих версиях SDK).
              0
              естественно, взять готовый класс всегда проще, но иногда не интересно, это раз.
              я показал свой вариант преобразования xml в словарь, а не класс, который позволить оперировать xml как только душе захочется, это два.
              по поводу удобства — субъективное мнение и, опять же, как вы заметили, все дописывается под нужды, у меня нужды искать все item в result нет. еще раз повторюсь, я не стремился показать готовый класс для работы с xml.
              и с чего такая уверенность, что в будущих версиях сдк не будет libxml?
                0
                Ну, значит я изначально не ошибся и Вы позиционируете данное решние как готовое.
                Тогда первый комментарий в силе, и я продолжаю утверждать, что предложенным на выход форматом пользоваться неудобно (ага, это мое субъективное мнение, но я был бы рад услышать о перпендикулярных критериях удобства). Я уж не знаю какие нужды у Вас, если даже искать все item в result нет подтребности, но обычно хотя бы такие вызовы бывают нужны (наряду с получить значение аттрибута в виде строки/числа/boolean).

                Общие замечания:
                — Вы же хотели «поделиться своим решением парсинга XML», а не научить писать самому ради интереса, правильно?
                — Обычно «парсинг» предполагает более-менее универсальную форму, подходящую под множество прикладных задач. Писать отдельный парсер под каждую микрозадачу — это наверное «интересно» и уж точно «не проще», но и цель не ясна ;)

                P.S. Последнего предложения я не понял вовсе, потому что а) обычно стараются использовать наиболее высокоуровневые возможности (т.е. классы Objective-C в данном случае) и б) насколько я понимаю, libxml и так доступен, и всегда таковым был.
                  0
                  начинается бесполезный спор…
                  ни где по тексту у меня нет упоминания что это «готовое универсальное решение для работы с XML», это всего лишь способ «перевести» XML в NSDictionary
                  «обычно стараются использовать наиболее высокоуровневые возможности» — в статье указаны два метода класса, который у меня является «высокоуровневой» оберткой, как то так, в результате я работаю с классом obj-c, а не напрямую с библиотекой libxml, помоему тут все в порядке.
                  «Я же говорю всего лишь о возможности сразу взять готовый набор классов и методов (с потенциальной возможностью выкинуть этот код безболезненно в будущих версиях SDK). » — вот это я видимо не совсем понял, поэтому и вы не поняли мое последнее предложение
        0
        А вот такой вопрос есть. Если стримить XML из интернета и парсить на лету, при помощи стандартного SAX-парсера (что адекватнее, так как меньше памяти используется), то у того же стандартного парсера есть проблема с кодировками (он всегда предполагает utf-8 кажется, вне зависимости от того, что прописано в начале файла). Какое-то решение есть?
          0
          сейчас не за маком и налету парсить не пробовал
          насколько помнится вопрос с кодировкой решал при помощи такого костыля: перегонял nsdata в строку с указанием кодировки, в которой xml, потом перегонял из строки в nsdata
          что то типа этого:
          NSString *str = [[NSString alloc] initWithData:winData encoding:NSWindowsCP1251StringEncoding];
          NSData *utfData = [st dataUsingEncoding:NSUTF8StringEncoding];
          0
          А что NSXMLParser уже отменили? Это тоже неплохой класс для разбора XML и не должен генерить ликов.

          Only users with full accounts can post comments. Log in, please.