From 4fff04f58b5564f86d0192403a404c58f80ab717 Mon Sep 17 00:00:00 2001 From: vers-one <12114169+vers-one@users.noreply.github.com> Date: Fri, 11 Jan 2019 22:21:58 -0500 Subject: [PATCH 1/7] Parsing EPUB 3 navigation document Part of #18. --- Source/VersOne.Epub/Entities/EpubSchema.cs | 2 +- Source/VersOne.Epub/Readers/ChapterReader.cs | 4 +- ...{NavigationReader.cs => Epub2NcxReader.cs} | 111 ++--- .../Readers/Epub3NavDocumentReader.cs | 193 +++++++++ Source/VersOne.Epub/Readers/PackageReader.cs | 55 ++- Source/VersOne.Epub/Readers/SchemaReader.cs | 11 +- .../Schema/Common/ManifestProperty.cs | 37 ++ .../Common/StructuralSemanticsProperty.cs | 409 ++++++++++++++++++ .../Schema/Navigation/EpubNavigation.cs | 14 - .../Schema/Navigation/EpubNavigationList.cs | 12 - .../Navigation/EpubNavigationPageList.cs | 8 - Source/VersOne.Epub/Schema/Ncx/Epub2Ncx.cs | 14 + .../Epub2NcxContent.cs} | 2 +- .../Epub2NcxDocAuthor.cs} | 2 +- .../Epub2NcxDocTitle.cs} | 2 +- .../Epub2NcxHead.cs} | 2 +- .../Epub2NcxHeadMeta.cs} | 2 +- .../Epub2NcxNavigationLabel.cs} | 2 +- .../Schema/Ncx/Epub2NcxNavigationList.cs | 12 + .../Schema/Ncx/Epub2NcxNavigationMap.cs | 8 + .../Epub2NcxNavigationPoint.cs} | 8 +- .../Epub2NcxNavigationTarget.cs} | 6 +- .../Epub2NcxPageList.cs} | 2 +- .../Epub2NcxPageTarget.cs} | 8 +- .../Epub2NcxPageTargetType.cs} | 2 +- .../Schema/Opf/EpubManifestItem.cs | 5 +- Source/VersOne.Epub/Schema/Opf/EpubVersion.cs | 3 +- Source/VersOne.Epub/Schema/Ops/Epub3Nav.cs | 10 + .../VersOne.Epub/Schema/Ops/Epub3NavAnchor.cs | 11 + .../Schema/Ops/Epub3NavDocument.cs | 9 + Source/VersOne.Epub/Schema/Ops/Epub3NavLi.cs | 9 + Source/VersOne.Epub/Schema/Ops/Epub3NavOl.cs | 10 + .../VersOne.Epub/Schema/Ops/Epub3NavSpan.cs | 9 + .../Utils/StringExtensionMethods.cs | 12 + .../VersOne.Epub/Utils/XmlExtensionMethods.cs | 17 + 35 files changed, 890 insertions(+), 133 deletions(-) rename Source/VersOne.Epub/Readers/{NavigationReader.cs => Epub2NcxReader.cs} (74%) create mode 100644 Source/VersOne.Epub/Readers/Epub3NavDocumentReader.cs create mode 100644 Source/VersOne.Epub/Schema/Common/ManifestProperty.cs create mode 100644 Source/VersOne.Epub/Schema/Common/StructuralSemanticsProperty.cs delete mode 100644 Source/VersOne.Epub/Schema/Navigation/EpubNavigation.cs delete mode 100644 Source/VersOne.Epub/Schema/Navigation/EpubNavigationList.cs delete mode 100644 Source/VersOne.Epub/Schema/Navigation/EpubNavigationPageList.cs create mode 100644 Source/VersOne.Epub/Schema/Ncx/Epub2Ncx.cs rename Source/VersOne.Epub/Schema/{Navigation/EpubNavigationContent.cs => Ncx/Epub2NcxContent.cs} (87%) rename Source/VersOne.Epub/Schema/{Navigation/EpubNavigationDocTitle.cs => Ncx/Epub2NcxDocAuthor.cs} (60%) rename Source/VersOne.Epub/Schema/{Navigation/EpubNavigationDocAuthor.cs => Ncx/Epub2NcxDocTitle.cs} (60%) rename Source/VersOne.Epub/Schema/{Navigation/EpubNavigationMap.cs => Ncx/Epub2NcxHead.cs} (57%) rename Source/VersOne.Epub/Schema/{Navigation/EpubNavigationHeadMeta.cs => Ncx/Epub2NcxHeadMeta.cs} (81%) rename Source/VersOne.Epub/Schema/{Navigation/EpubNavigationLabel.cs => Ncx/Epub2NcxNavigationLabel.cs} (81%) create mode 100644 Source/VersOne.Epub/Schema/Ncx/Epub2NcxNavigationList.cs create mode 100644 Source/VersOne.Epub/Schema/Ncx/Epub2NcxNavigationMap.cs rename Source/VersOne.Epub/Schema/{Navigation/EpubNavigationPoint.cs => Ncx/Epub2NcxNavigationPoint.cs} (59%) rename Source/VersOne.Epub/Schema/{Navigation/EpubNavigationTarget.cs => Ncx/Epub2NcxNavigationTarget.cs} (59%) rename Source/VersOne.Epub/Schema/{Navigation/EpubNavigationHead.cs => Ncx/Epub2NcxPageList.cs} (55%) rename Source/VersOne.Epub/Schema/{Navigation/EpubNavigationPageTarget.cs => Ncx/Epub2NcxPageTarget.cs} (51%) rename Source/VersOne.Epub/Schema/{Navigation/EpubNavigationPageTargetType.cs => Ncx/Epub2NcxPageTargetType.cs} (72%) create mode 100644 Source/VersOne.Epub/Schema/Ops/Epub3Nav.cs create mode 100644 Source/VersOne.Epub/Schema/Ops/Epub3NavAnchor.cs create mode 100644 Source/VersOne.Epub/Schema/Ops/Epub3NavDocument.cs create mode 100644 Source/VersOne.Epub/Schema/Ops/Epub3NavLi.cs create mode 100644 Source/VersOne.Epub/Schema/Ops/Epub3NavOl.cs create mode 100644 Source/VersOne.Epub/Schema/Ops/Epub3NavSpan.cs create mode 100644 Source/VersOne.Epub/Utils/StringExtensionMethods.cs create mode 100644 Source/VersOne.Epub/Utils/XmlExtensionMethods.cs diff --git a/Source/VersOne.Epub/Entities/EpubSchema.cs b/Source/VersOne.Epub/Entities/EpubSchema.cs index 80f524f..e2efbcd 100644 --- a/Source/VersOne.Epub/Entities/EpubSchema.cs +++ b/Source/VersOne.Epub/Entities/EpubSchema.cs @@ -5,7 +5,7 @@ namespace VersOne.Epub public class EpubSchema { public EpubPackage Package { get; set; } - public EpubNavigation Navigation { get; set; } + public Epub2Ncx Navigation { get; set; } public string ContentDirectoryPath { get; set; } } } diff --git a/Source/VersOne.Epub/Readers/ChapterReader.cs b/Source/VersOne.Epub/Readers/ChapterReader.cs index 44bf9a6..0d93da1 100644 --- a/Source/VersOne.Epub/Readers/ChapterReader.cs +++ b/Source/VersOne.Epub/Readers/ChapterReader.cs @@ -12,10 +12,10 @@ public static List GetChapters(EpubBookRef bookRef) return GetChapters(bookRef, bookRef.Schema.Navigation.NavMap); } - public static List GetChapters(EpubBookRef bookRef, List navigationPoints) + public static List GetChapters(EpubBookRef bookRef, List navigationPoints) { List result = new List(); - foreach (EpubNavigationPoint navigationPoint in navigationPoints) + foreach (Epub2NcxNavigationPoint navigationPoint in navigationPoints) { string contentFileName; string anchor; diff --git a/Source/VersOne.Epub/Readers/NavigationReader.cs b/Source/VersOne.Epub/Readers/Epub2NcxReader.cs similarity index 74% rename from Source/VersOne.Epub/Readers/NavigationReader.cs rename to Source/VersOne.Epub/Readers/Epub2NcxReader.cs index 2971514..58c60d4 100644 --- a/Source/VersOne.Epub/Readers/NavigationReader.cs +++ b/Source/VersOne.Epub/Readers/Epub2NcxReader.cs @@ -6,33 +6,34 @@ using System.Threading.Tasks; using System.Xml.Linq; using VersOne.Epub.Schema; +using VersOne.Epub.Utils; namespace VersOne.Epub.Internal { - internal static class NavigationReader + internal static class Epub2NcxReader { - public static async Task ReadNavigationAsync(ZipArchive epubArchive, string contentDirectoryPath, EpubPackage package) + public static async Task ReadEpub2NcxAsync(ZipArchive epubArchive, string contentDirectoryPath, EpubPackage package) { - EpubNavigation result = new EpubNavigation(); + Epub2Ncx result = new Epub2Ncx(); string tocId = package.Spine.Toc; if (String.IsNullOrEmpty(tocId)) { throw new Exception("EPUB parsing error: TOC ID is empty."); } - EpubManifestItem tocManifestItem = package.Manifest.FirstOrDefault(item => String.Compare(item.Id, tocId, StringComparison.OrdinalIgnoreCase) == 0); + EpubManifestItem tocManifestItem = package.Manifest.FirstOrDefault(item => item.Id.CompareOrdinalIgnoreCase(tocId)); if (tocManifestItem == null) { - throw new Exception(String.Format("EPUB parsing error: TOC item {0} not found in EPUB manifest.", tocId)); + throw new Exception($"EPUB parsing error: TOC item {tocId} not found in EPUB manifest."); } string tocFileEntryPath = ZipPathUtils.Combine(contentDirectoryPath, tocManifestItem.Href); ZipArchiveEntry tocFileEntry = epubArchive.GetEntry(tocFileEntryPath); if (tocFileEntry == null) { - throw new Exception(String.Format("EPUB parsing error: TOC file {0} not found in archive.", tocFileEntryPath)); + throw new Exception($"EPUB parsing error: TOC file {tocFileEntryPath} not found in archive."); } if (tocFileEntry.Length > Int32.MaxValue) { - throw new Exception(String.Format("EPUB parsing error: TOC file {0} is larger than 2 Gb.", tocFileEntryPath)); + throw new Exception($"EPUB parsing error: TOC file {tocFileEntryPath} is larger than 2 Gb."); } XDocument containerDocument; using (Stream containerStream = tocFileEntry.Open()) @@ -50,19 +51,19 @@ public static async Task ReadNavigationAsync(ZipArchive epubArch { throw new Exception("EPUB parsing error: TOC file does not contain head element."); } - EpubNavigationHead navigationHead = ReadNavigationHead(headNode); + Epub2NcxHead navigationHead = ReadNavigationHead(headNode); result.Head = navigationHead; XElement docTitleNode = ncxNode.Element(ncxNamespace + "docTitle"); if (docTitleNode == null) { throw new Exception("EPUB parsing error: TOC file does not contain docTitle element."); } - EpubNavigationDocTitle navigationDocTitle = ReadNavigationDocTitle(docTitleNode); + Epub2NcxDocTitle navigationDocTitle = ReadNavigationDocTitle(docTitleNode); result.DocTitle = navigationDocTitle; - result.DocAuthors = new List(); + result.DocAuthors = new List(); foreach (XElement docAuthorNode in ncxNode.Elements(ncxNamespace + "docAuthor")) { - EpubNavigationDocAuthor navigationDocAuthor = ReadNavigationDocAuthor(docAuthorNode); + Epub2NcxDocAuthor navigationDocAuthor = ReadNavigationDocAuthor(docAuthorNode); result.DocAuthors.Add(navigationDocAuthor); } XElement navMapNode = ncxNode.Element(ncxNamespace + "navMap"); @@ -70,31 +71,31 @@ public static async Task ReadNavigationAsync(ZipArchive epubArch { throw new Exception("EPUB parsing error: TOC file does not contain navMap element."); } - EpubNavigationMap navMap = ReadNavigationMap(navMapNode); + Epub2NcxNavigationMap navMap = ReadNavigationMap(navMapNode); result.NavMap = navMap; XElement pageListNode = ncxNode.Element(ncxNamespace + "pageList"); if (pageListNode != null) { - EpubNavigationPageList pageList = ReadNavigationPageList(pageListNode); + Epub2NcxPageList pageList = ReadNavigationPageList(pageListNode); result.PageList = pageList; } - result.NavLists = new List(); + result.NavLists = new List(); foreach (XElement navigationListNode in ncxNode.Elements(ncxNamespace + "navList")) { - EpubNavigationList navigationList = ReadNavigationList(navigationListNode); + Epub2NcxNavigationList navigationList = ReadNavigationList(navigationListNode); result.NavLists.Add(navigationList); } return result; } - private static EpubNavigationHead ReadNavigationHead(XElement headNode) + private static Epub2NcxHead ReadNavigationHead(XElement headNode) { - EpubNavigationHead result = new EpubNavigationHead(); + Epub2NcxHead result = new Epub2NcxHead(); foreach (XElement metaNode in headNode.Elements()) { if (String.Compare(metaNode.Name.LocalName, "meta", StringComparison.OrdinalIgnoreCase) == 0) { - EpubNavigationHeadMeta meta = new EpubNavigationHeadMeta(); + Epub2NcxHeadMeta meta = new Epub2NcxHeadMeta(); foreach (XAttribute metaNodeAttribute in metaNode.Attributes()) { string attributeValue = metaNodeAttribute.Value; @@ -125,9 +126,9 @@ private static EpubNavigationHead ReadNavigationHead(XElement headNode) return result; } - private static EpubNavigationDocTitle ReadNavigationDocTitle(XElement docTitleNode) + private static Epub2NcxDocTitle ReadNavigationDocTitle(XElement docTitleNode) { - EpubNavigationDocTitle result = new EpubNavigationDocTitle(); + Epub2NcxDocTitle result = new Epub2NcxDocTitle(); foreach (XElement textNode in docTitleNode.Elements()) { if (String.Compare(textNode.Name.LocalName, "text", StringComparison.OrdinalIgnoreCase) == 0) @@ -138,9 +139,9 @@ private static EpubNavigationDocTitle ReadNavigationDocTitle(XElement docTitleNo return result; } - private static EpubNavigationDocAuthor ReadNavigationDocAuthor(XElement docAuthorNode) + private static Epub2NcxDocAuthor ReadNavigationDocAuthor(XElement docAuthorNode) { - EpubNavigationDocAuthor result = new EpubNavigationDocAuthor(); + Epub2NcxDocAuthor result = new Epub2NcxDocAuthor(); foreach (XElement textNode in docAuthorNode.Elements()) { if (String.Compare(textNode.Name.LocalName, "text", StringComparison.OrdinalIgnoreCase) == 0) @@ -151,23 +152,23 @@ private static EpubNavigationDocAuthor ReadNavigationDocAuthor(XElement docAutho return result; } - private static EpubNavigationMap ReadNavigationMap(XElement navigationMapNode) + private static Epub2NcxNavigationMap ReadNavigationMap(XElement navigationMapNode) { - EpubNavigationMap result = new EpubNavigationMap(); + Epub2NcxNavigationMap result = new Epub2NcxNavigationMap(); foreach (XElement navigationPointNode in navigationMapNode.Elements()) { if (String.Compare(navigationPointNode.Name.LocalName, "navPoint", StringComparison.OrdinalIgnoreCase) == 0) { - EpubNavigationPoint navigationPoint = ReadNavigationPoint(navigationPointNode); + Epub2NcxNavigationPoint navigationPoint = ReadNavigationPoint(navigationPointNode); result.Add(navigationPoint); } } return result; } - private static EpubNavigationPoint ReadNavigationPoint(XElement navigationPointNode) + private static Epub2NcxNavigationPoint ReadNavigationPoint(XElement navigationPointNode) { - EpubNavigationPoint result = new EpubNavigationPoint(); + Epub2NcxNavigationPoint result = new Epub2NcxNavigationPoint(); foreach (XAttribute navigationPointNodeAttribute in navigationPointNode.Attributes()) { string attributeValue = navigationPointNodeAttribute.Value; @@ -188,22 +189,22 @@ private static EpubNavigationPoint ReadNavigationPoint(XElement navigationPointN { throw new Exception("Incorrect EPUB navigation point: point ID is missing."); } - result.NavigationLabels = new List(); - result.ChildNavigationPoints = new List(); + result.NavigationLabels = new List(); + result.ChildNavigationPoints = new List(); foreach (XElement navigationPointChildNode in navigationPointNode.Elements()) { switch (navigationPointChildNode.Name.LocalName.ToLowerInvariant()) { case "navlabel": - EpubNavigationLabel navigationLabel = ReadNavigationLabel(navigationPointChildNode); + Epub2NcxNavigationLabel navigationLabel = ReadNavigationLabel(navigationPointChildNode); result.NavigationLabels.Add(navigationLabel); break; case "content": - EpubNavigationContent content = ReadNavigationContent(navigationPointChildNode); + Epub2NcxContent content = ReadNavigationContent(navigationPointChildNode); result.Content = content; break; case "navpoint": - EpubNavigationPoint childNavigationPoint = ReadNavigationPoint(navigationPointChildNode); + Epub2NcxNavigationPoint childNavigationPoint = ReadNavigationPoint(navigationPointChildNode); result.ChildNavigationPoints.Add(childNavigationPoint); break; } @@ -219,9 +220,9 @@ private static EpubNavigationPoint ReadNavigationPoint(XElement navigationPointN return result; } - private static EpubNavigationLabel ReadNavigationLabel(XElement navigationLabelNode) + private static Epub2NcxNavigationLabel ReadNavigationLabel(XElement navigationLabelNode) { - EpubNavigationLabel result = new EpubNavigationLabel(); + Epub2NcxNavigationLabel result = new Epub2NcxNavigationLabel(); XElement navigationLabelTextNode = navigationLabelNode.Element(navigationLabelNode.Name.Namespace + "text"); if (navigationLabelTextNode == null) { @@ -231,9 +232,9 @@ private static EpubNavigationLabel ReadNavigationLabel(XElement navigationLabelN return result; } - private static EpubNavigationContent ReadNavigationContent(XElement navigationContentNode) + private static Epub2NcxContent ReadNavigationContent(XElement navigationContentNode) { - EpubNavigationContent result = new EpubNavigationContent(); + Epub2NcxContent result = new Epub2NcxContent(); foreach (XAttribute navigationContentNodeAttribute in navigationContentNode.Attributes()) { string attributeValue = navigationContentNodeAttribute.Value; @@ -254,23 +255,23 @@ private static EpubNavigationContent ReadNavigationContent(XElement navigationCo return result; } - private static EpubNavigationPageList ReadNavigationPageList(XElement navigationPageListNode) + private static Epub2NcxPageList ReadNavigationPageList(XElement navigationPageListNode) { - EpubNavigationPageList result = new EpubNavigationPageList(); + Epub2NcxPageList result = new Epub2NcxPageList(); foreach (XElement pageTargetNode in navigationPageListNode.Elements()) { if (String.Compare(pageTargetNode.Name.LocalName, "pageTarget", StringComparison.OrdinalIgnoreCase) == 0) { - EpubNavigationPageTarget pageTarget = ReadNavigationPageTarget(pageTargetNode); + Epub2NcxPageTarget pageTarget = ReadNavigationPageTarget(pageTargetNode); result.Add(pageTarget); } } return result; } - private static EpubNavigationPageTarget ReadNavigationPageTarget(XElement navigationPageTargetNode) + private static Epub2NcxPageTarget ReadNavigationPageTarget(XElement navigationPageTargetNode) { - EpubNavigationPageTarget result = new EpubNavigationPageTarget(); + Epub2NcxPageTarget result = new Epub2NcxPageTarget(); foreach (XAttribute navigationPageTargetNodeAttribute in navigationPageTargetNode.Attributes()) { string attributeValue = navigationPageTargetNodeAttribute.Value; @@ -283,14 +284,14 @@ private static EpubNavigationPageTarget ReadNavigationPageTarget(XElement naviga result.Value = attributeValue; break; case "type": - EpubNavigationPageTargetType type; + Epub2NcxPageTargetType type; if (Enum.TryParse(attributeValue, out type)) { result.Type = type; } else { - result.Type = EpubNavigationPageTargetType.UNKNOWN; + result.Type = Epub2NcxPageTargetType.UNKNOWN; } break; case "class": @@ -301,20 +302,20 @@ private static EpubNavigationPageTarget ReadNavigationPageTarget(XElement naviga break; } } - if (result.Type == default(EpubNavigationPageTargetType)) + if (result.Type == default(Epub2NcxPageTargetType)) { throw new Exception("Incorrect EPUB navigation page target: page target type is missing."); } - result.NavigationLabels = new List(); + result.NavigationLabels = new List(); foreach (XElement navigationPageTargetChildNode in navigationPageTargetNode.Elements()) switch (navigationPageTargetChildNode.Name.LocalName.ToLowerInvariant()) { case "navlabel": - EpubNavigationLabel navigationLabel = ReadNavigationLabel(navigationPageTargetChildNode); + Epub2NcxNavigationLabel navigationLabel = ReadNavigationLabel(navigationPageTargetChildNode); result.NavigationLabels.Add(navigationLabel); break; case "content": - EpubNavigationContent content = ReadNavigationContent(navigationPageTargetChildNode); + Epub2NcxContent content = ReadNavigationContent(navigationPageTargetChildNode); result.Content = content; break; } @@ -325,9 +326,9 @@ private static EpubNavigationPageTarget ReadNavigationPageTarget(XElement naviga return result; } - private static EpubNavigationList ReadNavigationList(XElement navigationListNode) + private static Epub2NcxNavigationList ReadNavigationList(XElement navigationListNode) { - EpubNavigationList result = new EpubNavigationList(); + Epub2NcxNavigationList result = new Epub2NcxNavigationList(); foreach (XAttribute navigationListNodeAttribute in navigationListNode.Attributes()) { string attributeValue = navigationListNodeAttribute.Value; @@ -346,11 +347,11 @@ private static EpubNavigationList ReadNavigationList(XElement navigationListNode switch (navigationListChildNode.Name.LocalName.ToLowerInvariant()) { case "navlabel": - EpubNavigationLabel navigationLabel = ReadNavigationLabel(navigationListChildNode); + Epub2NcxNavigationLabel navigationLabel = ReadNavigationLabel(navigationListChildNode); result.NavigationLabels.Add(navigationLabel); break; case "navTarget": - EpubNavigationTarget navigationTarget = ReadNavigationTarget(navigationListChildNode); + Epub2NcxNavigationTarget navigationTarget = ReadNavigationTarget(navigationListChildNode); result.NavigationTargets.Add(navigationTarget); break; } @@ -362,9 +363,9 @@ private static EpubNavigationList ReadNavigationList(XElement navigationListNode return result; } - private static EpubNavigationTarget ReadNavigationTarget(XElement navigationTargetNode) + private static Epub2NcxNavigationTarget ReadNavigationTarget(XElement navigationTargetNode) { - EpubNavigationTarget result = new EpubNavigationTarget(); + Epub2NcxNavigationTarget result = new Epub2NcxNavigationTarget(); foreach (XAttribute navigationPageTargetNodeAttribute in navigationTargetNode.Attributes()) { string attributeValue = navigationPageTargetNodeAttribute.Value; @@ -393,11 +394,11 @@ private static EpubNavigationTarget ReadNavigationTarget(XElement navigationTarg switch (navigationTargetChildNode.Name.LocalName.ToLowerInvariant()) { case "navlabel": - EpubNavigationLabel navigationLabel = ReadNavigationLabel(navigationTargetChildNode); + Epub2NcxNavigationLabel navigationLabel = ReadNavigationLabel(navigationTargetChildNode); result.NavigationLabels.Add(navigationLabel); break; case "content": - EpubNavigationContent content = ReadNavigationContent(navigationTargetChildNode); + Epub2NcxContent content = ReadNavigationContent(navigationTargetChildNode); result.Content = content; break; } diff --git a/Source/VersOne.Epub/Readers/Epub3NavDocumentReader.cs b/Source/VersOne.Epub/Readers/Epub3NavDocumentReader.cs new file mode 100644 index 0000000..4a66a5a --- /dev/null +++ b/Source/VersOne.Epub/Readers/Epub3NavDocumentReader.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; +using VersOne.Epub.Schema; + +namespace VersOne.Epub.Internal +{ + internal static class Epub3NavDocumentReader + { + public static async Task ReadEpub3NavDocumentAsync(ZipArchive epubArchive, string contentDirectoryPath, EpubPackage package) + { + Epub3NavDocument result = new Epub3NavDocument(); + EpubManifestItem navManifestItem = + package.Manifest.FirstOrDefault(item => item.Properties != null && item.Properties.Contains(ManifestProperty.NAV)); + if (navManifestItem == null) + { + throw new Exception("EPUB parsing error: NAV item not found in EPUB manifest."); + } + string navFileEntryPath = ZipPathUtils.Combine(contentDirectoryPath, navManifestItem.Href); + ZipArchiveEntry navFileEntry = epubArchive.GetEntry(navFileEntryPath); + if (navFileEntry == null) + { + throw new Exception($"EPUB parsing error: navigation file {navFileEntryPath} not found in archive."); + } + if (navFileEntry.Length > Int32.MaxValue) + { + throw new Exception($"EPUB parsing error: navigation file {navFileEntryPath} is larger than 2 Gb."); + } + XDocument navDocument; + using (Stream containerStream = navFileEntry.Open()) + { + navDocument = await XmlUtils.LoadDocumentAsync(containerStream).ConfigureAwait(false); + } + XNamespace xhtmlNamespace = navDocument.Root.Name.Namespace; + XElement htmlNode = navDocument.Element(xhtmlNamespace + "html"); + if (htmlNode == null) + { + throw new Exception("EPUB parsing error: navigation file does not contain html element."); + } + XElement bodyNode = htmlNode.Element(xhtmlNamespace + "body"); + if (bodyNode == null) + { + throw new Exception("EPUB parsing error: navigation file does not contain body element."); + } + result.Navs = new List(); + foreach (XElement navNode in bodyNode.Elements(xhtmlNamespace + "nav")) + { + Epub3Nav epub3Nav = ReadEpub3Nav(navNode); + result.Navs.Add(epub3Nav); + } + return result; + } + + private static Epub3Nav ReadEpub3Nav(XElement navNode) + { + Epub3Nav epub3Nav = new Epub3Nav(); + foreach (XAttribute navNodeAttribute in navNode.Attributes()) + { + string attributeValue = navNodeAttribute.Value; + switch (navNodeAttribute.Name.LocalName.ToLowerInvariant()) + { + case "type": + epub3Nav.Type = StructuralSemanticsPropertyParser.Parse(attributeValue); + break; + case "hidden": + epub3Nav.IsHidden = true; + break; + } + } + foreach (XElement navChildNode in navNode.Elements()) + { + switch (navChildNode.Name.LocalName.ToLowerInvariant()) + { + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + epub3Nav.Head = navChildNode.Value.Trim(); + break; + case "ol": + Epub3NavOl epub3NavOl = ReadEpub3NavOl(navChildNode); + epub3Nav.Ol = epub3NavOl; + break; + } + } + return epub3Nav; + } + + private static Epub3NavOl ReadEpub3NavOl(XElement epub3NavOlNode) + { + Epub3NavOl epub3NavOl = new Epub3NavOl(); + foreach (XAttribute navOlNodeAttribute in epub3NavOlNode.Attributes()) + { + string attributeValue = navOlNodeAttribute.Value; + switch (navOlNodeAttribute.Name.LocalName.ToLowerInvariant()) + { + case "hidden": + epub3NavOl.IsHidden = true; + break; + } + } + epub3NavOl.Lis = new List(); + foreach (XElement navOlChildNode in epub3NavOlNode.Elements()) + { + switch (navOlChildNode.Name.LocalName.ToLowerInvariant()) + { + case "li": + Epub3NavLi epub3NavLi = ReadEpub3NavLi(navOlChildNode); + epub3NavOl.Lis.Add(epub3NavLi); + break; + } + } + return epub3NavOl; + } + + private static Epub3NavLi ReadEpub3NavLi(XElement epub3NavLiNode) + { + Epub3NavLi epub3NavLi = new Epub3NavLi(); + foreach (XElement navLiChildNode in epub3NavLiNode.Elements()) + { + switch (navLiChildNode.Name.LocalName.ToLowerInvariant()) + { + case "a": + Epub3NavAnchor epub3NavAnchor = ReadEpub3NavAnchor(navLiChildNode); + epub3NavLi.Anchor = epub3NavAnchor; + break; + case "span": + Epub3NavSpan epub3NavSpan = ReadEpub3NavSpan(navLiChildNode); + epub3NavLi.Span = epub3NavSpan; + break; + case "ol": + Epub3NavOl epub3NavOl = ReadEpub3NavOl(navLiChildNode); + epub3NavLi.ChildOl = epub3NavOl; + break; + } + } + return epub3NavLi; + } + + private static Epub3NavAnchor ReadEpub3NavAnchor(XElement epub3NavAnchorNode) + { + Epub3NavAnchor epub3NavAnchor = new Epub3NavAnchor(); + foreach (XAttribute navAnchorNodeAttribute in epub3NavAnchorNode.Attributes()) + { + string attributeValue = navAnchorNodeAttribute.Value; + switch (navAnchorNodeAttribute.Name.LocalName.ToLowerInvariant()) + { + case "href": + epub3NavAnchor.Href = attributeValue; + break; + case "title": + epub3NavAnchor.Title = attributeValue; + break; + case "alt": + epub3NavAnchor.Alt = attributeValue; + break; + case "type": + epub3NavAnchor.Type = StructuralSemanticsPropertyParser.Parse(attributeValue); + break; + } + } + epub3NavAnchor.Text = epub3NavAnchorNode.Value.Trim(); + return epub3NavAnchor; + } + + private static Epub3NavSpan ReadEpub3NavSpan(XElement epub3NavSpanNode) + { + Epub3NavSpan epub3NavSpan = new Epub3NavSpan(); + foreach (XAttribute navSpanNodeAttribute in epub3NavSpanNode.Attributes()) + { + string attributeValue = navSpanNodeAttribute.Value; + switch (navSpanNodeAttribute.Name.LocalName.ToLowerInvariant()) + { + case "title": + epub3NavSpan.Title = attributeValue; + break; + case "alt": + epub3NavSpan.Alt = attributeValue; + break; + } + } + epub3NavSpan.Text = epub3NavSpanNode.Value.Trim(); + return epub3NavSpan; + } + } +} diff --git a/Source/VersOne.Epub/Readers/PackageReader.cs b/Source/VersOne.Epub/Readers/PackageReader.cs index 230d9fb..cbd196a 100644 --- a/Source/VersOne.Epub/Readers/PackageReader.cs +++ b/Source/VersOne.Epub/Readers/PackageReader.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using System.Xml.Linq; using VersOne.Epub.Schema; +using VersOne.Epub.Utils; namespace VersOne.Epub.Internal { @@ -26,18 +27,22 @@ public static async Task ReadPackageAsync(ZipArchive epubArchive, s XElement packageNode = containerDocument.Element(opfNamespace + "package"); EpubPackage result = new EpubPackage(); string epubVersionValue = packageNode.Attribute("version").Value; - if (epubVersionValue == "2.0") + EpubVersion epubVersion; + switch (epubVersionValue) { - result.EpubVersion = EpubVersion.EPUB_2; - } - else if (epubVersionValue == "3.0") - { - result.EpubVersion = EpubVersion.EPUB_3; - } - else - { - throw new Exception(String.Format("Unsupported EPUB version: {0}.", epubVersionValue)); + case "2.0": + epubVersion = EpubVersion.EPUB_2; + break; + case "3.0": + epubVersion = EpubVersion.EPUB_3_0; + break; + case "3.1": + epubVersion = EpubVersion.EPUB_3_1; + break; + default: + throw new Exception($"Unsupported EPUB version: {epubVersionValue}."); } + result.EpubVersion = epubVersion; XElement metadataNode = packageNode.Element(opfNamespace + "metadata"); if (metadataNode == null) { @@ -57,7 +62,7 @@ public static async Task ReadPackageAsync(ZipArchive epubArchive, s { throw new Exception("EPUB parsing error: spine not found in the package."); } - EpubSpine spine = ReadSpine(spineNode); + EpubSpine spine = ReadSpine(spineNode, epubVersion); result.Spine = spine; XElement guideNode = packageNode.Element(opfNamespace + "guide"); if (guideNode != null) @@ -148,7 +153,7 @@ private static EpubMetadata ReadMetadata(XElement metadataNode, EpubVersion epub EpubMetadataMeta meta = ReadMetadataMetaVersion2(metadataItemNode); result.MetaItems.Add(meta); } - else if (epubVersion == EpubVersion.EPUB_3) + else if (epubVersion == EpubVersion.EPUB_3_0 || epubVersion == EpubVersion.EPUB_3_1) { EpubMetadataMeta meta = ReadMetadataMetaVersion3(metadataItemNode); result.MetaItems.Add(meta); @@ -310,6 +315,9 @@ private static EpubManifest ReadManifest(XElement manifestNode) case "fallback-style": manifestItem.FallbackStyle = attributeValue; break; + case "properties": + manifestItem.Properties = ReadManifestProperties(attributeValue); + break; } } if (String.IsNullOrWhiteSpace(manifestItem.Id)) @@ -330,18 +338,31 @@ private static EpubManifest ReadManifest(XElement manifestNode) return result; } - private static EpubSpine ReadSpine(XElement spineNode) + private static List ReadManifestProperties(string propertiesAttributeValue) + { + List result = new List(); + foreach (string propertyStringValue in propertiesAttributeValue.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)) + { + result.Add(ManifestPropertyParser.Parse(propertyStringValue)); + } + return result; + } + + private static EpubSpine ReadSpine(XElement spineNode, EpubVersion epubVersion) { EpubSpine result = new EpubSpine(); XAttribute tocAttribute = spineNode.Attribute("toc"); - if (tocAttribute == null || String.IsNullOrWhiteSpace(tocAttribute.Value)) + if (tocAttribute != null && !String.IsNullOrWhiteSpace(tocAttribute.Value)) + { + result.Toc = tocAttribute.Value; + } + else if (epubVersion == EpubVersion.EPUB_2) { throw new Exception("Incorrect EPUB spine: TOC is missing"); } - result.Toc = tocAttribute.Value; foreach (XElement spineItemNode in spineNode.Elements()) { - if (String.Compare(spineItemNode.Name.LocalName, "itemref", StringComparison.OrdinalIgnoreCase) == 0) + if (spineItemNode.CompareNameTo("itemref")) { EpubSpineItemRef spineItemRef = new EpubSpineItemRef(); XAttribute idRefAttribute = spineItemNode.Attribute("idref"); @@ -351,7 +372,7 @@ private static EpubSpine ReadSpine(XElement spineNode) } spineItemRef.IdRef = idRefAttribute.Value; XAttribute linearAttribute = spineItemNode.Attribute("linear"); - spineItemRef.IsLinear = linearAttribute == null || String.Compare(linearAttribute.Value, "no", StringComparison.OrdinalIgnoreCase) != 0; + spineItemRef.IsLinear = linearAttribute == null || !linearAttribute.CompareValueTo("no"); result.Add(spineItemRef); } } diff --git a/Source/VersOne.Epub/Readers/SchemaReader.cs b/Source/VersOne.Epub/Readers/SchemaReader.cs index a4a00e9..e6dea27 100644 --- a/Source/VersOne.Epub/Readers/SchemaReader.cs +++ b/Source/VersOne.Epub/Readers/SchemaReader.cs @@ -14,8 +14,15 @@ public static async Task ReadSchemaAsync(ZipArchive epubArchive) result.ContentDirectoryPath = contentDirectoryPath; EpubPackage package = await PackageReader.ReadPackageAsync(epubArchive, rootFilePath).ConfigureAwait(false); result.Package = package; - EpubNavigation navigation = await NavigationReader.ReadNavigationAsync(epubArchive, contentDirectoryPath, package).ConfigureAwait(false); - result.Navigation = navigation; + if (package.EpubVersion == EpubVersion.EPUB_2) + { + Epub2Ncx epub2Ncx = await Epub2NcxReader.ReadEpub2NcxAsync(epubArchive, contentDirectoryPath, package).ConfigureAwait(false); + result.Navigation = epub2Ncx; + } + else + { + Epub3NavDocument epub3NavDocumentepub3Nav = await Epub3NavDocumentReader.ReadEpub3NavDocumentAsync(epubArchive, contentDirectoryPath, package).ConfigureAwait(false); + } return result; } } diff --git a/Source/VersOne.Epub/Schema/Common/ManifestProperty.cs b/Source/VersOne.Epub/Schema/Common/ManifestProperty.cs new file mode 100644 index 0000000..3439104 --- /dev/null +++ b/Source/VersOne.Epub/Schema/Common/ManifestProperty.cs @@ -0,0 +1,37 @@ +namespace VersOne.Epub.Schema +{ + public enum ManifestProperty + { + COVER_IMAGE = 1, + MATHML, + NAV, + REMOTE_RESOURCES, + SCRIPTED, + SVG, + UNKNOWN + } + + internal static class ManifestPropertyParser + { + public static ManifestProperty Parse(string stringValue) + { + switch (stringValue.ToLowerInvariant()) + { + case "cover-image": + return ManifestProperty.COVER_IMAGE; + case "mathml": + return ManifestProperty.MATHML; + case "nav": + return ManifestProperty.NAV; + case "remote-resources": + return ManifestProperty.REMOTE_RESOURCES; + case "scripted": + return ManifestProperty.SCRIPTED; + case "svg": + return ManifestProperty.SVG; + default: + return ManifestProperty.UNKNOWN; + } + } + } +} diff --git a/Source/VersOne.Epub/Schema/Common/StructuralSemanticsProperty.cs b/Source/VersOne.Epub/Schema/Common/StructuralSemanticsProperty.cs new file mode 100644 index 0000000..3041022 --- /dev/null +++ b/Source/VersOne.Epub/Schema/Common/StructuralSemanticsProperty.cs @@ -0,0 +1,409 @@ +namespace VersOne.Epub.Schema +{ + public enum StructuralSemanticsProperty + { + COVER = 1, + FRONTMATTER, + BODYMATTER, + BACKMATTER, + VOLUME, + PART, + CHAPTER, + SUBCHAPTER, + DIVISION, + ABSTRACT, + FOREWORD, + PREFACE, + PROLOGUE, + INTRODUCTION, + PREAMBLE, + CONCLUSION, + EPILOGUE, + AFTERWORD, + EPIGRAPH, + TOC, + TOC_BRIEF, + LANDMARKS, + LOA, + LOI, + LOT, + LOV, + APPENDIX, + COLOPHON, + CREDITS, + KEYWORDS, + INDEX, + INDEX_HEADNOTES, + INDEX_LEGEND, + INDEX_GROUP, + INDEX_ENTRY_LIST, + INDEX_ENTRY, + INDEX_TERM, + INDEX_EDITOR_NOTE, + INDEX_LOCATOR, + INDEX_LOCATOR_LIST, + INDEX_LOCATOR_RANGE, + INDEX_XREF_PREFERRED, + INDEX_XREF_RELATED, + INDEX_TERM_CATEGORY, + INDEX_TERM_CATEGORIES, + GLOSSARY, + GLOSSTERM, + GLOSSDEF, + BIBLIOGRAPHY, + BIBLIOENTRY, + TITLEPAGE, + HALFTITLEPAGE, + COPYRIGHT_PAGE, + SERIESPAGE, + ACKNOWLEDGMENTS, + IMPRINT, + IMPRIMATUR, + CONTRIBUTORS, + OTHER_CREDITS, + ERRATA, + DEDICATION, + REVISION_HISTORY, + CASE_STUDY, + HELP, + MARGINALIA, + NOTICE, + PULLQUOTE, + SIDEBAR, + TIP, + WARNING, + HALFTITLE, + FULLTITLE, + COVERTITLE, + TITLE, + SUBTITLE, + LABEL, + ORDINAL, + BRIDGEHEAD, + LEARNING_OBJECTIVE, + LEARNING_OBJECTIVES, + LEARNING_OUTCOME, + LEARNING_OUTCOMES, + LEARNING_RESOURCE, + LEARNING_RESOURCES, + LEARNING_STANDARD, + LEARNING_STANDARDS, + ANSWER, + ANSWERS, + ASSESSMENT, + ASSESSMENTS, + FEEDBACK, + FILL_IN_THE_BLANK_PROBLEM, + GENERAL_PROBLEM, + QNA, + MATCH_PROBLEM, + MULTIPLE_CHOICE_PROBLEM, + PRACTICE, + QUESTION, + PRACTICES, + TRUE_FALSE_PROBLEM, + PANEL, + PANEL_GROUP, + BALLOON, + TEXT_AREA, + SOUND_AREA, + ANNOTATION, + NOTE, + FOOTNOTE, + ENDNOTE, + REARNOTE, + FOOTNOTES, + ENDNOTES, + REARNOTES, + ANNOREF, + BIBLIOREF, + GLOSSREF, + NOTEREF, + BACKLINK, + CREDIT, + KEYWORD, + TOPIC_SENTENCE, + CONCLUDING_SENTENCE, + PAGEBREAK, + PAGE_LIST, + TABLE, + TABLE_ROW, + TABLE_CELL, + LIST, + LIST_ITEM, + FIGURE, + UNKNOWN + } + + internal static class StructuralSemanticsPropertyParser + { + public static StructuralSemanticsProperty Parse(string stringValue) + { + switch (stringValue.ToLowerInvariant()) + { + case "cover": + return StructuralSemanticsProperty.COVER; + case "frontmatter": + return StructuralSemanticsProperty.FRONTMATTER; + case "bodymatter": + return StructuralSemanticsProperty.BODYMATTER; + case "backmatter": + return StructuralSemanticsProperty.BACKMATTER; + case "volume": + return StructuralSemanticsProperty.VOLUME; + case "part": + return StructuralSemanticsProperty.PART; + case "chapter": + return StructuralSemanticsProperty.CHAPTER; + case "subchapter": + return StructuralSemanticsProperty.SUBCHAPTER; + case "division": + return StructuralSemanticsProperty.DIVISION; + case "abstract": + return StructuralSemanticsProperty.ABSTRACT; + case "foreword": + return StructuralSemanticsProperty.FOREWORD; + case "preface": + return StructuralSemanticsProperty.PREFACE; + case "prologue": + return StructuralSemanticsProperty.PROLOGUE; + case "introduction": + return StructuralSemanticsProperty.INTRODUCTION; + case "preamble": + return StructuralSemanticsProperty.PREAMBLE; + case "conclusion": + return StructuralSemanticsProperty.CONCLUSION; + case "epilogue": + return StructuralSemanticsProperty.EPILOGUE; + case "afterword": + return StructuralSemanticsProperty.AFTERWORD; + case "epigraph": + return StructuralSemanticsProperty.EPIGRAPH; + case "toc": + return StructuralSemanticsProperty.TOC; + case "toc-brief": + return StructuralSemanticsProperty.TOC_BRIEF; + case "landmarks": + return StructuralSemanticsProperty.LANDMARKS; + case "loa": + return StructuralSemanticsProperty.LOA; + case "loi": + return StructuralSemanticsProperty.LOI; + case "lot": + return StructuralSemanticsProperty.LOT; + case "lov": + return StructuralSemanticsProperty.LOV; + case "appendix": + return StructuralSemanticsProperty.APPENDIX; + case "colophon": + return StructuralSemanticsProperty.COLOPHON; + case "credits": + return StructuralSemanticsProperty.CREDITS; + case "keywords": + return StructuralSemanticsProperty.KEYWORDS; + case "index": + return StructuralSemanticsProperty.INDEX; + case "index-headnotes": + return StructuralSemanticsProperty.INDEX_HEADNOTES; + case "index-legend": + return StructuralSemanticsProperty.INDEX_LEGEND; + case "index-group": + return StructuralSemanticsProperty.INDEX_GROUP; + case "index-entry-list": + return StructuralSemanticsProperty.INDEX_ENTRY_LIST; + case "index-entry": + return StructuralSemanticsProperty.INDEX_ENTRY; + case "index-term": + return StructuralSemanticsProperty.INDEX_TERM; + case "index-editor-note": + return StructuralSemanticsProperty.INDEX_EDITOR_NOTE; + case "index-locator": + return StructuralSemanticsProperty.INDEX_LOCATOR; + case "index-locator-list": + return StructuralSemanticsProperty.INDEX_LOCATOR_LIST; + case "index-locator-range": + return StructuralSemanticsProperty.INDEX_LOCATOR_RANGE; + case "index-xref-preferred": + return StructuralSemanticsProperty.INDEX_XREF_PREFERRED; + case "index-xref-related": + return StructuralSemanticsProperty.INDEX_XREF_RELATED; + case "index-term-category": + return StructuralSemanticsProperty.INDEX_TERM_CATEGORY; + case "index-term-categories": + return StructuralSemanticsProperty.INDEX_TERM_CATEGORIES; + case "glossary": + return StructuralSemanticsProperty.GLOSSARY; + case "glossterm": + return StructuralSemanticsProperty.GLOSSTERM; + case "glossdef": + return StructuralSemanticsProperty.GLOSSDEF; + case "bibliography": + return StructuralSemanticsProperty.BIBLIOGRAPHY; + case "biblioentry": + return StructuralSemanticsProperty.BIBLIOENTRY; + case "titlepage": + return StructuralSemanticsProperty.TITLEPAGE; + case "halftitlepage": + return StructuralSemanticsProperty.HALFTITLEPAGE; + case "copyright-page": + return StructuralSemanticsProperty.COPYRIGHT_PAGE; + case "seriespage": + return StructuralSemanticsProperty.SERIESPAGE; + case "acknowledgments": + return StructuralSemanticsProperty.ACKNOWLEDGMENTS; + case "imprint": + return StructuralSemanticsProperty.IMPRINT; + case "imprimatur": + return StructuralSemanticsProperty.IMPRIMATUR; + case "contributors": + return StructuralSemanticsProperty.CONTRIBUTORS; + case "other-credits": + return StructuralSemanticsProperty.OTHER_CREDITS; + case "errata": + return StructuralSemanticsProperty.ERRATA; + case "dedication": + return StructuralSemanticsProperty.DEDICATION; + case "revision-history": + return StructuralSemanticsProperty.REVISION_HISTORY; + case "case-study": + return StructuralSemanticsProperty.CASE_STUDY; + case "help": + return StructuralSemanticsProperty.HELP; + case "marginalia": + return StructuralSemanticsProperty.MARGINALIA; + case "notice": + return StructuralSemanticsProperty.NOTICE; + case "pullquote": + return StructuralSemanticsProperty.PULLQUOTE; + case "sidebar": + return StructuralSemanticsProperty.SIDEBAR; + case "tip": + return StructuralSemanticsProperty.TIP; + case "warning": + return StructuralSemanticsProperty.WARNING; + case "halftitle": + return StructuralSemanticsProperty.HALFTITLE; + case "fulltitle": + return StructuralSemanticsProperty.FULLTITLE; + case "covertitle": + return StructuralSemanticsProperty.COVERTITLE; + case "title": + return StructuralSemanticsProperty.TITLE; + case "subtitle": + return StructuralSemanticsProperty.SUBTITLE; + case "label": + return StructuralSemanticsProperty.LABEL; + case "ordinal": + return StructuralSemanticsProperty.ORDINAL; + case "bridgehead": + return StructuralSemanticsProperty.BRIDGEHEAD; + case "learning-objective": + return StructuralSemanticsProperty.LEARNING_OBJECTIVE; + case "learning-objectives": + return StructuralSemanticsProperty.LEARNING_OBJECTIVES; + case "learning-outcome": + return StructuralSemanticsProperty.LEARNING_OUTCOME; + case "learning-outcomes": + return StructuralSemanticsProperty.LEARNING_OUTCOMES; + case "learning-resource": + return StructuralSemanticsProperty.LEARNING_RESOURCE; + case "learning-resources": + return StructuralSemanticsProperty.LEARNING_RESOURCES; + case "learning-standard": + return StructuralSemanticsProperty.LEARNING_STANDARD; + case "learning-standards": + return StructuralSemanticsProperty.LEARNING_STANDARDS; + case "answer": + return StructuralSemanticsProperty.ANSWER; + case "answers": + return StructuralSemanticsProperty.ANSWERS; + case "assessment": + return StructuralSemanticsProperty.ASSESSMENT; + case "assessments": + return StructuralSemanticsProperty.ASSESSMENTS; + case "feedback": + return StructuralSemanticsProperty.FEEDBACK; + case "fill-in-the-blank-problem": + return StructuralSemanticsProperty.FILL_IN_THE_BLANK_PROBLEM; + case "general-problem": + return StructuralSemanticsProperty.GENERAL_PROBLEM; + case "qna": + return StructuralSemanticsProperty.QNA; + case "match-problem": + return StructuralSemanticsProperty.MATCH_PROBLEM; + case "multiple-choice-problem": + return StructuralSemanticsProperty.MULTIPLE_CHOICE_PROBLEM; + case "practice": + return StructuralSemanticsProperty.PRACTICE; + case "question": + return StructuralSemanticsProperty.QUESTION; + case "practices": + return StructuralSemanticsProperty.PRACTICES; + case "true-false-problem": + return StructuralSemanticsProperty.TRUE_FALSE_PROBLEM; + case "panel": + return StructuralSemanticsProperty.PANEL; + case "panel-group": + return StructuralSemanticsProperty.PANEL_GROUP; + case "balloon": + return StructuralSemanticsProperty.BALLOON; + case "text-area": + return StructuralSemanticsProperty.TEXT_AREA; + case "sound-area": + return StructuralSemanticsProperty.SOUND_AREA; + case "annotation": + return StructuralSemanticsProperty.ANNOTATION; + case "note": + return StructuralSemanticsProperty.NOTE; + case "footnote": + return StructuralSemanticsProperty.FOOTNOTE; + case "endnote": + return StructuralSemanticsProperty.ENDNOTE; + case "rearnote": + return StructuralSemanticsProperty.REARNOTE; + case "footnotes": + return StructuralSemanticsProperty.FOOTNOTES; + case "endnotes": + return StructuralSemanticsProperty.ENDNOTES; + case "rearnotes": + return StructuralSemanticsProperty.REARNOTES; + case "annoref": + return StructuralSemanticsProperty.ANNOREF; + case "biblioref": + return StructuralSemanticsProperty.BIBLIOREF; + case "glossref": + return StructuralSemanticsProperty.GLOSSREF; + case "noteref": + return StructuralSemanticsProperty.NOTEREF; + case "backlink": + return StructuralSemanticsProperty.BACKLINK; + case "credit": + return StructuralSemanticsProperty.CREDIT; + case "keyword": + return StructuralSemanticsProperty.KEYWORD; + case "topic-sentence": + return StructuralSemanticsProperty.TOPIC_SENTENCE; + case "concluding-sentence": + return StructuralSemanticsProperty.CONCLUDING_SENTENCE; + case "pagebreak": + return StructuralSemanticsProperty.PAGEBREAK; + case "page-list": + return StructuralSemanticsProperty.PAGE_LIST; + case "table": + return StructuralSemanticsProperty.TABLE; + case "table-row": + return StructuralSemanticsProperty.TABLE_ROW; + case "table-cell": + return StructuralSemanticsProperty.TABLE_CELL; + case "list": + return StructuralSemanticsProperty.LIST; + case "list-item": + return StructuralSemanticsProperty.LIST_ITEM; + case "figure": + return StructuralSemanticsProperty.FIGURE; + default: + return StructuralSemanticsProperty.UNKNOWN; + } + } + } +} diff --git a/Source/VersOne.Epub/Schema/Navigation/EpubNavigation.cs b/Source/VersOne.Epub/Schema/Navigation/EpubNavigation.cs deleted file mode 100644 index cd72147..0000000 --- a/Source/VersOne.Epub/Schema/Navigation/EpubNavigation.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; - -namespace VersOne.Epub.Schema -{ - public class EpubNavigation - { - public EpubNavigationHead Head { get; set; } - public EpubNavigationDocTitle DocTitle { get; set; } - public List DocAuthors { get; set; } - public EpubNavigationMap NavMap { get; set; } - public EpubNavigationPageList PageList { get; set; } - public List NavLists { get; set; } - } -} diff --git a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationList.cs b/Source/VersOne.Epub/Schema/Navigation/EpubNavigationList.cs deleted file mode 100644 index 325ce50..0000000 --- a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationList.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; - -namespace VersOne.Epub.Schema -{ - public class EpubNavigationList - { - public string Id { get; set; } - public string Class { get; set; } - public List NavigationLabels { get; set; } - public List NavigationTargets { get; set; } - } -} diff --git a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationPageList.cs b/Source/VersOne.Epub/Schema/Navigation/EpubNavigationPageList.cs deleted file mode 100644 index 8aae30e..0000000 --- a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationPageList.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Collections.Generic; - -namespace VersOne.Epub.Schema -{ - public class EpubNavigationPageList : List - { - } -} diff --git a/Source/VersOne.Epub/Schema/Ncx/Epub2Ncx.cs b/Source/VersOne.Epub/Schema/Ncx/Epub2Ncx.cs new file mode 100644 index 0000000..4de232a --- /dev/null +++ b/Source/VersOne.Epub/Schema/Ncx/Epub2Ncx.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace VersOne.Epub.Schema +{ + public class Epub2Ncx + { + public Epub2NcxHead Head { get; set; } + public Epub2NcxDocTitle DocTitle { get; set; } + public List DocAuthors { get; set; } + public Epub2NcxNavigationMap NavMap { get; set; } + public Epub2NcxPageList PageList { get; set; } + public List NavLists { get; set; } + } +} diff --git a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationContent.cs b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxContent.cs similarity index 87% rename from Source/VersOne.Epub/Schema/Navigation/EpubNavigationContent.cs rename to Source/VersOne.Epub/Schema/Ncx/Epub2NcxContent.cs index d1fab6f..eb220ff 100644 --- a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationContent.cs +++ b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxContent.cs @@ -2,7 +2,7 @@ namespace VersOne.Epub.Schema { - public class EpubNavigationContent + public class Epub2NcxContent { public string Id { get; set; } public string Source { get; set; } diff --git a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationDocTitle.cs b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxDocAuthor.cs similarity index 60% rename from Source/VersOne.Epub/Schema/Navigation/EpubNavigationDocTitle.cs rename to Source/VersOne.Epub/Schema/Ncx/Epub2NcxDocAuthor.cs index 8a48fa0..ee9d024 100644 --- a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationDocTitle.cs +++ b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxDocAuthor.cs @@ -2,7 +2,7 @@ namespace VersOne.Epub.Schema { - public class EpubNavigationDocTitle : List + public class Epub2NcxDocAuthor : List { } } diff --git a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationDocAuthor.cs b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxDocTitle.cs similarity index 60% rename from Source/VersOne.Epub/Schema/Navigation/EpubNavigationDocAuthor.cs rename to Source/VersOne.Epub/Schema/Ncx/Epub2NcxDocTitle.cs index 21b1fb4..719cfc3 100644 --- a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationDocAuthor.cs +++ b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxDocTitle.cs @@ -2,7 +2,7 @@ namespace VersOne.Epub.Schema { - public class EpubNavigationDocAuthor : List + public class Epub2NcxDocTitle : List { } } diff --git a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationMap.cs b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxHead.cs similarity index 57% rename from Source/VersOne.Epub/Schema/Navigation/EpubNavigationMap.cs rename to Source/VersOne.Epub/Schema/Ncx/Epub2NcxHead.cs index dc01371..6adcb7c 100644 --- a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationMap.cs +++ b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxHead.cs @@ -2,7 +2,7 @@ namespace VersOne.Epub.Schema { - public class EpubNavigationMap : List + public class Epub2NcxHead : List { } } diff --git a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationHeadMeta.cs b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxHeadMeta.cs similarity index 81% rename from Source/VersOne.Epub/Schema/Navigation/EpubNavigationHeadMeta.cs rename to Source/VersOne.Epub/Schema/Ncx/Epub2NcxHeadMeta.cs index d6637b4..a9d1ac3 100644 --- a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationHeadMeta.cs +++ b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxHeadMeta.cs @@ -1,6 +1,6 @@ namespace VersOne.Epub.Schema { - public class EpubNavigationHeadMeta + public class Epub2NcxHeadMeta { public string Name { get; set; } public string Content { get; set; } diff --git a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationLabel.cs b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxNavigationLabel.cs similarity index 81% rename from Source/VersOne.Epub/Schema/Navigation/EpubNavigationLabel.cs rename to Source/VersOne.Epub/Schema/Ncx/Epub2NcxNavigationLabel.cs index 97e5802..735dc00 100644 --- a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationLabel.cs +++ b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxNavigationLabel.cs @@ -1,6 +1,6 @@ namespace VersOne.Epub.Schema { - public class EpubNavigationLabel + public class Epub2NcxNavigationLabel { public string Text { get; set; } diff --git a/Source/VersOne.Epub/Schema/Ncx/Epub2NcxNavigationList.cs b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxNavigationList.cs new file mode 100644 index 0000000..4c2c80e --- /dev/null +++ b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxNavigationList.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace VersOne.Epub.Schema +{ + public class Epub2NcxNavigationList + { + public string Id { get; set; } + public string Class { get; set; } + public List NavigationLabels { get; set; } + public List NavigationTargets { get; set; } + } +} diff --git a/Source/VersOne.Epub/Schema/Ncx/Epub2NcxNavigationMap.cs b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxNavigationMap.cs new file mode 100644 index 0000000..d38308b --- /dev/null +++ b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxNavigationMap.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace VersOne.Epub.Schema +{ + public class Epub2NcxNavigationMap : List + { + } +} diff --git a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationPoint.cs b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxNavigationPoint.cs similarity index 59% rename from Source/VersOne.Epub/Schema/Navigation/EpubNavigationPoint.cs rename to Source/VersOne.Epub/Schema/Ncx/Epub2NcxNavigationPoint.cs index 084e8f6..b6a4e2c 100644 --- a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationPoint.cs +++ b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxNavigationPoint.cs @@ -3,14 +3,14 @@ namespace VersOne.Epub.Schema { - public class EpubNavigationPoint + public class Epub2NcxNavigationPoint { public string Id { get; set; } public string Class { get; set; } public string PlayOrder { get; set; } - public List NavigationLabels { get; set; } - public EpubNavigationContent Content { get; set; } - public List ChildNavigationPoints { get; set; } + public List NavigationLabels { get; set; } + public Epub2NcxContent Content { get; set; } + public List ChildNavigationPoints { get; set; } public override string ToString() { diff --git a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationTarget.cs b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxNavigationTarget.cs similarity index 59% rename from Source/VersOne.Epub/Schema/Navigation/EpubNavigationTarget.cs rename to Source/VersOne.Epub/Schema/Ncx/Epub2NcxNavigationTarget.cs index ea5ac2a..973147f 100644 --- a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationTarget.cs +++ b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxNavigationTarget.cs @@ -2,13 +2,13 @@ namespace VersOne.Epub.Schema { - public class EpubNavigationTarget + public class Epub2NcxNavigationTarget { public string Id { get; set; } public string Class { get; set; } public string Value { get; set; } public string PlayOrder { get; set; } - public List NavigationLabels { get; set; } - public EpubNavigationContent Content { get; set; } + public List NavigationLabels { get; set; } + public Epub2NcxContent Content { get; set; } } } diff --git a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationHead.cs b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxPageList.cs similarity index 55% rename from Source/VersOne.Epub/Schema/Navigation/EpubNavigationHead.cs rename to Source/VersOne.Epub/Schema/Ncx/Epub2NcxPageList.cs index fb00035..928f5c3 100644 --- a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationHead.cs +++ b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxPageList.cs @@ -2,7 +2,7 @@ namespace VersOne.Epub.Schema { - public class EpubNavigationHead : List + public class Epub2NcxPageList : List { } } diff --git a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationPageTarget.cs b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxPageTarget.cs similarity index 51% rename from Source/VersOne.Epub/Schema/Navigation/EpubNavigationPageTarget.cs rename to Source/VersOne.Epub/Schema/Ncx/Epub2NcxPageTarget.cs index bb0b395..b712afa 100644 --- a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationPageTarget.cs +++ b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxPageTarget.cs @@ -2,14 +2,14 @@ namespace VersOne.Epub.Schema { - public class EpubNavigationPageTarget + public class Epub2NcxPageTarget { public string Id { get; set; } public string Value { get; set; } - public EpubNavigationPageTargetType Type { get; set; } + public Epub2NcxPageTargetType Type { get; set; } public string Class { get; set; } public string PlayOrder { get; set; } - public List NavigationLabels { get; set; } - public EpubNavigationContent Content { get; set; } + public List NavigationLabels { get; set; } + public Epub2NcxContent Content { get; set; } } } diff --git a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationPageTargetType.cs b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxPageTargetType.cs similarity index 72% rename from Source/VersOne.Epub/Schema/Navigation/EpubNavigationPageTargetType.cs rename to Source/VersOne.Epub/Schema/Ncx/Epub2NcxPageTargetType.cs index 8b09fdc..850c4a5 100644 --- a/Source/VersOne.Epub/Schema/Navigation/EpubNavigationPageTargetType.cs +++ b/Source/VersOne.Epub/Schema/Ncx/Epub2NcxPageTargetType.cs @@ -1,6 +1,6 @@ namespace VersOne.Epub.Schema { - public enum EpubNavigationPageTargetType + public enum Epub2NcxPageTargetType { FRONT = 1, NORMAL, diff --git a/Source/VersOne.Epub/Schema/Opf/EpubManifestItem.cs b/Source/VersOne.Epub/Schema/Opf/EpubManifestItem.cs index 4181589..89cc692 100644 --- a/Source/VersOne.Epub/Schema/Opf/EpubManifestItem.cs +++ b/Source/VersOne.Epub/Schema/Opf/EpubManifestItem.cs @@ -1,4 +1,4 @@ -using System; +using System.Collections.Generic; namespace VersOne.Epub.Schema { @@ -11,10 +11,11 @@ public class EpubManifestItem public string RequiredModules { get; set; } public string Fallback { get; set; } public string FallbackStyle { get; set; } + public List Properties { get; set; } public override string ToString() { - return String.Format("Id: {0}, Href = {1}, MediaType = {2}", Id, Href, MediaType); + return $"Id: {Id}, Href = {Href}, MediaType = {MediaType}"; } } } diff --git a/Source/VersOne.Epub/Schema/Opf/EpubVersion.cs b/Source/VersOne.Epub/Schema/Opf/EpubVersion.cs index b208e06..5c86194 100644 --- a/Source/VersOne.Epub/Schema/Opf/EpubVersion.cs +++ b/Source/VersOne.Epub/Schema/Opf/EpubVersion.cs @@ -3,6 +3,7 @@ public enum EpubVersion { EPUB_2 = 2, - EPUB_3 + EPUB_3_0, + EPUB_3_1 } } diff --git a/Source/VersOne.Epub/Schema/Ops/Epub3Nav.cs b/Source/VersOne.Epub/Schema/Ops/Epub3Nav.cs new file mode 100644 index 0000000..8ed049c --- /dev/null +++ b/Source/VersOne.Epub/Schema/Ops/Epub3Nav.cs @@ -0,0 +1,10 @@ +namespace VersOne.Epub.Schema +{ + public class Epub3Nav + { + public StructuralSemanticsProperty? Type { get; set; } + public bool IsHidden { get; set; } + public string Head { get; set; } + public Epub3NavOl Ol { get; set; } + } +} diff --git a/Source/VersOne.Epub/Schema/Ops/Epub3NavAnchor.cs b/Source/VersOne.Epub/Schema/Ops/Epub3NavAnchor.cs new file mode 100644 index 0000000..e6be969 --- /dev/null +++ b/Source/VersOne.Epub/Schema/Ops/Epub3NavAnchor.cs @@ -0,0 +1,11 @@ +namespace VersOne.Epub.Schema +{ + public class Epub3NavAnchor + { + public string Href { get; set; } + public string Text { get; set; } + public string Title { get; set; } + public string Alt { get; set; } + public StructuralSemanticsProperty? Type { get; set; } + } +} diff --git a/Source/VersOne.Epub/Schema/Ops/Epub3NavDocument.cs b/Source/VersOne.Epub/Schema/Ops/Epub3NavDocument.cs new file mode 100644 index 0000000..c6b1789 --- /dev/null +++ b/Source/VersOne.Epub/Schema/Ops/Epub3NavDocument.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace VersOne.Epub.Schema +{ + public class Epub3NavDocument + { + public List Navs { get; set; } + } +} diff --git a/Source/VersOne.Epub/Schema/Ops/Epub3NavLi.cs b/Source/VersOne.Epub/Schema/Ops/Epub3NavLi.cs new file mode 100644 index 0000000..797713d --- /dev/null +++ b/Source/VersOne.Epub/Schema/Ops/Epub3NavLi.cs @@ -0,0 +1,9 @@ +namespace VersOne.Epub.Schema +{ + public class Epub3NavLi + { + public Epub3NavAnchor Anchor { get; set; } + public Epub3NavSpan Span { get; set; } + public Epub3NavOl ChildOl { get; set; } + } +} diff --git a/Source/VersOne.Epub/Schema/Ops/Epub3NavOl.cs b/Source/VersOne.Epub/Schema/Ops/Epub3NavOl.cs new file mode 100644 index 0000000..9a642bf --- /dev/null +++ b/Source/VersOne.Epub/Schema/Ops/Epub3NavOl.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace VersOne.Epub.Schema +{ + public class Epub3NavOl + { + public bool IsHidden { get; set; } + public List Lis { get; set; } + } +} diff --git a/Source/VersOne.Epub/Schema/Ops/Epub3NavSpan.cs b/Source/VersOne.Epub/Schema/Ops/Epub3NavSpan.cs new file mode 100644 index 0000000..aa83604 --- /dev/null +++ b/Source/VersOne.Epub/Schema/Ops/Epub3NavSpan.cs @@ -0,0 +1,9 @@ +namespace VersOne.Epub.Schema +{ + public class Epub3NavSpan + { + public string Text { get; set; } + public string Title { get; set; } + public string Alt { get; set; } + } +} diff --git a/Source/VersOne.Epub/Utils/StringExtensionMethods.cs b/Source/VersOne.Epub/Utils/StringExtensionMethods.cs new file mode 100644 index 0000000..5cc921b --- /dev/null +++ b/Source/VersOne.Epub/Utils/StringExtensionMethods.cs @@ -0,0 +1,12 @@ +using System; + +namespace VersOne.Epub.Utils +{ + internal static class StringExtensionMethods + { + public static bool CompareOrdinalIgnoreCase(this string source, string value) + { + return String.Compare(source, value, StringComparison.OrdinalIgnoreCase) == 0; + } + } +} diff --git a/Source/VersOne.Epub/Utils/XmlExtensionMethods.cs b/Source/VersOne.Epub/Utils/XmlExtensionMethods.cs new file mode 100644 index 0000000..29c63ca --- /dev/null +++ b/Source/VersOne.Epub/Utils/XmlExtensionMethods.cs @@ -0,0 +1,17 @@ +using System.Xml.Linq; + +namespace VersOne.Epub.Utils +{ + internal static class XmlExtensionMethods + { + public static bool CompareNameTo(this XElement xElement, string value) + { + return xElement.Name.LocalName.CompareOrdinalIgnoreCase(value); + } + + public static bool CompareValueTo(this XAttribute xAttribute, string value) + { + return xAttribute.Value.CompareOrdinalIgnoreCase(value); + } + } +} From 9fcef0203ffd598d812bfaf6c1002f6ed7fb6868 Mon Sep 17 00:00:00 2001 From: vers-one <12114169+vers-one@users.noreply.github.com> Date: Mon, 28 Jan 2019 01:51:04 -0500 Subject: [PATCH 2/7] Parsing linear reading order Part of #18. --- README.md | 2 +- Source/EpubReader.sln | 3 +- .../ExtractPlainText.cs | 18 +-- .../VersOne.Epub.NetCoreDemo/ListChapters.cs | 26 ---- .../PrintNavigation.cs | 30 ++++ Source/VersOne.Epub.NetCoreDemo/Program.cs | 46 +++++- .../VersOne.Epub.NetCoreDemo/TestDirectory.cs | 117 +++++++++++++++ Source/VersOne.Epub/Entities/EpubBook.cs | 7 +- Source/VersOne.Epub/Entities/EpubChapter.cs | 19 --- .../Entities/EpubNavigationItem.cs | 33 +++++ .../Entities/EpubNavigationItemLink.cs | 21 +++ .../Entities/EpubNavigationItemType.cs | 8 ++ Source/VersOne.Epub/Entities/EpubSchema.cs | 3 +- Source/VersOne.Epub/EpubReader.cs | 43 +++--- Source/VersOne.Epub/Readers/ChapterReader.cs | 47 ------ Source/VersOne.Epub/Readers/ContentReader.cs | 2 +- Source/VersOne.Epub/Readers/Epub2NcxReader.cs | 2 +- .../Readers/Epub3NavDocumentReader.cs | 10 +- .../VersOne.Epub/Readers/NavigationReader.cs | 134 ++++++++++++++++++ Source/VersOne.Epub/Readers/PackageReader.cs | 41 ++++-- Source/VersOne.Epub/Readers/SchemaReader.cs | 11 +- Source/VersOne.Epub/Readers/SpineReader.cs | 29 ++++ .../VersOne.Epub/RefEntities/EpubBookRef.cs | 18 ++- .../RefEntities/EpubChapterRef.cs | 36 ----- .../RefEntities/EpubContentFileRef.cs | 10 +- .../RefEntities/EpubNavigationItemRef.cs | 33 +++++ Source/VersOne.Epub/Schema/Opf/EpubPackage.cs | 9 +- Source/VersOne.Epub/Schema/Opf/EpubSpine.cs | 2 + .../Schema/Opf/EpubSpineItemRef.cs | 16 ++- Source/VersOne.Epub/Schema/Opf/EpubVersion.cs | 19 ++- .../Schema/Opf/PageProgressionDirection.cs | 28 ++++ .../VersOne.Epub/Schema/Opf/SpineProperty.cs | 36 +++++ Source/VersOne.Epub/Utils/UrlParser.cs | 31 ++++ Source/VersOne.Epub/Utils/VersionUtils.cs | 23 +++ .../VersOne.Epub/Utils/XmlExtensionMethods.cs | 5 + 35 files changed, 715 insertions(+), 203 deletions(-) delete mode 100644 Source/VersOne.Epub.NetCoreDemo/ListChapters.cs create mode 100644 Source/VersOne.Epub.NetCoreDemo/PrintNavigation.cs create mode 100644 Source/VersOne.Epub.NetCoreDemo/TestDirectory.cs delete mode 100644 Source/VersOne.Epub/Entities/EpubChapter.cs create mode 100644 Source/VersOne.Epub/Entities/EpubNavigationItem.cs create mode 100644 Source/VersOne.Epub/Entities/EpubNavigationItemLink.cs create mode 100644 Source/VersOne.Epub/Entities/EpubNavigationItemType.cs delete mode 100644 Source/VersOne.Epub/Readers/ChapterReader.cs create mode 100644 Source/VersOne.Epub/Readers/NavigationReader.cs create mode 100644 Source/VersOne.Epub/Readers/SpineReader.cs delete mode 100644 Source/VersOne.Epub/RefEntities/EpubChapterRef.cs create mode 100644 Source/VersOne.Epub/RefEntities/EpubNavigationItemRef.cs create mode 100644 Source/VersOne.Epub/Schema/Opf/PageProgressionDirection.cs create mode 100644 Source/VersOne.Epub/Schema/Opf/SpineProperty.cs create mode 100644 Source/VersOne.Epub/Utils/UrlParser.cs create mode 100644 Source/VersOne.Epub/Utils/VersionUtils.cs diff --git a/README.md b/README.md index 3da66b5..238f25a 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ foreach (EpubNavigationHeadMeta meta in navigation.Head) DLL file from GitHub: [for .NET Framework](https://github.com/vers-one/EpubReader/releases/download/v2.0.4/VersOne.Epub.Net45.zip) (26.9 KB) / [for .NET Core](https://github.com/vers-one/EpubReader/releases/download/v2.0.4/VersOne.Epub.NetCore.zip) (27.0 KB) / [for .NET Standard](https://github.com/vers-one/EpubReader/releases/download/v2.0.4/VersOne.Epub.NetStandard.zip) (27.0 KB) ## Demo apps -[Download WPF demo app ](https://github.com/vers-one/EpubReader/releases/download/v2.0.4/WpfDemo.zip) (WpfDemo.zip, 409 KB) +[Download WPF demo app](https://github.com/vers-one/EpubReader/releases/download/v2.0.4/WpfDemo.zip) (WpfDemo.zip, 409 KB) This .NET Framework application demonstrates how to open EPUB books and extract their content using the library. diff --git a/Source/EpubReader.sln b/Source/EpubReader.sln index e0114d6..9a1875b 100644 --- a/Source/EpubReader.sln +++ b/Source/EpubReader.sln @@ -7,7 +7,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VersOne.Epub", "VersOne.Epu EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VersOne.Epub.WpfDemo", "VersOne.Epub.WpfDemo\VersOne.Epub.WpfDemo.csproj", "{2C48D6FB-EC93-4B79-8E52-79B579B3C324}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VersOne.Epub.NetCoreDemo", "VersOne.Epub.NetCoreDemo\VersOne.Epub.NetCoreDemo.csproj", "{A6ED4735-3D37-4E44-BEE4-218C6BBAC1BD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VersOne.Epub.NetCoreDemo", "VersOne.Epub.NetCoreDemo\VersOne.Epub.NetCoreDemo.csproj", "{A6ED4735-3D37-4E44-BEE4-218C6BBAC1BD}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -20,7 +20,6 @@ Global {313E44F0-4BC2-4A4F-B24B-C29D8FFB7C4E}.Release|Any CPU.ActiveCfg = Release|Any CPU {313E44F0-4BC2-4A4F-B24B-C29D8FFB7C4E}.Release|Any CPU.Build.0 = Release|Any CPU {2C48D6FB-EC93-4B79-8E52-79B579B3C324}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2C48D6FB-EC93-4B79-8E52-79B579B3C324}.Debug|Any CPU.Build.0 = Debug|Any CPU {2C48D6FB-EC93-4B79-8E52-79B579B3C324}.Release|Any CPU.ActiveCfg = Release|Any CPU {2C48D6FB-EC93-4B79-8E52-79B579B3C324}.Release|Any CPU.Build.0 = Release|Any CPU {A6ED4735-3D37-4E44-BEE4-218C6BBAC1BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU diff --git a/Source/VersOne.Epub.NetCoreDemo/ExtractPlainText.cs b/Source/VersOne.Epub.NetCoreDemo/ExtractPlainText.cs index 24ca413..3b7331a 100644 --- a/Source/VersOne.Epub.NetCoreDemo/ExtractPlainText.cs +++ b/Source/VersOne.Epub.NetCoreDemo/ExtractPlainText.cs @@ -9,30 +9,24 @@ internal static class ExtractPlainText public static void Run(string filePath) { EpubBook book = EpubReader.ReadBook(filePath); - foreach (EpubChapter chapter in book.Chapters) + foreach (EpubTextContentFile textContentFile in book.ReadingOrder) { - PrintChapter(chapter); + PrintTextContentFile(textContentFile); } } - private static void PrintChapter(EpubChapter chapter) + private static void PrintTextContentFile(EpubTextContentFile textContentFile) { HtmlDocument htmlDocument = new HtmlDocument(); - htmlDocument.LoadHtml(chapter.HtmlContent); + htmlDocument.LoadHtml(textContentFile.Content); StringBuilder sb = new StringBuilder(); foreach (HtmlNode node in htmlDocument.DocumentNode.SelectNodes("//text()")) { sb.AppendLine(node.InnerText.Trim()); } - string chapterTitle = chapter.Title; - string chapterText = sb.ToString(); - Console.WriteLine("------------ ", chapterTitle, "------------ "); - Console.WriteLine(chapterText); + string contentText = sb.ToString(); + Console.WriteLine(contentText); Console.WriteLine(); - foreach (EpubChapter subChapter in chapter.SubChapters) - { - PrintChapter(subChapter); - } } } } \ No newline at end of file diff --git a/Source/VersOne.Epub.NetCoreDemo/ListChapters.cs b/Source/VersOne.Epub.NetCoreDemo/ListChapters.cs deleted file mode 100644 index 022f351..0000000 --- a/Source/VersOne.Epub.NetCoreDemo/ListChapters.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; - -namespace VersOne.Epub.NetCoreDemo -{ - internal static class ListChapters - { - public static void Run(string filePath) - { - EpubBook book = EpubReader.ReadBook(filePath); - foreach (EpubChapter chapter in book.Chapters) - { - PrintChapterTitle(chapter, 0); - } - } - - private static void PrintChapterTitle(EpubChapter chapter, int identLevel) - { - Console.Write(new string(' ', identLevel * 2)); - Console.WriteLine(chapter.Title); - foreach (EpubChapter subChapter in chapter.SubChapters) - { - PrintChapterTitle(subChapter, identLevel + 1); - } - } - } -} diff --git a/Source/VersOne.Epub.NetCoreDemo/PrintNavigation.cs b/Source/VersOne.Epub.NetCoreDemo/PrintNavigation.cs new file mode 100644 index 0000000..18af16f --- /dev/null +++ b/Source/VersOne.Epub.NetCoreDemo/PrintNavigation.cs @@ -0,0 +1,30 @@ +using System; + +namespace VersOne.Epub.NetCoreDemo +{ + internal static class PrintNavigation + { + public static void Run(string filePath) + { + using (EpubBookRef bookRef = EpubReader.OpenBook(filePath)) + { + Console.WriteLine("Navigation:"); + foreach (EpubNavigationItemRef navigationItemRef in bookRef.GetNavigation()) + { + PrintNavigationItem(navigationItemRef, 0); + } + } + Console.WriteLine(); + } + + private static void PrintNavigationItem(EpubNavigationItemRef navigationItemRef, int identLevel) + { + Console.Write(new string(' ', identLevel * 2)); + Console.WriteLine(navigationItemRef.Title); + foreach (EpubNavigationItemRef nestedNavigationItemRef in navigationItemRef.NestedItems) + { + PrintNavigationItem(nestedNavigationItemRef, identLevel + 1); + } + } + } +} diff --git a/Source/VersOne.Epub.NetCoreDemo/Program.cs b/Source/VersOne.Epub.NetCoreDemo/Program.cs index 9907516..9c2f304 100644 --- a/Source/VersOne.Epub.NetCoreDemo/Program.cs +++ b/Source/VersOne.Epub.NetCoreDemo/Program.cs @@ -11,49 +11,81 @@ static void Main(string[] args) while (input != 'Q') { Console.WriteLine("Select example:"); - Console.WriteLine("1. List all chapters"); - Console.WriteLine("2. Extract plain text from all chapters"); + Console.WriteLine("1. Print book navigation tree (table of contents)"); + Console.WriteLine("2. Extract plain text from the whole book"); + Console.WriteLine("3. Test the library by reading all EPUB files from a directory"); Console.WriteLine("Q. Exit"); input = Char.ToUpper(Console.ReadKey(true).KeyChar); + Console.WriteLine(); switch (input) { case '1': - RunExample(ListChapters.Run); + RunFileExample(PrintNavigation.Run); break; case '2': - RunExample(ExtractPlainText.Run); + RunFileExample(ExtractPlainText.Run); + break; + case '3': + RunDirectoryExample(TestDirectory.Run); break; case 'Q': break; default: Console.WriteLine("Input is not recognized. Please try again."); + Console.WriteLine(); break; } } } - static void RunExample(Action example) + private static void RunFileExample(Action example) { Console.Write("Enter the path to the EPUB file: "); string filePath = Console.ReadLine(); + Console.WriteLine(); if (File.Exists(filePath) && Path.GetExtension(filePath).ToLower() == ".epub") { try { example(filePath); - Console.WriteLine(); } catch (Exception ex) { Console.WriteLine("Exception was thrown:"); Console.WriteLine(ex.ToString()); + Console.WriteLine(); } } else { Console.WriteLine("File doesn't exist."); + Console.WriteLine(); } } - } + private static void RunDirectoryExample(Action example) + { + Console.Write("Enter the path to the directory with EPUB files: "); + string directoryPath = Console.ReadLine(); + Console.WriteLine(); + if (Directory.Exists(directoryPath)) + { + try + { + example(directoryPath); + } + catch (Exception ex) + { + Console.WriteLine("Exception was thrown:"); + Console.WriteLine(ex.ToString()); + Console.WriteLine(); + } + } + else + { + Console.WriteLine("Directory doesn't exist."); + Console.WriteLine(); + } + } + } } diff --git a/Source/VersOne.Epub.NetCoreDemo/TestDirectory.cs b/Source/VersOne.Epub.NetCoreDemo/TestDirectory.cs new file mode 100644 index 0000000..8876999 --- /dev/null +++ b/Source/VersOne.Epub.NetCoreDemo/TestDirectory.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace VersOne.Epub.NetCoreDemo +{ + internal static class TestDirectory + { + public static void Run(string directoryPath) + { + int totalFiles = 0; + Dictionary filesByVersion = new Dictionary(); + List filesWithErrors = new List(); + TestEpubDirectory(directoryPath, ref totalFiles, filesByVersion, filesWithErrors); + if (totalFiles == 0) + { + Console.WriteLine("No EPUB files found."); + } + else + { + if (filesByVersion.Any(version => version.Value > 0)) + { + Console.WriteLine("Statistics"); + Console.WriteLine("-----------------------------------"); + Console.Write("Versions: "); + bool firstItem = true; + foreach (string epubVersionString in filesByVersion.Keys.OrderBy(version => version)) + { + if (firstItem) + { + firstItem = false; + } + else + { + Console.Write(", "); + } + Console.Write(epubVersionString + ": " + filesByVersion[epubVersionString]); + } + Console.WriteLine("."); + } + if (filesWithErrors.Any()) + { + Console.WriteLine("Files with errors:"); + foreach (string filePath in filesWithErrors) + { + Console.WriteLine(filePath); + } + } + else + { + Console.WriteLine("No errors."); + } + Console.WriteLine(); + } + } + + private static void TestEpubDirectory(string directoryPath, ref int totalFiles, Dictionary filesByVersion, List filesWithErrors) + { + foreach (string subdirectoryPath in Directory.EnumerateDirectories(directoryPath)) + { + TestEpubDirectory(subdirectoryPath, ref totalFiles, filesByVersion, filesWithErrors); + } + foreach (string filePath in Directory.EnumerateFiles(directoryPath, "*.epub")) + { + TestEpubFile(filePath, filesByVersion, filesWithErrors); + totalFiles++; + } + } + + private static void TestEpubFile(string epubFilePath, Dictionary filesByVersion, List filesWithErrors) + { + Console.WriteLine($"File: {epubFilePath}"); + Console.WriteLine("-----------------------------------"); + try + { + using (EpubBookRef bookRef = EpubReader.OpenBook(epubFilePath)) + { + string epubVersionString = bookRef.Schema.Package.GetVersionString(); + if (filesByVersion.ContainsKey(epubVersionString)) + { + filesByVersion[epubVersionString]++; + } + else + { + filesByVersion[epubVersionString] = 1; + } + Console.WriteLine($"EPUB version: {epubVersionString}"); + Console.WriteLine($"Total files: {bookRef.Content.AllFiles.Count}, HTML files: {bookRef.Content.Html.Count}," + + $" CSS files: {bookRef.Content.Css.Count}, image files: {bookRef.Content.Images.Count}, font files: {bookRef.Content.Fonts.Count}."); + Console.WriteLine($"Reading order: {bookRef.GetReadingOrder().Count} file(s)."); + Console.WriteLine("Navigation:"); + foreach (EpubNavigationItemRef navigationItemRef in bookRef.GetNavigation()) + { + PrintNavigationItem(navigationItemRef, 0); + } + } + } + catch (Exception exception) + { + Console.WriteLine(exception.ToString()); + filesWithErrors.Add(epubFilePath); + } + Console.WriteLine(); + } + + private static void PrintNavigationItem(EpubNavigationItemRef navigationItemRef, int identLevel) + { + Console.Write(new string(' ', identLevel * 2)); + Console.WriteLine(navigationItemRef.Title); + foreach (EpubNavigationItemRef nestedNavigationItemRef in navigationItemRef.NestedItems) + { + PrintNavigationItem(nestedNavigationItemRef, identLevel + 1); + } + } + } +} diff --git a/Source/VersOne.Epub/Entities/EpubBook.cs b/Source/VersOne.Epub/Entities/EpubBook.cs index b704d1a..67108b6 100644 --- a/Source/VersOne.Epub/Entities/EpubBook.cs +++ b/Source/VersOne.Epub/Entities/EpubBook.cs @@ -8,9 +8,10 @@ public class EpubBook public string Title { get; set; } public string Author { get; set; } public List AuthorList { get; set; } - public EpubSchema Schema { get; set; } - public EpubContent Content { get; set; } public byte[] CoverImage { get; set; } - public List Chapters { get; set; } + public List ReadingOrder { get; set; } + public List Navigation { get; set; } + public EpubContent Content { get; set; } + public EpubSchema Schema { get; set; } } } diff --git a/Source/VersOne.Epub/Entities/EpubChapter.cs b/Source/VersOne.Epub/Entities/EpubChapter.cs deleted file mode 100644 index cf022f3..0000000 --- a/Source/VersOne.Epub/Entities/EpubChapter.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace VersOne.Epub -{ - public class EpubChapter - { - public string Title { get; set; } - public string ContentFileName { get; set; } - public string Anchor { get; set; } - public string HtmlContent { get; set; } - public List SubChapters { get; set; } - - public override string ToString() - { - return String.Format("Title: {0}, Subchapter count: {1}", Title, SubChapters.Count); - } - } -} diff --git a/Source/VersOne.Epub/Entities/EpubNavigationItem.cs b/Source/VersOne.Epub/Entities/EpubNavigationItem.cs new file mode 100644 index 0000000..5c48065 --- /dev/null +++ b/Source/VersOne.Epub/Entities/EpubNavigationItem.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; + +namespace VersOne.Epub +{ + public class EpubNavigationItem + { + public EpubNavigationItem(EpubNavigationItemType type) + { + Type = type; + } + + public static EpubNavigationItem CreateAsHeader() + { + return new EpubNavigationItem(EpubNavigationItemType.HEADER); + } + + public static EpubNavigationItem CreateAsLink() + { + return new EpubNavigationItem(EpubNavigationItemType.LINK); + } + + public EpubNavigationItemType Type { get; } + public string Title { get; set; } + public EpubNavigationItemLink Link { get; set; } + public EpubTextContentFile HtmlContentFile { get; set; } + public List NestedItems { get; set; } + + public override string ToString() + { + return $"Type: {Type}, Title: {Title}, NestedItems.Count: {NestedItems.Count}"; + } + } +} diff --git a/Source/VersOne.Epub/Entities/EpubNavigationItemLink.cs b/Source/VersOne.Epub/Entities/EpubNavigationItemLink.cs new file mode 100644 index 0000000..5bf801b --- /dev/null +++ b/Source/VersOne.Epub/Entities/EpubNavigationItemLink.cs @@ -0,0 +1,21 @@ +using VersOne.Epub.Internal; + +namespace VersOne.Epub +{ + public class EpubNavigationItemLink + { + public EpubNavigationItemLink() + { + } + + public EpubNavigationItemLink(string url) + { + UrlParser urlParser = new UrlParser(url); + ContentFileName = urlParser.Path; + Anchor = urlParser.Anchor; + } + + public string ContentFileName { get; set; } + public string Anchor { get; set; } + } +} diff --git a/Source/VersOne.Epub/Entities/EpubNavigationItemType.cs b/Source/VersOne.Epub/Entities/EpubNavigationItemType.cs new file mode 100644 index 0000000..fa59ca3 --- /dev/null +++ b/Source/VersOne.Epub/Entities/EpubNavigationItemType.cs @@ -0,0 +1,8 @@ +namespace VersOne.Epub +{ + public enum EpubNavigationItemType + { + HEADER = 1, + LINK + } +} diff --git a/Source/VersOne.Epub/Entities/EpubSchema.cs b/Source/VersOne.Epub/Entities/EpubSchema.cs index e2efbcd..7f2f9d9 100644 --- a/Source/VersOne.Epub/Entities/EpubSchema.cs +++ b/Source/VersOne.Epub/Entities/EpubSchema.cs @@ -5,7 +5,8 @@ namespace VersOne.Epub public class EpubSchema { public EpubPackage Package { get; set; } - public Epub2Ncx Navigation { get; set; } + public Epub2Ncx Epub2Ncx { get; set; } + public Epub3NavDocument Epub3NavDocument { get; set; } public string ContentDirectoryPath { get; set; } } } diff --git a/Source/VersOne.Epub/EpubReader.cs b/Source/VersOne.Epub/EpubReader.cs index 4f07c93..84c926b 100644 --- a/Source/VersOne.Epub/EpubReader.cs +++ b/Source/VersOne.Epub/EpubReader.cs @@ -23,7 +23,7 @@ public static EpubBookRef OpenBook(string filePath) /// /// Opens the book synchronously without reading its whole content. /// - /// path to the EPUB file + /// seekable stream containing the EPUB file /// public static EpubBookRef OpenBook(Stream stream) { @@ -47,7 +47,7 @@ public static Task OpenBookAsync(string filePath) /// /// Opens the book asynchronously without reading its whole content. /// - /// path to the EPUB file + /// seekable stream containing the EPUB file /// public static Task OpenBookAsync(Stream stream) { @@ -65,9 +65,9 @@ public static EpubBook ReadBook(string filePath) } /// - /// Opens the book synchronously and reads all of its content into the memory. Does not hold the handle to the EPUB file. + /// Opens the book synchronously and reads all of its content into the memory. /// - /// path to the EPUB file + /// seekable stream containing the EPUB file /// public static EpubBook ReadBook(Stream stream) { @@ -88,7 +88,7 @@ public static async Task ReadBookAsync(string filePath) /// /// Opens the book asynchronously and reads all of its content into the memory. /// - /// path to the EPUB file + /// seekable stream containing the EPUB file /// public static async Task ReadBookAsync(Stream stream) { @@ -129,8 +129,10 @@ private static async Task ReadBookAsync(EpubBookRef epubBookRef) result.Author = epubBookRef.Author; result.Content = await ReadContent(epubBookRef.Content).ConfigureAwait(false); result.CoverImage = await epubBookRef.ReadCoverAsync().ConfigureAwait(false); - List chapterRefs = await epubBookRef.GetChaptersAsync().ConfigureAwait(false); - result.Chapters = await ReadChapters(chapterRefs).ConfigureAwait(false); + List htmlContentFileRefs = await epubBookRef.GetReadingOrderAsync().ConfigureAwait(false); + result.ReadingOrder = ReadReadingOrder(result, htmlContentFileRefs); + List navigationItemRefs = await epubBookRef.GetNavigationAsync().ConfigureAwait(false); + result.Navigation = ReadNavigation(result, navigationItemRefs); } return result; } @@ -210,20 +212,27 @@ private static async Task ReadByteContentFile(EpubContentFi return result; } - private static async Task> ReadChapters(List chapterRefs) + private static List ReadReadingOrder(EpubBook epubBook, List htmlContentFileRefs) { - List result = new List(); - foreach (EpubChapterRef chapterRef in chapterRefs) + return htmlContentFileRefs.Select(htmlContentFileRef => epubBook.Content.Html[htmlContentFileRef.FileName]).ToList(); + } + + private static List ReadNavigation(EpubBook epubBook, List navigationItemRefs) + { + List result = new List(); + foreach (EpubNavigationItemRef navigationItemRef in navigationItemRefs) { - EpubChapter chapter = new EpubChapter + EpubNavigationItem navigationItem = new EpubNavigationItem(navigationItemRef.Type) { - Title = chapterRef.Title, - ContentFileName = chapterRef.ContentFileName, - Anchor = chapterRef.Anchor + Title = navigationItemRef.Title, + Link = navigationItemRef.Link, }; - chapter.HtmlContent = await chapterRef.ReadHtmlContentAsync().ConfigureAwait(false); - chapter.SubChapters = await ReadChapters(chapterRef.SubChapters).ConfigureAwait(false); - result.Add(chapter); + if (navigationItemRef.HtmlContentFileRef != null) + { + navigationItem.HtmlContentFile = epubBook.Content.Html[navigationItemRef.HtmlContentFileRef.FileName]; + } + navigationItem.NestedItems = ReadNavigation(epubBook, navigationItemRef.NestedItems); + result.Add(navigationItem); } return result; } diff --git a/Source/VersOne.Epub/Readers/ChapterReader.cs b/Source/VersOne.Epub/Readers/ChapterReader.cs deleted file mode 100644 index 0d93da1..0000000 --- a/Source/VersOne.Epub/Readers/ChapterReader.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using VersOne.Epub.Schema; - -namespace VersOne.Epub.Internal -{ - internal static class ChapterReader - { - public static List GetChapters(EpubBookRef bookRef) - { - return GetChapters(bookRef, bookRef.Schema.Navigation.NavMap); - } - - public static List GetChapters(EpubBookRef bookRef, List navigationPoints) - { - List result = new List(); - foreach (Epub2NcxNavigationPoint navigationPoint in navigationPoints) - { - string contentFileName; - string anchor; - int contentSourceAnchorCharIndex = navigationPoint.Content.Source.IndexOf('#'); - if (contentSourceAnchorCharIndex == -1) - { - contentFileName = navigationPoint.Content.Source; - anchor = null; - } - else - { - contentFileName = navigationPoint.Content.Source.Substring(0, contentSourceAnchorCharIndex); - anchor = navigationPoint.Content.Source.Substring(contentSourceAnchorCharIndex + 1); - } - if (!bookRef.Content.Html.TryGetValue(contentFileName, out EpubTextContentFileRef htmlContentFileRef)) - { - throw new Exception(String.Format("Incorrect EPUB manifest: item with href = \"{0}\" is missing.", contentFileName)); - } - EpubChapterRef chapterRef = new EpubChapterRef(htmlContentFileRef); - chapterRef.ContentFileName = contentFileName; - chapterRef.Anchor = anchor; - chapterRef.Title = navigationPoint.NavigationLabels.First().Text; - chapterRef.SubChapters = GetChapters(bookRef, navigationPoint.ChildNavigationPoints); - result.Add(chapterRef); - } - return result; - } - } -} diff --git a/Source/VersOne.Epub/Readers/ContentReader.cs b/Source/VersOne.Epub/Readers/ContentReader.cs index 03be95f..3d68be3 100644 --- a/Source/VersOne.Epub/Readers/ContentReader.cs +++ b/Source/VersOne.Epub/Readers/ContentReader.cs @@ -100,9 +100,9 @@ private static EpubContentType GetContentTypeByContentMimeType(string contentMim case "image/svg+xml": return EpubContentType.IMAGE_SVG; case "font/truetype": + case "application/x-font-truetype": return EpubContentType.FONT_TRUETYPE; case "font/opentype": - return EpubContentType.FONT_OPENTYPE; case "application/vnd.ms-opentype": return EpubContentType.FONT_OPENTYPE; default: diff --git a/Source/VersOne.Epub/Readers/Epub2NcxReader.cs b/Source/VersOne.Epub/Readers/Epub2NcxReader.cs index 58c60d4..c7ae1a8 100644 --- a/Source/VersOne.Epub/Readers/Epub2NcxReader.cs +++ b/Source/VersOne.Epub/Readers/Epub2NcxReader.cs @@ -18,7 +18,7 @@ public static async Task ReadEpub2NcxAsync(ZipArchive epubArchive, str string tocId = package.Spine.Toc; if (String.IsNullOrEmpty(tocId)) { - throw new Exception("EPUB parsing error: TOC ID is empty."); + return null; } EpubManifestItem tocManifestItem = package.Manifest.FirstOrDefault(item => item.Id.CompareOrdinalIgnoreCase(tocId)); if (tocManifestItem == null) diff --git a/Source/VersOne.Epub/Readers/Epub3NavDocumentReader.cs b/Source/VersOne.Epub/Readers/Epub3NavDocumentReader.cs index 4a66a5a..be0f7a4 100644 --- a/Source/VersOne.Epub/Readers/Epub3NavDocumentReader.cs +++ b/Source/VersOne.Epub/Readers/Epub3NavDocumentReader.cs @@ -3,7 +3,6 @@ using System.IO; using System.IO.Compression; using System.Linq; -using System.Text; using System.Threading.Tasks; using System.Xml.Linq; using VersOne.Epub.Schema; @@ -19,7 +18,14 @@ public static async Task ReadEpub3NavDocumentAsync(ZipArchive package.Manifest.FirstOrDefault(item => item.Properties != null && item.Properties.Contains(ManifestProperty.NAV)); if (navManifestItem == null) { - throw new Exception("EPUB parsing error: NAV item not found in EPUB manifest."); + if (package.EpubVersion == EpubVersion.EPUB_2) + { + return null; + } + else + { + throw new Exception("EPUB parsing error: NAV item not found in EPUB manifest."); + } } string navFileEntryPath = ZipPathUtils.Combine(contentDirectoryPath, navManifestItem.Href); ZipArchiveEntry navFileEntry = epubArchive.GetEntry(navFileEntryPath); diff --git a/Source/VersOne.Epub/Readers/NavigationReader.cs b/Source/VersOne.Epub/Readers/NavigationReader.cs new file mode 100644 index 0000000..51bb081 --- /dev/null +++ b/Source/VersOne.Epub/Readers/NavigationReader.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using VersOne.Epub.Schema; + +namespace VersOne.Epub.Internal +{ + internal static class NavigationReader + { + public static List GetNavigationItems(EpubBookRef bookRef) + { + if (bookRef.Schema.Package.EpubVersion == EpubVersion.EPUB_2) + { + return GetNavigationItems(bookRef, bookRef.Schema.Epub2Ncx); + } + else + { + return GetNavigationItems(bookRef, bookRef.Schema.Epub3NavDocument); + } + } + + public static List GetNavigationItems(EpubBookRef bookRef, Epub2Ncx epub2Ncx) + { + return GetNavigationItems(bookRef, epub2Ncx.NavMap); + } + + public static List GetNavigationItems(EpubBookRef bookRef, Epub3NavDocument epub3NavDocument) + { + return GetNavigationItems(bookRef, epub3NavDocument.Navs.FirstOrDefault(nav => nav.Type == StructuralSemanticsProperty.TOC)); + } + + private static List GetNavigationItems(EpubBookRef bookRef, List navigationPoints) + { + List result = new List(); + if (navigationPoints != null) + { + foreach (Epub2NcxNavigationPoint navigationPoint in navigationPoints) + { + EpubNavigationItemRef navigationItemRef = EpubNavigationItemRef.CreateAsLink(); + navigationItemRef.Title = navigationPoint.NavigationLabels.First().Text; + navigationItemRef.Link = new EpubNavigationItemLink(navigationPoint.Content.Source); + navigationItemRef.HtmlContentFileRef = GetHtmlContentFileRef(bookRef, navigationItemRef.Link.ContentFileName); + navigationItemRef.NestedItems = GetNavigationItems(bookRef, navigationPoint.ChildNavigationPoints); + result.Add(navigationItemRef); + } + } + return result; + } + + private static List GetNavigationItems(EpubBookRef bookRef, Epub3Nav epub3Nav) + { + List result; + if (epub3Nav != null) + { + if (epub3Nav.Head != null) + { + result = new List(); + EpubNavigationItemRef navigationItemRef = EpubNavigationItemRef.CreateAsHeader(); + navigationItemRef.Title = epub3Nav.Head; + navigationItemRef.NestedItems = GetNavigationItems(bookRef, epub3Nav.Ol); + result.Add(navigationItemRef); + + } + else + { + result = GetNavigationItems(bookRef, epub3Nav.Ol); + } + } + else + { + result = new List(); + } + return result; + } + + private static List GetNavigationItems(EpubBookRef bookRef, Epub3NavOl epub3NavOl) + { + List result = new List(); + if (epub3NavOl != null && epub3NavOl.Lis != null) + { + foreach (Epub3NavLi epub3NavLi in epub3NavOl.Lis) + { + if (epub3NavLi != null && (epub3NavLi.Anchor != null || epub3NavLi.Span != null)) + { + if (epub3NavLi.Anchor != null) + { + Epub3NavAnchor navAnchor = epub3NavLi.Anchor; + EpubNavigationItemRef navigationItemRef = EpubNavigationItemRef.CreateAsLink(); + navigationItemRef.Title = GetFirstNonEmptyHeader(navAnchor.Text, navAnchor.Title, navAnchor.Alt); + navigationItemRef.Link = new EpubNavigationItemLink(navAnchor.Href); + navigationItemRef.HtmlContentFileRef = GetHtmlContentFileRef(bookRef, navigationItemRef.Link.ContentFileName); + navigationItemRef.NestedItems = GetNavigationItems(bookRef, epub3NavLi.ChildOl); + result.Add(navigationItemRef); + } + else if (epub3NavLi.Span != null) + { + Epub3NavSpan navSpan = epub3NavLi.Span; + EpubNavigationItemRef navigationItemRef = EpubNavigationItemRef.CreateAsHeader(); + navigationItemRef.Title = GetFirstNonEmptyHeader(navSpan.Text, navSpan.Title, navSpan.Alt); + navigationItemRef.NestedItems = GetNavigationItems(bookRef, epub3NavLi.ChildOl); + result.Add(navigationItemRef); + } + } + } + } + return result; + } + + private static EpubTextContentFileRef GetHtmlContentFileRef(EpubBookRef bookRef, string contentFileName) + { + if (contentFileName == null) + { + return null; + } + if (!bookRef.Content.Html.TryGetValue(contentFileName, out EpubTextContentFileRef htmlContentFileRef)) + { + return null; + } + return htmlContentFileRef; + } + + private static string GetFirstNonEmptyHeader(params string[] options) + { + foreach (string option in options) + { + if (!String.IsNullOrEmpty(option)) + { + return option; + } + } + return String.Empty; + } + } +} diff --git a/Source/VersOne.Epub/Readers/PackageReader.cs b/Source/VersOne.Epub/Readers/PackageReader.cs index cbd196a..6c9e33e 100644 --- a/Source/VersOne.Epub/Readers/PackageReader.cs +++ b/Source/VersOne.Epub/Readers/PackageReader.cs @@ -292,7 +292,7 @@ private static EpubManifest ReadManifest(XElement manifestNode) foreach (XAttribute manifestItemNodeAttribute in manifestItemNode.Attributes()) { string attributeValue = manifestItemNodeAttribute.Value; - switch (manifestItemNodeAttribute.Name.LocalName.ToLowerInvariant()) + switch (manifestItemNodeAttribute.GetLowerCaseLocalName()) { case "id": manifestItem.Id = attributeValue; @@ -351,12 +351,23 @@ private static List ReadManifestProperties(string propertiesAt private static EpubSpine ReadSpine(XElement spineNode, EpubVersion epubVersion) { EpubSpine result = new EpubSpine(); - XAttribute tocAttribute = spineNode.Attribute("toc"); - if (tocAttribute != null && !String.IsNullOrWhiteSpace(tocAttribute.Value)) + foreach (XAttribute spineNodeAttribute in spineNode.Attributes()) { - result.Toc = tocAttribute.Value; + string attributeValue = spineNodeAttribute.Value; + switch (spineNodeAttribute.GetLowerCaseLocalName()) + { + case "id": + result.Id = attributeValue; + break; + case "page-progression-direction": + result.PageProgressionDirection = PageProgressionDirectionParser.Parse(attributeValue); + break; + case "toc": + result.Toc = attributeValue; + break; + } } - else if (epubVersion == EpubVersion.EPUB_2) + if (epubVersion == EpubVersion.EPUB_2 && String.IsNullOrWhiteSpace(result.Toc)) { throw new Exception("Incorrect EPUB spine: TOC is missing"); } @@ -365,12 +376,26 @@ private static EpubSpine ReadSpine(XElement spineNode, EpubVersion epubVersion) if (spineItemNode.CompareNameTo("itemref")) { EpubSpineItemRef spineItemRef = new EpubSpineItemRef(); - XAttribute idRefAttribute = spineItemNode.Attribute("idref"); - if (idRefAttribute == null || String.IsNullOrWhiteSpace(idRefAttribute.Value)) + foreach (XAttribute spineItemNodeAttribute in spineItemNode.Attributes()) + { + string attributeValue = spineItemNodeAttribute.Value; + switch (spineItemNodeAttribute.GetLowerCaseLocalName()) + { + case "id": + spineItemRef.Id = attributeValue; + break; + case "idref": + spineItemRef.IdRef = attributeValue; + break; + case "properties": + spineItemRef.Properties = SpinePropertyParser.ParsePropertyList(attributeValue); + break; + } + } + if (String.IsNullOrWhiteSpace(spineItemRef.IdRef)) { throw new Exception("Incorrect EPUB spine: item ID ref is missing"); } - spineItemRef.IdRef = idRefAttribute.Value; XAttribute linearAttribute = spineItemNode.Attribute("linear"); spineItemRef.IsLinear = linearAttribute == null || !linearAttribute.CompareValueTo("no"); result.Add(spineItemRef); diff --git a/Source/VersOne.Epub/Readers/SchemaReader.cs b/Source/VersOne.Epub/Readers/SchemaReader.cs index e6dea27..32a7e4d 100644 --- a/Source/VersOne.Epub/Readers/SchemaReader.cs +++ b/Source/VersOne.Epub/Readers/SchemaReader.cs @@ -14,15 +14,8 @@ public static async Task ReadSchemaAsync(ZipArchive epubArchive) result.ContentDirectoryPath = contentDirectoryPath; EpubPackage package = await PackageReader.ReadPackageAsync(epubArchive, rootFilePath).ConfigureAwait(false); result.Package = package; - if (package.EpubVersion == EpubVersion.EPUB_2) - { - Epub2Ncx epub2Ncx = await Epub2NcxReader.ReadEpub2NcxAsync(epubArchive, contentDirectoryPath, package).ConfigureAwait(false); - result.Navigation = epub2Ncx; - } - else - { - Epub3NavDocument epub3NavDocumentepub3Nav = await Epub3NavDocumentReader.ReadEpub3NavDocumentAsync(epubArchive, contentDirectoryPath, package).ConfigureAwait(false); - } + result.Epub2Ncx = await Epub2NcxReader.ReadEpub2NcxAsync(epubArchive, contentDirectoryPath, package).ConfigureAwait(false); + result.Epub3NavDocument = await Epub3NavDocumentReader.ReadEpub3NavDocumentAsync(epubArchive, contentDirectoryPath, package).ConfigureAwait(false); return result; } } diff --git a/Source/VersOne.Epub/Readers/SpineReader.cs b/Source/VersOne.Epub/Readers/SpineReader.cs new file mode 100644 index 0000000..28eb8b3 --- /dev/null +++ b/Source/VersOne.Epub/Readers/SpineReader.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using VersOne.Epub.Schema; + +namespace VersOne.Epub.Internal +{ + internal static class SpineReader + { + public static List GetReadingOrder(EpubBookRef bookRef) + { + List result = new List(); + foreach (EpubSpineItemRef spineItemRef in bookRef.Schema.Package.Spine) + { + EpubManifestItem manifestItem = bookRef.Schema.Package.Manifest.FirstOrDefault(item => item.Id == spineItemRef.IdRef); + if (manifestItem == null) + { + throw new Exception($"Incorrect EPUB spine: item with IdRef = \"{spineItemRef.IdRef}\" is missing in the manifest."); + } + if (!bookRef.Content.Html.TryGetValue(manifestItem.Href, out EpubTextContentFileRef htmlContentFileRef)) + { + throw new Exception($"Incorrect EPUB manifest: item with href = \"{spineItemRef.IdRef}\" is missing in the book."); + } + result.Add(htmlContentFileRef); + } + return result; + } + } +} diff --git a/Source/VersOne.Epub/RefEntities/EpubBookRef.cs b/Source/VersOne.Epub/RefEntities/EpubBookRef.cs index 95a6d88..4dbe258 100644 --- a/Source/VersOne.Epub/RefEntities/EpubBookRef.cs +++ b/Source/VersOne.Epub/RefEntities/EpubBookRef.cs @@ -40,14 +40,24 @@ public async Task ReadCoverAsync() return await BookCoverReader.ReadBookCoverAsync(this).ConfigureAwait(false); } - public List GetChapters() + public List GetReadingOrder() { - return GetChaptersAsync().Result; + return GetReadingOrderAsync().Result; } - public async Task> GetChaptersAsync() + public async Task> GetReadingOrderAsync() { - return await Task.Run(() => ChapterReader.GetChapters(this)).ConfigureAwait(false); + return await Task.Run(() => SpineReader.GetReadingOrder(this)).ConfigureAwait(false); + } + + public List GetNavigation() + { + return GetNavigationAsync().Result; + } + + public async Task> GetNavigationAsync() + { + return await Task.Run(() => NavigationReader.GetNavigationItems(this)).ConfigureAwait(false); } public void Dispose() diff --git a/Source/VersOne.Epub/RefEntities/EpubChapterRef.cs b/Source/VersOne.Epub/RefEntities/EpubChapterRef.cs deleted file mode 100644 index b992e8e..0000000 --- a/Source/VersOne.Epub/RefEntities/EpubChapterRef.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace VersOne.Epub -{ - public class EpubChapterRef - { - private readonly EpubTextContentFileRef epubTextContentFileRef; - - public EpubChapterRef(EpubTextContentFileRef epubTextContentFileRef) - { - this.epubTextContentFileRef = epubTextContentFileRef; - } - - public string Title { get; set; } - public string ContentFileName { get; set; } - public string Anchor { get; set; } - public List SubChapters { get; set; } - - public string ReadHtmlContent() - { - return ReadHtmlContentAsync().Result; - } - - public Task ReadHtmlContentAsync() - { - return epubTextContentFileRef.ReadContentAsTextAsync(); - } - - public override string ToString() - { - return String.Format("Title: {0}, Subchapter count: {1}", Title, SubChapters.Count); - } - } -} diff --git a/Source/VersOne.Epub/RefEntities/EpubContentFileRef.cs b/Source/VersOne.Epub/RefEntities/EpubContentFileRef.cs index 1e26637..a9a5864 100644 --- a/Source/VersOne.Epub/RefEntities/EpubContentFileRef.cs +++ b/Source/VersOne.Epub/RefEntities/EpubContentFileRef.cs @@ -57,15 +57,19 @@ public Stream GetContentStream() private ZipArchiveEntry GetContentFileEntry() { + if (String.IsNullOrEmpty(FileName)) + { + throw new Exception("EPUB parsing error: file name of the specified content file is empty."); + } string contentFilePath = ZipPathUtils.Combine(epubBookRef.Schema.ContentDirectoryPath, FileName); ZipArchiveEntry contentFileEntry = epubBookRef.EpubArchive.GetEntry(contentFilePath); if (contentFileEntry == null) { - throw new Exception(String.Format("EPUB parsing error: file {0} not found in archive.", contentFilePath)); + throw new Exception($"EPUB parsing error: file \"{contentFilePath}\" was not found in the archive."); } if (contentFileEntry.Length > Int32.MaxValue) { - throw new Exception(String.Format("EPUB parsing error: file {0} is bigger than 2 Gb.", contentFilePath)); + throw new Exception($"EPUB parsing error: file \"{contentFilePath}\" is larger than 2 Gb."); } return contentFileEntry; } @@ -75,7 +79,7 @@ private Stream OpenContentStream(ZipArchiveEntry contentFileEntry) Stream contentStream = contentFileEntry.Open(); if (contentStream == null) { - throw new Exception(String.Format("Incorrect EPUB file: content file \"{0}\" specified in manifest is not found.", FileName)); + throw new Exception($"Incorrect EPUB file: content file \"{FileName}\" specified in the manifest was not found in the archive."); } return contentStream; } diff --git a/Source/VersOne.Epub/RefEntities/EpubNavigationItemRef.cs b/Source/VersOne.Epub/RefEntities/EpubNavigationItemRef.cs new file mode 100644 index 0000000..db78eda --- /dev/null +++ b/Source/VersOne.Epub/RefEntities/EpubNavigationItemRef.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; + +namespace VersOne.Epub +{ + public class EpubNavigationItemRef + { + public EpubNavigationItemRef(EpubNavigationItemType type) + { + Type = type; + } + + public static EpubNavigationItemRef CreateAsHeader() + { + return new EpubNavigationItemRef(EpubNavigationItemType.HEADER); + } + + public static EpubNavigationItemRef CreateAsLink() + { + return new EpubNavigationItemRef(EpubNavigationItemType.LINK); + } + + public EpubNavigationItemType Type { get; } + public string Title { get; set; } + public EpubNavigationItemLink Link { get; set; } + public EpubTextContentFileRef HtmlContentFileRef { get; set; } + public List NestedItems { get; set; } + + public override string ToString() + { + return $"Type: {Type}, Title: {Title}, NestedItems.Count: {NestedItems.Count}"; + } + } +} diff --git a/Source/VersOne.Epub/Schema/Opf/EpubPackage.cs b/Source/VersOne.Epub/Schema/Opf/EpubPackage.cs index 91efedd..3dbc0aa 100644 --- a/Source/VersOne.Epub/Schema/Opf/EpubPackage.cs +++ b/Source/VersOne.Epub/Schema/Opf/EpubPackage.cs @@ -1,4 +1,6 @@ -namespace VersOne.Epub.Schema +using VersOne.Epub.Internal; + +namespace VersOne.Epub.Schema { public class EpubPackage { @@ -7,5 +9,10 @@ public class EpubPackage public EpubManifest Manifest { get; set; } public EpubSpine Spine { get; set; } public EpubGuide Guide { get; set; } + + public string GetVersionString() + { + return VersionUtils.GetVersionString(EpubVersion); + } } } diff --git a/Source/VersOne.Epub/Schema/Opf/EpubSpine.cs b/Source/VersOne.Epub/Schema/Opf/EpubSpine.cs index 022db95..fadfdb3 100644 --- a/Source/VersOne.Epub/Schema/Opf/EpubSpine.cs +++ b/Source/VersOne.Epub/Schema/Opf/EpubSpine.cs @@ -4,6 +4,8 @@ namespace VersOne.Epub.Schema { public class EpubSpine : List { + public string Id { get; set; } + public PageProgressionDirection? PageProgressionDirection { get; set; } public string Toc { get; set; } } } diff --git a/Source/VersOne.Epub/Schema/Opf/EpubSpineItemRef.cs b/Source/VersOne.Epub/Schema/Opf/EpubSpineItemRef.cs index c239f95..650b36e 100644 --- a/Source/VersOne.Epub/Schema/Opf/EpubSpineItemRef.cs +++ b/Source/VersOne.Epub/Schema/Opf/EpubSpineItemRef.cs @@ -1,15 +1,27 @@ -using System; +using System.Collections.Generic; +using System.Text; namespace VersOne.Epub.Schema { public class EpubSpineItemRef { + public string Id { get; set; } public string IdRef { get; set; } public bool IsLinear { get; set; } + public List Properties { get; set; } public override string ToString() { - return String.Concat("IdRef: ", IdRef); + StringBuilder resultBuilder = new StringBuilder(); + if (Id != null) + { + resultBuilder.Append("Id: "); + resultBuilder.Append(Id); + resultBuilder.Append("; "); + } + resultBuilder.Append("IdRef: "); + resultBuilder.Append(IdRef ?? "(null)"); + return resultBuilder.ToString(); } } } diff --git a/Source/VersOne.Epub/Schema/Opf/EpubVersion.cs b/Source/VersOne.Epub/Schema/Opf/EpubVersion.cs index 5c86194..fcb867d 100644 --- a/Source/VersOne.Epub/Schema/Opf/EpubVersion.cs +++ b/Source/VersOne.Epub/Schema/Opf/EpubVersion.cs @@ -1,9 +1,26 @@ -namespace VersOne.Epub.Schema +using System; + +namespace VersOne.Epub.Schema { public enum EpubVersion { + [VersionString("2.0")] EPUB_2 = 2, + + [VersionString("3.0")] EPUB_3_0, + + [VersionString("3.1")] EPUB_3_1 } + + internal class VersionStringAttribute : Attribute + { + public VersionStringAttribute(string version) + { + Version = version; + } + + public string Version { get; } + } } diff --git a/Source/VersOne.Epub/Schema/Opf/PageProgressionDirection.cs b/Source/VersOne.Epub/Schema/Opf/PageProgressionDirection.cs new file mode 100644 index 0000000..8f66cdb --- /dev/null +++ b/Source/VersOne.Epub/Schema/Opf/PageProgressionDirection.cs @@ -0,0 +1,28 @@ +namespace VersOne.Epub.Schema +{ + public enum PageProgressionDirection + { + DEFAULT = 1, + LEFT_TO_RIGHT, + RIGHT_TO_LEFT, + UNKNOWN + } + + internal static class PageProgressionDirectionParser + { + public static PageProgressionDirection Parse(string stringValue) + { + switch (stringValue.ToLowerInvariant()) + { + case "default": + return PageProgressionDirection.DEFAULT; + case "ltr": + return PageProgressionDirection.LEFT_TO_RIGHT; + case "rtl": + return PageProgressionDirection.RIGHT_TO_LEFT; + default: + return PageProgressionDirection.UNKNOWN; + } + } + } +} diff --git a/Source/VersOne.Epub/Schema/Opf/SpineProperty.cs b/Source/VersOne.Epub/Schema/Opf/SpineProperty.cs new file mode 100644 index 0000000..9c98398 --- /dev/null +++ b/Source/VersOne.Epub/Schema/Opf/SpineProperty.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace VersOne.Epub.Schema +{ + public enum SpineProperty + { + PAGE_SPREAD_LEFT = 1, + PAGE_SPREAD_RIGHT, + UNKNOWN + } + + internal static class SpinePropertyParser + { + public static List ParsePropertyList(string stringValue) + { + return stringValue.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries). + Select(propertyString => ParseProperty(propertyString.Trim())). + ToList(); + } + + public static SpineProperty ParseProperty(string stringValue) + { + switch (stringValue.ToLowerInvariant()) + { + case "page-spread-left": + return SpineProperty.PAGE_SPREAD_LEFT; + case "page-spread-right": + return SpineProperty.PAGE_SPREAD_RIGHT; + default: + return SpineProperty.UNKNOWN; + } + } + } +} diff --git a/Source/VersOne.Epub/Utils/UrlParser.cs b/Source/VersOne.Epub/Utils/UrlParser.cs new file mode 100644 index 0000000..0423070 --- /dev/null +++ b/Source/VersOne.Epub/Utils/UrlParser.cs @@ -0,0 +1,31 @@ +namespace VersOne.Epub.Internal +{ + internal class UrlParser + { + public UrlParser(string url) + { + if (url == null) + { + Path = null; + Anchor = null; + } + else + { + int anchorCharIndex = url.IndexOf('#'); + if (anchorCharIndex == -1) + { + Path = url; + Anchor = null; + } + else + { + Path = url.Substring(0, anchorCharIndex); + Anchor = url.Substring(anchorCharIndex + 1); + } + } + } + + public string Path { get; } + public string Anchor { get; } + } +} diff --git a/Source/VersOne.Epub/Utils/VersionUtils.cs b/Source/VersOne.Epub/Utils/VersionUtils.cs new file mode 100644 index 0000000..37580c6 --- /dev/null +++ b/Source/VersOne.Epub/Utils/VersionUtils.cs @@ -0,0 +1,23 @@ +using System; +using System.Reflection; +using VersOne.Epub.Schema; + +namespace VersOne.Epub.Internal +{ + internal static class VersionUtils + { + public static string GetVersionString(EpubVersion epubVersion) + { + Type epubVersionType = typeof(EpubVersion); + FieldInfo fieldInfo = epubVersionType.GetRuntimeField(epubVersion.ToString()); + if (fieldInfo != null) + { + return fieldInfo.GetCustomAttribute().Version; + } + else + { + return epubVersion.ToString(); + } + } + } +} diff --git a/Source/VersOne.Epub/Utils/XmlExtensionMethods.cs b/Source/VersOne.Epub/Utils/XmlExtensionMethods.cs index 29c63ca..c2471bc 100644 --- a/Source/VersOne.Epub/Utils/XmlExtensionMethods.cs +++ b/Source/VersOne.Epub/Utils/XmlExtensionMethods.cs @@ -4,6 +4,11 @@ namespace VersOne.Epub.Utils { internal static class XmlExtensionMethods { + public static string GetLowerCaseLocalName(this XAttribute xAttribute) + { + return xAttribute.Name.LocalName.ToLowerInvariant(); + } + public static bool CompareNameTo(this XElement xElement, string value) { return xElement.Name.LocalName.CompareOrdinalIgnoreCase(value); From 6d87cfdebd0615bc543a9a4ae99e6bba06e24e32 Mon Sep 17 00:00:00 2001 From: vers-one <12114169+vers-one@users.noreply.github.com> Date: Mon, 25 Mar 2019 00:08:15 -0400 Subject: [PATCH 3/7] Changes to WpfDemo to use the new version of the library Part of #18. --- Source/EpubReader.sln | 1 + .../ApplicationContext.cs | 2 +- .../Controls/BookHtmlContent.cs | 73 +++++-- .../Controls/NavigationHeader.xaml | 18 ++ ...eView.xaml.cs => NavigationHeader.xaml.cs} | 4 +- ...ontentsButton.xaml => NavigationItem.xaml} | 11 +- ...sButton.xaml.cs => NavigationItem.xaml.cs} | 4 +- .../Controls/NavigationTemplateSelector.cs | 24 ++ ...sTreeView.xaml => NavigationTreeView.xaml} | 9 +- .../Controls/NavigationTreeView.xaml.cs | 12 + .../VersOne.Epub.WpfDemo/Models/BookModel.cs | 41 +++- .../VersOne.Epub.WpfDemo/Styles/Common.xaml | 9 +- .../Utils/ExpressionUtils.cs | 20 -- .../VersOne.Epub.WpfDemo.csproj | 25 ++- .../ViewModels/BookViewModel.cs | 206 ++++++++++++++---- .../ViewModels/ChapterContentViewModel.cs | 22 -- .../ViewModels/ChapterViewModel.cs | 52 ----- .../ViewModels/HtmlContentFileViewModel.cs | 22 ++ .../ViewModels/LibraryViewModel.cs | 2 +- .../ViewModels/NavigationItemViewModel.cs | 47 ++++ .../ViewModels/ViewModel.cs | 10 +- .../VersOne.Epub.WpfDemo/Views/BookView.xaml | 77 +++++-- 22 files changed, 478 insertions(+), 213 deletions(-) create mode 100644 Source/VersOne.Epub.WpfDemo/Controls/NavigationHeader.xaml rename Source/VersOne.Epub.WpfDemo/Controls/{ContentsTreeView.xaml.cs => NavigationHeader.xaml.cs} (62%) rename Source/VersOne.Epub.WpfDemo/Controls/{ContentsButton.xaml => NavigationItem.xaml} (66%) rename Source/VersOne.Epub.WpfDemo/Controls/{ContentsButton.xaml.cs => NavigationItem.xaml.cs} (64%) create mode 100644 Source/VersOne.Epub.WpfDemo/Controls/NavigationTemplateSelector.cs rename Source/VersOne.Epub.WpfDemo/Controls/{ContentsTreeView.xaml => NavigationTreeView.xaml} (95%) create mode 100644 Source/VersOne.Epub.WpfDemo/Controls/NavigationTreeView.xaml.cs delete mode 100644 Source/VersOne.Epub.WpfDemo/Utils/ExpressionUtils.cs delete mode 100644 Source/VersOne.Epub.WpfDemo/ViewModels/ChapterContentViewModel.cs delete mode 100644 Source/VersOne.Epub.WpfDemo/ViewModels/ChapterViewModel.cs create mode 100644 Source/VersOne.Epub.WpfDemo/ViewModels/HtmlContentFileViewModel.cs create mode 100644 Source/VersOne.Epub.WpfDemo/ViewModels/NavigationItemViewModel.cs diff --git a/Source/EpubReader.sln b/Source/EpubReader.sln index 9a1875b..450eb32 100644 --- a/Source/EpubReader.sln +++ b/Source/EpubReader.sln @@ -20,6 +20,7 @@ Global {313E44F0-4BC2-4A4F-B24B-C29D8FFB7C4E}.Release|Any CPU.ActiveCfg = Release|Any CPU {313E44F0-4BC2-4A4F-B24B-C29D8FFB7C4E}.Release|Any CPU.Build.0 = Release|Any CPU {2C48D6FB-EC93-4B79-8E52-79B579B3C324}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2C48D6FB-EC93-4B79-8E52-79B579B3C324}.Debug|Any CPU.Build.0 = Debug|Any CPU {2C48D6FB-EC93-4B79-8E52-79B579B3C324}.Release|Any CPU.ActiveCfg = Release|Any CPU {2C48D6FB-EC93-4B79-8E52-79B579B3C324}.Release|Any CPU.Build.0 = Release|Any CPU {A6ED4735-3D37-4E44-BEE4-218C6BBAC1BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU diff --git a/Source/VersOne.Epub.WpfDemo/ApplicationContext.cs b/Source/VersOne.Epub.WpfDemo/ApplicationContext.cs index f4d6c4a..919934f 100644 --- a/Source/VersOne.Epub.WpfDemo/ApplicationContext.cs +++ b/Source/VersOne.Epub.WpfDemo/ApplicationContext.cs @@ -64,7 +64,7 @@ private Settings LoadSettings() } using (FileStream fileStream = new FileStream(SETTINGS_FILE_NAME, FileMode.Open)) { - XmlSerializer xmlSerializer = new XmlSerializer(typeof(Settings)); + XmlSerializer xmlSerializer = new XmlSerializer(typeof(Settings), typeof(Settings).GetNestedTypes()); Settings result = (Settings)xmlSerializer.Deserialize(fileStream); return result; } diff --git a/Source/VersOne.Epub.WpfDemo/Controls/BookHtmlContent.cs b/Source/VersOne.Epub.WpfDemo/Controls/BookHtmlContent.cs index b6e75d2..8fb9aa8 100644 --- a/Source/VersOne.Epub.WpfDemo/Controls/BookHtmlContent.cs +++ b/Source/VersOne.Epub.WpfDemo/Controls/BookHtmlContent.cs @@ -5,39 +5,56 @@ using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; -using VersOne.Epub.WpfDemo.ViewModels; using TheArtOfDev.HtmlRenderer.Core.Entities; using TheArtOfDev.HtmlRenderer.WPF; +using VersOne.Epub.WpfDemo.ViewModels; namespace VersOne.Epub.WpfDemo.Controls { public class BookHtmlContent : HtmlPanel { - public static readonly DependencyProperty ChapterContentProperty = DependencyProperty.Register("ChapterContent", typeof(ChapterContentViewModel), typeof(BookHtmlContent), new PropertyMetadata(OnChapterContentChanged)); + public static readonly DependencyProperty HtmlContentFileProperty = DependencyProperty.Register("HtmlContentFile", typeof(HtmlContentFileViewModel), typeof(BookHtmlContent), new PropertyMetadata(OnHtmlContentFileChanged)); + public static readonly DependencyProperty AnchorProperty = DependencyProperty.Register("Anchor", typeof(string), typeof(BookHtmlContent), new PropertyMetadata(OnAnchorChanged)); private bool areFontsRegistered; + private bool isContentLoaded; + private string queuedScrollToAnchor; public BookHtmlContent() { areFontsRegistered = false; + isContentLoaded = false; + queuedScrollToAnchor = null; } - public ChapterContentViewModel ChapterContent + public HtmlContentFileViewModel HtmlContentFile { get { - return (ChapterContentViewModel)GetValue(ChapterContentProperty); + return (HtmlContentFileViewModel)GetValue(HtmlContentFileProperty); } set { - SetValue(ChapterContentProperty, value); + SetValue(HtmlContentFileProperty, value); + } + } + + public string Anchor + { + get + { + return (string)GetValue(AnchorProperty); + } + set + { + SetValue(AnchorProperty, value); } } protected override void OnImageLoad(HtmlImageLoadEventArgs e) { - string imageFilePath = GetFullPath(ChapterContent.HtmlFilePath, e.Src); - if (ChapterContent.Images.TryGetValue(imageFilePath, out byte[] imageContent)) + string imageFilePath = GetFullPath(HtmlContentFile.HtmlFilePath, e.Src); + if (HtmlContentFile.Images.TryGetValue(imageFilePath, out byte[] imageContent)) { using (MemoryStream imageStream = new MemoryStream(imageContent)) { @@ -56,18 +73,17 @@ protected override void OnImageLoad(HtmlImageLoadEventArgs e) protected override void OnStylesheetLoad(HtmlStylesheetLoadEventArgs e) { - string styleSheetFilePath = GetFullPath(ChapterContent.HtmlFilePath, e.Src); - if (ChapterContent.StyleSheets.TryGetValue(styleSheetFilePath, out string styleSheetContent)) + string styleSheetFilePath = GetFullPath(HtmlContentFile.HtmlFilePath, e.Src); + if (HtmlContentFile.StyleSheets.TryGetValue(styleSheetFilePath, out string styleSheetContent)) { e.SetStyleSheet = styleSheetContent; } base.OnStylesheetLoad(e); } - private static void OnChapterContentChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) + private static void OnHtmlContentFileChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) { - BookHtmlContent bookHtmlContent = dependencyObject as BookHtmlContent; - if (bookHtmlContent == null || bookHtmlContent.ChapterContent == null) + if (!(dependencyObject is BookHtmlContent bookHtmlContent) || bookHtmlContent.HtmlContentFile == null) { return; } @@ -75,7 +91,36 @@ private static void OnChapterContentChanged(DependencyObject dependencyObject, D { bookHtmlContent.RegisterFonts(); } - bookHtmlContent.Text = bookHtmlContent.ChapterContent.HtmlContent; + bookHtmlContent.isContentLoaded = false; + bookHtmlContent.queuedScrollToAnchor = null; + bookHtmlContent.Text = bookHtmlContent.HtmlContentFile.HtmlContent; + } + + protected override void OnLoadComplete(EventArgs e) + { + base.OnLoadComplete(e); + if (queuedScrollToAnchor != null) + { + ScrollToElement(queuedScrollToAnchor); + queuedScrollToAnchor = null; + } + isContentLoaded = true; + } + + private static void OnAnchorChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) + { + if (!(dependencyObject is BookHtmlContent bookHtmlContent) || bookHtmlContent.HtmlContentFile == null || bookHtmlContent.Anchor == null) + { + return; + } + if (bookHtmlContent.isContentLoaded) + { + bookHtmlContent.ScrollToElement(bookHtmlContent.Anchor); + } + else + { + bookHtmlContent.queuedScrollToAnchor = bookHtmlContent.Anchor; + } } private string GetFullPath(string htmlFilePath, string relativePath) @@ -96,7 +141,7 @@ private string GetFullPath(string htmlFilePath, string relativePath) private void RegisterFonts() { - foreach (KeyValuePair fontFile in ChapterContent.Fonts) + foreach (KeyValuePair fontFile in HtmlContentFile.Fonts) { MemoryStream packageStream = new MemoryStream(); Package package = Package.Open(packageStream, FileMode.Create, FileAccess.ReadWrite); diff --git a/Source/VersOne.Epub.WpfDemo/Controls/NavigationHeader.xaml b/Source/VersOne.Epub.WpfDemo/Controls/NavigationHeader.xaml new file mode 100644 index 0000000..c7184ec --- /dev/null +++ b/Source/VersOne.Epub.WpfDemo/Controls/NavigationHeader.xaml @@ -0,0 +1,18 @@ + diff --git a/Source/VersOne.Epub.WpfDemo/Controls/ContentsTreeView.xaml.cs b/Source/VersOne.Epub.WpfDemo/Controls/NavigationHeader.xaml.cs similarity index 62% rename from Source/VersOne.Epub.WpfDemo/Controls/ContentsTreeView.xaml.cs rename to Source/VersOne.Epub.WpfDemo/Controls/NavigationHeader.xaml.cs index 3d755f8..b377b4b 100644 --- a/Source/VersOne.Epub.WpfDemo/Controls/ContentsTreeView.xaml.cs +++ b/Source/VersOne.Epub.WpfDemo/Controls/NavigationHeader.xaml.cs @@ -2,9 +2,9 @@ namespace VersOne.Epub.WpfDemo.Controls { - public partial class ContentsTreeView : TreeView + public partial class NavigationHeader : Button { - public ContentsTreeView() + public NavigationHeader() { InitializeComponent(); } diff --git a/Source/VersOne.Epub.WpfDemo/Controls/ContentsButton.xaml b/Source/VersOne.Epub.WpfDemo/Controls/NavigationItem.xaml similarity index 66% rename from Source/VersOne.Epub.WpfDemo/Controls/ContentsButton.xaml rename to Source/VersOne.Epub.WpfDemo/Controls/NavigationItem.xaml index aee7fd1..ab28d22 100644 --- a/Source/VersOne.Epub.WpfDemo/Controls/ContentsButton.xaml +++ b/Source/VersOne.Epub.WpfDemo/Controls/NavigationItem.xaml @@ -1,10 +1,7 @@ -