import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Date;
import java.util.ArrayList;
import java.text.SimpleDateFormat;
import java.text.ParsePosition;
import java.lang.NullPointerException;
import android.util.log;
class Page {
// Error levels specification
// STRICT: Fail on unexpected behaviour
// READONLY: Unexpected behaviour prevents writing
// IGNORE: Unexpected headings are rewritten unmodified, missing headers are added
public enum ErrorLevel {
STRICT, READONLY, IGNORE
}
private String name; // Full page name
private String path; // File path
private String title; // Page title (file name)
private boolean isNewPage = false; // Flag for data loading
private ErrorLevel policy = ErrorLevel.STRICT
private boolean hasErrors = false;
// Page metadata variables
private String wikiVersion;
private Date creationDate;
// Store unknkown headers for forward compatibility
private ArrayList<String> extraHeaders;
// Zim format constants
public final String DEFAULT_VERSION = "zim 0.4";
public final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssXXX";
/** Create a new Page based on a Notebook and a page name.
* @param parent The notebook the page is in
* @param pageName The full page name
* @param policy The error handling/compatibility mode
* @throws IOException
*/
public Page(Notebook parent, String pageName, ErrorLevel policy) throws IOException {
this.name = pageName;
this.path = parent.getPageFilename(pageName);
if (pageName.contains(":")) {
int sepPos = pageName.lastIndexOf(":");
this.title = nameParts.substring(sepPos + 1, pageName.length());
} else {
this.title = pageName;
}
this.policy = policy;
this.loadData();
}
/** Create a new Page based on a Notebook and a page name.
* @param parent The notebook the page is in
* @param pageName The full page name
* @throws IOException
*/
public Page(Notebook parent, String pageName) throws IOException {
this.Page(parent, pageName, ErrorLevel.STRICT);
}
/** Create a new page at a file path.
* @param path The file path of the page
* @param policy The error handling mode to employ
* @throws IOException
*/
public Page(String path, ErrorLevel policy) throws IOException {
this.path = path;
String pageName;
if (! path.endsWith(".txt")) {
Log.w("Page name did not end in .txt");
}
int sep = path.lastIndexOf(File.separator);
int dot = path.lastIndexOf(".");
if (sep < 0) {
this.name = path.substring(0, dot);
} else {
this.name = path.substring(sep + 1, dot);
}
this.title = this.name;
this.policy = policy;
this.loadData();
}
/** Returns true if the page exists.
*/
public boolean exists() {
File page = new File(this.path);
return page.exists();
}
/** Returns the full name of the page.
*/
public String getName() {
return this.name;
}
/** Returns the title of the page.
*/
public String getTitle() {
return this.title;
}
/** Returns the wiki markup version as a string.
*/
public String getWikiVersion() {
return this.wikiVersion;
}
/** Sets the wiki version to be recorded in the page
* @param versionString The string representing the version, as it will be
* written to file.
*/
public void setWikiVersion(String versionString) {
this.wikiVersion = versionString;
}
/** Returns the page creation date
*/
public Date getCreationDate() {
return this.creationDate;
}
/** Sets the page creation date to be recorded on the page
* @param creationDate The date to record
*/
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}
/** Returns true if the page was created by this Page instance.
*/
public boolean isNewPage() {
return this.isNewPage;
}
/** Returns the contents of the page as a String (headers are excluded).
* Returns null if the page does not exist.
* @throws IOException
*/
public String read() throws IOException {
File pageFile = new File(this.path);
StringBuilder bodyText = new StringBuilder(2000);
try {
BufferedReader pageReader = new BufferedReader(new FileReader(path));
// Consume the headers (i.e. to the first blank line)
String nextLine = "nonblank_initial_value_for_loop";
while (! nextLine.equals("")) {
nextLine = pageReader.readLine();
}
// Read the rest of the body text and build it into a page
while (pageReader.ready()) {
bodyText.append(pageReader.readLine() + "\n");
}
} catch (FileNotFoundException e) {
Log.w("Tried to read nonexistant file at " + this.path);
return null;
}
return bodyText.toString();
}
/** Writes the contents of the page.
* @param bodyText The new page contents to be written
* @throws IOException
*/
public void write(String bodyText) throws IOException {
if (this.policy == ErrorLevel.READONLY && this.hasErrors == true) {
return;
}
// Header strings; perhaps these should be constants?
String contentTypeHeader = "Content-Type: text/x-zim-wiki\n";
String wikiFormatHeader = "Wiki-Format: ";
String creationDateHeader = "Creation-Date: ";
SimpleDateFormat formatter = new SimpleDateFormat(this.DATE_FORMAT);
// Append metadata values to header strings
wikiFormatHeader = wikiFormatHeader + this.wikiVersion + "\n";
creationDateHeader = creationDateHeader + formatter.format(this.creationDate) + "\n";
// Write the headers to disk
BufferedWriter pageFile = new BufferedWriter(new FileWriter(this.path));
pageFile.write(contentTypeHeader, 0, contentTypeHeader.length());
pageFile.write(wikiFormatHeader, 0, wikiFormatHeader.length());
pageFile.write(creationDateHeader, 0, creationDateHeader.length());
for (String extra : this.extraHeaders) {
pageFile.write(extra + "\n");
}
pageFile.write('\n');
// Write the body text to disk
pageFile.write(bodyText, 0, bodyText.length());
pageFile.close();
}
/** Loads the information from the headers.
* @throws IOException
*/
private void loadData() throws IOException {
this.extraHeaders = new ArrayList<String>();
String nextLine;
try {
BufferedReader pageFile = new BufferedReader(new FileReader(this.path));
nextLine = pageFile.readLine();
if (nextLine == null || ! nextLine.equals("Content-Type: text/x-zim-wiki")) {
throw new IOException("Not a zim page");
}
while (pageFile.ready()) {
nextLine = pageFile.readLine();
// The first empty line separates the headers from the contents; break when we find it
if (nextLine.equals("")) {
break;
}
// Split the line into header name and value
String[] tokens = nextLine.split(": ");
switch (tokens[0]) {
case "Wiki-Format":
this.wikiVersion = tokens[1];
break;
case "Creation-Date":
SimpleDateFormat formatter = new SimpleDateFormat(this.DATE_FORMAT);
this.creationDate = formatter.parse(tokens[1], new ParsePosition(0));
break;
default:
Log.w("Unrecognised header parsing " + this.path + ": " + tokens[0]);
this.extraHeaders.add(nextLine);
this.hasErrors = true;
break;
}
}
/* This block is here because without the version and creation date headers we get
* NullPointerExceptions elsewhere. Alternative strategies:
* Set Default Values - This has the disadvantage that it will add potentially
* redundant headers that might not be supported by other versions.
* Fail gracefully - This requires all related code to be rewritten to anticipate
* possible nulls.
*/
if (this.wikiVersion == null || this.creationDate == null) {
if (this.policy == ErrorLevel.STRICT) {
throw new IOException("Zim page is missing mandatory headers");
} else {
this.hasErrors = true;
if (this.wikiVersion == null) {
this.wikiVersion = this.DEFAULT_VERSION;
}
if (this.creationDate == null) {
this.creationDate = new Date();
}
}
}
} catch (FileNotFoundException e) {
// This means the page is new, so let's set some defaults
this.wikiVersion = this.DEFAULT_VERSION;
this.creationDate = new Date();
this.isNewPage = true;
} catch (NullPointerException e) {
Log.e("Unable to parse date for page at " + this.path);
Log.e("This shouldn't happen unless there is no date value.");
}
}
}