157 lines
6 KiB
Mathematica
157 lines
6 KiB
Mathematica
|
//
|
||
|
// MLBasePaser.m
|
||
|
// monalxmpp
|
||
|
//
|
||
|
// Created by Anurodh Pokharel on 4/11/20.
|
||
|
// Copyright © 2020 Monal.im. All rights reserved.
|
||
|
//
|
||
|
|
||
|
#import "MLConstants.h"
|
||
|
#import "MLBasePaser.h"
|
||
|
|
||
|
//#define DebugParser(...) DDLogDebug(__VA_ARGS__)
|
||
|
#define DebugParser(...)
|
||
|
|
||
|
@interface MLXMLNode()
|
||
|
@property (atomic, readwrite) MLXMLNode* parent;
|
||
|
-(MLXMLNode*) addChildNodeWithoutCopy:(MLXMLNode*) child;
|
||
|
@end
|
||
|
|
||
|
@interface MLBasePaser ()
|
||
|
{
|
||
|
//this stak is needed to hold strong references to all nodes until they are dispatched to our _completion callback
|
||
|
//(the parent references of the MLXMLNodes are weak and don't hold the parents alive)
|
||
|
NSMutableArray* _currentStack;
|
||
|
stanza_completion_t _completion;
|
||
|
NSMutableArray* _namespacePrefixes;
|
||
|
}
|
||
|
@end
|
||
|
|
||
|
@implementation MLBasePaser
|
||
|
|
||
|
-(id) initWithCompletion:(stanza_completion_t) completion
|
||
|
{
|
||
|
self = [super init];
|
||
|
_completion = completion;
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
-(void) reset
|
||
|
{
|
||
|
_currentStack = [NSMutableArray new];
|
||
|
}
|
||
|
|
||
|
-(void) parserDidStartDocument:(NSXMLParser*) parser
|
||
|
{
|
||
|
DDLogInfo(@"Document start");
|
||
|
[self reset];
|
||
|
}
|
||
|
|
||
|
-(void) parser:(NSXMLParser*) parser didStartMappingPrefix:(NSString*) prefix toURI:(NSString*) namespaceURI
|
||
|
{
|
||
|
DebugParser(@"Got new namespace prefix mapping for '%@' to '%@'...", prefix, namespaceURI);
|
||
|
}
|
||
|
|
||
|
-(void) parser:(NSXMLParser*) parser didEndMappingPrefix:(NSString*) prefix
|
||
|
{
|
||
|
DebugParser(@"Namespace prefix '%@' now out of scope again...", prefix);
|
||
|
}
|
||
|
|
||
|
-(void) parser:(NSXMLParser*) parser didStartElement:(NSString*) elementName namespaceURI:(NSString*) namespaceURI qualifiedName:(NSString*) qName attributes:(NSDictionary*) attributeDict
|
||
|
{
|
||
|
NSInteger depth = [_currentStack count] + 1; //this makes the depth in here equal to the depth in didEndElement:
|
||
|
DebugParser(@"Started element: %@ :: %@ (%@) depth %ld", elementName, namespaceURI, qName, depth);
|
||
|
|
||
|
//use appropriate MLXMLNode child classes for iq, message and presence stanzas
|
||
|
MLXMLNode* newNode;
|
||
|
if(depth == 2 && [elementName isEqualToString:@"iq"] && [namespaceURI isEqualToString:@"jabber:client"])
|
||
|
newNode = [XMPPIQ alloc];
|
||
|
else if(depth == 2 && [elementName isEqualToString:@"message"] && [namespaceURI isEqualToString:@"jabber:client"])
|
||
|
newNode = [XMPPMessage alloc];
|
||
|
else if(depth == 2 && [elementName isEqualToString:@"presence"] && [namespaceURI isEqualToString:@"jabber:client"])
|
||
|
newNode = [XMPPPresence alloc];
|
||
|
else if(depth >= 3 && [elementName isEqualToString:@"x"] && [namespaceURI isEqualToString:@"jabber:x:data"])
|
||
|
newNode = [XMPPDataForm alloc];
|
||
|
else
|
||
|
newNode = [MLXMLNode alloc];
|
||
|
newNode = [newNode initWithElement:elementName andNamespace:namespaceURI withAttributes:attributeDict andChildren:@[] andData:nil];
|
||
|
|
||
|
DebugParser(@"Current stack: %@", _currentStack);
|
||
|
//add new node to tree (each node needs a prototype MLXMLNode element and a mutable string to hold its future
|
||
|
//char data added to the MLXMLNode when the xml element is closed
|
||
|
newNode.parent = [_currentStack lastObject][@"node"];
|
||
|
[_currentStack addObject:@{@"node": newNode, @"charData": [NSMutableString new]}];
|
||
|
}
|
||
|
|
||
|
-(void) parser:(NSXMLParser*) parser foundCharacters:(NSString*) string
|
||
|
{
|
||
|
DebugParser(@"Got new xml character data: '%@'", string);
|
||
|
NSInteger depth = [_currentStack count];
|
||
|
if(depth == 0)
|
||
|
{
|
||
|
DDLogError(@"Got xml character data outside of any element!");
|
||
|
[self fakeStreamError];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
[[_currentStack lastObject][@"charData"] appendString:string];
|
||
|
DebugParser(@"_currentCharData is now: '%@'", [_currentStack lastObject][@"charData"]);
|
||
|
}
|
||
|
|
||
|
-(void) parser:(NSXMLParser*) parser didEndElement:(NSString*) elementName namespaceURI:(NSString*) namespaceURI qualifiedName:(NSString*) qName
|
||
|
{
|
||
|
NSInteger depth = [_currentStack count];
|
||
|
NSDictionary* topmostStackElement = [_currentStack lastObject];
|
||
|
MLXMLNode* currentNode = ((MLXMLNode*)topmostStackElement[@"node"]);
|
||
|
|
||
|
if([topmostStackElement[@"charData"] length])
|
||
|
currentNode.data = [topmostStackElement[@"charData"] copy];
|
||
|
|
||
|
DebugParser(@"Ended element: %@ :: %@ (%@) depth %ld", elementName, namespaceURI, qName, depth);
|
||
|
|
||
|
MLXMLNode* parent = currentNode.parent;
|
||
|
if(parent)
|
||
|
{
|
||
|
DebugParser(@"Ascending from child %@ to parent %@", currentNode.element, parent.element);
|
||
|
if(depth > 2) //don't add all received stanzas/nonzas as childs to our stream header (that would create a memory leak!)
|
||
|
{
|
||
|
DebugParser(@"Adding %@ to parent %@", currentNode.element, parent.element);
|
||
|
[parent addChildNodeWithoutCopy:currentNode];
|
||
|
}
|
||
|
}
|
||
|
[_currentStack removeLastObject];
|
||
|
|
||
|
//only call completion for stanzas, not for inner elements inside stanzas and not for our outermost stream start element
|
||
|
if(depth == 2)
|
||
|
_completion(currentNode);
|
||
|
}
|
||
|
|
||
|
-(void) parserDidEndDocument:(NSXMLParser*) parser
|
||
|
{
|
||
|
DDLogInfo(@"Document end");
|
||
|
}
|
||
|
|
||
|
-(void) parser:(NSXMLParser*) parser foundIgnorableWhitespace:(NSString*) whitespaceString
|
||
|
{
|
||
|
DebugParser(@"Found ignorable whitespace: '%@'", whitespaceString);
|
||
|
}
|
||
|
|
||
|
-(void) parser:(NSXMLParser*) parser parseErrorOccurred:(NSError*) parseError
|
||
|
{
|
||
|
DDLogError(@"XML parse error occurred: line: %ld , col: %ld desc: %@ ",(long)[parser lineNumber],
|
||
|
(long)[parser columnNumber], [parseError localizedDescription]);
|
||
|
[self fakeStreamError];
|
||
|
}
|
||
|
|
||
|
-(void) fakeStreamError
|
||
|
{
|
||
|
//fake stream error and let xmpp.m handle it
|
||
|
_completion([[MLXMLNode alloc] initWithElement:@"error" andNamespace:@"http://etherx.jabber.org/streams" withAttributes:@{} andChildren:@[
|
||
|
[[MLXMLNode alloc] initWithElement:@"bad-format" andNamespace:@"urn:ietf:params:xml:ns:xmpp-streams" withAttributes:@{} andChildren:@[
|
||
|
[[MLXMLNode alloc] initWithElement:@"text" andNamespace:@"urn:ietf:params:xml:ns:xmpp-streams" withAttributes:@{} andChildren:@[] andData:@"Could not parse XML coming from server"]
|
||
|
] andData:nil]
|
||
|
] andData:nil]);
|
||
|
}
|
||
|
|
||
|
@end
|