├── .gitignore ├── LICENSE.md ├── README.md └── RolerFileToolkit ├── Roler.Toolkit.File.Epub ├── Chapter.cs ├── ContentFile.cs ├── Define │ └── Const.cs ├── Engine │ ├── ContainerEngine.cs │ ├── NavEngine.cs │ ├── NcxEngine.cs │ └── OpfEngine.cs ├── Entity │ ├── Container.cs │ ├── Nav │ │ ├── Nav.cs │ │ ├── NavA.cs │ │ ├── NavElement.cs │ │ ├── NavHead.cs │ │ ├── NavLi.cs │ │ ├── NavOl.cs │ │ └── NavSpan.cs │ ├── Ncx │ │ ├── NavInfo.cs │ │ ├── NavMap.cs │ │ ├── NavPoint.cs │ │ ├── NavPointContent.cs │ │ ├── NavPointLabel.cs │ │ └── Ncx.cs │ └── Opf │ │ ├── DcElement.cs │ │ ├── LinkElement.cs │ │ ├── Manifest.cs │ │ ├── ManifestItem.cs │ │ ├── MetaElement.cs │ │ ├── Metadata.cs │ │ ├── Package.cs │ │ ├── Spine.cs │ │ └── SpineItemRef.cs ├── Epub.cs ├── EpubReader.cs ├── ExtendMethod.cs ├── Helper │ └── PathHelper.cs ├── Roler.Toolkit.File.Epub.csproj └── Structure.cs ├── Roler.Toolkit.File.Mobi ├── Compression │ ├── BitReader.cs │ ├── HuffCdicCompression.cs │ ├── ICompression.cs │ ├── NoneCompression.cs │ └── PalmDocCompression.cs ├── ContentFile.cs ├── Define │ └── Const.cs ├── Engine │ ├── ExthHeaderEngine.cs │ ├── IndxHeaderEngine.cs │ ├── MobiHeaderEngine.cs │ ├── PalmDBEngine.cs │ ├── PalmDOCHeaderEngine.cs │ └── RecordEngine.cs ├── Entity │ ├── AudiRecord.cs │ ├── CmetRecord.cs │ ├── ExthHeader │ │ ├── ExthHeader.cs │ │ ├── ExthRecord.cs │ │ └── ExthRecordType.cs │ ├── FcisRecord.cs │ ├── FlisRecord.cs │ ├── IndxHeader │ │ ├── IndexType.cs │ │ └── IndxHeader.cs │ ├── MobiHeader │ │ ├── MobiHeader.cs │ │ ├── MobiType.cs │ │ └── TextEncoding.cs │ ├── PalmDB │ │ ├── PalmDB.cs │ │ ├── PalmDBAttribute.cs │ │ ├── PalmDBRecordAttribute.cs │ │ └── PalmDBRecordInfo.cs │ ├── PalmDOCHeader │ │ ├── CompressionType.cs │ │ ├── EncryptionType.cs │ │ └── PalmDOCHeader.cs │ ├── SrcsRecord.cs │ └── VideRecord.cs ├── ExtendMethod.cs ├── Mobi.cs ├── MobiReader.cs ├── MobiReadingConfiguration.cs ├── PalmDBRecord.cs ├── Roler.Toolkit.File.Mobi.csproj └── Structure.cs ├── RolerFileToolkit.sln └── Samples ├── FileToolkitSample.UWP ├── App.xaml ├── App.xaml.cs ├── Assets │ ├── LockScreenLogo.scale-200.png │ ├── SplashScreen.scale-200.png │ ├── Square150x150Logo.scale-200.png │ ├── Square44x44Logo.scale-200.png │ ├── Square44x44Logo.targetsize-24_altform-unplated.png │ ├── StoreLogo.png │ └── Wide310x150Logo.scale-200.png ├── FileToolkitSample.UWP.csproj ├── MainPage.xaml ├── MainPage.xaml.cs ├── Package.appxmanifest └── Properties │ ├── AssemblyInfo.cs │ └── Default.rd.xml └── FileToolkitSample.WPF ├── App.config ├── App.xaml ├── App.xaml.cs ├── FileToolkitSample.WPF.csproj ├── MainWindow.xaml ├── MainWindow.xaml.cs └── Properties ├── AssemblyInfo.cs ├── Resources.Designer.cs ├── Resources.resx ├── Settings.Designer.cs └── Settings.settings /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 RolerZhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Build Status 2 | | Target | Branch |Recommended package version | 3 | | ------ | ------ | ------ | 4 | | Roler.Toolkit.File.Epub | master | [1.0.1](https://www.nuget.org/packages/Roler.Toolkit.File.Epub) | 5 | | Roler.Toolkit.File.Mobi | master | [1.0.5](https://www.nuget.org/packages/Roler.Toolkit.File.Mobi) | 6 | 7 | ## Sample Code 8 | ### Epub 9 | 10 | ```csharp 11 | using (var epubReader = new EpubReader(stream)) 12 | { 13 | if (epubReader.TryRead(out Epub epub)) 14 | { 15 | var title = epub.Title; 16 | var creator = epub.Creator; 17 | var publisher = epub.Publisher; 18 | var description = epub.Description; 19 | //... 20 | 21 | ContentFile cover = epub.Cover; 22 | IList chapters = epub.Chapters; 23 | IList allFiles = epub.AllFiles; 24 | IList ReadingFiles = epub.ReadingFiles; //Ordered files for read. 25 | 26 | Structure structure = epub.Structure; //Structure inside epub file. 27 | float version = structure.Package.Version; //The version of epub file. 28 | 29 | Stream coverStream = epubReader.ReadContentFile(cover.FilePath); //read content file by file path. 30 | } 31 | } 32 | ``` 33 | 34 | ### Mobi 35 | 36 | ```csharp 37 | using (var mobiReader = new MobiReader(fileStream)) 38 | { 39 | var mobi = mobiReader.Read(); 40 | 41 | var creator = mobi.Creator; 42 | var publisher = mobi.Publisher; 43 | var description = mobi.Description; 44 | //... 45 | 46 | Structure structure = mobi.Structure; //Structure inside mobi file. 47 | 48 | string text = mobi.Text; //full text content. 49 | } 50 | ``` 51 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Chapter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Roler.Toolkit.File.Epub 4 | { 5 | public class Chapter 6 | { 7 | public string Title { get; set; } 8 | public string ContentFilePath { get; set; } 9 | public string SecondPath { get; set; } 10 | public IList Chapters { get; } = new List(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/ContentFile.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Roler.Toolkit.File.Epub 3 | { 4 | public class ContentFile 5 | { 6 | public string MediaType { get; set; } 7 | public string FilePath { get; private set; } 8 | 9 | public ContentFile(string mediaType, string filePath) 10 | { 11 | MediaType = mediaType; 12 | FilePath = filePath; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Define/Const.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace Roler.Toolkit.File.Epub.Define 4 | { 5 | internal class Const 6 | { 7 | public const string CONTAINER_PATH = @"META-INF/container.xml"; 8 | 9 | public readonly static XName ATTRIBUTE_LANGUAGE = XNamespace.Xml + "lang"; 10 | public readonly static XNamespace EPUB_NAMESPACE = @"http://www.idpf.org/2007/ops"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Engine/ContainerEngine.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using System.Xml.Linq; 4 | using Roler.Toolkit.File.Epub.Entity; 5 | 6 | namespace Roler.Toolkit.File.Epub.Engine 7 | { 8 | internal static class ContainerEngine 9 | { 10 | #region Const String 11 | 12 | private const string FULLPATH = "full-path"; 13 | private const string MEDIATYPE = "media-type"; 14 | private const string ROOTFILE = "rootfile"; 15 | 16 | #endregion 17 | 18 | public static Container Read(Stream stream) 19 | { 20 | Container result = null; 21 | using (var streamReader = new StreamReader(stream)) 22 | { 23 | string xmlStr = streamReader.ReadToEnd(); 24 | var document = XElement.Parse(xmlStr.FixXml()); 25 | 26 | var xNamespace = document.GetDefaultNamespace(); 27 | result = new Container 28 | { 29 | Namespace = xNamespace.NamespaceName 30 | }; 31 | 32 | var xElement = document.Descendants(xNamespace + ROOTFILE).FirstOrDefault(); 33 | if (xElement != null) 34 | { 35 | result.FullPath = xElement.Attribute(FULLPATH).Value; 36 | result.MediaType = xElement.Attribute(MEDIATYPE).Value; 37 | } 38 | } 39 | return result; 40 | } 41 | 42 | public static void Write(Container file, Stream stream) 43 | { 44 | } 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Engine/NavEngine.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Xml.Linq; 4 | using Roler.Toolkit.File.Epub.Define; 5 | using Roler.Toolkit.File.Epub.Entity; 6 | 7 | namespace Roler.Toolkit.File.Epub.Engine 8 | { 9 | internal static class NavEngine 10 | { 11 | #region Define 12 | 13 | public const string ELEMENT_NAV = "nav"; 14 | public const string ELEMENT_H1 = "h1"; 15 | public const string ELEMENT_H2 = "h2"; 16 | public const string ELEMENT_H3 = "h3"; 17 | public const string ELEMENT_H4 = "h4"; 18 | public const string ELEMENT_H5 = "h5"; 19 | public const string ELEMENT_H6 = "h6"; 20 | public const string ELEMENT_OL = "ol"; 21 | public const string ELEMENT_LI = "li"; 22 | public const string ELEMENT_A = "a"; 23 | public const string ELEMENT_SPAN = "span"; 24 | 25 | public static readonly XName ATTRIBUTE_TYPE = Const.EPUB_NAMESPACE + "type"; 26 | public const string ATTRIBUTE_ID = "id"; 27 | public const string ATTRIBUTE_HIDDEN = "hidden"; 28 | public const string ATTRIBUTE_HREF = "href"; 29 | public const string ATTRIBUTE_TITLE = "title"; 30 | 31 | private static readonly IReadOnlyList NAV_HEAD_LIST = new List { ELEMENT_H1, ELEMENT_H2, ELEMENT_H3, ELEMENT_H4, ELEMENT_H5, ELEMENT_H6 }; 32 | 33 | #endregion 34 | 35 | public static Nav Read(Stream stream) 36 | { 37 | Nav result = null; 38 | using (var streamReader = new StreamReader(stream)) 39 | { 40 | string xmlStr = streamReader.ReadToEnd(); 41 | var document = XElement.Parse(xmlStr.FixXml()); 42 | 43 | var xNamespace = document.GetDefaultNamespace(); 44 | result = ParseNav(document) ?? throw new InvalidDataException("invalid data of nav file: nav"); 45 | } 46 | return result; 47 | } 48 | 49 | public static void Write(Nav file, Stream stream) 50 | { 51 | } 52 | 53 | private static Nav ParseNav(XElement element) 54 | { 55 | Nav result = null; 56 | 57 | if (element != null) 58 | { 59 | result = new Nav(); 60 | 61 | var xNamespace = element.GetDefaultNamespace(); 62 | foreach (var nav in element.Descendants(xNamespace + ELEMENT_NAV)) 63 | { 64 | var navElement = ParseNavElement(nav); 65 | if (navElement != null) 66 | { 67 | result.NavElements.Add(navElement); 68 | } 69 | } 70 | } 71 | 72 | return result; 73 | } 74 | 75 | private static NavElement ParseNavElement(XElement element) 76 | { 77 | NavElement result = null; 78 | 79 | if (element != null && element.Name.LocalName == ELEMENT_NAV && element.Attribute(ATTRIBUTE_TYPE) != null) 80 | { 81 | var xNamespace = element.GetDefaultNamespace(); 82 | 83 | result = new NavElement 84 | { 85 | Type = element.Attribute(ATTRIBUTE_TYPE)?.Value, 86 | Id = element.Attribute(ATTRIBUTE_ID)?.Value, 87 | IsHidden = element.Attribute(ATTRIBUTE_HIDDEN) != null, 88 | Ol = ParseNavOl(element.Element(xNamespace + ELEMENT_OL)), 89 | }; 90 | 91 | foreach (var head in NAV_HEAD_LIST) 92 | { 93 | var headELement = element.Element(xNamespace + head); 94 | if (headELement != null) 95 | { 96 | result.NavHead = new NavHead 97 | { 98 | Name = head, 99 | Value = headELement.Value, 100 | }; 101 | break; 102 | } 103 | } 104 | 105 | } 106 | 107 | return result; 108 | } 109 | 110 | private static NavOl ParseNavOl(XElement element) 111 | { 112 | NavOl result = null; 113 | 114 | if (element != null && element.Name.LocalName == ELEMENT_OL) 115 | { 116 | var xNamespace = element.GetDefaultNamespace(); 117 | 118 | result = new NavOl 119 | { 120 | Id = element.Attribute(ATTRIBUTE_ID)?.Value, 121 | IsHidden = element.Attribute(ATTRIBUTE_HIDDEN) != null, 122 | }; 123 | 124 | foreach (var liElement in element.Elements(xNamespace + ELEMENT_LI)) 125 | { 126 | var navLi = ParseNavLi(liElement); 127 | if (navLi != null) 128 | { 129 | result.Items.Add(navLi); 130 | } 131 | } 132 | } 133 | 134 | return result; 135 | } 136 | 137 | private static NavLi ParseNavLi(XElement element) 138 | { 139 | NavLi result = null; 140 | 141 | if (element != null && element.Name.LocalName == ELEMENT_LI) 142 | { 143 | var xNamespace = element.GetDefaultNamespace(); 144 | 145 | result = new NavLi 146 | { 147 | Id = element.Attribute(ATTRIBUTE_ID)?.Value, 148 | IsHidden = element.Attribute(ATTRIBUTE_HIDDEN) != null, 149 | Ol = ParseNavOl(element.Element(xNamespace + ELEMENT_OL)), 150 | }; 151 | 152 | var aElement = element.Element(xNamespace + ELEMENT_A); 153 | if (aElement != null) 154 | { 155 | result.A = new NavA 156 | { 157 | Href = aElement.Attribute(ATTRIBUTE_HREF)?.Value, 158 | Title = aElement.Attribute(ATTRIBUTE_TITLE)?.Value, 159 | Value = aElement.Value, 160 | }; 161 | } 162 | var spanElement = element.Element(xNamespace + ELEMENT_SPAN); 163 | if (spanElement != null) 164 | { 165 | result.Span = new NavSpan 166 | { 167 | Value = spanElement.Value, 168 | }; 169 | } 170 | } 171 | 172 | return result; 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Engine/NcxEngine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Xml.Linq; 6 | using Roler.Toolkit.File.Epub.Define; 7 | using Roler.Toolkit.File.Epub.Entity; 8 | 9 | namespace Roler.Toolkit.File.Epub.Engine 10 | { 11 | internal static class NcxEngine 12 | { 13 | #region Define 14 | 15 | public const string ELEMENT_NCX = "ncx"; 16 | public const string ELEMENT_NAVMAP = "navMap"; 17 | public const string ELEMENT_NAVINFO = "navInfo"; 18 | public const string ELEMENT_TEXT = "text"; 19 | public const string ELEMENT_NAVPOINT = "navPoint"; 20 | public const string ELEMENT_NAVLABEL = "navLabel"; 21 | public const string ELEMENT_CONTENT = "content"; 22 | 23 | public const string ATTRIBUTE_VERSION = "version"; 24 | public const string ATTRIBUTE_ID = "id"; 25 | public const string ATTRIBUTE_PLAYORDER = "playOrder"; 26 | public const string ATTRIBUTE_SRC = "src"; 27 | 28 | #endregion 29 | 30 | public static Ncx Read(Stream stream) 31 | { 32 | Ncx result = null; 33 | using (var streamReader = new StreamReader(stream)) 34 | { 35 | string xmlStr = streamReader.ReadToEnd(); 36 | var document = XElement.Parse(xmlStr.FixXml()); 37 | 38 | var xNamespace = document.GetDefaultNamespace(); 39 | result = ParseNcx(document) ?? throw new InvalidDataException("invalid data of ncx file: ncx"); 40 | } 41 | return result; 42 | } 43 | 44 | public static void Write(Ncx file, Stream stream) 45 | { 46 | } 47 | 48 | private static Ncx ParseNcx(XElement element) 49 | { 50 | Ncx result = null; 51 | 52 | if (element != null && element.Name.LocalName == ELEMENT_NCX) 53 | { 54 | var xNamespace = element.Name.Namespace; 55 | 56 | result = new Ncx 57 | { 58 | Version = element.Attribute(ATTRIBUTE_VERSION)?.Value, 59 | Language = element.Attribute(Const.ATTRIBUTE_LANGUAGE)?.Value, 60 | NavMap = ParseNavMap(element.Element(xNamespace + ELEMENT_NAVMAP)), 61 | }; 62 | } 63 | 64 | return result; 65 | } 66 | 67 | private static NavMap ParseNavMap(XElement element) 68 | { 69 | NavMap result = null; 70 | 71 | if (element != null && element.Name.LocalName == ELEMENT_NAVMAP) 72 | { 73 | var xNamespace = element.Name.Namespace; 74 | result = new NavMap(); 75 | var infoText = element.Element(xNamespace + ELEMENT_NAVINFO)?.Element(xNamespace + ELEMENT_TEXT).Value; 76 | if (!String.IsNullOrWhiteSpace(infoText)) 77 | { 78 | result.NavInfo = new NavInfo { Text = infoText }; 79 | } 80 | result.NavPoints = ParseNavPointList(element.Elements(xNamespace + ELEMENT_NAVPOINT), xNamespace); 81 | } 82 | 83 | return result; 84 | } 85 | 86 | private static IList ParseNavPointList(IEnumerable elements, XNamespace xNamespace) 87 | { 88 | IList result = null; 89 | if (elements != null && elements.Any()) 90 | { 91 | var navPoints = new List(); 92 | var data = from element in elements 93 | select new NavPoint 94 | { 95 | Id = element.Attribute(ATTRIBUTE_ID)?.Value, 96 | PlayOrder = element.Attribute(ATTRIBUTE_PLAYORDER)?.Value, 97 | Label = ParseNavPointLabel(element.Element(xNamespace + ELEMENT_NAVLABEL)), 98 | Content = ParseNavPointContent(element.Element(xNamespace + ELEMENT_CONTENT)), 99 | Children = ParseNavPointList(element.Elements(xNamespace + ELEMENT_NAVPOINT), xNamespace), 100 | }; 101 | navPoints.AddRange(data); 102 | result = navPoints; 103 | } 104 | return result; 105 | } 106 | 107 | private static NavPointLabel ParseNavPointLabel(XElement element) 108 | { 109 | NavPointLabel result = null; 110 | if (element != null && element.Name.LocalName == ELEMENT_NAVLABEL) 111 | { 112 | var xNamespace = element.Name.Namespace; 113 | var text = element.Element(xNamespace + ELEMENT_TEXT).Value; 114 | if (!String.IsNullOrWhiteSpace(text)) 115 | { 116 | result = new NavPointLabel { Text = text }; 117 | } 118 | } 119 | return result; 120 | } 121 | 122 | private static NavPointContent ParseNavPointContent(XElement element) 123 | { 124 | NavPointContent result = null; 125 | if (element != null && element.Name.LocalName == ELEMENT_CONTENT) 126 | { 127 | result = new NavPointContent 128 | { 129 | Id = element.Attribute(ATTRIBUTE_ID)?.Value, 130 | Source = element.Attribute(ATTRIBUTE_SRC)?.Value, 131 | }; 132 | } 133 | return result; 134 | } 135 | 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Engine/OpfEngine.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Xml.Linq; 3 | using Roler.Toolkit.File.Epub.Define; 4 | using Roler.Toolkit.File.Epub.Entity; 5 | 6 | namespace Roler.Toolkit.File.Epub.Engine 7 | { 8 | internal static class OpfEngine 9 | { 10 | #region Define 11 | 12 | public const string NAMESPACE_DC = "http://purl.org/dc/elements/1.1/"; 13 | 14 | public const string ELEMENT_PACKAGE = "package"; 15 | public const string ELEMENT_METADATA = "metadata"; 16 | public const string ELEMENT_META = "meta"; 17 | public const string ELEMENT_LINK = "link"; 18 | public const string ELEMENT_MANIFEST = "manifest"; 19 | public const string ELEMENT_ITEM = "item"; 20 | public const string ELEMENT_SPINE = "spine"; 21 | public const string ELEMENT_ITEMREF = "itemref"; 22 | 23 | public const string DC_ELEMENT_CONTRIBUTOR = "contributor"; 24 | public const string DC_ELEMENT_COVERAGE = "coverage"; 25 | public const string DC_ELEMENT_CREATOR = "creator"; 26 | public const string DC_ELEMENT_DATE = "date"; 27 | public const string DC_ELEMENT_DESCRIPTION = "description"; 28 | public const string DC_ELEMENT_FORMAT = "format"; 29 | public const string DC_ELEMENT_IDENTIFIER = "identifier"; 30 | public const string DC_ELEMENT_LANGUAGE = "language"; 31 | public const string DC_ELEMENT_PUBLISHER = "publisher"; 32 | public const string DC_ELEMENT_RELATION = "relation"; 33 | public const string DC_ELEMENT_RIGHTS = "rights"; 34 | public const string DC_ELEMENT_SOURCE = "source"; 35 | public const string DC_ELEMENT_SUBJECT = "subject"; 36 | public const string DC_ELEMENT_TITLE = "title"; 37 | public const string DC_ELEMENT_TYPE = "type"; 38 | 39 | public const string ATTRIBUTE_VERSION = "version"; 40 | public const string ATTRIBUTE_IDENTIFIER = "unique-identifier"; 41 | public const string ATTRIBUTE_PREFIX = "prefix"; 42 | public const string ATTRIBUTE_DIR = "dir"; 43 | public const string ATTRIBUTE_ID = "id"; 44 | public const string ATTRIBUTE_SCHEME = "scheme"; 45 | public const string ATTRIBUTE_PROPERTY = "property"; 46 | public const string ATTRIBUTE_REFINES = "refines"; 47 | public const string ATTRIBUTE_NAME = "name"; 48 | public const string ATTRIBUTE_CONTENT = "content"; 49 | public const string ATTRIBUTE_HREF = "href"; 50 | public const string ATTRIBUTE_REL = "rel"; 51 | public const string ATTRIBUTE_MEDIATYPE = "media-type"; 52 | public const string ATTRIBUTE_FALLBACK = "fallback"; 53 | public const string ATTRIBUTE_PROPERTIES = "properties"; 54 | public const string ATTRIBUTE_MEDIAOVERLAY = "media-overlay"; 55 | public const string ATTRIBUTE_TOC = "toc"; 56 | public const string ATTRIBUTE_PAGEPROGRESSIONDIRECTION = "page-progression-direction"; 57 | public const string ATTRIBUTE_IDREF = "idref"; 58 | public const string ATTRIBUTE_LINEAR = "linear"; 59 | 60 | #endregion 61 | 62 | public static Package Read(Stream stream) 63 | { 64 | Package result = null; 65 | using (var streamReader = new StreamReader(stream)) 66 | { 67 | string xmlStr = streamReader.ReadToEnd(); 68 | var document = XElement.Parse(xmlStr.FixXml()); 69 | 70 | var xNamespace = document.GetDefaultNamespace(); 71 | result = ParsePackage(document) ?? throw new InvalidDataException("invalid data of opf file: package"); 72 | 73 | var metadataElement = document.Element(xNamespace + ELEMENT_METADATA); 74 | var metadata = ParseMetadata(metadataElement); 75 | result.Metadata = metadata ?? throw new InvalidDataException("invalid data of opf file: metadata"); 76 | 77 | var manifestElement = document.Element(xNamespace + ELEMENT_MANIFEST); 78 | var manifest = ParseManifest(manifestElement); 79 | result.Manifest = manifest ?? throw new InvalidDataException("invalid data of opf file: manifest"); 80 | 81 | var spineElement = document.Element(xNamespace + ELEMENT_SPINE); 82 | var spine = ParseSpine(spineElement); 83 | result.Spine = spine ?? throw new InvalidDataException("invalid data of opf file: spine"); 84 | } 85 | return result; 86 | } 87 | 88 | public static void Write(Container file, Stream stream) 89 | { 90 | } 91 | 92 | private static Package ParsePackage(XElement element) 93 | { 94 | Package result = null; 95 | 96 | if (element != null) 97 | { 98 | result = new Package 99 | { 100 | Identifier = element.Attribute(ATTRIBUTE_IDENTIFIER)?.Value, 101 | Prefix = element.Attribute(ATTRIBUTE_PREFIX)?.Value, 102 | Language = element.Attribute(Const.ATTRIBUTE_LANGUAGE)?.Value, 103 | Dir = element.Attribute(ATTRIBUTE_DIR)?.Value, 104 | Id = element.Attribute(ATTRIBUTE_ID)?.Value, 105 | }; 106 | 107 | if (float.TryParse(element.Attribute(ATTRIBUTE_VERSION)?.Value, out float version)) 108 | { 109 | result.Version = version; 110 | } 111 | } 112 | 113 | return result; 114 | } 115 | 116 | private static Metadata ParseMetadata(XElement element) 117 | { 118 | Metadata result = null; 119 | 120 | if (element != null && element.Name.LocalName == ELEMENT_METADATA) 121 | { 122 | result = new Metadata(); 123 | foreach (var childElement in element.Elements()) 124 | { 125 | if (childElement.Name.Namespace == NAMESPACE_DC) 126 | { 127 | var dcElement = new DcElement 128 | { 129 | Id = childElement.Attribute(ATTRIBUTE_ID)?.Value, 130 | Language = childElement.Attribute(Const.ATTRIBUTE_LANGUAGE)?.Value, 131 | Dir = childElement.Attribute(ATTRIBUTE_DIR)?.Value, 132 | Value = childElement.Value 133 | }; 134 | switch (childElement.Name.LocalName) 135 | { 136 | case DC_ELEMENT_CONTRIBUTOR: result.Contributors.Add(dcElement); break; 137 | case DC_ELEMENT_COVERAGE: result.Coverages.Add(dcElement); break; 138 | case DC_ELEMENT_CREATOR: result.Creators.Add(dcElement); break; 139 | case DC_ELEMENT_DATE: result.Date = dcElement; break; 140 | case DC_ELEMENT_DESCRIPTION: result.Descriptions.Add(dcElement); break; 141 | case DC_ELEMENT_FORMAT: result.Formats.Add(dcElement); break; 142 | case DC_ELEMENT_IDENTIFIER: result.Identifiers.Add(dcElement); break; 143 | case DC_ELEMENT_LANGUAGE: result.Languages.Add(dcElement); break; 144 | case DC_ELEMENT_PUBLISHER: result.Publishers.Add(dcElement); break; 145 | case DC_ELEMENT_RELATION: result.Relations.Add(dcElement); break; 146 | case DC_ELEMENT_RIGHTS: result.Rights.Add(dcElement); break; 147 | case DC_ELEMENT_SOURCE: result.Sources.Add(dcElement); break; 148 | case DC_ELEMENT_SUBJECT: result.Subjects.Add(dcElement); break; 149 | case DC_ELEMENT_TITLE: result.Titles.Add(dcElement); break; 150 | case DC_ELEMENT_TYPE: result.Types.Add(dcElement); break; 151 | default: break; 152 | } 153 | } 154 | else 155 | { 156 | switch (childElement.Name.LocalName) 157 | { 158 | case ELEMENT_META: 159 | { 160 | var metaElement = new MetaElement 161 | { 162 | Id = childElement.Attribute(ATTRIBUTE_ID)?.Value, 163 | Language = childElement.Attribute(Const.ATTRIBUTE_LANGUAGE)?.Value, 164 | Dir = childElement.Attribute(ATTRIBUTE_DIR)?.Value, 165 | Scheme = childElement.Attribute(ATTRIBUTE_SCHEME)?.Value, 166 | Property = childElement.Attribute(ATTRIBUTE_PROPERTY)?.Value, 167 | Refines = childElement.Attribute(ATTRIBUTE_REFINES)?.Value, 168 | Value = childElement.Value, 169 | Name = childElement.Attribute(ATTRIBUTE_NAME)?.Value, 170 | Content = childElement.Attribute(ATTRIBUTE_CONTENT)?.Value, 171 | }; 172 | result.Metas.Add(metaElement); 173 | } 174 | break; 175 | case ELEMENT_LINK: 176 | { 177 | var linkElement = new LinkElement 178 | { 179 | Href = childElement.Attribute(ATTRIBUTE_HREF)?.Value, 180 | Rel = childElement.Attribute(ATTRIBUTE_REL)?.Value, 181 | Id = childElement.Attribute(ATTRIBUTE_ID)?.Value, 182 | Refines = childElement.Attribute(ATTRIBUTE_REFINES)?.Value, 183 | MediaType = childElement.Attribute(ATTRIBUTE_MEDIATYPE)?.Value, 184 | }; 185 | result.Links.Add(linkElement); 186 | } 187 | break; 188 | default: break; 189 | } 190 | } 191 | } 192 | } 193 | 194 | return result; 195 | } 196 | 197 | private static Manifest ParseManifest(XElement element) 198 | { 199 | Manifest result = null; 200 | 201 | if (element != null && element.Name.LocalName == ELEMENT_MANIFEST) 202 | { 203 | result = new Manifest 204 | { 205 | Id = element.Attribute(ATTRIBUTE_ID)?.Value 206 | }; 207 | foreach (var childElement in element.Elements()) 208 | { 209 | if (childElement.Name.LocalName == ELEMENT_ITEM) 210 | { 211 | var manifestItem = new ManifestItem 212 | { 213 | Id = childElement.Attribute(ATTRIBUTE_ID)?.Value, 214 | Href = childElement.Attribute(ATTRIBUTE_HREF)?.Value, 215 | MediaType = childElement.Attribute(ATTRIBUTE_MEDIATYPE)?.Value, 216 | Fallback = childElement.Attribute(ATTRIBUTE_FALLBACK)?.Value, 217 | Properties = childElement.Attribute(ATTRIBUTE_PROPERTIES)?.Value, 218 | MediaOverlay = childElement.Attribute(ATTRIBUTE_MEDIAOVERLAY)?.Value, 219 | }; 220 | result.Items.Add(manifestItem); 221 | } 222 | } 223 | } 224 | return result; 225 | } 226 | 227 | private static Spine ParseSpine(XElement element) 228 | { 229 | Spine result = null; 230 | 231 | if (element != null && element.Name.LocalName == ELEMENT_SPINE) 232 | { 233 | result = new Spine 234 | { 235 | Id = element.Attribute(ATTRIBUTE_ID)?.Value, 236 | Toc = element.Attribute(ATTRIBUTE_TOC)?.Value, 237 | PageProgressionDirection = element.Attribute(ATTRIBUTE_PAGEPROGRESSIONDIRECTION)?.Value, 238 | }; 239 | foreach (var childElement in element.Elements()) 240 | { 241 | if (childElement.Name.LocalName == ELEMENT_ITEMREF) 242 | { 243 | var spineItemRef = new SpineItemRef 244 | { 245 | IdRef = childElement.Attribute(ATTRIBUTE_IDREF)?.Value, 246 | Linear = childElement.Attribute(ATTRIBUTE_LINEAR)?.Value, 247 | Id = childElement.Attribute(ATTRIBUTE_ID)?.Value, 248 | Properties = childElement.Attribute(ATTRIBUTE_PROPERTIES)?.Value, 249 | }; 250 | result.Items.Add(spineItemRef); 251 | } 252 | } 253 | } 254 | return result; 255 | } 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Entity/Container.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Epub.Entity 2 | { 3 | public class Container 4 | { 5 | public string Namespace { get; set; } 6 | public string FullPath { get; set; } 7 | public string MediaType { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Entity/Nav/Nav.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Roler.Toolkit.File.Epub.Entity 4 | { 5 | public class Nav 6 | { 7 | public IList NavElements { get; } = new List(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Entity/Nav/NavA.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Epub.Entity 2 | { 3 | public class NavA 4 | { 5 | public string Href { get; set; } 6 | public string Title { get; set; } 7 | public string Value { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Entity/Nav/NavElement.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Epub.Entity 2 | { 3 | public class NavElement 4 | { 5 | public string Type { get; set; } 6 | public string Id { get; set; } 7 | public bool IsHidden { get; set; } 8 | public NavHead NavHead { get; set; } 9 | public NavOl Ol { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Entity/Nav/NavHead.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Epub.Entity 2 | { 3 | public class NavHead 4 | { 5 | public string Name { get; set; } 6 | public string Value { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Entity/Nav/NavLi.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Epub.Entity 2 | { 3 | public class NavLi 4 | { 5 | public string Id { get; set; } 6 | public bool IsHidden { get; set; } 7 | public NavA A { get; set; } 8 | public NavSpan Span { get; set; } 9 | public NavOl Ol { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Entity/Nav/NavOl.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Roler.Toolkit.File.Epub.Entity 4 | { 5 | public class NavOl 6 | { 7 | public string Id { get; set; } 8 | public bool IsHidden { get; set; } 9 | public IList Items { get; } = new List(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Entity/Nav/NavSpan.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Epub.Entity 2 | { 3 | public class NavSpan 4 | { 5 | public string Value { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Entity/Ncx/NavInfo.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Epub.Entity 2 | { 3 | public class NavInfo 4 | { 5 | public string Text { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Entity/Ncx/NavMap.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Roler.Toolkit.File.Epub.Entity 4 | { 5 | public class NavMap 6 | { 7 | public NavInfo NavInfo { get; set; } 8 | public IList NavPoints { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Entity/Ncx/NavPoint.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Roler.Toolkit.File.Epub.Entity 4 | { 5 | public class NavPoint 6 | { 7 | public string Id { get; set; } 8 | public string PlayOrder { get; set; } 9 | public NavPointLabel Label { get; set; } 10 | public NavPointContent Content { get; set; } 11 | public IList Children { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Entity/Ncx/NavPointContent.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Epub.Entity 2 | { 3 | public class NavPointContent 4 | { 5 | public string Id { get; set; } 6 | public string Source { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Entity/Ncx/NavPointLabel.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Epub.Entity 2 | { 3 | public class NavPointLabel 4 | { 5 | public string Text { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Entity/Ncx/Ncx.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Epub.Entity 2 | { 3 | public class Ncx 4 | { 5 | public string Version { get; set; } 6 | public string Language { get; set; } 7 | public NavMap NavMap { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Entity/Opf/DcElement.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Epub.Entity 2 | { 3 | /// 4 | /// The Element of DCMES. 5 | /// 6 | public class DcElement 7 | { 8 | public string Id { get; set; } 9 | public string Language { get; set; } 10 | public string Dir { get; set; } 11 | public string Value { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Entity/Opf/LinkElement.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Epub.Entity 2 | { 3 | public class LinkElement 4 | { 5 | public string Href { get; set; } 6 | public string Rel { get; set; } 7 | public string Id { get; set; } 8 | public string Refines { get; set; } 9 | public string MediaType { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Entity/Opf/Manifest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Roler.Toolkit.File.Epub.Entity 4 | { 5 | public class Manifest 6 | { 7 | public string Id { get; set; } 8 | public IList Items { get; } = new List(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Entity/Opf/ManifestItem.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Epub.Entity 2 | { 3 | public class ManifestItem 4 | { 5 | public string Id { get; set; } 6 | public string Href { get; set; } 7 | public string MediaType { get; set; } 8 | public string Fallback { get; set; } 9 | public string Properties { get; set; } 10 | public string MediaOverlay { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Entity/Opf/MetaElement.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Epub.Entity 2 | { 3 | public class MetaElement 4 | { 5 | public string Id { get; set; } 6 | public string Language { get; set; } 7 | public string Dir { get; set; } 8 | public string Scheme { get; set; } 9 | public string Property { get; set; } 10 | public string Refines { get; set; } 11 | public string Value { get; set; } 12 | 13 | /// 14 | /// Attribute name, Only Epub2.0 15 | /// 16 | public string Name { get; set; } 17 | 18 | /// 19 | /// Attribute content, Only Epub2.0 20 | /// 21 | public string Content { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Entity/Opf/Metadata.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Roler.Toolkit.File.Epub.Entity 4 | { 5 | public class Metadata 6 | { 7 | public IList Contributors { get; } = new List(); 8 | public IList Coverages { get; } = new List(); 9 | public IList Creators { get; } = new List(); 10 | public DcElement Date { get; set; } 11 | public IList Descriptions { get; } = new List(); 12 | public IList Formats { get; } = new List(); 13 | public IList Identifiers { get; } = new List(); 14 | public IList Languages { get; } = new List(); 15 | public IList Publishers { get; } = new List(); 16 | public IList Relations { get; } = new List(); 17 | public IList Rights { get; } = new List(); 18 | public IList Sources { get; } = new List(); 19 | public IList Subjects { get; } = new List(); 20 | public IList Titles { get; } = new List(); 21 | public IList Types { get; } = new List(); 22 | public IList Metas { get; } = new List(); 23 | public IList Links { get; } = new List(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Entity/Opf/Package.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Epub.Entity 2 | { 3 | public class Package 4 | { 5 | public float Version { get; set; } 6 | public string Identifier { get; set; } 7 | public string Prefix { get; set; } 8 | public string Language { get; set; } 9 | public string Dir { get; set; } 10 | public string Id { get; set; } 11 | public Metadata Metadata { get; set; } 12 | public Manifest Manifest { get; set; } 13 | public Spine Spine { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Entity/Opf/Spine.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Roler.Toolkit.File.Epub.Entity 4 | { 5 | public class Spine 6 | { 7 | public string Id { get; set; } 8 | public string Toc { get; set; } 9 | public string PageProgressionDirection { get; set; } 10 | public IList Items { get; } = new List(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Entity/Opf/SpineItemRef.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Epub.Entity 2 | { 3 | public class SpineItemRef 4 | { 5 | public string IdRef { get; set; } 6 | public string Linear { get; set; } 7 | public string Id { get; set; } 8 | public string Properties { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Epub.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Roler.Toolkit.File.Epub 4 | { 5 | public class Epub 6 | { 7 | public Structure Structure { get; set; } 8 | 9 | public string Contributor { get; set; } 10 | public string Coverage { get; set; } 11 | public string Creator { get; set; } 12 | public string Date { get; set; } 13 | public string Description { get; set; } 14 | public string Format { get; set; } 15 | public string Identifier { get; set; } 16 | public string Language { get; set; } 17 | public string Publisher { get; set; } 18 | public string Relation { get; set; } 19 | public string Rights { get; set; } 20 | public string Source { get; set; } 21 | public string Subject { get; set; } 22 | public string Title { get; set; } 23 | public string Type { get; set; } 24 | public ContentFile Cover { get; set; } 25 | public IList Chapters { get; } = new List(); 26 | public IList ReadingFiles { get; } = new List(); 27 | public IList AllFiles { get; } = new List(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/EpubReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.IO.Compression; 5 | using System.Linq; 6 | using Roler.Toolkit.File.Epub.Define; 7 | using Roler.Toolkit.File.Epub.Engine; 8 | using Roler.Toolkit.File.Epub.Entity; 9 | using Roler.Toolkit.File.Epub.Helper; 10 | 11 | namespace Roler.Toolkit.File.Epub 12 | { 13 | public class EpubReader : IDisposable 14 | { 15 | private const string SEPARATOR = ","; 16 | private const string COVER_PROPERTY = "cover-image"; 17 | private const string COVER_NAME = "cover"; 18 | private const char HREF_SEPARATOR = '#'; 19 | 20 | private bool _disposed; 21 | private readonly ZipArchive _archive; 22 | 23 | public EpubReader(Stream stream) 24 | { 25 | this._archive = new ZipArchive(stream); 26 | } 27 | 28 | #region Methods 29 | 30 | public Epub Read() 31 | { 32 | if (this._disposed) 33 | { 34 | throw new ObjectDisposedException("archive"); 35 | } 36 | 37 | var structure = this.ReadStructure(); 38 | var metadata = structure.Package.Metadata; 39 | var result = new Epub 40 | { 41 | Structure = structure, 42 | Contributor = String.Join(SEPARATOR, metadata.Contributors.Select(p => p.Value)), 43 | Coverage = String.Join(SEPARATOR, metadata.Coverages.Select(p => p.Value)), 44 | Creator = String.Join(SEPARATOR, metadata.Creators.Select(p => p.Value)), 45 | Date = metadata.Date?.Value, 46 | Description = String.Join(SEPARATOR, metadata.Descriptions.Select(p => p.Value)), 47 | Format = String.Join(SEPARATOR, metadata.Formats.Select(p => p.Value)), 48 | Identifier = String.Join(SEPARATOR, metadata.Identifiers.Select(p => p.Value)), 49 | Language = String.Join(SEPARATOR, metadata.Languages.Select(p => p.Value)), 50 | Publisher = String.Join(SEPARATOR, metadata.Publishers.Select(p => p.Value)), 51 | Relation = String.Join(SEPARATOR, metadata.Relations.Select(p => p.Value)), 52 | Rights = String.Join(SEPARATOR, metadata.Rights.Select(p => p.Value)), 53 | Source = String.Join(SEPARATOR, metadata.Sources.Select(p => p.Value)), 54 | Subject = String.Join(SEPARATOR, metadata.Subjects.Select(p => p.Value)), 55 | Title = String.Join(SEPARATOR, metadata.Titles.Select(p => p.Value)), 56 | Type = String.Join(SEPARATOR, metadata.Types.Select(p => p.Value)), 57 | }; 58 | 59 | var opfDirectory = Path.GetDirectoryName(structure.Container.FullPath); 60 | 61 | result.Cover = FindCoverFile(structure.Package, opfDirectory); 62 | FillChapters(result.Chapters, structure, opfDirectory); 63 | FillReadingFiles(result.ReadingFiles, structure.Package, opfDirectory); 64 | FillAllFiles(result.AllFiles, structure.Package, opfDirectory); 65 | 66 | return result; 67 | } 68 | 69 | public bool TryRead(out Epub epub) 70 | { 71 | bool result; 72 | 73 | try 74 | { 75 | epub = this.Read(); 76 | result = true; 77 | } 78 | catch (Exception) 79 | { 80 | epub = null; 81 | result = false; 82 | } 83 | 84 | return result; 85 | } 86 | 87 | public Stream ReadContentFile(string filePath) 88 | { 89 | if (this._disposed) 90 | { 91 | throw new ObjectDisposedException("archive"); 92 | } 93 | 94 | if (!String.IsNullOrWhiteSpace(filePath)) 95 | { 96 | var zipEntry = this._archive.GetEntry(filePath); 97 | if (zipEntry != null) 98 | { 99 | return zipEntry.Open(); 100 | } 101 | } 102 | return Stream.Null; 103 | } 104 | 105 | public string ReadContentAsText(string filePath) 106 | { 107 | using (var stream = this.ReadContentFile(filePath)) 108 | { 109 | using (var streamReader = new StreamReader(stream)) 110 | { 111 | return streamReader.ReadToEnd(); 112 | } 113 | } 114 | } 115 | 116 | #region Structure 117 | 118 | private Structure ReadStructure() 119 | { 120 | var container = ReadFileFromArchive(this._archive, Const.CONTAINER_PATH, p => ContainerEngine.Read(p)); 121 | if (container == null) 122 | { 123 | throw new InvalidDataException("container file not found."); 124 | } 125 | 126 | Structure result = new Structure 127 | { 128 | Container = container, 129 | Package = ReadFileFromArchive(this._archive, container.FullPath, p => OpfEngine.Read(p)) ?? throw new InvalidDataException("opf file not found."), 130 | }; 131 | 132 | var opfDirectory = Path.GetDirectoryName(container.FullPath); 133 | if (result.Package.Version >= 3f) 134 | { 135 | string relativePath = FindNavFilePath(result.Package); 136 | var filePath = PathHelper.Combine(opfDirectory, relativePath); 137 | result.Nav = ReadFileFromArchive(this._archive, filePath, p => NavEngine.Read(p)) ?? throw new InvalidDataException("navigation file not found."); 138 | } 139 | else 140 | { 141 | var relativePath = FindNcxFilePath(result.Package); 142 | var filePath = PathHelper.Combine(opfDirectory, relativePath); 143 | result.Ncx = ReadFileFromArchive(this._archive, filePath, p => NcxEngine.Read(p)) ?? throw new InvalidDataException("ncx file not found."); 144 | } 145 | 146 | return result; 147 | } 148 | 149 | private static T ReadFileFromArchive(ZipArchive zipArchive, string filePath, Func func) 150 | { 151 | if (zipArchive != null && !String.IsNullOrWhiteSpace(filePath) && func != null) 152 | { 153 | var zipEntry = zipArchive.GetEntry(filePath); 154 | if (zipEntry != null && zipEntry.Length > 0) 155 | { 156 | using (Stream stream = zipEntry.Open()) 157 | { 158 | return func(stream); 159 | } 160 | } 161 | } 162 | return default(T); 163 | } 164 | 165 | private static string FindNavFilePath(Package package) 166 | { 167 | if (package != null && package.Manifest != null && package.Manifest.Items.Any()) 168 | { 169 | var mainfestItem = package.Manifest.Items.FirstOrDefault(p => p.IsPropertiesContains("nav")); 170 | return mainfestItem?.Href; 171 | } 172 | return null; 173 | } 174 | 175 | private static string FindNcxFilePath(Package package) 176 | { 177 | if (package != null && package.Spine != null && !String.IsNullOrWhiteSpace(package.Spine.Toc) && package.Manifest != null) 178 | { 179 | var mainfestItem = package.Manifest.Items.FirstOrDefault(p => p.Id == package.Spine.Toc); 180 | return mainfestItem?.Href; 181 | } 182 | return null; 183 | } 184 | 185 | #endregion 186 | 187 | #region Cover 188 | 189 | private static ContentFile FindCoverFile(Package package, string opfDirectory) 190 | { 191 | var coverItem = FindCoverManifestItem(package); 192 | if (coverItem != null) 193 | { 194 | return new ContentFile(coverItem.MediaType, PathHelper.Combine(opfDirectory, coverItem.Href)); 195 | } 196 | return null; 197 | } 198 | 199 | private static ManifestItem FindCoverManifestItem(Package package) 200 | { 201 | if (package != null && package.Manifest != null && package.Manifest.Items.Any()) 202 | { 203 | if (package.Version >= 3f) 204 | { 205 | return package.Manifest.Items.FirstOrDefault(p => p.IsPropertiesContains(COVER_PROPERTY)); 206 | } 207 | else 208 | { 209 | var manifestItemId = package.Metadata?.Metas?.FirstOrDefault(p => p.Name == COVER_NAME)?.Content; 210 | if (!String.IsNullOrWhiteSpace(manifestItemId)) 211 | { 212 | return package.Manifest.Items.FirstOrDefault(p => p.Id == manifestItemId); 213 | } 214 | } 215 | } 216 | return null; 217 | } 218 | 219 | #endregion 220 | 221 | #region Chapters 222 | 223 | private static void FillChapters(IList chapters, Structure structure, string opfDirectory) 224 | { 225 | if (chapters != null && structure != null && structure.Package != null) 226 | { 227 | if (structure.Package.Version >= 3f) 228 | { 229 | FillChatpersByNav(chapters, structure.Nav, opfDirectory); 230 | } 231 | else 232 | { 233 | FillChaptersByNcx(chapters, structure.Ncx, opfDirectory); 234 | } 235 | } 236 | } 237 | 238 | private static void FillChatpersByNav(IList chapters, Nav nav, string opfDirectory) 239 | { 240 | if (chapters != null && nav != null) 241 | { 242 | var navLis = nav.NavElements.FirstOrDefault(p => !p.IsHidden)?.Ol?.Items?.Where(p => !p.IsHidden); 243 | if (navLis != null && navLis.Any()) 244 | { 245 | foreach (var li in navLis) 246 | { 247 | chapters.Add(GetChapterFromNavLi(li, opfDirectory)); 248 | } 249 | } 250 | } 251 | } 252 | 253 | private static void FillChaptersByNcx(IList chapters, Ncx ncx, string opfDirectory) 254 | { 255 | if (chapters != null && ncx != null && ncx.NavMap != null && ncx.NavMap.NavPoints != null) 256 | { 257 | foreach (var navPoint in ncx.NavMap.NavPoints) 258 | { 259 | chapters.Add(GetChapterFromNavPoint(navPoint, opfDirectory)); 260 | } 261 | } 262 | } 263 | 264 | private static Chapter GetChapterFromNavLi(NavLi li, string opfDirectory) 265 | { 266 | Chapter result = null; 267 | if (li != null && !li.IsHidden) 268 | { 269 | result = new Chapter(); 270 | if (li.A != null) 271 | { 272 | result.Title = String.IsNullOrWhiteSpace(li.A.Value) ? li.A.Title : li.A.Value; 273 | if (!String.IsNullOrWhiteSpace(li.A.Href)) 274 | { 275 | var hrefSplit = li.A.Href.Split(new char[] { HREF_SEPARATOR }); 276 | result.ContentFilePath = PathHelper.Combine(opfDirectory, hrefSplit[0]); 277 | if (hrefSplit.Length > 1) 278 | { 279 | result.SecondPath = hrefSplit[1]; 280 | } 281 | } 282 | } 283 | else if (li.Span != null) 284 | { 285 | result.Title = li.Span.Value; 286 | } 287 | 288 | if (li.Ol != null && !li.Ol.IsHidden && li.Ol.Items.Any()) 289 | { 290 | foreach (var liItem in li.Ol.Items) 291 | { 292 | result.Chapters.Add(GetChapterFromNavLi(liItem, opfDirectory)); 293 | } 294 | } 295 | } 296 | return result; 297 | } 298 | 299 | private static Chapter GetChapterFromNavPoint(NavPoint navPoint, string opfDirectory) 300 | { 301 | Chapter result = null; 302 | if (navPoint != null) 303 | { 304 | result = new Chapter { Title = navPoint.Label?.Text }; 305 | if (!String.IsNullOrWhiteSpace(navPoint.Content?.Source)) 306 | { 307 | var hrefSplit = navPoint.Content.Source.Split(new char[] { HREF_SEPARATOR }); 308 | result.ContentFilePath = PathHelper.Combine(opfDirectory, hrefSplit[0]); 309 | if (hrefSplit.Length > 1) 310 | { 311 | result.SecondPath = hrefSplit[1]; 312 | } 313 | } 314 | if (navPoint.Children != null) 315 | { 316 | foreach (var chid in navPoint.Children) 317 | { 318 | result.Chapters.Add(GetChapterFromNavPoint(chid, opfDirectory)); 319 | } 320 | } 321 | } 322 | return result; 323 | } 324 | 325 | #endregion 326 | 327 | #region ReadingFiles 328 | 329 | private static void FillReadingFiles(IList readingFiles, Package package, string opfDirectory) 330 | { 331 | if (readingFiles != null && package != null && package.Spine != null && package.Manifest != null) 332 | { 333 | foreach (var spineItem in package.Spine.Items) 334 | { 335 | var manifestItem = package.Manifest.Items.FirstOrDefault(p => p.Id == spineItem.IdRef); 336 | if (manifestItem != null) 337 | { 338 | readingFiles.Add(new ContentFile(manifestItem.MediaType, PathHelper.Combine(opfDirectory, manifestItem.Href))); 339 | } 340 | } 341 | } 342 | } 343 | 344 | #endregion 345 | 346 | #region AllFiles 347 | 348 | private static void FillAllFiles(IList allFiles, Package package, string opfDirectory) 349 | { 350 | if (allFiles != null && package != null && package.Manifest != null) 351 | { 352 | foreach (var manifestItem in package.Manifest.Items) 353 | { 354 | allFiles.Add(new ContentFile(manifestItem.MediaType, PathHelper.Combine(opfDirectory, manifestItem.Href))); 355 | } 356 | } 357 | } 358 | 359 | #endregion 360 | 361 | #region Disposable 362 | 363 | protected virtual void Dispose(bool disposing) 364 | { 365 | if (_disposed) 366 | { 367 | return; 368 | } 369 | 370 | if (disposing) 371 | { 372 | this._archive.Dispose(); 373 | } 374 | 375 | _disposed = true; 376 | } 377 | 378 | public void Dispose() 379 | { 380 | this.Dispose(true); 381 | GC.SuppressFinalize(this); 382 | } 383 | 384 | #endregion 385 | 386 | #endregion 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/ExtendMethod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Roler.Toolkit.File.Epub.Entity; 4 | 5 | namespace Roler.Toolkit.File.Epub 6 | { 7 | internal static class ExtendMethod 8 | { 9 | private const string XML_1_0_START = " 2 | 3 | 4 | netstandard2.0 5 | RolerZhang 6 | Roler.Toolkit.File.Epub 7 | epub 8 | .NET Standard Class Library for Epub File. support EPUB 3.0 9 | Apache-2.0 10 | https://github.com/rolerzhang/RolerFileToolkit 11 | 12 | 1.0.1 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Epub/Structure.cs: -------------------------------------------------------------------------------- 1 | using Roler.Toolkit.File.Epub.Entity; 2 | 3 | namespace Roler.Toolkit.File.Epub 4 | { 5 | public class Structure 6 | { 7 | public Container Container { get; set; } 8 | public Package Package { get; set; } 9 | 10 | /// 11 | /// 获取或设置ncx相关信息,仅在Epub3.0之前有效,3.0以后请使用Nav属性。 12 | /// 13 | public Ncx Ncx { get; set; } 14 | 15 | /// 16 | /// 获取或设置导航文件信息,仅在Epub3.0之后有效,3.0之前请使用Ncx属性。 17 | /// 18 | public Nav Nav { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Compression/BitReader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Roler.Toolkit.File.Mobi 4 | { 5 | internal class BitReader 6 | { 7 | private readonly IList _data; 8 | private readonly int _nbits; 9 | 10 | private uint _pos = 0; 11 | 12 | public bool IsEnd => this._nbits > this._pos; 13 | 14 | public BitReader(byte[] bytes) 15 | { 16 | this._data = new List(bytes) 17 | { 18 | 0, 19 | 0, 20 | 0, 21 | 0 22 | }; 23 | this._nbits = (this._data.Count - 4) * 8; 24 | } 25 | 26 | public ulong Peek(ulong n) 27 | { 28 | ulong r = 0; 29 | ulong g = 0; 30 | while (g < n) 31 | { 32 | r = (r << 8) | (char)(_data[(int)((long)(_pos + g >> 3))]); 33 | g = g + 8 - ((_pos + g) & 7); 34 | } 35 | return r >> (int)((long)(g - n)) & ((ulong)(1) << (int)n) - 1; 36 | } 37 | 38 | public bool Eat(uint n) 39 | { 40 | _pos += n; 41 | return _pos <= _nbits; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Compression/HuffCdicCompression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | 6 | namespace Roler.Toolkit.File.Mobi.Compression 7 | { 8 | /// 9 | /// HUFF/CDIC compression 10 | /// 11 | internal class HuffCdicCompression : ICompression 12 | { 13 | public readonly static byte[] HuffBytesStart = { 0x48, 0x55, 0x46, 0x46, 0x00, 0x00, 0x00, 0x18 }; 14 | public readonly static byte[] CdicBytesStart = { 0x43, 0x44, 0x49, 0x43, 0x00, 0x00, 0x00, 0x10 }; 15 | 16 | private readonly List _huff; 17 | private readonly List _cidcList; 18 | private readonly IList> _huffDict1 = new List>(); 19 | private readonly IList _minCodeList = new List(); 20 | private readonly IList _maxCodeList = new List(); 21 | private readonly IList, int>> _dictionary = new List, int>>(); 22 | 23 | public IReadOnlyList Huff => this._huff; 24 | public IReadOnlyList CdicList => this._cidcList; 25 | public uint ExtraFlags { get; set; } 26 | 27 | public HuffCdicCompression() 28 | { 29 | } 30 | 31 | public HuffCdicCompression(byte[] huff, IList cdicList) 32 | { 33 | if (huff is null) 34 | { 35 | throw new ArgumentNullException(nameof(huff)); 36 | } 37 | 38 | if (cdicList is null) 39 | { 40 | throw new ArgumentNullException(nameof(cdicList)); 41 | } 42 | 43 | this._huff = new List(huff); 44 | this._cidcList = new List(cdicList); 45 | 46 | this.InitMember(); 47 | } 48 | 49 | public byte[] Compress(byte[] bytes) 50 | { 51 | if (bytes is null) 52 | { 53 | throw new ArgumentNullException(nameof(bytes)); 54 | } 55 | 56 | throw new NotImplementedException(); 57 | } 58 | 59 | public byte[] Decompress(byte[] bytes) 60 | { 61 | if (bytes is null) 62 | { 63 | throw new ArgumentNullException(nameof(bytes)); 64 | } 65 | return this.Unpack(bytes); 66 | } 67 | 68 | private void InitMember() 69 | { 70 | this.LoadHuff(); 71 | 72 | this._dictionary.Clear(); 73 | foreach (var cdicBytes in this._cidcList) 74 | { 75 | this.LoadOneCdic(cdicBytes); 76 | } 77 | } 78 | 79 | private void LoadHuff() 80 | { 81 | if (this._huff.GetRange(0, 8).SequenceEqual(HuffBytesStart)) 82 | { 83 | uint off1 = this._huff.GetRange(8, 4).ToArray().ToUInt32(); 84 | uint off2 = this._huff.GetRange(12, 4).ToArray().ToUInt32(); 85 | 86 | this._huffDict1.Clear(); 87 | for (int i = 0; i < 256; i++) 88 | { 89 | uint v = this._huff.GetRange((int)(off1 + (i * 4)), 4).ToArray().ToUInt32(); 90 | var codeLength = (int)(v & 0x1f); 91 | var term = (int)(v & 0x80); 92 | uint maxCode = v >> 8; 93 | 94 | if (codeLength == 0) 95 | { 96 | throw new InvalidDataException("invalid data: Huff"); 97 | } 98 | else 99 | { 100 | maxCode = ((maxCode + 1) << (32 - codeLength)) - 1; 101 | } 102 | this._huffDict1.Add(new Tuple(codeLength, term, maxCode)); 103 | } 104 | 105 | this._minCodeList.Clear(); 106 | this._maxCodeList.Clear(); 107 | 108 | this._minCodeList.Add(uint.MinValue); 109 | this._maxCodeList.Add(uint.MaxValue); 110 | for (int i = 0; i < 64; i++) 111 | { 112 | var v = this._huff.GetRange((int)(off2 + (i * 4)), 4).ToArray().ToUInt32(); 113 | var step = 32 - i / 2 - 1; 114 | if (i % 2 == 0) 115 | { 116 | this._minCodeList.Add(v << step); 117 | } 118 | else 119 | { 120 | this._maxCodeList.Add(((v + 1) << step) - 1); 121 | } 122 | } 123 | } 124 | } 125 | 126 | private void LoadOneCdic(byte[] cdicBytes) 127 | { 128 | if (cdicBytes is null) 129 | { 130 | throw new ArgumentNullException(nameof(cdicBytes)); 131 | } 132 | if (cdicBytes.RangeEqual(0, 8, CdicBytesStart)) 133 | { 134 | var phrases = cdicBytes.Skip(8).Take(4).ToArray().ToUInt32(); 135 | var bits = cdicBytes.Skip(12).Take(4).ToArray().ToUInt32(); 136 | 137 | var n = Math.Min(1 << (int)bits, phrases - this._dictionary.Count); 138 | for (int i = 0; i < n; i++) 139 | { 140 | var off = cdicBytes.Skip(16 + (i * 2)).Take(2).ToArray().ToUInt16(); 141 | var blen = cdicBytes.Skip(16 + off).Take(2).ToArray().ToUInt16(); 142 | var length = 18 + off + (blen & 0x7fff); 143 | var sliceByteList = new List(); 144 | for (int j = 18 + off; j < length; j++) 145 | { 146 | sliceByteList.Add(cdicBytes[j]); 147 | } 148 | this._dictionary.Add(new Tuple, int>(sliceByteList, blen & 0x8000)); 149 | } 150 | } 151 | } 152 | 153 | private byte[] Unpack(byte[] bytes) 154 | { 155 | var bitsLeft = bytes.Length * 8; 156 | var data = new List(bytes); 157 | data.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); 158 | var pos = 0; 159 | var x = data.GetRange(pos, 8).ToArray().ToUInt64(); 160 | var n = 32; 161 | var result = new List(); 162 | 163 | while (true) 164 | { 165 | if (n <= 0) 166 | { 167 | pos += 4; 168 | x = data.GetRange(pos, 8).ToArray().ToUInt64(); 169 | n += 32; 170 | } 171 | 172 | var code = (uint)((x >> n) & 0xFFFFFFFF); 173 | var tuple = this._huffDict1[(int)(code >> 24)]; 174 | var codeLength = tuple.Item1; 175 | var maxCode = tuple.Item3; 176 | if (tuple.Item2 <= 0) 177 | { 178 | while (code < this._minCodeList[codeLength]) 179 | { 180 | codeLength++; 181 | } 182 | 183 | maxCode = this._maxCodeList[codeLength]; 184 | } 185 | 186 | n -= codeLength; 187 | bitsLeft -= codeLength; 188 | if (bitsLeft < 0) 189 | break; 190 | 191 | var r = (int)((maxCode - code) >> (32 - codeLength)); 192 | var tuple2 = this._dictionary[r]; 193 | var sliceList = tuple2.Item1; 194 | if (tuple2.Item2 <= 0) 195 | { 196 | this._dictionary.RemoveAt(r); 197 | this._dictionary.Insert(r, null); 198 | sliceList = this.Unpack(sliceList.ToArray()); 199 | this._dictionary.RemoveAt(r); 200 | this._dictionary.Insert(r, new Tuple, int>(sliceList, 1)); 201 | } 202 | 203 | result.AddRange(sliceList); 204 | } 205 | 206 | return result.ToArray(); 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Compression/ICompression.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Mobi.Compression 2 | { 3 | internal interface ICompression 4 | { 5 | byte[] Compress(byte[] bytes); 6 | byte[] Decompress(byte[] bytes); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Compression/NoneCompression.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Mobi.Compression 2 | { 3 | internal class NoneCompression : ICompression 4 | { 5 | public byte[] Compress(byte[] bytes) 6 | { 7 | return bytes; 8 | } 9 | 10 | public byte[] Decompress(byte[] bytes) 11 | { 12 | return bytes; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Compression/PalmDocCompression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Roler.Toolkit.File.Mobi.Compression 6 | { 7 | /// 8 | /// PalmDoc byte pair compression (LZ77). 9 | /// 10 | internal class PalmDocCompression : ICompression 11 | { 12 | #region Const 13 | 14 | private const int BlockSize = 4096; 15 | 16 | #endregion 17 | 18 | public byte[] Compress(byte[] bytes) 19 | { 20 | if (bytes is null) 21 | { 22 | throw new ArgumentNullException(nameof(bytes)); 23 | } 24 | throw new NotImplementedException(); 25 | } 26 | 27 | 28 | /// 29 | /// Decompress: 30 | /// 0x00: "1 literal" copy that byte unmodified to the decompressed stream. 31 | /// 0x09 to 0x7f: "1 literal" copy that byte unmodified to the decompressed stream. 32 | /// 0x01 to 0x08: "literals": the byte is interpreted as a count from 1 to 8, and that many literals are copied unmodified from the compressed stream to the decompressed stream. 33 | /// 0x80 to 0xbf: "length, distance" pair: the 2 leftmost bits of this byte ('10') are discarded, and the following 6 bits are combined with the 8 bits of the next byte to make a 14 bit "distance, length" item.Those 14 bits are broken into 11 bits of distance backwards from the current location in the uncompressed text, and 3 bits of length to copy from that point(copying n+3 bytes, 3 to 10 bytes). 34 | /// 0xc0 to 0xff: "byte pair": this byte is decoded into 2 characters: a space character, and a letter formed from this byte XORed with 0x80. 35 | /// 36 | public byte[] Decompress(byte[] bytes) 37 | { 38 | if (bytes is null) 39 | { 40 | throw new ArgumentNullException(nameof(bytes)); 41 | } 42 | 43 | IList blockBuilder = new List(); 44 | IList dataTemp = new List(bytes) { 0 }; 45 | int pos = 0; 46 | IList temps = new List(); 47 | 48 | while (pos < dataTemp.Count && blockBuilder.Count < BlockSize) 49 | { 50 | byte ab = dataTemp[pos++]; 51 | if (ab == 0x00 || (ab >= 0x09 && ab <= 0x7f)) 52 | { 53 | blockBuilder.Add(ab); 54 | } 55 | else if (ab >= 0x01 && ab <= 0x08) 56 | { 57 | if (pos + ab > dataTemp.Count) 58 | { 59 | //invaild data, not enough to copy. 60 | blockBuilder.Clear(); 61 | break; 62 | } 63 | for (byte i = 0; i < ab; i++) 64 | { 65 | blockBuilder.Add(dataTemp[pos++]); 66 | } 67 | } 68 | else if (ab >= 0x80 && ab <= 0xbf) 69 | { 70 | temps.Clear(); 71 | temps.Add(0); 72 | temps.Add(0); 73 | temps.Add((byte)(ab & 0x3f)); 74 | if (pos < dataTemp.Count) 75 | { 76 | temps.Add(dataTemp[pos++]); 77 | } 78 | else 79 | { 80 | temps.Add(0); 81 | } 82 | 83 | uint b = BytesToUint(temps.ToArray()); 84 | uint dist = b >> 3; 85 | int uncompressedPos = blockBuilder.Count - ((int)dist); 86 | if (uncompressedPos >= 0 && dist != 0) 87 | { 88 | uint len = (b & 0x07) + 3; 89 | for (int i = 0; i < len; i++) 90 | { 91 | blockBuilder.Add(blockBuilder[uncompressedPos + i]); 92 | } 93 | } 94 | else 95 | { 96 | //invaild data, distance larger then uncommpressed stream length or equal to 0. 97 | blockBuilder.Clear(); 98 | break; 99 | } 100 | } 101 | else if (ab >= 0xc0 && ab <= 0xff) 102 | { 103 | blockBuilder.Add(0x20); 104 | blockBuilder.Add((byte)(ab ^ 0x80)); 105 | } 106 | } 107 | return blockBuilder.ToArray(); 108 | } 109 | 110 | private static uint BytesToUint(byte[] bytes) 111 | { 112 | return (uint)((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/ContentFile.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Roler.Toolkit.File.Mobi 3 | { 4 | public class ContentFile 5 | { 6 | public string MediaType { get; set; } 7 | public string Source { get; private set; } 8 | 9 | public ContentFile(string mediaType, string source) 10 | { 11 | MediaType = mediaType; 12 | Source = source; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Define/Const.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Mobi.Define 2 | { 3 | internal class Const 4 | { 5 | public const int ByteBitLength = 8; 6 | public const int ShortBitLength = 2 * 8; 7 | public const int IntBitLength = 4 * 8; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Engine/ExthHeaderEngine.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Roler.Toolkit.File.Mobi.Entity; 3 | 4 | namespace Roler.Toolkit.File.Mobi.Engine 5 | { 6 | internal static class ExthHeaderEngine 7 | { 8 | #region Const String 9 | 10 | public const string Identifier = "EXTH"; 11 | public const uint UnavailableIndex = 0xFFFFFFFF; 12 | 13 | #endregion 14 | 15 | public static bool TryRead(Stream stream, long offset, out ExthHeader exthHeader) 16 | { 17 | bool result = false; 18 | exthHeader = null; 19 | stream.Seek(offset, SeekOrigin.Begin); 20 | if (stream.CheckStart(4, Identifier)) 21 | { 22 | exthHeader = Read(stream, offset); 23 | result = true; 24 | } 25 | 26 | if (!result) 27 | { 28 | stream.Seek(offset, SeekOrigin.Begin); 29 | } 30 | 31 | return result; 32 | } 33 | 34 | public static ExthHeader Read(Stream stream, long offset) 35 | { 36 | ExthHeader result = new ExthHeader(); 37 | stream.Seek(offset, SeekOrigin.Begin); 38 | if (stream.TryReadString(4, out string identifier)) 39 | { 40 | result.Identifier = identifier; 41 | } 42 | if (stream.TryReadUint(out uint length)) 43 | { 44 | result.Length = length; 45 | } 46 | if (stream.TryReadUint(out uint count)) 47 | { 48 | result.RecordCount = count; 49 | } 50 | 51 | for (int i = 0; i < count; i++) 52 | { 53 | var exthRecord = ReadRecord(stream, stream.Position); 54 | if (exthRecord != null) 55 | { 56 | result.RecordList.Add(exthRecord); 57 | } 58 | } 59 | 60 | uint padding = (4 - length % 4) % 4; 61 | stream.Seek(offset + length + padding, SeekOrigin.Begin); //skip to end, included padding 62 | 63 | return result; 64 | } 65 | 66 | public static void Write(ExthHeader file, Stream stream) 67 | { 68 | } 69 | 70 | private static ExthRecord ReadRecord(Stream stream, long offset) 71 | { 72 | ExthRecord result = new ExthRecord(); 73 | stream.Seek(offset, SeekOrigin.Begin); 74 | 75 | if (stream.TryReadUint(out uint recordType)) 76 | { 77 | result.Type = (ExthRecordType)recordType; 78 | } 79 | if (stream.TryReadUint(out uint length)) 80 | { 81 | result.Length = length; 82 | } 83 | 84 | var dataBytesLength = (int)length - 8; 85 | if (dataBytesLength > 0) 86 | { 87 | if (stream.TryReadBytes(dataBytesLength, out byte[] data)) 88 | { 89 | result.Data = data; 90 | } 91 | } 92 | 93 | stream.Seek(offset + length, SeekOrigin.Begin); //skip to end. 94 | return result; 95 | } 96 | 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Engine/IndxHeaderEngine.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Roler.Toolkit.File.Mobi.Entity; 3 | 4 | namespace Roler.Toolkit.File.Mobi.Engine 5 | { 6 | internal static class IndxHeaderEngine 7 | { 8 | #region Const String 9 | 10 | public const string Identifier = "INDX"; 11 | 12 | #endregion 13 | 14 | public static bool TryRead(Stream stream, long offset, out IndxHeader indxHeader) 15 | { 16 | bool result = false; 17 | indxHeader = null; 18 | stream.Seek(offset, SeekOrigin.Begin); 19 | if (stream.CheckStart(4, Identifier)) 20 | { 21 | indxHeader = Read(stream, offset); 22 | result = true; 23 | } 24 | 25 | if (!result) 26 | { 27 | stream.Seek(offset, SeekOrigin.Begin); 28 | } 29 | 30 | return result; 31 | } 32 | 33 | public static IndxHeader Read(Stream stream, long offset) 34 | { 35 | IndxHeader result = new IndxHeader(); 36 | stream.Seek(offset, SeekOrigin.Begin); 37 | if (stream.TryReadString(4, out string identifier)) 38 | { 39 | result.Identifier = identifier; 40 | } 41 | if (stream.TryReadUint(out uint length)) 42 | { 43 | result.Length = length; 44 | } 45 | if (stream.TryReadUint(out uint indexType)) 46 | { 47 | result.IndexType = (IndexType)indexType; 48 | } 49 | 50 | stream.Seek(8, SeekOrigin.Current); //skip 8 unknown bytes. 51 | 52 | if (stream.TryReadUint(out uint idxtStart)) 53 | { 54 | result.IdxtStart = idxtStart; 55 | } 56 | if (stream.TryReadUint(out uint indexCount)) 57 | { 58 | result.IndexCount = indexCount; 59 | } 60 | if (stream.TryReadUint(out uint indexEncoding)) 61 | { 62 | result.IndexEncoding = (TextEncoding)indexEncoding; 63 | } 64 | if (stream.TryReadString(4, out string indexLanguage)) 65 | { 66 | result.IndexLanguage = indexLanguage; 67 | } 68 | if (stream.TryReadUint(out uint totalIndexCount)) 69 | { 70 | result.TotalIndexCount = totalIndexCount; 71 | } 72 | if (stream.TryReadUint(out uint ordtStart)) 73 | { 74 | result.OrdtStart = ordtStart; 75 | } 76 | if (stream.TryReadUint(out uint ligtStart)) 77 | { 78 | result.LigtStart = ligtStart; 79 | } 80 | 81 | stream.Seek(8, SeekOrigin.Current); //skip 8 unknown bytes. 82 | stream.Seek(offset + length, SeekOrigin.Begin); //skip to end. 83 | 84 | return result; 85 | } 86 | 87 | public static void Write(IndxHeader file, Stream stream) 88 | { 89 | } 90 | 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Engine/MobiHeaderEngine.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Roler.Toolkit.File.Mobi.Entity; 3 | 4 | namespace Roler.Toolkit.File.Mobi.Engine 5 | { 6 | internal static class MobiHeaderEngine 7 | { 8 | #region Const String 9 | 10 | public const string Identifier = "MOBI"; 11 | public const uint UnavailableIndex = 0xFFFFFFFF; 12 | 13 | #endregion 14 | 15 | public static bool TryRead(Stream stream, long offset, out MobiHeader mobiHeader) 16 | { 17 | bool result = false; 18 | mobiHeader = null; 19 | stream.Seek(offset, SeekOrigin.Begin); 20 | if (stream.CheckStart(4, Identifier)) 21 | { 22 | mobiHeader = Read(stream, offset); 23 | result = true; 24 | } 25 | 26 | if (!result) 27 | { 28 | stream.Seek(offset, SeekOrigin.Begin); 29 | } 30 | 31 | return result; 32 | } 33 | 34 | public static MobiHeader Read(Stream stream, long offset) 35 | { 36 | MobiHeader result = new MobiHeader(); 37 | stream.Seek(offset, SeekOrigin.Begin); 38 | if (stream.TryReadString(4, out string identifier)) 39 | { 40 | result.Identifier = identifier; 41 | } 42 | if (stream.TryReadUint(out uint length)) 43 | { 44 | result.Length = length; 45 | } 46 | if (stream.TryReadUint(out uint mobiType)) 47 | { 48 | result.MobiType = (MobiType)mobiType; 49 | } 50 | if (stream.TryReadUint(out uint textEncoding)) 51 | { 52 | result.TextEncoding = (TextEncoding)textEncoding; 53 | } 54 | if (stream.TryReadUint(out uint uniqueId)) 55 | { 56 | result.UniqueId = uniqueId; 57 | } 58 | if (stream.TryReadUint(out uint fileVersion)) 59 | { 60 | result.FileVersion = fileVersion; 61 | } 62 | if (stream.TryReadUint(out uint ortographicIndex)) 63 | { 64 | result.OrtographicIndex = ortographicIndex; 65 | } 66 | if (stream.TryReadUint(out uint inflectionIndex)) 67 | { 68 | result.InflectionIndex = inflectionIndex; 69 | } 70 | if (stream.TryReadUint(out uint indexNames)) 71 | { 72 | result.IndexNames = indexNames; 73 | } 74 | if (stream.TryReadUint(out uint indexKeys)) 75 | { 76 | result.IndexKeys = indexKeys; 77 | } 78 | if (stream.TryReadUint(out uint extraIndex0)) 79 | { 80 | result.ExtraIndex0 = extraIndex0; 81 | } 82 | if (stream.TryReadUint(out uint extraIndex1)) 83 | { 84 | result.ExtraIndex1 = extraIndex1; 85 | } 86 | if (stream.TryReadUint(out uint extraIndex2)) 87 | { 88 | result.ExtraIndex2 = extraIndex2; 89 | } 90 | if (stream.TryReadUint(out uint extraIndex3)) 91 | { 92 | result.ExtraIndex3 = extraIndex3; 93 | } 94 | if (stream.TryReadUint(out uint extraIndex4)) 95 | { 96 | result.ExtraIndex4 = extraIndex4; 97 | } 98 | if (stream.TryReadUint(out uint extraIndex5)) 99 | { 100 | result.ExtraIndex5 = extraIndex5; 101 | } 102 | if (stream.TryReadUint(out uint firstNonBookIndex)) 103 | { 104 | result.FirstNonBookIndex = firstNonBookIndex; 105 | } 106 | if (stream.TryReadUint(out uint fullNameOffset)) 107 | { 108 | result.FullNameOffset = fullNameOffset; 109 | } 110 | if (stream.TryReadUint(out uint fullNameLength)) 111 | { 112 | result.FullNameLength = fullNameLength; 113 | } 114 | if (stream.TryReadUint(out uint locale)) 115 | { 116 | result.Locale = locale; 117 | } 118 | if (stream.TryReadString(4, out string inputLanguage)) 119 | { 120 | result.InputLanguage = inputLanguage; 121 | } 122 | if (stream.TryReadString(4, out string outputLanguage)) 123 | { 124 | result.OutputLanguage = outputLanguage; 125 | } 126 | if (stream.TryReadUint(out uint minVersion)) 127 | { 128 | result.MinVersion = minVersion; 129 | } 130 | if (stream.TryReadUint(out uint firstImageIndex)) 131 | { 132 | result.FirstImageIndex = firstImageIndex; 133 | } 134 | if (stream.TryReadUint(out uint huffmanRecordOffset)) 135 | { 136 | result.HuffmanRecordOffset = huffmanRecordOffset; 137 | } 138 | if (stream.TryReadUint(out uint huffmanRecordCount)) 139 | { 140 | result.HuffmanRecordCount = huffmanRecordCount; 141 | } 142 | if (stream.TryReadUint(out uint huffmanTableOffset)) 143 | { 144 | result.HuffmanTableOffset = huffmanTableOffset; 145 | } 146 | if (stream.TryReadUint(out uint huffmanTableLength)) 147 | { 148 | result.HuffmanTableLength = huffmanTableLength; 149 | } 150 | if (stream.TryReadUint(out uint exthFlags)) 151 | { 152 | result.EXTHFlags = exthFlags; 153 | } 154 | 155 | stream.Seek(32 + 4, SeekOrigin.Current); //skip 32 unknown bytes and 0xFFFFFFFF. 156 | 157 | if (stream.TryReadUint(out uint drmOffset)) 158 | { 159 | result.DRMOffset = drmOffset; 160 | } 161 | if (stream.TryReadUint(out uint drmCount)) 162 | { 163 | result.DRMCount = drmCount; 164 | } 165 | if (stream.TryReadUint(out uint drmSize)) 166 | { 167 | result.DRMSize = drmSize; 168 | } 169 | if (stream.TryReadUint(out uint drmFlags)) 170 | { 171 | result.DRMFlags = drmFlags; 172 | } 173 | 174 | stream.Seek(8, SeekOrigin.Current); //skip 8 bytes, Bytes to the end of the MOBI header, including the following if the header length >= 228 (244 from start of record). Use 0x0000000000000000. 175 | 176 | if (stream.TryReadUshort(out ushort firstContentRecordOffset)) 177 | { 178 | result.FirstContentRecordOffset = firstContentRecordOffset; 179 | } 180 | if (stream.TryReadUshort(out ushort lastContentRecordOffset)) 181 | { 182 | result.LastContentRecordOffset = lastContentRecordOffset; 183 | } 184 | 185 | stream.Seek(4, SeekOrigin.Current); //skip 4 bytes, 0x00000001. 186 | 187 | if (stream.TryReadUint(out uint fcisRecordOffset)) 188 | { 189 | result.FCISRecordOffset = fcisRecordOffset; 190 | } 191 | 192 | stream.Seek(4, SeekOrigin.Current); //skip 4 bytes, 0x00000001. 193 | 194 | if (stream.TryReadUint(out uint flisRecordOffset)) 195 | { 196 | result.FLISRecordOffset = flisRecordOffset; 197 | } 198 | 199 | stream.Seek(28, SeekOrigin.Current); //skip 0x00000001, 0x0000000000000000, 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 200 | 201 | if (stream.TryReadUint(out uint extraRecordDataFlags)) 202 | { 203 | result.ExtraRecordDataFlags = extraRecordDataFlags; 204 | } 205 | if (stream.TryReadUint(out uint iNDXRecordOffset)) 206 | { 207 | result.INDXRecordOffset = iNDXRecordOffset; 208 | } 209 | 210 | stream.Seek(offset + length, SeekOrigin.Begin); //skip to end. 211 | 212 | return result; 213 | } 214 | 215 | public static void Write(MobiHeader file, Stream stream) 216 | { 217 | } 218 | 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Engine/PalmDBEngine.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using Roler.Toolkit.File.Mobi.Entity; 4 | 5 | namespace Roler.Toolkit.File.Mobi.Engine 6 | { 7 | internal static class PalmDBEngine 8 | { 9 | #region Const 10 | 11 | private const int HeaderByteLength = 72; 12 | private const int NameByteLength = 32; 13 | 14 | #endregion 15 | 16 | public static PalmDB Read(Stream stream) 17 | { 18 | PalmDB result = null; 19 | stream.Seek(0, SeekOrigin.Begin); 20 | byte[] buffer = new byte[NameByteLength]; 21 | if (stream.Read(buffer, 0, NameByteLength) == NameByteLength) 22 | { 23 | result = new PalmDB { Name = Encoding.UTF8.GetString(buffer) }; 24 | if (stream.TryReadUshort(out ushort attribute)) 25 | { 26 | result.Attribute = (PalmDBAttribute)attribute; 27 | } 28 | if (stream.TryReadUshort(out ushort version)) 29 | { 30 | result.Version = version; 31 | } 32 | stream.Seek(4 * 6, SeekOrigin.Current); 33 | 34 | if (stream.TryReadUint(out uint type)) 35 | { 36 | result.Type = type; 37 | } 38 | if (stream.TryReadUint(out uint creator)) 39 | { 40 | result.Creator = creator; 41 | } 42 | if (stream.TryReadUint(out uint uniqueIDseed)) 43 | { 44 | result.UniqueIDseed = uniqueIDseed; 45 | } 46 | if (stream.TryReadUint(out uint nextRecordListID)) 47 | { 48 | result.NextRecordListID = nextRecordListID; 49 | } 50 | if (stream.TryReadUshort(out ushort recordCount)) 51 | { 52 | result.RecordCount = recordCount; 53 | } 54 | 55 | for (ushort i = 0; i < recordCount; i++) 56 | { 57 | var recordInfo = new PalmDBRecordInfo(); 58 | if (stream.TryReadUint(out uint recordInfoOffset)) 59 | { 60 | recordInfo.Offset = recordInfoOffset; 61 | } 62 | if (stream.TryReadByte(out byte recordInfoAttribute)) 63 | { 64 | recordInfo.Attribute = (PalmDBRecordAttribute)recordInfoAttribute; 65 | } 66 | var recordUniqueIDBuff = new byte[3]; 67 | if (stream.Read(recordUniqueIDBuff, 0, 3) == 3) 68 | { 69 | recordInfo.UniqueID = recordUniqueIDBuff.ToUInt32(); 70 | } 71 | result.RecordInfoList.Add(recordInfo); 72 | } 73 | } 74 | 75 | return result; 76 | } 77 | 78 | public static void Write(PalmDB file, Stream stream) 79 | { 80 | } 81 | 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Engine/PalmDOCHeaderEngine.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Roler.Toolkit.File.Mobi.Entity; 3 | 4 | namespace Roler.Toolkit.File.Mobi.Engine 5 | { 6 | internal static class PalmDOCHeaderEngine 7 | { 8 | #region Const String 9 | 10 | #endregion 11 | 12 | public static PalmDOCHeader Read(Stream stream, long offset) 13 | { 14 | PalmDOCHeader result = new PalmDOCHeader(); 15 | stream.Seek(offset, SeekOrigin.Begin); 16 | if (stream.TryReadUshort(out ushort compression)) 17 | { 18 | result.Compression = (CompressionType)compression; 19 | } 20 | stream.Seek(2, SeekOrigin.Current); 21 | if (stream.TryReadUint(out uint textLength)) 22 | { 23 | result.TextLength = textLength; 24 | } 25 | if (stream.TryReadUshort(out ushort recordCount)) 26 | { 27 | result.RecordCount = recordCount; 28 | } 29 | if (stream.TryReadUshort(out ushort recordSize)) 30 | { 31 | result.RecordSize = recordSize; 32 | } 33 | 34 | if (result.Compression == CompressionType.HUFF_CDIC) 35 | { 36 | if (stream.TryReadUshort(out ushort encryption)) 37 | { 38 | result.Encryption = (EncryptionType)encryption; 39 | } 40 | stream.Seek(2, SeekOrigin.Current); 41 | } 42 | else 43 | { 44 | if (stream.TryReadUint(out uint currentPosition)) 45 | { 46 | result.CurrentPosition = currentPosition; 47 | } 48 | } 49 | 50 | 51 | return result; 52 | } 53 | 54 | public static void Write(PalmDOCHeader file, Stream stream) 55 | { 56 | } 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Engine/RecordEngine.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Roler.Toolkit.File.Mobi.Entity; 3 | 4 | namespace Roler.Toolkit.File.Mobi.Engine 5 | { 6 | internal static class RecordEngine 7 | { 8 | #region Const String 9 | 10 | public const string Identifier_Flis = "FLIS"; 11 | public const string Identifier_Fcis = "FCIS"; 12 | 13 | #endregion 14 | 15 | public static bool TryReadFlisRecord(Stream stream, long offset, out FlisRecord flisRecord) 16 | { 17 | bool result = false; 18 | flisRecord = null; 19 | stream.Seek(offset, SeekOrigin.Begin); 20 | if (stream.CheckStart(4, Identifier_Flis)) 21 | { 22 | stream.Seek(offset, SeekOrigin.Begin); 23 | flisRecord = new FlisRecord(); 24 | if (stream.TryReadString(4, out string identifier)) 25 | { 26 | flisRecord.Identifier = identifier; 27 | } 28 | if (stream.TryReadUint(out uint value0)) 29 | { 30 | flisRecord.Value0 = value0; 31 | } 32 | if (stream.TryReadUshort(out ushort value1)) 33 | { 34 | flisRecord.Value1 = value1; 35 | } 36 | if (stream.TryReadUshort(out ushort value2)) 37 | { 38 | flisRecord.Value2 = value2; 39 | } 40 | if (stream.TryReadUint(out uint value3)) 41 | { 42 | flisRecord.Value3 = value3; 43 | } 44 | if (stream.TryReadUint(out uint value4)) 45 | { 46 | flisRecord.Value4 = value4; 47 | } 48 | if (stream.TryReadUshort(out ushort value5)) 49 | { 50 | flisRecord.Value5 = value5; 51 | } 52 | if (stream.TryReadUshort(out ushort value6)) 53 | { 54 | flisRecord.Value6 = value6; 55 | } 56 | if (stream.TryReadUint(out uint value7)) 57 | { 58 | flisRecord.Value7 = value7; 59 | } 60 | if (stream.TryReadUint(out uint value8)) 61 | { 62 | flisRecord.Value8 = value8; 63 | } 64 | if (stream.TryReadUint(out uint value9)) 65 | { 66 | flisRecord.Value9 = value9; 67 | } 68 | result = true; 69 | } 70 | 71 | if (!result) 72 | { 73 | stream.Seek(offset, SeekOrigin.Begin); 74 | } 75 | 76 | return result; 77 | } 78 | 79 | public static bool TryReadFcisRecord(Stream stream, long offset, out FcisRecord fcisRecord) 80 | { 81 | bool result = false; 82 | fcisRecord = null; 83 | stream.Seek(offset, SeekOrigin.Begin); 84 | if (stream.CheckStart(4, Identifier_Fcis)) 85 | { 86 | stream.Seek(offset, SeekOrigin.Begin); 87 | fcisRecord = new FcisRecord(); 88 | if (stream.TryReadString(4, out string identifier)) 89 | { 90 | fcisRecord.Identifier = identifier; 91 | } 92 | if (stream.TryReadUint(out uint value0)) 93 | { 94 | fcisRecord.Value0 = value0; 95 | } 96 | if (stream.TryReadUint(out uint value1)) 97 | { 98 | fcisRecord.Value1 = value1; 99 | } 100 | if (stream.TryReadUint(out uint value2)) 101 | { 102 | fcisRecord.Value2 = value2; 103 | } 104 | if (stream.TryReadUint(out uint value3)) 105 | { 106 | fcisRecord.Value3 = value3; 107 | } 108 | if (stream.TryReadUint(out uint value4)) 109 | { 110 | fcisRecord.Value4 = value4; 111 | } 112 | if (stream.TryReadUint(out uint value5)) 113 | { 114 | fcisRecord.Value5 = value5; 115 | } 116 | if (stream.TryReadUint(out uint value6)) 117 | { 118 | fcisRecord.Value6 = value6; 119 | } 120 | if (stream.TryReadUint(out uint value7)) 121 | { 122 | fcisRecord.Value7 = value7; 123 | } 124 | if (stream.TryReadUshort(out ushort value8)) 125 | { 126 | fcisRecord.Value8 = value8; 127 | } 128 | if (stream.TryReadUshort(out ushort value9)) 129 | { 130 | fcisRecord.Value9 = value9; 131 | } 132 | if (stream.TryReadUint(out uint value10)) 133 | { 134 | fcisRecord.Value10 = value10; 135 | } 136 | result = true; 137 | } 138 | 139 | if (!result) 140 | { 141 | stream.Seek(offset, SeekOrigin.Begin); 142 | } 143 | 144 | return result; 145 | } 146 | 147 | public static void Write(IndxHeader file, Stream stream) 148 | { 149 | } 150 | 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Entity/AudiRecord.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Mobi.Entity 2 | { 3 | public class AudiRecord 4 | { 5 | /// 6 | /// Gets or sets the identifier, always 'AUDI'. 7 | /// 8 | public string Identifier { get; set; } 9 | 10 | /// 11 | /// Gets or sets the media data continues to the end of this record. 12 | /// 13 | public byte[] Media { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Entity/CmetRecord.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Mobi.Entity 2 | { 3 | public class CmetRecord 4 | { 5 | /// 6 | /// Gets or sets the identifier, always 'CMET'. 7 | /// 8 | public string Identifier { get; set; } 9 | 10 | /// 11 | /// Gets or sets the value0, fixed value: 0x0000000C. 12 | /// 13 | public uint Value0 { get; set; } 14 | 15 | /// 16 | /// Gets or sets the length of the text. 17 | /// 18 | public uint TextLength { get; set; } 19 | 20 | /// 21 | /// Gets or sets the compilation output text, line endings are CRLF. 22 | /// 23 | public string Text { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Entity/ExthHeader/ExthHeader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Roler.Toolkit.File.Mobi.Entity 4 | { 5 | public class ExthHeader 6 | { 7 | /// 8 | /// Gets or sets the identifier, always 'EXTH'. 9 | /// 10 | public string Identifier { get; set; } 11 | 12 | /// 13 | /// Gets or sets the length of the EXTH header, including the previous 4 bytes. 14 | /// 15 | public uint Length { get; set; } 16 | 17 | /// 18 | /// Gets or sets the number of records in the EXTH header. the rest of the EXTH header consists of repeated EXTH records to the end of the EXTH length. 19 | /// 20 | public uint RecordCount { get; set; } 21 | 22 | public IList RecordList { get; } = new List(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Entity/ExthHeader/ExthRecord.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Mobi.Entity 2 | { 3 | public class ExthRecord 4 | { 5 | /// 6 | /// Gets or sets the Exth Record type. Just a number identifying what's stored in the record. 7 | /// 8 | public ExthRecordType Type { get; set; } 9 | 10 | /// 11 | /// Gets or sets the length of the EXTH record = L , including the 8 bytes in the type and length fields. 12 | /// 13 | public uint Length { get; set; } 14 | 15 | /// 16 | /// Gets or sets the Data of the EXTH record. 17 | /// 18 | public byte[] Data { get; set; } 19 | 20 | public string DataAsString() 21 | { 22 | if (this.Data != null) 23 | { 24 | return System.Text.Encoding.UTF8.GetString(this.Data); 25 | } 26 | return null; 27 | } 28 | 29 | public override string ToString() 30 | { 31 | return $"{this.Type} : {this.Data}"; 32 | } 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Entity/ExthHeader/ExthRecordType.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Mobi.Entity 2 | { 3 | public enum ExthRecordType : uint 4 | { 5 | Unkown, 6 | DRMServerId = 1, 7 | DRMCommerceId = 2, 8 | DRMEbookbaseBookId = 3, 9 | Author = 100, 10 | Publisher, 11 | Imprint, 12 | Description, 13 | ISBN, 14 | Subject, 15 | PublishingDate, 16 | Review, 17 | Contributor, 18 | Rights, 19 | SubjectCode, 20 | Type, 21 | Source, 22 | ASIN, 23 | VersionNumber, 24 | Sample, 25 | StartReading, 26 | Adult, 27 | RetailPrice, 28 | RetailPriceCurrency, 29 | KF8BoundaryOffset = 121, 30 | ResourceCount = 125, 31 | KF8CoverUri = 129, 32 | Unknown1 = 131, 33 | DictionaryShortName = 200, 34 | CoverOffset, 35 | thumbOffset, 36 | HasFakeCover, 37 | CreatorSoftware, 38 | CreatorMajorVersion, 39 | CreatorMinorVersion, 40 | CreatorBuildVersion, 41 | Watermark, 42 | TamperProofKeys, 43 | FontSignature = 300, 44 | ClippingLimit = 401, 45 | PublisherLimit, 46 | Unknown2, 47 | 48 | /// 49 | /// 1 - Text to Speech disabled; 0 - Text to Speech enabled 50 | /// 51 | TTSFlag, 52 | 53 | Unknown3, 54 | RentBorrowExpirationDate, 55 | Unknown4 = 407, 56 | Unknown5 = 450, 57 | Unknown6, 58 | Unknown7, 59 | Unknown8, 60 | 61 | /// 62 | /// PDOC - Personal Doc; EBOK - ebook; EBSP - ebook sample; 63 | /// 64 | CdeType = 501, 65 | 66 | LastUpdateTime, 67 | UpdatedTitle, 68 | ASIN_Copy, 69 | Language = 524, 70 | WritingMode, 71 | CreatorBuildNumber = 535, 72 | Unknown9, 73 | Unknown10 = 542, 74 | InMemory = 547, 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Entity/FcisRecord.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Mobi.Entity 2 | { 3 | public class FcisRecord 4 | { 5 | /// 6 | /// Gets or sets the identifier, always 'FCIS'. 7 | /// 8 | public string Identifier { get; set; } 9 | 10 | /// 11 | /// Gets or sets the value0, fixed value: 20. 12 | /// 13 | public uint Value0 { get; set; } 14 | 15 | /// 16 | /// Gets or sets the value1, fixed value: 16. 17 | /// 18 | public uint Value1 { get; set; } 19 | 20 | /// 21 | /// Gets or sets the value2, fixed value: 1. 22 | /// 23 | public uint Value2 { get; set; } 24 | 25 | /// 26 | /// Gets or sets the value3, fixed value: 0. 27 | /// 28 | public uint Value3 { get; set; } 29 | 30 | /// 31 | /// Gets or sets the value4, text length (the same value as "text length" in the PalmDoc header). 32 | /// 33 | public uint Value4 { get; set; } 34 | 35 | /// 36 | /// Gets or sets the value5, fixed value: 0. 37 | /// 38 | public uint Value5 { get; set; } 39 | 40 | /// 41 | /// Gets or sets the value6, fixed value: 32. 42 | /// 43 | public uint Value6 { get; set; } 44 | 45 | /// 46 | /// Gets or sets the value7, fixed value: 8. 47 | /// 48 | public uint Value7 { get; set; } 49 | 50 | /// 51 | /// Gets or sets the value8, fixed value: 1. 52 | /// 53 | public ushort Value8 { get; set; } 54 | 55 | /// 56 | /// Gets or sets the value9, fixed value: 1. 57 | /// 58 | public ushort Value9 { get; set; } 59 | 60 | /// 61 | /// Gets or sets the value10, fixed value: 0. 62 | /// 63 | public uint Value10 { get; set; } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Entity/FlisRecord.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Mobi.Entity 2 | { 3 | public class FlisRecord 4 | { 5 | /// 6 | /// Gets or sets the identifier, always 'FLIS'. 7 | /// 8 | public string Identifier { get; set; } 9 | 10 | /// 11 | /// Gets or sets the value0, fixed value: 8. 12 | /// 13 | public uint Value0 { get; set; } 14 | 15 | /// 16 | /// Gets or sets the value1, fixed value: 65. 17 | /// 18 | public ushort Value1 { get; set; } 19 | 20 | /// 21 | /// Gets or sets the value2, fixed value: 0. 22 | /// 23 | public ushort Value2 { get; set; } 24 | 25 | /// 26 | /// Gets or sets the value3, fixed value: 0. 27 | /// 28 | public uint Value3 { get; set; } 29 | 30 | /// 31 | /// Gets or sets the value4, fixed value: -1 (0xFFFFFFFF). 32 | /// 33 | public uint Value4 { get; set; } 34 | 35 | /// 36 | /// Gets or sets the value5, fixed value: 1. 37 | /// 38 | public ushort Value5 { get; set; } 39 | 40 | /// 41 | /// Gets or sets the value6, fixed value: 3. 42 | /// 43 | public ushort Value6 { get; set; } 44 | 45 | /// 46 | /// Gets or sets the value7, fixed value: 3. 47 | /// 48 | public uint Value7 { get; set; } 49 | 50 | /// 51 | /// Gets or sets the value8, fixed value: 1. 52 | /// 53 | public uint Value8 { get; set; } 54 | 55 | /// 56 | /// Gets or sets the value9, fixed value: -1 (0xFFFFFFFF). 57 | /// 58 | public uint Value9 { get; set; } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Entity/IndxHeader/IndexType.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Mobi.Entity 2 | { 3 | public enum IndexType : uint 4 | { 5 | Normal, 6 | 7 | Inflections = 2, 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Entity/IndxHeader/IndxHeader.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Mobi.Entity 2 | { 3 | public class IndxHeader 4 | { 5 | /// 6 | /// Gets or sets the identifier, always 'INDX'. 7 | /// 8 | public string Identifier { get; set; } 9 | 10 | /// 11 | /// Gets or sets the length of the INDX header, including the previous 4 bytes. 12 | /// 13 | public uint Length { get; set; } 14 | 15 | /// 16 | /// Gets or sets the type of the index. 17 | /// 18 | public IndexType IndexType { get; set; } 19 | 20 | /// 21 | /// Gets or sets the offset to the IDXT section. 22 | /// 23 | public uint IdxtStart { get; set; } 24 | 25 | /// 26 | /// Gets or sets the number of index records. 27 | /// 28 | public uint IndexCount { get; set; } 29 | 30 | /// 31 | /// Gets or sets the number of index records. 32 | /// 33 | public TextEncoding IndexEncoding { get; set; } 34 | 35 | /// 36 | /// Gets or sets the language code of the index. 37 | /// 38 | public string IndexLanguage { get; set; } 39 | 40 | /// 41 | /// Gets or sets the number of index entries. 42 | /// 43 | public uint TotalIndexCount { get; set; } 44 | 45 | /// 46 | /// Gets or sets the offset to the ORDT section. 47 | /// 48 | public uint OrdtStart { get; set; } 49 | 50 | /// 51 | /// Gets or sets the offset to the LIGT section. 52 | /// 53 | public uint LigtStart { get; set; } 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Entity/MobiHeader/MobiHeader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Roler.Toolkit.File.Mobi.Entity 4 | { 5 | public class MobiHeader 6 | { 7 | /// 8 | /// Gets or sets the identifier, always 'MOBI'. 9 | /// 10 | public string Identifier { get; set; } 11 | 12 | /// 13 | /// Gets or sets the length of the MOBI header, including the previous 4 bytes. 14 | /// 15 | public uint Length { get; set; } 16 | 17 | public MobiType MobiType { get; set; } 18 | 19 | public TextEncoding TextEncoding { get; set; } 20 | 21 | /// 22 | /// Some kind of unique ID number. 23 | /// 24 | public uint UniqueId { get; set; } 25 | 26 | /// 27 | /// Gets or sets the version of the Mobipocket format used in this file. 28 | /// 29 | public uint FileVersion { get; set; } 30 | 31 | /// 32 | /// Gets or sets the section number of orthographic meta index. 0xFFFFFFFF if index is not available. 33 | /// 34 | public uint OrtographicIndex { get; set; } 35 | 36 | /// 37 | /// Gets or sets the section number of inflection meta index. 0xFFFFFFFF if index is not available. 38 | /// 39 | public uint InflectionIndex { get; set; } 40 | 41 | public uint IndexNames { get; set; } 42 | 43 | public uint IndexKeys { get; set; } 44 | 45 | /// 46 | /// Gets or sets the section number of extra 0 meta index. 0xFFFFFFFF if index is not available. 47 | /// 48 | public uint ExtraIndex0 { get; set; } 49 | 50 | /// 51 | /// Gets or sets the section number of extra 1 meta index. 0xFFFFFFFF if index is not available. 52 | /// 53 | public uint ExtraIndex1 { get; set; } 54 | 55 | /// 56 | /// Gets or sets the section number of extra 2 meta index. 0xFFFFFFFF if index is not available. 57 | /// 58 | public uint ExtraIndex2 { get; set; } 59 | 60 | /// 61 | /// Gets or sets the section number of extra 3 meta index. 0xFFFFFFFF if index is not available. 62 | /// 63 | public uint ExtraIndex3 { get; set; } 64 | 65 | /// 66 | /// Gets or sets the section number of extra 4 meta index. 0xFFFFFFFF if index is not available. 67 | /// 68 | public uint ExtraIndex4 { get; set; } 69 | 70 | /// 71 | /// Gets or sets the section number of extra 5 meta index. 0xFFFFFFFF if index is not available. 72 | /// 73 | public uint ExtraIndex5 { get; set; } 74 | 75 | /// 76 | /// Gets or sets the first record number (starting with 0) that's not the book's text. 77 | /// 78 | public uint FirstNonBookIndex { get; set; } 79 | 80 | /// 81 | /// Gets or sets the offset in record 0 (not from start of file) of the full name of the book. 82 | /// 83 | public uint FullNameOffset { get; set; } 84 | 85 | /// 86 | /// Gets or sets the length in bytes of the full name of the book. 87 | /// 88 | public uint FullNameLength { get; set; } 89 | 90 | /// 91 | /// Gets or sets the book locale code. Low byte is main language 09= English, next byte is dialect, 08 = British, 04 = US. Thus US English is 1033, UK English is 2057. 92 | /// 93 | public uint Locale { get; set; } 94 | 95 | /// 96 | /// Gets or sets the input language for a dictionary. 97 | /// 98 | public string InputLanguage { get; set; } 99 | 100 | /// 101 | /// Gets or sets the output language for a dictionary. 102 | /// 103 | public string OutputLanguage { get; set; } 104 | 105 | /// 106 | /// Gets or sets the minimum mobipocket version support needed to read this file. 107 | /// 108 | public uint MinVersion { get; set; } 109 | 110 | /// 111 | /// Gets or sets the first record number (starting with 0) that contains an image. Image records should be sequential. 112 | /// 113 | public uint FirstImageIndex { get; set; } 114 | 115 | /// 116 | /// Gets or sets the record number of the first huffman compression record. 117 | /// 118 | public uint HuffmanRecordOffset { get; set; } 119 | 120 | /// 121 | /// Gets or sets the number of huffman compression records. 122 | /// 123 | public uint HuffmanRecordCount { get; set; } 124 | 125 | public uint HuffmanTableOffset { get; set; } 126 | 127 | public uint HuffmanTableLength { get; set; } 128 | 129 | public uint EXTHFlags { get; set; } 130 | 131 | /// 132 | /// Gets or sets the offset to DRM key info in DRMed files. 0xFFFFFFFF if no DRM. 133 | /// 134 | public uint DRMOffset { get; set; } 135 | 136 | /// 137 | /// Gets or sets the number of entries in DRM info. 0xFFFFFFFF if no DRM. 138 | /// 139 | public uint DRMCount { get; set; } 140 | 141 | /// 142 | /// Gets or sets the number of bytes in DRM info. 143 | /// 144 | public uint DRMSize { get; set; } 145 | 146 | /// 147 | /// Gets or sets some flags concerning the DRM info. 148 | /// 149 | public uint DRMFlags { get; set; } 150 | 151 | /// 152 | /// Gets or sets the number of first text record. Normally 1. 153 | /// 154 | public ushort FirstContentRecordOffset { get; set; } 155 | 156 | /// 157 | /// Gets or sets the number of last image record or number of last text record if it contains no images. Includes Image, DATP, HUFF, DRM. . 158 | /// 159 | public ushort LastContentRecordOffset { get; set; } 160 | 161 | public uint FCISRecordOffset { get; set; } 162 | 163 | public uint FLISRecordOffset { get; set; } 164 | 165 | public uint ExtraRecordDataFlags { get; set; } 166 | 167 | /// 168 | /// Gets or sets the record number of the first INDX record created from an ncx file. 169 | /// 170 | public uint INDXRecordOffset { get; set; } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Entity/MobiHeader/MobiType.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Mobi.Entity 2 | { 3 | public enum MobiType : uint 4 | { 5 | Unkown, 6 | 7 | /// 8 | /// Mobipocket Book. 9 | /// 10 | Mobipocket = 2, 11 | 12 | /// 13 | /// PalmDoc Book. 14 | /// 15 | PalmDoc = 3, 16 | 17 | /// 18 | /// Audio. 19 | /// 20 | Audio = 4, 21 | 22 | /// 23 | /// mobipocket? generated by kindlegen1.2. 24 | /// 25 | Mobipocket_Kindlegen_1_2 = 232, 26 | 27 | /// 28 | /// generated by kindlegen2. 29 | /// 30 | KF8 = 248, 31 | 32 | News = 257, 33 | 34 | News_Feed = 258, 35 | 36 | News_Magazine = 259, 37 | 38 | PICS = 513, 39 | 40 | WORD = 514, 41 | 42 | XLS = 515, 43 | 44 | PPT = 516, 45 | 46 | TEXT = 517, 47 | 48 | HTML = 518, 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Entity/MobiHeader/TextEncoding.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Mobi.Entity 2 | { 3 | public enum TextEncoding : uint 4 | { 5 | Unkown, 6 | 7 | CP1252 = 1252, 8 | 9 | UTF8 = 65001, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Entity/PalmDB/PalmDB.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Roler.Toolkit.File.Mobi.Entity 6 | { 7 | public class PalmDB 8 | { 9 | /// 10 | /// Gets or sets the name of PDB. 11 | /// 12 | public string Name { get; set; } 13 | 14 | /// 15 | /// Gets or sets attributes of PDB. 16 | /// 17 | public PalmDBAttribute Attribute { get; set; } 18 | 19 | public ushort Version { get; set; } 20 | 21 | /// 22 | /// See above table. (For Applications this data will be 'appl') 23 | /// 24 | public uint Type { get; set; } 25 | 26 | /// 27 | /// See above table. This program will be launched if the file is tapped. 28 | /// 29 | public uint Creator { get; set; } 30 | 31 | /// 32 | /// used internally to identify record. 33 | /// 34 | public uint UniqueIDseed { get; set; } 35 | 36 | /// 37 | /// Only used when in-memory on Palm OS. Always set to zero in stored files. 38 | /// 39 | public uint NextRecordListID { get; set; } 40 | 41 | /// 42 | /// Gets or sets the number of records in the file. 43 | /// 44 | public ushort RecordCount { get; set; } 45 | 46 | public IList RecordInfoList { get; } = new List(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Entity/PalmDB/PalmDBAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Mobi.Entity 2 | { 3 | public enum PalmDBAttribute : ushort 4 | { 5 | Unkown, 6 | 7 | /// 8 | /// Read-Only. 9 | /// 10 | ReadOnly = 0x0002, 11 | 12 | /// 13 | /// Dirty AppInfoArea. 14 | /// 15 | DirtyAppInfoArea = 0x0004, 16 | 17 | /// 18 | /// Backup this database. 19 | /// 20 | Backup = 0x0008, 21 | 22 | /// 23 | /// Okay to install newer over existing copy, if present on PalmPilot. 24 | /// 25 | OverInstall = 0x0010, 26 | 27 | /// 28 | /// Force the PalmPilot to reset after this database is installed. 29 | /// 30 | ResetAfterInstall = 0x0020, 31 | 32 | /// 33 | /// Don't allow copy of file to be beamed to other Pilot. 34 | /// 35 | DisallowCopy = 0x0040, 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Entity/PalmDB/PalmDBRecordAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Mobi.Entity 2 | { 3 | public enum PalmDBRecordAttribute : byte 4 | { 5 | Unkown, 6 | 7 | /// 8 | /// Secret record bit. 9 | /// 10 | Secret = 0x10, 11 | 12 | /// 13 | /// Record in use (busy bit). 14 | /// 15 | InUse = 0x20, 16 | 17 | /// 18 | /// Dirty record bit. 19 | /// 20 | Dirty = 0x40, 21 | 22 | /// 23 | /// Delete record on next HotSync. 24 | /// 25 | Delete = 0x80, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Entity/PalmDB/PalmDBRecordInfo.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Mobi.Entity 2 | { 3 | public class PalmDBRecordInfo 4 | { 5 | /// 6 | /// Gets or sets the offset of record n from the start of the PDB of this record. 7 | /// 8 | public uint Offset { get; set; } 9 | 10 | public PalmDBRecordAttribute Attribute { get; set; } 11 | 12 | /// 13 | /// Gets or sets the unique ID for this record. Often just a sequential count from 0. 14 | /// 15 | public uint UniqueID { get; set; } 16 | 17 | public override string ToString() 18 | { 19 | return $"{UniqueID}, {this.Attribute} , {Offset}"; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Entity/PalmDOCHeader/CompressionType.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Mobi.Entity 2 | { 3 | public enum CompressionType : ushort 4 | { 5 | Unkown, 6 | 7 | /// 8 | /// No compression. 9 | /// 10 | No = 1, 11 | 12 | /// 13 | /// PalmDOC compression. 14 | /// 15 | PalmDOC = 2, 16 | 17 | /// 18 | /// HUFF/CDIC compression. 19 | /// 20 | HUFF_CDIC = 17480, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Entity/PalmDOCHeader/EncryptionType.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Mobi.Entity 2 | { 3 | public enum EncryptionType : ushort 4 | { 5 | /// 6 | /// No encryption. 7 | /// 8 | None = 0, 9 | 10 | /// 11 | /// Old Mobipocket Encryption. 12 | /// 13 | OldMobi = 1, 14 | 15 | /// 16 | /// Mobipocket Encryption. 17 | /// 18 | Mobi = 2, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Entity/PalmDOCHeader/PalmDOCHeader.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Mobi.Entity 2 | { 3 | public class PalmDOCHeader 4 | { 5 | public CompressionType Compression { get; set; } 6 | 7 | /// 8 | /// Always zero. 9 | /// 10 | public short Unused { get; } 11 | 12 | /// 13 | /// Gets or sets the uncompressed length of the entire text of the book. 14 | /// 15 | public uint TextLength { get; set; } 16 | 17 | /// 18 | /// Gets or sets the number of PDB records used for the text of the book. 19 | /// 20 | public ushort RecordCount { get; set; } 21 | 22 | /// 23 | /// Gets or sets the maximum size of each record containing text, always 4096. 24 | /// 25 | public ushort RecordSize { get; set; } 26 | 27 | /// 28 | /// Gets or sets the current reading position, as an offset into the uncompressed text. 29 | /// 30 | public uint CurrentPosition { get; set; } 31 | 32 | /// 33 | /// Gets or sets the encryption type, Only HUFF/CDIC compression. 34 | /// 35 | public EncryptionType Encryption { get; set; } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Entity/SrcsRecord.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Mobi.Entity 2 | { 3 | public class SrcsRecord 4 | { 5 | /// 6 | /// Gets or sets the identifier, always 'SRCS'. 7 | /// 8 | public string Identifier { get; set; } 9 | 10 | /// 11 | /// Gets or sets the value0, fixed value: 0x00000010. 12 | /// 13 | public uint Value0 { get; set; } 14 | 15 | /// 16 | /// Gets or sets the value1, fixed value: 0x0000002f. 17 | /// 18 | public uint Value1 { get; set; } 19 | 20 | /// 21 | /// Gets or sets the value2, fixed value: 0x00000001. 22 | /// 23 | public uint Value2 { get; set; } 24 | 25 | /// 26 | /// Gets or sets the zip archive continues to the end of this record. 27 | /// 28 | public byte[] Zip { get; set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Entity/VideRecord.cs: -------------------------------------------------------------------------------- 1 | namespace Roler.Toolkit.File.Mobi.Entity 2 | { 3 | public class VideRecord 4 | { 5 | /// 6 | /// Gets or sets the identifier, always 'VIDE'. 7 | /// 8 | public string Identifier { get; set; } 9 | 10 | /// 11 | /// Gets or sets the media data continues to the end of this record. 12 | /// 13 | public byte[] Media { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/ExtendMethod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | 5 | namespace Roler.Toolkit.File.Mobi 6 | { 7 | internal static class ExtendMethod 8 | { 9 | public static bool TryReadByte(this Stream stream, out byte outResult) 10 | { 11 | byte[] buffer = new byte[1]; 12 | if (stream.Read(buffer, 0, 1) == 1) 13 | { 14 | outResult = buffer[0]; 15 | return true; 16 | } 17 | outResult = 0; 18 | return false; 19 | } 20 | 21 | public static bool TryReadBytes(this Stream stream, int length, out byte[] outResult) 22 | { 23 | byte[] buffer = new byte[length]; 24 | if (stream.Read(buffer, 0, length) == length) 25 | { 26 | outResult = buffer; 27 | return true; 28 | } 29 | outResult = null; 30 | return false; 31 | } 32 | 33 | public static bool TryReadUshort(this Stream stream, out ushort outResult) 34 | { 35 | byte[] buffer = new byte[2]; 36 | if (stream.Read(buffer, 0, 2) == 2) 37 | { 38 | outResult = buffer.ToUInt16(); 39 | return true; 40 | } 41 | outResult = 0; 42 | return false; 43 | } 44 | 45 | public static bool TryReadUint(this Stream stream, out uint outResult) 46 | { 47 | byte[] buffer = new byte[4]; 48 | if (stream.Read(buffer, 0, 4) == 4) 49 | { 50 | outResult = buffer.ToUInt32(); 51 | return true; 52 | } 53 | outResult = 0; 54 | return false; 55 | } 56 | 57 | public static bool TryReadString(this Stream stream, int length, out string outResult) 58 | { 59 | byte[] buffer = new byte[length]; 60 | if (stream.Read(buffer, 0, length) == length) 61 | { 62 | outResult = System.Text.Encoding.UTF8.GetString(buffer); 63 | return true; 64 | } 65 | outResult = null; 66 | return false; 67 | } 68 | 69 | public static void Skip(this Stream stream) 70 | { 71 | int b; 72 | do 73 | { 74 | b = stream.ReadByte(); 75 | } while (b == 0); 76 | if (b > 0) 77 | { 78 | stream.Seek(stream.Position - 1, SeekOrigin.Begin); 79 | } 80 | } 81 | 82 | public static bool CheckStart(this Stream stream, int length, string value) 83 | { 84 | if (stream.TryReadString(length, out string start)) 85 | { 86 | return string.Equals(start, value, StringComparison.OrdinalIgnoreCase); 87 | } 88 | return false; 89 | } 90 | 91 | public static ushort ToUInt16(this byte[] bytes) 92 | { 93 | if (bytes.Length == 0) 94 | { 95 | throw new ArgumentException("bytes can not empty."); 96 | } 97 | 98 | var length = Math.Min(2, bytes.Length); 99 | ushort result = 0; 100 | for (int i = 0; i < length; i++) 101 | { 102 | result |= (ushort)(bytes[i] << (length - 1 - i) * 8); 103 | } 104 | return result; 105 | } 106 | 107 | public static uint ToUInt32(this byte[] bytes) 108 | { 109 | if (bytes.Length == 0) 110 | { 111 | throw new ArgumentException("bytes can not empty."); 112 | } 113 | 114 | var length = Math.Min(4, bytes.Length); 115 | uint result = 0; 116 | for (int i = 0; i < length; i++) 117 | { 118 | result |= (uint)bytes[i] << ((length - 1 - i) * 8); 119 | } 120 | return result; 121 | } 122 | 123 | public static ulong ToUInt64(this byte[] bytes) 124 | { 125 | if (bytes.Length == 0) 126 | { 127 | throw new ArgumentException("bytes can not empty."); 128 | } 129 | 130 | var length = Math.Min(8, bytes.Length); 131 | ulong result = 0; 132 | for (int i = 0; i < length; i++) 133 | { 134 | result |= (ulong)bytes[i] << ((length - 1 - i) * 8); 135 | } 136 | return result; 137 | } 138 | 139 | public static bool RangeEqual(this byte[] bytes, int start, int length, byte[] second) 140 | { 141 | if (second is null) 142 | { 143 | throw new ArgumentNullException(nameof(second)); 144 | } 145 | 146 | return bytes.Skip(start).Take(length).SequenceEqual(second); 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Mobi.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Roler.Toolkit.File.Mobi 3 | { 4 | public class Mobi 5 | { 6 | public Structure Structure { get; set; } 7 | 8 | public string Title { get; set; } 9 | public string Creator { get; set; } 10 | public string Publisher { get; set; } 11 | public string Description { get; set; } 12 | public string Subject { get; set; } 13 | public string Date { get; set; } 14 | public string Contributor { get; set; } 15 | public string Rights { get; set; } 16 | public string Type { get; set; } 17 | public string Source { get; set; } 18 | public string Language { get; set; } 19 | public ContentFile Cover { get; set; } 20 | 21 | public string Text { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/MobiReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using Roler.Toolkit.File.Mobi.Compression; 7 | using Roler.Toolkit.File.Mobi.Engine; 8 | using Roler.Toolkit.File.Mobi.Entity; 9 | 10 | namespace Roler.Toolkit.File.Mobi 11 | { 12 | public class MobiReader : IDisposable 13 | { 14 | private bool _disposed; 15 | private readonly Stream _stream; 16 | private readonly IList _palmDBRecordList = new List(); 17 | 18 | public MobiReader(Stream stream) 19 | { 20 | this._stream = stream; 21 | } 22 | 23 | #region Methods 24 | 25 | public Mobi ReadWithoutText() 26 | { 27 | if (this._disposed) 28 | { 29 | throw new ObjectDisposedException("stream"); 30 | } 31 | 32 | var structure = this.ReadStructure(); 33 | var result = new Mobi 34 | { 35 | Structure = structure, 36 | Title = structure.FullName, 37 | }; 38 | 39 | if (structure.ExthHeader != null) 40 | { 41 | var exthHeader = structure.ExthHeader; 42 | result.Creator = exthHeader.RecordList.FirstOrDefault(p => p.Type == ExthRecordType.Author)?.DataAsString(); 43 | result.Publisher = exthHeader.RecordList.FirstOrDefault(p => p.Type == ExthRecordType.Publisher)?.DataAsString(); 44 | result.Description = exthHeader.RecordList.FirstOrDefault(p => p.Type == ExthRecordType.Description)?.DataAsString(); 45 | result.Subject = exthHeader.RecordList.FirstOrDefault(p => p.Type == ExthRecordType.Subject)?.DataAsString(); 46 | result.Date = exthHeader.RecordList.FirstOrDefault(p => p.Type == ExthRecordType.PublishingDate)?.DataAsString(); 47 | result.Contributor = exthHeader.RecordList.FirstOrDefault(p => p.Type == ExthRecordType.Contributor)?.DataAsString(); 48 | result.Rights = exthHeader.RecordList.FirstOrDefault(p => p.Type == ExthRecordType.Rights)?.DataAsString(); 49 | result.Type = exthHeader.RecordList.FirstOrDefault(p => p.Type == ExthRecordType.Type)?.DataAsString(); 50 | result.Source = exthHeader.RecordList.FirstOrDefault(p => p.Type == ExthRecordType.Source)?.DataAsString(); 51 | result.Language = exthHeader.RecordList.FirstOrDefault(p => p.Type == ExthRecordType.Language)?.DataAsString(); 52 | 53 | } 54 | 55 | this.RefreshPalmDBRecordList(structure.PalmDB.RecordInfoList); 56 | 57 | result.Cover = this.ReadCover(structure); 58 | 59 | return result; 60 | } 61 | 62 | public bool TryReadWithoutText(out Mobi mobi) 63 | { 64 | bool result; 65 | 66 | try 67 | { 68 | mobi = this.ReadWithoutText(); 69 | result = true; 70 | } 71 | catch (Exception) 72 | { 73 | mobi = null; 74 | result = false; 75 | } 76 | 77 | return result; 78 | } 79 | 80 | public Mobi Read(MobiReadingConfiguration configuration = null) 81 | { 82 | var result = this.ReadWithoutText(); 83 | result.Text = this.ReadText(result.Structure, configuration); 84 | 85 | return result; 86 | } 87 | 88 | public bool TryRead(out Mobi mobi) 89 | { 90 | bool result; 91 | 92 | try 93 | { 94 | mobi = this.Read(); 95 | result = true; 96 | } 97 | catch (Exception) 98 | { 99 | mobi = null; 100 | result = false; 101 | } 102 | 103 | return result; 104 | } 105 | 106 | public string ReadText(Structure structure, MobiReadingConfiguration configuration = null) 107 | { 108 | if (this._disposed) 109 | { 110 | throw new ObjectDisposedException("stream"); 111 | } 112 | if (structure is null) 113 | { 114 | throw new ArgumentNullException(nameof(structure)); 115 | } 116 | configuration = configuration ?? new MobiReadingConfiguration(); 117 | 118 | var decompressedByteList = new List(); 119 | ICompression compression = null; 120 | Encoding encoding = Encoding.UTF8; 121 | ushort maxRecordSize = 4096; 122 | 123 | if (structure.PalmDOCHeader != null) 124 | { 125 | if (structure.PalmDOCHeader.RecordSize > 0) 126 | { 127 | maxRecordSize = structure.PalmDOCHeader.RecordSize; 128 | } 129 | switch (structure.PalmDOCHeader.Compression) 130 | { 131 | case CompressionType.PalmDOC: 132 | { 133 | compression = new PalmDocCompression(); 134 | } 135 | break; 136 | case CompressionType.HUFF_CDIC: 137 | { 138 | compression = CreateHuffCdicCompression(structure.MobiHeader); 139 | } 140 | break; 141 | default: 142 | { 143 | compression = new NoneCompression(); 144 | } 145 | break; 146 | } 147 | } 148 | 149 | if (structure.MobiHeader != null) 150 | { 151 | var firstTextRecordIndex = configuration.FindFirstTextRecordIndex(structure.MobiHeader); 152 | var firstNonTextRecordIndex = configuration.FindFirstNonTextRecordIndex(structure.MobiHeader, this._palmDBRecordList.Count); 153 | for (int i = firstTextRecordIndex; i < firstNonTextRecordIndex; i++) 154 | { 155 | var recordBytes = this.ReadPalmDBRecord(this._palmDBRecordList[i]); 156 | var decompressedBytes = compression.Decompress(recordBytes); 157 | var fixedDecompressedBytes = decompressedBytes.Length > maxRecordSize ? 158 | decompressedBytes.Take(maxRecordSize) : 159 | decompressedBytes; 160 | decompressedByteList.AddRange(fixedDecompressedBytes); 161 | } 162 | } 163 | 164 | return encoding.GetString(decompressedByteList.ToArray()); 165 | } 166 | 167 | public Stream ReadContentFile(ContentFile contentFile) 168 | { 169 | if (this._disposed) 170 | { 171 | throw new ObjectDisposedException("stream"); 172 | } 173 | if (contentFile is null) 174 | { 175 | throw new ArgumentNullException(nameof(contentFile)); 176 | } 177 | 178 | if (int.TryParse(contentFile.Source, out int index) && index < this._palmDBRecordList.Count) 179 | { 180 | byte[] bytes = this.ReadPalmDBRecord(this._palmDBRecordList[index]); 181 | if (bytes != null) 182 | { 183 | return new MemoryStream(bytes); 184 | } 185 | } 186 | return null; 187 | } 188 | 189 | #region Structure 190 | 191 | private Structure ReadStructure() 192 | { 193 | var result = new Structure(); 194 | var palmDB = PalmDBEngine.Read(this._stream) ?? throw new InvalidDataException("file can not open."); 195 | result.PalmDB = palmDB; 196 | if (palmDB.RecordInfoList.Any()) 197 | { 198 | long firstRecordOffset = palmDB.RecordInfoList.First().Offset; 199 | result.PalmDOCHeader = PalmDOCHeaderEngine.Read(this._stream, firstRecordOffset) ?? throw new InvalidDataException("invalid file! missing part:PalmDOC Header."); 200 | 201 | if (MobiHeaderEngine.TryRead(this._stream, this._stream.Position, out MobiHeader mobiHeader)) 202 | { 203 | result.MobiHeader = mobiHeader; 204 | } 205 | else 206 | { 207 | throw new InvalidDataException("invalid file! missing part:MOBI Header."); 208 | } 209 | 210 | if (ExthHeaderEngine.TryRead(this._stream, this._stream.Position, out ExthHeader exthHeader)) 211 | { 212 | result.ExthHeader = exthHeader; 213 | } 214 | 215 | if (mobiHeader.FullNameLength > 0) 216 | { 217 | long fullNameOffset = firstRecordOffset + mobiHeader.FullNameOffset; 218 | this._stream.Seek(fullNameOffset, SeekOrigin.Begin); 219 | if (this._stream.TryReadString((int)mobiHeader.FullNameLength, out string fullName)) 220 | { 221 | result.FullName = fullName; 222 | } 223 | } 224 | 225 | if (mobiHeader.INDXRecordOffset != MobiHeaderEngine.UnavailableIndex && 226 | mobiHeader.INDXRecordOffset < palmDB.RecordInfoList.Count && 227 | IndxHeaderEngine.TryRead(this._stream, palmDB.RecordInfoList[(int)mobiHeader.INDXRecordOffset].Offset, out IndxHeader indxHeader)) 228 | { 229 | result.IndxHeader = indxHeader; 230 | } 231 | 232 | if (mobiHeader.FLISRecordOffset != MobiHeaderEngine.UnavailableIndex && 233 | mobiHeader.FLISRecordOffset < palmDB.RecordInfoList.Count && 234 | RecordEngine.TryReadFlisRecord(this._stream, palmDB.RecordInfoList[(int)mobiHeader.FLISRecordOffset].Offset, out FlisRecord flisRecord)) 235 | { 236 | result.FlisRecord = flisRecord; 237 | } 238 | 239 | if (mobiHeader.FCISRecordOffset != MobiHeaderEngine.UnavailableIndex && 240 | mobiHeader.FCISRecordOffset < palmDB.RecordInfoList.Count && 241 | RecordEngine.TryReadFcisRecord(this._stream, palmDB.RecordInfoList[(int)mobiHeader.FCISRecordOffset].Offset, out FcisRecord fcisRecord)) 242 | { 243 | result.FcisRecord = fcisRecord; 244 | } 245 | 246 | } 247 | 248 | return result; 249 | } 250 | 251 | #endregion 252 | 253 | #region Text 254 | 255 | private void RefreshPalmDBRecordList(IList palmDBRecordInfoList) 256 | { 257 | this._palmDBRecordList.Clear(); 258 | PalmDBRecord lastRecord = null; 259 | foreach (var palmDBRecordInfo in palmDBRecordInfoList) 260 | { 261 | var record = new PalmDBRecord(palmDBRecordInfo); 262 | if (lastRecord != null) 263 | { 264 | lastRecord.Length = (int)(palmDBRecordInfo.Offset - lastRecord.Info.Offset); 265 | } 266 | this._palmDBRecordList.Add(record); 267 | lastRecord = record; 268 | } 269 | } 270 | 271 | private HuffCdicCompression CreateHuffCdicCompression(MobiHeader mobiHeader) 272 | { 273 | HuffCdicCompression result = null; 274 | if (mobiHeader.HuffmanRecordOffset != MobiHeaderEngine.UnavailableIndex && 275 | mobiHeader.HuffmanRecordOffset < this._palmDBRecordList.Count) 276 | { 277 | var huff = this.ReadPalmDBRecord(this._palmDBRecordList[(int)mobiHeader.HuffmanRecordOffset]); 278 | 279 | var cdicList = new List(); 280 | for (int i = 1; i < mobiHeader.HuffmanRecordCount; i++) 281 | { 282 | var recordBytes = this.ReadPalmDBRecord(this._palmDBRecordList[(int)mobiHeader.HuffmanRecordOffset + i]); 283 | cdicList.Add(recordBytes); 284 | } 285 | 286 | result = new HuffCdicCompression(huff, cdicList) 287 | { 288 | ExtraFlags = mobiHeader.ExtraRecordDataFlags 289 | }; 290 | } 291 | return result; 292 | } 293 | 294 | private byte[] ReadPalmDBRecord(PalmDBRecord palmDBRecord) 295 | { 296 | byte[] result = new byte[palmDBRecord.Length]; 297 | this._stream.Seek(palmDBRecord.Info.Offset, SeekOrigin.Begin); 298 | this._stream.Read(result, 0, result.Length); 299 | return result; 300 | } 301 | 302 | #endregion 303 | 304 | #region Cover 305 | 306 | private ContentFile ReadCover(Structure structure) 307 | { 308 | if (structure is null) 309 | { 310 | throw new ArgumentNullException(nameof(structure)); 311 | } 312 | 313 | ContentFile result = null; 314 | if (structure.MobiHeader != null && 315 | structure.MobiHeader.FirstImageIndex != MobiHeaderEngine.UnavailableIndex && 316 | structure.MobiHeader.FirstImageIndex < this._palmDBRecordList.Count && 317 | structure.ExthHeader != null) 318 | { 319 | var exthHeader = structure.ExthHeader; 320 | var coverOffsetBytes = exthHeader.RecordList.FirstOrDefault(p => p.Type == ExthRecordType.CoverOffset)?.Data; 321 | if (coverOffsetBytes != null) 322 | { 323 | var coverImageIndex = structure.MobiHeader.FirstImageIndex + coverOffsetBytes.ToUInt32(); 324 | if (coverImageIndex < this._palmDBRecordList.Count) 325 | { 326 | result = new ContentFile(String.Empty, coverImageIndex.ToString()); 327 | } 328 | } 329 | } 330 | return result; 331 | } 332 | 333 | #endregion 334 | 335 | #region Disposable 336 | 337 | protected virtual void Dispose(bool disposing) 338 | { 339 | if (_disposed) 340 | { 341 | return; 342 | } 343 | 344 | if (disposing) 345 | { 346 | } 347 | 348 | _disposed = true; 349 | } 350 | 351 | public void Dispose() 352 | { 353 | this.Dispose(true); 354 | GC.SuppressFinalize(this); 355 | } 356 | 357 | #endregion 358 | 359 | #endregion 360 | } 361 | } 362 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/MobiReadingConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Roler.Toolkit.File.Mobi.Engine; 3 | using Roler.Toolkit.File.Mobi.Entity; 4 | 5 | namespace Roler.Toolkit.File.Mobi 6 | { 7 | public class MobiReadingConfiguration 8 | { 9 | public virtual int FindFirstTextRecordIndex(MobiHeader mobiHeader) 10 | { 11 | if (mobiHeader is null) 12 | { 13 | throw new ArgumentNullException(nameof(mobiHeader)); 14 | } 15 | 16 | return mobiHeader.FirstContentRecordOffset > 0 ? mobiHeader.FirstContentRecordOffset : 1; 17 | } 18 | 19 | public virtual long FindFirstNonTextRecordIndex(MobiHeader mobiHeader, long recordCount) 20 | { 21 | if (mobiHeader is null) 22 | { 23 | throw new ArgumentNullException(nameof(mobiHeader)); 24 | } 25 | 26 | long result; 27 | if (mobiHeader.FirstNonBookIndex != MobiHeaderEngine.UnavailableIndex && 28 | mobiHeader.FirstNonBookIndex < recordCount) 29 | { 30 | result = mobiHeader.FirstNonBookIndex; 31 | } 32 | else 33 | { 34 | result = Math.Min(mobiHeader.LastContentRecordOffset, mobiHeader.INDXRecordOffset); 35 | result = Math.Min(result, mobiHeader.FLISRecordOffset); 36 | result = Math.Min(result, mobiHeader.FCISRecordOffset); 37 | result = Math.Min(result, recordCount); 38 | } 39 | return result; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/PalmDBRecord.cs: -------------------------------------------------------------------------------- 1 | using Roler.Toolkit.File.Mobi.Entity; 2 | 3 | namespace Roler.Toolkit.File.Mobi 4 | { 5 | internal class PalmDBRecord 6 | { 7 | public PalmDBRecordInfo Info { get; } 8 | public int Length { get; set; } 9 | 10 | public PalmDBRecord(PalmDBRecordInfo info) 11 | { 12 | this.Info = info; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Roler.Toolkit.File.Mobi.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | RolerZhang 6 | .NET Standard Class Library for Mobi File. 7 | Apache-2.0 8 | https://github.com/rolerzhang/RolerFileToolkit 9 | mobi 10 | 1.0.5 11 | 1.0.5.0 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /RolerFileToolkit/Roler.Toolkit.File.Mobi/Structure.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Roler.Toolkit.File.Mobi.Entity; 3 | 4 | namespace Roler.Toolkit.File.Mobi 5 | { 6 | public class Structure 7 | { 8 | public string FullName { get; set; } 9 | public PalmDB PalmDB { get; set; } 10 | public PalmDOCHeader PalmDOCHeader { get; set; } 11 | public MobiHeader MobiHeader { get; set; } 12 | public ExthHeader ExthHeader { get; set; } 13 | public IndxHeader IndxHeader { get; set; } 14 | public FlisRecord FlisRecord { get; set; } 15 | public FcisRecord FcisRecord { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /RolerFileToolkit/RolerFileToolkit.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29409.12 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roler.Toolkit.File.Epub", "Roler.Toolkit.File.Epub\Roler.Toolkit.File.Epub.csproj", "{C67B8F9F-1677-4FF2-BB7B-8CFF9CE3B7B6}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{C329D920-D541-4A0D-857D-6001C468EAA5}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileToolkitSample.UWP", "Samples\FileToolkitSample.UWP\FileToolkitSample.UWP.csproj", "{5D986DC5-9B28-486B-A713-3395BDED35A5}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileToolkitSample.WPF", "Samples\FileToolkitSample.WPF\FileToolkitSample.WPF.csproj", "{09F75429-54D6-4963-83AA-5E1EFA4872F7}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Roler.Toolkit.File.Mobi", "Roler.Toolkit.File.Mobi\Roler.Toolkit.File.Mobi.csproj", "{512EC6EB-7CB3-4372-8954-CF5E578E5AEE}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Debug|ARM = Debug|ARM 20 | Debug|ARM64 = Debug|ARM64 21 | Debug|x64 = Debug|x64 22 | Debug|x86 = Debug|x86 23 | Release|Any CPU = Release|Any CPU 24 | Release|ARM = Release|ARM 25 | Release|ARM64 = Release|ARM64 26 | Release|x64 = Release|x64 27 | Release|x86 = Release|x86 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {C67B8F9F-1677-4FF2-BB7B-8CFF9CE3B7B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {C67B8F9F-1677-4FF2-BB7B-8CFF9CE3B7B6}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {C67B8F9F-1677-4FF2-BB7B-8CFF9CE3B7B6}.Debug|ARM.ActiveCfg = Debug|Any CPU 33 | {C67B8F9F-1677-4FF2-BB7B-8CFF9CE3B7B6}.Debug|ARM.Build.0 = Debug|Any CPU 34 | {C67B8F9F-1677-4FF2-BB7B-8CFF9CE3B7B6}.Debug|ARM64.ActiveCfg = Debug|Any CPU 35 | {C67B8F9F-1677-4FF2-BB7B-8CFF9CE3B7B6}.Debug|ARM64.Build.0 = Debug|Any CPU 36 | {C67B8F9F-1677-4FF2-BB7B-8CFF9CE3B7B6}.Debug|x64.ActiveCfg = Debug|Any CPU 37 | {C67B8F9F-1677-4FF2-BB7B-8CFF9CE3B7B6}.Debug|x64.Build.0 = Debug|Any CPU 38 | {C67B8F9F-1677-4FF2-BB7B-8CFF9CE3B7B6}.Debug|x86.ActiveCfg = Debug|Any CPU 39 | {C67B8F9F-1677-4FF2-BB7B-8CFF9CE3B7B6}.Debug|x86.Build.0 = Debug|Any CPU 40 | {C67B8F9F-1677-4FF2-BB7B-8CFF9CE3B7B6}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {C67B8F9F-1677-4FF2-BB7B-8CFF9CE3B7B6}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {C67B8F9F-1677-4FF2-BB7B-8CFF9CE3B7B6}.Release|ARM.ActiveCfg = Release|Any CPU 43 | {C67B8F9F-1677-4FF2-BB7B-8CFF9CE3B7B6}.Release|ARM.Build.0 = Release|Any CPU 44 | {C67B8F9F-1677-4FF2-BB7B-8CFF9CE3B7B6}.Release|ARM64.ActiveCfg = Release|Any CPU 45 | {C67B8F9F-1677-4FF2-BB7B-8CFF9CE3B7B6}.Release|ARM64.Build.0 = Release|Any CPU 46 | {C67B8F9F-1677-4FF2-BB7B-8CFF9CE3B7B6}.Release|x64.ActiveCfg = Release|Any CPU 47 | {C67B8F9F-1677-4FF2-BB7B-8CFF9CE3B7B6}.Release|x64.Build.0 = Release|Any CPU 48 | {C67B8F9F-1677-4FF2-BB7B-8CFF9CE3B7B6}.Release|x86.ActiveCfg = Release|Any CPU 49 | {C67B8F9F-1677-4FF2-BB7B-8CFF9CE3B7B6}.Release|x86.Build.0 = Release|Any CPU 50 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Debug|Any CPU.ActiveCfg = Debug|x86 51 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Debug|ARM.ActiveCfg = Debug|ARM 52 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Debug|ARM.Build.0 = Debug|ARM 53 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Debug|ARM.Deploy.0 = Debug|ARM 54 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Debug|ARM64.ActiveCfg = Debug|ARM64 55 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Debug|ARM64.Build.0 = Debug|ARM64 56 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Debug|ARM64.Deploy.0 = Debug|ARM64 57 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Debug|x64.ActiveCfg = Debug|x64 58 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Debug|x64.Build.0 = Debug|x64 59 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Debug|x64.Deploy.0 = Debug|x64 60 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Debug|x86.ActiveCfg = Debug|x86 61 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Debug|x86.Build.0 = Debug|x86 62 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Debug|x86.Deploy.0 = Debug|x86 63 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Release|Any CPU.ActiveCfg = Release|x86 64 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Release|ARM.ActiveCfg = Release|ARM 65 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Release|ARM.Build.0 = Release|ARM 66 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Release|ARM.Deploy.0 = Release|ARM 67 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Release|ARM64.ActiveCfg = Release|ARM64 68 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Release|ARM64.Build.0 = Release|ARM64 69 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Release|ARM64.Deploy.0 = Release|ARM64 70 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Release|x64.ActiveCfg = Release|x64 71 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Release|x64.Build.0 = Release|x64 72 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Release|x64.Deploy.0 = Release|x64 73 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Release|x86.ActiveCfg = Release|x86 74 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Release|x86.Build.0 = Release|x86 75 | {5D986DC5-9B28-486B-A713-3395BDED35A5}.Release|x86.Deploy.0 = Release|x86 76 | {09F75429-54D6-4963-83AA-5E1EFA4872F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 77 | {09F75429-54D6-4963-83AA-5E1EFA4872F7}.Debug|Any CPU.Build.0 = Debug|Any CPU 78 | {09F75429-54D6-4963-83AA-5E1EFA4872F7}.Debug|ARM.ActiveCfg = Debug|Any CPU 79 | {09F75429-54D6-4963-83AA-5E1EFA4872F7}.Debug|ARM.Build.0 = Debug|Any CPU 80 | {09F75429-54D6-4963-83AA-5E1EFA4872F7}.Debug|ARM64.ActiveCfg = Debug|Any CPU 81 | {09F75429-54D6-4963-83AA-5E1EFA4872F7}.Debug|ARM64.Build.0 = Debug|Any CPU 82 | {09F75429-54D6-4963-83AA-5E1EFA4872F7}.Debug|x64.ActiveCfg = Debug|Any CPU 83 | {09F75429-54D6-4963-83AA-5E1EFA4872F7}.Debug|x64.Build.0 = Debug|Any CPU 84 | {09F75429-54D6-4963-83AA-5E1EFA4872F7}.Debug|x86.ActiveCfg = Debug|Any CPU 85 | {09F75429-54D6-4963-83AA-5E1EFA4872F7}.Debug|x86.Build.0 = Debug|Any CPU 86 | {09F75429-54D6-4963-83AA-5E1EFA4872F7}.Release|Any CPU.ActiveCfg = Release|Any CPU 87 | {09F75429-54D6-4963-83AA-5E1EFA4872F7}.Release|Any CPU.Build.0 = Release|Any CPU 88 | {09F75429-54D6-4963-83AA-5E1EFA4872F7}.Release|ARM.ActiveCfg = Release|Any CPU 89 | {09F75429-54D6-4963-83AA-5E1EFA4872F7}.Release|ARM.Build.0 = Release|Any CPU 90 | {09F75429-54D6-4963-83AA-5E1EFA4872F7}.Release|ARM64.ActiveCfg = Release|Any CPU 91 | {09F75429-54D6-4963-83AA-5E1EFA4872F7}.Release|ARM64.Build.0 = Release|Any CPU 92 | {09F75429-54D6-4963-83AA-5E1EFA4872F7}.Release|x64.ActiveCfg = Release|Any CPU 93 | {09F75429-54D6-4963-83AA-5E1EFA4872F7}.Release|x64.Build.0 = Release|Any CPU 94 | {09F75429-54D6-4963-83AA-5E1EFA4872F7}.Release|x86.ActiveCfg = Release|Any CPU 95 | {09F75429-54D6-4963-83AA-5E1EFA4872F7}.Release|x86.Build.0 = Release|Any CPU 96 | {512EC6EB-7CB3-4372-8954-CF5E578E5AEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 97 | {512EC6EB-7CB3-4372-8954-CF5E578E5AEE}.Debug|Any CPU.Build.0 = Debug|Any CPU 98 | {512EC6EB-7CB3-4372-8954-CF5E578E5AEE}.Debug|ARM.ActiveCfg = Debug|Any CPU 99 | {512EC6EB-7CB3-4372-8954-CF5E578E5AEE}.Debug|ARM.Build.0 = Debug|Any CPU 100 | {512EC6EB-7CB3-4372-8954-CF5E578E5AEE}.Debug|ARM64.ActiveCfg = Debug|Any CPU 101 | {512EC6EB-7CB3-4372-8954-CF5E578E5AEE}.Debug|ARM64.Build.0 = Debug|Any CPU 102 | {512EC6EB-7CB3-4372-8954-CF5E578E5AEE}.Debug|x64.ActiveCfg = Debug|Any CPU 103 | {512EC6EB-7CB3-4372-8954-CF5E578E5AEE}.Debug|x64.Build.0 = Debug|Any CPU 104 | {512EC6EB-7CB3-4372-8954-CF5E578E5AEE}.Debug|x86.ActiveCfg = Debug|Any CPU 105 | {512EC6EB-7CB3-4372-8954-CF5E578E5AEE}.Debug|x86.Build.0 = Debug|Any CPU 106 | {512EC6EB-7CB3-4372-8954-CF5E578E5AEE}.Release|Any CPU.ActiveCfg = Release|Any CPU 107 | {512EC6EB-7CB3-4372-8954-CF5E578E5AEE}.Release|Any CPU.Build.0 = Release|Any CPU 108 | {512EC6EB-7CB3-4372-8954-CF5E578E5AEE}.Release|ARM.ActiveCfg = Release|Any CPU 109 | {512EC6EB-7CB3-4372-8954-CF5E578E5AEE}.Release|ARM.Build.0 = Release|Any CPU 110 | {512EC6EB-7CB3-4372-8954-CF5E578E5AEE}.Release|ARM64.ActiveCfg = Release|Any CPU 111 | {512EC6EB-7CB3-4372-8954-CF5E578E5AEE}.Release|ARM64.Build.0 = Release|Any CPU 112 | {512EC6EB-7CB3-4372-8954-CF5E578E5AEE}.Release|x64.ActiveCfg = Release|Any CPU 113 | {512EC6EB-7CB3-4372-8954-CF5E578E5AEE}.Release|x64.Build.0 = Release|Any CPU 114 | {512EC6EB-7CB3-4372-8954-CF5E578E5AEE}.Release|x86.ActiveCfg = Release|Any CPU 115 | {512EC6EB-7CB3-4372-8954-CF5E578E5AEE}.Release|x86.Build.0 = Release|Any CPU 116 | EndGlobalSection 117 | GlobalSection(SolutionProperties) = preSolution 118 | HideSolutionNode = FALSE 119 | EndGlobalSection 120 | GlobalSection(NestedProjects) = preSolution 121 | {5D986DC5-9B28-486B-A713-3395BDED35A5} = {C329D920-D541-4A0D-857D-6001C468EAA5} 122 | {09F75429-54D6-4963-83AA-5E1EFA4872F7} = {C329D920-D541-4A0D-857D-6001C468EAA5} 123 | EndGlobalSection 124 | GlobalSection(ExtensibilityGlobals) = postSolution 125 | SolutionGuid = {B93EFA96-6362-4583-85F1-1A8ED5BCA1A4} 126 | EndGlobalSection 127 | EndGlobal 128 | -------------------------------------------------------------------------------- /RolerFileToolkit/Samples/FileToolkitSample.UWP/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | -------------------------------------------------------------------------------- /RolerFileToolkit/Samples/FileToolkitSample.UWP/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Runtime.InteropServices.WindowsRuntime; 6 | using Windows.ApplicationModel; 7 | using Windows.ApplicationModel.Activation; 8 | using Windows.Foundation; 9 | using Windows.Foundation.Collections; 10 | using Windows.UI.Xaml; 11 | using Windows.UI.Xaml.Controls; 12 | using Windows.UI.Xaml.Controls.Primitives; 13 | using Windows.UI.Xaml.Data; 14 | using Windows.UI.Xaml.Input; 15 | using Windows.UI.Xaml.Media; 16 | using Windows.UI.Xaml.Navigation; 17 | 18 | namespace FileToolkitSample.UWP 19 | { 20 | /// 21 | /// Provides application-specific behavior to supplement the default Application class. 22 | /// 23 | sealed partial class App : Application 24 | { 25 | /// 26 | /// Initializes the singleton application object. This is the first line of authored code 27 | /// executed, and as such is the logical equivalent of main() or WinMain(). 28 | /// 29 | public App() 30 | { 31 | this.InitializeComponent(); 32 | this.Suspending += OnSuspending; 33 | } 34 | 35 | /// 36 | /// Invoked when the application is launched normally by the end user. Other entry points 37 | /// will be used such as when the application is launched to open a specific file. 38 | /// 39 | /// Details about the launch request and process. 40 | protected override void OnLaunched(LaunchActivatedEventArgs e) 41 | { 42 | Frame rootFrame = Window.Current.Content as Frame; 43 | 44 | // Do not repeat app initialization when the Window already has content, 45 | // just ensure that the window is active 46 | if (rootFrame == null) 47 | { 48 | // Create a Frame to act as the navigation context and navigate to the first page 49 | rootFrame = new Frame(); 50 | 51 | rootFrame.NavigationFailed += OnNavigationFailed; 52 | 53 | if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) 54 | { 55 | //TODO: Load state from previously suspended application 56 | } 57 | 58 | // Place the frame in the current Window 59 | Window.Current.Content = rootFrame; 60 | } 61 | 62 | if (e.PrelaunchActivated == false) 63 | { 64 | if (rootFrame.Content == null) 65 | { 66 | // When the navigation stack isn't restored navigate to the first page, 67 | // configuring the new page by passing required information as a navigation 68 | // parameter 69 | rootFrame.Navigate(typeof(MainPage), e.Arguments); 70 | } 71 | // Ensure the current window is active 72 | Window.Current.Activate(); 73 | } 74 | } 75 | 76 | /// 77 | /// Invoked when Navigation to a certain page fails 78 | /// 79 | /// The Frame which failed navigation 80 | /// Details about the navigation failure 81 | void OnNavigationFailed(object sender, NavigationFailedEventArgs e) 82 | { 83 | throw new Exception("Failed to load Page " + e.SourcePageType.FullName); 84 | } 85 | 86 | /// 87 | /// Invoked when application execution is being suspended. Application state is saved 88 | /// without knowing whether the application will be terminated or resumed with the contents 89 | /// of memory still intact. 90 | /// 91 | /// The source of the suspend request. 92 | /// Details about the suspend request. 93 | private void OnSuspending(object sender, SuspendingEventArgs e) 94 | { 95 | var deferral = e.SuspendingOperation.GetDeferral(); 96 | //TODO: Save application state and stop any background activity 97 | deferral.Complete(); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /RolerFileToolkit/Samples/FileToolkitSample.UWP/Assets/LockScreenLogo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolerzhang/RolerFileToolkit/e0bd85306c633c94ca7575c7c1327d3a0975f7d5/RolerFileToolkit/Samples/FileToolkitSample.UWP/Assets/LockScreenLogo.scale-200.png -------------------------------------------------------------------------------- /RolerFileToolkit/Samples/FileToolkitSample.UWP/Assets/SplashScreen.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolerzhang/RolerFileToolkit/e0bd85306c633c94ca7575c7c1327d3a0975f7d5/RolerFileToolkit/Samples/FileToolkitSample.UWP/Assets/SplashScreen.scale-200.png -------------------------------------------------------------------------------- /RolerFileToolkit/Samples/FileToolkitSample.UWP/Assets/Square150x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolerzhang/RolerFileToolkit/e0bd85306c633c94ca7575c7c1327d3a0975f7d5/RolerFileToolkit/Samples/FileToolkitSample.UWP/Assets/Square150x150Logo.scale-200.png -------------------------------------------------------------------------------- /RolerFileToolkit/Samples/FileToolkitSample.UWP/Assets/Square44x44Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolerzhang/RolerFileToolkit/e0bd85306c633c94ca7575c7c1327d3a0975f7d5/RolerFileToolkit/Samples/FileToolkitSample.UWP/Assets/Square44x44Logo.scale-200.png -------------------------------------------------------------------------------- /RolerFileToolkit/Samples/FileToolkitSample.UWP/Assets/Square44x44Logo.targetsize-24_altform-unplated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolerzhang/RolerFileToolkit/e0bd85306c633c94ca7575c7c1327d3a0975f7d5/RolerFileToolkit/Samples/FileToolkitSample.UWP/Assets/Square44x44Logo.targetsize-24_altform-unplated.png -------------------------------------------------------------------------------- /RolerFileToolkit/Samples/FileToolkitSample.UWP/Assets/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolerzhang/RolerFileToolkit/e0bd85306c633c94ca7575c7c1327d3a0975f7d5/RolerFileToolkit/Samples/FileToolkitSample.UWP/Assets/StoreLogo.png -------------------------------------------------------------------------------- /RolerFileToolkit/Samples/FileToolkitSample.UWP/Assets/Wide310x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolerzhang/RolerFileToolkit/e0bd85306c633c94ca7575c7c1327d3a0975f7d5/RolerFileToolkit/Samples/FileToolkitSample.UWP/Assets/Wide310x150Logo.scale-200.png -------------------------------------------------------------------------------- /RolerFileToolkit/Samples/FileToolkitSample.UWP/FileToolkitSample.UWP.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | x86 7 | {5D986DC5-9B28-486B-A713-3395BDED35A5} 8 | AppContainerExe 9 | Properties 10 | FileToolkitSample.UWP 11 | FileToolkitSample.UWP 12 | en-US 13 | UAP 14 | 10.0.17763.0 15 | 10.0.16299.0 16 | 14 17 | 512 18 | {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 19 | true 20 | FileToolkitSample.UWP_TemporaryKey.pfx 21 | 22 | 23 | true 24 | bin\x86\Debug\ 25 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP 26 | ;2008 27 | full 28 | x86 29 | false 30 | prompt 31 | true 32 | 33 | 34 | bin\x86\Release\ 35 | TRACE;NETFX_CORE;WINDOWS_UWP 36 | true 37 | ;2008 38 | pdbonly 39 | x86 40 | false 41 | prompt 42 | true 43 | true 44 | 45 | 46 | true 47 | bin\ARM\Debug\ 48 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP 49 | ;2008 50 | full 51 | ARM 52 | false 53 | prompt 54 | true 55 | 56 | 57 | bin\ARM\Release\ 58 | TRACE;NETFX_CORE;WINDOWS_UWP 59 | true 60 | ;2008 61 | pdbonly 62 | ARM 63 | false 64 | prompt 65 | true 66 | true 67 | 68 | 69 | true 70 | bin\ARM64\Debug\ 71 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP 72 | ;2008 73 | full 74 | ARM64 75 | false 76 | prompt 77 | true 78 | true 79 | 80 | 81 | bin\ARM64\Release\ 82 | TRACE;NETFX_CORE;WINDOWS_UWP 83 | true 84 | ;2008 85 | pdbonly 86 | ARM64 87 | false 88 | prompt 89 | true 90 | true 91 | 92 | 93 | true 94 | bin\x64\Debug\ 95 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP 96 | ;2008 97 | full 98 | x64 99 | false 100 | prompt 101 | true 102 | 103 | 104 | bin\x64\Release\ 105 | TRACE;NETFX_CORE;WINDOWS_UWP 106 | true 107 | ;2008 108 | pdbonly 109 | x64 110 | false 111 | prompt 112 | true 113 | true 114 | 115 | 116 | PackageReference 117 | 118 | 119 | 120 | App.xaml 121 | 122 | 123 | MainPage.xaml 124 | 125 | 126 | 127 | 128 | 129 | Designer 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | MSBuild:Compile 146 | Designer 147 | 148 | 149 | MSBuild:Compile 150 | Designer 151 | 152 | 153 | 154 | 155 | 6.2.3 156 | 157 | 158 | 159 | 160 | {c67b8f9f-1677-4ff2-bb7b-8cff9ce3b7b6} 161 | Roler.Toolkit.File.Epub 162 | 163 | 164 | 165 | 14.0 166 | 167 | 168 | 175 | -------------------------------------------------------------------------------- /RolerFileToolkit/Samples/FileToolkitSample.UWP/MainPage.xaml: -------------------------------------------------------------------------------- 1 |  10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 32 | 33 |