diff --git a/Source/CFXMLParser.c b/Source/CFXMLParser.c index 2dfb525..e323b87 100644 --- a/Source/CFXMLParser.c +++ b/Source/CFXMLParser.c @@ -381,13 +381,157 @@ CFStringRef CFXMLCreateStringByEscapingEntities(CFAllocatorRef allocator, CFStringRef string, CFDictionaryRef entitiesDictionary) { - return NULL; + CFMutableStringRef result; + CFIndex length; + CFIndex idx; + + if (string == NULL) + return NULL; + + length = CFStringGetLength (string); + result = CFStringCreateMutable (allocator, 0); + for (idx = 0 ; idx < length ; ++idx) + { + UniChar c = CFStringGetCharacterAtIndex (string, idx); + switch (c) + { + case '&': + CFStringAppendCString (result, "&", kCFStringEncodingASCII); + break; + case '<': + CFStringAppendCString (result, "<", kCFStringEncodingASCII); + break; + case '>': + CFStringAppendCString (result, ">", kCFStringEncodingASCII); + break; + case '"': + CFStringAppendCString (result, """, kCFStringEncodingASCII); + break; + case '\'': + CFStringAppendCString (result, "'", kCFStringEncodingASCII); + break; + default: + CFStringAppendCharacters (result, &c, 1); + break; + } + } + + return result; } CFStringRef CFXMLCreateStringByUnescapingEntities(CFAllocatorRef allocator, CFStringRef string, CFDictionaryRef entitiesDictionary) { - return NULL; + CFMutableStringRef result; + CFIndex length; + CFIndex idx; + + if (string == NULL) + return NULL; + + length = CFStringGetLength (string); + result = CFStringCreateMutable (allocator, 0); + idx = 0; + while (idx < length) + { + UniChar c = CFStringGetCharacterAtIndex (string, idx); + Boolean handled = false; + + if (c == '&') + { + CFIndex semi = idx + 1; + + while (semi < length + && CFStringGetCharacterAtIndex (string, semi) != ';') + semi += 1; + + if (semi < length && semi > idx + 1) + { + if (CFStringGetCharacterAtIndex (string, idx + 1) == '#') + { + /* Numeric character reference, decimal or hexadecimal. */ + CFIndex pos = idx + 2; + long value = 0; + int base = 10; + + if (pos < semi + && (CFStringGetCharacterAtIndex (string, pos) == 'x' + || CFStringGetCharacterAtIndex (string, pos) == 'X')) + { + base = 16; + pos += 1; + } + for ( ; pos < semi ; ++pos) + { + UniChar d = CFStringGetCharacterAtIndex (string, pos); + int v; + + if (d >= '0' && d <= '9') + v = d - '0'; + else if (base == 16 && d >= 'a' && d <= 'f') + v = d - 'a' + 10; + else if (base == 16 && d >= 'A' && d <= 'F') + v = d - 'A' + 10; + else + { + value = -1; + break; + } + value = value * base + v; + } + if (value >= 0 && value <= 0xFFFF) + { + UniChar u = (UniChar)value; + CFStringAppendCharacters (result, &u, 1); + handled = true; + } + } + else + { + CFStringRef name; + const void *repl = NULL; + + name = CFStringCreateWithSubstring (NULL, string, + CFRangeMake (idx + 1, semi - idx - 1)); + if (CFStringCompare (name, CFSTR ("amp"), 0) + == kCFCompareEqualTo) + repl = CFSTR ("&"); + else if (CFStringCompare (name, CFSTR ("lt"), 0) + == kCFCompareEqualTo) + repl = CFSTR ("<"); + else if (CFStringCompare (name, CFSTR ("gt"), 0) + == kCFCompareEqualTo) + repl = CFSTR (">"); + else if (CFStringCompare (name, CFSTR ("quot"), 0) + == kCFCompareEqualTo) + repl = CFSTR ("\""); + else if (CFStringCompare (name, CFSTR ("apos"), 0) + == kCFCompareEqualTo) + repl = CFSTR ("'"); + else if (entitiesDictionary != NULL) + repl = CFDictionaryGetValue (entitiesDictionary, name); + + if (repl != NULL) + { + CFStringAppend (result, repl); + handled = true; + } + CFRelease (name); + } + + if (handled) + idx = semi + 1; + } + } + + if (!handled) + { + CFStringAppendCharacters (result, &c, 1); + idx += 1; + } + } + + return result; } diff --git a/Tests/CFXMLParser/TestInfo b/Tests/CFXMLParser/TestInfo new file mode 100644 index 0000000..e69de29 diff --git a/Tests/CFXMLParser/escaping.m b/Tests/CFXMLParser/escaping.m new file mode 100755 index 0000000..f064cc7 --- /dev/null +++ b/Tests/CFXMLParser/escaping.m @@ -0,0 +1,54 @@ +#include "CoreFoundation/CFXMLParser.h" +#include "CoreFoundation/CFString.h" + +#include "../CFTesting.h" + +int +main (void) +{ + CFStringRef esc; + CFStringRef un; + + /* Each of the five predefined entities is escaped, other text is left + * alone. + */ + esc = CFXMLCreateStringByEscapingEntities (NULL, CFSTR ("ac&d\"e'f"), + NULL); + PASS_CFEQ(esc, CFSTR ("a<b>c&d"e'f"), + "The five predefined entities are escaped."); + + /* Unescaping is the inverse. */ + un = CFXMLCreateStringByUnescapingEntities (NULL, esc, NULL); + PASS_CFEQ(un, CFSTR ("ac&d\"e'f"), + "Unescaping the escaped string round-trips."); + CFRelease (un); + CFRelease (esc); + + /* A string with no markup characters is unchanged. */ + esc = CFXMLCreateStringByEscapingEntities (NULL, CFSTR ("plain text 123"), + NULL); + PASS_CFEQ(esc, CFSTR ("plain text 123"), + "Text without markup characters is copied verbatim."); + CFRelease (esc); + + /* The empty string escapes to the empty string. */ + esc = CFXMLCreateStringByEscapingEntities (NULL, CFSTR (""), NULL); + PASS_CFEQ(esc, CFSTR (""), "The empty string escapes to itself."); + CFRelease (esc); + + /* Unescaping resolves named entities and numeric character references. */ + un = CFXMLCreateStringByUnescapingEntities (NULL, + CFSTR ("x & y <z> A "q" '"), NULL); + PASS_CFEQ(un, CFSTR ("x & y A \"q\" '"), + "Named entities and a decimal character reference are resolved."); + CFRelease (un); + + /* A hexadecimal character reference resolves too. */ + un = CFXMLCreateStringByUnescapingEntities (NULL, CFSTR ("AB"), + NULL); + PASS_CFEQ(un, CFSTR ("AB"), + "A hexadecimal character reference is resolved."); + CFRelease (un); + + return 0; +}