You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@corinthia.apache.org by pm...@apache.org on 2015/08/19 17:38:21 UTC

[4/4] incubator-corinthia git commit: Add Objective C framework code from UX Write

Add Objective C framework code from UX Write

These files come from the portion of the UX Write editor that works on
both Apple platforms (that is, it can run on either OS X or iOS). It's
in the repository for illustrative purposes only, to assist with the
creation of the framework for the Corinthia editor UI. The code does not
compile independently in its present form.


Project: http://git-wip-us.apache.org/repos/asf/incubator-corinthia/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-corinthia/commit/9f851a34
Tree: http://git-wip-us.apache.org/repos/asf/incubator-corinthia/tree/9f851a34
Diff: http://git-wip-us.apache.org/repos/asf/incubator-corinthia/diff/9f851a34

Branch: refs/heads/master
Commit: 9f851a34268fc20fe708626dff9795f4526edee9
Parents: 5202fbf
Author: Peter Kelly <pe...@uxproductivity.com>
Authored: Wed Aug 19 22:37:11 2015 +0700
Committer: Peter Kelly <pe...@uxproductivity.com>
Committed: Wed Aug 19 22:37:11 2015 +0700

----------------------------------------------------------------------
 experiments/objcFramework/DocFormats.h          |   79 +
 experiments/objcFramework/EDDocumentSetup.h     |   32 +
 experiments/objcFramework/EDDocumentSetup.m     |  225 +++
 experiments/objcFramework/EDEditor.h            |  112 ++
 experiments/objcFramework/EDEditor.m            |  413 ++++
 experiments/objcFramework/EDFileFormat.h        |   65 +
 experiments/objcFramework/EDFileFormat.m        |  968 ++++++++++
 experiments/objcFramework/EDFindReplace.h       |   48 +
 experiments/objcFramework/EDFindReplace.m       |   82 +
 experiments/objcFramework/EDGeometry.h          |   80 +
 experiments/objcFramework/EDGeometry.m          |  119 ++
 experiments/objcFramework/EDHTMLTidy.h          |   42 +
 experiments/objcFramework/EDHTMLTidy.m          |   58 +
 experiments/objcFramework/EDJSInterface.h       |  365 ++++
 experiments/objcFramework/EDJSInterface.m       | 1775 ++++++++++++++++++
 experiments/objcFramework/EDObservation.h       |   30 +
 experiments/objcFramework/EDObservation.m       |   56 +
 experiments/objcFramework/EDOutline.h           |  132 ++
 experiments/objcFramework/EDOutline.m           |  270 +++
 experiments/objcFramework/EDSaveOperation.h     |   44 +
 experiments/objcFramework/EDSaveOperation.m     |  166 ++
 experiments/objcFramework/EDScan.h              |   64 +
 experiments/objcFramework/EDScan.m              |  138 ++
 experiments/objcFramework/EDScanResults.h       |   73 +
 experiments/objcFramework/EDScanResults.m       |  109 ++
 .../objcFramework/EDSelectionFormatting.h       |   55 +
 .../objcFramework/EDSelectionFormatting.m       |   73 +
 experiments/objcFramework/EDStyle.h             |   59 +
 experiments/objcFramework/EDStyle.m             |  150 ++
 experiments/objcFramework/EDTiming.h            |   80 +
 experiments/objcFramework/EDTiming.m            |  271 +++
 experiments/objcFramework/EDUtil.h              |   41 +
 experiments/objcFramework/EDUtil.m              |  325 ++++
 experiments/objcFramework/EDWordCount.h         |   42 +
 experiments/objcFramework/EDWordCount.m         |   57 +
 experiments/objcFramework/Editor.h              |   40 +
 36 files changed, 6738 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/DocFormats.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/DocFormats.h b/experiments/objcFramework/DocFormats.h
new file mode 100644
index 0000000..ebfc49c
--- /dev/null
+++ b/experiments/objcFramework/DocFormats.h
@@ -0,0 +1,79 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+/** \mainpage
+
+ Documentation for the DocFormats library
+
+ # CSS
+
+ \see CSSProperties.h
+ \see CSSSheet.h
+ \see CSSStyle.h
+
+ # XML
+
+ \see DFDOM.h
+
+ # Word
+
+ \see WordPPr.h
+
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "DFPlatform.h"
+#import <DocFormats/DocFormats.h>
+
+//#import <DocFormats/DFXMLNames.h>
+#import "DFXMLNames.h"
+//#import <DocFormats/DFXMLNamespaces.h>
+#import "DFXMLNamespaces.h"
+
+//#import <DocFormats/DFDOM.h>
+//#import <DocFormats/DFXML.h>
+#import "DFDOM.h"
+#import "DFXML.h"
+#import <DocFormats/DFXMLForward.h>
+
+#import "CSS.h"
+#import "CSSClassNames.h"
+#import "CSSLength.h"
+#import "CSSProperties.h"
+#import "CSSSelector.h"
+#import "CSSStyle.h"
+#import "CSSSheet.h"
+
+#import "DFHTML.h"
+#import "DFHTMLNormalization.h"
+
+#import "DFString.h"
+//#import "DFHashTable.h"
+#import "DFCallback.h"
+#import "DFFilesystem.h"
+#import <DocFormats/DFError.h>
+#import "DFZipFile.h"
+#import "Word.h"
+#import "HTMLToLaTeX.h"

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDDocumentSetup.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDDocumentSetup.h b/experiments/objcFramework/EDDocumentSetup.h
new file mode 100644
index 0000000..e05c2bf
--- /dev/null
+++ b/experiments/objcFramework/EDDocumentSetup.h
@@ -0,0 +1,32 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import <Foundation/Foundation.h>
+#import <Editor/DocFormats.h>
+
+void CSSSheetUseCSSNumbering(CSSSheet *sheet);
+CSSSheet *CSSSheetCreateDefault(void);
+DFDocument *DFHTMLCreateDefault(CSSSheet *styleSheet);
+int DFHTMLCreateDefaultFile(const char *filename, DFError **error);
+int DFWordCreateDefault(const char *filename, DFError **error);

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDDocumentSetup.m
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDDocumentSetup.m b/experiments/objcFramework/EDDocumentSetup.m
new file mode 100644
index 0000000..1ee2283
--- /dev/null
+++ b/experiments/objcFramework/EDDocumentSetup.m
@@ -0,0 +1,225 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import "EDDocumentSetup.h"
+#import "EDUtil.h"
+#import <FileClient/FileClient.h>
+
+#define L10NDocStringPreface                         NSLocalizedString(@"DocStringPreface",NULL)
+#define L10NDocStringRef                             NSLocalizedString(@"DocStringRef",NULL)
+#define L10NDocStringAbstract                        NSLocalizedString(@"DocStringAbstract",NULL)
+#define L10NDocStringBib                             NSLocalizedString(@"DocStringBib",NULL)
+#define L10NDocStringChapter                         NSLocalizedString(@"DocStringChapter",NULL)
+#define L10NDocStringAppendix                        NSLocalizedString(@"DocStringAppendix",NULL)
+#define L10NDocStringContents                        NSLocalizedString(@"DocStringContents",NULL)
+#define L10NDocStringListFigure                      NSLocalizedString(@"DocStringListFigure",NULL)
+#define L10NDocStringListTable                       NSLocalizedString(@"DocStringListTable",NULL)
+#define L10NDocStringIndex                           NSLocalizedString(@"DocStringIndex",NULL)
+#define L10NDocStringFigure                          NSLocalizedString(@"DocStringFigure",NULL)
+#define L10NDocStringTable                           NSLocalizedString(@"DocStringTable",NULL)
+#define L10NDocStringPart                            NSLocalizedString(@"DocStringPart",NULL)
+#define L10NDocStringEncl                            NSLocalizedString(@"DocStringEncl",NULL)
+#define L10NDocStringCc                              NSLocalizedString(@"DocStringCc",NULL)
+#define L10NDocStringHeadto                          NSLocalizedString(@"DocStringHeadto",NULL)
+#define L10NDocStringPage                            NSLocalizedString(@"DocStringPage",NULL)
+#define L10NDocStringSee                             NSLocalizedString(@"DocStringSee",NULL)
+#define L10NDocStringAlso                            NSLocalizedString(@"DocStringAlso",NULL)
+#define L10NDocStringProof                           NSLocalizedString(@"DocStringProof",NULL)
+#define L10NDocStringGlossary                        NSLocalizedString(@"DocStringGlossary",NULL)
+
+// FIXME: This won't work now for Word documents, where styles always have class names
+// FIXME: Not covered by tests
+void CSSSheetUseCSSNumbering(CSSSheet *sheet)
+{
+    CSSStyle *style;
+
+    style = CSSSheetLookupElement(sheet,"body",NULL,1,0);
+    CSSPut(CSSStyleRule(style),"counter-reset","h1 h2 h3 h4 h5 h6 figure table");
+
+    // Figure caption
+    style = CSSSheetLookupElement(sheet,"figcaption",NULL,1,1);
+    CSSPut(CSSStyleRule(style),"counter-increment","figure");
+    NSString *content = [NSString stringWithFormat: @"\"%@ \" counter(figure) \": \"", L10NDocStringFigure];
+    CSSPut(CSSStyleBefore(style),"content",content.UTF8String);
+
+    style = CSSSheetLookupElement(sheet,"figcaption","Unnumbered",1,1);
+    CSSPut(CSSStyleRule(style),"counter-increment","figure 0");
+    CSSPut(CSSStyleBefore(style),"content","\"\"");
+
+    // Table caption
+    style = CSSSheetLookupElement(sheet,"caption",NULL,1,1);
+    CSSPut(CSSStyleRule(style),"caption-side","bottom");
+    CSSPut(CSSStyleRule(style),"counter-increment","table");
+    content = [NSString stringWithFormat: @"\"%@ \" counter(table) \": \"", L10NDocStringTable];
+    CSSPut(CSSStyleBefore(style),"content",content.UTF8String);
+
+    style = CSSSheetLookupElement(sheet,"caption","Unnumbered",1,1);
+    CSSPut(CSSStyleRule(style),"counter-increment","table 0");
+    CSSPut(CSSStyleBefore(style),"content","\"\"");
+
+    // Table of contents
+    style = CSSSheetLookupElement(sheet,"nav","tableofcontents",1,1);
+    if (CSSGet(CSSStyleBefore(style),"content") == NULL) {
+        content = [NSString stringWithFormat: @"\"%@\"", L10NDocStringContents];
+        CSSPut(CSSStyleBefore(style),"content",content.UTF8String);
+        CSSPut(CSSStyleBefore(style),"font-size","2em");
+        CSSSetBold(CSSStyleBefore(style),1);
+        CSSPut(CSSStyleBefore(style),"margin-top",".67em");
+        CSSPut(CSSStyleBefore(style),"margin-bottom",".67em");
+        CSSPut(CSSStyleBefore(style),"display","block");
+    }
+
+    // List of figures
+    style = CSSSheetLookupElement(sheet,"nav","listoffigures",1,1);
+    if (CSSGet(CSSStyleBefore(style),"content") == NULL) {
+        content = [NSString stringWithFormat: @"\"%@\"", L10NDocStringListFigure];
+        CSSPut(CSSStyleBefore(style),"content",content.UTF8String);
+        CSSPut(CSSStyleBefore(style),"font-size","2em");
+        CSSSetBold(CSSStyleBefore(style),1);
+        CSSPut(CSSStyleBefore(style),"margin-top",".67em");
+        CSSPut(CSSStyleBefore(style),"margin-bottom",".67em");
+        CSSPut(CSSStyleBefore(style),"display","block");
+    }
+
+    // List of tables
+    style = CSSSheetLookupElement(sheet,"nav","listoftables",1,1);
+    if (CSSGet(CSSStyleBefore(style),"content") == NULL) {
+        content = [NSString stringWithFormat: @"\"%@\"", L10NDocStringListTable];
+        CSSPut(CSSStyleBefore(style),"content",content.UTF8String);
+        CSSPut(CSSStyleBefore(style),"font-size","2em");
+        CSSSetBold(CSSStyleBefore(style),1);
+        CSSPut(CSSStyleBefore(style),"margin-top",".67em");
+        CSSPut(CSSStyleBefore(style),"margin-bottom",".67em");
+        CSSPut(CSSStyleBefore(style),"display","block");
+    }
+}
+
+CSSSheet *CSSSheetCreateDefault(void)
+{
+    CSSSheet *styleSheet = CSSSheetNew();
+    CSSSheetUseCSSNumbering(styleSheet);
+
+    CSSStyle *title = CSSSheetLookupElement(styleSheet,"p","Title",1,0);
+    CSSPut(CSSStyleRule(title),"font-size","24pt");
+    CSSPut(CSSStyleRule(title),"text-align","center");
+
+    CSSStyle *author = CSSSheetLookupElement(styleSheet,"p","Author",1,0);
+    CSSPut(CSSStyleRule(author),"font-size","18pt");
+    CSSPut(CSSStyleRule(author),"text-align","center");
+
+    CSSStyle *abstract = CSSSheetLookupElement(styleSheet,"p","Abstract",1,0);
+    CSSPut(CSSStyleRule(abstract),"font-style","italic");
+    CSSPut(CSSStyleRule(abstract),"margin-left","20%");
+    CSSPut(CSSStyleRule(abstract),"margin-right","20%");
+
+    CSSStyle *body = CSSSheetLookupElement(styleSheet,"body",NULL,1,0);
+    CSSPut(CSSStyleRule(body),"margin-left","10%");
+    CSSPut(CSSStyleRule(body),"margin-right","10%");
+    CSSPut(CSSStyleRule(body),"margin-top","10%");
+    CSSPut(CSSStyleRule(body),"margin-bottom","10%");
+    CSSPut(CSSStyleRule(body),"text-align","justify");
+
+    CSSStyle *page = CSSSheetLookupElement(styleSheet,"@page",NULL,1,0);
+    CSSPut(CSSStyleRule(page),"size","a4 portrait");
+    return styleSheet;
+}
+
+DFDocument *DFHTMLCreateDefault(CSSSheet *styleSheet)
+{
+    DFDocument *doc = DFDocumentNewWithRoot(HTML_HTML);
+    DFNode *head = DFCreateChildElement(doc->root,HTML_HEAD);
+    DFNode *body = DFCreateChildElement(doc->root,HTML_BODY);
+    DFNode *meta = DFCreateChildElement(head,HTML_META);
+    DFSetAttribute(meta,HTML_CHARSET,"utf-8");
+    DFNode *style = DFCreateChildElement(head,HTML_STYLE);
+    char *cssText = CSSSheetCopyCSSText(styleSheet);
+    DFCreateChildTextNode(style,cssText);
+    free(cssText);
+    DFNode *p = DFCreateChildElement(body,HTML_P);
+    DFCreateChildElement(p,HTML_BR);
+    return doc;
+}
+
+int DFHTMLCreateDefaultFile(const char *filename, DFError **error)
+{
+    CSSSheet *styleSheet = CSSSheetCreateDefault();
+    DFDocument *doc = DFHTMLCreateDefault(styleSheet);
+    int ok = DFSerializeXMLFile(doc,0,1,filename,error);
+    DFDocumentRelease(doc);
+    CSSSheetRelease(styleSheet);
+    return ok;
+}
+
+static void addDefaultStyles(CSSSheet *styleSheet)
+{
+    // Explicitly add default heading formatting properties
+    for (int i = 1; i <= 6; i++) {
+        char *elementName = DFFormatString("h%d",i);
+        CSSStyle *style = CSSSheetLookupElement(styleSheet,elementName,NULL,1,0);
+        CSSStyleAddDefaultHTMLProperties(style);
+        free(elementName);
+    }
+
+    // Word uses the same "Caption" style for both figures and tables. So we only need to set
+    // caption. Note that in HTML the alignment is (I think) implicit.
+    CSSStyle *caption = CSSSheetLookupElement(styleSheet,"caption",NULL,1,0);
+    CSSPut(CSSStyleRule(caption),"text-align","center");
+    caption->latent = 0;
+}
+
+int DFWordCreateDefault(const char *filename, DFError **error)
+{
+    int ok = 0;
+    CSSSheet *styleSheet = NULL;
+    DFDocument *htmlDoc = NULL;
+    DFStorage *abstractStorage = NULL;
+    DFConcreteDocument *concreteDoc = NULL;
+    DFAbstractDocument *abstractDoc = NULL;
+
+    styleSheet = CSSSheetCreateDefault();
+    addDefaultStyles(styleSheet);
+
+    concreteDoc = DFConcreteDocumentCreateFile(filename,error);
+    if (concreteDoc == NULL)
+        goto end;
+
+    abstractStorage = DFStorageNewMemory(DFFileFormatHTML);
+    abstractDoc = DFAbstractDocumentNew(abstractStorage);
+
+    htmlDoc = DFHTMLCreateDefault(styleSheet);
+    DFAbstractDocumentSetHTML(abstractDoc,htmlDoc);
+
+    if (!DFCreate(concreteDoc,abstractDoc,error))
+        goto end;
+
+    ok = 1;
+
+end:
+    CSSSheetRelease(styleSheet);
+    DFDocumentRelease(htmlDoc);
+    DFStorageRelease(abstractStorage);
+    DFConcreteDocumentRelease(concreteDoc);
+    DFAbstractDocumentRelease(abstractDoc);
+    return ok;
+}

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDEditor.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDEditor.h b/experiments/objcFramework/EDEditor.h
new file mode 100644
index 0000000..e3f53a7
--- /dev/null
+++ b/experiments/objcFramework/EDEditor.h
@@ -0,0 +1,112 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import <Foundation/Foundation.h>
+#import <Editor/DocFormats.h>
+#import "EDGeometry.h"
+#import "EDSaveOperation.h"
+
+@class EDFileFormat;
+@class JSInterface;
+@class EDOutline;
+@class EDSelectionFormatting;
+@class EDTimingInfo;
+@class EDSaveOperation;
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                        EDSystemDelegate                                        //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@protocol EDSystemDelegate
+
+- (void)runInBackground:(void (^)(void))block completion:(void (^)(void))completion;
+- (void)runCommandInBackground:(BOOL (^)(NSError **commandError))command
+                    completion:(void (^)(NSError *completionError))completion;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                        EDEditorDelegate                                        //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@protocol EDEditorDelegate
+
+@property (nonatomic) BOOL editorIsSaving;
+@property (nonatomic, readonly) NSString *editorPath;
+
+- (void)editorDidUpdateCSS;
+- (void)editorDidUpdateOutline;
+- (void)editorDidSaveFile;
+- (void)editorShowResizeHandles:(EDItemGeometry *)geometry vertical:(BOOL)vertical;
+- (void)editorHideResizeHandles;
+
+- (NSString *)saveResource:(NSData *)data prefix:(NSString *)prefix extension:(NSString *)extension
+                     error:(NSError **)error;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                            EDEditor                                            //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDEditor : NSObject <EDResizeDelegate>
+
+@property (weak) NSObject<EDEditorDelegate> *delegate;
+@property (weak) NSObject<EDSystemDelegate> *system;
+@property (strong, readonly) EDFileFormat *fileFormat;
+@property (strong, readonly) NSString *generator;
+@property (strong, readonly) NSString *tempDir;
+@property (strong, readwrite) JSInterface *js;
+@property (assign, readonly) BOOL jsInitOk;
+@property (strong, readonly) EDOutline *outline;
+@property (strong, readwrite, nonatomic) EDSelectionFormatting *formatting;
+@property (copy, nonatomic) NSString *paragraphStyleId;
+@property (assign, readonly) CSSSheet *styleSheet;
+@property (strong, readonly) EDTimingInfo *loadTiming;
+@property (strong, readonly) EDTimingInfo *saveTiming;
+@property (strong) EDSaveOperation *activeSave;
+@property (strong) EDSaveOperation *pendingSave;
+@property (strong, readonly) NSString *origGenerator;
+@property (copy, nonatomic) NSString *locale;
+
+- (EDEditor *)initWithFileFormat:(EDFileFormat *)fileFormat generator:(NSString *)generator tempDir:(NSString *)tempDir;
+- (void)updateFormatting;
+- (void)updateCSS;
+- (void)retrieveStyles;
+- (void)makeStyleNonLatent:(const char *)ident;
+- (void)setOutlineDirty;
+- (void)loadAndInitSucceeded;
+- (void)dumpHTML;
+- (void)saveTo:(NSString *)path completion:(EDSaveCompletion)completion;
+- (void)debugSaveStatus;
+- (void)undo;
+- (void)redo;
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDEditor.m
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDEditor.m b/experiments/objcFramework/EDEditor.m
new file mode 100644
index 0000000..d653952
--- /dev/null
+++ b/experiments/objcFramework/EDEditor.m
@@ -0,0 +1,413 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import "EDEditor.h"
+#import "EDOutline.h"
+#import "EDSelectionFormatting.h"
+#import "EDTiming.h"
+#import "EDObservation.h"
+#import "EDJSInterface.h"
+#import "EDSaveOperation.h"
+#import "EDFileFormat.h"
+#import "EDHTMLTidy.h"
+#import "EDUtil.h"
+#import "EDDocumentSetup.h"
+#import <FileClient/FCError.h>
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                        EDEditorDelegate                                        //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation EDEditor
+{
+    BOOL _outlineDirty;
+    int _programmaticChange;
+    BOOL _formattingDirty;
+    NSDictionary *_lastReceivedFormatting;
+}
+
+static void formattingPropertiesChanged(void *ctx, void *object, void *data)
+{
+    EDEditor *editor = (__bridge EDEditor *)ctx;
+    [editor detectedFormattingChange];
+}
+
+- (EDEditor *)initWithFileFormat:(EDFileFormat *)fileFormat generator:(NSString *)generator tempDir:(NSString *)tempDir
+{
+    if (!(self = [super init]))
+        return nil;
+
+    _fileFormat = fileFormat;
+    _fileFormat.editor = self;
+    _generator = [generator copy];
+    _tempDir = [tempDir copy];
+    _outline = [[EDOutline alloc] init];
+    _styleSheet = CSSSheetNew();
+    _loadTiming = [[EDTimingInfo alloc] init];
+    _saveTiming = [[EDTimingInfo alloc] init];
+
+    self.formatting = [[EDSelectionFormatting alloc] init];
+
+    return self;
+}
+
+- (void)dealloc
+{
+    self.formatting = nil;
+    CSSSheetRelease(_styleSheet);
+}
+
+- (void)setFormatting:(EDSelectionFormatting *)newFormatting
+{
+    if (_formatting != nil) {
+        removeObserverForAllProperties(_formatting,self);
+        DFCallbackRemove(&_formatting.cssProperties->changeCallbacks,formattingPropertiesChanged,(__bridge void *)self);
+    }
+
+    _formatting = newFormatting;
+
+    if (_formatting != nil) {
+        addObserverForAllProperties(_formatting,self,0,nil);
+        DFCallbackAdd(&_formatting.cssProperties->changeCallbacks,formattingPropertiesChanged,(__bridge void *)self);
+    }
+}
+
+- (void)setParagraphStyleId:(NSString *)paragraphStyleId
+{
+    _paragraphStyleId = [paragraphStyleId copy];
+    [self detectedFormattingChange];
+}
+
+- (void)setLocale:(NSString *)newLocale
+{
+    _locale = [newLocale copy];
+    [_js.main setLanguage: _locale];
+}
+
+- (void)updateResizeHandles
+{
+    BOOL vertical = NO;
+    EDItemGeometry *geometry = nil;
+    if (_formatting.inFigure) {
+        NSString *itemId = [_js.figures getSelectedFigureId];
+        if (itemId != nil) {
+            NSDictionary *dict = [_js.figures getGeometry: itemId];
+            if (dict != nil) {
+                geometry = [EDItemGeometry fromDict: dict];
+                vertical = YES;
+            }
+        }
+    }
+    else if (_formatting.inTable) {
+        NSString *itemId = [_js.tables getSelectedTableId];
+        if (itemId != nil) {
+            NSDictionary *dict = [_js.tables getGeometry: itemId];
+            if (dict != nil) {
+                geometry = [EDItemGeometry fromDict: dict];
+                vertical = NO;
+            }
+        }
+    }
+
+    if (geometry != nil)
+        [_delegate editorShowResizeHandles: geometry vertical: vertical];
+    else
+        [_delegate editorHideResizeHandles];
+}
+
+- (void)updateFormatting
+{
+    if (!_js.jsInitialised)
+        return;
+
+    _programmaticChange++;
+
+    NSMutableDictionary *modified = [[_js.formatting getFormatting] mutableCopy];
+    if (modified == nil)
+        modified = [NSMutableDictionary dictionaryWithCapacity: 0]; // in case of JS exception
+
+    NSString *newStyle = [modified objectForKey: @"-uxwrite-paragraph-style"];
+    [modified removeObjectForKey: @"-uxwrite-paragraph-style"];
+    if ((newStyle != nil) && (newStyle.length == 0))
+        newStyle = @"p";
+
+    _lastReceivedFormatting = modified;
+
+    // FIXME: Receive paragraph style id from javascript as an (element,class) tuple, not a string
+    self.paragraphStyleId = newStyle;
+
+    self.formatting = [[EDSelectionFormatting alloc] initWithProperties: modified];
+    [self updateResizeHandles];
+
+    _programmaticChange--;
+}
+
+- (void)applyFormattingChanges
+{
+    if (!_formattingDirty)
+        return;
+    _formattingDirty = false;
+
+    if (_js.jsInitialised) {
+        // If some properties that were previously set have been removed (e.g. font-weight in the
+        // case where bold has been turned off), set these as null entries in the dictionary we
+        // pass to applyFormattingChanges(). Otherwise, they won't be considered as properties
+        // that need changing, and will retain their previous values (e.g. font-weight will
+        // remain set if there's no explicit instruction to remove it).
+        DFHashTable *collapsed = CSSCollapseProperties(_formatting.cssProperties);
+        NSMutableDictionary *changes = [NSDictionaryFromHashTable(collapsed) mutableCopy];
+        DFHashTableRelease(collapsed);
+        if (_lastReceivedFormatting != nil) {
+            for (NSString *key in _lastReceivedFormatting) {
+                if ([changes objectForKey: key] == nil)
+                    [changes setObject: [NSNull null] forKey: key];
+            }
+        }
+        // FIXME: Send paragraph style id to javascript as an (element,class) tuple, not a string
+        [_js.formatting applyFormattingChangesStyle: _paragraphStyleId properties: changes];
+    }
+    [self updateFormatting];
+}
+
+- (void)detectedFormattingChange
+{
+    if (_programmaticChange == 0) {
+        _formattingDirty = YES;
+        // Invoke applyFormattingChanges after the current iteration of the event loop has finished.
+        // This way, if multiple properties of the formatting object are changed at the same time,
+        // we will only call the javascript applyFormatting() method once after all the changes
+        // have been made.
+        [self performSelector: @selector(applyFormattingChanges) withObject: nil afterDelay: 0];
+    }
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
+                        change:(NSDictionary *)change context:(void *)context
+{
+    if (object == _formatting)
+        [self detectedFormattingChange];
+}
+
+- (void)updateCSS
+{
+    [_delegate editorDidUpdateCSS];
+}
+
+- (void)retrieveStyles
+{
+    NSString *cssText = [_js.styles getCSSText];
+    CSSSheetUpdateFromCSSText(_styleSheet,cssText.UTF8String);
+    [_fileFormat setupInitialStyles];
+    CSSSheetUseCSSNumbering(_styleSheet);
+
+    // If any of the built-in styles (e.g. figcaption) are used in the document, but not explicitly
+    // mentioned in the CSS stylesheet text, then mark them as non-latent. This will cause their
+    // definitions to be explicitly included in the stylesheet text.
+    // FIXME: Get these sent from javascript as (element,class) pairs
+    NSDictionary *usedStyles = [_js.outline findUsedStyles];
+    if (_js.error == nil) {
+        for (NSString *rawSelector in usedStyles.allKeys) {
+            CSSStyle *style = CSSSheetLookupSelector(_styleSheet,rawSelector.UTF8String,NO,NO);
+            if ((style != nil) && (style->latent))
+                style->latent = NO;
+        }
+    }
+
+    // updateCSS causes the documentModified flag to be set, because normally it does actually
+    // represent an actual change to the document. But since this function is only called at load
+    // time, all we're actually doing here is making sure the HTML document's stylesheet is
+    // consistent with what we maintain on the Objective C side. So in this particular case we
+    // don't want it to count as a modification - otherwise simp,ly opening and closing the document
+    // would cause it to be saved, resulting in an unnecessary upload to the server
+    BOOL oldModified = _js.documentModified;
+    [self updateCSS];
+    _js.documentModified = oldModified;
+}
+
+- (void)makeStyleNonLatent:(const char *)ident
+{
+    // We must set the add parameter to YES here; otherwise, the latent parameter will be ignored
+    CSSSheetLookupSelector(_styleSheet,ident,YES,NO);
+    [self updateCSS];
+}
+
+- (void)updateOutline
+{
+    if (!_js.jsInitialised)
+        return;
+    _outlineDirty = NO;
+    _outline.json = [_js.outline getOutline];
+    [_delegate editorDidUpdateOutline];
+}
+
+- (void)setOutlineDirty
+{
+    if (!_outlineDirty) {
+        _outlineDirty = YES;
+        [self performSelector: @selector(updateOutline) withObject: nil afterDelay: 0];
+    }
+}
+
+- (void)loadAndInitSucceeded
+{
+    _jsInitOk = YES;
+    _js.jsInitialised = YES;
+    [_fileFormat finishLoad];
+    [_loadTiming addEntry: @"File format-specific initialisation"];
+    _origGenerator = [_js.main setGenerator: _generator];
+    [self retrieveStyles];
+    [self updateOutline];
+
+    NSString *localeOrEmpty = [_js.main getLanguage];
+    if (localeOrEmpty.length > 0)
+        _locale = localeOrEmpty;
+    else
+        _locale = nil;
+
+    [self updateFormatting];
+}
+
+- (void)dumpHTML
+{
+    NSString *inputStr = [_js.main getHTML];
+    NSData *input = [inputStr dataUsingEncoding: NSUTF8StringEncoding];
+    [EDHTMLTidy tidy: input isXHTML: NO editor: self completion:^(NSData *output, NSError *error) {
+        if (error != nil) {
+            debug(@"HTMLTidy failed: %@\n",FCErrorDescription(error));
+        }
+        else {
+            NSString *outputStr = [[NSString alloc] initWithData: output encoding: NSUTF8StringEncoding];
+            debug(@"\n");
+            debug(@"------------------------------------- HTML -------------------------------------\n");
+            debug(@"%@",outputStr);
+            debug(@"--------------------------------------------------------------------------------\n");
+            debug(@"\n");
+        }
+    }];
+}
+
+- (void)saveTo:(NSString *)path completion:(EDSaveCompletion)completion
+{
+    if (!_js.jsInitialised)
+        return;
+
+    if (_activeSave == nil) {
+        _activeSave = [[EDSaveOperation alloc] initWithEditor: self path: path];
+        [_activeSave addCompletion: completion];
+        [_activeSave start];
+    }
+    else {
+        if (_pendingSave == nil)
+            _pendingSave = [[EDSaveOperation alloc] initWithEditor: self path: path];
+        [_pendingSave addCompletion: completion];
+    }
+    [self debugSaveStatus];
+}
+
+- (void)debugSaveStatus
+{
+    NSMutableString *str = [NSMutableString stringWithCapacity: 0];
+    [str appendFormat: @"Save status:"];
+
+    if (_activeSave != nil)
+        [str appendFormat: @" activeSave = %p (%d)", _activeSave, (int)_activeSave.completionCount];
+    else
+        [str appendFormat: @" activeSave = nil"];
+
+    if (_pendingSave != nil)
+        [str appendFormat: @", pendingSave = %p (%d)", _pendingSave, (int)_pendingSave.completionCount];
+    else
+        [str appendFormat: @", pendingSave = nil"];
+
+    debug(@"%@\n",str);
+}
+
+- (void)undo
+{
+    if (self.js.jsInitialised) {
+        [self.js.undoManager undo];
+        [self updateFormatting];
+    }
+}
+
+- (void)redo
+{
+    if (self.js.jsInitialised) {
+        [self.js.undoManager redo];
+        [self updateFormatting];
+    }
+}
+
+// ResizeDelegate methods
+
+- (void)resizedWidthPct:(CGFloat)widthPct
+{
+    NSString *widthStr = [NSString stringWithFormat: @"%d%%", (int)round(widthPct)];
+
+    if (_formatting.inFigure) {
+        NSString *itemId = [_js.figures getSelectedFigureId];
+        if (itemId == nil)
+            return;
+
+        NSDictionary *properties = [_js.figures getProperties: itemId];
+        if (properties == nil)
+            return;
+
+        NSString *src = [properties objectForKey: @"src"];
+        [_js.figures setProperties: itemId width: widthStr src: src];
+
+    }
+    else if (_formatting.inTable) {
+        NSString *itemId = [_js.tables getSelectedTableId];
+        if (itemId == nil)
+            return;
+
+        [_js.tables setProperties: itemId width: widthStr];
+    }
+
+    [self updateFormatting];
+}
+
+- (void)resizedColumns:(NSArray *)widthPcts
+{
+    if (_formatting.inTable) {
+        NSString *itemId = [_js.tables getSelectedTableId];
+        if (itemId == nil)
+            return;
+
+        NSMutableArray *rounded = [NSMutableArray arrayWithCapacity: widthPcts.count];
+        for (NSUInteger i = 0; i < widthPcts.count; i++) {
+            NSNumber *raw = [widthPcts objectAtIndex: i];
+            [rounded addObject: [NSNumber numberWithDouble: round(raw.doubleValue)]];
+        }
+
+        [_js.tables set: itemId colWidths: rounded];
+    }
+    
+    [self updateFormatting];
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDFileFormat.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDFileFormat.h b/experiments/objcFramework/EDFileFormat.h
new file mode 100644
index 0000000..d5d7417
--- /dev/null
+++ b/experiments/objcFramework/EDFileFormat.h
@@ -0,0 +1,65 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import <Foundation/Foundation.h>
+#import <Editor/DocFormats.h>
+
+typedef NSComparisonResult(^Comparator)(id,id);
+
+@class EDEditor;
+@protocol EDSystemDelegate;
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                          EDFileFormat                                          //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDFileFormat : NSObject
+
+@property (weak) EDEditor *editor;
+@property (copy, readonly) NSString *fileLocalPath;
+@property (copy, readonly) NSString *absoluteHTMLPath;
+@property (assign, readonly) BOOL supportsBareStyles;
+@property (copy, readonly) NSString *imagePath;
+@property (readonly) Comparator styleComparator;
+
+- (EDFileFormat *)init;
+- (NSString *)prepareForLoad:(NSError **)error;
+- (void)finishLoad;
+- (void)save:(NSString *)filename html:(NSString *)html completion:(void (^)(NSError *error))completion;
+- (void)setupInitialStyles;
+- (CSSStyle *)setupTableStyle;
+- (void)setupFigureStyle;
+- (void)setupOutlineStyle;
+- (NSString *)addImage:(NSData *)data extension:(NSString *)extension error:(NSError **)error;
+- (NSString *)localizedStyleName:(CSSStyle *)style;
+
++ (BOOL)create:(NSString *)filename error:(NSError **)error;
++ (EDFileFormat *)formatForExtension:(NSString *)extension;
++ (BOOL)isExtensionSupported:(NSString *)extension;
++ (NSString *)findUniqueFilenameWithPrefix:(NSString *)prefix
+                                 extension:(NSString *)extension
+                             existingNames:(NSArray *)names;
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDFileFormat.m
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDFileFormat.m b/experiments/objcFramework/EDFileFormat.m
new file mode 100644
index 0000000..e94d1f6
--- /dev/null
+++ b/experiments/objcFramework/EDFileFormat.m
@@ -0,0 +1,968 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import "EDFileFormat.h"
+#import "EDEditor.h"
+#import "EDHTMLTidy.h"
+#import "EDJSInterface.h"
+#import "EDTiming.h"
+#import "EDStyle.h"
+#import "EDDocumentSetup.h"
+#import "EDUtil.h"
+#import <FileClient/FCError.h>
+#import <FileClient/FCUtil.h>
+
+#define L10NStyleNameHeading1                        NSLocalizedString(@"StyleNameHeading1",NULL)
+#define L10NStyleNameHeading2                        NSLocalizedString(@"StyleNameHeading2",NULL)
+#define L10NStyleNameHeading3                        NSLocalizedString(@"StyleNameHeading3",NULL)
+#define L10NStyleNameHeading4                        NSLocalizedString(@"StyleNameHeading4",NULL)
+#define L10NStyleNameHeading5                        NSLocalizedString(@"StyleNameHeading5",NULL)
+#define L10NStyleNameHeading6                        NSLocalizedString(@"StyleNameHeading6",NULL)
+#define L10NStyleNameParagraph                       NSLocalizedString(@"StyleNameParagraph",NULL)
+#define L10NStyleNameBlockQuote                      NSLocalizedString(@"StyleNameBlockQuote",NULL)
+#define L10NStyleNamePre                             NSLocalizedString(@"StyleNamePre",NULL)
+#define L10NStyleNameBody                            NSLocalizedString(@"StyleNameBody",NULL)
+#define L10NStyleNameFigure                          NSLocalizedString(@"StyleNameFigure",NULL)
+#define L10NStyleNameFigureCaption                   NSLocalizedString(@"StyleNameFigureCaption",NULL)
+#define L10NStyleNameTable                           NSLocalizedString(@"StyleNameTable",NULL)
+#define L10NStyleNameTableCaption                    NSLocalizedString(@"StyleNameTableCaption",NULL)
+#define L10NStyleNameTableOfContents                 NSLocalizedString(@"StyleNameTableOfContents",NULL)
+#define L10NStyleNameListOfFigures                   NSLocalizedString(@"StyleNameListOfFigures",NULL)
+#define L10NStyleNameListOfTables                    NSLocalizedString(@"StyleNameListOfTables",NULL)
+
+@interface HTMLFormat : EDFileFormat
+@end
+
+@interface TextFormat : EDFileFormat
+@end
+
+@interface MarkdownFormat : EDFileFormat
+@end
+
+@interface LaTeXFormat : EDFileFormat
+@end
+
+@interface WordFormat : EDFileFormat
+@end
+
+static NSString *HTML_localizedTagName(Tag tag)
+{
+    switch (tag) {
+        case HTML_H1: return L10NStyleNameHeading1;
+        case HTML_H2: return L10NStyleNameHeading2;
+        case HTML_H3: return L10NStyleNameHeading3;
+        case HTML_H4: return L10NStyleNameHeading4;
+        case HTML_H5: return L10NStyleNameHeading5;
+        case HTML_H6: return L10NStyleNameHeading6;
+        case HTML_P: return L10NStyleNameParagraph;
+        case HTML_BLOCKQUOTE: return L10NStyleNameBlockQuote;
+        case HTML_PRE: return L10NStyleNamePre;
+        case HTML_BODY: return L10NStyleNameBody;
+        case HTML_FIGURE: return L10NStyleNameFigure;
+        case HTML_FIGCAPTION: return L10NStyleNameFigureCaption;
+        case HTML_TABLE: return L10NStyleNameTable;
+        case HTML_CAPTION: return L10NStyleNameTableCaption;
+        default: return nil;
+    }
+}
+
+NSString *HTML_uiNameForSelector(const char *ident)
+{
+    NSString *rawSelector = NSStringFromC(ident);
+    if ([rawSelector isEqualToString: @"nav.tableofcontents"])
+        return L10NStyleNameTableOfContents;
+    if ([rawSelector isEqualToString: @"nav.listoffigures"])
+        return L10NStyleNameListOfFigures;
+    if ([rawSelector isEqualToString: @"nav.listoftables"])
+        return L10NStyleNameListOfTables;
+
+    NSString *elementUIName = HTML_localizedTagName(CSSSelectorGetTag(ident));
+    if (elementUIName == nil)
+        return nil;
+
+    NSString *result = NULL;
+    char *className = CSSSelectorCopyClassName(ident);
+
+    if (!CSSSelectorHasClassName(ident))
+        result = elementUIName;
+    else if (CSSSelectorGetTag(ident) == HTML_P)
+        result = NSStringFromC(className);
+    else
+        result = [NSString stringWithFormat: @"%@ (%@)", elementUIName, NSStringFromC(className)];
+
+    free(className);
+    return result;
+}
+
+@interface NSOutputStream(CompleteWrite)
+
+- (int)completeWrite:(const uint8_t *)buffer length:(NSUInteger)length;
+
+@end
+
+@implementation NSOutputStream(CompleteWrite)
+
+- (int)completeWrite:(const uint8_t *)buffer length:(NSUInteger)length
+{
+    NSUInteger offset = 0;
+    do {
+        NSUInteger remaining = length - offset;
+        NSInteger written = [self write: &buffer[offset] maxLength: remaining];
+        if (written <= 0)
+            return 0;
+        offset += written;
+    } while (offset < length);
+    return 1;
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                          EDFileFormat                                          //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation EDFileFormat
+
+- (EDFileFormat *)init
+{
+    if (!(self = [super init]))
+        return nil;
+    return self;
+}
+
+- (NSString *)fileLocalPath
+{
+    return self.editor.delegate.editorPath;
+}
+
+- (BOOL)supportsBareStyles
+{
+    return NO;
+}
+
+- (NSString *)absoluteHTMLPath
+{
+    [NSException raise: @"FileFormat" format: @"%@ absoluteHTMLPath not implemented", self.class];
+    return nil;
+}
+
+- (void)save:(NSString *)filename html:(NSString *)html completion:(void (^)(NSError *error))completion
+{
+    completion([NSError error: @"FileFormat save not implemented"]);
+}
+
+- (NSString *)prepareForLoad:(NSError **)error
+{
+    [NSException raise: @"FileFormat" format: @"%@ prepareForLoad not implemented", self.class];
+    return nil;
+}
+
+- (void)finishLoad
+{
+    [NSException raise: @"FileFormat" format: @"%@ finishLoad not implemented", self.class];
+}
+
++ (BOOL)create2:(NSString *)filename error:(DFError **)error
+{
+    NSString *extension = filename.pathExtension.lowercaseString;
+    if ([extension isEqualToString: @"html"] || [extension isEqualToString: @"htm"])
+        return DFHTMLCreateDefaultFile(filename.UTF8String,error);
+    else if ([extension isEqualToString: @"docx"]) {
+        return DFWordCreateDefault(filename.UTF8String,error);
+    }
+
+    DFErrorFormat(error,"Unsupported file format: %s\n",extension.UTF8String);
+    return NO;
+}
+
++ (BOOL)create:(NSString *)filename error:(NSError **)error
+{
+    DFError *dferror = NULL;
+    BOOL ok = [self create2: filename error: &dferror];
+    if (!ok)
+        DFErrorReleaseToNSError(dferror,error);
+    return ok;
+}
+
++ (EDFileFormat *)formatForExtension:(NSString *)extension
+{
+    extension = extension.lowercaseString;
+    if ([extension isEqualToString: @"html"] || [extension isEqualToString: @"htm"])
+        return [[HTMLFormat alloc] init];
+    else if ([extension isEqualToString: @"txt"])
+        return [[TextFormat alloc] init];
+    else if ([extension isEqualToString: @"md"])
+        return [[MarkdownFormat alloc] init];
+    else if ([extension isEqualToString: @"tex"])
+        return [[LaTeXFormat alloc] init];
+    else if ([extension isEqualToString: @"docx"])
+        return [[WordFormat alloc] init];
+    return nil;
+}
+
++ (BOOL)isExtensionSupported:(NSString *)extension
+{
+    return ([self formatForExtension: extension] != nil);
+}
+
+- (void)setupInitialStyles
+{
+    // Ensure there is at least one style present for a given heading level
+    NSMutableSet *allElementNames = [NSMutableSet setWithCapacity: 0];
+    CSSSheet *styleSheet = self.editor.styleSheet;
+    const char **allSelectors = CSSSheetCopySelectors(styleSheet);
+    for (int i = 0; allSelectors[i]; i++) {
+        CSSStyle *style = CSSSheetLookupSelector(styleSheet,allSelectors[i],NO,NO);
+        [allElementNames addObject: NSStringFromC(style->elementName)];
+    }
+    free(allSelectors);
+
+    for (int i = 1; i <= 6; i++) {
+        NSString *elementName = [NSString stringWithFormat: @"h%d", i];
+        if (![allElementNames containsObject: elementName])
+            CSSSheetLookupElement(styleSheet,elementName.UTF8String,NULL,YES,YES);
+    }
+}
+
+- (CSSStyle *)setupTableStyle
+{
+    int changed = NO;
+    CSSSheet *styleSheet = self.editor.styleSheet;
+    CSSStyle *result = WordSetupTableGridStyle(styleSheet,&changed);
+    if (changed)
+        [self.editor updateCSS];
+    return result;
+}
+
+- (void)setupFigureStyle
+{
+    CSSStyle *figure = CSSSheetLookupElement(self.editor.styleSheet,"figure",NULL,NO,NO);
+    if ((figure == nil) || CSSStyleIsEmpty(figure)) {
+        figure = CSSSheetLookupElement(self.editor.styleSheet,"figure",NULL,YES,NO);
+        CSSProperties *rule = CSSStyleRule(figure);
+        CSSPut(rule,"text-align","center");
+        CSSPut(rule,"margin-left","auto");
+        CSSPut(rule,"margin-right","auto");
+        CSSPut(rule,"margin-top","12pt");
+        CSSPut(rule,"margin-bottom","12pt");
+        figure->latent = NO;
+        [self.editor updateCSS];
+    }
+}
+
+- (void)setupOutlineStyle
+{
+}
+
+- (NSString *)imagePath
+{
+    return [self.fileLocalPath stringByDeletingLastPathComponent];
+}
+
++ (NSString *)findUniqueFilenameWithPrefix:(NSString *)prefix
+                                 extension:(NSString *)extension
+                             existingNames:(NSArray *)names
+{
+    // We first need to obtain a listing of all files in the directory, because we want a name
+    // for which there is no other file with that name and a different extension
+    // (e.g. image001.jpg and image001.png)
+
+    NSMutableSet *existingNames = [NSMutableSet setWithCapacity: 0];
+    for (NSString *filename in names)
+        [existingNames addObject: [filename.lowercaseString stringByDeletingPathExtension]];
+
+    int num = 1;
+    NSString *candidate;
+    do {
+        candidate = [NSString stringWithFormat: @"%@%03d", prefix, num];
+        num++;
+    } while ([existingNames containsObject: candidate.lowercaseString]);
+
+    return [candidate stringByAppendingPathExtension: extension];
+}
+
+- (NSString *)addImage:(NSData *)data extension:(NSString *)extension error:(NSError **)error
+{
+    NSString *baseName = [self.fileLocalPath.lastPathComponent stringByDeletingPathExtension];
+    NSString *prefix = [NSString stringWithFormat: @"%@_image", baseName];
+    return [self.editor.delegate saveResource: data prefix: prefix extension: extension error: error];
+}
+
+- (NSString *)localizedStyleName:(CSSStyle *)style
+{
+    char *cDisplayName = CSSStyleCopyDisplayName(style);
+    if (cDisplayName != NULL) {
+        NSString *displayName = NSStringFromC(cDisplayName);
+        free(cDisplayName);
+        return displayName;
+    }
+    NSString *str = HTML_uiNameForSelector(style->selector);
+    if (str != nil)
+        return str;
+    return HTML_uiNameForSelector(style->selector);
+}
+
+- (Comparator)styleComparator
+{
+    return ^NSComparisonResult(id obj1, id obj2) {
+        EDStyle *style1 = (EDStyle *)obj1;
+        EDStyle *style2 = (EDStyle *)obj2;
+        NSString *name1 = [self localizedStyleName: style1.cssStyle];
+        NSString *name2 = [self localizedStyleName: style2.cssStyle];
+        return [name1 localizedCaseInsensitiveCompare: name2];
+    };
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                           HTMLFormat                                           //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation HTMLFormat
+
+- (HTMLFormat *)init
+{
+    if (!(self = [super init]))
+        return nil;
+    return self;
+}
+
+- (BOOL)supportsBareStyles
+{
+    return YES;
+}
+
+- (NSString *)absoluteHTMLPath
+{
+    return self.fileLocalPath;
+}
+
+- (NSSet *)builtinRawSelectors
+{
+    return [NSSet setWithObjects:
+            @"h1",
+            @"h2",
+            @"h3",
+            @"h4",
+            @"h5",
+            @"h6",
+            @"p",
+            @"pre",
+            @"blockquote",
+            @"@page",
+            @"body",
+            @"figure",
+            @"figcaption",
+            @"table",
+            @"caption",
+            @"nav.tableofcontents",
+            @"nav.listoffigures",
+            @"nav.listoftables",
+            nil];
+}
+
+- (NSString *)prepareForLoad:(NSError **)error
+{
+    // Nothing to do here - just return the name of the HTML file to load
+    return self.fileLocalPath;
+}
+
+- (void)finishLoad
+{
+    // Nothing to do here - default initialisation is sufficient
+}
+
+- (void)save:(NSString *)filename html:(NSString *)html completion:(void (^)(NSError *error))completion
+{
+    completion = [completion copy];
+    NSString *extension = self.fileLocalPath.pathExtension.lowercaseString;
+    BOOL isXHTML = [extension isEqualToString: @"xml"] || [extension isEqualToString: @"xhtml"];
+    [self.editor.saveTiming addEntry: @"JS getHTML"];
+    if (html.length == 0) {
+        completion([NSError error: @"HTML code is 0 bytes"]);
+        return;
+    }
+    html = [@"<!DOCTYPE html>\n" stringByAppendingString: html];
+
+    NSData *rawData = [html dataUsingEncoding: NSUTF8StringEncoding];
+
+    [EDHTMLTidy tidy: rawData isXHTML: isXHTML editor: self.editor completion:^(NSData *output, NSError *error) {
+        [self.editor.saveTiming addEntry: @"HTML Tidy"];
+
+        if (error != nil) {
+            completion(error);
+            return;
+        }
+
+        if ((output == nil) || (output.length == 0)) {
+            completion([NSError error: @"Output of HTML Tidy is 0 bytes"]);
+            return;
+        }
+
+        NSOutputStream *out = [NSOutputStream outputStreamToFileAtPath: filename append: NO];
+        [out open];
+        [out completeWrite: output.bytes length: output.length];
+        [out close];
+
+        if (out.streamError != nil) {
+            completion(out.streamError);
+            return;
+        }
+        [self.editor.saveTiming addEntry: @"Write temp file"];
+        completion(nil);
+    }];
+}
+
+- (BOOL)setupCellStyle:(NSString *)elementName
+{
+    CSSStyle *style = CSSSheetLookupElement(self.editor.styleSheet,elementName.UTF8String,NULL,NO,NO);
+    if (style != nil)
+        return NO;
+    style = CSSSheetLookupElement(self.editor.styleSheet,elementName.UTF8String,NULL,YES,NO);
+
+    CSSProperties *base = CSSStyleRuleForSuffix(style,"");
+    CSSProperties *firstChild = CSSStyleRuleForSuffix(style," > :first-child");
+    CSSProperties *lastChild = CSSStyleRuleForSuffix(style," > :last-child");
+
+    CSSPut(base,"border-left-width","1px");
+    CSSPut(base,"border-right-width","1px");
+    CSSPut(base,"border-top-width","1px");
+    CSSPut(base,"border-bottom-width","1px");
+
+    CSSPut(base,"border-left-style","solid");
+    CSSPut(base,"border-right-style","solid");
+    CSSPut(base,"border-top-style","solid");
+    CSSPut(base,"border-bottom-style","solid");
+
+    CSSPut(base,"border-left-color","black");
+    CSSPut(base,"border-right-color","black");
+    CSSPut(base,"border-top-color","black");
+    CSSPut(base,"border-bottom-color","black");
+
+    CSSPut(firstChild,"margin-top","0");
+    CSSPut(lastChild,"margin-bottom","0");
+    return YES;
+}
+
+- (void)fixCorruptUXWrite10CSS
+{
+    // Document was edited in UX Write >= 1.1.0 and then later edited in UX Write 1.0.x
+    // This results in corrupted counter-increment, counter-reset, and content values,
+    // due to a bug in UX Write 1.0.x which serialises these incorrectly into the stylesheet
+
+    // counter-increment and counter-reset are invalid, because WebKit's CSS serialisation
+    // code (used by 1.0.x to generate the stylesheet text for these) places commas between
+    // all of the values, which (I think) is not valid CSS
+
+    // counter is invalid, because Styles_discoverStyles() in 1.0.x stupidly removes single
+    // and double quotes from the start of any property values, on the assumption that any
+    // such values are whole strings. This turns the following:
+    //
+    // h1::before { content: counter(h1) ". " }
+    //
+    // into:
+    //
+    // h1::before { content: counter(h1) " }
+    //
+    // Because CSS strings can't end in a newline, the parser discards the rest of the
+    // property, and the ::before rule becomes empty
+
+    // To get around these problems, we simply delete all the counter-increment,
+    // counter-reset, and content properties, and re-create them. This is only going to be
+    // a problem in a very small number of cases where a document is taken from 1.1.x back
+    // to 1.0.x, e.g. due to the upgrade not being completed on all devices
+
+    CSSSheet *styleSheet = self.editor.styleSheet;
+
+    // First determine if heading numbering was on
+    // FIXME: Won't work with Word documents, in which all styles have a class name
+    CSSStyle *h1 = CSSSheetLookupElement(styleSheet,"h1",NULL,NO,NO);
+    BOOL hadHeadingNumbering = (CSSGet(CSSStyleRule(h1),"counter-increment") != NULL);
+
+    // Now clear all the counter-increment, counter-reset, and content properties
+    const char **allSelectors = CSSSheetCopySelectors(styleSheet);
+    for (int selIndex = 0; allSelectors[selIndex]; selIndex++) {
+        const char *selector = allSelectors[selIndex];
+        CSSStyle *style = CSSSheetLookupSelector(styleSheet,selector,NO,NO);
+
+        const char **allSuffixes = CSSStyleCopySuffixes(style);
+        for (int suffixIndex = 0; allSuffixes[suffixIndex]; suffixIndex++) {
+            const char *suffix = allSuffixes[suffixIndex];
+            CSSProperties *properties = CSSStyleRuleForSuffix(style,suffix);
+            CSSPut(properties,"counter-increment",NULL);
+            CSSPut(properties,"counter-reset",NULL);
+            CSSPut(properties,"content",NULL);
+        }
+        free(allSuffixes);
+    }
+    free(allSelectors);
+
+    // If the document *was* using heading numbering, turn it on again
+    if (hadHeadingNumbering)
+        CSSSheetSetHeadingNumbering(self.editor.styleSheet,YES);
+}
+
+- (void)transitionFromUXWrite10Numbering
+{
+    if (CSSSheetIsNumberingUsed(self.editor.styleSheet)) {
+        // Document was edited in UX Write >= 1.1.0 and then later edited in UX Write 1.0.x
+        [self fixCorruptUXWrite10CSS];
+    }
+    else {
+        // Document was created in UX Write 1.0.x and is being opened in 1.1.x for the first time
+        // Switch to using CSS counters for numbering
+        BOOL sectionNumbering = [self.editor.js.outline detectSectionNumbering];
+        if (sectionNumbering)
+            CSSSheetSetHeadingNumbering(self.editor.styleSheet,YES);
+    }
+}
+
+- (void)setupInitialStyles
+{
+    [super setupInitialStyles];
+
+    for (NSString *rawSelector in self.builtinRawSelectors)
+        CSSSheetLookupSelector(self.editor.styleSheet,rawSelector.UTF8String,YES,YES);
+
+    // Styles
+    CSSStyle *p = CSSSheetLookupElement(self.editor.styleSheet,"p",NULL,YES,NO);
+    CSSSheetSetDefaultStyle(self.editor.styleSheet,p,StyleFamilyParagraph);
+
+    NSString *origGenerator = self.editor.origGenerator;
+    if ((origGenerator != nil) && [origGenerator hasPrefix: @"UX Write 1.0"])
+        [self transitionFromUXWrite10Numbering];
+}
+
+- (CSSStyle *)setupTableStyle
+{
+//    td-paragraph-margins
+//        td > :first-child { margin-top: 0; }
+//        td > :last-child { margin-bottom: 0; }
+//    th-paragraph-margins
+//        th > :first-child { margin-top: 0; }
+//        th > :last-child { margin-bottom: 0; }
+//    table-borders
+//        table { border-collapse: collapse; margin-left: auto; margin-right: auto; }
+//        td { border: 1px solid black; }
+//        th { border: 1px solid black; }
+//    table-caption
+//        caption { caption-side: bottom; }
+
+    BOOL changed = NO;
+
+    if ([self setupCellStyle: @"td"])
+        changed = YES;
+    if ([self setupCellStyle: @"th"])
+        changed = YES;
+
+    CSSStyle *table = CSSSheetLookupElement(self.editor.styleSheet,"table",NULL,NO,NO);
+    if ((table == nil) || CSSStyleIsEmpty(table)) {
+        table = CSSSheetLookupElement(self.editor.styleSheet,"table",NULL,YES,NO);
+        CSSProperties *rule = CSSStyleRule(table);
+        CSSPut(rule,"border-collapse","collapse");
+        CSSPut(rule,"margin-left","auto");
+        CSSPut(rule,"margin-right","auto");
+        changed = YES;
+    }
+
+    if (table->latent) {
+        table->latent = NO;
+        changed = YES;
+    }
+
+    // FIXME: this is already done in useCSSNumbering - need to ignore duplication
+    CSSStyle *caption = CSSSheetLookupElement(self.editor.styleSheet,"caption",NULL,NO,NO);
+    if ((caption == nil) || CSSStyleIsEmpty(caption)) {
+        caption = CSSSheetLookupElement(self.editor.styleSheet,"caption",NULL,YES,NO);
+        CSSPut(CSSStyleRule(caption),"caption-side","bottom");
+        changed = YES;
+    }
+
+    if (changed)
+        [self.editor updateCSS];
+
+    return table;
+}
+
+- (void)setupOutlineStyle
+{
+    BOOL changed = NO;
+
+    CSSStyle *style = CSSSheetLookupElement(self.editor.styleSheet,"p","toc1",NO,NO);
+    if (style == nil) {
+        style = CSSSheetLookupElement(self.editor.styleSheet,"p","toc1",YES,NO);
+        CSSProperties *rule = CSSStyleRule(style);
+        CSSPut(rule,"margin-bottom","6pt");
+        CSSPut(rule,"margin-left","0pt");
+        CSSPut(rule,"margin-top","12pt");
+        changed = YES;
+    }
+
+    style = CSSSheetLookupElement(self.editor.styleSheet,"p","toc2",NO,NO);
+    if (style == nil) {
+        style = CSSSheetLookupElement(self.editor.styleSheet,"p","toc2",YES,NO);
+        CSSProperties *rule = CSSStyleRule(style);
+        CSSPut(rule,"margin-bottom","6pt");
+        CSSPut(rule,"margin-left","24pt");
+        CSSPut(rule,"margin-top","6pt");
+        changed = YES;
+    }
+
+    style = CSSSheetLookupElement(self.editor.styleSheet,"p","toc3",NO,NO);
+    if (style == nil) {
+        style = CSSSheetLookupElement(self.editor.styleSheet,"p","toc3",YES,NO);
+        CSSProperties *rule = CSSStyleRule(style);
+        CSSPut(rule,"margin-bottom","6pt");
+        CSSPut(rule,"margin-left","48pt");
+        CSSPut(rule,"margin-top","6pt");
+        changed = YES;
+    }
+
+    if (changed)
+        [self.editor updateCSS];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                           TextFormat                                           //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation TextFormat
+
+- (TextFormat *)init
+{
+    if (!(self = [super init]))
+        return nil;
+    return self;
+}
+
+- (void)save:(NSString *)filename html:(NSString *)html completion:(void (^)(NSError *error))completion
+{
+    completion([NSError error: @"TextFormat save not implemented"]);
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                         MarkdownFormat                                         //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation MarkdownFormat
+
+- (MarkdownFormat *)init
+{
+    if (!(self = [super init]))
+        return nil;
+    return self;
+}
+
+- (void)save:(NSString *)filename html:(NSString *)html completion:(void (^)(NSError *error))completion
+{
+    completion([NSError error: @"MarkdownFormat save not implemented"]);
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                           LaTeXFormat                                          //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation LaTeXFormat
+
+- (LaTeXFormat *)init
+{
+    if (!(self = [super init]))
+        return nil;
+    return self;
+}
+
+- (void)save:(NSString *)filename html:(NSString *)html completion:(void (^)(NSError *error))completion
+{
+    completion([NSError error: @"LaTeXFormat save not implemented"]);
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                           WordFormat                                           //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface WordFormat()
+
+@property (copy) NSString *tempDir;
+@property (copy) NSString *idPrefix;
+
+@property (copy, readonly) NSString *originalBackupPath;
+@property (copy, readonly) NSString *htmlTempDir;
+@property (copy, readonly) NSString *htmlTempFilename;
+
+@end
+
+@implementation WordFormat
+
+- (WordFormat *)init
+{
+    if (!(self = [super init]))
+        return nil;
+    _idPrefix = [[NSString alloc] initWithFormat: @"bdt%u-", arc4random()];
+    return self;
+}
+
+- (void)dealloc
+{
+    if (_tempDir != nil)
+        [[NSFileManager defaultManager] removeItemAtPath: _tempDir error: nil];
+}
+
+- (NSString *)originalBackupPath
+{
+    return [self.tempDir stringByAppendingPathComponent: @"original.docx"];
+}
+
+- (NSString *)packageTempDir
+{
+    return [self.tempDir stringByAppendingPathComponent: @"package"];
+}
+
+- (NSString *)htmlTempDir
+{
+    return [self.tempDir stringByAppendingPathComponent: @"html"];
+}
+
+- (NSString *)htmlTempFilename
+{
+    return [self.htmlTempDir stringByAppendingPathComponent: @"document.html"];
+}
+
+- (NSString *)absoluteHTMLPath
+{
+    return self.htmlTempFilename;
+}
+
+- (NSString *)prepareForLoad:(NSError **)error
+{
+    self.tempDir = FCCreateTemporaryDirectory(error);
+    if (self.tempDir == nil)
+        return nil;
+
+    NSFileManager *fm = [NSFileManager defaultManager];
+    if (![fm createDirectoryAtPath: self.htmlTempDir withIntermediateDirectories: YES
+                        attributes: nil error: error])
+        return nil;
+
+    NSString *resetSource = [[NSBundle mainBundle] pathForResource: @"reset" ofType: @"css"];
+    assert(resetSource != nil);
+    NSString *resetDest = [self.htmlTempDir stringByAppendingPathComponent: @"reset.css"];
+    if (![fm copyItemAtPath: resetSource toPath: resetDest error: error])
+        return nil;
+
+    if (![fm copyItemAtPath: self.fileLocalPath toPath: self.originalBackupPath error: error])
+        return nil;
+
+    DFError *dferror = NULL;
+    DFConcreteDocument *concreteDoc = DFConcreteDocumentOpenFile(self.originalBackupPath.UTF8String,&dferror);
+    if (concreteDoc == NULL) {
+        DFErrorReleaseToNSError(dferror,error);
+        return nil;
+    }
+
+    DFStorage *abstractPackage = DFStorageNewFilesystem(self.htmlTempDir.UTF8String,DFFileFormatHTML);
+    DFAbstractDocument *abstractDoc = DFAbstractDocumentNew(abstractPackage);
+
+    int ok = DFGet(concreteDoc,abstractDoc,&dferror);
+    DFDocument *htmlDoc = NULL;
+    if (ok)
+        htmlDoc = DFDocumentRetain(DFAbstractDocumentGetHTML(abstractDoc));
+
+    DFStorageRelease(abstractPackage);
+    DFAbstractDocumentRelease(abstractDoc);
+    DFConcreteDocumentRelease(concreteDoc);
+
+    if (!ok) {
+        DFErrorReleaseToNSError(dferror,error);
+        return nil;
+    }
+
+    if (htmlDoc == NULL) {
+        DFErrorFormat(&dferror,"htmlDoc is NULL");
+        DFErrorReleaseToNSError(dferror,error);
+        return nil;
+    }
+
+
+    if (!DFSerializeXMLFile(htmlDoc,0,0,self.htmlTempFilename.UTF8String,&dferror)) {
+        DFErrorReleaseToNSError(dferror,error);
+        DFDocumentRelease(htmlDoc);
+        return nil;
+    }
+
+    DFDocumentRelease(htmlDoc);
+    return self.htmlTempFilename;
+}
+
+- (void)finishLoad
+{
+}
+
+// This method is always run in a background thread, initiated via save
+- (BOOL)directSave:(NSString *)filename html:(NSString *)html error:(NSError **)error
+{
+    DFError *dferror = NULL;
+    DFDocument *htmlDoc = DFParseHTMLString(html.UTF8String,1,&dferror);
+    if (htmlDoc == nil) {
+        DFErrorReleaseToNSError(dferror,error);
+        FCErrorPrepend(error,@"Parse HTML");
+        return NO;
+    }
+
+    // Write out the HTML file in case wer're printing or generating a PDF, so it can load the
+    // page content from that
+    // FIXME: In this case, we don't really need to save the word document itself - saving *just*
+    // the HTML will be sufficient. And when we are actually saving the word document, we don't
+    // need to write out the HTML data. So this is just a temporary solution.
+    DFError *localError = NULL;
+    if (!DFSerializeXMLFile(htmlDoc,0,0,self.htmlTempFilename.UTF8String,&localError)) {
+        DFErrorReleaseToNSError(localError,error);
+        FCErrorPrepend(error,@"Save HTML");
+        DFDocumentRelease(htmlDoc);
+        return NO;
+    }
+
+    NSString *modifiedPath = [self.tempDir stringByAppendingPathComponent: @"modified.docx"];
+    NSFileManager *fm = [NSFileManager defaultManager];
+    if (![fm copyItemAtPath: self.originalBackupPath toPath: modifiedPath error: error]) {
+        FCErrorPrepend(error,@"Copy original to modified");
+        return NO;
+    }
+
+    DFConcreteDocument *concreteDoc = DFConcreteDocumentOpenFile(modifiedPath.UTF8String,&dferror);
+    if (concreteDoc == NULL) {
+        DFErrorReleaseToNSError(dferror,error);
+        FCErrorPrepend(error,@"Open document for update");
+        return NO;
+    }
+
+    BOOL ok = NO;
+
+    DFStorage *abstractPackage = DFStorageNewFilesystem(self.htmlTempDir.UTF8String,DFFileFormatHTML);
+    DFAbstractDocument *abstractDoc = DFAbstractDocumentNew(abstractPackage);
+
+    DFAbstractDocumentSetHTML(abstractDoc,htmlDoc);
+
+    if (!DFPut(concreteDoc,abstractDoc,&dferror)) {
+        DFErrorReleaseToNSError(dferror,error);
+        FCErrorPrepend(error,@"DFPut");
+        goto end;
+    }
+
+    if ([fm fileExistsAtPath: filename] && ![fm removeItemAtPath: filename error: error]) {
+        FCErrorPrepend(error,@"Remove old version of document");
+        goto end;
+    }
+
+    if (![fm moveItemAtPath: modifiedPath toPath: filename error: error]) {
+        FCErrorPrepend(error,@"Move new version of document into place");
+        goto end;
+    }
+
+    ok = YES;
+
+end:
+    DFDocumentRelease(htmlDoc);
+    DFStorageRelease(abstractPackage);
+    DFAbstractDocumentRelease(abstractDoc);
+    DFConcreteDocumentRelease(concreteDoc);
+    return ok;
+}
+
+- (void)save:(NSString *)filename html:(NSString *)html completion:(void (^)(NSError *error))completion
+{
+    // Create a copy of the completion on the heap, in case we're passed one that's allocated on
+    // the stack
+    completion = [completion copy];
+    [self.editor.system runCommandInBackground:^BOOL(NSError **commandError) {
+        return [self directSave: filename html: html error: commandError];
+    } completion:^(NSError *completionError) {
+        completion(completionError);
+    }];
+}
+
+- (NSString *)imagePath
+{
+    return self.htmlTempDir;
+}
+
+- (void)setupInitialStyles
+{
+    [super setupInitialStyles];
+    [self.editor.js.styles setParagraphClass: @"Normal"];
+}
+
+- (NSString *)addImage:(NSData *)data extension:(NSString *)extension error:(NSError **)error
+{
+    NSFileManager *fm = [NSFileManager defaultManager];
+    NSArray *contents = [fm contentsOfDirectoryAtPath: self.imagePath error: error];
+    if (contents == nil)
+        return nil;
+
+    NSString *filename = [EDFileFormat findUniqueFilenameWithPrefix: @"image"
+                                                        extension: extension
+                                                    existingNames: contents];
+
+    NSString *fullPath = [self.imagePath stringByAppendingPathComponent: filename];
+    if (![data writeToFile: fullPath options: 0 error: error])
+        return nil;
+
+    return fullPath;
+}
+
+- (NSString *)localizedStyleName:(CSSStyle *)style
+{
+    char *cDisplayName = CSSStyleCopyDisplayName(style);
+    if (cDisplayName != nil) {
+        NSString *displayName = NSStringFromC(cDisplayName);
+        free(cDisplayName);
+        return [[NSBundle mainBundle] localizedStringForKey: displayName.lowercaseString
+                                                      value: displayName
+                                                      table: @"WordBuiltinStyles"];
+    }
+
+    return [super localizedStyleName: style];
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDFindReplace.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDFindReplace.h b/experiments/objcFramework/EDFindReplace.h
new file mode 100644
index 0000000..50d4fac
--- /dev/null
+++ b/experiments/objcFramework/EDFindReplace.h
@@ -0,0 +1,48 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import <Foundation/Foundation.h>
+
+#import "EDScan.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                         EDFindOperation                                        //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDFindOperation : EDScanOperation
+
+@property (copy, readonly) NSString *findTerm;
+@property (copy, readonly) NSString *replaceTerm;
+@property (assign, readonly) BOOL caseSensitive;
+@property (strong, readonly) NSRegularExpression *regex;
+
+- (EDFindOperation *)initWithEditor:(EDEditor *)editor
+                           findTerm:(NSString *)findTerm
+                        replaceTerm:(NSString *)replaceTerm
+                      caseSensitive:(BOOL)caseSensitive
+                              regex:(NSRegularExpression *)regex;
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDFindReplace.m
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDFindReplace.m b/experiments/objcFramework/EDFindReplace.m
new file mode 100644
index 0000000..59cc151
--- /dev/null
+++ b/experiments/objcFramework/EDFindReplace.m
@@ -0,0 +1,82 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import "EDFindReplace.h"
+#import "EDEditor.h"
+#import "EDScanResults.h"
+#import "EDJSInterface.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                         EDFindOperation                                        //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation EDFindOperation
+
+- (EDFindOperation *)initWithEditor:(EDEditor *)editor
+                           findTerm:(NSString *)findTerm
+                        replaceTerm:(NSString *)replaceTerm
+                      caseSensitive:(BOOL)caseSensitive
+                              regex:(NSRegularExpression *)regex
+{
+    if (!(self = [super initWithEditor: editor]))
+        return nil;
+    _findTerm = [findTerm copy];
+    _replaceTerm = [replaceTerm copy];
+    _caseSensitive = caseSensitive;
+    _regex = regex;
+    return self;
+}
+
+- (void)processParagraph:(EDScanParagraph *)paragraph
+{
+    NSString *text = paragraph.text;
+
+    NSMutableArray *newMatches = [NSMutableArray arrayWithCapacity: 0];
+
+    NSStringCompareOptions compareOptions = _caseSensitive ? 0 : NSCaseInsensitiveSearch;
+    NSUInteger index = 0;
+    while (true) {
+        NSRange remainder = NSMakeRange(index,text.length-index);
+        NSRange range;
+        if (_regex != nil)
+            range = [_regex rangeOfFirstMatchInString: text options: 0 range: remainder];
+        else
+            range = [text rangeOfString: _findTerm options: compareOptions range: remainder];
+        if (range.location == NSNotFound)
+            break;
+        index = range.location + range.length;
+
+        [self includeCurrentSection];
+
+        int matchId = [self.editor.js.scan addMatchStart: (int)range.location end: (int)(range.location + range.length)];
+        NSString *matchText = [text substringWithRange: range];
+        [newMatches addObject: [[EDScanMatch alloc] initWithMatchId: matchId text: matchText]];
+    }
+
+    [self foundMatches: newMatches];
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDGeometry.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDGeometry.h b/experiments/objcFramework/EDGeometry.h
new file mode 100644
index 0000000..cba3101
--- /dev/null
+++ b/experiments/objcFramework/EDGeometry.h
@@ -0,0 +1,80 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import <Foundation/Foundation.h>
+#import <CoreGraphics/CoreGraphics.h>
+
+typedef enum {
+    EDSizeTypeNone = 0,
+    EDSizeTypeTopLeft = 0x01,
+    EDSizeTypeTopRight = 0x02,
+    EDSizeTypeBottomLeft = 0x04,
+    EDSizeTypeBottomRight = 0x08,
+    EDSizeTypeInnerTop = 0x10,
+    EDSizeTypeInnerBottom = 0x20,
+} EDSizeType;
+
+typedef enum {
+    EDResizeEdgeTop = EDSizeTypeTopLeft | EDSizeTypeTopRight,
+    EDResizeEdgeBottom = EDSizeTypeBottomLeft | EDSizeTypeBottomRight,
+    EDResizeEdgeLeft = EDSizeTypeTopLeft | EDSizeTypeBottomLeft,
+    EDResizeEdgeRight = EDSizeTypeTopRight | EDSizeTypeBottomRight,
+    EDResizeEdgeInner = EDSizeTypeInnerTop | EDSizeTypeInnerBottom,
+} EDResizeEdge;
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                         EDItemGeometry                                         //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDItemGeometry : NSObject
+
+@property (assign, readonly) CGRect contentRect; // Doc's rect for figure or table (view is slightly bigger, for resize handles)
+@property (assign, readonly) CGRect fullRect; // Includes the caption
+@property (assign, readonly) CGRect parentRect; // Rect of the figure or table's containing block (usually the body)
+@property (assign, readonly) BOOL hasCaption;
+
+// columnWidths contains NSNumber (double) objects.
+// No specific value range; calculated relative to total.
+// This property should be nil for figures, in which case no inner handles will be shown
+@property (strong, readonly) NSArray *columnWidths;
+@property (strong, readonly) NSArray *columnOffsets;
+
++ (EDItemGeometry *)fromDict:(NSDictionary *)dict;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                        EDResizeDelegate                                        //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@protocol EDResizeDelegate <NSObject>
+
+- (void)resizedWidthPct:(CGFloat)widthPct;
+- (void)resizedColumns:(NSArray *)widthPcts;
+
+@end