PageRenderTime 48ms CodeModel.GetById 16ms app.highlight 26ms RepoModel.GetById 1ms app.codeStats 1ms

/Objective-J/CFPropertyList.js

http://github.com/cacaodev/cappuccino
JavaScript | 670 lines | 471 code | 150 blank | 49 comment | 71 complexity | e6eac93e871caccd799c0dc2093d95c1 MD5 | raw file
  1/*
  2 * CFPropertyList.js
  3 * Objective-J
  4 *
  5 * Created by Francisco Tolmasky.
  6 * Copyright 2008-2010, 280 North, Inc.
  7 *
  8 * This library is free software; you can redistribute it and/or
  9 * modify it under the terms of the GNU Lesser General Public
 10 * License as published by the Free Software Foundation; either
 11 * version 2.1 of the License, or (at your option) any later version.
 12 *
 13 * This library is distributed in the hope that it will be useful,
 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 16 * Lesser General Public License for more details.
 17 *
 18 * You should have received a copy of the GNU Lesser General Public
 19 * License along with this library; if not, write to the Free Software
 20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 21 */
 22
 23var OBJECT_COUNT   = 0;
 24
 25GLOBAL(objj_generateObjectUID) = function()
 26{
 27    return OBJECT_COUNT++;
 28}
 29
 30GLOBAL(CFPropertyList) = function()
 31{
 32    this._UID = objj_generateObjectUID();
 33}
 34
 35// We are really liberal when accepting DOCTYPEs.
 36CFPropertyList.DTDRE = /^\s*(?:<\?\s*xml\s+version\s*=\s*\"1.0\"[^>]*\?>\s*)?(?:<\!DOCTYPE[^>]*>\s*)?/i
 37CFPropertyList.XMLRE = /^\s*(?:<\?\s*xml\s+version\s*=\s*\"1.0\"[^>]*\?>\s*)?(?:<\!DOCTYPE[^>]*>\s*)?<\s*plist[^>]*\>/i;
 38
 39CFPropertyList.FormatXMLDTD = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">";
 40CFPropertyList.Format280NorthMagicNumber = "280NPLIST";
 41
 42// Serialization Formats
 43
 44CFPropertyList.FormatOpenStep         = 1,
 45CFPropertyList.FormatXML_v1_0         = 100,
 46CFPropertyList.FormatBinary_v1_0      = 200,
 47CFPropertyList.Format280North_v1_0    = -1000;
 48
 49CFPropertyList.sniffedFormatOfString = function(/*String*/ aString)
 50{
 51    // Check if this is an XML Plist.
 52    if (aString.match(CFPropertyList.XMLRE))
 53        return CFPropertyList.FormatXML_v1_0;
 54
 55    if (aString.substr(0, CFPropertyList.Format280NorthMagicNumber.length) === CFPropertyList.Format280NorthMagicNumber)
 56       return CFPropertyList.Format280North_v1_0;
 57
 58    return NULL;
 59}
 60
 61// Serialization
 62
 63CFPropertyList.dataFromPropertyList = function(/*CFPropertyList*/ aPropertyList, /*Format*/ aFormat)
 64{
 65    var data = new CFMutableData();
 66
 67    data.setRawString(CFPropertyList.stringFromPropertyList(aPropertyList, aFormat));
 68
 69    return data;
 70}
 71
 72CFPropertyList.stringFromPropertyList = function(/*CFPropertyList*/ aPropertyList, /*Format*/ aFormat)
 73{
 74    if (!aFormat)
 75        aFormat = CFPropertyList.Format280North_v1_0;
 76
 77    var serializers = CFPropertyListSerializers[aFormat];
 78
 79    return  serializers["start"]() +
 80            serializePropertyList(aPropertyList, serializers) +
 81            serializers["finish"]();
 82}
 83
 84#ifdef COMMONJS
 85CFPropertyList.readPropertyListFromFile = function(/*String*/ aFilePath)
 86{
 87    return CFPropertyList.propertyListFromString(FILE.read(aFilePath, { charset:"UTF-8" }));
 88}
 89
 90CFPropertyList.writePropertyListToFile = function(/*CFPropertyList*/ aPropertyList, /*String*/ aFilePath, /*Format*/ aFormat)
 91{
 92    return FILE.write(aFilePath, CFPropertyList.stringFromPropertyList(aPropertyList, aFormat), { charset:"UTF-8" });
 93}
 94CFPropertyList.modifyPlist = function(/*String*/ aFilePath, /*Function*/ aCallback, /*String*/ aFormat)
 95{
 96    var string = FILE.read(aFilePath, { charset:"UTF-8" });
 97    var format = CFPropertyList.sniffedFormatOfString(string);
 98    var plist = CFPropertyList.propertyListFromString(string, format);
 99
100    aCallback(plist);
101
102    CFPropertyList.writePropertyListToFile(plist, aFilePath, aFormat || format);
103}
104#endif
105
106function serializePropertyList(/*CFPropertyList*/ aPropertyList, /*Object*/ serializers)
107{
108    var type = typeof aPropertyList,
109        valueOf = aPropertyList.valueOf(),
110        typeValueOf = typeof valueOf;
111
112    if (type !== typeValueOf)
113    {
114        type = typeValueOf;
115        aPropertyList = valueOf;
116    }
117
118    if (aPropertyList === YES || aPropertyList === NO)
119        type = "boolean";
120
121    else if (type === "number")
122    {
123        // A number like 3.4028234663852885e+54 should not be written as an integer, even that it is
124        // an integer - it'd be awfully long if written by expanding it. Worse, if written as an
125        // integer with scientific notation the parseInt() used to read it back will parse it as
126        // just 3, thereby wasting many hours of sanity when you're trying to nib2cib a table and
127        // columns are mysteriously showing up 3 pixels wide.
128        if (FLOOR(aPropertyList) === aPropertyList && ("" + aPropertyList).indexOf('e') == -1)
129            type = "integer";
130        else
131            type = "real";
132    }
133
134    else if (type !== "string")
135    {
136        if (aPropertyList.slice)
137            type = "array";
138
139        else
140            type = "dictionary";
141    }
142
143    return serializers[type](aPropertyList, serializers);
144}
145
146var CFPropertyListSerializers = { };
147
148CFPropertyListSerializers[CFPropertyList.FormatXML_v1_0] =
149{
150    "start":        function()
151                    {
152                        return CFPropertyList.FormatXMLDTD + "<plist version = \"1.0\">";
153                    },
154
155    "finish":       function()
156                    {
157                        return "</plist>";
158                    },
159
160    "string":       function(/*String*/ aString)
161                    {
162                        return "<string>" + encodeHTMLComponent(aString) + "</string>";
163                    },
164
165    "boolean" :     function(/*Boolean*/ aBoolean)
166                    {
167                        return aBoolean ? "<true/>" : "<false/>";
168                    },
169
170    "integer":      function(/*Integer*/ anInteger)
171                    {
172                        return "<integer>" + anInteger + "</integer>";
173                    },
174
175    "real":         function(/*Float*/ aFloat)
176                    {
177                        return "<real>" + aFloat + "</real>";
178                    },
179
180    "array":        function(/*Array*/ anArray, /*Object*/ serializers)
181                    {
182                        var index = 0,
183                            count = anArray.length,
184                            string = "<array>";
185
186                        for (; index < count; ++index)
187                            string += serializePropertyList(anArray[index], serializers);
188
189                        return string + "</array>";
190                    },
191
192    "dictionary":   function(/*CFDictionary*/ aDictionary, /*Object*/ serializers)
193                    {
194                        var keys = aDictionary._keys,
195                            index = 0,
196                            count = keys.length,
197                            string = "<dict>";
198
199                        for (; index < count; ++index)
200                        {
201                            var key = keys[index];
202
203                            string += "<key>" + key + "</key>";
204                            string += serializePropertyList(aDictionary.valueForKey(key), serializers);
205                        }
206
207                        return string + "</dict>";
208                    }
209}
210
211// 280 North Plist Format
212
213var ARRAY_MARKER        = "A",
214    DICTIONARY_MARKER   = "D",
215    FLOAT_MARKER        = "f",
216    INTEGER_MARKER      = "d",
217    STRING_MARKER       = "S",
218    TRUE_MARKER         = "T",
219    FALSE_MARKER        = "F",
220    KEY_MARKER          = "K",
221    END_MARKER          = "E";
222
223CFPropertyListSerializers[CFPropertyList.Format280North_v1_0] =
224{
225    "start":        function()
226                    {
227                        return CFPropertyList.Format280NorthMagicNumber + ";1.0;";
228                    },
229
230    "finish":       function()
231                    {
232                        return "";
233                    },
234
235    "string" :      function(/*String*/ aString)
236                    {
237                        return STRING_MARKER + ';' + aString.length + ';' + aString;
238                    },
239
240    "boolean" :     function(/*Boolean*/ aBoolean)
241                    {
242                        return (aBoolean ? TRUE_MARKER : FALSE_MARKER) + ';';
243                    },
244
245    "integer":      function(/*Integer*/ anInteger)
246                    {
247                        var string = "" + anInteger;
248
249                        return INTEGER_MARKER + ';' + string.length + ';' + string;
250                    },
251
252    "real":         function(/*Float*/ aFloat)
253                    {
254                        var string = "" + aFloat;
255
256                        return FLOAT_MARKER + ';' + string.length + ';' + string;
257                    },
258
259    "array":        function(/*Array*/ anArray, /*Object*/ serializers)
260                    {
261                        var index = 0,
262                            count = anArray.length,
263                            string = ARRAY_MARKER + ';';
264
265                        for (; index < count; ++index)
266                            string += serializePropertyList(anArray[index], serializers);
267
268                        return string + END_MARKER + ';';
269                    },
270
271    "dictionary":   function(/*CFDictionary*/ aDictionary, /*Object*/ serializers)
272                    {
273                        var keys = aDictionary._keys,
274                            index = 0,
275                            count = keys.length,
276                            string = DICTIONARY_MARKER +';';
277
278                        for (; index < count; ++index)
279                        {
280                            var key = keys[index];
281
282                            string += KEY_MARKER + ';' + key.length + ';' + key;
283                            string += serializePropertyList(aDictionary.valueForKey(key), serializers);
284                        }
285
286                        return string + END_MARKER + ';';
287                    }
288}
289
290// Deserialization
291
292var XML_XML                 = "xml",
293    XML_DOCUMENT            = "#document",
294
295    PLIST_PLIST             = "plist",
296    PLIST_KEY               = "key",
297    PLIST_DICTIONARY        = "dict",
298    PLIST_ARRAY             = "array",
299    PLIST_STRING            = "string",
300    PLIST_DATE              = "date",
301    PLIST_BOOLEAN_TRUE      = "true",
302    PLIST_BOOLEAN_FALSE     = "false",
303    PLIST_NUMBER_REAL       = "real",
304    PLIST_NUMBER_INTEGER    = "integer",
305    PLIST_DATA              = "data";
306
307#define NODE_NAME(anXMLNode)        (String(anXMLNode.nodeName))
308#define NODE_TYPE(anXMLNode)        (anXMLNode.nodeType)
309#define TEXT_CONTENT(anXMLNode)     (anXMLNode.textContent || (anXMLNode.textContent !== "" && textContent([anXMLNode])))
310#define FIRST_CHILD(anXMLNode)      (anXMLNode.firstChild)
311#define NEXT_SIBLING(anXMLNode)     (anXMLNode.nextSibling)
312#define PARENT_NODE(anXMLNode)      (anXMLNode.parentNode)
313#define DOCUMENT_ELEMENT(aDocument) (aDocument.documentElement)
314
315#define HAS_ATTRIBUTE_VALUE(anXMLNode, anAttributeName, aValue) (anXMLNode.getAttribute(anAttributeName) === aValue)
316
317#define IS_OF_TYPE(anXMLNode, aType) (NODE_NAME(anXMLNode) === aType)
318#define IS_PLIST(anXMLNode) IS_OF_TYPE(anXMLNode, PLIST_PLIST)
319
320#define IS_WHITESPACE(anXMLNode) (NODE_TYPE(anXMLNode) === 8 || NODE_TYPE(anXMLNode) === 3)
321#define IS_DOCUMENTTYPE(anXMLNode) (NODE_TYPE(anXMLNode) === 10)
322
323#define PLIST_NEXT_SIBLING(anXMLNode) while ((anXMLNode = NEXT_SIBLING(anXMLNode)) && IS_WHITESPACE(anXMLNode));
324#define PLIST_FIRST_CHILD(anXMLNode) { anXMLNode = FIRST_CHILD(anXMLNode); if (anXMLNode !== NULL && IS_WHITESPACE(anXMLNode)) PLIST_NEXT_SIBLING(anXMLNode) }
325
326var textContent = function(nodes)
327{
328    var text = "",
329        index = 0,
330        count = nodes.length;
331
332    for (; index < count; ++index)
333    {
334        var node = nodes[index];
335
336        if (node.nodeType === 3 || node.nodeType === 4)
337            text += node.nodeValue;
338
339        else if (node.nodeType !== 8)
340            text += textContent(node.childNodes);
341    }
342
343    return text;
344}
345
346var _plist_traverseNextNode = function(anXMLNode, stayWithin, stack)
347{
348    var node = anXMLNode;
349
350    PLIST_FIRST_CHILD(node);
351
352    // If this element has a child, traverse to it.
353    if (node)
354        return node;
355
356    // If not, first check if it is a container class (as opposed to a designated leaf).
357    // If it is, then we have to pop this container off the stack, since it is empty.
358    if (NODE_NAME(anXMLNode) === PLIST_ARRAY || NODE_NAME(anXMLNode) === PLIST_DICTIONARY)
359        stack.pop();
360
361    // If not, next check whether it has a sibling.
362    else
363    {
364        if (node === stayWithin)
365            return NULL;
366
367        node = anXMLNode;
368
369        PLIST_NEXT_SIBLING(node);
370
371        if (node)
372            return node;
373    }
374
375    // If it doesn't, start working our way back up the node tree.
376    node = anXMLNode;
377
378    // While we have a node and it doesn't have a sibling (and we're within our stayWithin),
379    // keep moving up.
380    while (node)
381    {
382        var next = node;
383
384        PLIST_NEXT_SIBLING(next);
385
386        // If we have a next sibling, just go to it.
387        if (next)
388            return next;
389
390        var node = PARENT_NODE(node);
391
392        // If we are being asked to move up, and our parent is the stay within, then just
393        if (stayWithin && node === stayWithin)
394            return NULL;
395
396        // Pop the stack if we have officially "moved up"
397        stack.pop();
398    }
399
400    return NULL;
401}
402
403CFPropertyList.propertyListFromData = function(/*Data*/ aData, /*Format*/ aFormat)
404{
405    return CFPropertyList.propertyListFromString(aData.rawString(), aFormat);
406}
407
408CFPropertyList.propertyListFromString = function(/*String*/ aString, /*Format*/ aFormat)
409{
410    if (!aFormat)
411        aFormat = CFPropertyList.sniffedFormatOfString(aString);
412
413    if (aFormat === CFPropertyList.FormatXML_v1_0)
414        return CFPropertyList.propertyListFromXML(aString);
415
416    if (aFormat === CFPropertyList.Format280North_v1_0)
417        return propertyListFrom280NorthString(aString);
418
419    return NULL;
420}
421
422// 280 North Plist Format
423
424var ARRAY_MARKER        = "A",
425    DICTIONARY_MARKER   = "D",
426    FLOAT_MARKER        = "f",
427    INTEGER_MARKER      = "d",
428    STRING_MARKER       = "S",
429    TRUE_MARKER         = "T",
430    FALSE_MARKER        = "F",
431    KEY_MARKER          = "K",
432    END_MARKER          = "E";
433
434function propertyListFrom280NorthString(/*String*/ aString)
435{
436    var stream = new MarkedStream(aString),
437
438        marker = NULL,
439
440        key = "",
441        object = NULL,
442        plistObject = NULL,
443
444        containers = [],
445        currentContainer = NULL;
446
447    while (marker = stream.getMarker())
448    {
449        if (marker === END_MARKER)
450        {
451            containers.pop();
452            continue;
453        }
454
455        var count = containers.length;
456
457        if (count)
458            currentContainer = containers[count - 1];
459
460        if (marker === KEY_MARKER)
461        {
462            key = stream.getString();
463            marker = stream.getMarker();
464        }
465
466        switch (marker)
467        {
468            case ARRAY_MARKER:      object = []
469                                    containers.push(object);
470                                    break;
471            case DICTIONARY_MARKER: object = new CFMutableDictionary();
472                                    containers.push(object);
473                                    break;
474
475            case FLOAT_MARKER:      object = parseFloat(stream.getString());
476                                    break;
477
478            case INTEGER_MARKER:    object = parseInt(stream.getString(), 10);
479                                    break;
480
481            case STRING_MARKER:     object = stream.getString();
482                                    break;
483
484            case TRUE_MARKER:       object = YES;
485                                    break;
486            case FALSE_MARKER:      object = NO;
487                                    break;
488
489            default:                throw new Error("*** " + marker + " marker not recognized in Plist.");
490        }
491
492        if (!plistObject)
493            plistObject = object;
494
495        else if (currentContainer)
496            // If the container is an array...
497            if (currentContainer.slice)
498                currentContainer.push(object);
499            else
500                currentContainer.setValueForKey(key, object);
501    }
502
503    return plistObject;
504}
505
506function encodeHTMLComponent(/*String*/ aString)
507{
508    return aString.replace(/&/g,'&amp;').replace(/"/g, '&quot;').replace(/'/g, '&apos;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
509}
510
511function decodeHTMLComponent(/*String*/ aString)
512{
513    return aString.replace(/&quot;/g, '"').replace(/&apos;/g, '\'').replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&');
514}
515
516function parseXML(/*String*/ aString)
517{
518    if (window.DOMParser)
519        return DOCUMENT_ELEMENT(new window.DOMParser().parseFromString(aString, "text/xml"));
520
521    else if (window.ActiveXObject)
522    {
523        XMLNode = new ActiveXObject("Microsoft.XMLDOM");
524
525        // Extract the DTD, which confuses IE.
526        var matches = aString.match(CFPropertyList.DTDRE);
527
528        if (matches)
529            aString = aString.substr(matches[0].length);
530
531        XMLNode.loadXML(aString);
532
533        return XMLNode
534    }
535
536    return NULL;
537}
538
539CFPropertyList.propertyListFromXML = function(/*String | XMLNode*/ aStringOrXMLNode)
540{
541    var XMLNode = aStringOrXMLNode;
542
543    if (aStringOrXMLNode.valueOf && typeof aStringOrXMLNode.valueOf() === "string")
544        XMLNode = parseXML(aStringOrXMLNode);
545
546    // Skip over DOCTYPE and so forth.
547    while (IS_OF_TYPE(XMLNode, XML_DOCUMENT) || IS_OF_TYPE(XMLNode, XML_XML))
548        PLIST_FIRST_CHILD(XMLNode);
549
550    // Skip over the DOCTYPE... see a pattern?
551    if (IS_DOCUMENTTYPE(XMLNode))
552        PLIST_NEXT_SIBLING(XMLNode);
553
554    // If this is not a PLIST, bail.
555    if (!IS_PLIST(XMLNode))
556        return NULL;
557
558    var key = "",
559        object = NULL,
560        plistObject = NULL,
561
562        plistNode = XMLNode,
563
564        containers = [],
565        currentContainer = NULL;
566
567    while (XMLNode = _plist_traverseNextNode(XMLNode, plistNode, containers))
568    {
569        var count = containers.length;
570
571        if (count)
572            currentContainer = containers[count - 1];
573
574        if (NODE_NAME(XMLNode) === PLIST_KEY)
575        {
576            key = TEXT_CONTENT(XMLNode);
577            PLIST_NEXT_SIBLING(XMLNode);
578        }
579
580        switch (String(NODE_NAME(XMLNode)))
581        {
582            case PLIST_ARRAY:           object = []
583                                        containers.push(object);
584                                        break;
585            case PLIST_DICTIONARY:      object = new CFMutableDictionary();
586                                        containers.push(object);
587                                        break;
588
589            case PLIST_NUMBER_REAL:     object = parseFloat(TEXT_CONTENT(XMLNode));
590                                        break;
591            case PLIST_NUMBER_INTEGER:  object = parseInt(TEXT_CONTENT(XMLNode), 10);
592                                        break;
593
594            case PLIST_STRING:          if (HAS_ATTRIBUTE_VALUE(XMLNode, "type", "base64"))
595                                            object = FIRST_CHILD(XMLNode) ? CFData.decodeBase64ToString(TEXT_CONTENT(XMLNode)) : "";
596                                        else
597                                            object = decodeHTMLComponent(FIRST_CHILD(XMLNode) ? TEXT_CONTENT(XMLNode) : "");
598
599                                        break;
600                                        
601            case PLIST_DATE:            var timestamp = Date.parseISO8601(TEXT_CONTENT(XMLNode));
602                                        object = isNaN(timestamp) ? new Date() : new Date(timestamp);
603                                        break;
604
605            case PLIST_BOOLEAN_TRUE:    object = YES;
606                                        break;
607            case PLIST_BOOLEAN_FALSE:   object = NO;
608                                        break;
609
610            case PLIST_DATA:            object = new CFMutableData();
611                                        var data_bytes = FIRST_CHILD(XMLNode) ? CFData.decodeBase64ToArray(TEXT_CONTENT(XMLNode), YES) : [];
612                                        object.setBytes(data_bytes);
613                                        break;
614
615            default:                    throw new Error("*** " + NODE_NAME(XMLNode) + " tag not recognized in Plist.");
616        }
617
618        if (!plistObject)
619            plistObject = object;
620
621        else if (currentContainer)
622            // If the container is an array...
623            if (currentContainer.slice)
624                currentContainer.push(object);
625            else
626                currentContainer.setValueForKey(key, object);
627    }
628
629    return plistObject;
630}
631
632GLOBAL(kCFPropertyListOpenStepFormat)      = CFPropertyList.FormatOpenStep;
633GLOBAL(kCFPropertyListXMLFormat_v1_0)      = CFPropertyList.FormatXML_v1_0;
634GLOBAL(kCFPropertyListBinaryFormat_v1_0)   = CFPropertyList.FormatBinary_v1_0;
635GLOBAL(kCFPropertyList280NorthFormat_v1_0) = CFPropertyList.Format280North_v1_0;
636
637GLOBAL(CFPropertyListCreate) = function()
638{
639    return new CFPropertyList();
640}
641
642GLOBAL(CFPropertyListCreateFromXMLData) = function(/*Data*/ data)
643{
644    return CFPropertyList.propertyListFromData(data, CFPropertyList.FormatXML_v1_0);
645}
646
647GLOBAL(CFPropertyListCreateXMLData) = function(/*PropertyList*/ aPropertyList)
648{
649    return CFPropertyList.dataFromPropertyList(aPropertyList, CFPropertyList.FormatXML_v1_0);
650}
651
652GLOBAL(CFPropertyListCreateFrom280NorthData) = function(/*Data*/ data)
653{
654    return CFPropertyList.propertyListFromData(data, CFPropertyList.Format280North_v1_0);
655}
656
657GLOBAL(CFPropertyListCreate280NorthData) = function(/*PropertyList*/ aPropertyList)
658{
659    return CFPropertyList.dataFromPropertyList(aPropertyList, CFPropertyList.Format280North_v1_0);
660}
661
662GLOBAL(CPPropertyListCreateFromData) = function(/*CFData*/ data, /*Format*/ aFormat)
663{
664    return CFPropertyList.propertyListFromData(data, aFormat);
665}
666
667GLOBAL(CPPropertyListCreateData) = function(/*PropertyList*/ aPropertyList, /*Format*/ aFormat)
668{
669    return CFPropertyList.dataFromPropertyList(aPropertyList, aFormat);
670}