I need to unzip a .epub file in swift to read the data myself entirely. I know how to parse the output of an ePub if I can get it (I've written a working example in python), but SSZipArchive apparently will not unzip .epubs. It does, however, works fine on a dummy .zip file; only .epub is a problem. So far as I can tell, there has been no question asking how to actually do this by hand on S.O. beyond simply pointing people to projects that do it for you in objective-c with lots of overhead (which I don't understand or need) that defeats the purpose of what I need to do. Below is my current attempt. Note that the epub in question can be found at the following link (project gutenberg) http://www.gutenberg.org/ebooks/158.epub.noimages and that when I run this the print statement emits: "true, true, true, false" (that is, the files and paths all exist, but won't unzip):
import Foundation
class EpubExtractor: NSObject, SSZipArchiveDelegate {
init(fileName: String) {
fName = fileName
}
func getEpubInfo() {
var paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
let documentsDir = paths[0]
let zipPath = documentsDir.stringByAppendingString("/MyZipFiles") // My folder name in document directory
let fileManager = NSFileManager.defaultManager()
let success1 = fileManager.fileExistsAtPath(zipPath) as Bool
if success1 == false {
print("no directory")
do {
try! fileManager.createDirectoryAtPath(zipPath, withIntermediateDirectories: true, attributes: nil)
}
}
let archivePath = zipPath.stringByAppendingString("/emma.epub") // Sample folder is going to zip with name Demo.zip
let success2 = fileManager.fileExistsAtPath(archivePath) as Bool
let destPath = zipPath.stringByAppendingString("/Hello")
let success3 = fileManager.fileExistsAtPath(destPath) as Bool
let worked = SSZipArchive.unzipFileAtPath(archivePath, toDestination: destPath, delegate:self)
print(success1, success2, success3, worked)
}
}
EDIT
Below is proof of concept code written in python in which I CAN get the very same epub to be recognized as a zip file and read its container content:
import zipfile
dir = "sampleData/epubs/"
fileName = "emma.epub"
print zipfile.is_zipfile(dir+fileName) # Check whether file is zip (this returns true, though in swift it fails)
zip = zipfile.ZipFile(dir+fileName)
txt = zip.read('META-INF/container.xml') # Print contents of container (this is what I need swift to be able to do)
print txt # This successfully prints the container content text
I figured it out after many many hours of reading. Turns out the solution is extremely simple if non-obvious.
The "fileName.epub" file needs to be renamed to "fileName.zip". That's it!
After that either SSZipArchive or Zip will unzip the file into its META-Inf, mimetype, and OEBPS files in a folder called "fileName" (at least as the default name).
Hope this helps anyone struggling with this. Of course if there is another way to do this please let me know in comments.
Related
Short version:
How to access an archived file in the bundle for unarchiving with unarchivedObject(ofClass:from:). The core issue is going from a path (string or URL) to Data which is required by the method.
Long version:
I have many plists containing SCNNode hierarchies stored as plists in bundle resources.
I was able to access them in Swift 3 with:
let fileName = MoleName.DNA_ideal_comps // retrieves String
let filePath = getFilePath(fileName)
guard let components = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as? [SCNNode] , components.count >= 4
else { // no value for this key, or not enough nodes
print("Couldn't find molecule (DNA_ideal_comps)")
return
} // [ atoms_A, bonds_A, atoms_B, bonds_B ]
scaleNode_2 = components[0] // DNA A
fixedNode_2 = components[1] // bonds
scaleNode_3 = components[2] // DNA B
fixedNode_3 = components[3] // bonds
// where filePath is:
func getFilePath(_ fileName: String) -> String {
if let path = Bundle.main.path(forResource: fileName, ofType: "plist") {
return path
}
return "path could not be found"
}
Here fileName is the name of the plist to retrieve, "DNA_ideal_comps" in this example. I created these independently of the active program due to the sheer volume of data; some of these plists contain over 30,000 items and there are about 90 total.
The above attempt to unarchive is deprecated and I've struggled to replace it. My best try so far:
guard let components = NSKeyedUnarchiver.unarchivedObject(ofClass: SCNNode.self, from: moleData), components.count >= 4
But this fails with Cannot convert value of type 'String' to expected argument type 'Data'. Unlike the method in Swift 3, there appears no way here to use the path of the object to retrieve it.
Is there a way to access these plists using this method? Is there another method more appropriate?
The core issue is going from a path (string or URL) to Data which is required by the method
Well, that's trivial enough; just call the Data initializer that reads from a file on disk. You would then decode the Data by calling NSKeyedUnarchiver.unarchivedObject(ofClass: SCNNode.self, from: theData).
https://developer.apple.com/documentation/foundation/data/3126626-init
https://developer.apple.com/documentation/foundation/nskeyedunarchiver/2983380-unarchivedobject
However, I'm confused as to why you're using NSKeyedUnarchiver to read a plist file. A plist file would be read using a PropertyListSerialization object.
https://developer.apple.com/documentation/foundation/propertylistserialization
I'm using the following code to save a 2D String array to a plist:
func saveFavourites(favouriteStops: [[String]]) {
let directories = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)
if let library = directories.first {
if let libraryUrl = URL(string: library) {
let favouritesUrl = libraryUrl.appendingPathComponent("favourites.plist")
// Write favourites to disk
let favsArray = favouriteStops as NSArray
print(favsArray)
favsArray.write(toFile: favouritesUrl.path, atomically: true)
}
}
}
The above snippet properly creates the .plist file (confirmed by looking at the simulator's filesystem in ~/Library/Developer/CoreServices). However, when I try reading it back to a NSArray with the following snippet, it results in nil:
let directories = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)
if let library = directories.first {
if let libraryUrl = URL(string: library) {
let favouritesUrl = libraryUrl.appendingPathComponent("favourites.plist")
// favsToLoad is nil
let favsToLoad = NSArray(contentsOf: favouritesUrl)
// Do stuff with favsToLoad, if it would load properly
}
}
You're doing two very basic things wrong.
First, never make a URL from a file path by saying URL(string); this is a file on disk, so you must use URL.fileURL.
Second, don't start with a file path at all! Obtain the directory as a URL right from the start.
(Also, though I do not know whether this is the source of the issue, do not read and write directly in the Library directory. Use the Documents directory, the Application Support directory, or similar.)
So, for example, I would write:
let fm = FileManager.default
let docsurl = try fm.url(for:.documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let favouritesurl = docsurl.appendingPathComponent("favourites.plist")
I see your problem. You misspelled "favorites". :)
But seriously...
Plists can only contain a very small set of "property list objects": (dictionaries, arrays, strings, numbers (integer and float), dates, binary data, and Boolean values).
If your array's "object graph" (the objects the array contains and any container objects inside the array recursively contain) contain anything other than the above types, the save will fail.
I don't honestly know what gets saved when it fails. Have you tried opening the plist file in a text editor and looking at it?
My guess is that something other than a string has snuck into your array, it's not one of the above types, and THAT'S why it's failing.
I try to create a PLIST-File with the NSFileManager and the method createFileAtPath. In the end, the file was created, it has the size of 0 Bytes, and i even can see the specific PLIST-Icon for that file in the Finder.
But when i want to open it (for example with Xcode) it says:The data couldn't be read because it isn't in the correct format.
I want to write to this file but when its not in the correct format i can't do this.
There is something wrong with the File-creation but i don't know what it is.
I hope you can help me with this.
Here is my code:
pListPath = NSURL(fileURLWithPath: reportsPath.path!).URLByAppendingPathComponent("myReports.plist", isDirectory: false)
let data: NSData = NSData()
var isDir: ObjCBool = false
if fileManager.fileExistsAtPath(pListPath.path!, isDirectory: &isDir)
{
print("File already exits")
}
else
{
let success = fileManager.createFileAtPath(pListPath.path!, contents: data, attributes: nil)
print("Was file created?: \(success)")
print("plistPath: \(pListPath)")
}
reports.path = .../UserDir/.../Documents/Reports
Any help is highly appreciated.
filemanager.createFileAtPath works absolutely correctly,
but you're creating an empty file by writing an empty NSData object to disk.
NSData objects are not implicitly serialized to a property list.
Either use the NSPropertyListSerialization class or – simpler - write an empty dictionary to disk.
let dictionary = NSDictionary()
let success = dictionary.writeToURL(pListPath, atomically: true)
print("Was file created?: \(success)")
print("plistPath: \(pListPath)")
PS: you don't need to create an URL from an URL
pListPath = reportsPath.URLByAppendingPathComponent("myReports.plist", isDirectory: false)
but I recommend to use more descriptive variable names to distinguish String paths and NSURL e.g. pListURL and reportsURL
My question, after I open PDF file with my app('register the document types that your application can open with iOS') I getting the files as NSCFString format)
Example:
let filemgr: NSFileManager = NSFileManager.defaultManager();
var paths: [AnyObject] = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true);
let documentsDirectory: String = paths[0] as! String
let inboxPath: String = documentsDirectory.stringByAppendingPathComponent("Inbox");
do
{
let dirFiles: [AnyObject] = try filemgr.contentsOfDirectoryAtPath(inboxPath);
}
catch{}
My question is from this step I want to gather all this documents to one document and also open it with UIWebView. from what i read i need to find a way to convert it to nsdata or even draw it as PDF, but i really dont know how to handle it.
Thank you for help
I am new to Swift and am using Xcode 6.
I am attempting to read data from the app's plist file, but it is not working.
The data.plist file is included in Xcode's Supporting Files group.
I am using the code below:
var dataList = NSDictionary(contentsOfURL:NSBundle.mainBundle().URLForResource("data", withExtension:"plist"))
however the NSURL:
NSBundle.mainBundle().URLForResource("data", withExtension:"plist")
always returns nil.
I don't know what is wrong.
Generally you would want to use this code to create your plist. This finds the the path to your plist and then moves it into the documents directory if it isn't already there. If you don't move it, you are not allowed to write to it, hence this chunk of code is vital. To fetch the information from the plist, use the second bit of code. Obviously if you have an array rather than a dictionary, you would have to alter it to deal with that.
var path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
path = path.stringByAppendingPathComponent("data.plist")
let fileManager = NSFileManager.defaultManager()
if !fileManager.fileExistsAtPath(path) {
let sourcePath = NSBundle.mainBundle().pathForResource("data", ofType: "plist")
fileManager.copyItemAtPath(sourcePath, toPath: path, error: nil)
}
.
let dict = NSMutableDictionary(contentsOfFile: path) as NSMutableDictionary