1 /*
  2  * Copyright ©2012 SARA bv, The Netherlands
  3  *
  4  * This file is part of js-webdav-client.
  5  *
  6  * js-webdav-client is free software: you can redistribute it and/or modify
  7  * it under the terms of the GNU Lesser General Public License as published
  8  * by the Free Software Foundation, either version 3 of the License, or
  9  * (at your option) any later version.
 10  *
 11  * js-webdav-client is distributed in the hope that it will be useful,
 12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14  * GNU Lesser General Public License for more details.
 15  *
 16  * You should have received a copy of the GNU Lesser General Public License
 17  * along with js-webdav-client.  If not, see <http://www.gnu.org/licenses/>.
 18  */
 19 "use strict";
 20 
 21 // If nl.sara.webdav.Property is already defined, we have a namespace clash!
 22 if (nl.sara.webdav.Property !== undefined) {
 23   throw new nl.sara.webdav.Exception('Namespace name nl.sara.webdav.Property already taken, could not load JavaScript library for WebDAV connectivity.', nl.sara.webdav.Exception.NAMESPACE_TAKEN);
 24 }
 25 
 26 /**
 27  * @class a WebDAV property
 28  *
 29  * @param  {Node}      [xmlNode]              Optional; The xmlNode describing the propstat object (should be compliant with RFC 4918)
 30  * @param  {Number}    [status]               Optional; The (HTTP) status code
 31  * @param  {String}    [responsedescription]  Optional; The response description
 32  * @param  {String[]}  [errors]               Optional; An array of errors
 33  * @property  {String}    namespace            The namespace
 34  * @property  {String}    tagname              The tag name
 35  * @property  {NodeList}  xmlvalue             A NodeList with the value of this property
 36  * @property  {Number}    status               The (HTTP) status code
 37  * @property  {String}    responsedescription  The response description
 38  */
 39 nl.sara.webdav.Property = function(xmlNode, status, responsedescription, errors) {
 40   // First define private attributes
 41   Object.defineProperty(this, '_xmlvalue', {
 42     'value': null,
 43     'enumerable': false,
 44     'configurable': false,
 45     'writable': true
 46   });
 47   Object.defineProperty(this, '_errors', {
 48     'value': [],
 49     'enumerable': false,
 50     'configurable': false,
 51     'writable': true
 52   });
 53   // Second define public attributes
 54   Object.defineProperty(this, 'namespace', {
 55     'value': null,
 56     'enumerable': true,
 57     'configurable': false,
 58     'writable': true
 59   });
 60   Object.defineProperty(this, 'tagname', {
 61     'value': null,
 62     'enumerable': true,
 63     'configurable': false,
 64     'writable': true
 65   });
 66   Object.defineProperty(this, 'status', {
 67     'value': null,
 68     'enumerable': true,
 69     'configurable': false,
 70     'writable': true
 71   });
 72   Object.defineProperty(this, 'responsedescription', {
 73     'value': null,
 74     'enumerable': true,
 75     'configurable': false,
 76     'writable': true
 77   });
 78 
 79   // Constructor logic
 80   if ((typeof xmlNode !== 'undefined') && (xmlNode.nodeType === 1)) {
 81     this.namespace = xmlNode.namespaceURI;
 82     this.tagname = nl.sara.webdav.Ie.getLocalName(xmlNode);
 83     this.xmlvalue = xmlNode.childNodes;
 84   }
 85   if (status !== undefined) {
 86     this.status = status;
 87   }
 88   if (responsedescription !== undefined) {
 89     this.responsedescription = responsedescription;
 90   }
 91   if (errors instanceof Array) {
 92     for (var i = 0; i < errors.length; i++) {
 93       this.addError(errors[i]);
 94     }
 95   }
 96 };
 97 
 98 //######################### DEFINE PUBLIC ATTRIBUTES ###########################
 99 (function() {
100   // This creates a (private) static variable. It will contain all codecs
101   var codecNamespaces = {};
102 
103   Object.defineProperty(nl.sara.webdav.Property.prototype, 'xmlvalue', {
104     'set': function(value) {
105       if (value === null) {
106         this._xmlvalue = null;
107         return;
108       }
109       if (!nl.sara.webdav.Ie.isIE && !(value instanceof NodeList)) {
110         throw new nl.sara.webdav.Exception('xmlvalue must be an instance of NodeList', nl.sara.webdav.Exception.WRONG_TYPE);
111       }
112       this._xmlvalue = value;
113     },
114     'get': function() {
115       return this._xmlvalue;
116     }
117   });
118 
119 //########################## DEFINE PUBLIC METHODS #############################
120   /**
121    * Adds functions to encode or decode properties
122    *
123    * This allows exact control in how Property.xmlvalue is parsed when
124    * getParsedValue() is called or how it is rebuild when
125    * setValueAndRebuildXml() is called. You can specify two functions: 'fromXML'
126    * and 'toXML'. These should be complementary. That is, toXML should be able
127    * to create a NodeList based on the output of fromXML. For example:
128    * A == toXML(fromXML(A)) &&
129    * B == fromXML(toXML(B))
130    *
131    * @param    {nl.sara.webdav.Codec}  codec  The codec to add
132    * @returns  {void}
133    */
134   nl.sara.webdav.Property.addCodec = function(codec) {
135     if (typeof codec.namespace !== 'string') {
136       throw new nl.sara.webdav.Exception('addCodec: codec.namespace must be a String', nl.sara.webdav.Exception.WRONG_TYPE);
137     }
138     if (typeof codec.tagname !== 'string') {
139       throw new nl.sara.webdav.Exception('addCodec: codec.tagname must be a String', nl.sara.webdav.Exception.WRONG_TYPE);
140     }
141     if (codecNamespaces[codec.namespace] === undefined) {
142       codecNamespaces[codec.namespace] = {};
143     }
144     codecNamespaces[codec.namespace][codec.tagname] = {
145       'fromXML': (codec.fromXML ? codec.fromXML : undefined),
146       'toXML': (codec.toXML ? codec.toXML : undefined)
147     };
148   };
149 
150   /**
151    * Sets a new value and rebuilds xmlvalue
152    *
153    * If a codec for this property is specified, it will use this codec to
154    * rebuild xmlvallue. Else it will attempt to create one CDATA element with
155    * the string value of whatever was fiven as parameter.
156    *
157    * @param   {mixed}  value  The object to base the xmlvalue on
158    * @return  {void}
159    */
160   nl.sara.webdav.Property.prototype.setValueAndRebuildXml = function(value) {
161     // Call codec to automatically create correct 'xmlvalue'
162     var xmlDoc = document.implementation.createDocument(this.namespace, this.tagname, null);
163     if ((codecNamespaces[this.namespace] === undefined) ||
164         (codecNamespaces[this.namespace][this.tagname] === undefined) ||
165         (codecNamespaces[this.namespace][this.tagname]['toXML'] === undefined)) {
166       // No 'toXML' function set, so create a NodeList with just one CDATA node
167       if (value !== null) { // If the value is NULL, then we should add anything to the NodeList
168         var cdata = xmlDoc.createCDATASection(value);
169         xmlDoc.documentElement.appendChild(cdata);
170       }
171       this._xmlvalue = xmlDoc.documentElement.childNodes;
172     }else{
173       this._xmlvalue = codecNamespaces[this.namespace][this.tagname]['toXML'](value, xmlDoc).documentElement.childNodes;
174     }
175   };
176 
177   /**
178    * Parses the xmlvalue
179    *
180    * If a codec for this property is specified, it will use this codec to parse
181    * the XML nodes. Else it will attempt to parse it as text or CDATA elements.
182    *
183    * @return  {mixed}  If a codec is specified, the return type depends on that
184    * codec. If no codec is specified and at least one node in xmlvalue is not a
185    * text or CDATA node, it will return undefined. If xmlvalue is empty, it will
186    * return null.
187    */
188   nl.sara.webdav.Property.prototype.getParsedValue = function() {
189     // Call codec to automatically create correct 'value'
190     if (this._xmlvalue.length > 0) {
191       if ((codecNamespaces[this.namespace] === undefined) ||
192           (codecNamespaces[this.namespace][this.tagname] === undefined) ||
193           (codecNamespaces[this.namespace][this.tagname]['fromXML'] === undefined)) {
194         // No 'fromXML' function set, so try to create a text value based on TextNodes and CDATA nodes. If other nodes are present, set 'value' to null
195         var parsedValue = '';
196         for (var i = 0; i < this._xmlvalue.length; i++) {
197           var node = this._xmlvalue.item(i);
198           if ((node.nodeType === 3) || (node.nodeType === 4)) { // Make sure text and CDATA content is stored
199             parsedValue += node.nodeValue;
200           }else{ // If even one of the nodes is not text or CDATA, then we don't parse a text value at all
201             parsedValue = undefined;
202             break;
203           }
204         }
205         return parsedValue;
206       }else{
207         return codecNamespaces[this.namespace][this.tagname]['fromXML'](this._xmlvalue);
208       }
209     }
210     return null;
211   };
212 })(); // Ends the static scope
213 
214 /**
215 * Adds an error to this property
216 *
217 * @param    {Node}      error  The Node which represents the error
218 * @returns  {Property}         Itself for chaining multiple methods
219 */
220 nl.sara.webdav.Property.prototype.addError = function(error) {
221   if (!nl.sara.webdav.Ie.isIE && !(error instanceof Node)) {
222     throw new nl.sara.webdav.Exception('Error must be an instance of Node', nl.sara.webdav.Exception.WRONG_TYPE);
223   }
224   this._errors.push(error);
225   return this;
226 };
227 
228 /**
229 * Returns all errors
230 *
231 * @returns {array} An array of Node representing the error
232 */
233 nl.sara.webdav.Property.prototype.getErrors = function() {
234   return this._errors;
235 };
236 
237 /**
238  * Overloads the default toString() method so it returns the value of this property
239  *
240  * @returns  {String}  A string representation of this property
241  */
242 nl.sara.webdav.Property.prototype.toString = function() {
243   return this.getParsedValue();
244 };
245 
246 // End of Property
247