1 (function(){
  2 
  3   var APOS = "'"; QUOTE = '"';
  4   var ESCAPED_QUOTE = {  };
  5   ESCAPED_QUOTE[QUOTE] = '"';
  6   ESCAPED_QUOTE[APOS] = ''';
  7    
  8   function formatAttributes(attributes) {
  9     var att_value;
 10     var apos_pos, quot_pos;
 11     var use_quote, escape, quote_to_escape;
 12     var att_str;
 13     var re;
 14     var result = '';
 15     
 16     for (var att in attributes) {
 17       att_value = attributes[att] || "";
 18 
 19       att_value = att_value.replace(/&/g, "&");
 20       att_value = att_value.replace(/</g, "<");
 21       att_value = att_value.replace(/>/g, ">");
 22 
 23       // Find first quote marks if any
 24       apos_pos = att_value.indexOf(APOS);
 25       quot_pos = att_value.indexOf(QUOTE);
 26       
 27       // Determine which quote type to use around 
 28       // the attribute value
 29       if (apos_pos == -1 && quot_pos == -1) {
 30         att_str = ' ' + att + "='" + att_value +  "'";
 31         result += att_str;
 32         continue;
 33       }
 34       
 35       // Prefer the single quote unless forced to use double
 36       if (quot_pos != -1 && quot_pos < apos_pos) {
 37         use_quote = APOS;
 38       }
 39       else {
 40         use_quote = QUOTE;
 41       }
 42       
 43       // Figure out which kind of quote to escape
 44       // Use nice dictionary instead of yucky if-else nests
 45       escape = ESCAPED_QUOTE[use_quote];
 46       
 47       // Escape only the right kind of quote
 48       re = new RegExp(use_quote,'g');
 49       att_str = ' ' + att + '=' + use_quote + 
 50         att_value.replace(re, escape) + use_quote;
 51       result += att_str;
 52     }
 53     return result;
 54   };
 55 
 56 
 57   /** JavaScript API reporter.
 58  *
 59  * @constructor
 60  */
 61 jasmine.XMLReporter = function() {
 62   this.started = false;
 63   this.finished = false;
 64   this.suites_ = [];
 65   this.results_ = {};
 66 };
 67 
 68 jasmine.XMLReporter.prototype.reportRunnerStarting = function(runner) {
 69   this.started = true;
 70   var suites = runner.suites();
 71   for (var i = 0; i < suites.length; i++) {
 72     var suite = suites[i];
 73     this.suites_.push(this.summarize_(suite));
 74   }
 75   this.startedAt = new Date();
 76   puts("<testsuites>")
 77 };
 78 
 79 jasmine.XMLReporter.prototype.suites = function() {
 80   return this.suites_;
 81 };
 82 
 83 jasmine.XMLReporter.prototype.summarize_ = function(suiteOrSpec) {
 84   var isSuite = suiteOrSpec instanceof jasmine.Suite
 85   var summary = {
 86     id: suiteOrSpec.id,
 87     name: suiteOrSpec.description,
 88     type: isSuite ? 'suite' : 'spec',
 89     children: []
 90   };
 91   if (isSuite) {
 92     var specs = suiteOrSpec.specs();
 93     for (var i = 0; i < specs.length; i++) {
 94       summary.children.push(this.summarize_(specs[i]));
 95     }
 96   }
 97   return summary;
 98 };
 99 
100 jasmine.XMLReporter.prototype.results = function() {
101   return this.results_;
102 };
103 
104 jasmine.XMLReporter.prototype.resultsForSpec = function(specId) {
105   return this.results_[specId];
106 };
107 
108 //noinspection JSUnusedLocalSymbols
109 jasmine.XMLReporter.prototype.reportRunnerResults = function(runner) {
110   this.finished = true;
111   var results = runner.results();
112   var specs = runner.specs();
113   var specCount = specs.length;
114   if(jasmine.XMLReporter.current_suite){
115     puts("  </testsuite>");
116   }
117   // puts("  <tests>"+specCount+"</tests>");
118   // puts("  <errors>"+results.failedCount+"</errors>");
119   // puts("  <skipped>0</skipped>");
120   // puts("  <failures>0</failures>");
121   // puts("  <time>"+((new Date().getTime() - this.startedAt.getTime()) / 1000)+"</time>");
122   puts("</testsuites>");
123 };
124 
125 //noinspection JSUnusedLocalSymbols
126 jasmine.XMLReporter.prototype.reportSuiteResults = function(suite) {
127 };
128 
129 //noinspection JSUnusedLocalSymbols
130 jasmine.XMLReporter.prototype.reportSpecResults = function(spec) {
131   var suite = spec.suite;
132   if(jasmine.XMLReporter.current_suite != suite){
133     if(jasmine.XMLReporter.current_suite){
134       puts("  </testsuite>");
135     }
136     var name = [ spec.description ];
137     while(suite){
138       name.unshift( suite.description );
139       suite = suite.parentSuite;
140     }
141     puts("  <testsuite"+formatAttributes({name:name.join(" : ")}) +">");
142     jasmine.XMLReporter.current_suite = spec.suite;
143   }
144   this.results_[spec.id] = {
145     spec: spec,
146     messages: spec.results().getItems(),
147     result: spec.results().failedCount > 0 ? "failed" : "passed"
148   };
149   var results = spec.results().getItems();
150   for(var i in results) {
151     var result = results[i];
152     puts("    <testcase"+formatAttributes({name:(1+parseInt(i))+ ": " + result.matcherName})+">");
153     if(!result.passed()){
154       puts("     <failure"+formatAttributes({type:result.matcherName,
155                                               message:result.message})+">");
156       puts("<![CDATA[");
157       puts(get_exception_trace(result.trace));
158       puts("]]>");
159       puts("     </failure>");
160     }
161     puts("    </testcase>");
162   }
163 };
164 
165 //noinspection JSUnusedLocalSymbols
166 jasmine.XMLReporter.prototype.log = function(str) {
167 // print(str);
168 };
169 
170 jasmine.XMLReporter.prototype.resultsForSpecs = function(specIds){
171   var results = {};
172   for (var i = 0; i < specIds.length; i++) {
173     var specId = specIds[i];
174     results[specId] = this.summarizeResult_(this.results_[specId]);
175   }
176   return results;
177 };
178 
179 jasmine.XMLReporter.prototype.summarizeResult_ = function(result){
180   var summaryMessages = [];
181   for (var messageIndex in result.messages) {
182     var resultMessage = result.messages[messageIndex];
183     summaryMessages.push({
184       text: resultMessage.text,
185       passed: resultMessage.passed ? resultMessage.passed() : true,
186       type: resultMessage.type,
187       message: resultMessage.message,
188       trace: {
189         stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : undefined
190       }
191     });
192   };
193 
194   var summaryResult = {
195     result : result.result,
196     messages : summaryMessages
197   };
198 
199   return summaryResult;
200 };
201 
202 })();
203