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