webglade

JavaScript library to dynamically create XUL GUI from Glade XML files
git clone https://logand.com/git/webglade.git/
Log | Files | Refs | README | LICENSE

XMDManager.js (17369B)


      1 /**
      2  * XMD (XML Mab Document Manager)
      3  *
      4  * @filename XMDManager.js
      5  * $LastChangedDate: 2004-12-06 13:08:50 +0100 (Mon, 06 Dec 2004) $
      6  * @author Fabio Serra <faser@faser.net>
      7  * @copyright Fabio Serra (The Initial Developer of the Original Code)
      8  * @license Mozilla Public License Version 1.1
      9  *
     10 */
     11 
     12 /**
     13  * Construct a new XMD Manager object
     14  * @class This class is used to manage the XML resulting from the transformation of Amazon XML
     15  * response. The XML Mab Document is the data model.
     16  * @constructor
     17  * @return A new XMD Manager
     18  *
     19 */
     20 function XMDManager() {
     21 	/**
     22 	 * This flag is valid for the current session. It means that when a details (record)
     23      * has been deleted from the loaded document, it can't be inserted again. In this way I
     24      * prevent unuseful duplicated records
     25 	 * @type bool
     26 	*/
     27 	this.preventDuplicateDetails = true;
     28 
     29 	/**
     30 	 * Save all primary keys that are going to be processed
     31      * @type object array
     32 	*/
     33 	this.primaryKeys = new Array();
     34 
     35 	/**
     36 	 * Save a reference in array of each details node present in the XML document
     37      * @type DOM nodes array
     38 	*/
     39 	this.hashTable = new Array();
     40 
     41 	/**
     42 	 * This property is used to remember if this document was saved (file or memory is the same)
     43      * @type bool
     44 	*/
     45 	this.wasSaved = false;
     46 
     47 	/**
     48 	 * The name of the current document.
     49      * @type string
     50 	*/
     51 	this.name = "";
     52 
     53 	/**
     54 	 * The full path including the file name and extension if the document has
     55      * been saved on file system
     56      * @type string
     57 	*/
     58 	this.fullPath = "";
     59 
     60 	/**
     61 	 * The Mab Xml Document
     62      * @type XML Doc
     63 	*/
     64 	this.xmlDoc = this.make();
     65 }
     66 
     67 
     68 /**
     69  * Create an empty XML Document to use as a Mab model data
     70  * @return A new XML Dcoument
     71  * @type XMLDocument
     72 */
     73 XMDManager.prototype.make = function() {
     74 	var today = new Date();
     75 	var tsDocumentCreated = Date.parse(today);
     76 
     77 	var xmlStr = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'+
     78 	'<MABInfo xmlns="http://www.faser.net/mab/NS/MABInfo">\n' +
     79 	'<MABTSDocumentCreated>' + tsDocumentCreated + '</MABTSDocumentCreated>\n' +
     80 	'<MABDetails/>\n' +
     81 	'</MABInfo>';
     82 
     83 	var xmlDoc = new DOMParser().parseFromString(xmlStr, 'text/xml');
     84 
     85 	return xmlDoc;
     86 }
     87 
     88 /**
     89  * Build the hashtable for the current document. When a document is merged the
     90  * hashtable is created by default. This method should be used when an XMD document
     91  * is loaded from file
     92  * @return void
     93  */
     94 XMDManager.prototype.buildHashTable = function() {
     95 	//remove all previous key
     96 	this.hashTable = null;
     97 	this.hashTable = new Array();
     98 	//build
     99 	var details = this.xmlDoc.getElementsByTagName('Details');
    100 	var pk = "";
    101 	for(var i=0;i<details.length;i++) {
    102 		pk = this.getSingleElement(details.item(i),"MABPrimaryKey");
    103 		if(pk) {this.hashTable[pk] = details.item(i);}
    104 	}
    105 }
    106 
    107 /**
    108  * Merge the Details node of the new xml document with the current one.
    109  * The last xml nodes are inserted at the top of the document
    110  * @param {XMLDocument} docToMerge the new XML document
    111  * @param {bool} overwrite means that the new details overwrite the old ones
    112  * @return object with properties to show how many nodes was merged, duplicated
    113  * and reloaded
    114  * @type object
    115 */
    116 XMDManager.prototype.merge = function(docToMerge,overwrite) {
    117 	if(typeof overwrite == "undefined") {overwrite = true;}
    118 
    119 	//Check if the new document was created by MAB or come from an Amazon search
    120 	var bAmazonResult = true;
    121 	if(this.isValidMABDoc(docToMerge)) {bAmazonResult = false;}
    122 
    123 	var mabRootDetails = this.xmlDoc.getElementsByTagName("MABDetails").item(0);
    124 	var details = docToMerge.getElementsByTagName('Details');
    125 
    126 	var node, replacedNode, pk, lastUpdate, oldDetail, mabLabel;
    127 
    128 	var today = new Date();
    129 	var isNewNode = false;
    130 	var oldNode = false;
    131 
    132 	var result = {};
    133 		result.nrMergedNode = 0;
    134 		result.nrDuplicatedNode = 0;
    135 		result.nrReloadedNode = 0;
    136 
    137 
    138 	//LIFO - Insert new records before the old ones
    139 	var lifo = this.xmlDoc.getElementsByTagName("Details").item(0);
    140 
    141 	if(bAmazonResult) {
    142 		//Default locale if missing
    143 		var locale = "us";
    144 		var path = "//Request/Args/Arg[@name='locale']";
    145 		var xpathResult = docToMerge.evaluate(path,docToMerge,null,XPathResult.ANY_TYPE,null).iterateNext();
    146 		if(xpathResult != null) { locale = xpathResult.getAttribute("value");}
    147 		var amazAsin = "";
    148 	}
    149 
    150 	for (var i=0;i<details.length; i++) {
    151 		//node from the document that I have to process
    152 		node = details.item(i).cloneNode(true);
    153 		if(bAmazonResult) {
    154 			amazAsin = this.getSingleElement(node,"Asin");
    155 			pk = amazAsin + '_' + locale;
    156 			//Add mandatory tags for details that come from Amazon
    157 			this.addMABTag(node,pk);
    158 		}else{
    159 			pk = this.getSingleElement(node,"MABPrimaryKey")
    160 		}
    161 
    162 
    163 		//Check the details date according to the Amazon Licence Agreement (November 5, 2003)
    164 		//http://forums.prospero.com/n/mb/message.asp?webtag=am-assosdev&msg=91.1&ctx=0
    165 
    166 		//Price Information must be refreshed every 24 hours, the other details every 3 months
    167 		//So, if these info are expired remove them from the XML
    168 		//The user have to be update the info
    169 
    170 		lastUpdate = this.getLastUpdate(node);
    171 		if(lastUpdate > EXPIRED_LONG) {
    172 			this.removeExpiredInfo(node);
    173 		}else if(lastUpdate > EXPIRED_SHORT) {
    174 			this.removeExpiredPrice(node);
    175 		}
    176 
    177 		//prevent duplicate records
    178 		isNewNode = this.setPK(pk);
    179 		oldNode = this.getNodeDetail(pk);
    180 
    181 		//The record is new
    182 		if(isNewNode || (!this.preventDuplicateDetails && !oldNode)) {
    183 			if(lifo) {
    184 				lifo.parentNode.insertBefore(node,lifo);
    185 			}else{
    186 				mabRootDetails.appendChild(node);
    187 			}
    188 
    189 			this.hashTable[pk] = node;
    190 			result.nrMergedNode++;
    191 
    192 		//The records is currently present in xml doc and I can overwrite it
    193 		//but avoid to replace MABLabel
    194 		}else if(overwrite && oldNode) {
    195 
    196 			mabLabel = this.getSingleElement(oldNode,"MABLabel");
    197 
    198 			//mabRootDetails.replaceChild(node,oldNode.parentNode);
    199 			mabRootDetails.removeChild(oldNode);
    200 			replacedNode = mabRootDetails.appendChild(node);
    201 			this.hashTable[pk] = replacedNode;
    202 
    203 			//No comments? Create a new fake one to prevent comment request
    204 			var comment = this.getSingleElement(replacedNode);
    205 			if(!comment) {
    206 				var comElem = this.xmlDoc.createElement("Reviews");
    207 				replacedNode.appendChild(comElem);
    208 			}
    209 
    210 			//Reset the old Mab Label
    211 			//replacedNode.getElementsByTagName("MABLabel").item(0).firstChild.nodeValue = mabLabel;
    212 			result.nrReloadedNode++;
    213 		//Skip node
    214 		}else{
    215 			result.nrDuplicatedNode ++;
    216 		}
    217 	}
    218 	return result;
    219 }
    220 
    221 /**
    222  * Add mandatory MAB tags to a new detail node coming from an Amazon search
    223  * @param {xml node} node The node to which append the new MAB tags
    224  * @param {string} pk The mab items primary key
    225  * @return void
    226 */
    227 XMDManager.prototype.addMABTag = function(node,pk) {
    228 	//Timestamp
    229 	var today = new Date();
    230 	var	ts = Date.parse(today);
    231 
    232 	//MAB Status (read | unread)
    233 	var MABStatus = "unread";
    234 
    235 	//Locale
    236 	var locale = pk.substr((pk.length-2),pk.length);
    237 
    238 	var strMabTag = "<MABPrimaryKey>" + pk + "</MABPrimaryKey>\n" +
    239 	"<MABTSLastUpdate>" + ts + "</MABTSLastUpdate>\n" +
    240 	"<MABLabel/>\n" +
    241 	"<MABLocale>" + locale + "</MABLocale>\n" +
    242 	"<MABStatus>" + MABStatus + "</MABStatus>\n";
    243 
    244 	//Append new tag to the passed node
    245 	innerXML(node,strMabTag);
    246 }
    247 
    248 /**
    249  * Get all products older than an elapsed time determined by Amazon license
    250  * @param {int} dayBefore Maximum days that should be passed from the last product update
    251  * @return object array contains the asinList grouped by locale
    252  * @type array associative
    253 */
    254 XMDManager.prototype.getAllExpiredProducts = function(dayBefore) {
    255 	if(typeof dayBefore == "undefined") {dayBefore = EXPIRED_SHORT;}
    256 	var node, lastUpdate, locale, amazAsin;
    257 	var asinList = {};
    258 	for(var d in this.hashTable) {
    259 		node = this.hashTable[d];
    260 		lastUpdate = this.getLastUpdate(node);
    261 		if(lastUpdate > dayBefore) {
    262 			locale =  this.getSingleElement(node,"MABLocale");
    263 			amazAsin = this.getSingleElement(node,"Asin");
    264 			if(!locale || !amazAsin) {continue;}
    265 
    266 			if(typeof asinList[locale] == "undefined") {
    267 				asinList[locale] = new Array();
    268 			}
    269 
    270 			asinList[locale].push(amazAsin);
    271 		}
    272 	}
    273 
    274 	return asinList;
    275 }
    276 
    277 /**
    278  * Count how many days are passed from the node last update
    279  * @param {xml node} nodeDetail
    280  * @return the elapsed days
    281  * @type int
    282 */
    283 XMDManager.prototype.getLastUpdate = function(nodeDetail) {
    284 	var today = new Date();
    285 	var dayElapsed = 0;
    286 	var lastUpdate = this.getSingleElement(nodeDetail,"MABTSLastUpdate");
    287 	if(lastUpdate) {
    288 		dayElapsed = Math.round((Date.parse(today) - parseInt(lastUpdate)) / 86400000);
    289 	}
    290 
    291 	return dayElapsed;
    292 }
    293 
    294 /**
    295  * Remove all tags from a node except the most relevant
    296  * @param {xml node} nodeDetail an XML node
    297  * @return void
    298 */
    299 XMDManager.prototype.removeExpiredInfo = function(nodeDetail) {
    300 	var tagExcluded = new Array("Asin","ProductName","Catalog","Authors","Artists","Directors","MABPrimaryKey","MABTSLastUpdate","MABLabel","MABLocale","MABStatus");
    301 	var clonedNode = new Array();
    302 	var i, cNode;
    303 	while(nodeDetail.hasChildNodes()) {
    304 		for(i=0; i<tagExcluded.length; i++) {
    305 			if(nodeDetail.lastChild.nodeName == tagExcluded[i]) {
    306 				cNode = nodeDetail.lastChild.cloneNode(true);
    307 				clonedNode.push(cNode);
    308 			}
    309 		}
    310 
    311 		nodeDetail.removeChild(nodeDetail.lastChild);
    312 	}
    313 
    314 	//Write back the excluded tag
    315 	for(i=0; i<clonedNode.length; i++) {
    316 		nodeDetail.appendChild(clonedNode[i]);
    317 	}
    318 
    319 	this.wasSaved = false;
    320 }
    321 
    322 /**
    323  * Remove expired main prices tag
    324  * @param {node xml} nodeDetail
    325  * @return void
    326 */
    327 XMDManager.prototype.removeExpiredPrice = function(nodeDetail) {
    328 	var tags = new Array("ListPrice","OurPrice","UsedPrice");
    329 	var el;
    330 	for(var i=0; i < tags.length; i++) {
    331 		el = nodeDetail.getElementsByTagName(tags[i]).item(0);
    332 		if(el) {nodeDetail.removeChild(el);}
    333 	}
    334 
    335 	this.wasSaved = false;
    336 }
    337 
    338 /**
    339  * Set the primary key to prevent duplicate
    340  * @param {string} pk A Mab primary key
    341  * @return true if a primary key has been added, false if the primary key already exist
    342  * @type bool
    343 */
    344 XMDManager.prototype.setPK = function(pk) {
    345 	var res = this.checkPK(pk);
    346 	if(!res) {
    347 		this.primaryKeys.push(pk);
    348 		return true;
    349 	}else{
    350 		return false;
    351 	}
    352 }
    353 
    354 /**
    355  * Check if the primary key has been saved
    356  * @param {string} pk
    357  * @return bool
    358  * @type bool
    359 */
    360 XMDManager.prototype.checkPK = function(pk) {
    361 	for(var i=0;i<this.primaryKeys.length;i++){
    362 		if(this.primaryKeys[i] == pk) {
    363 			return true;
    364 		}
    365 	}
    366 	return false;
    367 }
    368 
    369 /**
    370  * Check if the loaded file is a valid MAB file looking for MABInfo tag
    371  * @param {XMLDocument} xmlDoc
    372  * @return bool
    373  * @type bool
    374 */
    375 XMDManager.prototype.isValidMABDoc = function(xmlDoc) {
    376 	var root = xmlDoc.documentElement.nodeName;
    377 	var XMDRoot = "MABInfo";
    378 	if(root == XMDRoot) {
    379 		return true;
    380 	}else{
    381 		return false;
    382 	}
    383 }
    384 
    385 /**
    386  *Get a Details node looking for the primary key using the hashtable
    387  *@param {string} primaryKey The node primary key
    388  *@return nodeDetail
    389  *@type XML Node
    390 */
    391 XMDManager.prototype.getNodeDetail = function(primaryKey) {
    392 	if(typeof this.hashTable[primaryKey] == "undefined"){return false;}
    393 	var nodeDetail = this.hashTable[primaryKey];
    394 	if(nodeDetail) {
    395 		return nodeDetail;
    396 	}else{
    397 		return false;
    398 	}
    399 }
    400 
    401 /**
    402  * Remove a node detail from the XMD Document
    403  * @param {string} primaryKey The primary key of the node to remove
    404  * @return bool
    405  * @type bool
    406 */
    407 XMDManager.prototype.removeDetail = function(primaryKey) {
    408 	var nodeDetail = this.getNodeDetail(primaryKey);
    409 	if(nodeDetail) {
    410 		nodeDetail.parentNode.removeChild(nodeDetail);
    411 		delete this.hashTable[primaryKey];
    412 		this.wasSaved = false;
    413 		return true;
    414 	}else{
    415 		return false;
    416 	}
    417 }
    418 
    419 /**
    420  * Get text from the first node element
    421  * @param {node xml} nodeDetail
    422  * @param {string} tagName
    423  * @return The text contained inside the element
    424  * @type string
    425 */
    426 XMDManager.prototype.getSingleElement = function(nodeDetail,tagName) {
    427 	if(!nodeDetail) {return false;}
    428 	var el = nodeDetail.getElementsByTagName(tagName).item(0);
    429 	if(el && el.hasChildNodes()) {
    430 		return el.firstChild.nodeValue;
    431 	}else{
    432 		return false;
    433 	}
    434 }
    435 
    436 /**
    437  * Return an array with the text got from all elements
    438  * @param {node xml} nodeDetail
    439  * @param {string} tagName
    440  * @return array
    441  * @type array
    442 */
    443 XMDManager.prototype.getArrayElement = function(nodeDetail,tagName) {
    444 	var elArray = new Array();
    445 	if(!nodeDetail) {return elArray;}
    446 	var el = nodeDetail.getElementsByTagName(tagName).item(0);
    447 	if(el) {
    448 		var node = nodeDetail.getElementsByTagName(tagName);
    449 		var content;
    450 		for(var i=0;i<node.length;i++) {
    451 			content = node.item(i).firstChild.nodeValue;
    452 			if(content) {elArray[i] = content;}
    453 		}
    454 	}
    455 	return elArray;
    456 }
    457 
    458 /**
    459  * Check if an element exists inside the xml node
    460  * @param {node xml} nodeDetail
    461  * @param {string} tagName
    462  * @return true if tag exists
    463  * @type bool
    464 */
    465 XMDManager.prototype.tagExists = function(nodeDetail,tagName) {
    466 	if(!nodeDetail) {return false;}
    467 	var el = nodeDetail.getElementsByTagName(tagName).item(0);
    468 	if(el) {
    469 		return true;
    470 	}else{
    471 		return false;
    472 	}
    473 }
    474 
    475 /**
    476  * Set a new value text inside the provided tag only for the first child element.
    477  * If newValue is "none" the child element is removed. If the child element doesn't
    478  * exists a text node will be created.
    479  * @param {node xml} node
    480  * @param {string} tagName
    481  * @param {string} newValue
    482  * @return True if something has been modified
    483  * @type bool
    484 */
    485 XMDManager.prototype.setNodeValue = function(node,tagName,newValue) {
    486 	var el = node.getElementsByTagName(tagName).item(0);
    487 	if(el) {
    488 		if(newValue == "none") {
    489 			if(el.hasChildNodes()) {el.removeChild(el.firstChild);}
    490 		} else if(el.hasChildNodes()) {
    491 			el.firstChild.nodeValue = newValue;
    492 		}else{
    493 			var text = document.createTextNode(newValue);
    494 			el.appendChild(text);
    495 		}
    496 		return true;
    497 	}else{
    498 		return false;
    499 	}
    500 }
    501 
    502 /**
    503  * Remove all Details node from the document
    504  * @return void
    505 */
    506 XMDManager.prototype.removeAllDetails = function() {
    507 	var root = this.xmlDoc.getElementsByTagName("MABDetails").item(0);
    508 	while(root.hasChildNodes()) {
    509 		root.removeChild(root.lastChild);
    510 	}
    511 }
    512 
    513 /**
    514  * Sort the xml document
    515  * Unfortunately this sort method is too slow, pratically unusable when there are
    516  * more than 150 node. TODO (try using XSLT)
    517  * @param {string} sortType The kind of value to sort (numeric | date | string)
    518  * @param {string} sortTag The xml tag to use for sorting the node
    519  * @param {bool} bAscending Ascending or descending sorting
    520  * @return void
    521 */
    522 XMDManager.prototype.sort = function(sortType,sortTag,bAscending) {
    523 
    524 	var theBody = this.xmlDoc.getElementsByTagName("MABDetails").item(0);
    525 
    526 	var numRows = 0;
    527 	var theSortedRows = new Array();
    528 	for (var i in this.hashTable) {
    529 		theSortedRows[numRows] = this.hashTable[i].cloneNode(true);
    530 		numRows++;
    531 	}
    532 
    533 	theSortedRows.sort(this.sortCallBack(sortType,sortTag,bAscending));
    534 
    535 	//Clear current xml document
    536 	this.removeAllDetails();
    537 
    538 	//recreate
    539 	for(i=0;i<numRows;i++) {
    540 		theBody.appendChild(theSortedRows[i])
    541 	}
    542 	//rebuild the hashtable
    543 	this.buildHashTable();
    544 }
    545 
    546 /**
    547  * Calculate how should be sorted the xml node. Actually the xml document is not re-ordered
    548  * @param {string} sortType The kind of value to sort (numeric | date | string)
    549  * @param {string} sortTag The xml tag to use for sorting the node
    550  * @param {bool} bAscending Ascending or descending sorting
    551  * @return an array with virtually sorted xml node.
    552  * @type array
    553 */
    554 XMDManager.prototype.virtualSort = function(sortType,sortTag,bAscending) {
    555 	var numRows = 0;
    556 	var theSortedRows = new Array();
    557 	for (var i in this.hashTable) {
    558 		theSortedRows[numRows] = this.hashTable[i];
    559 		numRows++;
    560 	}
    561 
    562 	theSortedRows.sort(this.sortCallBack(sortType,sortTag,bAscending));
    563 
    564 	return theSortedRows;
    565 }
    566 
    567 /**
    568  * Callback method used in the sort javascript.
    569  * @private
    570  * @param {string} sortType The kind of value to sort (numeric | date | string)
    571  * @param {string} sortTag The xml tag to use for sorting the node
    572  * @param {bool} bAscending Ascending or descending sorting
    573  * @type int
    574 */
    575 XMDManager.prototype.sortCallBack = function(sortType,sortTag,bAscending) {
    576 
    577 	var fTypeCast = String;
    578 	var asc = bAscending;
    579 
    580 	switch(sortType) {
    581 		case "numeric":
    582 			fTypeCast = toNumber;
    583 			break;
    584 		case "date":
    585 			fTypeCast = toYear;
    586 			break;
    587 		default:
    588 			fTypeCast = CaseInsensitiveString;
    589 	}
    590 
    591 	var me = this;
    592 	return function (a,b) {
    593 
    594 		var col1 = me.getSingleElement(a,sortTag);
    595 		var col2 = me.getSingleElement(b,sortTag);
    596 
    597 		var text1 = "";
    598 		var text2 = "";
    599 
    600 		if(col1) {text1 = col1;}
    601 		if(col2) {text2 = col2;}
    602 
    603 		//Sort Tag SalesRank remove decimal separator
    604 		if(sortTag == "SalesRank") {
    605 			text1 = text1.replace(/,/g,"");
    606 			text1 = text1.replace(/\./g,"");
    607 
    608 			text2 = text2.replace(/,/g,"");
    609 			text2 = text2.replace(/\./g,"");
    610 		}
    611 
    612 		if (fTypeCast(text1) < fTypeCast(text2)) {
    613 			return asc ? -1 : 1;
    614 		} else if (fTypeCast(text1) > fTypeCast(text2)) {
    615 			return asc ? 1 : -1;
    616 		}else{
    617 			return 0;
    618 		}
    619 	};
    620 }
    621