Igor Karelin
Igor Karelin

Reputation: 126

Parse nested XML in Dart / Flutter

I need help parsing a nested XML file in Dart/Flutter.

I have a Dart class

class Genre {
String id;
String title;
String token;
String type;
// For children objets I want to get list of IDs
// as String
List<String> subGenres;
}

The XML file looks as follow:

<genres>
  <genre id="5003" title="Бизнес-книги" type="root">
    <genre id="5049" title="Банковское дело" token="bankovskoe_delo" type="genre"/>
    <genre id="5047" title="Кадровый менеджмент" token="kadrovyj_menedzhment" type="container">
      <genre id="5334" title="Аттестация персонала" token="attestaciya_personala" type="genre"/>
      <genre id="5330" title="Гендерные различия" token="gendernyye_razlichiya" type="genre"/>
      <genre id="5332" title="Конфликты" token="konflikty" type="genre"/>
    </genre>
  </genre>
  <genre id="5013" title="Юмористическая литература" type="root">
    <genre id="5201" title="Анекдоты" token="anekdoty" type="genre"/>
    <genre id="5202" title="Зарубежный юмор" token="zarubezhnyy" type="genre"/>
  </genre>
</genres>

So I have maybe unlimited levels of sub-genres. If a type of object is 'root' or 'container' it contains sub-genres, type 'genre' is a single record (like folders and files).

How can I get the list of all of my objects and parse it to class objects ?

Upvotes: 3

Views: 7219

Answers (1)

Richard Heap
Richard Heap

Reputation: 51732

Use the xml package. I like to use a factory constructor that takes an XmlElement. There's no reason to limit yourself to a List<String> for the children; you could have a List<Genre> to build a tree that mirrors your XML. (You could, of course, do that instead if you prefer.)

Notice how the factory constructor iterates through any child XML elements, recursively calling itself. The normal constructor is private, so that only the factory constructor uses it. If a genre has no sub-genres, the subGenres list isEmpty.

import 'package:xml/xml.dart';

var sample = '''<?xml version="1.0" encoding="UTF-8"?>
<genres>
  <genre id="5003" title="Бизнес-книги" type="root">
    <genre id="5049" title="Банковское дело" token="bankovskoe_delo" type="genre"/>
    <genre id="5047" title="Кадровый менеджмент" token="kadrovyj_menedzhment" type="container">
      <genre id="5334" title="Аттестация персонала" token="attestaciya_personala" type="genre"/>
      <genre id="5330" title="Гендерные различия" token="gendernyye_razlichiya" type="genre"/>
      <genre id="5332" title="Конфликты" token="konflikty" type="genre"/>
    </genre>
  </genre>
  <genre id="5013" title="Юмористическая литература" type="root">
    <genre id="5201" title="Анекдоты" token="anekdoty" type="genre"/>
    <genre id="5202" title="Зарубежный юмор" token="zarubezhnyy" type="genre"/>
  </genre>
</genres>
''';

class Genre {
  Genre._(this.id, this.title, this.token, this.type, this.subGenres);

  factory Genre.fromElement(XmlElement genreElement) {
    return Genre._(
      genreElement.getAttribute('id'),
      genreElement.getAttribute('title'),
      genreElement.getAttribute('token'),
      genreElement.getAttribute('type'),
      genreElement
          .findElements('genre')
          .map<Genre>((e) => Genre.fromElement(e))
          .toList(),
    );
  }

  String id;
  String title;
  String token;
  String type;
  List<Genre> subGenres;
}

void main() {
  var root = XmlDocument.parse(sample).getElement('genres');
  var rootGenres = root
      .findElements('genre')
      .map<Genre>((e) => Genre.fromElement(e))
      .toList();

  print(rootGenres);
}

Upvotes: 4

Related Questions