View Javadoc
1   package org.apache.maven.plugins.surefire.report;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.text.NumberFormat;
24  import java.util.List;
25  import java.util.Locale;
26  import java.util.Map;
27  import org.apache.maven.doxia.markup.HtmlMarkup;
28  import org.apache.maven.doxia.sink.Sink;
29  import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
30  import org.apache.maven.doxia.util.DoxiaUtils;
31  import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
32  import org.apache.maven.reporting.MavenReportException;
33  
34  import static org.apache.maven.doxia.markup.HtmlMarkup.A;
35  import static org.apache.maven.doxia.sink.Sink.JUSTIFY_LEFT;
36  import static org.apache.maven.doxia.sink.SinkEventAttributes.CLASS;
37  import static org.apache.maven.doxia.sink.SinkEventAttributes.HREF;
38  import static org.apache.maven.doxia.sink.SinkEventAttributes.ID;
39  import static org.apache.maven.doxia.sink.SinkEventAttributes.NAME;
40  import static org.apache.maven.doxia.sink.SinkEventAttributes.STYLE;
41  import static org.apache.maven.doxia.sink.SinkEventAttributes.TYPE;
42  
43  /**
44   * This generator creates HTML Report from Surefire and Failsafe XML Report.
45   */
46  public final class SurefireReportGenerator
47  {
48      private static final int LEFT = JUSTIFY_LEFT;
49      private static final Object[] TAG_TYPE_START = { HtmlMarkup.TAG_TYPE_START };
50      private static final Object[] TAG_TYPE_END = { HtmlMarkup.TAG_TYPE_END };
51  
52      private final SurefireReportParser report;
53      private final boolean showSuccess;
54      private final String xrefLocation;
55      private List<ReportTestSuite> testSuites;
56  
57      public SurefireReportGenerator( List<File> reportsDirectories, Locale locale, boolean showSuccess,
58                                      String xrefLocation, ConsoleLogger consoleLogger )
59      {
60          report = new SurefireReportParser( reportsDirectories, locale, consoleLogger );
61          this.showSuccess = showSuccess;
62          this.xrefLocation = xrefLocation;
63      }
64  
65      public void doGenerateReport( LocalizedProperties bundle, Sink sink )
66          throws MavenReportException
67      {
68          testSuites = report.parseXMLReportFiles();
69  
70          sink.head();
71  
72          sink.title();
73          sink.text( bundle.getReportHeader() );
74          sink.title_();
75  
76          sink.head_();
77  
78          sink.body();
79  
80          SinkEventAttributeSet atts = new SinkEventAttributeSet();
81          atts.addAttribute( TYPE, "application/javascript" );
82          sink.unknown( "script", new Object[]{ HtmlMarkup.TAG_TYPE_START }, atts );
83          sink.unknown( "cdata", new Object[]{ HtmlMarkup.CDATA_TYPE, javascriptToggleDisplayCode() }, null );
84          sink.unknown( "script", new Object[]{ HtmlMarkup.TAG_TYPE_END }, null );
85  
86          sink.section1();
87          sink.sectionTitle1();
88          sink.text( bundle.getReportHeader() );
89          sink.sectionTitle1_();
90          sink.section1_();
91  
92          constructSummarySection( bundle, sink );
93  
94          Map<String, List<ReportTestSuite>> suitePackages = report.getSuitesGroupByPackage( testSuites );
95          if ( !suitePackages.isEmpty() )
96          {
97              constructPackagesSection( bundle, sink, suitePackages );
98          }
99  
100         if ( !testSuites.isEmpty() )
101         {
102             constructTestCasesSection( bundle, sink );
103         }
104 
105         List<ReportTestCase> failureList = report.getFailureDetails( testSuites );
106         if ( !failureList.isEmpty() )
107         {
108             constructFailureDetails( sink, bundle, failureList );
109         }
110 
111         sink.body_();
112 
113         sink.flush();
114 
115         sink.close();
116     }
117 
118     private void constructSummarySection( LocalizedProperties bundle, Sink sink )
119     {
120         Map<String, String> summary = report.getSummary( testSuites );
121 
122         sink.section1();
123         sink.sectionTitle1();
124         sink.text( bundle.getReportLabelSummary() );
125         sink.sectionTitle1_();
126 
127         sinkAnchor( sink, "Summary" );
128 
129         constructHotLinks( sink, bundle );
130 
131         sinkLineBreak( sink );
132 
133         sink.table();
134 
135         sink.tableRows( new int[]{ LEFT, LEFT, LEFT, LEFT, LEFT, LEFT }, true );
136 
137         sink.tableRow();
138 
139         sinkHeader( sink, bundle.getReportLabelTests() );
140 
141         sinkHeader( sink, bundle.getReportLabelErrors() );
142 
143         sinkHeader( sink, bundle.getReportLabelFailures() );
144 
145         sinkHeader( sink, bundle.getReportLabelSkipped() );
146 
147         sinkHeader( sink, bundle.getReportLabelSuccessRate() );
148 
149         sinkHeader( sink, bundle.getReportLabelTime() );
150 
151         sink.tableRow_();
152 
153         sink.tableRow();
154 
155         sinkCell( sink, summary.get( "totalTests" ) );
156 
157         sinkCell( sink, summary.get( "totalErrors" ) );
158 
159         sinkCell( sink, summary.get( "totalFailures" ) );
160 
161         sinkCell( sink, summary.get( "totalSkipped" ) );
162 
163         sinkCell( sink, summary.get( "totalPercentage" ) + "%" );
164 
165         sinkCell( sink, summary.get( "totalElapsedTime" ) );
166 
167         sink.tableRow_();
168 
169         sink.tableRows_();
170 
171         sink.table_();
172 
173         sink.lineBreak();
174 
175         sink.paragraph();
176         sink.text( bundle.getReportTextNode1() );
177         sink.paragraph_();
178 
179         sinkLineBreak( sink );
180 
181         sink.section1_();
182     }
183 
184     private void constructPackagesSection( LocalizedProperties bundle, Sink sink,
185                                            Map<String, List<ReportTestSuite>> suitePackages )
186     {
187         NumberFormat numberFormat = report.getNumberFormat();
188 
189         sink.section1();
190         sink.sectionTitle1();
191         sink.text( bundle.getReportLabelPackageList() );
192         sink.sectionTitle1_();
193 
194         sinkAnchor( sink, "Package_List" );
195 
196         constructHotLinks( sink, bundle );
197 
198         sinkLineBreak( sink );
199 
200         sink.table();
201 
202         sink.tableRows( new int[]{ LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT }, true );
203 
204         sink.tableRow();
205 
206         sinkHeader( sink, bundle.getReportLabelPackage() );
207 
208         sinkHeader( sink, bundle.getReportLabelTests() );
209 
210         sinkHeader( sink, bundle.getReportLabelErrors() );
211 
212         sinkHeader( sink, bundle.getReportLabelFailures() );
213 
214         sinkHeader( sink, bundle.getReportLabelSkipped() );
215 
216         sinkHeader( sink, bundle.getReportLabelSuccessRate() );
217 
218         sinkHeader( sink, bundle.getReportLabelTime() );
219 
220         sink.tableRow_();
221 
222         for ( Map.Entry<String, List<ReportTestSuite>> entry : suitePackages.entrySet() )
223         {
224             sink.tableRow();
225 
226             String packageName = entry.getKey();
227 
228             List<ReportTestSuite> testSuiteList = entry.getValue();
229 
230             Map<String, String> packageSummary = report.getSummary( testSuiteList );
231 
232             sinkCellLink( sink, packageName, "#" + packageName );
233 
234             sinkCell( sink, packageSummary.get( "totalTests" ) );
235 
236             sinkCell( sink, packageSummary.get( "totalErrors" ) );
237 
238             sinkCell( sink, packageSummary.get( "totalFailures" ) );
239 
240             sinkCell( sink, packageSummary.get( "totalSkipped" ) );
241 
242             sinkCell( sink, packageSummary.get( "totalPercentage" ) + "%" );
243 
244             sinkCell( sink, packageSummary.get( "totalElapsedTime" ) );
245 
246             sink.tableRow_();
247         }
248 
249         sink.tableRows_();
250 
251         sink.table_();
252 
253         sink.lineBreak();
254 
255         sink.paragraph();
256         sink.text( bundle.getReportTextNode2() );
257         sink.paragraph_();
258 
259         for ( Map.Entry<String, List<ReportTestSuite>> entry : suitePackages.entrySet() )
260         {
261             String packageName = entry.getKey();
262 
263             List<ReportTestSuite> testSuiteList = entry.getValue();
264 
265             sink.section2();
266             sink.sectionTitle2();
267             sink.text( packageName );
268             sink.sectionTitle2_();
269 
270             sinkAnchor( sink, packageName );
271 
272             boolean showTable = false;
273 
274             for ( ReportTestSuite suite : testSuiteList )
275             {
276                 if ( showSuccess || suite.getNumberOfErrors() != 0 || suite.getNumberOfFailures() != 0 )
277                 {
278                     showTable = true;
279 
280                     break;
281                 }
282             }
283 
284             if ( showTable )
285             {
286                 sink.table();
287 
288                 sink.tableRows( new int[]{ LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT, LEFT }, true );
289 
290                 sink.tableRow();
291 
292                 sinkHeader( sink, "" );
293 
294                 sinkHeader( sink, bundle.getReportLabelClass() );
295 
296                 sinkHeader( sink, bundle.getReportLabelTests() );
297 
298                 sinkHeader( sink, bundle.getReportLabelErrors() );
299 
300                 sinkHeader( sink, bundle.getReportLabelFailures() );
301 
302                 sinkHeader( sink, bundle.getReportLabelSkipped() );
303 
304                 sinkHeader( sink, bundle.getReportLabelSuccessRate() );
305 
306                 sinkHeader( sink, bundle.getReportLabelTime() );
307 
308                 sink.tableRow_();
309 
310                 for ( ReportTestSuite suite : testSuiteList )
311                 {
312                     if ( showSuccess || suite.getNumberOfErrors() != 0 || suite.getNumberOfFailures() != 0 )
313                     {
314                         constructTestSuiteSection( sink, numberFormat, suite );
315                     }
316                 }
317 
318                 sink.tableRows_();
319 
320                 sink.table_();
321             }
322 
323             sink.section2_();
324         }
325 
326         sinkLineBreak( sink );
327 
328         sink.section1_();
329     }
330 
331     private void constructTestSuiteSection( Sink sink, NumberFormat numberFormat, ReportTestSuite suite )
332     {
333         sink.tableRow();
334 
335         sink.tableCell();
336 
337         sink.link( "#" + suite.getPackageName() + '.' + suite.getName() );
338 
339         if ( suite.getNumberOfErrors() > 0 )
340         {
341             sinkIcon( "error", sink );
342         }
343         else if ( suite.getNumberOfFailures() > 0 )
344         {
345             sinkIcon( "junit.framework", sink );
346         }
347         else if ( suite.getNumberOfSkipped() > 0 )
348         {
349             sinkIcon( "skipped", sink );
350         }
351         else
352         {
353             sinkIcon( "success", sink );
354         }
355 
356         sink.link_();
357 
358         sink.tableCell_();
359 
360         sinkCellLink( sink, suite.getName(), "#" + suite.getPackageName() + '.' + suite.getName() );
361 
362         sinkCell( sink, Integer.toString( suite.getNumberOfTests() ) );
363 
364         sinkCell( sink, Integer.toString( suite.getNumberOfErrors() ) );
365 
366         sinkCell( sink, Integer.toString( suite.getNumberOfFailures() ) );
367 
368         sinkCell( sink, Integer.toString( suite.getNumberOfSkipped() ) );
369 
370         String percentage = report.computePercentage( suite.getNumberOfTests(), suite.getNumberOfErrors(),
371                                                       suite.getNumberOfFailures(), suite.getNumberOfSkipped() );
372         sinkCell( sink, percentage + "%" );
373 
374         sinkCell( sink, numberFormat.format( suite.getTimeElapsed() ) );
375 
376         sink.tableRow_();
377     }
378 
379     private void constructTestCasesSection( LocalizedProperties bundle, Sink sink )
380     {
381         NumberFormat numberFormat = report.getNumberFormat();
382 
383         sink.section1();
384         sink.sectionTitle1();
385         sink.text( bundle.getReportLabelTestCases() );
386         sink.sectionTitle1_();
387 
388         sinkAnchor( sink, "Test_Cases" );
389 
390         constructHotLinks( sink, bundle );
391 
392         for ( ReportTestSuite suite : testSuites )
393         {
394             List<ReportTestCase> testCases = suite.getTestCases();
395 
396             if ( !testCases.isEmpty() )
397             {
398                 sink.section2();
399                 sink.sectionTitle2();
400                 sink.text( suite.getName() );
401                 sink.sectionTitle2_();
402 
403                 sinkAnchor( sink, suite.getPackageName() + '.' + suite.getName() );
404 
405                 boolean showTable = false;
406 
407                 for ( ReportTestCase testCase : testCases )
408                 {
409                     if ( !testCase.isSuccessful() || showSuccess )
410                     {
411                         showTable = true;
412 
413                         break;
414                     }
415                 }
416 
417                 if ( showTable )
418                 {
419                     sink.table();
420 
421                     sink.tableRows( new int[]{ LEFT, LEFT, LEFT }, true );
422 
423                     for ( ReportTestCase testCase : testCases )
424                     {
425                         if ( !testCase.isSuccessful() || showSuccess )
426                         {
427                             constructTestCaseSection( sink, numberFormat, testCase );
428                         }
429                     }
430 
431                     sink.tableRows_();
432 
433                     sink.table_();
434                 }
435 
436                 sink.section2_();
437             }
438         }
439 
440         sinkLineBreak( sink );
441 
442         sink.section1_();
443     }
444 
445     private static void constructTestCaseSection( Sink sink, NumberFormat numberFormat, ReportTestCase testCase )
446     {
447         sink.tableRow();
448 
449         sink.tableCell();
450 
451         if ( testCase.getFailureType() != null )
452         {
453             sink.link( "#" + toHtmlId( testCase.getFullName() ) );
454 
455             sinkIcon( testCase.getFailureType(), sink );
456 
457             sink.link_();
458         }
459         else
460         {
461             sinkIcon( "success", sink );
462         }
463 
464         sink.tableCell_();
465 
466         if ( !testCase.isSuccessful() )
467         {
468             sink.tableCell();
469 
470             sinkAnchor( sink, "TC_" + toHtmlId( testCase.getFullName() ) );
471 
472             sinkLink( sink, testCase.getName(), "#" + toHtmlId( testCase.getFullName() ) );
473 
474             SinkEventAttributeSet atts = new SinkEventAttributeSet();
475             atts.addAttribute( CLASS, "detailToggle" );
476             atts.addAttribute( STYLE, "display:inline" );
477             sink.unknown( "div", TAG_TYPE_START, atts );
478 
479             sinkLink( sink, "javascript:toggleDisplay('" + toHtmlId( testCase.getFullName() ) + "');" );
480 
481             atts = new SinkEventAttributeSet();
482             atts.addAttribute( STYLE, "display:inline;" );
483             atts.addAttribute( ID, toHtmlId( testCase.getFullName() ) + "-off" );
484             sink.unknown( "span", TAG_TYPE_START, atts );
485             sink.text( " + " );
486             sink.unknown( "span", TAG_TYPE_END, null );
487 
488             atts = new SinkEventAttributeSet();
489             atts.addAttribute( STYLE, "display:none;" );
490             atts.addAttribute( ID, toHtmlId( testCase.getFullName() ) + "-on" );
491             sink.unknown( "span", TAG_TYPE_START, atts );
492             sink.text( " - " );
493             sink.unknown( "span", TAG_TYPE_END, null );
494 
495             sink.text( "[ Detail ]" );
496             sinkLink_( sink );
497 
498             sink.unknown( "div", TAG_TYPE_END, null );
499 
500             sink.tableCell_();
501         }
502         else
503         {
504             sinkCellAnchor( sink, testCase.getName(), "TC_" + toHtmlId( testCase.getFullName() ) );
505         }
506 
507         sinkCell( sink, numberFormat.format( testCase.getTime() ) );
508 
509         sink.tableRow_();
510 
511         if ( !testCase.isSuccessful() )
512         {
513             sink.tableRow();
514 
515             sinkCell( sink, "" );
516             sinkCell( sink, testCase.getFailureMessage() );
517             sinkCell( sink, "" );
518             sink.tableRow_();
519 
520             String detail = testCase.getFailureDetail();
521             if ( detail != null )
522             {
523                 sink.tableRow();
524                 sinkCell( sink, "" );
525 
526                 sink.tableCell();
527                 SinkEventAttributeSet atts = new SinkEventAttributeSet();
528                 atts.addAttribute( ID, toHtmlId( testCase.getFullName() ) + toHtmlIdFailure( testCase ) );
529                 atts.addAttribute( STYLE, "display:none;" );
530                 sink.unknown( "div", TAG_TYPE_START, atts );
531 
532                 sink.verbatim( null );
533                 sink.text( detail );
534                 sink.verbatim_();
535 
536                 sink.unknown( "div", TAG_TYPE_END, null );
537                 sink.tableCell_();
538 
539                 sinkCell( sink, "" );
540 
541                 sink.tableRow_();
542             }
543         }
544     }
545 
546     private static String toHtmlId( String id )
547     {
548         return DoxiaUtils.isValidId( id ) ? id : DoxiaUtils.encodeId( id, true );
549     }
550 
551     private void constructFailureDetails( Sink sink, LocalizedProperties bundle, List<ReportTestCase> failures )
552     {
553         sink.section1();
554         sink.sectionTitle1();
555         sink.text( bundle.getReportLabelFailureDetails() );
556         sink.sectionTitle1_();
557 
558         sinkAnchor( sink, "Failure_Details" );
559 
560         constructHotLinks( sink, bundle );
561 
562         sinkLineBreak( sink );
563 
564         sink.table();
565 
566         sink.tableRows( new int[]{ LEFT, LEFT }, true );
567 
568         for ( ReportTestCase tCase : failures )
569         {
570             sink.tableRow();
571 
572             sink.tableCell();
573 
574             String type = tCase.getFailureType();
575 
576             sinkIcon( type, sink );
577 
578             sink.tableCell_();
579 
580             sinkCellAnchor( sink, tCase.getName(), toHtmlId( tCase.getFullName() ) );
581 
582             sink.tableRow_();
583 
584             String message = tCase.getFailureMessage();
585 
586             sink.tableRow();
587 
588             sinkCell( sink, "" );
589 
590             sinkCell( sink, message == null ? type : type + ": " + message );
591 
592             sink.tableRow_();
593 
594             String detail = tCase.getFailureDetail();
595             if ( detail != null )
596             {
597                 sink.tableRow();
598 
599                 sinkCell( sink, "" );
600 
601                 sink.tableCell();
602                 SinkEventAttributeSet atts = new SinkEventAttributeSet();
603                 atts.addAttribute( ID, tCase.getName() + toHtmlIdFailure( tCase ) );
604                 sink.unknown( "div", TAG_TYPE_START, atts );
605 
606                 String fullClassName = tCase.getFullClassName();
607                 String errorLineNumber = tCase.getFailureErrorLine();
608                 if ( xrefLocation != null )
609                 {
610                     String path = fullClassName.replace( '.', '/' );
611                     sink.link( xrefLocation + "/" + path + ".html#" + errorLineNumber );
612                 }
613                 sink.text( fullClassName + ":" + errorLineNumber );
614 
615                 if ( xrefLocation != null )
616                 {
617                     sink.link_();
618                 }
619                 sink.unknown( "div", TAG_TYPE_END, null );
620 
621                 sink.tableCell_();
622 
623                 sink.tableRow_();
624             }
625         }
626 
627         sink.tableRows_();
628 
629         sink.table_();
630 
631         sinkLineBreak( sink );
632 
633         sink.section1_();
634     }
635 
636     private void constructHotLinks( Sink sink, LocalizedProperties bundle )
637     {
638         if ( !testSuites.isEmpty() )
639         {
640             sink.paragraph();
641 
642             sink.text( "[" );
643             sinkLink( sink, bundle.getReportLabelSummary(), "#Summary" );
644             sink.text( "]" );
645 
646             sink.text( " [" );
647             sinkLink( sink, bundle.getReportLabelPackageList(), "#Package_List" );
648             sink.text( "]" );
649 
650             sink.text( " [" );
651             sinkLink( sink, bundle.getReportLabelTestCases(), "#Test_Cases" );
652             sink.text( "]" );
653 
654             sink.paragraph_();
655         }
656     }
657 
658     private static String toHtmlIdFailure( ReportTestCase tCase )
659     {
660         return tCase.hasError() ? "-error" : "-failure";
661     }
662 
663     private static void sinkLineBreak( Sink sink )
664     {
665         sink.lineBreak();
666     }
667 
668     private static void sinkIcon( String type, Sink sink )
669     {
670         sink.figure();
671 
672         if ( type.startsWith( "junit.framework" ) || "skipped".equals( type ) )
673         {
674             sink.figureGraphics( "images/icon_warning_sml.gif" );
675         }
676         else if ( type.startsWith( "success" ) )
677         {
678             sink.figureGraphics( "images/icon_success_sml.gif" );
679         }
680         else
681         {
682             sink.figureGraphics( "images/icon_error_sml.gif" );
683         }
684 
685         sink.figure_();
686     }
687 
688     private static void sinkHeader( Sink sink, String header )
689     {
690         sink.tableHeaderCell();
691         sink.text( header );
692         sink.tableHeaderCell_();
693     }
694 
695     private static void sinkCell( Sink sink, String text )
696     {
697         sink.tableCell();
698         sink.text( text );
699         sink.tableCell_();
700     }
701 
702     private static void sinkLink( Sink sink, String text, String link )
703     {
704         sink.link( link );
705         sink.text( text );
706         sink.link_();
707     }
708 
709     private static void sinkCellLink( Sink sink, String text, String link )
710     {
711         sink.tableCell();
712         sinkLink( sink, text, link );
713         sink.tableCell_();
714     }
715 
716     private static void sinkCellAnchor( Sink sink, String text, String anchor )
717     {
718         sink.tableCell();
719         sinkAnchor( sink, anchor );
720         sink.text( text );
721         sink.tableCell_();
722     }
723 
724     private static void sinkAnchor( Sink sink, String anchor )
725     {
726         // Dollar '$' for nested classes is not valid character in sink.anchor() and therefore it is ignored
727         // https://issues.apache.org/jira/browse/SUREFIRE-1443
728         sink.unknown( A.toString(), TAG_TYPE_START, new SinkEventAttributeSet( NAME, anchor ) );
729         sink.unknown( A.toString(), TAG_TYPE_END, null );
730     }
731 
732     private static void sinkLink( Sink sink, String href )
733     {
734         // The "'" argument in this JavaScript function would be escaped to "&apos;"
735         // sink.link( "javascript:toggleDisplay('" + toHtmlId( testCase.getFullName() ) + "');" );
736         sink.unknown( A.toString(), TAG_TYPE_START, new SinkEventAttributeSet( HREF, href ) );
737     }
738 
739     @SuppressWarnings( "checkstyle:methodname" )
740     private static void sinkLink_( Sink sink )
741     {
742         sink.unknown( A.toString(), TAG_TYPE_END, null );
743     }
744 
745     private static String javascriptToggleDisplayCode()
746     {
747 
748         // the javascript code is emitted within a commented CDATA section
749         // so we have to start with a newline and comment the CDATA closing in the end
750 
751         return "\n" + "function toggleDisplay(elementId) {\n"
752                 + " var elm = document.getElementById(elementId + '-error');\n"
753                 + " if (elm == null) {\n"
754                 + "  elm = document.getElementById(elementId + '-failure');\n"
755                 + " }\n"
756                 + " if (elm && typeof elm.style != \"undefined\") {\n"
757                 + "  if (elm.style.display == \"none\") {\n"
758                 + "   elm.style.display = \"\";\n"
759                 + "   document.getElementById(elementId + '-off').style.display = \"none\";\n"
760                 + "   document.getElementById(elementId + '-on').style.display = \"inline\";\n"
761                 + "  } else if (elm.style.display == \"\") {"
762                 + "   elm.style.display = \"none\";\n"
763                 + "   document.getElementById(elementId + '-off').style.display = \"inline\";\n"
764                 + "   document.getElementById(elementId + '-on').style.display = \"none\";\n"
765                 + "  } \n"
766                 + " } \n"
767                 + " }\n"
768                 + "//";
769     }
770 }