public static ZipDirectory FromArchive(IArchive archive)
		{
			ZipDirectory root = new ZipDirectory();

			Dictionary<string, ZipDirectory> directories = new Dictionary<string, ZipDirectory>
			{
				{"", root}
			};

			foreach (IArchiveEntry entry in archive.Entries)
			{
				string entryKey = entry.Key.Trim('/');

				string directoryPath = entryKey.Substring(0, entryKey.LastIndexOf('/') + 1);
				string entryName = entryKey.Substring(entryKey.LastIndexOf('/') + 1);

				ZipDirectory container = directories[directoryPath];

				if (entry.IsDirectory)
				{
					ZipDirectory newDirectory = new ZipDirectory();

					container.Directories.Add(entryName, newDirectory);
					directories.Add(entry.Key, newDirectory);
				}
				else
				{
					container.Files.Add(entryName, entry);
				}
			}

			return root;
		}
		private async Task ReadCollectionFromZip(IArchiveEntry entry, ZipDirectory currentDirectory, string currentPath, Dictionary<string, IArchiveEntry> resourceEntries, Indiagram parent)
		{
			using (Stream entryStream = entry.OpenEntryStream())
			{
				XDocument xmlDocument = XDocument.Load(entryStream);

				XElement rootElement = xmlDocument.Element("indiagram");
				if (rootElement != null)
				{
					// the current element is an indiagram, just read it
					await CreateIndiagramFromXml(rootElement, false, parent, async (key, type) =>
					{
						return await Task.Run(() =>
						{
							if (resourceEntries.ContainsKey(key))
							{
								return resourceEntries[key].OpenEntryStream();
							}
							throw new IndexOutOfRangeException(string.Format("Key {0} is not available in resources", key));
						});
					});
				}
				else
				{
					// the current element is a category, read it + process its children
					rootElement = xmlDocument.Element("category");
					if (rootElement == null)
					{
						return;
					}

					Indiagram category = await CreateIndiagramFromXml(rootElement, true, parent, async (key, type) =>
					{
						return await Task.Run(() =>
						{
							if (resourceEntries.ContainsKey(key))
							{
								return resourceEntries[key].OpenEntryStream();
							}
							throw new IndexOutOfRangeException(string.Format("Key {0} is not available in resources", key));
						});
					});

					XElement indiagramsElement = rootElement.Element("indiagrams");
					if (indiagramsElement == null)
					{
						return;
					}
					foreach (XElement child in indiagramsElement.Elements("indiagram"))
					{
						// look for the entry
						string directoryName = Path.GetDirectoryName(child.Value);
						string fileName = Path.GetFileName(child.Value);

						ZipDirectory directory = GetSubZipDirectory(currentDirectory, currentPath, directoryName);

						if (directory.Files.ContainsKey(fileName))
						{
							await ReadCollectionFromZip(directory.Files[fileName], directory, directoryName, resourceEntries, category);
						}
						else
						{
							throw new IndexOutOfRangeException("file names mismatch");
						}
					}
				}
			}
		}
		private ZipDirectory GetSubZipDirectory(ZipDirectory current, string currentPath, string expectedPath)
		{
			if (string.Equals(currentPath, expectedPath, StringComparison.CurrentCultureIgnoreCase))
			{
				return current;
			}

			ZipDirectory sub = GetSubZipDirectory(current, currentPath, Path.GetDirectoryName(expectedPath));

			string dirName = Path.GetFileName(expectedPath);

			if (sub.Directories.ContainsKey(dirName))
			{
				return sub.Directories[dirName];
			}
			throw new IndexOutOfRangeException("directory names mismatch");
		}