| /************************************************************** |
| * |
| * 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 <svgio/svgreader/svgdocumenthandler.hxx> |
| #include <svgio/svgreader/svgtoken.hxx> |
| #include <svgio/svgreader/svgsvgnode.hxx> |
| #include <svgio/svgreader/svggnode.hxx> |
| #include <svgio/svgreader/svgnode.hxx> |
| #include <svgio/svgreader/svgpathnode.hxx> |
| #include <svgio/svgreader/svgrectnode.hxx> |
| #include <svgio/svgreader/svggradientnode.hxx> |
| #include <svgio/svgreader/svggradientstopnode.hxx> |
| #include <svgio/svgreader/svgsymbolnode.hxx> |
| #include <svgio/svgreader/svgusenode.hxx> |
| #include <svgio/svgreader/svgcirclenode.hxx> |
| #include <svgio/svgreader/svgellipsenode.hxx> |
| #include <svgio/svgreader/svglinenode.hxx> |
| #include <svgio/svgreader/svgpolynode.hxx> |
| #include <svgio/svgreader/svgsymbolnode.hxx> |
| #include <svgio/svgreader/svgtextnode.hxx> |
| #include <svgio/svgreader/svgcharacternode.hxx> |
| #include <svgio/svgreader/svgtspannode.hxx> |
| #include <svgio/svgreader/svgtrefnode.hxx> |
| #include <svgio/svgreader/svgtextpathnode.hxx> |
| #include <svgio/svgreader/svgstylenode.hxx> |
| #include <svgio/svgreader/svgimagenode.hxx> |
| #include <svgio/svgreader/svgclippathnode.hxx> |
| #include <svgio/svgreader/svgmasknode.hxx> |
| #include <svgio/svgreader/svgmarkernode.hxx> |
| #include <svgio/svgreader/svgpatternnode.hxx> |
| #include <svgio/svgreader/svgtitledescnode.hxx> |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| 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 |