AppController.m
/* |
File: AppController.m |
Abstract: Use Key-Value Bindings to create a simple XML browser/editor. |
Version: 1.1 |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple |
Inc. ("Apple") in consideration of your agreement to the following |
terms, and your use, installation, modification or redistribution of |
this Apple software constitutes acceptance of these terms. If you do |
not agree with these terms, please do not use, install, modify or |
redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Inc. may |
be used to endorse or promote products derived from the Apple Software |
without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or |
implied, are granted by Apple herein, including but not limited to any |
patent rights that may be infringed by your derivative works or by other |
works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
Copyright (C) 2012 Apple Inc. All Rights Reserved. |
*/ |
#import <WebKit/WebFrame.h> |
#import "AppController.h" |
#import "NodeKindValueTransformer.h" |
// The options available in the fidelityMatrix |
enum { |
FidelityNamespace = 0, |
FidelityAttribute, |
FidelityEntities, |
FidelityPrefixes, |
FidelityEmpty, |
FidelityWhitespace, |
FidelityCDATA, |
FidelityQuotes, |
TidyXML, |
TidyHTML, |
PrettyPrint, |
FidelityCharacters, |
FidelityDTD, |
UseXInclude, |
Validate |
}; |
enum { |
SourceTab = 0, |
EditorTab, |
ResultTab, |
XQueryTab |
}; |
@implementation AppController |
- (void)dealloc { |
[url release]; |
[self setData:nil encoding:nil]; |
[self setDocument:nil]; |
[self setCurrent:nil]; |
[alertSheet release]; |
[super dealloc]; |
} |
- (id)init { |
self = [super init]; |
if (self) { |
options = NSXMLNodeOptionsNone; |
// Setup value transformers so interface elements can be bound to enumerations (NSXMLNodeKind) |
// Change from a kind to an NSString (NSXMLElementKind -> "Element") |
{ |
NodeKindValueTransformer *kindTransformer = [[NodeKindValueTransformer alloc] init]; |
[NSValueTransformer setValueTransformer:kindTransformer forName:@"NodeKindValueTransformer"]; |
[kindTransformer release]; |
} |
// Change from a kind to a NSNumber boolean (NSXMLElementKind -> YES) |
{ |
NodeCanHaveChildrenValueTransformer *kindTransformer = [[NodeCanHaveChildrenValueTransformer alloc] init]; |
[NSValueTransformer setValueTransformer:kindTransformer forName:@"NodeCanHaveChildrenValueTransformer"]; |
[kindTransformer release]; |
} |
// Create an alert sheet used to show connection and parse errors |
alertSheet = [[NSAlert alloc] init]; |
[alertSheet addButtonWithTitle:@"OK"]; |
[alertSheet setAlertStyle:NSWarningAlertStyle]; |
} |
return self; |
} |
// The command keys 1-4 select the four tabs |
- (IBAction)setSelectedTab:(id)sender { |
[mainTabView selectTabViewItemAtIndex:[sender tag]]; |
} |
- (BOOL)tabView:(NSTabView *)tabView shouldSelectTabViewItem:(NSTabViewItem *)tabViewItem |
{ |
BOOL shouldSelect = YES; |
int tab = [mainTabView indexOfTabViewItem:[mainTabView selectedTabViewItem]]; |
// Trying to switch out of the source view will throw an exception if the document is invalid. By catching it we can prevent the tab switch from happening |
NS_DURING |
switch (tab) { |
case SourceTab: |
[self applyNSXML:nil]; |
break; |
case EditorTab: |
[self setResult:nil]; |
break; |
default: |
break; |
} |
NS_HANDLER |
shouldSelect = NO; |
NS_ENDHANDLER |
return shouldSelect; |
} |
/* There are two ways to enter a URL to display: either use File->Open or type directly in the URL field. Saving could be simply added by bringing up a save panel then using [[document XMLData] writeToURL:sheetURL atomically:YES] */ |
// Create and show a open sheet. |
- (IBAction)openFile:(id)sender { |
NSOpenPanel *openPanel = [NSOpenPanel openPanel]; |
[openPanel setAllowsMultipleSelection:NO]; |
[openPanel beginSheetModalForWindow:[NSApp mainWindow] completionHandler:^(NSInteger code) { |
if (code == NSAlertDefaultReturn) { |
[urlTextField setStringValue:[[openPanel URL] absoluteString]]; |
[self fetchAndDisplayURL:nil]; |
} |
}]; |
} |
// This is called when the user hits "return" in the URL field, or OKs |
// the open file sheet. We fetch the data from the URL, |
// then display it in the text view |
- (IBAction)fetchAndDisplayURL:(id)sender { |
if ([[urlTextField stringValue] length]) { |
[self setURL:[NSURL URLWithString:[urlTextField stringValue]]]; |
// Synchronously grab the data |
NSURLRequest *request = [NSURLRequest requestWithURL:url]; |
NSError *error; |
NSURLResponse *response; |
NSData *result = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; |
[self setData:result encoding:[response textEncodingName]]; |
if (!result) { |
// Change the text of the alert sheet to contain the connection error, then display it |
[alertSheet setMessageText:@"Request error"]; |
[alertSheet setInformativeText:[error localizedDescription]]; |
[alertSheet beginSheetModalForWindow:[NSApp mainWindow] modalDelegate:self didEndSelector:nil contextInfo:nil]; |
// clear out old value |
[self setDocument:nil]; |
} |
} else { |
[self setData:nil encoding:nil]; |
} |
} |
// The user has switched away from the Source tab, so parse the source |
- (IBAction)applyNSXML:(id)sender { |
NSError *error; |
// Parse the document with NSXMLDocument |
NSXMLDocument *doc = [[NSXMLDocument alloc] initWithData:data options:options error:&error]; |
if (doc) { |
[doc setURI:[url absoluteString]]; |
// Set both the document and "current". Current is used in the context of the second tab |
// (editing) to show what node is currently displayed/available to modify |
[self setDocument:doc]; |
[self setCurrent:doc]; |
} else { |
[self setDocument:nil]; |
[self setCurrent:nil]; |
if (error) { |
// Change the text of the alert sheet to contain the parse error, then display it |
[alertSheet setMessageText:@"Parse error"]; |
[alertSheet setInformativeText:[error localizedDescription]]; |
[alertSheet beginSheetModalForWindow:[NSApp mainWindow] modalDelegate:self didEndSelector:nil contextInfo:nil]; |
// Don't allow the user to switch from the Source tab |
NSAssert(NO, @"Invalid XML"); |
} |
} |
[doc release]; |
} |
// The user has clicked the Apply button in the XQuery tab |
- (IBAction)applyXQuery:(id)sender { |
// Clear out the old result |
[xqueryResultTextView setString:@""]; |
if (document) { |
NSError *error; |
// Apply the contents of the XQuery text field to the current document |
NSArray *result = [document objectsForXQuery:[xquerySourceTextView string] constants:nil error:&error]; |
if (result) { |
unsigned count = [result count]; |
unsigned i; |
NSMutableString *stringResult = [[NSMutableString alloc] init]; |
// Format the result array to display the position in the array with its contents enclosed in curly braces |
// In XQuery arrays are one-based so display them array index + 1 |
for (i = 0; i < count; i++) { |
[stringResult appendString:[NSString stringWithFormat:@"%d: {\r", i + 1]]; |
[stringResult appendString:[[result objectAtIndex:i] description]]; |
[stringResult appendString:@"\r}\r"]; |
} |
[xqueryResultTextView setString:stringResult]; |
[stringResult release]; |
} else if (error) { |
// Change the text of the alert sheet to contain the XQuery parse error, then display it |
[alertSheet setMessageText:@"XQuery error"]; |
[alertSheet setInformativeText:[error localizedDescription]]; |
[alertSheet beginSheetModalForWindow:[NSApp mainWindow] modalDelegate:self didEndSelector:nil contextInfo:nil]; |
} |
} |
} |
// The user has clicked on the Results tab |
- (IBAction)setResult:(id)sender { |
[self setResultTextViewString: [document XMLStringWithOptions:options]]; |
} |
// The user has checked one of the fidelity checkboxes in the Source tab. |
// Keep track of what they've checked/unchecked so we can apply these options when the data is parsed |
- (IBAction)setFidelity:(id)sender { |
if ([[fidelityMatrix cellWithTag:FidelityNamespace] state] == NSOnState) { |
options |= NSXMLNodePreserveNamespaceOrder; |
} else { |
options &= ~NSXMLNodePreserveNamespaceOrder; |
} |
if ([[fidelityMatrix cellWithTag:FidelityAttribute] state] == NSOnState) { |
options |= NSXMLNodePreserveAttributeOrder; |
} else { |
options &= ~NSXMLNodePreserveAttributeOrder; |
} |
if ([[fidelityMatrix cellWithTag:FidelityEmpty] state] == NSOnState) { |
options |= NSXMLNodePreserveEmptyElements; |
} else { |
options &= ~NSXMLNodePreserveEmptyElements; |
} |
if ([[fidelityMatrix cellWithTag:FidelityWhitespace] state] == NSOnState) { |
options |= NSXMLNodePreserveWhitespace; |
} else { |
options &= ~NSXMLNodePreserveWhitespace; |
} |
if ([[fidelityMatrix cellWithTag:FidelityEntities] state] == NSOnState) { |
options |= NSXMLNodePreserveEntities; |
} else { |
options &= ~NSXMLNodePreserveEntities; |
} |
if ([[fidelityMatrix cellWithTag:FidelityPrefixes] state] == NSOnState) { |
options |= NSXMLNodePreservePrefixes; |
} else { |
options &= ~NSXMLNodePreservePrefixes; |
} |
if ([[fidelityMatrix cellWithTag:FidelityCDATA] state] == NSOnState) { |
options |= NSXMLNodePreserveCDATA; |
} else { |
options &= ~NSXMLNodePreserveCDATA; |
} |
if ([[fidelityMatrix cellWithTag:FidelityQuotes] state] == NSOnState) { |
options |= NSXMLNodePreserveQuotes; |
} else { |
options &= ~NSXMLNodePreserveQuotes; |
} |
if ([[fidelityMatrix cellWithTag:TidyXML] state] == NSOnState) { |
options |= NSXMLDocumentTidyXML; |
} else { |
options &= ~NSXMLDocumentTidyXML; |
} |
if ([[fidelityMatrix cellWithTag:TidyHTML] state] == NSOnState) { |
options |= NSXMLDocumentTidyHTML; |
} else { |
options &= ~NSXMLDocumentTidyHTML; |
} |
if ([[fidelityMatrix cellWithTag:PrettyPrint] state] == NSOnState) { |
options |= NSXMLNodePrettyPrint; |
} else { |
options &= ~NSXMLNodePrettyPrint; |
} |
if ([[fidelityMatrix cellWithTag:FidelityCharacters] state] == NSOnState) { |
options |= NSXMLNodePreserveCharacterReferences; |
} else { |
options &= ~NSXMLNodePreserveCharacterReferences; |
} |
if ([[fidelityMatrix cellWithTag:FidelityDTD] state] == NSOnState) { |
options |= NSXMLNodePreserveDTD; |
} else { |
options &= ~NSXMLNodePreserveDTD; |
} |
if ([[fidelityMatrix cellWithTag:UseXInclude] state] == NSOnState) { |
options |= NSXMLDocumentXInclude; |
} else { |
options &= ~NSXMLDocumentXInclude; |
} |
if ([[fidelityMatrix cellWithTag:Validate] state] == NSOnState) { |
options |= NSXMLDocumentValidate; |
} else { |
options &= ~NSXMLDocumentValidate; |
} |
} |
// The user has clicked the down button or used the shortcut apple-] to |
// move down the tree. Using KVC we set "current" to the selected child in |
// the table view |
- (IBAction)setCurrentToSelectedChildren:(id)sender { |
int selectedRow = [childrenTableView selectedRow]; |
NSXMLNode *node = [current childAtIndex:selectedRow]; |
if ([node kind] == NSXMLElementKind || [node kind] == NSXMLDocumentKind) { |
[childrenTableView deselectRow:selectedRow]; |
[self setValue:node forKey:@"current"]; |
} |
} |
// The user has clicked the up button or used the shortcut apple-[ to |
// move up the tree. Using KVC we set "current" to current's parent. |
- (IBAction)setCurrentToSelectedParent:(id)sender { |
[childrenTableView deselectRow:[childrenTableView selectedRow]]; |
[self setValue:[current parent] forKey:@"current"]; |
} |
// The user has selected a child from the upper right "+" menu on the second tab. |
// Here the sender's tag corresponds to the enum kind we create |
- (IBAction)addChildToCurrent:(id)sender { |
NSXMLNode *child = [[NSXMLNode alloc] initWithKind:[[sender selectedItem] tag]]; |
[children addObject:child]; |
[child release]; |
} |
// In the "Element" subtab of the second tab, the user has clicked "+" above the attributes table |
- (IBAction)addAttributeToCurrent:(id)sender { |
if ([[[children selectedObjects] objectAtIndex:0] isKindOfClass:[NSXMLElement class]]) { |
NSXMLNode *attribute = [NSXMLNode attributeWithName:@"name" stringValue:@"value"]; |
[attributes addObject:attribute]; |
} |
} |
// In the "Element" subtab of the second tab, the user has clicked "+" above the namespaces table |
- (IBAction)addNamespaceToCurrent:(id)sender { |
if ([[[children selectedObjects] objectAtIndex:0] isKindOfClass:[NSXMLElement class]]) { |
NSXMLNode *namespace = [NSXMLNode namespaceWithName:@"prefix" stringValue:@"uri"]; |
[namespaces addObject:namespace]; |
} |
} |
#pragma mark --- Getters and setters --- |
- (void)setData:(NSData *)theData encoding:(NSString *)encoding { |
if (data != theData) { |
[data release]; |
data = [theData retain]; |
// NSURLResponse's encoding is an IANA string. Use CF utilities to convert it to a CFStringEncoding then a NSStringEncoding |
NSStringEncoding nsEncoding = NSUTF8StringEncoding; // default to UTF-8 |
if (encoding) { |
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)encoding); |
if (cfEncoding != kCFStringEncodingInvalidId) { |
nsEncoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding); |
} |
} |
NSString *displayString = [[NSString alloc] initWithData:data encoding:nsEncoding]; |
[self setSourceTextViewString: displayString]; |
[displayString release]; |
} |
} |
- (NSData *)data { |
return [[data retain] autorelease]; |
} |
- (void)setURL:(NSURL *)theUrl { |
if (url != theUrl) { |
[url release]; |
url = [theUrl retain]; |
[urlTextField setStringValue:[url absoluteString]]; |
} |
} |
- (NSURL *)url { |
return [[url retain] autorelease]; |
} |
- (void)setDocument:(NSXMLDocument *)doc { |
if (document != doc) { |
[document release]; |
document = [doc retain]; |
} |
[self setResultTextViewString: [document XMLStringWithOptions:options]]; |
} |
- (NSXMLDocument *)document { |
return [[document retain] autorelease]; |
} |
- (void)setCurrent:(NSXMLNode *)theCurrent { |
if (current != theCurrent) { |
[current release]; |
current = [theCurrent retain]; |
} |
} |
- (NSXMLNode *)current { |
if (!current) { |
NSXMLDocument *doc = [NSXMLNode document]; |
[self setDocument:doc]; |
current = [doc retain]; |
} |
return [[current retain] autorelease]; |
} |
- (void)setSourceTextViewString:(NSString*)string |
{ |
if (!string) string = @""; |
[sourceTextView setString:string]; |
} |
- (void)setNodeTextViewString:(NSString*)string |
{ |
if (!string) string = @""; |
[nodeTextView setString:string]; |
} |
- (void)setXQuerySourceTextViewString:(NSString*)string |
{ |
if (!string) string = @""; |
[xquerySourceTextView setString:string]; |
} |
- (void)setXQueryResultTextViewString:(NSString*)string |
{ |
if (!string) string = @""; |
[xqueryResultTextView setString:string]; |
} |
- (void)setResultTextViewString:(NSString*)string |
{ |
if (!string) string = @""; |
[resultTextView setString:string]; |
} |
@end |
Copyright © 2012 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2012-05-30