예제 #1
0
bool PasteCommand::processXmlData(Element *element, KoXmlDocument *data)
{
    const QRect pasteArea = element->rect();
    Sheet *const sheet = element->sheet();
    Q_ASSERT(sheet == m_sheet);
    Map *const map = sheet->map();

    const KoXmlElement root = data->documentElement(); // "spreadsheet-snippet"
    if (root.hasAttribute("cut")) {
        const Region cutRegion(root.attribute("cut"), map, sheet);
        if (cutRegion.isValid()) {
            const Cell destination(sheet, pasteArea.topLeft());
            map->dependencyManager()->regionMoved(cutRegion, destination);
        }
    }

    const int sourceHeight = root.attribute("rows").toInt();
    const int sourceWidth  = root.attribute("columns").toInt();

    // Find size of rectangle that we want to paste to (either clipboard size or current selection)
    const bool noRowsInClipboard    = root.namedItem("rows").toElement().isNull();
    const bool noColumnsInClipboard = root.namedItem("columns").toElement().isNull();
    const bool noRowsSelected       = !Region::Range(pasteArea).isRow();
    const bool noColumnsSelected    = !Region::Range(pasteArea).isColumn();
    const bool biggerSelectedWidth  = pasteArea.width()  >= sourceWidth;
    const bool biggerSelectedHeight = pasteArea.height() >= sourceHeight;

    const int pasteWidth  = biggerSelectedWidth && noRowsSelected && noRowsInClipboard
                            ? pasteArea.width() : sourceWidth;
    const int pasteHeight = biggerSelectedHeight && noColumnsSelected && noColumnsInClipboard
                            ? pasteArea.height() : sourceHeight;

    const int xOffset = noRowsInClipboard ? pasteArea.left() - 1 : 0;
    const int yOffset = noColumnsInClipboard ? pasteArea.top() - 1 : 0;

    kDebug(36005) << "selected size (col x row):" << pasteArea.width() << 'x' << pasteArea.height();
    kDebug(36005) << "source size (col x row):" << sourceWidth << 'x' << sourceHeight;
    kDebug(36005) << "paste area size (col x row):" << pasteWidth << 'x' << pasteHeight;
    kDebug(36005) << "xOffset:" << xOffset << "yOffset:" << yOffset;

    // Determine the shift direction, if needed.
    if (m_insertMode == ShiftCells) {
        if (!noColumnsInClipboard && !noRowsInClipboard) {
            // There are columns and rows in the source data.
            m_insertMode = ShiftCellsRight; // faster than down
        } else if (!noColumnsInClipboard) {
            // There are columns in the source data.
            m_insertMode = ShiftCellsRight;
        } else if (!noRowsInClipboard) {
            // There are rows in the source data.
            m_insertMode = ShiftCellsDown;
        } else {
            // Should not happen.
            // ShiftCells should only be set, if the data contains columns/rows.
            Q_ASSERT(false);
            m_insertMode = ShiftCellsRight; // faster than down
        }
    }

    const bool noColumns = noColumnsInClipboard && noColumnsSelected;
    const bool noRows = noRowsInClipboard && noRowsSelected;

    // Shift cells down.
    if (m_insertMode == ShiftCellsDown) {
        // Cases:
        // 1. Columns AND rows are contained in either source or selection
        // 1.a Columns in source and rows in selection
        //      I.e. yOffset=0
        //      Clear everything.
        //      Taking the column data/style and fill all columns.
        // 1.b Columns and rows in source, but not in selection
        //      I.e. xOffset=0,yOffset=0
        //      Leave everything as is? No, data from different sheet is possible!
        //      Clear everything.
        //      Fill with the source column/row data/style,
        //      i.e. the sheet data becomes equal to the source data.
        //      Same procedure as in 1.e
        // 1.c Columns and rows in selection, but not in source
        //      Clear everything.
        //      Fill with the source data. Tiling -> HUGE task!
        // 1.d Rows in source and columns in selection
        //      I.e. xOffset=0
        //      Clear everything.
        //      Taking the row data/style and fill all rows.
        // 1.e Columns AND rows in both
        //      I.e. xOffset=0,yOffset=0
        //      Leave everything as is? No, data from different sheet is possible!
        //      Clear everything.
        //      Fill with the source column/row data/style,
        //      i.e. the sheet data becomes equal to the source data.
        //      Same procedure as in 1.b
        // 2. Columns are present in either source or selection, but no rows
        // 2a Columns in source
        //      I.e. yOffset=0
        //      Clear the appropriate columns in the paste area.
        //      Fill them with the source data.
        // 2b Columns in selection
        //      Clear the selected columns.
        //      Fill them with the source data. Tiling -> HUGE task!
        // 2c Columns in both
        //      I.e. yOffset=0
        //      Clear the selected columns.
        //      Fill them with the source column data/style.
        // 3. Rows are present in either source or selection, but no columns
        // 3a Rows in source
        //      I.e. xOffset=0
        //      Insert rows.
        //      Fill in data.
        // 3b Rows in selection
        //      Insert rows.
        //      Fill in data. Tiling -> HUGE task!
        // 3c Rows in both
        //      I.e. xOffset=0
        //      Insert rows.
        //      Fill in data/style from source rows.
        // 4. Neither column, nor rows are present
        //      Shift the cell down.
        //      Fill in data.
        if ((!noColumns && !noRows) || (!noColumns && noRows)) {
            // Everything or only columns present.
            DeleteCommand *const command = new DeleteCommand(this);
            command->setSheet(m_sheet);
            command->add(Region(pasteArea.x(), pasteArea.y(), pasteWidth, pasteHeight, sheet));
            command->setMode(DeleteCommand::OnlyCells);
        } else if (noColumns && !noRows) {
            // Rows present.
            InsertDeleteRowManipulator *const command = new InsertDeleteRowManipulator(this);
            command->setSheet(sheet);
            command->add(Region(pasteArea.x(), pasteArea.y(), pasteWidth, pasteHeight, sheet));
        } else {
            // Neither columns, nor rows present.
            ShiftManipulator *const command = new ShiftManipulator(this);
            command->setSheet(sheet);
            command->add(Region(pasteArea.x(), pasteArea.y(), pasteWidth, pasteHeight, sheet));
            command->setDirection(ShiftManipulator::ShiftBottom);
        }
    }
    // Shift cells right.
    if (m_insertMode == ShiftCellsRight) {
        // Cases:
        // Same as for ShiftCellsDown,
        // except that clearing and inserting are exchanged for cases 2 and 3.
        // Shifting a column to the right is the same as column insertion.
        // Shifting a row to the right is the same as clearing the row.
        if ((!noColumns && !noRows) || (noColumns && !noRows)) {
            // Everything or only rows present.
            DeleteCommand *const command = new DeleteCommand(this);
            command->setSheet(m_sheet);
            command->add(Region(pasteArea.x(), pasteArea.y(), pasteWidth, pasteHeight, sheet));
            command->setMode(DeleteCommand::OnlyCells);
        } else if (!noColumns && noRows) {
            // Columns present.
            InsertDeleteColumnManipulator *const command = new InsertDeleteColumnManipulator(this);
            command->setSheet(sheet);
            command->add(Region(pasteArea.x(), pasteArea.y(), pasteWidth, pasteHeight, sheet));
        } else {
            // Neither columns, nor rows present.
            ShiftManipulator *const command = new ShiftManipulator(this);
            command->setSheet(sheet);
            command->add(Region(pasteArea.x(), pasteArea.y(), pasteWidth, pasteHeight, sheet));
            command->setDirection(ShiftManipulator::ShiftRight);
        }
    }

    // This command will collect as many cell loads as possible in the iteration.
    PasteCellCommand *pasteCellCommand = 0;

    KoXmlElement e = root.firstChild().toElement(); // "columns", "rows" or "cell"
    for (; !e.isNull(); e = e.nextSibling().toElement()) {
        // If the element is not a cell, unset the pasteCellCommand pointer.
        // If existing, it is attached as child commnand, so no leaking here.
        if (e.tagName() != "cell") {
            pasteCellCommand = 0;
        }

        // entire columns given
        if (e.tagName() == "columns" && !sheet->isProtected()) {
            const int number = e.attribute("count").toInt();
            if (m_insertMode == NoInsertion) {
                // Clear the existing content; not the column style.
                DeleteCommand *const command = new DeleteCommand(this);
                command->setSheet(m_sheet);
                const int col = e.attribute("column").toInt();
                const int cols = qMax(pasteArea.width(), number);
                const Region region(col + xOffset, 1, cols, KS_rowMax, m_sheet);
                command->add(region);
                command->setMode(DeleteCommand::OnlyCells);
            }

            // Set the column style.
            ColumnFormat columnFormat;
            columnFormat.setSheet(sheet);
            KoXmlElement c = e.firstChild().toElement();
            for (; !c.isNull(); c = c.nextSibling().toElement()) {
                if (c.tagName() != "column") {
                    continue;
                }
                if (columnFormat.load(c, xOffset, m_pasteMode)) {
                    const int col = columnFormat.column();
                    const int cols = qMax(pasteArea.width(), number);
                    for (int coff = 0; col - xOffset + coff <= cols; coff += number) {
                        ColumnStyleCommand *const command = new ColumnStyleCommand(this);
                        command->setSheet(m_sheet);
                        command->add(Region(col + coff, 1, 1, 1, m_sheet));
                        command->setTemplate(columnFormat);
                    }
                }
            }
        }

        // entire rows given
        if (e.tagName() == "rows" && !sheet->isProtected()) {
            const int number = e.attribute("count").toInt();
            if (m_insertMode == NoInsertion) {
                // Clear the existing content; not the row style.
                DeleteCommand *const command = new DeleteCommand(this);
                command->setSheet(m_sheet);
                const int row = e.attribute("row").toInt();
                const int rows = qMax(pasteArea.height(), number);
                const Region region(1, row + yOffset, KS_colMax, rows, m_sheet);
                command->add(region);
                command->setMode(DeleteCommand::OnlyCells);
            }

            // Set the row style.
            RowFormat rowFormat;
            rowFormat.setSheet(sheet);
            KoXmlElement c = e.firstChild().toElement();
            for (; !c.isNull(); c = c.nextSibling().toElement()) {
                if (c.tagName() != "row") {
                    continue;
                }
                if (rowFormat.load(c, yOffset, m_pasteMode)) {
                    const int row = rowFormat.row();
                    const int rows = qMax(pasteArea.height(), number);
                    for (int roff = 0; row - yOffset + roff <= rows; roff += number) {
                        RowStyleCommand *const command = new RowStyleCommand(this);
                        command->setSheet(m_sheet);
                        command->add(Region(1, rowFormat.row(), 1, 1, m_sheet));
                        command->setTemplate(rowFormat);
                    }
                }
            }
        }

        if (e.tagName() == "cell") {
            // Create a new PasteCellCommand, if necessary.
            if (!pasteCellCommand) {
                pasteCellCommand = new PasteCellCommand(this);
                pasteCellCommand->setSheet(m_sheet);
                pasteCellCommand->m_pasteMode = m_pasteMode;
                pasteCellCommand->m_pasteOperation = m_operation;
                pasteCellCommand->m_pasteFC = m_pasteFC;
            }

            // Source cell location:
            const int row = e.attribute("row").toInt();
            const int col = e.attribute("column").toInt();

            // tile the selection with the clipboard contents
            for (int roff = 0; row + roff <= pasteHeight; roff += sourceHeight) {
                for (int coff = 0; col + coff <= pasteWidth; coff += sourceWidth) {
                    kDebug(36005) << "cell at" << (col + xOffset + coff) << ',' << (row + yOffset + roff)
                    << " with roff,coff=" << roff << ',' << coff
                    << ", xOffset:" << xOffset << ", yOffset:" << yOffset << endl;

                    // Destination cell:
                    const Cell cell(sheet, col + xOffset + coff, row + yOffset + roff);
                    // Do nothing, if the sheet and the cell are protected.
                    if (sheet->isProtected() && !cell.style().notProtected()) {
                        continue;
                    }
                    // Add the destination cell and the XML element itself.
                    pasteCellCommand->addXmlElement(cell, e);
                }
            }
        }
    }
    return true;
}
예제 #2
0
void Validity::loadOdfValidation(Cell* const cell, const QString& validationName,
                                 OdfLoadingContext& tableContext)
{
    KoXmlElement element = tableContext.validities.value(validationName);
    Validity validity;
    if (element.hasAttributeNS(KoXmlNS::table, "condition")) {
        QString valExpression = element.attributeNS(KoXmlNS::table, "condition", QString());
        kDebug(36003) << " element.attribute( table:condition )" << valExpression;
        //Condition ::= ExtendedTrueCondition | TrueFunction 'and' TrueCondition
        //TrueFunction ::= cell-content-is-whole-number() | cell-content-is-decimal-number() | cell-content-is-date() | cell-content-is-time()
        //ExtendedTrueCondition ::= ExtendedGetFunction | cell-content-text-length() Operator Value
        //TrueCondition ::= GetFunction | cell-content() Operator Value
        //GetFunction ::= cell-content-is-between(Value, Value) | cell-content-is-not-between(Value, Value)
        //ExtendedGetFunction ::= cell-content-text-length-is-between(Value, Value) | cell-content-text-length-is-not-between(Value, Value)
        //Operator ::= '<' | '>' | '<=' | '>=' | '=' | '!='
        //Value ::= NumberValue | String | Formula
        //A Formula is a formula without an equals (=) sign at the beginning. See section 8.1.3 for more information.
        //A String comprises one or more characters surrounded by quotation marks.
        //A NumberValue is a whole or decimal number. It must not contain comma separators for numbers of 1000 or greater.

        //ExtendedTrueCondition
        if (valExpression.contains("cell-content-text-length()")) {
            //"cell-content-text-length()>45"
            valExpression = valExpression.remove("oooc:cell-content-text-length()");
            kDebug(36003) << " valExpression = :" << valExpression;
            setRestriction(Validity::TextLength);

            loadOdfValidationCondition(valExpression, cell->sheet()->map()->parser());
        } else if (valExpression.contains("cell-content-is-text()")) {
            setRestriction(Validity::Text);
        }
        //cell-content-text-length-is-between(Value, Value) | cell-content-text-length-is-not-between(Value, Value) | cell-content-is-in-list( StringList )
        else if (valExpression.contains("cell-content-text-length-is-between")) {
            setRestriction(Validity::TextLength);
            setCondition(Conditional::Between);
            valExpression = valExpression.remove("oooc:cell-content-text-length-is-between(");
            kDebug(36003) << " valExpression :" << valExpression;
            valExpression = valExpression.remove(')');
            QStringList listVal = valExpression.split(',', QString::SkipEmptyParts);
            loadOdfValidationValue(listVal, cell->sheet()->map()->parser());
        } else if (valExpression.contains("cell-content-text-length-is-not-between")) {
            setRestriction(Validity::TextLength);
            setCondition(Conditional::Different);
            valExpression = valExpression.remove("oooc:cell-content-text-length-is-not-between(");
            kDebug(36003) << " valExpression :" << valExpression;
            valExpression = valExpression.remove(')');
            kDebug(36003) << " valExpression :" << valExpression;
            QStringList listVal = valExpression.split(',', QString::SkipEmptyParts);
            loadOdfValidationValue(listVal, cell->sheet()->map()->parser());
        } else if (valExpression.contains("cell-content-is-in-list(")) {
            setRestriction(Validity::List);
            valExpression = valExpression.remove("oooc:cell-content-is-in-list(");
            kDebug(36003) << " valExpression :" << valExpression;
            valExpression = valExpression.remove(')');
            setValidityList(valExpression.split(';',  QString::SkipEmptyParts));

        }
        //TrueFunction ::= cell-content-is-whole-number() | cell-content-is-decimal-number() | cell-content-is-date() | cell-content-is-time()
        else {
            if (valExpression.contains("cell-content-is-whole-number()")) {
                setRestriction(Validity::Number);
                valExpression = valExpression.remove("oooc:cell-content-is-whole-number() and ");
            } else if (valExpression.contains("cell-content-is-decimal-number()")) {
                setRestriction(Validity::Integer);
                valExpression = valExpression.remove("oooc:cell-content-is-decimal-number() and ");
            } else if (valExpression.contains("cell-content-is-date()")) {
                setRestriction(Validity::Date);
                valExpression = valExpression.remove("oooc:cell-content-is-date() and ");
            } else if (valExpression.contains("cell-content-is-time()")) {
                setRestriction(Validity::Time);
                valExpression = valExpression.remove("oooc:cell-content-is-time() and ");
            }
            kDebug(36003) << "valExpression :" << valExpression;

            if (valExpression.contains("cell-content()")) {
                valExpression = valExpression.remove("cell-content()");
                loadOdfValidationCondition(valExpression, cell->sheet()->map()->parser());
            }
            //GetFunction ::= cell-content-is-between(Value, Value) | cell-content-is-not-between(Value, Value)
            //for the moment we support just int/double value, not text/date/time :(
            if (valExpression.contains("cell-content-is-between(")) {
                valExpression = valExpression.remove("cell-content-is-between(");
                valExpression = valExpression.remove(')');
                QStringList listVal = valExpression.split(',', QString::SkipEmptyParts);
                loadOdfValidationValue(listVal, cell->sheet()->map()->parser());
                setCondition(Conditional::Between);
            }
            if (valExpression.contains("cell-content-is-not-between(")) {
                valExpression = valExpression.remove("cell-content-is-not-between(");
                valExpression = valExpression.remove(')');
                QStringList listVal = valExpression.split(',', QString::SkipEmptyParts);
                loadOdfValidationValue(listVal, cell->sheet()->map()->parser());
                setCondition(Conditional::Different);
            }
        }
    }
    if (element.hasAttributeNS(KoXmlNS::table, "allow-empty-cell")) {
        kDebug(36003) << " element.hasAttribute( table:allow-empty-cell ) :" << element.hasAttributeNS(KoXmlNS::table, "allow-empty-cell");
        setAllowEmptyCell(((element.attributeNS(KoXmlNS::table, "allow-empty-cell", QString()) == "true") ? true : false));
    }
    if (element.hasAttributeNS(KoXmlNS::table, "base-cell-address")) {
        //todo what is it ?
    }

    KoXmlElement help = KoXml::namedItemNS(element, KoXmlNS::table, "help-message");
    if (!help.isNull()) {
        if (help.hasAttributeNS(KoXmlNS::table, "title")) {
            kDebug(36003) << "help.attribute( table:title ) :" << help.attributeNS(KoXmlNS::table, "title", QString());
            setTitleInfo(help.attributeNS(KoXmlNS::table, "title", QString()));
        }
        if (help.hasAttributeNS(KoXmlNS::table, "display")) {
            kDebug(36003) << "help.attribute( table:display ) :" << help.attributeNS(KoXmlNS::table, "display", QString());
            setDisplayValidationInformation(((help.attributeNS(KoXmlNS::table, "display", QString()) == "true") ? true : false));
        }
        KoXmlElement attrText = KoXml::namedItemNS(help, KoXmlNS::text, "p");
        if (!attrText.isNull()) {
            kDebug(36003) << "help text :" << attrText.text();
            setMessageInfo(attrText.text());
        }
    }

    KoXmlElement error = KoXml::namedItemNS(element, KoXmlNS::table, "error-message");
    if (!error.isNull()) {
        if (error.hasAttributeNS(KoXmlNS::table, "title"))
            setTitle(error.attributeNS(KoXmlNS::table, "title", QString()));
        if (error.hasAttributeNS(KoXmlNS::table, "message-type")) {
            QString str = error.attributeNS(KoXmlNS::table, "message-type", QString());
            if (str == "warning")
                setAction(Validity::Warning);
            else if (str == "information")
                setAction(Validity::Information);
            else if (str == "stop")
                setAction(Validity::Stop);
            else
                kDebug(36003) << "validation : message type unknown  :" << str;
        }

        if (error.hasAttributeNS(KoXmlNS::table, "display")) {
            kDebug(36003) << " display message :" << error.attributeNS(KoXmlNS::table, "display", QString());
            setDisplayMessage((error.attributeNS(KoXmlNS::table, "display", QString()) == "true"));
        }
        KoXmlElement attrText = KoXml::namedItemNS(error, KoXmlNS::text, "p");
        if (!attrText.isNull())
            setMessage(attrText.text());
    }
    cell->setValidity(validity);
}
예제 #3
0
bool Validity::loadXML(Cell* const cell, const KoXmlElement& validityElement)
{
    ValueParser *const parser = cell->sheet()->map()->parser();
    bool ok = false;
    KoXmlElement param = validityElement.namedItem("param").toElement();
    if (!param.isNull()) {
        if (param.hasAttribute("cond")) {
            d->cond = (Conditional::Type) param.attribute("cond").toInt(&ok);
            if (!ok)
                return false;
        }
        if (param.hasAttribute("action")) {
            d->action = (Action) param.attribute("action").toInt(&ok);
            if (!ok)
                return false;
        }
        if (param.hasAttribute("allow")) {
            d->restriction = (Restriction) param.attribute("allow").toInt(&ok);
            if (!ok)
                return false;
        }
        if (param.hasAttribute("valmin")) {
            d->minValue = parser->tryParseNumber(param.attribute("valmin"), &ok);
            if (!ok)
                return false;
        }
        if (param.hasAttribute("valmax")) {
            d->maxValue = parser->tryParseNumber(param.attribute("valmax"), &ok);
            if (!ok)
                return false;
        }
        if (param.hasAttribute("displaymessage")) {
            d->displayMessage = (bool)param.attribute("displaymessage").toInt();
        }
        if (param.hasAttribute("displayvalidationinformation")) {
            d->displayValidationInformation = (bool)param.attribute("displayvalidationinformation").toInt();
        }
        if (param.hasAttribute("allowemptycell")) {
            d->allowEmptyCell = (bool)param.attribute("allowemptycell").toInt();
        }
        if (param.hasAttribute("listvalidity")) {
            d->listValidity = param.attribute("listvalidity").split(';', QString::SkipEmptyParts);
        }
    }
    KoXmlElement inputTitle = validityElement.namedItem("inputtitle").toElement();
    if (!inputTitle.isNull()) {
        d->titleInfo = inputTitle.text();
    }
    KoXmlElement inputMessage = validityElement.namedItem("inputmessage").toElement();
    if (!inputMessage.isNull()) {
        d->messageInfo = inputMessage.text();
    }

    KoXmlElement titleElement = validityElement.namedItem("title").toElement();
    if (!titleElement.isNull()) {
        d->title = titleElement.text();
    }
    KoXmlElement messageElement = validityElement.namedItem("message").toElement();
    if (!messageElement.isNull()) {
        d->message = messageElement.text();
    }
    KoXmlElement timeMinElement = validityElement.namedItem("timemin").toElement();
    if (!timeMinElement.isNull()) {
        d->minValue = parser->tryParseTime(timeMinElement.text());
    }
    KoXmlElement timeMaxElement = validityElement.namedItem("timemax").toElement();
    if (!timeMaxElement.isNull()) {
        d->maxValue = parser->tryParseTime(timeMaxElement.text());
    }
    KoXmlElement dateMinElement = validityElement.namedItem("datemin").toElement();
    if (!dateMinElement.isNull()) {
        d->minValue = parser->tryParseTime(dateMinElement.text());
    }
    KoXmlElement dateMaxElement = validityElement.namedItem("datemax").toElement();
    if (!dateMaxElement.isNull()) {
        d->maxValue = parser->tryParseTime(dateMaxElement.text());
    }
    return true;
}
예제 #4
0
bool SvgParser::parseGradient(const KoXmlElement &e, const KoXmlElement &referencedBy)
{
    // IMPROVEMENTS:
    // - Store the parsed colorstops in some sort of a cache so they don't need to be parsed again.
    // - A gradient inherits attributes it does not have from the referencing gradient.
    // - Gradients with no color stops have no fill or stroke.
    // - Gradients with one color stop have a solid color.

    SvgGraphicsContext *gc = m_context.currentGC();
    if (!gc)
        return false;

    SvgGradientHelper gradhelper;

    if (e.hasAttribute("xlink:href")) {
        QString href = e.attribute("xlink:href").mid(1);
        if (! href.isEmpty()) {
            // copy the referenced gradient if found
            SvgGradientHelper *pGrad = findGradient(href);
            if (pGrad)
                gradhelper = *pGrad;
        } else {
            //gc->fillType = SvgGraphicsContext::None; // <--- TODO Fill OR Stroke are none
            return false;
        }
    }

    // Use the gradient that is referencing, or if there isn't one, the original gradient.
    KoXmlElement b;
    if (!referencedBy.isNull())
        b = referencedBy;
    else
        b = e;

    QString gradientId = b.attribute("id");

    if (! gradientId.isEmpty()) {
        // check if we have this gradient already parsed
        // copy existing gradient if it exists
        if (m_gradients.find(gradientId) != m_gradients.end())
            gradhelper.copyGradient(m_gradients[ gradientId ].gradient());
    }

    if (b.attribute("gradientUnits") == "userSpaceOnUse")
        gradhelper.setGradientUnits(SvgGradientHelper::UserSpaceOnUse);

    // parse color prop
    QColor c = gc->currentColor;

    if (!b.attribute("color").isEmpty()) {
        m_context.styleParser().parseColor(c, b.attribute("color"));
    } else {
        // try style attr
        QString style = b.attribute("style").simplified();
        const QStringList substyles = style.split(';', QString::SkipEmptyParts);
        for (QStringList::ConstIterator it = substyles.begin(); it != substyles.end(); ++it) {
            QStringList substyle = it->split(':');
            QString command = substyle[0].trimmed();
            QString params  = substyle[1].trimmed();
            if (command == "color")
                m_context.styleParser().parseColor(c, params);
        }
    }
    gc->currentColor = c;

    if (b.tagName() == "linearGradient") {
        QLinearGradient *g = new QLinearGradient();
        if (gradhelper.gradientUnits() == SvgGradientHelper::ObjectBoundingBox) {
            g->setCoordinateMode(QGradient::ObjectBoundingMode);
            g->setStart(QPointF(SvgUtil::fromPercentage(b.attribute("x1", "0%")),
                                SvgUtil::fromPercentage(b.attribute("y1", "0%"))));
            g->setFinalStop(QPointF(SvgUtil::fromPercentage(b.attribute("x2", "100%")),
                                    SvgUtil::fromPercentage(b.attribute("y2", "0%"))));
        } else {
            g->setStart(QPointF(SvgUtil::fromUserSpace(b.attribute("x1").toDouble()),
                                SvgUtil::fromUserSpace(b.attribute("y1").toDouble())));
            g->setFinalStop(QPointF(SvgUtil::fromUserSpace(b.attribute("x2").toDouble()),
                                    SvgUtil::fromUserSpace(b.attribute("y2").toDouble())));
        }
        // preserve color stops
        if (gradhelper.gradient())
            g->setStops(gradhelper.gradient()->stops());
        gradhelper.setGradient(g);
    } else if (b.tagName() == "radialGradient") {
        QRadialGradient *g = new QRadialGradient();
        if (gradhelper.gradientUnits() == SvgGradientHelper::ObjectBoundingBox) {
            g->setCoordinateMode(QGradient::ObjectBoundingMode);
            g->setCenter(QPointF(SvgUtil::fromPercentage(b.attribute("cx", "50%")),
                                 SvgUtil::fromPercentage(b.attribute("cy", "50%"))));
            g->setRadius(SvgUtil::fromPercentage(b.attribute("r", "50%")));
            g->setFocalPoint(QPointF(SvgUtil::fromPercentage(b.attribute("fx", "50%")),
                                     SvgUtil::fromPercentage(b.attribute("fy", "50%"))));
        } else {
            g->setCenter(QPointF(SvgUtil::fromUserSpace(b.attribute("cx").toDouble()),
                                 SvgUtil::fromUserSpace(b.attribute("cy").toDouble())));
            g->setFocalPoint(QPointF(SvgUtil::fromUserSpace(b.attribute("fx").toDouble()),
                                     SvgUtil::fromUserSpace(b.attribute("fy").toDouble())));
            g->setRadius(SvgUtil::fromUserSpace(b.attribute("r").toDouble()));
        }
        // preserve color stops
        if (gradhelper.gradient())
            g->setStops(gradhelper.gradient()->stops());
        gradhelper.setGradient(g);
    } else {
        return false;
    }

    // handle spread method
    QString spreadMethod = b.attribute("spreadMethod");
    if (!spreadMethod.isEmpty()) {
        if (spreadMethod == "reflect")
            gradhelper.gradient()->setSpread(QGradient::ReflectSpread);
        else if (spreadMethod == "repeat")
            gradhelper.gradient()->setSpread(QGradient::RepeatSpread);
        else
            gradhelper.gradient()->setSpread(QGradient::PadSpread);
    } else
        gradhelper.gradient()->setSpread(QGradient::PadSpread);

    // Parse the color stops. The referencing gradient does not have colorstops,
    // so use the stops from the gradient it references to (e in this case and not b)
    m_context.styleParser().parseColorStops(gradhelper.gradient(), e);
    gradhelper.setTransform(SvgUtil::parseTransform(b.attribute("gradientTransform")));
    m_gradients.insert(gradientId, gradhelper);

    return true;
}