//
// XTHtmlTagContainer.m
// XTads
//
// Created by Rune Berg on 10/05/2018.
// Copyright © 2018 Rune Berg. All rights reserved.
//
#import "XTHtmlTag_private.h"
#import "XTHtmlTagContainer.h"
#import "XTHtmlTagContainer_private.h"
#import "XTHtmlTagSecondOutermost.h"
#import "XTOutputTextParserHtml.h"
#import "XTLogger.h"
#import "XTHtmlTagCenter.h"
#import "XTHtmlUtils.h"
@implementation XTHtmlTagContainer
static XTLogger* logger;
@synthesize cachedIsEnclosedByListOrListItem = _cachedIsEnclosedByListOrListItem;
@synthesize hasFormattedExit = _hasFormattedExit;
+ (void)initialize
{
logger = [XTLogger loggerForClass:[XTHtmlTagContainer class]];
}
- (instancetype)init
{
self = [super init];
if (self) {
_contents = [NSMutableArray arrayWithCapacity:10];
_formattingSpecForHtmlTag = [XTFormattingSpecificationForHtmlTag new];
_closed = NO;
_cachedIsEnclosedByListOrListItem = nil;
_hasFormattedExit = NO;
}
return self;
}
- (void)onParsing:(NSObject *)parser
{
[parser appendTagToCurrentContainer:self];
//TODO !!! reconsider vs html tads
[parser pushContainer:self];
}
// see htmltags.cpp, CHtmlTagContainer::get_next_fmt_tag
- (XTHtmlTag *)getNextTagToFormat:(NSObject *)formatter
textHandler:(XTBaseTextHandler *)textHandler
shouldFormat:(BOOL)shouldFormat
{
[self assertHasContainer];
/*
* if I have a child, return it; otherwise, return the next sibling
* or container's sibling (etc) as though we were an ordinary tag
*/
if (self.contents.firstObject != nil) {
/* return my first child */
return self.contents.firstObject;
} else {
//TODO !!! write test game for this case
/*
* I have no children. Call my format_exit() method right now.
* Normally, the last child would call this on the way out; since
* we have no children, though, no one would otherwise call this.
* Note that, as usual, we only want to call format_exit() if the
* tag is finished (i.e., closed).
*/
if (self.closed) {
//if (! self.hasFormatted) { //TODO exp rm for size_unspec/10b etc. bugs
if (shouldFormat) {
[self formatExit:formatter textHandler:textHandler];
}
//}
}
/*
* get the next item using the non-container algorithm; this
* will find our container's next sibling, or its container's
* next sibling, and so on, until we find a container with a
* sibling or run out of containers
*/
return [super getNextTagToFormat:formatter textHandler:textHandler shouldFormat:shouldFormat];
}
}
- (void)format:(NSObject *)formatter
textHandler:(XTBaseTextHandler *)textHandler
{
[self.formattingSpecForHtmlTag formattingEntry:formatter tagContainer:self];
}
- (void)appendToContents:(XTHtmlTag *)tag
{
XTHtmlTag *lastInContents = self.contents.lastObject;
if (lastInContents != nil) {
lastInContents.nextSibling = tag;
}
[self.contents addObject:tag];
tag.container = self;
}
- (void)removeFromContents:(XTHtmlTag *)tag
{
//XT_DEF_SELNAME;
//XT_WARN_2(@"of %@ : %@", self.name, tag.name);
NSUInteger idxOfTag = [self.contents indexOfObject:tag];
if (idxOfTag != NSNotFound) {
if (idxOfTag >= 1) {
XTHtmlTag *tagBefore = [self.contents objectAtIndex:(idxOfTag - 1)];
tagBefore.nextSibling = tag.nextSibling;
}
[self.contents removeObjectAtIndex:idxOfTag];
} else {
XT_DEF_SELNAME;
XT_WARN_1(@"contents don't have tag %@", tag.name);
}
}
- (BOOL)hasContents
{
BOOL res = (self.contents.count >= 1);
return res;
}
//TODO !!! override as in htmltags.h
- (void) preClose:(NSObject *)parser
{
// Nothing by default
}
/*
* When we reach the end of a container, the parser will call on_close
* on the current container to let it know that it's been closed.
* Note that we don't need to pop the container off the parser stack,
* as the parser will always do this automatically. Note also that
* this is called while we're still the active container.
*/
- (void)onClose:(NSObject *)parser
{
self.closed = YES;
}
/*
* receive notification that we've just finished closing the tag; the
* immediate container is now re-established as the active container
*/
- (void)postClose:(NSObject *)parser
{
// Nothing to do by default
}
- (void)checkNotHasFormatted
{
// Nothing - allow re-traversal for "dangling" open containers
}
- (void)formatExit:(NSObject *)formatter
textHandler:(XTBaseTextHandler *)textHandler
{
[self.formattingSpecForHtmlTag formattingExit:formatter];
[self postFormatForBlockLevel:formatter textHandler:textHandler];
[self removeChildren];
_hasFormattedExit = YES;
}
- (void)removeChildren
{
NSMutableArray *childrenToRemove = [NSMutableArray array];
NSUInteger numObjsToRemove = self.contents.count;
if (numObjsToRemove >= 1) {
// Make sure each child's container is set to nil
for (id obj in self.contents) {
if ([obj isKindOfClass:[XTHtmlTag class]]) {
XTHtmlTag *tag = (XTHtmlTag *)obj;
[childrenToRemove addObject:tag];
} else {
int brkpt = 1;
}
}
for (XTHtmlTag *tag in childrenToRemove) {
[tag removeFromContainer];
}
[self.contents removeAllObjects];
//XT_DEF_SELNAME;
//XT_WARN_3(@"removed all %lu children from contents of <%@> (uid=%lu)", numObjsToRemove, self.name, self.uniqueId);
}
}
- (void)removeChildrenUntilFirstOpenContainer
{
NSMutableArray *childrenToRemove = [NSMutableArray array];
for (id obj in self.contents) {
if ([obj isKindOfClass:[XTHtmlTagContainer class]]) {
XTHtmlTagContainer *container = (XTHtmlTagContainer *)obj;
if (! container.closed) {
break;
}
}
if ([obj isKindOfClass:[XTHtmlTag class]]) {
if (! [obj isKindOfClass:[XTHtmlTagSecondOutermost class]]) {
XTHtmlTag *tag = (XTHtmlTag *)obj;
[childrenToRemove addObject:tag];
}
}
}
for (XTHtmlTag *tag in childrenToRemove) {
[tag removeFromContainer];
}
}
- (BOOL)isInList
{
BOOL res;
if (self.cachedIsEnclosedByListOrListItem != nil) {
res = [self.cachedIsEnclosedByListOrListItem boolValue];
} else {
res = [XTHtmlUtils tagIsEnclosedByListOrListItem:self];
_cachedIsEnclosedByListOrListItem = [NSNumber numberWithBool:res];
}
return res;
}
- (NSUInteger)recursivelyCountChildren
{
NSUInteger res = 1; // self
if (self.contents != nil) {
for (XTHtmlTag *tag in self.contents) {
res += [tag recursivelyCountChildren];
}
}
return res;
}
@end