From b1f779d7762d3949d7ceac440ee3e3df28a053d2 Mon Sep 17 00:00:00 2001 From: Rolf Date: Sun, 1 Jan 2012 19:01:30 -0500 Subject: [PATCH] Fixes #54 - Outputers and Format Completes the code for SAXOutputter and Format, thus completing the issue. Implements tests, but some tests are ignored because of two issues: 1. Cannot affect format of content outside of root element using Format 2. Cannot process DOCTYPE declarations properly. --- .../java/org/jdom2/input/sax/SAXHandler.java | 2 +- .../output/AbstractSAXOutputProcessor.java | 7 +- .../java/org/jdom2/output/SAXOutputter.java | 4 +- .../test/cases/input/TestSAXBuilder.java | 3 +- .../cases/output/AbstractTestOutputter.java | 91 +++- .../test/cases/output/TestSAXOutputter.java | 469 +++++++++++++++++- 6 files changed, 535 insertions(+), 41 deletions(-) diff --git a/core/src/java/org/jdom2/input/sax/SAXHandler.java b/core/src/java/org/jdom2/input/sax/SAXHandler.java index c9e18dae5..b17a7a338 100644 --- a/core/src/java/org/jdom2/input/sax/SAXHandler.java +++ b/core/src/java/org/jdom2/input/sax/SAXHandler.java @@ -722,7 +722,7 @@ private void transferNamespaces(Element element) { public void characters(char[] ch, int start, int length) throws SAXException { - if (suppress || (length == 0)) + if (suppress || (length == 0 && !inCDATA)) return; if (previousCDATA != inCDATA) { diff --git a/core/src/java/org/jdom2/output/AbstractSAXOutputProcessor.java b/core/src/java/org/jdom2/output/AbstractSAXOutputProcessor.java index 68289ebd9..37fbea403 100644 --- a/core/src/java/org/jdom2/output/AbstractSAXOutputProcessor.java +++ b/core/src/java/org/jdom2/output/AbstractSAXOutputProcessor.java @@ -295,8 +295,11 @@ protected void printDocument(final SAXTarget out, final FormatStack fstack, // Handle root element, as well as any root level // processing instructions and comments // ignore DocType, if any. - for (Content obj : document.getContent()) { + + int sz = document.getContentSize(); + for (int c = 0; c < sz; c++) { + Content obj = document.getContent(c); // update locator out.getLocator().setNode(obj); @@ -762,7 +765,7 @@ protected void printContent(final SAXTarget out, final FormatStack fstack, // 'trimming' variants, if it is all whitespace we do // not want to even print the indent/newline. if (!isAllWhiteSpace(content, txti, index - txti)) { - printIndent(null, fstack); + printIndent(out, fstack); helperTextType(out, fstack, content, txti, index - txti); printEOL(out, fstack); } diff --git a/core/src/java/org/jdom2/output/SAXOutputter.java b/core/src/java/org/jdom2/output/SAXOutputter.java index 3d41ce55d..c0e109d7c 100644 --- a/core/src/java/org/jdom2/output/SAXOutputter.java +++ b/core/src/java/org/jdom2/output/SAXOutputter.java @@ -600,7 +600,7 @@ public SAXOutputProcessor getSAXOutputProcessor() { * the new SAXOutputProcessor */ public void setSAXOutputProcessor(SAXOutputProcessor processor) { - this.processor = processor; + this.processor = processor == null ? DEFAULT_PROCESSOR : processor; } /** @@ -619,7 +619,7 @@ public Format getFormat() { * the new Format */ public void setFormat(Format format) { - this.format = format; + this.format = format == null ? Format.getRawFormat() : format; } private final SAXTarget buildTarget(Document doc) { diff --git a/test/src/java/org/jdom2/test/cases/input/TestSAXBuilder.java b/test/src/java/org/jdom2/test/cases/input/TestSAXBuilder.java index 839537b43..1a6b4ae48 100644 --- a/test/src/java/org/jdom2/test/cases/input/TestSAXBuilder.java +++ b/test/src/java/org/jdom2/test/cases/input/TestSAXBuilder.java @@ -97,6 +97,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT import org.jdom2.Content; import org.jdom2.DefaultJDOMFactory; import org.jdom2.Document; +import org.jdom2.Element; import org.jdom2.EntityRef; import org.jdom2.JDOMException; import org.jdom2.JDOMFactory; @@ -836,7 +837,7 @@ private void assertXMLMatches(String baseuri, Document doc) { } } } - + @Test public void testBuildInputSource() { try { diff --git a/test/src/java/org/jdom2/test/cases/output/AbstractTestOutputter.java b/test/src/java/org/jdom2/test/cases/output/AbstractTestOutputter.java index 20c528213..658c89f7a 100644 --- a/test/src/java/org/jdom2/test/cases/output/AbstractTestOutputter.java +++ b/test/src/java/org/jdom2/test/cases/output/AbstractTestOutputter.java @@ -20,6 +20,7 @@ import org.jdom2.Parent; import org.jdom2.ProcessingInstruction; import org.jdom2.Text; +import org.jdom2.Verifier; import org.jdom2.output.Format; import org.jdom2.output.Format.TextMode; @@ -35,14 +36,14 @@ protected static interface FormatSetup { private final boolean pademptyelement; private final boolean forceexpand; private final boolean padpi; - private final boolean forceplatformeol; + //private final boolean rawoutsideroot; - public AbstractTestOutputter(boolean cr2xD, boolean padpreempty, boolean padpi, boolean forceexpand, boolean forceplatformeol) { + public AbstractTestOutputter(boolean cr2xD, boolean padpreempty, boolean padpi, boolean forceexpand, boolean rawoutsideroot) { this.cr2xD = cr2xD; this.pademptyelement = padpreempty; this.forceexpand = forceexpand; this.padpi = padpi; - this.forceplatformeol = forceplatformeol; + //this.rawoutsideroot = rawoutsideroot; } protected final String expect(String expect) { @@ -66,9 +67,69 @@ protected final String expect(String expect) { expect = expect.replaceAll("<(\\w+)(\\s+.+?)?\\s+/>", "<$1$2/>"); expect = expect.replaceAll("<(\\w+:\\w+)(\\s+.+?)?\\s+/>", "<$1$2/>"); } - if (forceplatformeol) { - //expect = expect.replaceAll("\n", System.getProperty("line.separator")); - } +// if (rawoutsideroot) { +// // outside the root element will be raw-formatted. +// StringBuilder sb = new StringBuilder(expect.length()); +// int gotstuff = 0; +// boolean indoctype = false; +// boolean gotroot = false; +// int depth = 0; +// char[] chars = expect.toCharArray(); +// int i = 0; +// while (i < chars.length && Verifier.isXMLWhitespace(chars[i])) { +// // skip initial whitespace. +// i++; +// } +// for (; i < chars.length; i++) { +// char c = chars[i]; +// sb.append(c); +// if (!gotroot) { +// if (c == '<') { +// if (depth == 0) { +// if (i < chars.length - 2) { +// if (chars[i + 1] == '?') { +// // PI or XML Declaration +// gotstuff++; +// } else if (chars[i + 1] == '!') { +// // Comment of DOCTYPE +// gotstuff++; +// if (chars[i + 2] == 'D') { +// // DOCTYPE +// indoctype = true; +// } +// } else { +// // root element +// gotroot = true; +// } +// } else { +// gotroot = true; +// } +// } +// depth++; +// } else if (c == '>') { +// depth--; +// if (depth == 0) { +// if (indoctype) { +// sb.append('\n'); +// indoctype = false; +// } +// while (i+1 < chars.length && Verifier.isXMLWhitespace(chars[i + 1])) { +// // skip whitespace after top-level content. +// i++; +// } +// } +// } +// } +// } +// while (Verifier.isXMLWhitespace(sb.charAt(sb.length() - 1))) { +// // eliminate trailing whitespace. +// sb.setLength(sb.length() - 1); +// } +// if (gotstuff > 1 || (gotroot && gotstuff > 0)) { +// // there is multiple content stuff, need to trim the whitespace.... +// expect = sb.toString(); +// } +// } return expect; } @@ -380,14 +441,14 @@ public void testDocTypeSimple() { @Test public void testDocTypeSimpleISS() { DocType content = new DocType("root"); - content.setInternalSubset("internal"); - assertEquals("", + content.setInternalSubset(""); + assertEquals("]>", outputString(fraw, content)); - assertEquals("", + assertEquals("]>", outputString(fcompact, content)); - assertEquals("", + assertEquals("]>", outputString(fpretty, content)); - assertEquals("", + assertEquals("]>", outputString(ftfw, content)); } @@ -986,6 +1047,10 @@ public void testOutputDocumentFull() { @Test public void testDeepNesting() { + testDeepNestingCore(true); + } + + protected final void testDeepNestingCore(boolean startnewline) { // need to get beyond 16 levels of XML. DocType dt = new DocType("root"); Element root = new Element("root"); @@ -1000,7 +1065,9 @@ public void testDeepNesting() { StringBuilder raw = new StringBuilder(base); StringBuilder pretty = new StringBuilder(base); raw.append(""); - pretty.append(lf); + if (startnewline) { + pretty.append(lf); + } pretty.append(""); pretty.append(lf); final int depth = 40; diff --git a/test/src/java/org/jdom2/test/cases/output/TestSAXOutputter.java b/test/src/java/org/jdom2/test/cases/output/TestSAXOutputter.java index b0b63dea7..6bc3748a8 100644 --- a/test/src/java/org/jdom2/test/cases/output/TestSAXOutputter.java +++ b/test/src/java/org/jdom2/test/cases/output/TestSAXOutputter.java @@ -1,12 +1,35 @@ package org.jdom2.test.cases.output; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import org.junit.Ignore; +import org.junit.Test; +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.DTDHandler; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; +import org.xml.sax.SAXParseException; +import org.xml.sax.ext.DeclHandler; +import org.xml.sax.ext.DefaultHandler2; +import org.xml.sax.ext.LexicalHandler; +import org.xml.sax.helpers.AttributesImpl; +import org.xml.sax.helpers.LocatorImpl; + import org.jdom2.Attribute; import org.jdom2.AttributeType; import org.jdom2.CDATA; @@ -21,37 +44,37 @@ import org.jdom2.ProcessingInstruction; import org.jdom2.Text; import org.jdom2.input.sax.SAXHandler; +import org.jdom2.output.AbstractSAXOutputProcessor; import org.jdom2.output.Format; import org.jdom2.output.JDOMLocator; +import org.jdom2.output.LineSeparator; +import org.jdom2.output.SAXOutputProcessor; import org.jdom2.output.SAXOutputter; import org.jdom2.output.XMLOutputter; import org.jdom2.test.util.UnitTestUtil; -import org.junit.Test; -import org.xml.sax.Attributes; -import org.xml.sax.ContentHandler; -import org.xml.sax.DTDHandler; -import org.xml.sax.EntityResolver; -import org.xml.sax.ErrorHandler; -import org.xml.sax.Locator; -import org.xml.sax.SAXException; -import org.xml.sax.SAXNotRecognizedException; -import org.xml.sax.SAXNotSupportedException; -import org.xml.sax.SAXParseException; -import org.xml.sax.ext.DeclHandler; -import org.xml.sax.ext.DefaultHandler2; -import org.xml.sax.ext.LexicalHandler; -import org.xml.sax.helpers.AttributesImpl; -import org.xml.sax.helpers.LocatorImpl; @SuppressWarnings("javadoc") -public class TestSAXOutputter { +public class TestSAXOutputter extends AbstractTestOutputter { private interface SAXSetup { public SAXOutputter buildOutputter(SAXHandler handler); } + + + + /** + * @param cr2xD + * @param padpreempty + * @param padpi + * @param forceexpand + * @param forceplatformeol + */ + public TestSAXOutputter() { + super(true, true, false, false, true); + } - private void roundTrip(Document doc) { + private void roundTrip(Document doc) { roundTrip(null, doc); } @@ -552,13 +575,13 @@ public void testProperty() throws SAXNotRecognizedException, SAXNotSupportedExce } @Test - public void testOutputDocumentSimple() { + public void testSAXOutputDocumentSimple() { Document doc = new Document(new Element("root")); roundTrip(doc); } @Test - public void testOutputDocumentFull() { + public void testSAXOutputDocumentFull() { Document doc = new Document(); doc.addContent(new DocType("root")); doc.addContent(new Comment("This is a document")); @@ -658,7 +681,7 @@ public void testOutputDocumentNamespaces() { } @Test - public void testOutputList() { + public void testSAXOutputList() { List list = new ArrayList(); list.add(new ProcessingInstruction("jdomtest", "")); list.add(new Comment("comment")); @@ -675,7 +698,7 @@ public void testOutputElementAttributes() { } @Test - public void testOutputElementNamespaces() { + public void testSAXOutputElementNamespaces() { Element emt = new Element("root", Namespace.getNamespace("ns", "myns")); Namespace ans = Namespace.getNamespace("ans", "attributens"); emt.addNamespaceDeclaration(ans); @@ -764,5 +787,405 @@ public void endElement(String uri, String localname, String qname) { saxout.output(doc); assertTrue(coderan.get()); } + + + + + @Test + public void testGetSetFormat() { + SAXOutputter sout = new SAXOutputter(); + Format def = sout.getFormat(); + assertTrue(def != null); + Format f = Format.getPrettyFormat(); + sout.setFormat(f); + assertTrue(f == sout.getFormat()); + sout.setFormat(null); + TestFormat.checkEquals(def, sout.getFormat()); + } + + @Test + public void testGetSetDOMOutputProcessor() { + SAXOutputProcessor dop = new AbstractSAXOutputProcessor() { + // nothing. + }; + + SAXOutputter dout = new SAXOutputter(); + SAXOutputProcessor def = dout.getSAXOutputProcessor(); + assertTrue(def != null); + dout.setSAXOutputProcessor(dop); + assertTrue(dop == dout.getSAXOutputProcessor()); + dout.setSAXOutputProcessor(null); + assertEquals(def, dout.getSAXOutputProcessor()); + } + + @Override + public String outputString(Format format, Document doc) { + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = new SAXOutputter(null, format, handler, handler, + handler, handler, handler); + try { + saxout.output(doc); + } catch (Exception e) { + throw new IllegalStateException("Could not output", e); + } + XMLOutputter xout = new XMLOutputter(); + xout.getFormat().setLineSeparator(LineSeparator.NL); + return xout.outputString(handler.getDocument()); + } + + @Override + public String outputString(Format format, DocType doctype) { + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = new SAXOutputter(null, format, handler, handler, + handler, handler, handler); + List list = new ArrayList(1); + list.add(doctype); + //list.add(new Element("root")); + + try { + saxout.output(list); + } catch (Exception e) { + throw new IllegalStateException("Could not output", e); + } + XMLOutputter xout = new XMLOutputter(); + xout.getFormat().setLineSeparator(LineSeparator.NL); + return xout.outputString(handler.getDocument().getDocType()); + } + + @Override + public String outputString(Format format, Element element) { + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = new SAXOutputter(null, format, handler, handler, + handler, handler, handler); + + try { + saxout.output(element); + } catch (Exception e) { + throw new IllegalStateException("Could not output", e); + } + XMLOutputter xout = new XMLOutputter(); + xout.getFormat().setLineSeparator(LineSeparator.NL); + return xout.outputString(handler.getDocument().getRootElement()); + } + + @Override + public String outputString(Format format, List list) { + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = new SAXOutputter(null, format, handler, handler, + handler, handler, handler); + + try { + handler.startDocument(); + handler.startElement("", "root", "root", new AttributesImpl()); + saxout.outputFragment(list); + handler.endElement("", "root", "root"); + handler.endDocument(); + } catch (Exception e) { + throw new IllegalStateException("Could not output", e); + } + XMLOutputter xout = new XMLOutputter(); + xout.getFormat().setLineSeparator(LineSeparator.NL); + return xout.outputString(handler.getDocument().getRootElement().getContent()); + } + + @Override + public String outputString(Format format, CDATA cdata) { + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = new SAXOutputter(null, format, handler, handler, + handler, handler, handler); + + try { + handler.startDocument(); + handler.startElement("", "root", "root", new AttributesImpl()); + saxout.outputFragment(cdata); + handler.endElement("", "root", "root"); + handler.endDocument(); + } catch (Exception e) { + throw new IllegalStateException("Could not output", e); + } + XMLOutputter xout = new XMLOutputter(); + xout.getFormat().setLineSeparator(LineSeparator.NL); + return xout.outputString(handler.getDocument().getRootElement().getContent()); + } + @Override + public String outputString(Format format, Text text) { + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = new SAXOutputter(null, format, handler, handler, + handler, handler, handler); + + try { + handler.startDocument(); + handler.startElement("", "root", "root", new AttributesImpl()); + saxout.outputFragment(text); + handler.endElement("", "root", "root"); + handler.endDocument(); + } catch (Exception e) { + throw new IllegalStateException("Could not output", e); + } + XMLOutputter xout = new XMLOutputter(); + xout.getFormat().setLineSeparator(LineSeparator.NL); + return xout.outputString(handler.getDocument().getRootElement().getContent()); + } + + @Override + public String outputString(Format format, Comment comment) { + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = new SAXOutputter(null, format, handler, handler, + handler, handler, handler); + + try { + handler.startDocument(); + handler.startElement("", "root", "root", new AttributesImpl()); + saxout.outputFragment(comment); + handler.endElement("", "root", "root"); + handler.endDocument(); + } catch (Exception e) { + throw new IllegalStateException("Could not output", e); + } + XMLOutputter xout = new XMLOutputter(); + xout.getFormat().setLineSeparator(LineSeparator.NL); + return xout.outputString(handler.getDocument().getRootElement().getContent()); + } + + @Override + public String outputString(Format format, ProcessingInstruction pi) { + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = new SAXOutputter(null, format, handler, handler, + handler, handler, handler); + + try { + handler.startDocument(); + handler.startElement("", "root", "root", new AttributesImpl()); + saxout.outputFragment(pi); + handler.endElement("", "root", "root"); + handler.endDocument(); + } catch (Exception e) { + throw new IllegalStateException("Could not output", e); + } + XMLOutputter xout = new XMLOutputter(); + xout.getFormat().setLineSeparator(LineSeparator.NL); + return xout.outputString(handler.getDocument().getRootElement().getContent()); + } + + @Override + public String outputString(Format format, EntityRef entity) { + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = new SAXOutputter(null, format, handler, handler, + handler, handler, handler); + + try { + handler.startDocument(); + handler.startElement("", "root", "root", new AttributesImpl()); + saxout.outputFragment(entity); + handler.endElement("", "root", "root"); + handler.endDocument(); + } catch (Exception e) { + throw new IllegalStateException("Could not output", e); + } + XMLOutputter xout = new XMLOutputter(); + xout.getFormat().setLineSeparator(LineSeparator.NL); + return xout.outputString(handler.getDocument().getRootElement().getContent()); + } + + @Override + public String outputElementContentString(Format format, Element element) { + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = new SAXOutputter(null, format, handler, handler, + handler, handler, handler); + + try { + saxout.output(element); + } catch (Exception e) { + throw new IllegalStateException("Could not output", e); + } + XMLOutputter xout = new XMLOutputter(); + xout.getFormat().setLineSeparator(LineSeparator.NL); + return xout.outputString(handler.getDocument().getRootElement()); + } + + + + + @Test + @Override + @Ignore + public void testOutputElementExpandEmpty() { + // Can't control expand in SAXOutputter. + } + + @Test + @Override + @Ignore + public void testOutputElementMultiAllWhiteExpandEmpty() { + // Can't control expand in SAXOutputter. + } + + @Test + @Override + @Ignore + public void testCDATAEmpty() { + // can't test empty CDATA in SAXHandler. + } + + + @Test + @Ignore + @Override + public void testDocTypeSimpleISS() { + //Cannot preserve internal subset in DOCTYPE through the round-trip test. + } + + @Test + @Ignore + @Override + public void testDocTypePublicSystemID() { + //Cannot test with a SystemID because it needs to be resolved/referenced + } + + @Test + @Ignore + @Override + public void testDocTypePublicSystemIDISS() { + //Cannot test with a SystemID because it needs to be resolved/referenced + //Cannot preserve internal subset in DOCTYPE through the round-trip test. + } + + @Test + @Ignore + @Override + public void testDocTypeSystemID() { + //Cannot test with a SystemID because it needs to be resolved/referenced + } + + @Test + @Ignore + @Override + public void testDocTypeSystemIDISS() { + //Cannot preserve internal subset in DOCTYPE through the round-trip test. + } + + @Test + @Override + @Ignore + public void testDocumentDocType() { + // override because we can't control whitespace outside of the root element + } + + + @Test + @Override + @Ignore + public void testOutputDocTypeInternalSubset() { + //Cannot preserve internal subset in DOCTYPE through the round-trip test. + } + + + @Test + @Override + @Ignore + public void testOutputDocTypeSystem() { + //Cannot test with a SystemID because it needs to be resolved/referenced + } + + @Test + @Override + @Ignore + public void testOutputDocTypePublic() { + // cannot test with a non-resolved publicID + } + + @Test + @Override + @Ignore + public void testOutputDocTypePublicSystem() { + //Cannot test with a SystemID because it needs to be resolved/referenced + } + + + @Test + @Override + @Ignore + public void testOutputDocumentOmitEncoding() { + // Cannot test for formatting outside of root element + } + + @Test + @Override + @Ignore + public void testOutputDocumentOmitDeclaration() { + // Cannot test for formatting outside of root element + } + + @Test + @Override + @Ignore + public void testOutputDocumentFull() { + // need to change formatting of whitespace outside root element + DocType dt = new DocType("root"); + Comment comment = new Comment("comment"); + ProcessingInstruction pi = new ProcessingInstruction("jdomtest", ""); + Element root = new Element("root"); + Document doc = new Document(); + doc.addContent(dt); + doc.addContent(comment); + doc.addContent(pi); + doc.addContent(root); + String xmldec = ""; + String dtdec = ""; + String commentdec = ""; + String pidec = ""; + String rtdec = ""; + String lf = "\n"; + checkOutput(doc, + xmldec + lf + dtdec + lf + commentdec + pidec + rtdec + lf, + xmldec + lf + dtdec + lf + commentdec + pidec + rtdec + lf, + xmldec + lf + dtdec + lf + commentdec + pidec + rtdec + lf, + xmldec + lf + dtdec + lf + commentdec + pidec + rtdec + lf); + } + + + @Test + @Override + public void testDocumentSimple() { + // change expected whitespace outside root element + Document content = new Document(); + assertEquals("\n\n", + outputString(fraw, content)); + assertEquals("\n\n", + outputString(fcompact, content)); + assertEquals("\n\n", + outputString(fpretty, content)); + assertEquals("\n\n", + outputString(ftfw, content)); + } + + @Test + @Override + public void testDocumentComment() { + // change expected whitespace outside root element + Document content = new Document(); + content.addContent(new Comment("comment")); + assertEquals("\n\n", + outputString(fraw, content)); + assertEquals("\n\n", + outputString(fcompact, content)); + assertEquals("\n\n", + outputString(fpretty, content)); + assertEquals("\n\n", + outputString(ftfw, content)); + } + + @Override + public void testDeepNesting() { + testDeepNestingCore(false); + } + + @Test + @Override + @Ignore + public void testOutputElementContent() { + // cannot test the formatting of an Element's contents easily... skip it for now. + } + }