Analise um XML enorme (3Gb) no Dart / Flutter

Aug 15 2020

Tenho um site do qual estou recebendo um arquivo XML enorme. Eu preciso analisá-lo em 2 classes no Dart.

XML (parte dele):

<fb-updates xmlns:l="https://www.w3.org/1999/xlink" timestamp="2018-04-19 11:33:14">
    <updated-book id="32498526" created="2018-04-18 16:35:49" last_release="2018-04-18 16:35:49" updated="2018-04-18 16:35:49" valid_till="2018-12-31" valid_from="2013-01-01" size="579411" sent_by_name="[email protected]" sent_by_id="9351951" must_import="0" options="0" price="0.90" you_can_sell="0" allow_read="0" allow_sell="0" has_trial="0" allow_full_free="0" public_domain="0" show_card="0" contract_author="9339265" contract_title="АСТ" subject_id="44580" external_id="b4854f32-430a-11e8-9a05-0cc47a52085c" cover="jpg" type="0" adult="16" file_parts="6" available="-1" available_date="2018-04-18 16:18:02" copyright_read_online="1" contract_ends="2018-12-31" udk="821.111-312.4" art_cover="jpg" lvl="2" sell_open="0" can_preorder="0" litex="1" chars="46369" images="1" drm="0" publisher="АСТ" date_written_s="1969" date_written_d="1969-01-01" lang="ru" src_lang="en" cover_h="1960" cover_w="1400" art_cover_w="1400" art_cover_h="1960" lang3="rus" src_lang3="eng" status="approved" rating="6030" url="" inapp_price="0.90">
        <files>
            <file size="579409" type="fb2.zip"/>
            <file size="51846" type="html"/>
            <file size="583471" type="html.zip"/>
            <file size="47604" type="txt"/>
            <file size="20832" type="txt.zip"/>
            <file size="623059" type="rtf.zip"/>
            <file size="663336" type="a4.pdf"/>
            <file size="708752" type="a6.pdf"/>
            <file size="447161" type="mobi.prc"/>
            <file size="1474324" type="epub"/>
            <file size="590189" type="ios.epub"/>
            <file size="578047" type="fb3"/>
        </files>
        <book-title title="Наследство Боксдейла"/>
        <annotation>
            <p>
            «– Видишь ли, мой дорогой Адам, – мягко объяснял каноник, прохаживаясь с главным суперинтендентом Дэлглишем под вязами возле своего пасторского дома, – как бы нам ни было кстати это наследство, оно не принесет мне радости, если моя приемная бабушка Элли получила в свое время эти деньги недостойным способом.
            </p>
            <p>
            Каноник имел в виду, что они с женой не смогут воспользоваться пятьюдесятью тысячами фунтов, оставленными им его приемной бабушкой Элли, если шестьдесят семь лет назад она отравила своего престарелого мужа мышьяком, чтобы получить их. Поскольку в 1902 году это обвинение было снято с тетушки Элли судом, который, по мнению ее хемпширских соседей, в качестве публичного зрелища мог состязаться с церемонией коронации, щепетильность каноника казалась не совсем уместной…»
            </p>
        </annotation>
        <authors>
            <author id="dc3b5610-2a80-102a-9ae1-2dfe723fe7c7">
                <subject_id>44580</subject_id>
                <url>fillis-dzheyms/</url>
                <first-name>Филлис Дороти</first-name>
                <middle-name/>
                <last-name>Джеймс</last-name>
                <full-name-rodit>Филлис Дороти Джеймс</full-name-rodit>
                <lvl>2</lvl>
                <relation>0</relation>
            </author>
            <author id="e4f1d9b0-2a80-102a-9ae1-2dfe723fe7c7">
                <subject_id>44696</subject_id>
                <url>i-doronina/</url>
                <first-name>Ирина</first-name>
                <middle-name>Яковлевна</middle-name>
                <last-name>Доронина</last-name>
                <full-name-rodit>Ирины Дорониной</full-name-rodit>
                <lvl>1</lvl>
                <relation>1</relation>
            </author>
            <author id="c9a05514-1ce6-11e2-86b3-b737ee03444a">
                <subject_id>2835185</subject_id>
                <url>raznoe-4/</url>
                <first-name>Литагент</first-name>
                <middle-name/>
                <last-name>АСТ</last-name>
                <full-name-rodit/>
                <lvl>2</lvl>
                <relation>2</relation>
            </author>
        </authors>
        <genres>
            <genre title="зарубежные детективы" id="5219" bisac="FIC022000" master="1"/>
            <genre title="классические детективы" id="5261" bisac="FIC022000"/>
        </genres>
        <relations>
            <related uuid="BA8F3184-9049-4EAF-A47B-9D711D9135DC" relation="6" type="0"/>
        </relations>
        <copyrights>
            <copyright id="9339265" title="АСТ" percent="100.00"/>
        </copyrights>
        <livelib livelib_id="1001260109" rating="7.263" avg_mark="3.8246" count_readers="2271" widget_url="book/1100906/ratingbutton2015.png" widget_url2="book/1100906/ratingbuttonwhite.png" mark_1="2" mark_2="5" mark_3="48" mark_4="78" mark_5="885"/>
    </updated-book>
    <updated-book id="32523047" created="2018-04-19 09:14:23" last_release="2018-04-19 09:14:23" updated="2018-04-19 09:14:23" valid_till="2021-11-01" valid_from="2016-11-01" size="5653231" sent_by_name="sabanova" sent_by_id="9355626" must_import="0" options="68" price="5.99" you_can_sell="0" allow_read="0" allow_sell="0" has_trial="0" allow_full_free="0" public_domain="0" show_card="64" contract_author="9356032" contract_title="Эксмо" subject_id="44790" external_id="0a6e477f-4398-11e8-aa6b-0cc47a520474" cover="jpg" type="1" adult="0" file_parts="0" available="-1" available_date="2018-04-19 09:11:16" copyright_read_online="0" contract_ends="2021-02-12" art_cover="jpg" lvl="4" sell_open="0" can_preorder="0" litex="0" chars="4368" drm="0" publisher="" lang="ru" cover_h="1500" cover_w="1071" art_cover_w="1071" art_cover_h="1500" lang3="rus" status="approved" url="" inapp_price="5.99">
        <files>
            <group value="Ознакомительный фрагмент. MP3" group_id="1">
                <file id="37754271" size="5653231" filename="Sample.mp3" seconds="4368" mime_type="audio/mpeg" file_description="MP3"/>
            </group>
            <group value="Стандартное качество. MP3" group_id="5">
                <file id="37754255" size="5669432" filename="01.mp3" seconds="354" mime_type="audio/mpeg" file_description="MP3"/>
                <file id="37754231" size="14099251" filename="02.mp3" seconds="880" mime_type="audio/mpeg" file_description="MP3"/>
                <file id="37754263" size="11474467" filename="03.mp3" seconds="716" mime_type="audio/mpeg" file_description="MP3"/>
                <file id="37754215" size="5472573" filename="04.mp3" seconds="341" mime_type="audio/mpeg" file_description="MP3"/>
                <file id="37754239" size="24781451" filename="05.mp3" seconds="1548" mime_type="audio/mpeg" file_description="MP3"/>
                <file id="37754247" size="8470594" filename="06.mp3" seconds="529" mime_type="audio/mpeg" file_description="MP3"/>
            </group>
            <group value="Мобильная версия. MP4" group_id="19">
                <file id="37754223" size="31707503" filename="Sovetnik_Po_Kulture.m4b" seconds="4366" mime_type="audio/m4b" file_description="M4B-файл"/>
            </group>
        </files>
        <book-title title="Советник по культуре"/>
        <annotation>
            <p>
            Николай Стверцов прибыл на планету Ниона в качестве советника по культуре посольства Земной Федерации. Он должен сыграть важную роль в межгалактическом проекте «Восхождение». Проект призван помочь пяти расам Нионы достичь более высокого уровня развития. Но, узнав о страшной тайне изготовления шейота, который является главным предметом экспорта с отсталой планеты, Стверцов начинает сомневаться в правильности политики Земной Федерации на Нионе…
            </p>
        </annotation>
        <authors>
            <author id="ea92d3b4-2a80-102a-9ae1-2dfe723fe7c7">
                <subject_id>44790</subject_id>
                <url>aleksey-kalugin/</url>
                <first-name>Алексей</first-name>
                <middle-name>Александрович</middle-name>
                <last-name>Калугин</last-name>
                <full-name-rodit>Алексея Калугина</full-name-rodit>
                <lvl>4</lvl>
                <relation>0</relation>
                <exid>1-00000025829</exid>
            </author>
            <author id="556f2637-8bde-11e6-9c73-0cc47a1952f2">
                <subject_id>10117645</subject_id>
                <url>audioagent-litres-chtec-pablik/</url>
                <first-name>Аудиоагент</first-name>
                <middle-name/>
                <last-name>ЛитРес Чтец</last-name>
                <full-name-rodit>Аудиоагента ЛитРес Чтец</full-name-rodit>
                <lvl>1</lvl>
                <relation>2</relation>
            </author>
            <author id="a4d115a3-a4f2-11e6-a11d-0cc47a5203ba">
                <subject_id>10389074</subject_id>
                <url>raznoe-10389074/</url>
                <first-name>Аудиоагент</first-name>
                <middle-name/>
                <last-name>1 редакция-прямой договор</last-name>
                <full-name-rodit/>
                <lvl>1</lvl>
                <relation>2</relation>
            </author>
            <author id="f703f2a3-24cc-11e7-b088-0cc47a52085c">
                <subject_id>11119920</subject_id>
                <url>dumanskiy-andrey/</url>
                <first-name>Андрей</first-name>
                <middle-name/>
                <last-name>Думанский</last-name>
                <full-name-rodit>Думанского Андрея</full-name-rodit>
                <lvl>1</lvl>
                <relation>2</relation>
            </author>
            <author id="f703f2a3-24cc-11e7-b088-0cc47a52085c">
                <subject_id>11119920</subject_id>
                <url>dumanskiy-andrey/</url>
                <first-name>Андрей</first-name>
                <middle-name/>
                <last-name>Думанский</last-name>
                <full-name-rodit>Думанского Андрея</full-name-rodit>
                <lvl>1</lvl>
                <relation>6</relation>
            </author>
        </authors>
        <genres>
            <genre title="научная фантастика" id="5073" bisac="FIC028020"/>
            <genre title="социальная фантастика" id="5078" bisac="FIC028000"/>
        </genres>
        <relations>
            <related uuid="76316fbc-2c44-102b-839c-b3fddb510218" relation="8" type="0"/>
        </relations>
        <copyrights>
            <copyright id="9354189" title="ЛитРес: чтец" percent="26.00"/>
            <copyright id="9354794" title="Эксмо" percent="50.00"/>
            <copyright id="9356032" title="Думанский Андрей" percent="100.00"/>
        </copyrights>
    </updated-book>
    <removed-book id="24261892" uid="fc3ba230-4753-11e7-b2fb-0cc47a52085c" removed="2018-04-19 10:34:13"/>
</fb-updates>

Portanto, tenho 2 classes para implementar, uma para o livro atualizado e outra para o livro removido.

As aulas são as seguintes:

O livro removido é simples:

class RemovedBook {
    RemovedBook({
        this.id,
        this.uid,
        this.removed,
    });

    String id;
    String uid;
    DateTime removed;
}

A classe UpdatedBook é mais complicada, mas não é o escopo da questão.

A questão é: como posso analisar a resposta XML com o pacote Dart Xml.

Eles têm uma descrição da função de fluxo, mas não consigo perceber como obter minhas aulas a partir dela.

final url = Uri.parse('http://ip-api.com/xml/');
final request = await httpClient.getUrl(url);
final response = await request.close();
final stream = response
    .transform(utf8.decoder)
    .transform(const XmlEventDecoder())
    .transform(const XmlNormalizer())
    .expand((events) => events)
    .forEach((event) => print(event));

Então, como posso obter minhas aulas do stream?

Respostas

1 LukasRenggli Aug 16 2020 at 10:26

A resposta em Como agrupar elementos XML de um fluxo HttpClient no Flutter explica como converter um fluxo de eventos (limitado pelas condições em skipWhilee takeWhile) em subárvores de nós (usando XmlNodeDecoder).

Neste caso, é um pouco mais complicado: precisamos entrar / sair do salto / retirada de peças várias vezes. Infelizmente, não sei como fazer isso com o Dart Streams de uma maneira elegante (talvez mais fácil usando algumas das extensões Rx?), Mas a partir do xml 4.4.0 você pode escrever:

response
    .transform(utf8.decoder)
    .toXmlEvents()
    .selectSubtreeEvents((event) =>
        event.name == 'updated-book' || event.name == 'removed-book')
    .toXmlNodes()
    .flatten()
    .where((node) => node is XmlElement)
    .cast<XmlElement>()
    .map((element) {
      if (element.name.qualified == 'removed-book') {
        return RemovedBook(
            id: element.getAttribute('id'),
            ...
        ));
      } else {
        ...
      }
    })
    .forEach(print);

Embora seja definitivamente possível ler e processar esses dados em um dispositivo móvel com Flutter, você pode querer considerar o pré-processamento de uma grande quantidade de dados em um dispositivo mais poderoso de antemão.