From 2e4a107e7e97a2dc36471cbd795ec94096f1a771 Mon Sep 17 00:00:00 2001 From: Andy Shaw Date: Thu, 4 Dec 2014 11:00:30 +0100 Subject: [PATCH] Ensure that CSS rules are inherited from the parent tags When CSS is set in a head tag then it was not being inherited by the child tags when the CSS specification indicates that the properties are in fact inherited. This ensures that those properties are inherited and the non inheritable ones are unchanged. A test is added to cover the different situations with inheritance and a fix is done for the QTextDocumentFragment test which was passing despite having incorrect defaults. Task-number: QTBUG-28770 Task-number: QTBUG-34153 Change-Id: I55966240845a885852a04ecb82c61926dea9f800 Reviewed-by: Simon Hausmann --- src/gui/text/qcssparser.cpp | 24 ++ src/gui/text/qcssparser_p.h | 5 +- src/gui/text/qtexthtmlparser.cpp | 270 +++++++++++++----- tests/auto/gui/text/qtextdocument/test.css | 3 + .../text/qtextdocument/tst_qtextdocument.cpp | 138 ++++++++- .../tst_qtextdocumentfragment.cpp | 6 +- 6 files changed, 376 insertions(+), 70 deletions(-) create mode 100644 tests/auto/gui/text/qtextdocument/test.css diff --git a/src/gui/text/qcssparser.cpp b/src/gui/text/qcssparser.cpp index f9e6a7e335..3c98cb568d 100644 --- a/src/gui/text/qcssparser.cpp +++ b/src/gui/text/qcssparser.cpp @@ -364,6 +364,29 @@ static quint64 findKnownValue(const QString &name, const QCssKnownValue *start, return prop->id; } +static inline bool isInheritable(Property propertyId) +{ + switch (propertyId) { + case Font: + case FontFamily: + case FontSize: + case FontStyle: + case FontWeight: + case TextIndent: + case Whitespace: + case ListStyleType: + case ListStyle: + case TextAlignment: + case FontVariant: + case TextTransform: + case LineHeight: + return true; + default: + break; + } + return false; +} + /////////////////////////////////////////////////////////////////////////////// // Value Extractor ValueExtractor::ValueExtractor(const QVector &decls, const QPalette &pal) @@ -2317,6 +2340,7 @@ bool Parser::parseProperty(Declaration *decl) { decl->d->property = lexem(); decl->d->propertyId = static_cast(findKnownValue(decl->d->property, properties, NumProperties)); + decl->d->inheritable = isInheritable(decl->d->propertyId); skipSpace(); return true; } diff --git a/src/gui/text/qcssparser_p.h b/src/gui/text/qcssparser_p.h index ebd9904762..75802ba36a 100644 --- a/src/gui/text/qcssparser_p.h +++ b/src/gui/text/qcssparser_p.h @@ -426,12 +426,13 @@ struct Q_GUI_EXPORT Declaration { struct DeclarationData : public QSharedData { - inline DeclarationData() : propertyId(UnknownProperty), important(false) {} + inline DeclarationData() : propertyId(UnknownProperty), important(false), inheritable(false) {} QString property; Property propertyId; QVector values; QVariant parsed; - bool important; + bool important:1; + bool inheritable:1; }; QExplicitlySharedDataPointer d; inline Declaration() : d(new DeclarationData()) {} diff --git a/src/gui/text/qtexthtmlparser.cpp b/src/gui/text/qtexthtmlparser.cpp index edace3d13c..866e0046c0 100644 --- a/src/gui/text/qtexthtmlparser.cpp +++ b/src/gui/text/qtexthtmlparser.cpp @@ -1050,25 +1050,14 @@ void QTextHtmlParserNode::initializeProperties(const QTextHtmlParserNode *parent // set element specific attributes switch (id) { case Html_a: - charFormat.setAnchor(true); for (int i = 0; i < attributes.count(); i += 2) { const QString key = attributes.at(i); if (key.compare(QLatin1String("href"), Qt::CaseInsensitive) == 0 && !attributes.at(i + 1).isEmpty()) { hasHref = true; - charFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline); - charFormat.setForeground(QGuiApplication::palette().link()); } } - - break; - case Html_em: - case Html_i: - case Html_cite: - case Html_address: - case Html_var: - case Html_dfn: - charFormat.setFontItalic(true); + charFormat.setAnchor(true); break; case Html_big: charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(1)); @@ -1076,36 +1065,27 @@ void QTextHtmlParserNode::initializeProperties(const QTextHtmlParserNode *parent case Html_small: charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(-1)); break; - case Html_strong: - case Html_b: - charFormat.setFontWeight(QFont::Bold); - break; case Html_h1: - charFormat.setFontWeight(QFont::Bold); charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(3)); margin[QTextHtmlParser::MarginTop] = 18; margin[QTextHtmlParser::MarginBottom] = 12; break; case Html_h2: - charFormat.setFontWeight(QFont::Bold); charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(2)); margin[QTextHtmlParser::MarginTop] = 16; margin[QTextHtmlParser::MarginBottom] = 12; break; case Html_h3: - charFormat.setFontWeight(QFont::Bold); charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(1)); margin[QTextHtmlParser::MarginTop] = 14; margin[QTextHtmlParser::MarginBottom] = 12; break; case Html_h4: - charFormat.setFontWeight(QFont::Bold); charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(0)); margin[QTextHtmlParser::MarginTop] = 12; margin[QTextHtmlParser::MarginBottom] = 12; break; case Html_h5: - charFormat.setFontWeight(QFont::Bold); charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(-1)); margin[QTextHtmlParser::MarginTop] = 12; margin[QTextHtmlParser::MarginBottom] = 4; @@ -1114,11 +1094,7 @@ void QTextHtmlParserNode::initializeProperties(const QTextHtmlParserNode *parent margin[QTextHtmlParser::MarginTop] = 12; margin[QTextHtmlParser::MarginBottom] = 12; break; - case Html_center: - blockFormat.setAlignment(Qt::AlignCenter); - break; case Html_ul: - listStyle = QTextListFormat::ListDisc; // nested lists don't have margins, except for the toplevel one if (!isNestedList(parser)) { margin[QTextHtmlParser::MarginTop] = 12; @@ -1127,7 +1103,6 @@ void QTextHtmlParserNode::initializeProperties(const QTextHtmlParserNode *parent // no left margin as we use indenting instead break; case Html_ol: - listStyle = QTextListFormat::ListDecimal; // nested lists don't have margins, except for the toplevel one if (!isNestedList(parser)) { margin[QTextHtmlParser::MarginTop] = 12; @@ -1135,26 +1110,12 @@ void QTextHtmlParserNode::initializeProperties(const QTextHtmlParserNode *parent } // no left margin as we use indenting instead break; - case Html_code: - case Html_tt: - case Html_kbd: - case Html_samp: - charFormat.setFontFamily(QString::fromLatin1("Courier New,courier")); - // uses a fixed font, so set the property - charFormat.setFontFixedPitch(true); - break; case Html_br: text = QChar(QChar::LineSeparator); - wsm = QTextHtmlParserNode::WhiteSpacePre; break; - // ##### sub / sup case Html_pre: - charFormat.setFontFamily(QString::fromLatin1("Courier New,courier")); - wsm = WhiteSpacePre; margin[QTextHtmlParser::MarginTop] = 12; margin[QTextHtmlParser::MarginBottom] = 12; - //
 uses a fixed font
-            charFormat.setFontFixedPitch(true);
             break;
         case Html_blockquote:
             margin[QTextHtmlParser::MarginTop] = 12;
@@ -1169,28 +1130,6 @@ void QTextHtmlParserNode::initializeProperties(const QTextHtmlParserNode *parent
         case Html_dd:
             margin[QTextHtmlParser::MarginLeft] = 30;
             break;
-        case Html_u:
-            charFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline);
-            break;
-        case Html_s:
-            charFormat.setFontStrikeOut(true);
-            break;
-        case Html_nobr:
-            wsm = WhiteSpaceNoWrap;
-            break;
-        case Html_th:
-            charFormat.setFontWeight(QFont::Bold);
-            blockFormat.setAlignment(Qt::AlignCenter);
-            break;
-        case Html_td:
-            blockFormat.setAlignment(Qt::AlignLeft);
-            break;
-        case Html_sub:
-            charFormat.setVerticalAlignment(QTextCharFormat::AlignSubScript);
-            break;
-        case Html_sup:
-            charFormat.setVerticalAlignment(QTextCharFormat::AlignSuperScript);
-            break;
         default: break;
     }
 }
@@ -1345,6 +1284,14 @@ void QTextHtmlParserNode::applyCssDeclarations(const QVector
         case QCss::QtListNumberSuffix:
             textListNumberSuffix = decl.d->values.first().variant.toString();
             break;
+        case QCss::TextAlignment:
+            switch (identifier) {
+            case QCss::Value_Left: blockFormat.setAlignment(Qt::AlignLeft); break;
+            case QCss::Value_Center: blockFormat.setAlignment(Qt::AlignCenter); break;
+            case QCss::Value_Right: blockFormat.setAlignment(Qt::AlignRight); break;
+            default: break;
+            }
+            break;
         default: break;
         }
     }
@@ -1833,6 +1780,189 @@ void QTextHtmlParser::importStyleSheet(const QString &href)
     }
 }
 
+QVector standardDeclarationForNode(const QTextHtmlParserNode &node)
+{
+    QVector decls;
+    QCss::Declaration decl;
+    QCss::Value val;
+    switch (node.id) {
+    case Html_a:
+    case Html_u: {
+        bool needsUnderline = (node.id == Html_u) ? true : false;
+        if (node.id == Html_a) {
+            for (int i = 0; i < node.attributes.count(); i += 2) {
+                const QString key = node.attributes.at(i);
+                if (key.compare(QLatin1String("href"), Qt::CaseInsensitive) == 0
+                    && !node.attributes.at(i + 1).isEmpty()) {
+                    needsUnderline = true;
+                    decl.d->property = QLatin1String("color");
+                    decl.d->propertyId = QCss::Property::Color;
+                    val.type = QCss::Value::Color;
+                    val.variant = QVariant(QGuiApplication::palette().link());
+                    decl.d->values = QVector() << val;
+                    decl.d->inheritable = true;
+                    decls << decl;
+                    break;
+                }
+            }
+        }
+        if (needsUnderline) {
+            decl = QCss::Declaration();
+            decl.d->property = QLatin1String("text-decoration");
+            decl.d->propertyId = QCss::Property::TextDecoration;
+            val.type = QCss::Value::KnownIdentifier;
+            val.variant = QVariant(QCss::Value_Underline);
+            decl.d->values = QVector() << val;
+            decl.d->inheritable = true;
+            decls << decl;
+        }
+        break;
+    }
+    case Html_b:
+    case Html_strong:
+    case Html_h1:
+    case Html_h2:
+    case Html_h3:
+    case Html_h4:
+    case Html_h5:
+    case Html_th:
+        decl = QCss::Declaration();
+        decl.d->property = QLatin1String("font-weight");
+        decl.d->propertyId = QCss::Property::FontWeight;
+        val.type = QCss::Value::KnownIdentifier;
+        val.variant = QVariant(QCss::Value_Bold);
+        decl.d->values = QVector() << val;
+        decl.d->inheritable = true;
+        decls << decl;
+        if (node.id == Html_b || node.id == Html_strong)
+            break;
+        // Delibrate fall through
+    case Html_big:
+    case Html_small:
+        if (node.id != Html_th) {
+            decl = QCss::Declaration();
+            decl.d->property = QLatin1String("font-size");
+            decl.d->propertyId = QCss::Property::FontSize;
+            decl.d->inheritable = false;
+            val.type = QCss::Value::KnownIdentifier;
+            switch (node.id) {
+            case Html_h1: val.variant = QVariant(QCss::Value_XXLarge); break;
+            case Html_h2: val.variant = QVariant(QCss::Value_XLarge); break;
+            case Html_h3: case Html_big: val.variant = QVariant(QCss::Value_Large); break;
+            case Html_h4: val.variant = QVariant(QCss::Value_Medium); break;
+            case Html_h5: case Html_small: val.variant = QVariant(QCss::Value_Small); break;
+            default: break;
+            }
+            decl.d->values = QVector() << val;
+            decls << decl;
+            break;
+        }
+        // Delibrate fall through
+    case Html_center:
+    case Html_td:
+        decl = QCss::Declaration();
+        decl.d->property = QLatin1String("text-align");
+        decl.d->propertyId = QCss::Property::TextAlignment;
+        val.type = QCss::Value::KnownIdentifier;
+        val.variant = (node.id == Html_td) ? QVariant(QCss::Value_Left) : QVariant(QCss::Value_Center);
+        decl.d->values = QVector() << val;
+        decl.d->inheritable = true;
+        decls << decl;
+        break;
+    case Html_s:
+        decl = QCss::Declaration();
+        decl.d->property = QLatin1String("text-decoration");
+        decl.d->propertyId = QCss::Property::TextDecoration;
+        val.type = QCss::Value::KnownIdentifier;
+        val.variant = QVariant(QCss::Value_LineThrough);
+        decl.d->values = QVector() << val;
+        decl.d->inheritable = true;
+        decls << decl;
+        break;
+    case Html_em:
+    case Html_i:
+    case Html_cite:
+    case Html_address:
+    case Html_var:
+    case Html_dfn:
+        decl = QCss::Declaration();
+        decl.d->property = QLatin1String("font-style");
+        decl.d->propertyId = QCss::Property::FontStyle;
+        val.type = QCss::Value::KnownIdentifier;
+        val.variant = QVariant(QCss::Value_Italic);
+        decl.d->values = QVector() << val;
+        decl.d->inheritable = true;
+        decls << decl;
+        break;
+    case Html_sub:
+    case Html_sup:
+        decl = QCss::Declaration();
+        decl.d->property = QLatin1String("vertical-align");
+        decl.d->propertyId = QCss::Property::VerticalAlignment;
+        val.type = QCss::Value::KnownIdentifier;
+        val.variant = (node.id == Html_sub) ? QVariant(QCss::Value_Sub) : QVariant(QCss::Value_Super);
+        decl.d->values = QVector() << val;
+        decl.d->inheritable = true;
+        decls << decl;
+        break;
+    case Html_ul:
+    case Html_ol:
+        decl = QCss::Declaration();
+        decl.d->property = QLatin1String("list-style");
+        decl.d->propertyId = QCss::Property::ListStyle;
+        val.type = QCss::Value::KnownIdentifier;
+        val.variant = (node.id == Html_ul) ? QVariant(QCss::Value_Disc) : QVariant(QCss::Value_Decimal);
+        decl.d->values = QVector() << val;
+        decl.d->inheritable = true;
+        decls << decl;
+        break;
+    case Html_code:
+    case Html_tt:
+    case Html_kbd:
+    case Html_samp:
+    case Html_pre: {
+        decl = QCss::Declaration();
+        decl.d->property = QLatin1String("font-family");
+        decl.d->propertyId = QCss::Property::FontFamily;
+        QVector values;
+        val.type = QCss::Value::String;
+        val.variant = QLatin1String("Courier New");
+        values << val;
+        val.type = QCss::Value::TermOperatorComma;
+        val.variant = QVariant();
+        values << val;
+        val.type = QCss::Value::String;
+        val.variant = QLatin1String("courier");
+        values << val;
+        decl.d->values = values;
+        decl.d->inheritable = true;
+        decls << decl;
+        }
+        if (node.id != Html_pre)
+            break;
+        // Delibrate fall through
+    case Html_br:
+    case Html_nobr:
+        decl = QCss::Declaration();
+        decl.d->property = QLatin1String("whitespace");
+        decl.d->propertyId = QCss::Property::Whitespace;
+        val.type = QCss::Value::KnownIdentifier;
+        switch (node.id) {
+        case Html_br: val.variant = QVariant(QCss::Value_PreWrap); break;
+        case Html_nobr: val.variant = QVariant(QCss::Value_NoWrap); break;
+        case Html_pre: val.variant = QVariant(QCss::Value_Pre); break;
+        default: break;
+        }
+        decl.d->values = QVector() << val;
+        decl.d->inheritable = true;
+        decls << decl;
+        break;
+    default:
+        break;
+    }
+    return decls;
+}
+
 QVector QTextHtmlParser::declarationsForNode(int node) const
 {
     QVector decls;
@@ -1860,8 +1990,20 @@ QVector QTextHtmlParser::declarationsForNode(int node) const
     const char *extraPseudo = 0;
     if (nodes.at(node).id == Html_a && nodes.at(node).hasHref)
         extraPseudo = "link";
-    decls = selector.declarationsForNode(n, extraPseudo);
-
+    // Ensure that our own style is taken into consideration
+    decls = standardDeclarationForNode(nodes.at(node));
+    decls += selector.declarationsForNode(n, extraPseudo);
+    n = selector.parentNode(n);
+    while (!selector.isNullNode(n)) {
+        QVector inheritedDecls;
+        inheritedDecls = selector.declarationsForNode(n, extraPseudo);
+        for (int i = 0; i < inheritedDecls.size(); ++i) {
+            const QCss::Declaration &decl = inheritedDecls.at(i);
+            if (decl.d->inheritable)
+                decls.prepend(decl);
+        }
+        n = selector.parentNode(n);
+    }
     return decls;
 }
 
diff --git a/tests/auto/gui/text/qtextdocument/test.css b/tests/auto/gui/text/qtextdocument/test.css
new file mode 100644
index 0000000000..16a2b90666
--- /dev/null
+++ b/tests/auto/gui/text/qtextdocument/test.css
@@ -0,0 +1,3 @@
+body {
+    font: normal 400 14px/1.2 Arial;
+}
diff --git a/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp b/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp
index c51d33487e..5e6b606d83 100644
--- a/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp
+++ b/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp
@@ -187,7 +187,7 @@ private slots:
     void QTBUG28998_linkColor();
 
     void textCursorUsageWithinContentsChange();
-
+    void cssInheritance();
 private:
     void backgroundImage_checkExpectedHtml(const QTextDocument &doc);
     void buildRegExpData();
@@ -3006,6 +3006,37 @@ void tst_QTextDocument::QTBUG27354_spaceAndSoftSpace()
         QPainter p(&image);
         document.drawContents(&p, image.rect());
     }
+    {
+        // If no p tag is specified it should not be inheriting it
+        QTextDocument td;
+        td.setHtml("Foo
  • First
"); + QTextBlock block = td.begin(); + while (block.isValid()) { + QTextBlockFormat fmt = block.blockFormat(); + QVERIFY(fmt.lineHeightType() == QTextBlockFormat::SingleHeight); + QVERIFY(fmt.lineHeight() == 0); + block = block.next(); + } + } + { + QTextDocument td; + td.setHtml("

Foo

  • First
"); + QList originalMargins; + QTextBlock block = td.begin(); + while (block.isValid()) { + originalMargins << block.blockFormat().topMargin(); + block = block.next(); + } + originalMargins[0] = 85; + td.setHtml("

Foo

  • First
"); + block = td.begin(); + int count = 0; + while (block.isValid()) { + QTextBlockFormat fmt = block.blockFormat(); + QCOMPARE(fmt.topMargin(), originalMargins.at(count++)); + block = block.next(); + } + } } class BaseDocument : public QTextDocument @@ -3124,5 +3155,110 @@ void tst_QTextDocument::textCursorUsageWithinContentsChange() QCOMPARE(handler.verticalMovementX, -1); } +void tst_QTextDocument::cssInheritance() +{ + { + QTextDocument td; + td.setHtml("" + "

Foo

Bar

Baz

"); + QTextBlock block = td.begin(); + while (block.isValid()) { + QTextBlockFormat fmt = block.blockFormat(); + QVERIFY(fmt.lineHeightType() == QTextBlockFormat::ProportionalHeight); + QVERIFY(fmt.lineHeight() == 200); + block = block.next(); + } + } + { + QTextDocument td; + td.setHtml("" + "

Foo

Bar

Baz

"); + QTextBlock block = td.begin(); + QTextBlockFormat fmt = block.blockFormat(); + QVERIFY(fmt.lineHeightType() == QTextBlockFormat::FixedHeight); + QVERIFY(fmt.lineHeight() == 40); + block = block.next(); + fmt = block.blockFormat(); + QVERIFY(fmt.lineHeightType() == QTextBlockFormat::ProportionalHeight); + QVERIFY(fmt.lineHeight() == 300); + } + { + QTextDocument td; + td.setHtml("" + "

Foo

Bar

Baz

"); + QTextBlock block = td.begin(); + while (block.isValid()) { + QVERIFY(block.blockFormat().background() == QBrush()); + QVERIFY(block.charFormat().font().bold()); + block = block.next(); + } + } + { + QTextDocument td; + td.setHtml("" + "
Foo
Bar
"); + QTextBlock block = td.begin(); + // First is the table + QTextCharFormat fmt = block.charFormat(); + QVERIFY(!fmt.font().bold()); + QVERIFY(fmt.font().italic()); + // Then the th + block = block.next(); + fmt = block.charFormat(); + QVERIFY(fmt.font().bold()); + QVERIFY(fmt.font().italic()); + // Then the td + block = block.next(); + fmt = block.charFormat(); + QVERIFY(!fmt.font().bold()); + QVERIFY(fmt.font().italic()); + } + { + QTextDocument td; + td.setHtml("" + "

This should be bold

"); + QTextBlock block = td.begin(); + // First is the p + QTextCharFormat fmt = block.charFormat(); + QVERIFY(!fmt.font().bold()); + QTextBlock::iterator it = block.begin(); + // The non bold text is first + QTextFragment currentFragment = it.fragment(); + QVERIFY(currentFragment.isValid()); + fmt = currentFragment.charFormat(); + QVERIFY(!fmt.font().bold()); + ++it; + QVERIFY(!it.atEnd()); + // Now check the "bold" text + currentFragment = it.fragment(); + QVERIFY(currentFragment.isValid()); + fmt = currentFragment.charFormat(); + QVERIFY(!fmt.font().bold()); + QVERIFY(fmt.font().italic()); + } + { + QTextDocument td; + td.setHtml("" + "

This should be bold

"); + QTextBlock block = td.begin(); + // First is the p + QTextCharFormat fmt = block.charFormat(); + QVERIFY(!fmt.font().bold()); + QTextBlock::iterator it = block.begin(); + // The non bold text is first + QTextFragment currentFragment = it.fragment(); + QVERIFY(currentFragment.isValid()); + fmt = currentFragment.charFormat(); + QVERIFY(!fmt.font().bold()); + ++it; + QVERIFY(!it.atEnd()); + // Now check the bold text + currentFragment = it.fragment(); + QVERIFY(currentFragment.isValid()); + fmt = currentFragment.charFormat(); + QVERIFY(fmt.font().bold()); + } +} + QTEST_MAIN(tst_QTextDocument) #include "tst_qtextdocument.moc" diff --git a/tests/auto/gui/text/qtextdocumentfragment/tst_qtextdocumentfragment.cpp b/tests/auto/gui/text/qtextdocumentfragment/tst_qtextdocumentfragment.cpp index 0fb66c8cdc..8f0d306cba 100644 --- a/tests/auto/gui/text/qtextdocumentfragment/tst_qtextdocumentfragment.cpp +++ b/tests/auto/gui/text/qtextdocumentfragment/tst_qtextdocumentfragment.cpp @@ -841,13 +841,13 @@ void tst_QTextDocumentFragment::unorderedListEnumeration() setHtml(QString::fromLatin1(html)); cursor.movePosition(QTextCursor::End); QVERIFY(cursor.currentList()); - QVERIFY(cursor.currentList()->format().style() == QTextListFormat::ListCircle); + QVERIFY(cursor.currentList()->format().style() == QTextListFormat::ListDisc); - const char html2[] = "
      • Blah
    "; + const char html2[] = "
        • Blah
      "; setHtml(QString::fromLatin1(html2)); cursor.movePosition(QTextCursor::End); QVERIFY(cursor.currentList()); - QVERIFY(cursor.currentList()->format().style() == QTextListFormat::ListDisc); + QVERIFY(cursor.currentList()->format().style() == QTextListFormat::ListCircle); }