/************************************************************** * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * *************************************************************/ // MARKER(update_precomp.py): autogen include statement, do not remove #include "precompiled_svgio.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include ////////////////////////////////////////////////////////////////////////////// using namespace com::sun::star; ////////////////////////////////////////////////////////////////////////////// namespace { svgio::svgreader::SvgCharacterNode* whiteSpaceHandling(svgio::svgreader::SvgNode* pNode, svgio::svgreader::SvgCharacterNode* pLast) { if(pNode) { const svgio::svgreader::SvgNodeVector& rChilds = pNode->getChildren(); const sal_uInt32 nCount(rChilds.size()); for(sal_uInt32 a(0); a < nCount; a++) { svgio::svgreader::SvgNode* pCandidate = rChilds[a]; if(pCandidate) { switch(pCandidate->getType()) { case svgio::svgreader::SVGTokenCharacter: { // clean whitespace in text span svgio::svgreader::SvgCharacterNode* pCharNode = static_cast< svgio::svgreader::SvgCharacterNode* >(pCandidate); pCharNode->whiteSpaceHandling(); // pCharNode may have lost all text. If that's the case, ignore // as invalid character node if(pCharNode->getText().getLength()) { if(pLast) { bool bAddGap(true); static bool bNoGapsForBaselineShift(true); if(bNoGapsForBaselineShift) { // With this option a baseline shift between two char parts ('words') // will not add a space 'gap' to the end of the (non-last) word. This // seems to be the standard behaviour, see last bugdoc attached #122524# const svgio::svgreader::SvgStyleAttributes* pStyleLast = pLast->getSvgStyleAttributes(); const svgio::svgreader::SvgStyleAttributes* pStyleCurrent = pCandidate->getSvgStyleAttributes(); if(pStyleLast && pStyleCurrent && pStyleLast->getBaselineShift() != pStyleCurrent->getBaselineShift()) { bAddGap = false; } } // add in-between whitespace (single space) to last // known character node if(bAddGap) { pLast->addGap(); } } // remember new last corected character node pLast = pCharNode; } break; } case svgio::svgreader::SVGTokenTspan: case svgio::svgreader::SVGTokenTextPath: case svgio::svgreader::SVGTokenTref: { // recursively clean whitespaces in subhierarchy pLast = whiteSpaceHandling(pCandidate, pLast); break; } default: { OSL_ENSURE(false, "Unexpected token inside SVGTokenText (!)"); break; } } } } } return pLast; } } ////////////////////////////////////////////////////////////////////////////// namespace svgio { namespace svgreader { SvgDocHdl::SvgDocHdl(const rtl::OUString& aAbsolutePath) : maDocument(aAbsolutePath), mpTarget(0), maCssContents() { } SvgDocHdl::~SvgDocHdl() { #ifdef DBG_UTIL if(mpTarget) { OSL_ENSURE(false, "SvgDocHdl destructed with active target (!)"); delete mpTarget; } OSL_ENSURE(!maCssContents.size(), "SvgDocHdl destructed with active css style stack entry (!)"); #endif } void SvgDocHdl::startDocument( ) throw (xml::sax::SAXException, uno::RuntimeException) { OSL_ENSURE(!mpTarget, "Already a target at document start (!)"); OSL_ENSURE(!maCssContents.size(), "SvgDocHdl startDocument with active css style stack entry (!)"); } void SvgDocHdl::endDocument( ) throw (xml::sax::SAXException, uno::RuntimeException) { OSL_ENSURE(!mpTarget, "Still a target at document end (!)"); OSL_ENSURE(!maCssContents.size(), "SvgDocHdl endDocument with active css style stack entry (!)"); } void SvgDocHdl::startElement( const ::rtl::OUString& aName, const uno::Reference< xml::sax::XAttributeList >& xAttribs ) throw (xml::sax::SAXException, uno::RuntimeException) { if(aName.getLength()) { const SVGToken aSVGToken(StrToSVGToken(aName, false)); switch(aSVGToken) { /// structural elements case SVGTokenSymbol: { /// new basic node for Symbol. Content gets scanned, but /// will not be decomposed (see SvgNode::decomposeSvgNode and bReferenced) mpTarget = new SvgSymbolNode(maDocument, mpTarget); mpTarget->parseAttributes(xAttribs); break; } case SVGTokenDefs: case SVGTokenG: { /// new node for Defs/G mpTarget = new SvgGNode(aSVGToken, maDocument, mpTarget); mpTarget->parseAttributes(xAttribs); break; } case SVGTokenSvg: { /// new node for Svg mpTarget = new SvgSvgNode(maDocument, mpTarget); mpTarget->parseAttributes(xAttribs); break; } case SVGTokenUse: { /// new node for Use mpTarget = new SvgUseNode(maDocument, mpTarget); mpTarget->parseAttributes(xAttribs); break; } /// shape elements case SVGTokenCircle: { /// new node for Circle mpTarget = new SvgCircleNode(maDocument, mpTarget); mpTarget->parseAttributes(xAttribs); break; } case SVGTokenEllipse: { /// new node for Ellipse mpTarget = new SvgEllipseNode(maDocument, mpTarget); mpTarget->parseAttributes(xAttribs); break; } case SVGTokenLine: { /// new node for Line mpTarget = new SvgLineNode(maDocument, mpTarget); mpTarget->parseAttributes(xAttribs); break; } case SVGTokenPath: { /// new node for Path mpTarget = new SvgPathNode(maDocument, mpTarget); mpTarget->parseAttributes(xAttribs); break; } case SVGTokenPolygon: { /// new node for Polygon mpTarget = new SvgPolyNode(maDocument, mpTarget, false); mpTarget->parseAttributes(xAttribs); break; } case SVGTokenPolyline: { /// new node for Polyline mpTarget = new SvgPolyNode(maDocument, mpTarget, true); mpTarget->parseAttributes(xAttribs); break; } case SVGTokenRect: { /// new node for Rect mpTarget = new SvgRectNode(maDocument, mpTarget); mpTarget->parseAttributes(xAttribs); break; } case SVGTokenImage: { /// new node for Image mpTarget = new SvgImageNode(maDocument, mpTarget); mpTarget->parseAttributes(xAttribs); break; } /// title and description case SVGTokenTitle: case SVGTokenDesc: { /// new node for Title and/or Desc mpTarget = new SvgTitleDescNode(aSVGToken, maDocument, mpTarget); break; } /// gradients case SVGTokenLinearGradient: case SVGTokenRadialGradient: { mpTarget = new SvgGradientNode(aSVGToken, maDocument, mpTarget); mpTarget->parseAttributes(xAttribs); break; } /// gradient stops case SVGTokenStop: { mpTarget = new SvgGradientStopNode(maDocument, mpTarget); mpTarget->parseAttributes(xAttribs); break; } /// text case SVGTokenText: { mpTarget = new SvgTextNode(maDocument, mpTarget); mpTarget->parseAttributes(xAttribs); break; } case SVGTokenTspan: { mpTarget = new SvgTspanNode(maDocument, mpTarget); mpTarget->parseAttributes(xAttribs); break; } case SVGTokenTref: { mpTarget = new SvgTrefNode(maDocument, mpTarget); mpTarget->parseAttributes(xAttribs); break; } case SVGTokenTextPath: { mpTarget = new SvgTextPathNode(maDocument, mpTarget); mpTarget->parseAttributes(xAttribs); break; } /// styles (as stylesheets) case SVGTokenStyle: { SvgStyleNode* pNew = new SvgStyleNode(maDocument, mpTarget); mpTarget = pNew; const sal_uInt32 nAttributes(xAttribs->getLength()); if(0 == nAttributes) { // #125326# no attributes, thus also no type="text/css". This is allowed to be missing, // thus do mark this style as CssStyle. This is required to read the contained // text (which defines the css style) pNew->setTextCss(true); } else { // #125326# there are attributes, read them. This will set isTextCss to true if // a type="text/css" is contained as exact match, else not mpTarget->parseAttributes(xAttribs); } if(pNew->isTextCss()) { // if it is a Css style, allow reading text between the start and end tag (see // SvgDocHdl::characters for details) maCssContents.push_back(rtl::OUString()); } break; } /// structural elements clip-path and mask. Content gets scanned, but /// will not be decomposed (see SvgNode::decomposeSvgNode and bReferenced) case SVGTokenClipPathNode: { /// new node for ClipPath mpTarget = new SvgClipPathNode(maDocument, mpTarget); mpTarget->parseAttributes(xAttribs); break; } case SVGTokenMask: { /// new node for Mask mpTarget = new SvgMaskNode(maDocument, mpTarget); mpTarget->parseAttributes(xAttribs); break; } /// structural element marker case SVGTokenMarker: { /// new node for marker mpTarget = new SvgMarkerNode(maDocument, mpTarget); mpTarget->parseAttributes(xAttribs); break; } /// structural element pattern case SVGTokenPattern: { /// new node for pattern mpTarget = new SvgPatternNode(maDocument, mpTarget); mpTarget->parseAttributes(xAttribs); break; } default: { /// invalid token, ignore #ifdef DBG_UTIL myAssert( rtl::OUString::createFromAscii("Unknown Base SvgToken <") + aName + rtl::OUString::createFromAscii("> (!)")); #endif break; } } } } void SvgDocHdl::endElement( const ::rtl::OUString& aName ) throw (xml::sax::SAXException, uno::RuntimeException) { if(aName.getLength()) { const SVGToken aSVGToken(StrToSVGToken(aName, false)); SvgNode* pWhitespaceCheck(SVGTokenText == aSVGToken ? mpTarget : 0); SvgStyleNode* pCssStyle(SVGTokenStyle == aSVGToken ? static_cast< SvgStyleNode* >(mpTarget) : 0); SvgTitleDescNode* pSvgTitleDescNode(SVGTokenTitle == aSVGToken || SVGTokenDesc == aSVGToken ? static_cast< SvgTitleDescNode* >(mpTarget) : 0); switch(aSVGToken) { /// valid tokens for which a new one was created /// structural elements case SVGTokenDefs: case SVGTokenG: case SVGTokenSvg: case SVGTokenSymbol: case SVGTokenUse: /// shape elements case SVGTokenCircle: case SVGTokenEllipse: case SVGTokenLine: case SVGTokenPath: case SVGTokenPolygon: case SVGTokenPolyline: case SVGTokenRect: case SVGTokenImage: /// title and description case SVGTokenTitle: case SVGTokenDesc: /// gradients case SVGTokenLinearGradient: case SVGTokenRadialGradient: /// gradient stops case SVGTokenStop: /// text case SVGTokenText: case SVGTokenTspan: case SVGTokenTextPath: case SVGTokenTref: /// styles (as stylesheets) case SVGTokenStyle: /// structural elements clip-path and mask case SVGTokenClipPathNode: case SVGTokenMask: /// structural element marker case SVGTokenMarker: /// structural element pattern case SVGTokenPattern: /// content handling after parsing { if(mpTarget) { if(!mpTarget->getParent()) { // last element closing, save this tree maDocument.appendNode(mpTarget); } mpTarget = const_cast< SvgNode* >(mpTarget->getParent()); } else { OSL_ENSURE(false, "Closing token, but no context (!)"); } break; } default: { /// invalid token, ignore } } if(pSvgTitleDescNode && mpTarget) { const rtl::OUString aText(pSvgTitleDescNode->getText()); if(aText.getLength()) { if(SVGTokenTitle == aSVGToken) { mpTarget->parseAttribute(getStrTitle(), aSVGToken, aText); } else // if(SVGTokenDesc == aSVGToken) { mpTarget->parseAttribute(getStrDesc(), aSVGToken, aText); } } } if(pCssStyle && pCssStyle->isTextCss()) { // css style parsing if(maCssContents.size()) { // need to interpret css styles and remember them as StyleSheets // #125325# Caution! the Css content may contain block comments // (see http://www.w3.org/wiki/CSS_basics#CSS_comments). These need // to be removed first const rtl::OUString aCommentFreeSource(removeBlockComments(*(maCssContents.end() - 1))); if(aCommentFreeSource.getLength()) { pCssStyle->addCssStyleSheet(aCommentFreeSource); } maCssContents.pop_back(); } else { OSL_ENSURE(false, "Closing CssStyle, but no collector string on stack (!)"); } } if(pWhitespaceCheck) { // cleanup read strings whiteSpaceHandling(pWhitespaceCheck, 0); } } } void SvgDocHdl::characters( const ::rtl::OUString& aChars ) throw (xml::sax::SAXException, uno::RuntimeException) { const sal_uInt32 nLength(aChars.getLength()); if(mpTarget && nLength) { switch(mpTarget->getType()) { case SVGTokenText: case SVGTokenTspan: case SVGTokenTextPath: { const SvgNodeVector& rChilds = mpTarget->getChildren(); SvgCharacterNode* pTarget = 0; if(rChilds.size()) { pTarget = dynamic_cast< SvgCharacterNode* >(rChilds[rChilds.size() - 1]); } if(pTarget) { // concatenate to current character span pTarget->concatenate(aChars); } else { // add character span as simplified tspan (no arguments) // as direct child of SvgTextNode/SvgTspanNode/SvgTextPathNode new SvgCharacterNode(maDocument, mpTarget, aChars); } break; } case SVGTokenStyle: { SvgStyleNode& rSvgStyleNode = static_cast< SvgStyleNode& >(*mpTarget); if(rSvgStyleNode.isTextCss()) { // collect characters for css style if(maCssContents.size()) { const ::rtl::OUString aTrimmedChars(aChars.trim()); if(aTrimmedChars.getLength()) { std::vector< rtl::OUString >::iterator aString(maCssContents.end() - 1); (*aString) += aTrimmedChars; } } else { OSL_ENSURE(false, "Closing CssStyle, but no collector string on stack (!)"); } } break; } case SVGTokenTitle: case SVGTokenDesc: { SvgTitleDescNode& rSvgTitleDescNode = static_cast< SvgTitleDescNode& >(*mpTarget); // add text directly to SvgTitleDescNode rSvgTitleDescNode.concatenate(aChars); break; } default: { // characters not used by a known node break; } } } } void SvgDocHdl::ignorableWhitespace(const ::rtl::OUString& /*aWhitespaces*/) throw (xml::sax::SAXException, uno::RuntimeException) { } void SvgDocHdl::processingInstruction(const ::rtl::OUString& /*aTarget*/, const ::rtl::OUString& /*aData*/) throw (xml::sax::SAXException, uno::RuntimeException) { } void SvgDocHdl::setDocumentLocator(const uno::Reference< xml::sax::XLocator >& /*xLocator*/) throw (xml::sax::SAXException, uno::RuntimeException) { } } // end of namespace svgreader } // end of namespace svgio ////////////////////////////////////////////////////////////////////////////// // eof