View Javadoc

1   package org.codehaus.modello.plugin.xdoc;
2   
3   /*
4    * Copyright (c) 2004, Codehaus.org
5    *
6    * Permission is hereby granted, free of charge, to any person obtaining a copy of
7    * this software and associated documentation files (the "Software"), to deal in
8    * the Software without restriction, including without limitation the rights to
9    * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10   * of the Software, and to permit persons to whom the Software is furnished to do
11   * so, subject to the following conditions:
12   *
13   * The above copyright notice and this permission notice shall be included in all
14   * copies or substantial portions of the Software.
15   *
16   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22   * SOFTWARE.
23   */
24  
25  import java.io.File;
26  import java.io.IOException;
27  import java.io.Writer;
28  import java.util.HashSet;
29  import java.util.List;
30  import java.util.Properties;
31  import java.util.Set;
32  import java.util.Stack;
33  
34  import org.codehaus.modello.ModelloException;
35  import org.codehaus.modello.ModelloParameterConstants;
36  import org.codehaus.modello.ModelloRuntimeException;
37  import org.codehaus.modello.model.BaseElement;
38  import org.codehaus.modello.model.Model;
39  import org.codehaus.modello.model.ModelAssociation;
40  import org.codehaus.modello.model.ModelClass;
41  import org.codehaus.modello.model.ModelDefault;
42  import org.codehaus.modello.model.ModelField;
43  import org.codehaus.modello.model.Version;
44  import org.codehaus.modello.model.VersionRange;
45  import org.codehaus.modello.plugin.xdoc.metadata.XdocFieldMetadata;
46  import org.codehaus.modello.plugin.xsd.XsdModelHelper;
47  import org.codehaus.modello.plugins.xml.AbstractXmlGenerator;
48  import org.codehaus.modello.plugins.xml.metadata.XmlAssociationMetadata;
49  import org.codehaus.modello.plugins.xml.metadata.XmlClassMetadata;
50  import org.codehaus.modello.plugins.xml.metadata.XmlFieldMetadata;
51  import org.codehaus.modello.plugins.xml.metadata.XmlModelMetadata;
52  import org.codehaus.plexus.util.StringUtils;
53  import org.codehaus.plexus.util.WriterFactory;
54  import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
55  import org.codehaus.plexus.util.xml.XMLWriter;
56  
57  /**
58   * @author <a href="mailto:jason@modello.org">Jason van Zyl</a>
59   * @author <a href="mailto:emmanuel@venisse.net">Emmanuel Venisse</a>
60   */
61  public class XdocGenerator
62      extends AbstractXmlGenerator
63  {
64      private static final VersionRange DEFAULT_VERSION_RANGE = new VersionRange( "0.0.0+" );
65  
66      private Version firstVersion = DEFAULT_VERSION_RANGE.getFromVersion();
67  
68      private Version version = DEFAULT_VERSION_RANGE.getFromVersion();
69  
70      public void generate( Model model, Properties parameters )
71          throws ModelloException
72      {
73          initialize( model, parameters );
74  
75          if ( parameters.getProperty( ModelloParameterConstants.FIRST_VERSION ) != null )
76          {
77              firstVersion = new Version( parameters.getProperty( ModelloParameterConstants.FIRST_VERSION ) );
78          }
79  
80          if ( parameters.getProperty( ModelloParameterConstants.VERSION ) != null )
81          {
82              version = new Version( parameters.getProperty( ModelloParameterConstants.VERSION ) );
83          }
84  
85          try
86          {
87              generateXdoc( parameters );
88          }
89          catch ( IOException ex )
90          {
91              throw new ModelloException( "Exception while generating XDoc.", ex );
92          }
93      }
94  
95      private void generateXdoc( Properties parameters )
96          throws IOException
97      {
98          Model objectModel = getModel();
99  
100         File directory = getOutputDirectory();
101 
102         if ( isPackageWithVersion() )
103         {
104             directory = new File( directory, getGeneratedVersion().toString() );
105         }
106 
107         if ( !directory.exists() )
108         {
109             directory.mkdirs();
110         }
111 
112         // we assume parameters not null
113         String xdocFileName = parameters.getProperty( ModelloParameterConstants.OUTPUT_XDOC_FILE_NAME );
114 
115         File f = new File( directory, objectModel.getId() + ".xml" );
116 
117         if ( xdocFileName != null )
118         {
119             f = new File( directory, xdocFileName );
120         }
121 
122         Writer writer = WriterFactory.newXmlWriter( f );
123 
124         XMLWriter w = new PrettyPrintXMLWriter( writer );
125 
126         writer.write( "<?xml version=\"1.0\"?>\n" );
127 
128         initHeader( w );
129 
130         w.startElement( "document" );
131 
132         w.startElement( "properties" );
133 
134         writeTextElement( w, "title", objectModel.getName() );
135 
136         w.endElement();
137 
138         // Body
139 
140         w.startElement( "body" );
141 
142         w.startElement( "section" );
143 
144         w.addAttribute( "name", objectModel.getName() );
145 
146         writeMarkupElement( w, "p", getDescription( objectModel ) );
147 
148         // XML representation of the model with links
149         ModelClass root = objectModel.getClass( objectModel.getRoot( getGeneratedVersion() ), getGeneratedVersion() );
150 
151         writeMarkupElement( w, "source", "\n" + getModelXmlDescriptor( root ) );
152 
153         // Element descriptors
154         // Traverse from root so "abstract" models aren't included
155         writeModelDescriptor( w, root );
156 
157         w.endElement();
158 
159         w.endElement();
160 
161         w.endElement();
162 
163         writer.flush();
164 
165         writer.close();
166     }
167 
168     /**
169      * Get the anchor name by which model classes can be accessed in the generated xdoc/html file.
170      *
171      * @param tagName the name of the XML tag of the model class
172      * @return the corresponding anchor name
173      */
174     private String getAnchorName( String tagName )
175     {
176         return "class_" + tagName ;
177     }
178 
179     /**
180      * Write description of the whole model.
181      *
182      * @param w the output writer
183      * @param rootModelClass the root class of the model
184      */
185     private void writeModelDescriptor( XMLWriter w, ModelClass rootModelClass )
186     {
187         writeElementDescriptor( w, rootModelClass, null, new HashSet<String>() );
188     }
189 
190     /**
191      * Write description of an element of the XML representation of the model. This method is recursive.
192      *
193      * @param w the output writer
194      * @param modelClass the mode class to describe
195      * @param association the association we are coming from (can be <code>null</code>)
196      * @param written set of data already written
197      */
198     private void writeElementDescriptor( XMLWriter w, ModelClass modelClass, ModelAssociation association,
199                                          Set<String> written )
200     {
201         String tagName = resolveTagName( modelClass, association );
202 
203         String id = getId( tagName, modelClass );
204         if ( written.contains( id ) )
205         {
206             // tag already written for this model class accessed as this tag name
207             return;
208         }
209         written.add( id );
210 
211         written.add( tagName );
212 
213         w.startElement( "a" );
214 
215         w.addAttribute( "name", getAnchorName( tagName ) );
216 
217         w.endElement();
218 
219         w.startElement( "subsection" );
220 
221         w.addAttribute( "name", tagName );
222 
223         writeMarkupElement( w, "p", getDescription( modelClass ) );
224 
225         List<ModelField> elementFields = getFieldsForXml( modelClass, getGeneratedVersion() );
226 
227         ModelField contentField = getContentField( elementFields );
228 
229         if ( contentField != null )
230         {
231             // this model class has a Content field
232             w.startElement( "p" );
233 
234             writeTextElement( w, "b", "Element Content: " );
235 
236             w.writeMarkup( getDescription( contentField ) );
237 
238             w.endElement();
239         }
240 
241         List<ModelField> attributeFields = getXmlAttributeFields( elementFields );
242 
243         elementFields.removeAll( attributeFields );
244 
245         writeFieldsTable( w, attributeFields, false ); // write attributes
246         writeFieldsTable( w, elementFields, true ); // write elements
247 
248         w.endElement();
249 
250         // check every fields that are inner associations to write their element descriptor
251         for ( ModelField f : elementFields )
252         {
253             if ( isInnerAssociation( f ) )
254             {
255                 ModelAssociation assoc = (ModelAssociation) f;
256                 ModelClass fieldModelClass = getModel().getClass( assoc.getTo(), getGeneratedVersion() );
257 
258                 if ( !written.contains( getId( resolveTagName( fieldModelClass, assoc ), fieldModelClass ) ) )
259                 {
260                     writeElementDescriptor( w, fieldModelClass, assoc, written );
261                 }
262             }
263         }
264     }
265 
266     private String getId( String tagName, ModelClass modelClass )
267     {
268         return tagName + '/' + modelClass.getPackageName() + '.' + modelClass.getName();
269     }
270 
271     /**
272      * Write a table containing model fields description.
273      *
274      * @param w the output writer
275      * @param fields the fields to add in the table
276      * @param elementFields <code>true</code> if fields are elements, <code>false</code> if fields are attributes
277      */
278     private void writeFieldsTable( XMLWriter w, List<ModelField> fields, boolean elementFields )
279     {
280         if ( fields == null || fields.isEmpty() )
281         {
282             // skip empty table
283             return;
284         }
285 
286         // skip if only one element field with xml.content == true
287         if ( elementFields && ( fields.size() == 1 ) && hasContentField( fields ) )
288         {
289             return;
290         }
291 
292         w.startElement( "table" );
293 
294         w.startElement( "tr" );
295 
296         writeTextElement( w, "th", elementFields ? "Element" : "Attribute" );
297 
298         writeTextElement( w, "th", "Type" );
299 
300         boolean showSinceColumn = version.greaterThan( firstVersion );
301 
302         if ( showSinceColumn )
303         {
304             writeTextElement( w, "th", "Since" );
305         }
306 
307         writeTextElement( w, "th", "Description" );
308 
309         w.endElement(); // tr
310 
311         for ( ModelField f : fields )
312         {
313             XmlFieldMetadata xmlFieldMetadata = (XmlFieldMetadata) f.getMetadata( XmlFieldMetadata.ID );
314 
315             if ( xmlFieldMetadata.isContent() )
316             {
317                 continue;
318             }
319 
320             w.startElement( "tr" );
321 
322             // Element/Attribute column
323 
324             String tagName = resolveTagName( f, xmlFieldMetadata );
325 
326             w.startElement( "td" );
327 
328             w.startElement( "code" );
329 
330             boolean manyAssociation = false;
331 
332             if ( f instanceof ModelAssociation )
333             {
334                 ModelAssociation assoc = (ModelAssociation) f;
335 
336                 XmlAssociationMetadata xmlAssociationMetadata =
337                     (XmlAssociationMetadata) assoc.getAssociationMetadata( XmlAssociationMetadata.ID );
338 
339                 manyAssociation = assoc.isManyMultiplicity();
340 
341                 String itemTagName = manyAssociation ? resolveTagName( tagName, xmlAssociationMetadata ) : tagName;
342 
343                 if ( manyAssociation && xmlAssociationMetadata.isWrappedItems() )
344                 {
345                     w.writeText( tagName );
346                     w.writeMarkup( "/" );
347                 }
348                 if ( isInnerAssociation( f ) )
349                 {
350                     w.startElement( "a" );
351                     w.addAttribute( "href", "#" + getAnchorName( itemTagName ) );
352                     w.writeText( itemTagName );
353                     w.endElement();
354                 }
355                 else if ( ModelDefault.PROPERTIES.equals( f.getType() ) )
356                 {
357                     if ( xmlAssociationMetadata.isMapExplode() )
358                     {
359                         w.writeText( "(key,value)" );
360                     }
361                     else
362                     {
363                         w.writeMarkup( "<i>key</i>=<i>value</i>" );
364                     }
365                 }
366                 else
367                 {
368                     w.writeText( itemTagName );
369                 }
370                 if ( manyAssociation )
371                 {
372                     w.writeText( "*" );
373                 }
374             }
375             else
376             {
377                 w.writeText( tagName );
378             }
379 
380             w.endElement(); // code
381 
382             w.endElement(); // td
383 
384             // Type column
385 
386             w.startElement( "td" );
387 
388             w.startElement( "code" );
389 
390             if ( f instanceof ModelAssociation )
391             {
392                 ModelAssociation assoc = (ModelAssociation) f;
393 
394                 if ( assoc.isOneMultiplicity() )
395                 {
396                     w.writeText( assoc.getTo() );
397                 }
398                 else
399                 {
400                     w.writeText( assoc.getType().substring( "java.util.".length() ) );
401 
402                     if ( assoc.isGenericType() )
403                     {
404                         w.writeText( "<" + assoc.getTo() + ">" );
405                     }
406                 }
407             }
408             else
409             {
410                 w.writeText( f.getType() );
411             }
412 
413             w.endElement(); // code
414 
415             w.endElement(); // td
416 
417             // Since column
418 
419             if ( showSinceColumn )
420             {
421                 w.startElement( "td" );
422 
423                 if ( f.getVersionRange() != null )
424                 {
425                     Version fromVersion = f.getVersionRange().getFromVersion();
426                     if ( fromVersion != null && fromVersion.greaterThan( firstVersion ) )
427                     {
428                         w.writeMarkup( fromVersion.toString() );
429                     }
430                 }
431 
432                 w.endElement();
433             }
434 
435             // Description column
436 
437             w.startElement( "td" );
438 
439             if ( manyAssociation )
440             {
441                 w.writeMarkup( "<b>(Many)</b> " );
442             }
443 
444             w.writeMarkup( getDescription( f ) );
445 
446             // Write the default value, if it exists.
447             // But only for fields that are not a ModelAssociation
448             if ( f.getDefaultValue() != null && !( f instanceof ModelAssociation ) )
449             {
450                 w.writeMarkup( "<br/><strong>Default value is</strong>: " );
451 
452                 writeTextElement( w, "code", f.getDefaultValue() );
453 
454                 w.writeText( "." );
455             }
456 
457             w.endElement(); // td
458 
459             w.endElement(); // tr
460         }
461 
462         w.endElement(); // table
463 
464     }
465 
466     /**
467      * Build the pretty tree describing the XML representation of the model.
468      *
469      * @param rootModelClass the model root class
470      * @return the String representing the tree model
471      */
472     private String getModelXmlDescriptor( ModelClass rootModelClass )
473     {
474         return getElementXmlDescriptor( rootModelClass, null, new Stack<String>() );
475     }
476 
477     /**
478      * Build the pretty tree describing the XML representation of an element of the model. This method is recursive.
479      *
480      * @param modelClass the class we are printing the model
481      * @param association the association we are coming from (can be <code>null</code>)
482      * @param stack the stack of elements that have been traversed to come to the current one
483      * @return the String representing the tree model
484      * @throws ModelloRuntimeException
485      */
486     private String getElementXmlDescriptor( ModelClass modelClass, ModelAssociation association, Stack<String> stack )
487         throws ModelloRuntimeException
488     {
489         StringBuffer sb = new StringBuffer();
490 
491         appendSpacer( sb, stack.size() );
492 
493         String tagName = resolveTagName( modelClass, association );
494 
495         // <tagName
496         sb.append( "&lt;<a href=\"#" ).append( getAnchorName( tagName ) ).append( "\">" );
497         sb.append( tagName ).append( "</a>" );
498 
499         boolean addNewline = false;
500         if ( stack.size() == 0 )
501         {
502             // try to add XML Schema reference
503             try
504             {
505                 String targetNamespace = XsdModelHelper.getTargetNamespace( modelClass.getModel(), getGeneratedVersion() );
506 
507                 XmlModelMetadata xmlModelMetadata = (XmlModelMetadata) modelClass.getModel().getMetadata( XmlModelMetadata.ID );
508 
509                 if ( StringUtils.isNotBlank( targetNamespace ) && ( xmlModelMetadata.getSchemaLocation() != null ) )
510                 {
511                     String schemaLocation = xmlModelMetadata.getSchemaLocation( getGeneratedVersion() );
512 
513                     sb.append( " xmlns=\"" + targetNamespace + "\"" );
514                     sb.append( " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" );
515                     sb.append( "  xsi:schemaLocation=\"" + targetNamespace );
516                     sb.append( " <a href=\"" + schemaLocation + "\">" + schemaLocation + "</a>\"" );
517 
518                     addNewline = true;
519                 }
520             }
521             catch ( ModelloException me )
522             {
523                 // ignore unavailable XML Schema configuration
524             }
525         }
526 
527         String id = tagName + '/' + modelClass.getPackageName() + '.' + modelClass.getName();
528         if ( stack.contains( id ) )
529         {
530             // recursion detected
531             sb.append( "&gt;...recursion...&lt;" ).append( tagName ).append( "&gt;\n" );
532             return sb.toString();
533         }
534 
535         List<ModelField> fields = getFieldsForXml( modelClass, getGeneratedVersion() );
536 
537         List<ModelField> attributeFields = getXmlAttributeFields( fields );
538 
539         if ( attributeFields.size() > 0 )
540         {
541 
542             for ( ModelField f : attributeFields )
543             {
544                 XmlFieldMetadata xmlFieldMetadata = (XmlFieldMetadata) f.getMetadata( XmlFieldMetadata.ID );
545 
546                 if ( addNewline )
547                 {
548                     addNewline = false;
549 
550                     sb.append( "\n  " );
551                 }
552                 else
553                 {
554                     sb.append( ' ' );
555                 }
556 
557                 sb.append( resolveTagName( f, xmlFieldMetadata ) ).append( "=.." );
558             }
559 
560             sb.append( ' ' );
561 
562         }
563 
564         fields.removeAll( attributeFields );
565 
566         if ( ( fields.size() == 0 ) || ( ( fields.size() == 1 ) && hasContentField( fields ) ) )
567         {
568             sb.append( "/&gt;\n" );
569         }
570         else
571         {
572             sb.append( "&gt;\n" );
573 
574             stack.push( id );
575 
576             for ( ModelField f : fields )
577             {
578                 XmlFieldMetadata xmlFieldMetadata = (XmlFieldMetadata) f.getMetadata( XmlFieldMetadata.ID );
579 
580                 XdocFieldMetadata xdocFieldMetadata = (XdocFieldMetadata) f.getMetadata( XdocFieldMetadata.ID );
581 
582                 if ( XdocFieldMetadata.BLANK.equals( xdocFieldMetadata.getSeparator() ) )
583                 {
584                     sb.append( '\n' );
585                 }
586 
587                 String fieldTagName = resolveTagName( f, xmlFieldMetadata );
588 
589                 if ( isInnerAssociation( f ) )
590                 {
591                     ModelAssociation assoc = (ModelAssociation) f;
592 
593                     boolean wrappedItems = false;
594                     if ( assoc.isManyMultiplicity() )
595                     {
596                         XmlAssociationMetadata xmlAssociationMetadata =
597                             (XmlAssociationMetadata) assoc.getAssociationMetadata( XmlAssociationMetadata.ID );
598                         wrappedItems = xmlAssociationMetadata.isWrappedItems();
599                     }
600 
601                     if ( wrappedItems )
602                     {
603                         appendSpacer( sb, stack.size() );
604 
605                         sb.append( "&lt;" ).append( fieldTagName ).append( "&gt;\n" );
606 
607                         stack.push( fieldTagName );
608                     }
609 
610                     ModelClass fieldModelClass = getModel().getClass( assoc.getTo(), getGeneratedVersion() );
611 
612                     sb.append( getElementXmlDescriptor( fieldModelClass, assoc, stack ) );
613 
614                     if ( wrappedItems )
615                     {
616                         stack.pop();
617 
618                         appendSpacer( sb, stack.size() );
619 
620                         sb.append( "&lt;/" ).append( fieldTagName ).append( "&gt;\n" );
621                     }
622                 }
623                 else if ( ModelDefault.PROPERTIES.equals( f.getType() ) )
624                 {
625                     ModelAssociation assoc = (ModelAssociation) f;
626                     XmlAssociationMetadata xmlAssociationMetadata =
627                         (XmlAssociationMetadata) assoc.getAssociationMetadata( XmlAssociationMetadata.ID );
628 
629                     appendSpacer( sb, stack.size() );
630                     sb.append( "&lt;" ).append( fieldTagName ).append( "&gt;\n" );
631 
632                     if ( xmlAssociationMetadata.isMapExplode() )
633                     {
634                         appendSpacer( sb, stack.size() + 1 );
635                         sb.append( "&lt;key/&gt;\n" );
636                         appendSpacer( sb, stack.size() + 1 );
637                         sb.append( "&lt;value/&gt;\n" );
638                     }
639                     else
640                     {
641                         appendSpacer( sb, stack.size() + 1 );
642                         sb.append( "&lt;<i>key</i>&gt;<i>value</i>&lt;/<i>key</i>&gt;\n" );
643                     }
644 
645                     appendSpacer( sb, stack.size() );
646                     sb.append( "&lt;/" ).append( fieldTagName ).append( "&gt;\n" );
647                 }
648                 else
649                 {
650                     appendSpacer( sb, stack.size() );
651 
652                     sb.append( "&lt;" ).append( fieldTagName ).append( "/&gt;\n" );
653                 }
654             }
655 
656             stack.pop();
657 
658             appendSpacer( sb, stack.size() );
659 
660             sb.append( "&lt;/" ).append( tagName ).append( "&gt;\n" );
661         }
662 
663         return sb.toString();
664     }
665 
666     /**
667      * Compute the tagName of a given class, living inside an association.
668      * @param modelClass the class we are looking for the tag name
669      * @param association the association where this class is used
670      * @return the tag name to use
671      * @todo refactor to use resolveTagName helpers instead
672      */
673     private String resolveTagName( ModelClass modelClass, ModelAssociation association )
674     {
675         XmlClassMetadata xmlClassMetadata = (XmlClassMetadata) modelClass.getMetadata( XmlClassMetadata.ID );
676 
677         String tagName;
678         if ( xmlClassMetadata == null || xmlClassMetadata.getTagName() == null )
679         {
680             if ( association == null )
681             {
682                 tagName = uncapitalise( modelClass.getName() );
683             }
684             else
685             {
686                 tagName = association.getName();
687 
688                 if ( association.isManyMultiplicity() )
689                 {
690                     tagName = singular( tagName );
691                 }
692             }
693         }
694         else
695         {
696             tagName = xmlClassMetadata.getTagName();
697         }
698 
699         if ( association != null )
700         {
701             XmlFieldMetadata xmlFieldMetadata = (XmlFieldMetadata) association.getMetadata( XmlFieldMetadata.ID );
702 
703             XmlAssociationMetadata xmlAssociationMetadata =
704                 (XmlAssociationMetadata) association.getAssociationMetadata( XmlAssociationMetadata.ID );
705 
706             if ( xmlFieldMetadata != null )
707             {
708                 if ( xmlAssociationMetadata.getTagName() != null )
709                 {
710                     tagName = xmlAssociationMetadata.getTagName();
711                 }
712                 else if ( xmlFieldMetadata.getTagName() != null )
713                 {
714                     tagName = xmlFieldMetadata.getTagName();
715 
716                     if ( association.isManyMultiplicity() )
717                     {
718                         tagName = singular( tagName );
719                     }
720                 }
721             }
722         }
723 
724         return tagName;
725     }
726 
727     /**
728      * Appends the required spacers to the given StringBuffer.
729      * @param sb where to append the spacers
730      * @param depth the depth of spacers to generate
731      */
732     private static void appendSpacer( StringBuffer sb, int depth )
733     {
734         for ( int i = 0; i < depth; i++ )
735         {
736             sb.append( "  " );
737         }
738     }
739 
740     private static String getDescription( BaseElement element )
741     {
742         return ( element.getDescription() == null ) ? "No description." : element.getDescription();
743     }
744 
745     private static void writeTextElement( XMLWriter w, String name, String text )
746     {
747         w.startElement( name );
748         w.writeText( text );
749         w.endElement();
750     }
751 
752     private static void writeMarkupElement( XMLWriter w, String name, String markup )
753     {
754         w.startElement( name );
755         w.writeMarkup( markup );
756         w.endElement();
757     }
758 }