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