001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.scxml.io;
018    
019    import java.io.StringWriter;
020    import java.util.Iterator;
021    import java.util.List;
022    import java.util.Map;
023    import java.util.Properties;
024    import java.util.Set;
025    
026    import javax.xml.transform.OutputKeys;
027    import javax.xml.transform.Result;
028    import javax.xml.transform.Source;
029    import javax.xml.transform.Transformer;
030    import javax.xml.transform.TransformerConfigurationException;
031    import javax.xml.transform.TransformerException;
032    import javax.xml.transform.TransformerFactory;
033    import javax.xml.transform.TransformerFactoryConfigurationError;
034    import javax.xml.transform.dom.DOMSource;
035    import javax.xml.transform.stream.StreamResult;
036    
037    import org.apache.commons.logging.LogFactory;
038    import org.apache.commons.scxml.SCXMLHelper;
039    import org.apache.commons.scxml.model.Action;
040    import org.apache.commons.scxml.model.Assign;
041    import org.apache.commons.scxml.model.Cancel;
042    import org.apache.commons.scxml.model.Data;
043    import org.apache.commons.scxml.model.Datamodel;
044    import org.apache.commons.scxml.model.Else;
045    import org.apache.commons.scxml.model.ElseIf;
046    import org.apache.commons.scxml.model.Exit;
047    import org.apache.commons.scxml.model.ExternalContent;
048    import org.apache.commons.scxml.model.Finalize;
049    import org.apache.commons.scxml.model.History;
050    import org.apache.commons.scxml.model.If;
051    import org.apache.commons.scxml.model.Initial;
052    import org.apache.commons.scxml.model.Invoke;
053    import org.apache.commons.scxml.model.Log;
054    import org.apache.commons.scxml.model.NamespacePrefixesHolder;
055    import org.apache.commons.scxml.model.OnEntry;
056    import org.apache.commons.scxml.model.OnExit;
057    import org.apache.commons.scxml.model.Parallel;
058    import org.apache.commons.scxml.model.Param;
059    import org.apache.commons.scxml.model.SCXML;
060    import org.apache.commons.scxml.model.Send;
061    import org.apache.commons.scxml.model.State;
062    import org.apache.commons.scxml.model.Transition;
063    import org.apache.commons.scxml.model.TransitionTarget;
064    import org.apache.commons.scxml.model.Var;
065    import org.w3c.dom.Node;
066    
067    /**
068     * <p>Utility class for serializing the Commons SCXML Java object
069     * model. Class uses the visitor pattern to trace through the
070     * object heirarchy. Used primarily for testing, debugging and
071     * visual verification.</p>
072     *
073     * <b>NOTE:</b> This serializer makes the following assumptions about the
074     * original SCXML document(s) parsed to create the object model:
075     * <ul>
076     *  <li>The default document namespace is the SCXML namespace:
077     *      <i>http://www.w3.org/2005/07/scxml</i></li>
078     *  <li>The Commons SCXML namespace
079     *      ( <i>http://commons.apache.org/scxml</i> ), if needed, uses the
080     *      &quot;<i>cs</i>&quot; prefix</li>
081     *  <li>All namespace prefixes needed throughout the document are
082     *      declared on the document root element (&lt;scxml&gt;)</li>
083     * </ul>
084     */
085    public class SCXMLSerializer {
086    
087        /** The indent to be used while serializing an SCXML object. */
088        private static final String INDENT = " ";
089        /** The JAXP transformer. */
090        private static final Transformer XFORMER = getTransformer();
091        /** The SCXML namespace. */
092        private static final String NAMESPACE_SCXML =
093            "http://www.w3.org/2005/07/scxml";
094        /** The Commons SCXML namespace. */
095        private static final String NAMESPACE_COMMONS_SCXML =
096            "http://commons.apache.org/scxml";
097    
098        /**
099         * Serialize this SCXML object (primarily for debugging).
100         *
101         * @param scxml
102         *            The SCXML to be serialized
103         * @return String The serialized SCXML
104         */
105        public static String serialize(final SCXML scxml) {
106            StringBuffer b =
107                new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n").
108                    append("<scxml xmlns=\"").append(NAMESPACE_SCXML).
109                    append("\"").append(serializeNamespaceDeclarations(scxml)).
110                    append(" version=\"").append(scxml.getVersion()).
111                    append("\" initial=\"").append(scxml.getInitial()).
112                    append("\">\n");
113            if (XFORMER == null) {
114                org.apache.commons.logging.Log log = LogFactory.
115                    getLog(SCXMLSerializer.class);
116                log.warn("SCXMLSerializer: DOM serialization pertinent to"
117                    + " the document will be skipped since a suitable"
118                    + " JAXP Transformer could not be instantiated.");
119            }
120            b.append(INDENT).append("<!-- http://commons.apache.org/scxml -->\n");
121            Datamodel dm = scxml.getDatamodel();
122            if (dm != null) {
123                serializeDatamodel(b, dm, INDENT);
124            }
125            Map c = scxml.getChildren();
126            Iterator i = c.keySet().iterator();
127            while (i.hasNext()) {
128                TransitionTarget tt = (TransitionTarget) c.get(i.next());
129                if (tt instanceof State) {
130                    serializeState(b, (State) tt, INDENT);
131                } else {
132                    serializeParallel(b, (Parallel) tt, INDENT);
133                }
134            }
135            b.append("</scxml>\n");
136            return b.toString();
137        }
138    
139        /**
140         * Serialize this State object.
141         *
142         * @param b The buffer to append the serialization to
143         * @param s The State to serialize
144         * @param indent The indent for this XML element
145         */
146        public static void serializeState(final StringBuffer b,
147                final State s, final String indent) {
148            b.append(indent).append("<state");
149            serializeTransitionTargetAttributes(b, s);
150            boolean f = s.isFinal();
151            if (f) {
152                b.append(" final=\"true\"");
153            }
154            b.append(">\n");
155            Initial ini = s.getInitial();
156            if (ini != null) {
157                serializeInitial(b, ini, indent + INDENT);
158            }
159            List h = s.getHistory();
160            if (h != null) {
161                serializeHistory(b, h, indent + INDENT);
162            }
163            Datamodel dm = s.getDatamodel();
164            if (dm != null) {
165                serializeDatamodel(b, dm, indent + INDENT);
166            }
167            serializeOnEntry(b, s, indent + INDENT);
168            List t = s.getTransitionsList();
169            for (int i = 0; i < t.size(); i++) {
170                serializeTransition(b, (Transition) t.get(i), indent + INDENT);
171            }
172            Parallel p = s.getParallel(); //TODO: Remove in v1.0
173            Invoke inv = s.getInvoke();
174            if (p != null) {
175                serializeParallel(b, p, indent + INDENT);
176            } else if (inv != null) {
177                serializeInvoke(b , inv, indent + INDENT);
178            } else {
179                Map c = s.getChildren();
180                Iterator j = c.keySet().iterator();
181                while (j.hasNext()) {
182                    TransitionTarget tt = (TransitionTarget) c.get(j.next());
183                    if (tt instanceof State) {
184                        serializeState(b, (State) tt, indent + INDENT);
185                    } else if (tt instanceof Parallel) {
186                        serializeParallel(b, (Parallel) tt, indent + INDENT);
187                    }
188                }
189            }
190            serializeOnExit(b, s, indent + INDENT);
191            b.append(indent).append("</state>\n");
192        }
193    
194        /**
195         * Serialize this Parallel object.
196         *
197         * @param b The buffer to append the serialization to
198         * @param p The Parallel to serialize
199         * @param indent The indent for this XML element
200         */
201        public static void serializeParallel(final StringBuffer b,
202                final Parallel p, final String indent) {
203            b.append(indent).append("<parallel");
204            serializeTransitionTargetAttributes(b, p);
205            b.append(">\n");
206            serializeOnEntry(b, p, indent + INDENT);
207            Set s = p.getChildren();
208            Iterator i = s.iterator();
209            while (i.hasNext()) {
210                serializeState(b, (State) i.next(), indent + INDENT);
211            }
212            serializeOnExit(b, p, indent + INDENT);
213            b.append(indent).append("</parallel>\n");
214        }
215    
216        /**
217         * Serialize this Invoke object.
218         *
219         * @param b The buffer to append the serialization to
220         * @param i The Invoke to serialize
221         * @param indent The indent for this XML element
222         */
223        public static void serializeInvoke(final StringBuffer b,
224                final Invoke i, final String indent) {
225            b.append(indent).append("<invoke");
226            String type = i.getType();
227            String src = i.getSrc();
228            String srcexpr = i.getSrcexpr();
229            if (type != null) {
230                b.append(" type=\"").append(type).append("\"");
231            }
232            // Prefer src
233            if (src != null) {
234                b.append(" src=\"").append(src).append("\"");
235            } else if (srcexpr != null) {
236                b.append(" srcexpr=\"").append(srcexpr).append("\"");
237            }
238            b.append(">\n");
239            List params = i.params();
240            for (Iterator iter = params.iterator(); iter.hasNext();) {
241                Param p = (Param) iter.next();
242                b.append(indent).append(INDENT).append("<param name=\"").
243                    append(p.getName()).append("\" expr=\"").
244                    append(SCXMLHelper.escapeXML(p.getExpr())).append("\"/>\n");
245            }
246            Finalize f = i.getFinalize();
247            if (f != null) {
248                b.append(indent).append(INDENT).append("<finalize>\n");
249                serializeActions(b, f.getActions(), indent + INDENT + INDENT);
250                b.append(indent).append(INDENT).append("</finalize>\n");
251            }
252            b.append(indent).append("</invoke>\n");
253        }
254    
255        /**
256         * Serialize this Initial object.
257         *
258         * @param b The buffer to append the serialization to
259         * @param i The Initial to serialize
260         * @param indent The indent for this XML element
261         */
262        public static void serializeInitial(final StringBuffer b, final Initial i,
263                final String indent) {
264            b.append(indent).append("<initial");
265            serializeTransitionTargetAttributes(b, i);
266            b.append(">\n");
267            serializeTransition(b, i.getTransition(), indent + INDENT);
268            b.append(indent).append("</initial>\n");
269        }
270    
271        /**
272         * Serialize the History.
273         *
274         * @param b The buffer to append the serialization to
275         * @param l The List of History objects to serialize
276         * @param indent The indent for this XML element
277         */
278        public static void serializeHistory(final StringBuffer b, final List l,
279                final String indent) {
280            if (l.size() > 0) {
281                for (int i = 0; i < l.size(); i++) {
282                    History h = (History) l.get(i);
283                    b.append(indent).append("<history");
284                    serializeTransitionTargetAttributes(b, h);
285                     if (h.isDeep()) {
286                         b.append(" type=\"deep\"");
287                     } else {
288                         b.append(" type=\"shallow\"");
289                     }
290                    b.append(">\n");
291                    serializeTransition(b, h.getTransition(), indent + INDENT);
292                    b.append(indent).append("</history>\n");
293                }
294            }
295        }
296    
297        /**
298         * Serialize this Transition object.
299         *
300         * @param b The buffer to append the serialization to
301         * @param t The Transition to serialize
302         * @param indent The indent for this XML element
303         */
304        public static void serializeTransition(final StringBuffer b,
305                final Transition t, final String indent) {
306            b.append(indent).append("<transition");
307            if (!SCXMLHelper.isStringEmpty(t.getEvent())) {
308                b.append(" event=\"").append(t.getEvent()).append("\"");
309            }
310            if (!SCXMLHelper.isStringEmpty(t.getCond())) {
311                b.append(" cond=\"").append(SCXMLHelper.escapeXML(t.getCond())).
312                    append("\"");
313            }
314            boolean next = !SCXMLHelper.isStringEmpty(t.getNext());
315            if (next) {
316                b.append(" target=\"" + t.getNext() + "\"");
317            }
318            b.append(">\n");
319            boolean exit = serializeActions(b, t.getActions(), indent + INDENT);
320            if (!next && !exit) {
321                serializeTarget(b, t, indent + INDENT);
322            }
323            b.append(indent).append("</transition>\n");
324        }
325    
326        /**
327         * Serialize this Transition's Target.
328         *
329         *
330         * @param b The buffer to append the serialization to
331         * @param t The Transition whose Target needs to be serialized
332         * @param indent The indent for this XML element
333         *
334         * @deprecated Inline &lt;target&gt; element has been deprecated
335         *             in the SCXML WD
336         */
337        public static void serializeTarget(final StringBuffer b,
338                final Transition t, final String indent) {
339            if (t.getTarget() != null) {
340                b.append(indent).append("<target>");
341                // The inline transition target can only be a state
342                serializeState(b, (State) t.getTarget(), indent + INDENT);
343                b.append(indent).append("</target>");
344            }
345        }
346    
347        /**
348         * Serialize this Datamodel object.
349         *
350         * @param b The buffer to append the serialization to
351         * @param dm The Datamodel to be serialized
352         * @param indent The indent for this XML element
353         */
354        public static void serializeDatamodel(final StringBuffer b,
355                final Datamodel dm, final String indent) {
356            List data = dm.getData();
357            if (data != null && data.size() > 0) {
358                b.append(indent).append("<datamodel>\n");
359                if (XFORMER == null) {
360                    b.append(indent).append(INDENT).
361                        append("<!-- Body content was not serialized -->\n");
362                    b.append(indent).append("</datamodel>\n");
363                    return;
364                }
365                for (Iterator iter = data.iterator(); iter.hasNext();) {
366                    Data datum = (Data) iter.next();
367                    Node dataNode = datum.getNode();
368                    if (dataNode != null) {
369                        StringWriter out = new StringWriter();
370                        try {
371                            Source input = new DOMSource(dataNode);
372                            Result output = new StreamResult(out);
373                            XFORMER.transform(input, output);
374                        } catch (TransformerException te) {
375                            org.apache.commons.logging.Log log = LogFactory.
376                                getLog(SCXMLSerializer.class);
377                            log.error(te.getMessage(), te);
378                            b.append(indent).append(INDENT).
379                                append("<!-- Data content not serialized -->\n");
380                        }
381                        b.append(indent).append(INDENT).append(out.toString());
382                    } else {
383                        b.append(indent).append(INDENT).append("<data id=\"").
384                            append(datum.getId()).append("\" expr=\"").
385                            append(SCXMLHelper.escapeXML(datum.getExpr())).
386                            append("\" />\n");
387                    }
388                }
389                b.append(indent).append("</datamodel>\n");
390            }
391        }
392    
393        /**
394         * Serialize this OnEntry object.
395         *
396         * @param b The buffer to append the serialization to
397         * @param t The TransitionTarget whose OnEntry is to be serialized
398         * @param indent The indent for this XML element
399         */
400        public static void serializeOnEntry(final StringBuffer b,
401                final TransitionTarget t, final String indent) {
402            OnEntry e = t.getOnEntry();
403            if (e != null && e.getActions().size() > 0) {
404                b.append(indent).append("<onentry>\n");
405                serializeActions(b, e.getActions(), indent + INDENT);
406                b.append(indent).append("</onentry>\n");
407            }
408        }
409    
410        /**
411         * Serialize this OnExit object.
412         *
413         * @param b The buffer to append the serialization to
414         * @param t The TransitionTarget whose OnExit is to be serialized
415         * @param indent The indent for this XML element
416         */
417        public static void serializeOnExit(final StringBuffer b,
418                final TransitionTarget t, final String indent) {
419            OnExit x = t.getOnExit();
420            if (x != null && x.getActions().size() > 0) {
421                b.append(indent).append("<onexit>\n");
422                serializeActions(b, x.getActions(), indent + INDENT);
423                b.append(indent).append("</onexit>\n");
424            }
425        }
426    
427        /**
428         * Serialize this List of actions.
429         *
430         * @param b The buffer to append the serialization to
431         * @param l The List of actions to serialize
432         * @param indent The indent for this XML element
433         * @return boolean true if the list of actions contains an &lt;exit/&gt;
434         */
435        public static boolean serializeActions(final StringBuffer b, final List l,
436                final String indent) {
437            if (l == null) {
438                return false;
439            }
440            boolean exit = false;
441            Iterator i = l.iterator();
442            while (i.hasNext()) {
443                Action a = (Action) i.next();
444                if (a instanceof Var) {
445                    Var v = (Var) a;
446                    b.append(indent).append("<cs:var name=\"").append(v.getName())
447                        .append("\" expr=\"")
448                        .append(SCXMLHelper.escapeXML(v.getExpr()))
449                        .append("\"/>\n");
450                } else if (a instanceof Assign) {
451                    Assign asn = (Assign) a;
452                    b.append(indent).append("<assign");
453                    if (!SCXMLHelper.isStringEmpty(asn.getLocation())) {
454                        b.append(" location=\"").append(asn.getLocation());
455                        if (!SCXMLHelper.isStringEmpty(asn.getSrc())) {
456                            b.append("\" src=\"").append(asn.getSrc());
457                        } else {
458                            b.append("\" expr=\"").
459                                append(SCXMLHelper.escapeXML(asn.getExpr()));
460                        }
461                    } else {
462                        b.append(" name=\"").append(asn.getName()).
463                            append("\" expr=\"").
464                            append(SCXMLHelper.escapeXML(asn.getExpr()));
465                    }
466                    b.append("\"/>\n");
467                } else if (a instanceof Send) {
468                    serializeSend(b, (Send) a, indent);
469                } else if (a instanceof Cancel) {
470                    Cancel c = (Cancel) a;
471                    b.append(indent).append("<cancel sendid=\"")
472                        .append(c.getSendid()).append("\"/>\n");
473                } else if (a instanceof Log) {
474                    Log lg = (Log) a;
475                    b.append(indent).append("<log expr=\"").
476                        append(SCXMLHelper.escapeXML(lg.getExpr())).
477                        append("\"/>\n");
478                } else if (a instanceof Exit) {
479                    Exit e = (Exit) a;
480                    b.append(indent).append("<cs:exit");
481                    String expr = SCXMLHelper.escapeXML(e.getExpr());
482                    String nl = e.getNamelist();
483                    if (expr != null) {
484                        b.append(" expr=\"" + expr + "\"");
485                    }
486                    if (nl != null) {
487                        b.append(" namelist=\"" + nl + "\"");
488                    }
489                    b.append("/>\n");
490                    exit = true;
491                } else if (a instanceof If) {
492                    If iff = (If) a;
493                    serializeIf(b, iff, indent);
494                } else if (a instanceof Else) {
495                    b.append(indent).append("<else/>\n");
496                } else if (a instanceof ElseIf) {
497                    ElseIf eif = (ElseIf) a;
498                    b.append(indent).append("<elseif cond=\"")
499                        .append(SCXMLHelper.escapeXML(eif.getCond()))
500                        .append("\" />\n");
501                }
502            }
503            return exit;
504        }
505    
506        /**
507         * Serialize this Send object.
508         *
509         * @param b The buffer to append the serialization to
510         * @param send The Send object to serialize
511         * @param indent The indent for this XML element
512         */
513        public static void serializeSend(final StringBuffer b,
514                final Send send, final String indent) {
515            b.append(indent).append("<send");
516            if (send.getSendid() != null) {
517                b.append(" sendid=\"").append(send.getSendid()).append("\"");
518            }
519            if (send.getTarget() != null) {
520                b.append(" target=\"").append(send.getTarget()).append("\"");
521            }
522            if (send.getType() != null) {
523                b.append(" type=\"").append(send.getType()).append("\"");
524            }
525            if (send.getNamelist() != null) {
526                b.append(" namelist=\"").append(send.getNamelist()).append("\"");
527            }
528            if (send.getDelay() != null) {
529                b.append(" delay=\"").append(send.getDelay()).append("\"");
530            }
531            if (send.getEvent() != null) {
532                b.append(" event=\"").append(send.getEvent()).append("\"");
533            }
534            if (send.getHints() != null) {
535                b.append(" hints=\"").append(send.getHints()).append("\"");
536            }
537            b.append(">\n");
538            b.append(getBodyContent(send));
539            b.append(indent).append("</send>\n");
540        }
541    
542        /**
543         * Return serialized body of <code>ExternalContent</code>.
544         *
545         * @param externalContent The model element containing the body content
546         * @return String The serialized body content
547         */
548        public static final String getBodyContent(
549                final ExternalContent externalContent) {
550            StringBuffer buf = new StringBuffer();
551            List externalNodes = externalContent.getExternalNodes();
552            if (externalNodes.size() > 0 && XFORMER == null) {
553                buf.append("<!-- Body content was not serialized -->\n");
554                return buf.toString();
555            }
556            for (int i = 0; i < externalNodes.size(); i++) {
557                Source input = new DOMSource((Node) externalNodes.get(i));
558                StringWriter out = new StringWriter();
559                Result output = new StreamResult(out);
560                try {
561                    XFORMER.transform(input, output);
562                } catch (TransformerException te) {
563                    org.apache.commons.logging.Log log = LogFactory.
564                        getLog(SCXMLSerializer.class);
565                    log.error(te.getMessage(), te);
566                    buf.append("<!-- Not all body content was serialized -->");
567                }
568                buf.append(out.toString()).append("\n");
569            }
570            return buf.toString();
571        }
572    
573        /**
574         * Serialize this If object.
575         *
576         * @param b The buffer to append the serialization to
577         * @param iff The If object to serialize
578         * @param indent The indent for this XML element
579         */
580        public static void serializeIf(final StringBuffer b,
581                final If iff, final String indent) {
582            b.append(indent).append("<if cond=\"").append(SCXMLHelper.
583                escapeXML(iff.getCond())).append("\">\n");
584            serializeActions(b, iff.getActions(), indent + INDENT);
585            b.append(indent).append("</if>\n");
586        }
587    
588        /**
589         * Serialize properties of TransitionTarget which are element attributes.
590         *
591         * @param b The buffer to append the serialization to
592         * @param t The TransitionTarget
593         */
594        private static void serializeTransitionTargetAttributes(
595                final StringBuffer b, final TransitionTarget t) {
596            String id = t.getId();
597            if (id != null) {
598                b.append(" id=\"").append(id).append("\"");
599            }
600        }
601    
602        /**
603         * Serialize namespace declarations for the root SCXML element.
604         *
605         * @param holder The {@link NamespacePrefixesHolder} object
606         * @return The serialized namespace declarations
607         */
608        private static String serializeNamespaceDeclarations(
609                final NamespacePrefixesHolder holder) {
610            Map ns = holder.getNamespaces();
611            StringBuffer b = new StringBuffer();
612            if (ns != null) {
613                Iterator iter = ns.entrySet().iterator();
614                while (iter.hasNext()) {
615                    Map.Entry entry = (Map.Entry) iter.next();
616                    String prefix = (String) entry.getKey();
617                    String nsURI = (String) entry.getValue();
618                    if (prefix.length() == 0 && !nsURI.equals(NAMESPACE_SCXML)) {
619                        org.apache.commons.logging.Log log = LogFactory.
620                            getLog(SCXMLSerializer.class);
621                        log.warn("When using the SCXMLSerializer, the default "
622                            + "namespace must be the SCXML namespace:"
623                            + NAMESPACE_SCXML);
624                    } if (prefix.equals("cs")
625                            && !nsURI.equals(NAMESPACE_COMMONS_SCXML)) {
626                        org.apache.commons.logging.Log log = LogFactory.
627                            getLog(SCXMLSerializer.class);
628                        log.warn("When using the SCXMLSerializer, the namespace"
629                            + "prefix \"cs\" must bind to the Commons SCXML "
630                            + "namespace:" + NAMESPACE_COMMONS_SCXML);
631                    } else if (prefix.length() > 0) {
632                        b.append(" xmlns:").append(prefix).append("=\"").
633                            append(nsURI).append("\"");
634                    }
635                }
636            }
637            return b.toString();
638        }
639    
640        /**
641         * Get a <code>Transformer</code> instance.
642         *
643         * @return Transformer The <code>Transformer</code> instance.
644         */
645        private static Transformer getTransformer() {
646            Transformer transformer = null;
647            Properties outputProps = new Properties();
648            outputProps.put(OutputKeys.OMIT_XML_DECLARATION, "yes");
649            outputProps.put(OutputKeys.STANDALONE, "no");
650            outputProps.put(OutputKeys.INDENT, "yes");
651            try {
652                TransformerFactory tfFactory = TransformerFactory.newInstance();
653                transformer = tfFactory.newTransformer();
654                transformer.setOutputProperties(outputProps);
655            } catch (TransformerFactoryConfigurationError t) {
656                return null;
657            } catch (TransformerConfigurationException e) {
658                return null;
659            }
660            return transformer;
661        }
662    
663        /*
664         * Private methods.
665         */
666        /**
667         * Discourage instantiation since this is a utility class.
668         */
669        private SCXMLSerializer() {
670            super();
671        }
672    
673    }