Skip to content
Browse files

rewrite list support

  • Loading branch information...
1 parent ac38a26 commit d65442a957ddbb86a2090a482c997264f0a4f116 @odrobnik odrobnik committed Feb 27, 2012
View
24 Core/Source/DTCSSListStyle.h
@@ -53,4 +53,28 @@ typedef enum
- (NSString *)prefixWithCounter:(NSInteger)counter;
+/**
+ @name Managing Item Numbering
+ */
+
+
+/**
+ Sets the starting item number for the text list.
+
+ The default value is `1`. This value will be used only for ordered lists, and ignored in other cases.
+ @param itemNum The item number.
+ */
+- (void)setStartingItemNumber:(NSInteger)itemNum;
+
+
+/**
+ Returns the starting item number for the text list.
+
+ The default value is `1`. This value will be used only for ordered lists, and ignored in other cases.
+ @returns The item number.
+ */
+- (NSInteger)startingItemNumber;
+
+
+
@end
View
7 Core/Source/DTCSSListStyle.m
@@ -20,6 +20,8 @@ @interface DTCSSListStyle ()
- (void)updateFromStyleDictionary:(NSDictionary *)styles;
+@property (nonatomic, assign) NSInteger startingItemNumber;
+
@end
@@ -32,6 +34,7 @@ @implementation DTCSSListStyle
DTCSSListStylePosition _position;
NSString *_imageName;
+ NSInteger _startingItemNumber;
}
+ (DTCSSListStyle *)listStyleWithStyles:(NSDictionary *)styles
@@ -71,6 +74,7 @@ - (id)initWithStyles:(NSDictionary *)styles
{
// default
_position = DTCSSListStylePositionOutside;
+ _startingItemNumber = 1;
[self updateFromStyleDictionary:styles];
}
@@ -264,7 +268,7 @@ - (void)updateFromStyleDictionary:(NSDictionary *)styles
- (NSString *)description
{
- return [NSString stringWithFormat:@"<%@ type=%d position=%d>", NSStringFromClass([self class]), _type, _position];
+ return [NSString stringWithFormat:@"<%@ 0x%x type=%d position=%d>", NSStringFromClass([self class]), self, _type, _position];
}
#pragma mark Copying
@@ -368,6 +372,7 @@ - (NSString *)prefixWithCounter:(NSInteger)counter
@synthesize type = _type;
@synthesize position = _position;
@synthesize imageName = _imageName;
+@synthesize startingItemNumber = _startingItemNumber;
@end
View
1 Core/Source/DTCoreTextParagraphStyle.m
@@ -265,6 +265,7 @@ - (id)copyWithZone:(NSZone *)zone
newObject.alignment = self.alignment;
newObject.baseWritingDirection = self.baseWritingDirection;
newObject.tabStops = self.tabStops; // copy
+ newObject.textLists = self.textLists; //copy
return newObject;
}
View
213 Core/Source/DTHTMLAttributedStringBuilder.m
@@ -26,6 +26,8 @@ @interface DTHTMLAttributedStringBuilder ()
- (void)_registerTagStartHandlers;
- (void)_registerTagEndHandlers;
+- (void)_flushCurrentTagContent:(NSString *)tagContent;
+- (void)_flushEmptyListItem;
@end
@@ -458,17 +460,17 @@ - (void)_registerTagStartHandlers
void (^liBlock)(void) = ^
{
// have inherited the correct list counter from parent
- DTHTMLElement *counterElement = currentTag.parent;
+// DTHTMLElement *counterElement = currentTag.parent;
- NSString *valueNum = [currentTag attributeForKey:@"value"];
- if (valueNum)
- {
- NSInteger value = [valueNum integerValue];
- counterElement.listCounter = value;
- currentTag.listCounter = value;
- }
-
- counterElement.listCounter++;
+// NSString *valueNum = [currentTag attributeForKey:@"value"];
+// if (valueNum)
+// {
+// NSInteger value = [valueNum integerValue];
+// counterElement.listCounter = value;
+// currentTag.listCounter = value;
+// }
+//
+// counterElement.listCounter++;
needsListItemStart = YES;
currentTag.paragraphStyle.paragraphSpacing = 0;
@@ -488,17 +490,41 @@ - (void)_registerTagStartHandlers
void (^olBlock)(void) = ^
{
+ if (needsListItemStart)
+ {
+ // we have an opening but not have flushed text since
+ needsNewLineBefore = YES;
+
+
+ currentTag.paragraphStyle.paragraphSpacing = 0;
+
+ // output the prefix
+ [self _flushEmptyListItem];
+ }
+
+ // create the appropriate list style from CSS
+ NSDictionary *styles = [currentTag styles];
+ DTCSSListStyle *newListStyle = [[DTCSSListStyle alloc] initWithStyles:styles];
+
+ // set a different starting value if set
NSString *valueNum = [currentTag attributeForKey:@"start"];
if (valueNum)
{
NSInteger value = [valueNum integerValue];
- currentTag.listCounter = value;
+ newListStyle.startingItemNumber = value;
}
- else
+
+ // append this list style to the current paragraph style text lists
+ NSMutableArray *textLists = [currentTag.paragraphStyle.textLists mutableCopy];
+ if (!textLists)
{
- currentTag.listCounter = 1;
+ textLists = [NSMutableArray array];
}
+
+ [textLists addObject:newListStyle];
+ currentTag.paragraphStyle.textLists = textLists;
+ // next text needs a NL before it
needsNewLineBefore = YES;
};
@@ -507,9 +533,33 @@ - (void)_registerTagStartHandlers
void (^ulBlock)(void) = ^
{
+ if (needsListItemStart)
+ {
+ // we have an opening but not have flushed text since
+ NSLog(@"open!");
+ needsNewLineBefore = YES;
+
+ [self _flushEmptyListItem];
+ }
+
+
needsNewLineBefore = YES;
- currentTag.listCounter = 0;
+ // create the appropriate list style from CSS
+ NSDictionary *styles = [currentTag styles];
+ DTCSSListStyle *newListStyle = [[DTCSSListStyle alloc] initWithStyles:styles];
+
+ // append this list style to the current paragraph style text lists
+ NSMutableArray *textLists = [currentTag.paragraphStyle.textLists mutableCopy];
+ if (!textLists)
+ {
+ textLists = [NSMutableArray array];
+ }
+ [textLists addObject:newListStyle];
+ currentTag.paragraphStyle.textLists = textLists;
+
+ // next text needs a NL before it
+ needsNewLineBefore = YES;
};
[_tagStartHandlers setObject:[ulBlock copy] forKey:@"ul"];
@@ -693,26 +743,43 @@ - (void)_registerTagEndHandlers
void (^ulBlock)(void) = ^
{
- if (currentTag.listDepth < 1)
+ // pop the current list style from the paragraph style text lists
+ NSMutableArray *textLists = [currentTag.paragraphStyle.textLists mutableCopy];
+ [textLists removeLastObject];
+ currentTag.paragraphStyle.textLists = textLists;
+
+ // if this was the last active list
+ if ([textLists count]==0)
{
// adjust spacing after last li to be the one defined for ol/ul
- NSRange effectiveRange;
+ NSInteger index = [tmpString length];
- NSMutableDictionary *finalAttributes = [[tmpString attributesAtIndex:[tmpString length]-1 effectiveRange:&effectiveRange] mutableCopy];
- CTParagraphStyleRef style = (__bridge CTParagraphStyleRef)[finalAttributes objectForKey:(id)kCTParagraphStyleAttributeName];
- DTCoreTextParagraphStyle *paragraphStyle = [DTCoreTextParagraphStyle paragraphStyleWithCTParagraphStyle:style];
-
- if (paragraphStyle.paragraphSpacing != currentTag.paragraphStyle.paragraphSpacing)
+ if (index)
{
- paragraphStyle.paragraphSpacing = currentTag.paragraphStyle.paragraphSpacing;
-
- CTParagraphStyleRef newParagraphStyle = [paragraphStyle createCTParagraphStyle];
- [finalAttributes setObject:CFBridgingRelease(newParagraphStyle) forKey:(id)kCTParagraphStyleAttributeName];
-
- [tmpString setAttributes:finalAttributes range:effectiveRange];
+ index--;
+
+ // get the paragraph style for the previous paragraph
+ NSRange effectiveRange;
+ CTParagraphStyleRef prevParagraphStyle = (__bridge CTParagraphStyleRef)[tmpString attribute:(id)kCTParagraphStyleAttributeName
+ atIndex:index
+ effectiveRange:&effectiveRange];
+
+ // convert it to DTCoreText
+ DTCoreTextParagraphStyle *paragraphStyle = [DTCoreTextParagraphStyle paragraphStyleWithCTParagraphStyle:prevParagraphStyle];
+
+ if (paragraphStyle.paragraphSpacing != currentTag.paragraphStyle.paragraphSpacing)
+ {
+ paragraphStyle.paragraphSpacing = currentTag.paragraphStyle.paragraphSpacing;
+
+ CTParagraphStyleRef newParagraphStyle = [paragraphStyle createCTParagraphStyle];
+
+ // because we have multiple paragraph styles per paragraph still, we need to extend towards the begin of the paragraph
+ NSRange paragraphRange = [[tmpString string] rangeOfParagraphAtIndex:effectiveRange.location];
+
+ [tmpString addAttribute:(id)kCTParagraphStyleAttributeName value:CFBridgingRelease(newParagraphStyle) range:paragraphRange];
+ }
}
}
-
};
[_tagEndHandlers setObject:[ulBlock copy] forKey:@"ul"];
@@ -744,6 +811,64 @@ - (void)_handleTagContent:(NSString *)string
}
+- (void)_flushEmptyListItem
+{
+ if (needsNewLineBefore)
+ {
+ if (!outputHasNewline)
+ {
+ [tmpString appendString:UNICODE_LINE_FEED];
+ outputHasNewline = YES;
+ }
+
+ needsNewLineBefore = NO;
+ }
+
+ // if we start a list, then we wait until we have actual text
+ if (needsListItemStart)
+ {
+ DTCSSListStyle *effectiveList = [currentTag.paragraphStyle.textLists lastObject];
+
+ NSInteger index = [tmpString length]-1;
+ NSInteger counter = 0;
+
+ if (index>=0)
+ {
+ // check if there was a list item before this one
+ index--;
+
+ NSRange prevListRange;
+ NSArray *prevLists = [tmpString attribute:DTTextListsAttribute atIndex:index effectiveRange:&prevListRange];
+
+ if ([prevLists containsObject:effectiveList])
+ {
+ NSInteger prevItemIndex = [tmpString itemNumberInTextList:effectiveList atIndex:index];
+ counter = prevItemIndex + 1;
+ }
+ else
+ {
+ // new list start
+ counter = [effectiveList startingItemNumber];
+ }
+ }
+ else
+ {
+ // new list start at beginning of string
+ counter = [effectiveList startingItemNumber];
+ }
+
+ NSAttributedString *prefixString = [currentTag prefixForListItemWithCounter:counter];
+
+ if (prefixString)
+ {
+ [tmpString appendAttributedString:prefixString];
+ outputHasNewline = NO;
+ }
+
+ needsListItemStart = NO;
+ }
+}
+
- (void)_flushCurrentTagContent:(NSString *)tagContent
{
NSAssert(dispatch_get_current_queue() == _stringAssemblyQueue, @"method called from invalid queue");
@@ -805,7 +930,37 @@ - (void)_flushCurrentTagContent:(NSString *)tagContent
// if we start a list, then we wait until we have actual text
if (needsListItemStart && [tagContents length] > 0 && ![tagContents isEqualToString:@" "])
{
- NSAttributedString *prefixString = [currentTag prefixForListItem];
+ DTCSSListStyle *effectiveList = [currentTag.paragraphStyle.textLists lastObject];
+
+ NSInteger index = [tmpString length]-1;
+ NSInteger counter = 0;
+
+ if (index>=0)
+ {
+ // check if there was a list item before this one
+ index--;
+
+ NSRange prevListRange;
+ NSArray *prevLists = [tmpString attribute:DTTextListsAttribute atIndex:index effectiveRange:&prevListRange];
+
+ if ([prevLists containsObject:effectiveList])
+ {
+ NSInteger prevItemIndex = [tmpString itemNumberInTextList:effectiveList atIndex:index];
+ counter = prevItemIndex + 1;
+ }
+ else
+ {
+ // new list start
+ counter = [effectiveList startingItemNumber];
+ }
+ }
+ else
+ {
+ // new list start at beginning of string
+ counter = [effectiveList startingItemNumber];
+ }
+
+ NSAttributedString *prefixString = [currentTag prefixForListItemWithCounter:counter];
if (prefixString)
{
View
6 Core/Source/DTHTMLElement.h
@@ -59,20 +59,18 @@ typedef enum
@property (nonatomic, assign) BOOL isColorInherited;
@property (nonatomic, assign) BOOL preserveNewlines;
@property (nonatomic, assign) DTHTMLElementFontVariant fontVariant;
-@property (nonatomic, copy) DTCSSListStyle *listStyle;
@property (nonatomic, assign) CGFloat textScale;
@property (nonatomic, assign) CGSize size;
-@property (nonatomic, readonly) NSInteger listDepth;
-@property (nonatomic) NSInteger listCounter;
@property (nonatomic, strong) NSDictionary *attributes;
- (NSAttributedString *)attributedString;
-- (NSAttributedString *)prefixForListItem;
+- (NSAttributedString *)prefixForListItemWithCounter:(NSUInteger)listCounter;
- (NSDictionary *)attributesDictionary;
- (void)parseStyleString:(NSString *)styleString;
- (void)applyStyleDictionary:(NSDictionary *)styles;
+- (NSDictionary *)styles;
- (void)addAdditionalAttribute:(id)attribute forKey:(id)key;
View
111 Core/Source/DTHTMLElement.m
@@ -68,7 +68,6 @@ @implementation DTHTMLElement
DTHTMLElementDisplayStyle _displayStyle;
DTHTMLElementFloatStyle floatStyle;
- DTCSSListStyle *_listStyle;
BOOL isColorInherited;
@@ -79,20 +78,17 @@ @implementation DTHTMLElement
CGFloat textScale;
CGSize size;
- NSInteger _listDepth;
- NSInteger _listCounter;
-
NSMutableArray *_children;
NSDictionary *_attributes; // contains all attributes from parsing
+
+ NSDictionary *_styles;
}
- (id)init
{
self = [super init];
if (self)
{
- _listDepth = -1;
- _listCounter = NSIntegerMin;
}
return self;
@@ -222,9 +218,9 @@ - (NSDictionary *)attributesDictionary
[tmpDict setObject:[NSNumber numberWithInteger:headerLevel] forKey:DTHeaderLevelAttribute];
}
- if (_listStyle)
+ if (paragraphStyle.textLists)
{
- [tmpDict setObject:[NSArray arrayWithObject:_listStyle] forKey:DTTextListsAttribute];
+ [tmpDict setObject:paragraphStyle.textLists forKey:DTTextListsAttribute];
}
return tmpDict;
@@ -267,7 +263,7 @@ - (NSAttributedString *)attributedString
}
}
-- (NSAttributedString *)prefixForListItem
+- (NSAttributedString *)prefixForListItemWithCounter:(NSUInteger)listCounter
{
NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
@@ -297,14 +293,14 @@ - (NSAttributedString *)prefixForListItem
}
// get calculated list style
- DTCSSListStyle *calculatedListStyle = [self calculatedListStyle];
+ DTCSSListStyle *calculatedListStyle = [self.paragraphStyle.textLists lastObject];// [self calculatedListStyle];
- if (_listStyle)
+ if (calculatedListStyle)
{
- [attributes setObject:[NSArray arrayWithObject:_listStyle] forKey:DTTextListsAttribute];
+ [attributes setObject:self.paragraphStyle.textLists forKey:DTTextListsAttribute];
}
- NSString *prefix = [calculatedListStyle prefixWithCounter:_listCounter];
+ NSString *prefix = [calculatedListStyle prefixWithCounter:listCounter];
if (prefix)
{
@@ -319,7 +315,7 @@ - (NSAttributedString *)prefixForListItem
// image invalid
calculatedListStyle.imageName = nil;
- prefix = [calculatedListStyle prefixWithCounter:_listCounter];
+ prefix = [calculatedListStyle prefixWithCounter:listCounter];
}
}
@@ -367,6 +363,9 @@ - (void)applyStyleDictionary:(NSDictionary *)styles
return;
}
+ // keep that for later lookup
+ _styles = styles;
+
NSString *fontSize = [styles objectForKey:@"font-size"];
if (fontSize)
{
@@ -691,8 +690,8 @@ - (void)applyStyleDictionary:(NSDictionary *)styles
}
}
- // list style became it's own object
- self.listStyle = [DTCSSListStyle listStyleWithStyles:styles];
+// // list style became it's own object
+// self.listStyle = [DTCSSListStyle listStyleWithStyles:styles];
NSString *widthString = [styles objectForKey:@"width"];
@@ -765,6 +764,11 @@ - (void)applyStyleDictionary:(NSDictionary *)styles
}
}
+- (NSDictionary *)styles
+{
+ return _styles;
+}
+
- (void)parseStyleString:(NSString *)styleString
{
NSDictionary *styles = [styleString dictionaryOfCSSStyles];
@@ -904,7 +908,7 @@ - (id)copyWithZone:(NSZone *)zone
newObject.preserveNewlines = self.preserveNewlines;
newObject.fontCache = self.fontCache; // reference
- newObject.listCounter = self.listCounter;
+ //newObject.listCounter = self.listCounter;
return newObject;
}
@@ -961,77 +965,6 @@ - (NSString *)path
return @"root";
}
-- (NSInteger)listDepth
-{
- if (_listDepth < 0)
- {
- // See if this is a list related element.
- if ([tagName isEqualToString:@"ol"] || [tagName isEqualToString:@"ul"] || [tagName isEqualToString:@"li"])
- {
- // Walk up the tree to the root. Increment the count every time we hit an OL or UL tag
- // so we have our nesting count correct.
- DTHTMLElement *elem = self;
- _listDepth = 0;
- while (elem.parent) {
- NSString *tag = elem.parent.tagName;
- if ([tag isEqualToString:@"ol"] || [tag isEqualToString:@"ul"])
- {
- _listDepth++;
- }
- elem = elem.parent;
- }
- }
- else {
- // We're not a list element, so set the depth to zero.
- _listDepth = 0;
- }
- }
- return _listDepth;
-}
-
-- (NSInteger)listCounter
-{
- // If the counter is set to NSIntegerMin, it hasn't been calculated or manually set.
- // Calculate it on demand.
- if (_listCounter == NSIntegerMin)
- {
- // See if this is an LI. No other elements get a counter.
- if ([tagName isEqualToString:@"li"])
- {
- // Count the number of LI elements in the parent until we reach self. That's our counter.
- NSInteger counter = 1;
- NSUInteger numChildren = [parent.children count];
- for (NSUInteger i = 0; i < numChildren; i++)
- {
- // We walk through the children and check for LI elements just in case someone
- // slipped us some bad HTML.
- DTHTMLElement *child = [parent.children objectAtIndex:i];
- if (child != self && [child.tagName isEqualToString:@"li"])
- {
- // Add one to the last LI's value just in case its listCounter property got overridden and
- // set to something other than its natural order in the elements list.
- counter = child.listCounter + 1;
- }
- else
- {
- break;
- }
- }
- _listCounter = counter;
- }
- else
- {
- _listCounter = 0;
- }
- }
- return _listCounter;
-}
-
-- (void)setListCounter:(NSInteger)count
-{
- _listCounter = count;
-}
-
- (NSMutableArray *)children
{
if (!_children)
@@ -1080,7 +1013,7 @@ - (void)setTextAttachment:(DTTextAttachment *)textAttachment
@synthesize preserveNewlines;
@synthesize displayStyle = _displayStyle;
@synthesize fontVariant;
-@synthesize listStyle = _listStyle;
+//@synthesize listStyle = _listStyle;
@synthesize textScale;
@synthesize size;
View
28 Core/Source/NSAttributedString+DTCoreText.h
@@ -6,6 +6,8 @@
// Copyright (c) 2012 Drobnik.com. All rights reserved.
//
+@class DTCSSListStyle;
+
@interface NSAttributedString (DTCoreText)
// convenience methods
@@ -14,6 +16,32 @@
// attachment handling
- (NSArray *)textAttachmentsWithPredicate:(NSPredicate *)predicate;
+/**
+ @name Calculating Ranges
+ */
+
+
+/**
+ Returns the index of the item at the given location within the list.
+
+ @param list The text list.
+ @param location The location of the item.
+ @returns Returns the index within the list.
+*/
+- (NSInteger)itemNumberInTextList:(DTCSSListStyle *)list atIndex:(NSUInteger)location;
+
+/**
+ Returns the range of the given text list that contains the given location.
+
+ @param list The text list.
+ @param location The location in the text.
+ @returns The range of the given text list containing the location.
+ */
+- (NSRange)rangeOfTextList:(DTCSSListStyle *)list atIndex:(NSUInteger)location;
+
+
+
+
// encoding back to HTML
- (NSString *)htmlString;
- (NSString *)plainTextString;
View
234 Core/Source/NSAttributedString+DTCoreText.m
@@ -65,6 +65,140 @@ - (NSArray *)textAttachmentsWithPredicate:(NSPredicate *)predicate
return nil;
}
+
+#pragma mark Calculating Ranges
+
+- (NSInteger)itemNumberInTextList:(DTCSSListStyle *)list atIndex:(NSUInteger)location
+{
+ NSRange effectiveRange;
+ NSArray *textListsAtIndex = [self attribute:DTTextListsAttribute atIndex:location effectiveRange:&effectiveRange];
+
+ if (!textListsAtIndex)
+ {
+ return 0;
+ }
+
+ // get outermost list
+ DTCSSListStyle *outermostList = [textListsAtIndex objectAtIndex:0];
+
+ // get the range of all lists
+ NSRange totalRange = [self rangeOfTextList:outermostList atIndex:location];
+
+ // get naked NSString
+ NSString *string = [[self string] substringWithRange:totalRange];
+
+ // entire string
+ NSRange range = NSMakeRange(0, [string length]);
+
+ NSMutableDictionary *countersPerList = [NSMutableDictionary dictionary];
+
+
+ [string enumerateSubstringsInRange:range options:NSStringEnumerationByParagraphs usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop)
+ {
+ NSRange actualRange = substringRange;
+ actualRange.location += totalRange.location;
+
+ NSRange paragraphListRange;
+ NSArray *textLists = [self attribute:DTTextListsAttribute atIndex:substringRange.location + totalRange.location effectiveRange:&paragraphListRange];
+
+ DTCSSListStyle *currentEffectiveList = [textLists lastObject];
+
+ NSNumber *key = [NSNumber numberWithInteger:[currentEffectiveList hash]]; // hash defaults to address
+ NSNumber *currentCounterNum = [countersPerList objectForKey:key];
+
+ NSInteger currentCounter=0;
+
+ if (!currentCounterNum)
+ {
+ currentCounter = currentEffectiveList.startingItemNumber;
+ }
+ else
+ {
+ currentCounter = [currentCounterNum integerValue]+1;
+ }
+
+ currentCounterNum = [NSNumber numberWithInteger:currentCounter];
+ [countersPerList setObject:currentCounterNum forKey:key];
+
+ if (NSLocationInRange(location, actualRange))
+ {
+ *stop = YES;
+ }
+ }
+ ];
+
+ NSNumber *key = [NSNumber numberWithInteger:[list hash]]; // hash defaults to address
+ NSNumber *currentCounterNum = [countersPerList objectForKey:key];
+
+ return [currentCounterNum integerValue];
+}
+
+- (NSRange)rangeOfTextList:(DTCSSListStyle *)list atIndex:(NSUInteger)location
+{
+ NSInteger searchIndex = location;
+
+ NSArray *textListsAtIndex;
+ NSInteger minFoundIndex = NSIntegerMax;
+ NSInteger maxFoundIndex = 0;
+
+ BOOL foundList = NO;
+
+ do
+ {
+ NSRange effectiveRange;
+ textListsAtIndex = [self attribute:DTTextListsAttribute atIndex:searchIndex effectiveRange:&effectiveRange];
+
+ if([textListsAtIndex containsObject:list])
+ {
+ foundList = YES;
+
+ searchIndex = effectiveRange.location;
+
+ minFoundIndex = MIN(minFoundIndex, searchIndex);
+ maxFoundIndex = MAX(maxFoundIndex, NSMaxRange(effectiveRange));
+ }
+
+ if (!searchIndex || !foundList)
+ {
+ // reached beginning of string
+ break;
+ }
+
+ searchIndex--;
+ }
+ while (foundList && searchIndex>0);
+
+ // if we didn't find the list at all, return
+ if (!foundList)
+ {
+ return NSMakeRange(0, NSNotFound);
+ }
+
+ // now search forward
+
+ searchIndex = maxFoundIndex;
+
+ while (searchIndex < [self length])
+ {
+ NSRange effectiveRange;
+ textListsAtIndex = [self attribute:DTTextListsAttribute atIndex:searchIndex effectiveRange:&effectiveRange];
+
+ foundList = [textListsAtIndex containsObject:list];
+
+ if (!foundList)
+ {
+ break;
+ }
+
+ searchIndex = NSMaxRange(effectiveRange);
+
+ minFoundIndex = MIN(minFoundIndex, effectiveRange.location);
+ maxFoundIndex = MAX(maxFoundIndex, NSMaxRange(effectiveRange));
+ }
+
+ return NSMakeRange(minFoundIndex, maxFoundIndex-minFoundIndex);
+}
+
#pragma mark HTML Encoding
@@ -197,12 +331,16 @@ - (NSString *)htmlString
NSInteger location = 0;
- DTCSSListStyle *previousListStyle = nil;
+ NSArray *previousListStyles = nil;
for (NSString *oneParagraph in paragraphs)
{
+
+
NSRange paragraphRange = NSMakeRange(location, [oneParagraph length]);
+ BOOL needsToRemovePrefix = NO;
+
// skip empty paragraph at end
if (oneParagraph == [paragraphs lastObject] && !paragraphRange.length)
{
@@ -225,8 +363,12 @@ - (NSString *)htmlString
NSDictionary *paraAttributes = [self attributesAtIndex:paragraphRange.location effectiveRange:NULL];
+ NSLog(@"%@", paraAttributes);
+
// lets see if we have a list style
- DTCSSListStyle *listStyle = [[paraAttributes objectForKey:DTTextListsAttribute] lastObject];
+ NSArray *currentListStyles = [paraAttributes objectForKey:DTTextListsAttribute];
+
+ DTCSSListStyle *effectiveListStyle = [currentListStyles lastObject];
CTParagraphStyleRef paraStyle = (__bridge CTParagraphStyleRef)[paraAttributes objectForKey:(id)kCTParagraphStyleAttributeName];
NSString *paraStyleString = nil;
@@ -265,26 +407,51 @@ - (NSString *)htmlString
{
blockElement = [NSString stringWithFormat:@"h%d", [headerLevel integerValue]];
}
- else
- {
- if (listStyle)
+
+ NSLog(@"string: '%@' range: %@", [plainString substringWithRange:paragraphRange], NSStringFromRange(paragraphRange));
+
+
+ // close until we are at current or nil
+ if ([previousListStyles count]>[currentListStyles count])
{
- if (!previousListStyle)
+ NSMutableArray *closingStyles = [previousListStyles mutableCopy];
+
+ do
+ {
+ DTCSSListStyle *closingStyle = [closingStyles lastObject];
+
+ if (closingStyle == effectiveListStyle)
+ {
+ break;
+ }
+
+ // end of a list block
+ [retString appendString:[self _tagRepresentationForListStyle:closingStyle closingTag:YES]];
+ [retString appendString:@"\n"];
+
+ [closingStyles removeLastObject];
+
+ previousListStyles = closingStyles;
+ }
+ while ([closingStyles count]);
+ }
+
+ if (effectiveListStyle)
+ {
+ // next text needs to have list prefix removed
+ needsToRemovePrefix = YES;
+
+ if (![previousListStyles containsObject:effectiveListStyle])
{
// beginning of a list block
- [retString appendString:[self _tagRepresentationForListStyle:listStyle closingTag:NO]];
+ [retString appendString:[self _tagRepresentationForListStyle:effectiveListStyle closingTag:NO]];
+ [retString appendString:@"\n"];
}
blockElement = @"li";
}
else
{
- if (previousListStyle)
- {
- // need closing of previous list block
- [retString appendString:[self _tagRepresentationForListStyle:previousListStyle closingTag:YES]];
- }
-
blockElement = @"p";
}
@@ -298,7 +465,7 @@ - (NSString *)htmlString
blockElement = @"span";
}
}
- }
+
if ([paraStyleString length])
{
@@ -317,10 +484,24 @@ - (NSString *)htmlString
{
NSDictionary *attributes = [self attributesAtIndex:index longestEffectiveRange:&effectiveRange inRange:paragraphRange];
- index += effectiveRange.length;
-
+ NSString *plainSubString =[plainString substringWithRange:effectiveRange];
- NSString *subString = [[plainString substringWithRange:effectiveRange] stringByAddingHTMLEntities];
+ if (effectiveListStyle && needsToRemovePrefix)
+ {
+ NSInteger counter = [self itemNumberInTextList:effectiveListStyle atIndex:index];
+ NSString *prefix = [effectiveListStyle prefixWithCounter:counter];
+
+ if ([plainSubString hasPrefix:prefix])
+ {
+ plainSubString = [plainSubString substringFromIndex:[prefix length]];
+ }
+
+ needsToRemovePrefix = NO;
+ }
+
+ index += effectiveRange.length;
+
+ NSString *subString = [plainSubString stringByAddingHTMLEntities];
if (!subString)
{
@@ -525,14 +706,25 @@ - (NSString *)htmlString
// end of paragraph loop
- previousListStyle = listStyle;
+ previousListStyles = [currentListStyles copy];
}
// close list if still open
- if (previousListStyle)
+ if ([previousListStyles count])
{
- // need closing of previous list block
- [retString appendString:[self _tagRepresentationForListStyle:previousListStyle closingTag:YES]];
+ NSMutableArray *closingStyles = [previousListStyles mutableCopy];
+
+ do
+ {
+ DTCSSListStyle *closingStyle = [closingStyles lastObject];
+
+ // end of a list block
+ [retString appendString:[self _tagRepresentationForListStyle:closingStyle closingTag:YES]];
+ [retString appendString:@"\n"];
+
+ [closingStyles removeLastObject];
+ }
+ while ([closingStyles count]);
}
return retString;
View
2 Core/Source/NSString+Paragraphs.h
@@ -13,4 +13,6 @@
- (NSRange)rangeOfParagraphsContainingRange:(NSRange)range parBegIndex:(NSUInteger *)parBegIndex parEndIndex:(NSUInteger *)parEndIndex;
- (BOOL)indexIsAtBeginningOfParagraph:(NSUInteger)index;
+- (NSRange)rangeOfParagraphAtIndex:(NSUInteger)index;
+
@end
View
10 Core/Source/NSString+Paragraphs.m
@@ -49,4 +49,14 @@ - (BOOL)indexIsAtBeginningOfParagraph:(NSUInteger)index
return NO;
}
+- (NSRange)rangeOfParagraphAtIndex:(NSUInteger)index
+{
+ NSUInteger start;
+ NSUInteger end;
+
+ [self rangeOfParagraphsContainingRange:NSMakeRange(index, 1) parBegIndex:&start parEndIndex:&end];
+
+ return NSMakeRange(start, end-start);
+}
+
@end
View
15 Core/Test/Source/MacUnitTest.m
@@ -13,7 +13,7 @@
#import </usr/include/objc/objc-class.h>
#define TESTCASE_FILE_EXTENSION @"html"
-//#define ONLY_TEST_CURRENT 1
+#define ONLY_TEST_CURRENT 1
@implementation MacUnitTest
@@ -130,7 +130,7 @@ - (void)internalTestCaseWithURL:(NSURL *)URL withTempPath:(NSString *)tempPath
NSAttributedString *iosAttributedString = [doc generatedAttributedString];
NSString *iosString = [iosAttributedString string];
- /*
+
NSMutableString *dumpOutput = [[NSMutableString alloc] init];
NSData *dump = [macString dataUsingEncoding:NSUTF8StringEncoding];
for (NSInteger i = 0; i < [dump length]; i++)
@@ -166,9 +166,14 @@ - (void)internalTestCaseWithURL:(NSURL *)URL withTempPath:(NSString *)tempPath
break;
}
}
- */
- //NSLog(@"%@", dumpOutput);
+
+ NSLog(@"%@", dumpOutput);
+
+ NSParagraphStyle *para = [macAttributedString attribute:NSParagraphStyleAttributeName atIndex:0 effectiveRange:NULL];
+ NSTextList *list = [para.textLists lastObject];
+
+
STAssertEquals([macString length], [iosString length], @"String output has different length");
@@ -213,7 +218,7 @@ - (void)internalTestCaseWithURL:(NSURL *)URL withTempPath:(NSString *)tempPath
if (!isSame)
{
- STFail(@"First differing haracter at index %d: iOS '%@' versus Mac '%@'", i, [ios stringByAddingSlashEscapes] , [mac stringByAddingSlashEscapes]);
+ STFail(@"First differing character at index %d: iOS '%@' versus Mac '%@'", i, [ios stringByAddingSlashEscapes] , [mac stringByAddingSlashEscapes]);
}
break;
}
View
4 DTCoreText.xcodeproj/project.pbxproj
@@ -25,6 +25,7 @@
1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; };
288765A50DF7441C002DB57D /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 288765A40DF7441C002DB57D /* CoreGraphics.framework */; };
A704C93A13901FDB0045CFC6 /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A704C93913901FDB0045CFC6 /* ImageIO.framework */; };
+ A70A737A14FBC9060043E932 /* NSString+Paragraphs.m in Sources */ = {isa = PBXBuildFile; fileRef = A788C94B14863E8700E1AFD9 /* NSString+Paragraphs.m */; };
A70B4C9F1486558200873A4A /* DTCoreText.h in Headers */ = {isa = PBXBuildFile; fileRef = A70B4C9E1486558200873A4A /* DTCoreText.h */; settings = {ATTRIBUTES = (Public, ); }; };
A70B4CA01486558200873A4A /* DTCoreText.h in Headers */ = {isa = PBXBuildFile; fileRef = A70B4C9E1486558200873A4A /* DTCoreText.h */; settings = {ATTRIBUTES = (Public, ); }; };
A70F11DF148632CD009202BF /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; };
@@ -1468,6 +1469,7 @@
A7B0B56D14D9921F0091C2C9 /* NSAttributedString+DTCoreText.m in Sources */,
A7C7AD0114DA6955005A9C69 /* NSString+SlashEscaping.m in Sources */,
A7C7AD0814DA7C17005A9C69 /* MacUnitTest.m in Sources */,
+ A70A737A14FBC9060043E932 /* NSString+Paragraphs.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1708,7 +1710,6 @@
ONLY_ACTIVE_ARCH = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
- TEST_AFTER_BUILD = YES;
WRAPPER_EXTENSION = octest;
};
name = Debug;
@@ -1731,7 +1732,6 @@
MACOSX_DEPLOYMENT_TARGET = 10.7;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
- TEST_AFTER_BUILD = YES;
WRAPPER_EXTENSION = octest;
};
name = Release;
View
9 Demo/Resources/CurrentTest.html
@@ -1,5 +1,8 @@
<ul>
-<li>one</li>
-<li>two</li>
-<li><ul><li>a</li><li>b</li></ul></li>
+ <li>First item</li>
+ <li>Second item
+ <ul>
+ <li>Nested unordered lists</li>
+ <li>Nested line 2</li>
+</ul>
</ul>
View
1 Readme.markdown
@@ -92,5 +92,6 @@ In the following "Mac" means the initWithHTML: methods there, "DTCoreText" means
- I suspect that Mac makes use of the -webkit-margin-* CSS styles for spacing the paragraphs, DTCoreText only uses the -webkit-margin-bottom and margin-bottom at present.
- Mac supports CSS following addresses, e.g. "ul ul" to change the list style for stacked lists. DTCoreText does not support that and so list bullets stay the same for multiple levels.
- Mac outputs newlines in PRE tags as \n, iOS replaces these with Unicode Line Feed characters so that the paragraph spacing is applied at the end of the PRE tag, not after each line. (iOS wraps code lines when layouting)
+- Mac does not properly encode a double list start. iOS prints the empty list prefix.
If you find an issue then you are welcome to fix it and contribute your fix via a GitHub pull request.

0 comments on commit d65442a

Please sign in to comment.
Something went wrong with that request. Please try again.