/// <summary>
        /// Reads the document's spine, containing all sections in reading order.
        /// </summary>
        /// <param>book</param>
        /// <param>resourcesById</param>
        /// <param name="packageDocument"></param>
        /// <param name="epubReader"></param>
        /// <param name="resources"></param>
        /// <param name="idMapping"></param>
        private static Spine readSpine(XElement packageDocument, EpubReader epubReader, Resources resources, Dictionary<String, String> idMapping)
        {
            XElement spineElement = DOMUtil.getFirstElementByTagNameNS(packageDocument, NAMESPACE_OPF, OPFTags.spine);
            if (spineElement == null)
            {
                //log.error("Element " + OPFTags.spine + " not found in package document, generating one automatically");
                return generateSpineFromResources(resources);
            }
            Spine result = new Spine();
            result.setTocResource(findTableOfContentsResource(spineElement, resources));
            var spineNodes = packageDocument.Elements(NAMESPACE_OPF + OPFTags.itemref).Elements<XElement>();
            IEnumerator spineNode = spineNodes.GetEnumerator();
            List<SpineReference> spineReferences = new List<SpineReference>();
            while (spineNode.MoveNext())
            {
                XElement spineItem = (XElement)spineNode.Current;
                String itemref = DOMUtil.getAttribute(spineItem, OPFAttributes.idref);
                if (StringUtil.isBlank(itemref))
                {
                    //log.error("itemref with missing or empty idref"); // XXX
                    continue;
                }
                String id = idMapping[itemref];
                if (id == null)
                {
                    id = itemref;
                }
                Resource resource = resources.getByIdOrHref(id);
                if (resource == null)
                {
                    //log.error("resource with id \'" + id + "\' not found");
                    continue;
                }

                SpineReference spineReference = new SpineReference(resource);
                if (OPFValues.no.Equals(DOMUtil.getAttribute(spineItem, OPFAttributes.linear)))
                {
                    spineReference.setLinear(false);
                }
                spineReferences.Add(spineReference);
            }
            result.setSpineReferences(spineReferences);
            return result;
        }
        /// 
        /// <param name="packageResource"></param>
        /// <param name="epubReader"></param>
        /// <param name="book"></param>
        /// <param name="resources"></param>
        public static void read(Resource packageResource, EpubReader epubReader, Book book, Resources resources)
        {
            XElement packageDocument = XElement.Load(packageResource.getInputStream());
            String packageHref = packageResource.getHref();
            resources = fixHrefs(packageHref, resources);
            readGuide(packageDocument, epubReader, book, resources);
            System.Collections.Generic.Dictionary<string, string> idMapping = new Dictionary<string, string>();
            resources = readManifest(packageDocument, packageHref, epubReader, resources, idMapping);

            book.setResources(resources);
            readCover(packageDocument, book);
            //  book.setMetadata(PackageDocumentMetadataReader.readMetadata(packageDocument, book.getResources()));
            book.setSpine(readSpine(packageDocument, epubReader, book.getResources(), idMapping));

            // if we did not find a cover page then we make the first page of the book the cover page
            if (book.getCoverPage() == null && book.getSpine().size() > 0)
            {
                book.setCoverPage(book.getSpine().getResource(0));
            }
        }
        /// 
        /// <param name="packageResource"></param>
        /// <param name="epubReader"></param>
        /// <param name="book"></param>
        /// <param name="resources"></param>
        public static void read(Resource packageResource, EpubReader epubReader, Book book, Resources resources)
        {
            XElement packageDocument = XElement.Load(packageResource.getInputStream());
            String packageHref = packageResource.getHref();
            resources = fixHrefs(packageHref, resources);
            readGuide(packageDocument, epubReader, book, resources);
            System.Collections.Generic.Dictionary<string, string> idMapping = new Dictionary<string, string>();
            resources = readManifest(packageDocument, packageHref, epubReader, resources, idMapping);

            book.setResources(resources);
            readCover(packageDocument, book);
            //  book.setMetadata(PackageDocumentMetadataReader.readMetadata(packageDocument, book.getResources()));
            book.setSpine(readSpine(packageDocument, epubReader, book.getResources(), idMapping));

            // if we did not find a cover page then we make the first page of the book the cover page
            if (book.getCoverPage() == null && book.getSpine().size() > 0)
            {
                book.setCoverPage(book.getSpine().getResource(0));
            }
        }
 /// <summary>
 /// Reads the book's guide. Here some more attempts are made at finding the cover
 /// page.
 /// </summary>
 /// <param name="packageDocument"></param>
 /// <param name="epubReader"></param>
 /// <param name="book"></param>
 /// <param name="resources">resources</param>
 private static void readGuide(XElement packageDocument, EpubReader epubReader, Book book, Resources resources)
 {
     XElement guideElement = DOMUtil.getFirstElementByTagNameNS(packageDocument, NAMESPACE_OPF, OPFTags.guide);
     if (guideElement == null)
     {
         return;
     }
     Guide guide = book.getGuide();
     var guideReferences = packageDocument.Elements(NAMESPACE_OPF + OPFTags.reference).Elements<XElement>();
     foreach (XElement referenceElement in (from e in guideReferences where e.Value.Trim() != string.Empty select e))
     {
         String resourceHref = DOMUtil.getAttribute(referenceElement, OPFAttributes.href);
         if (StringUtil.isBlank(resourceHref))
         {
             continue;
         }
         Resource resource = resources.getByHref(StringUtil.substringBefore(resourceHref, Constants.FRAGMENT_SEPARATOR_CHAR));
         if (resource == null)
         {
             //log.error("Guide is referencing resource with href " + resourceHref + " which could not be found");
             continue;
         }
         String type = DOMUtil.getAttribute(referenceElement, OPFAttributes.type);
         if (StringUtil.isBlank(type))
         {
             //log.error("Guide is referencing resource with href " + resourceHref + " which is missing the 'type' attribute");
             continue;
         }
         String title = DOMUtil.getAttribute(referenceElement, OPFAttributes.title);
         if (GuideReference.COVER.Equals(type))
         {
             continue; // cover is handled elsewhere
         }
         GuideReference reference = new GuideReference(resource, type, title, StringUtil.substringAfter(resourceHref, Constants.FRAGMENT_SEPARATOR_CHAR));
         guide.addReference(reference);
     }
 }
        /// <summary>
        /// Reads the document's spine, containing all sections in reading order.
        /// </summary>
        /// <param>book</param>
        /// <param>resourcesById</param>
        /// <param name="packageDocument"></param>
        /// <param name="epubReader"></param>
        /// <param name="resources"></param>
        /// <param name="idMapping"></param>
        private static Spine readSpine(XElement packageDocument, EpubReader epubReader, Resources resources, Dictionary<String, String> idMapping)
        {
            XElement spineElement = DOMUtil.getFirstElementByTagNameNS(packageDocument, NAMESPACE_OPF, OPFTags.spine);
            if (spineElement == null)
            {
                //log.error("Element " + OPFTags.spine + " not found in package document, generating one automatically");
                return generateSpineFromResources(resources);
            }
            Spine result = new Spine();
            result.setTocResource(findTableOfContentsResource(spineElement, resources));
            var spineNodes = packageDocument.Elements(NAMESPACE_OPF + OPFTags.itemref).Elements<XElement>();
            IEnumerator spineNode = spineNodes.GetEnumerator();
            List<SpineReference> spineReferences = new List<SpineReference>();
            while (spineNode.MoveNext())
            {
                XElement spineItem = (XElement)spineNode.Current;
                String itemref = DOMUtil.getAttribute(spineItem, OPFAttributes.idref);
                if (StringUtil.isBlank(itemref))
                {
                    //log.error("itemref with missing or empty idref"); // XXX
                    continue;
                }
                String id = idMapping[itemref];
                if (id == null)
                {
                    id = itemref;
                }
                Resource resource = resources.getByIdOrHref(id);
                if (resource == null)
                {
                    //log.error("resource with id \'" + id + "\' not found");
                    continue;
                }

                SpineReference spineReference = new SpineReference(resource);
                if (OPFValues.no.Equals(DOMUtil.getAttribute(spineItem, OPFAttributes.linear)))
                {
                    spineReference.setLinear(false);
                }
                spineReferences.Add(spineReference);
            }
            result.setSpineReferences(spineReferences);
            return result;
        }
        /// <summary>
        /// Reads the manifest containing the resource ids, hrefs and mediatypes.
        /// </summary>
        /// <param>book</param>
        /// <param>resourcesByHref</param>
        /// <param>a Map with resources, with their id's as key.</param>
        /// <param name="packageDocument"></param>
        /// <param name="packageHref"></param>
        /// <param name="epubReader"></param>
        /// <param name="resources"></param>
        /// <param name="idMapping"></param>
        private static Resources readManifest(XElement packageDocument, String packageHref, EpubReader epubReader, Resources resources, Dictionary<String, String> idMapping)
        {
            XElement manifestElement = DOMUtil.getFirstElementByTagNameNS(packageDocument, NAMESPACE_OPF, OPFTags.manifest);
            Resources result = new Resources();
            if (manifestElement == null)
            {
                return result;
            }
            var itemElements = packageDocument.Elements(NAMESPACE_OPF + OPFTags.item).Elements<XElement>();

            foreach (XElement itemElement in (from e in itemElements where e.Value.Trim() != string.Empty select e))
            {
                String id = DOMUtil.getAttribute(itemElement, OPFAttributes.id);
                String href = DOMUtil.getAttribute(itemElement, OPFAttributes.href);
                try
                {
                    href = System.Web.HttpUtility.UrlDecode(href, System.Text.Encoding.GetEncoding(Constants.ENCODING));
                }
                catch (Exception e)
                {
                    //log.error(e.getMessage());
                }
                String mediaTypeName = DOMUtil.getAttribute(itemElement, OPFAttributes.media_type);
                Resource resource = resources.remove(href);
                if (resource == null)
                {
                    //log.error("resource with href '" + href + "' not found");
                    continue;
                }
                resource.setId(id);
                MediaType mediaType = MediatypeService.getMediaTypeByName(mediaTypeName);
                if (mediaType != null)
                {
                    resource.setMediaType(mediaType);
                }
                result.add(resource);
                idMapping.Add(id, resource.getId());
            }
            return result;
        }
 /// <summary>
 /// Reads the book's guide. Here some more attempts are made at finding the cover
 /// page.
 /// </summary>
 /// <param name="packageDocument"></param>
 /// <param name="epubReader"></param>
 /// <param name="book"></param>
 /// <param name="resources">resources</param>
 private static void readGuide(XElement packageDocument, EpubReader epubReader, Book book, Resources resources)
 {
     XElement guideElement = DOMUtil.getFirstElementByTagNameNS(packageDocument, NAMESPACE_OPF, OPFTags.guide);
     if (guideElement == null)
     {
         return;
     }
     Guide guide = book.getGuide();
     var guideReferences = packageDocument.Elements(NAMESPACE_OPF + OPFTags.reference).Elements<XElement>();
     foreach (XElement referenceElement in (from e in guideReferences where e.Value.Trim() != string.Empty select e))
     {
         String resourceHref = DOMUtil.getAttribute(referenceElement, OPFAttributes.href);
         if (StringUtil.isBlank(resourceHref))
         {
             continue;
         }
         Resource resource = resources.getByHref(StringUtil.substringBefore(resourceHref, Constants.FRAGMENT_SEPARATOR_CHAR));
         if (resource == null)
         {
             //log.error("Guide is referencing resource with href " + resourceHref + " which could not be found");
             continue;
         }
         String type = DOMUtil.getAttribute(referenceElement, OPFAttributes.type);
         if (StringUtil.isBlank(type))
         {
             //log.error("Guide is referencing resource with href " + resourceHref + " which is missing the 'type' attribute");
             continue;
         }
         String title = DOMUtil.getAttribute(referenceElement, OPFAttributes.title);
         if (GuideReference.COVER.Equals(type))
         {
             continue; // cover is handled elsewhere
         }
         GuideReference reference = new GuideReference(resource, type, title, StringUtil.substringAfter(resourceHref, Constants.FRAGMENT_SEPARATOR_CHAR));
         guide.addReference(reference);
     }
 }
        /// <summary>
        /// Reads the manifest containing the resource ids, hrefs and mediatypes.
        /// </summary>
        /// <param>book</param>
        /// <param>resourcesByHref</param>
        /// <param>a Map with resources, with their id's as key.</param>
        /// <param name="packageDocument"></param>
        /// <param name="packageHref"></param>
        /// <param name="epubReader"></param>
        /// <param name="resources"></param>
        /// <param name="idMapping"></param>
        private static Resources readManifest(XElement packageDocument, String packageHref, EpubReader epubReader, Resources resources, Dictionary<String, String> idMapping)
        {
            XElement manifestElement = DOMUtil.getFirstElementByTagNameNS(packageDocument, NAMESPACE_OPF, OPFTags.manifest);
            Resources result = new Resources();
            if (manifestElement == null)
            {
                return result;
            }
            var itemElements = packageDocument.Elements(NAMESPACE_OPF + OPFTags.item).Elements<XElement>();

            foreach (XElement itemElement in (from e in itemElements where e.Value.Trim() != string.Empty select e))
            {
                String id = DOMUtil.getAttribute(itemElement, OPFAttributes.id);
                String href = DOMUtil.getAttribute(itemElement, OPFAttributes.href);
                try
                {
                    href = System.Web.HttpUtility.UrlDecode(href, System.Text.Encoding.GetEncoding(Constants.ENCODING));
                }
                catch (Exception e)
                {
                    //log.error(e.getMessage());
                }
                String mediaTypeName = DOMUtil.getAttribute(itemElement, OPFAttributes.media_type);
                Resource resource = resources.remove(href);
                if (resource == null)
                {
                    //log.error("resource with href '" + href + "' not found");
                    continue;
                }
                resource.setId(id);
                MediaType mediaType = MediatypeService.getMediaTypeByName(mediaTypeName);
                if (mediaType != null)
                {
                    resource.setMediaType(mediaType);
                }
                result.add(resource);
                idMapping.Add(id, resource.getId());
            }
            return result;
        }