/Prototipo/Servlet/lib/xstream-distribution-1.4.1-bin/xstream-1.4.1/docs/converter-tutorial.html
http://prototipomemoria.googlecode.com/ · HTML · 693 lines · 558 code · 123 blank · 12 comment · 0 complexity · fc036998685304a6130ad91049187bb6 MD5 · raw file
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml">
- <!--
- Copyright (C) 2005, 2006 Joe Walnes.
- Copyright (C) 2006, 2007, 2008 XStream committers.
- All rights reserved.
-
- The software in this package is published under the terms of the BSD
- style license a copy of which has been included with this distribution in
- the LICENSE.txt file.
-
- Created on 29. January 2005 by Joe Walnes
- -->
- <head>
- <title>XStream - Converter Tutorial</title>
- <link rel="stylesheet" type="text/css" href="style.css"/>
-
-
-
- <!-- Google analytics -->
- <script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
- </script>
- <script type="text/javascript">
- _uacct = "UA-110973-2";
- urchinTracker();
- </script>
- </head>
- <body>
- <div id="banner">
- <a href="index.html"><img id="logo" src="logo.gif" alt="XStream"/></a>
- </div>
- <div id="center" class="Content2Column"> <!-- Content3Column for index -->
- <div id="content">
- <h1 class="FirstChild">Converter Tutorial</h1>
-
- <h1 id="SimpleConverter">Simple Converter</h1>
- <h2>Setting up a simple example</h2>
- <p>This is the most basic converter... let's start with a simple Person:</p>
- <div class="Source Java"><pre>package com.thoughtworks.xstream.examples;
- public class Person {
- private String name;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- }</pre></div><p>So let's create a person and convert it to
- XML...</p><div class="Source Java"><pre>package com.thoughtworks.xstream.examples;
- import com.thoughtworks.xstream.XStream;
- import com.thoughtworks.xstream.io.xml.DomDriver;
- public class PersonTest {
- public static void main(String[] args) {
- Person person = new Person();
- person.setName("Guilherme");
- XStream xStream = new XStream(new DomDriver());
- System.out.println(xStream.toXML(person));
- }
- }</pre></div><p>This results in a really ugly XML code which contains the full
- class name (including
- package)...</p><div class="Source Java"><pre><com.thoughtworks.xstream.examples.Person>
- <name>Guilherme</name>
- </com.thoughtworks.xstream.examples.Person></pre></div><p>So we make use
- of an 'alias' to change this full class name to something more 'human', for
- example
- 'person'.</p><div class="Source Java"><pre>XStream xStream = new XStream(new DomDriver());
- xStream.alias("person", Person.class);
- System.out.println(xStream.toXML(person));</pre></div><p>And the outcome is
- much easier to read (and
- smaller):</p><div class="Source Java"><pre><person>
- <name>Guilherme</name>
- </person></pre></div><p>Now that we have configured a simple class to
- play with, let's see what XStream converters can do for us...</p>
- <h2 id="CreatingPersonConverter">Creating a PersonConverter</h2>
- <p>Let's create a simple converter capable
- of:</p><ol style="list-style-type: decimal">
- <li>telling its capable of converting Person's</li>
- <li>translating a Person instance in XML</li>
- <li>translate XML into a new Person</li>
- </ol><p>We begin creating the PersonConverter class and implementing the
- Converter
- interface:</p><div class="Source Java"><pre>package com.thoughtworks.xstream.examples;
- import com.thoughtworks.xstream.converters.Converter;
- import com.thoughtworks.xstream.converters.MarshallingContext;
- import com.thoughtworks.xstream.converters.UnmarshallingContext;
- import com.thoughtworks.xstream.io.HierarchicalStreamReader;
- import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
- public class PersonConverter implements Converter {
- public boolean canConvert(Class clazz) {
- return false;
- }
- public void marshal(Object value, HierarchicalStreamWriter writer,
- MarshallingContext context) {
- }
- public Object unmarshal(HierarchicalStreamReader reader,
- UnmarshallingContext context) {
- return null;
- }
- }</pre></div><p>Now we tell whoever calls us that we can handle only Person's
- (and <b>nothing</b> else, including those classes which extends
- Person).</p><div class="Source Java"><pre>public boolean canConvert(Class clazz) {
- return clazz.equals(Person.class);
- }</pre></div><p>The second step is usually quite clean, unless you are dealing
- with generic converters.</p><p>The marshal method is responsible for
- translating an object to XML. It receives three
- arguments:</p><ol style="list-style-type: decimal">
- <li>the object we are trying to convert</li>
- <li>the writer were we should output the data</li>
- <li>the current marshalling context</li>
- </ol><p>We start casting the object to
- person:</p><div class="Source Java"><pre>Person person = (Person) value;</pre></div><p>Now
- we can output the data... let's start creating a node called <i>fullname</i>
- and adding the person's name to
- it:</p><div class="Source Java"><pre>writer.startNode("fullname");
- writer.setValue(person.getName());
- writer.endNode();</pre></div><p>Quite simple
- huh?</p><div class="Source Java"><pre>public void marshal(Object value, HierarchicalStreamWriter writer,
- MarshallingContext context) {
- Person person = (Person) value;
- writer.startNode("fullname");
- writer.setValue(person.getName());
- writer.endNode();
- }</pre></div><p>We could have called start/end node as many times as we would
- like (but remember to close everything you open)... and conversion usually
- takes place when calling the <i>setValue</i> method.</p><p>And now let's go to
- the unmarshal. We use the <i>moveDown</i> and <i>moveUp</i> methods to move
- in the tree hierarchy, so we can simply <i>moveDown</i>, read the value and
- <i>moveUp</i>.</p><div class="Source Java"><pre> Person person = new Person();
- reader.moveDown();
- person.setName(reader.getValue());
- reader.moveUp();</pre></div><p>Which gives us the following
- converter:</p><div class="Source Java"><pre>package com.thoughtworks.xstream.examples;
- import com.thoughtworks.xstream.converters.Converter;
- import com.thoughtworks.xstream.converters.MarshallingContext;
- import com.thoughtworks.xstream.converters.UnmarshallingContext;
- import com.thoughtworks.xstream.io.HierarchicalStreamReader;
- import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
- public class PersonConverter implements Converter {
- public boolean canConvert(Class clazz) {
- return clazz.equals(Person.class);
- }
- public void marshal(Object value, HierarchicalStreamWriter writer,
- MarshallingContext context) {
- Person person = (Person) value;
- writer.startNode("fullname");
- writer.setValue(person.getName());
- writer.endNode();
- }
- public Object unmarshal(HierarchicalStreamReader reader,
- UnmarshallingContext context) {
- Person person = new Person();
- reader.moveDown();
- person.setName(reader.getValue());
- reader.moveUp();
- return person;
- }
- }</pre></div><p>Now let's register our converter and see how our application
- <i>main</i> method looks
- like:</p><div class="Source Java"><pre>package com.thoughtworks.xstream.examples;
- import com.thoughtworks.xstream.XStream;
- import com.thoughtworks.xstream.io.xml.DomDriver;
- public class PersonTest {
- public static void main(String[] args) {
- Person person = new Person();
- person.setName("Guilherme");
- XStream xStream = new XStream(new DomDriver());
- xStream.registerConverter(new PersonConverter());
- xStream.alias("person", Person.class);
- System.out.println(xStream.toXML(person));
- }
- }</pre></div><p>Did you notice how we registered our converter? It's a simple
- call to
- <i>registerConverter</i>:</p><div class="Source Java"><pre>xStream.registerConverter(new PersonConverter());</pre></div><p>The
- final result
- is:</p><div class="Source Java"><pre><person>
- <fullname>Guilherme</fullname>
- </person></pre></div><p>So you might say... that only changed my tree, I
- want to convert data!</p><p>Try using an attribute called <i>fullname</i> in
- the <i>person</i> tag instead of creating a new child node.</p>
- <h2 id="SingleValueConverter">An alternative for types with String representation</h2>
- <p>Let's enhance the Person with a String representation, that contains all necessary
- text to recreate the instance:</p>
- <div class="Source Java"><pre>package com.thoughtworks.xstream.examples;
- public class Person {
- private String name;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public String toString() {
- return getName();
- }
- }</pre></div><p>In this case we can simplify our Converter to</p>
- <div class="Source Java"><pre>package com.thoughtworks.xstream.examples;
- import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;
- public class PersonConverter extends AbstractSingleValueConverter {
- public boolean canConvert(Class clazz) {
- return clazz.equals(Person.class);
- }
- public Object fromString(String str) {
- Person person = new Person();
- person.setName(string);
- return person;
- }
- }</pre></div><p>But even nicer, our XML is also simplified (using the alias for the
- Person class). Since the String representation is complete, a nested element is not
- necessary anymore:</p>
- <div class="Source Java"><pre><person>Guilherme</person></pre></div>
- <h1 id="CustomConverter">Date Converter</h1>
- <p>Now that we know how the <i>Converter</i> interface works, let's create a
- simple calendar converter which uses the locale to convert the
- information.</p><p>Our converter will receive the Locale in its constructor
- and we will keep a reference to it in a member
- variable:</p><div class="Source Java"><pre>package com.thoughtworks.xstream.examples;
- import java.util.Locale;
- import com.thoughtworks.xstream.converters.Converter;
- import com.thoughtworks.xstream.converters.MarshallingContext;
- import com.thoughtworks.xstream.converters.UnmarshallingContext;
- import com.thoughtworks.xstream.io.HierarchicalStreamReader;
- import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
- public class DateConverter implements Converter {
- private Locale locale;
- public DateConverter(Locale locale) {
- super();
- this.locale = locale;
- }
- public boolean canConvert(Class clazz) {
- return false;
- }
- public void marshal(Object value, HierarchicalStreamWriter writer,
- MarshallingContext context) {
- }
- public Object unmarshal(HierarchicalStreamReader reader,
- UnmarshallingContext context) {
- return null;
- }
- }</pre></div><p>Now let's convert anything which extends <i>Calendar</i>:
- means if instances of class <i>clazz</i> can be assigned to the
- <i>Calendar</i> class, they extends the abstract class
- <i>Calendar</i>:</p><div class="Source Java"><pre>public boolean canConvert(Class clazz) {
- return Calendar.class.isAssignableFrom(clazz);
- }</pre></div><p>Let's go for converting a <i>Calendar</i> in a localized
- string... we first cast the object to <i>Calendar</i>, extract its <i>Date</i>
- and then use a <i>DateFormat</i> factory method to get a date converter to our
- localized
- string.</p><div class="Source Java"><pre>public void marshal(Object value, HierarchicalStreamWriter writer,
- MarshallingContext context) {
- Calendar calendar = (Calendar) value;
- // grabs the date
- Date date = calendar.getTime();
- // grabs the formatter
- DateFormat formatter = DateFormat.getDateInstance(DateFormat.FULL,
- this.locale);
- // formats and sets the value
- writer.setValue(formatter.format(date));
- }</pre></div><p>And the other way around... in order to unmarshall, we create
- a <i>GregorianCalendar</i>, retrieves the localized <i>DateFormat</i>
- instance, parses the string into a <i>Date</i> and puts this date in the
- original
- <i>GregorianCalendar</i>:</p><div class="Source Java"><pre>public Object unmarshal(HierarchicalStreamReader reader,
- UnmarshallingContext context) {
- // creates the calendar
- GregorianCalendar calendar = new GregorianCalendar();
- // grabs the converter
- DateFormat formatter = DateFormat.getDateInstance(DateFormat.FULL,
- this.locale);
- // parses the string and sets the time
- try {
- calendar.setTime(formatter.parse(reader.getValue()));
- } catch (ParseException e) {
- throw new ConversionException(e.getMessage(), e);
- }
- // returns the new object
- return calendar;
- }</pre></div><p>Note 1: remember that some <i>DateFormat</i> implementations
- are not thread-safe, therefore don't put your formatter as a member of your
- converter.</p><p>Note 2: this implementation <b>will</b> convert other types
- of Calendar's to GregorianCalendar after save/load. If this is not what you
- want, change your <i>canConvert</i> method to return <i>true</i> only if
- <i>class</i> equals <i>GregorianCalendar</i>.</p><p>So we get the following
- converter:</p><div class="Source Java"><pre>package com.thoughtworks.xstream.examples;
- import java.text.DateFormat;
- import java.text.ParseException;
- import java.util.Calendar;
- import java.util.Date;
- import java.util.GregorianCalendar;
- import java.util.Locale;
- import com.thoughtworks.xstream.converters.ConversionException;
- import com.thoughtworks.xstream.converters.Converter;
- import com.thoughtworks.xstream.converters.MarshallingContext;
- import com.thoughtworks.xstream.converters.UnmarshallingContext;
- import com.thoughtworks.xstream.io.HierarchicalStreamReader;
- import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
- public class DateConverter implements Converter {
- private Locale locale;
- public DateConverter(Locale locale) {
- super();
- this.locale = locale;
- }
- public boolean canConvert(Class clazz) {
- return Calendar.class.isAssignableFrom(clazz);
- }
- public void marshal(Object value, HierarchicalStreamWriter writer,
- MarshallingContext context) {
- Calendar calendar = (Calendar) value;
- Date date = calendar.getTime();
- DateFormat formatter = DateFormat.getDateInstance(DateFormat.FULL,
- this.locale);
- writer.setValue(formatter.format(date));
- }
- public Object unmarshal(HierarchicalStreamReader reader,
- UnmarshallingContext context) {
- GregorianCalendar calendar = new GregorianCalendar();
- DateFormat formatter = DateFormat.getDateInstance(DateFormat.FULL,
- this.locale);
- try {
- calendar.setTime(formatter.parse(reader.getValue()));
- } catch (ParseException e) {
- throw new ConversionException(e.getMessage(), e);
- }
- return calendar;
- }
- }</pre></div><p>And let's try it out. We create a <i>DateTest</i> class with a
- <i>main</i> method:</p><ol style="list-style-type: decimal">
- <li>creates a calendar (current date)</li>
- <li>creates the XStream object</li>
- <li>registers the converter with a Brazilian Portuguese locale</li>
- <li>translates the object in XML</li>
- </ol>
- <p>Well, we already know how to do all those steps... so let's go:</p>
- <div class="Source Java"><pre>package com.thoughtworks.xstream.examples;
- import java.util.Calendar;
- import java.util.GregorianCalendar;
- import java.util.Locale;
- import com.thoughtworks.xstream.XStream;
- import com.thoughtworks.xstream.io.xml.DomDriver;
- public class DateTest {
- public static void main(String[] args) {
- // grabs the current date from the virtual machine
- Calendar calendar = new GregorianCalendar();
- // creates the xstream
- XStream xStream = new XStream(new DomDriver());
- // brazilian portuguese locale
- xStream.registerConverter(new DateConverter(new Locale("pt", "br")));
- // prints the result
- System.out.println(xStream.toXML(calendar));
- }
- }</pre></div><p>The result? Well... it depends, but it will be something
- like:</p><div class="Source Java"><pre><gregorian-calendar>Sexta-feira, 10 de Fevereiro de 2006</gregorian-calendar></pre></div><p>Note:
- we did not put any alias as <i>gregorian-calendar</i> is the default alias for
- <i>GregorianCalendar</i>.</p><p>And now let's try to unmarshal the result
- shown
- above:</p><div class="Source Java"><pre>// loads the calendar from the string
- Calendar loaded = (Calendar) xStream
- .fromXML("<gregorian-calendar>Sexta-feira, 10 de Fevereiro de 2006</gregorian-calendar>");</pre></div><p>And
- print it using the system locale, short date
- format:</p><div class="Source Java"><pre>// prints using the system defined locale
- System.out.println(DateFormat.getDateInstance(DateFormat.SHORT).format(
- loaded.getTime()));</pre></div><p>The result might be
- something like (if your system locale is American
- English):</p><div class="Source Java"><pre>2/10/06</pre></div>
- <h1 id="ComplexConverter">Complex Converter</h1>
- <h2>Setting up another example</h2>
- <p>We already defined some classes, so let them glue together:</p>
- <div class="Source Java"><pre>package com.thoughtworks.xstream.examples;
- public class Birthday {
- private Person person;
- private Calendar date;
- private char gender;
- public Person getPerson() {
- return person;
- }
- public void setPerson(Person person) {
- this.person = person;
- }
- public Calendar getDate() {
- return date;
- }
- public void setDate(Calendar date) {
- this.date = date;
- }
-
- public char getGender() {
- return gender;
- }
- public void setGenderMale() {
- this.gender = 'm';
- }
- public void setGenderFemale() {
- this.gender = 'f';
- }
- }</pre></div><p>While XStream is capable of converting this class without any problem, we write our own custom converter
- just for demonstration. This time we want to reuse our already written converters for the Person and the Calendar and add an
- own attribute for the gender. The <code>canConvert</code> method is plain simple. We convert no derived classes this time,
- since they might have additional fields. But we reuse the converters registered in XStream for our member fields and handle
- <code>null</code> values:
- </p><div class="Source Java"><pre>package com.thoughtworks.xstream.examples;
- import java.util.Calendar;
- import com.thoughtworks.xstream.converters.ConversionException;
- import com.thoughtworks.xstream.converters.Converter;
- import com.thoughtworks.xstream.converters.MarshallingContext;
- import com.thoughtworks.xstream.converters.UnmarshallingContext;
- import com.thoughtworks.xstream.io.HierarchicalStreamReader;
- import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
- public class BirthdayConverter implements Converter {
- public boolean canConvert(Class clazz) {
- return Birthday.class == clazz;
- }
- public void marshal(Object value, HierarchicalStreamWriter writer,
- MarshallingContext context) {
- Birthday birthday = (Birthday)value;
- if (birthday.getGender() != '\0') {
- writer.addAttribute("gender", Character.toString(birthday.getGender()));
- }
- if (birthday.getPerson() != null) {
- writer.startNode("person");
- context.convertAnother(birthday.getPerson());
- writer.endNode();
- }
- if (birthday.getDate() != null) {
- writer.startNode("birth");
- context.convertAnother(birthday.getDate());
- writer.endNode();
- }
- }
- public Object unmarshal(HierarchicalStreamReader reader,
- UnmarshallingContext context) {
- Birthday birthday = new Birthday();
- String gender = reader.getAttribute("gender");
- if (gender != null) {
- if (gender.length() > 0) {
- if (gender.char(0) == 'f') {
- birthday.setGenderFemale();
- } else if (gender.char(0) == 'm') {
- birthday.setFemale();
- } else {
- throw new ConversionException("Invalid gender value: " + gender);
- }
- } else {
- throw new ConversionException("Empty string is invalid gender value");
- }
- }
- while (reader.hasMoreChildren()) {
- reader.moveDown();
- if ("person".equals(reader.getNodeName())) {
- Person person = (Person)context.convertAnother(birthday, Person.class);
- birthday.setPerson(person);
- } else if ("birth".equals(reader.getNodeName())) {
- Calendar date = (Calendar)context.convertAnother(birthday, Calendar.class);
- birthday.setDate(date);
- }
- reader.moveUp();
- }
- return birthday;
- }
- }</pre></div><p>The unmarshal method ensures the valid value for the gender by throwing a
- ConversionException for invalid entries.</p>
- <p class=highlight>Note, that attributes will always have to be written and read first. You work on a stream and
- accessing the value of a tag or its members will close the surrounding tag (that is still active when the method is
- called).</p>
- <p>If the implementation of <code>Birthday</code> ensures, that none of its fields
- could hold a <code>null</code> value and gender contains a valid value, then we could drop the
- <code>null</code> condition in the <code>marshal</code> method and in <code>unmarshal</code>
- we could omit the loop as well as the comparison of the tag names:</p><div class="Source Java"><pre>package com.thoughtworks.xstream.examples;
- import java.util.Calendar;
- import com.thoughtworks.xstream.converters.Converter;
- import com.thoughtworks.xstream.converters.MarshallingContext;
- import com.thoughtworks.xstream.converters.UnmarshallingContext;
- import com.thoughtworks.xstream.io.HierarchicalStreamReader;
- import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
- public class BirthdayConverter implements Converter {
- public boolean canConvert(Class clazz) {
- return Birthday.class == clazz;
- }
- public void marshal(Object value, HierarchicalStreamWriter writer,
- MarshallingContext context) {
- writer.addAttribute("gender", Character.toString(birthday.getGender()));
- Birthday birthday = (Birthday)value;
- writer.startNode("person");
- context.convertAnother(birthday.getPerson());
- writer.endNode();
- writer.startNode("birth");
- context.convertAnother(birthday.getDate());
- writer.endNode();
- }
- public Object unmarshal(HierarchicalStreamReader reader,
- UnmarshallingContext context) {
- Birthday birthday = new Birthday();
- if (reader.getAttribute("gender").charAt(0) == 'm') {
- birthday.setGenderMale();
- } else {
- birthday.setGenderFemale();
- }
- reader.moveDown();
- Person person = (Person)context.convertAnother(birthday, Person.class);
- birthday.setPerson(person);
- reader.moveUp();
- reader.moveDown();
- Calendar date = (Calendar)context.convertAnother(birthday, Calendar.class);
- birthday.setDate(date);
- reader.moveUp();
- return birthday;
- }
- }</pre></div>
-
- <br/>
- </div>
- </div>
- <div class="SidePanel" id="left">
- <div class="MenuGroup">
- <h1>Software</h1>
- <ul>
- <li><a href="index.html">About XStream</a></li>
- <li><a href="news.html">News</a></li>
- <li><a href="changes.html">Change History</a></li>
- <li><a href="versioning.html">About Versioning</a></li>
- </ul>
- </div>
- <div class="MenuGroup">
- <h1>Evaluating XStream</h1>
- <ul>
- <li><a href="tutorial.html">Two Minute Tutorial</a></li>
- <li><a href="graphs.html">Object references</a></li>
- <li><a href="manual-tweaking-output.html">Tweaking the Output</a></li>
- <li><a href="license.html">License</a></li>
- <li><a href="download.html">Download</a></li>
- <li><a href="references.html">References</a></li>
- <li><a href="parser-benchmarks.html">Parser Benchmarks</a></li>
- <li><a href="http://www.ohloh.net/projects/3459">Code Statistics</a></li>
- </ul>
- </div>
- <div class="MenuGroup">
- <h1>Using XStream</h1>
- <ul>
- <li><a href="architecture.html">Architecture Overview</a></li>
- <li><a href="converters.html">Converters</a></li>
- <li><a href="faq.html">Frequently Asked Questions</a></li>
- <li><a href="list-user.html">Users' Mailing List</a></li>
- <li><a href="issues.html">Reporting Issues</a></li>
- </ul>
- </div>
- <div class="MenuGroup">
- <h1>Javadoc</h1>
- <ul>
- <li><a href="javadoc/index.html">XStream Core</a></li>
- <li><a href="hibernate-javadoc/index.html">Hibernate Extensions</a></li>
- <li><a href="benchmark-javadoc/index.html">Benchmark Module</a></li>
- </ul>
- </div>
- <div class="MenuGroup">
- <h1>Tutorials</h1>
- <ul>
- <li><a href="tutorial.html">Two Minute Tutorial</a></li>
- <li><a href="alias-tutorial.html">Alias Tutorial</a></li>
- <li><a href="annotations-tutorial.html">Annotations Tutorial</a></li>
- <li class="currentLink">Converter Tutorial</li>
- <li><a href="objectstream.html">Object Streams Tutorial</a></li>
- <li><a href="persistence-tutorial.html">Persistence API Tutorial</a></li>
- <li><a href="json-tutorial.html">JSON Tutorial</a></li>
- </ul>
- </div>
- <div class="MenuGroup">
- <h1>Developing XStream</h1>
- <ul>
- <li><a href="how-to-contribute.html">How to Contribute</a></li>
- <li><a href="list-dev.html">Developers' Mailing List</a></li>
- <li><a href="team.html">Development Team</a></li>
- <li><a href="repository.html">Source Repository</a></li>
- <li><a href="http://bamboo.ci.codehaus.org/browse/XSTREAM">Continuous Integration</a></li>
- </ul>
- </div>
- </div>
- </body>
- </html>