1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <svgdocumenthandler.hxx>
21 #include <svgtoken.hxx>
22 #include <svgsvgnode.hxx>
23 #include <svggnode.hxx>
24 #include <svganode.hxx>
25 #include <svgnode.hxx>
26 #include <svgpathnode.hxx>
27 #include <svgrectnode.hxx>
28 #include <svggradientnode.hxx>
29 #include <svggradientstopnode.hxx>
30 #include <svgsymbolnode.hxx>
31 #include <svgusenode.hxx>
32 #include <svgcirclenode.hxx>
33 #include <svgellipsenode.hxx>
34 #include <svglinenode.hxx>
35 #include <svgpolynode.hxx>
36 #include <svgtextnode.hxx>
37 #include <svgcharacternode.hxx>
38 #include <svgtspannode.hxx>
39 #include <svgtrefnode.hxx>
40 #include <svgtextpathnode.hxx>
41 #include <svgstylenode.hxx>
42 #include <svgimagenode.hxx>
43 #include <svgclippathnode.hxx>
44 #include <svgmasknode.hxx>
45 #include <svgmarkernode.hxx>
46 #include <svgpatternnode.hxx>
47 #include <svgtitledescnode.hxx>
48 #include <sal/log.hxx>
49 #include <osl/diagnose.h>
50 #include <o3tl/string_view.hxx>
52 using namespace com::sun::star
;
54 namespace svgio::svgreader
59 svgio::svgreader::SvgCharacterNode
* whiteSpaceHandling(svgio::svgreader::SvgNode
const * pNode
, svgio::svgreader::SvgCharacterNode
* pLast
)
63 const auto& rChilds
= pNode
->getChildren();
64 const sal_uInt32
nCount(rChilds
.size());
66 for(sal_uInt32
a(0); a
< nCount
; a
++)
68 svgio::svgreader::SvgNode
* pCandidate
= rChilds
[a
].get();
72 switch(pCandidate
->getType())
74 case SVGToken::Character
:
76 // clean whitespace in text span
77 svgio::svgreader::SvgCharacterNode
* pCharNode
= static_cast< svgio::svgreader::SvgCharacterNode
* >(pCandidate
);
79 pCharNode
->whiteSpaceHandling();
81 // pCharNode may have lost all text. If that's the case, ignore
82 // as invalid character node
83 // Also ignore if textBeforeSpaceHandling just have spaces
84 if(!pCharNode
->getText().isEmpty() && !o3tl::trim(pCharNode
->getTextBeforeSpaceHandling()).empty())
90 // Do not add a gap if last node doesn't end with a space and
91 // current note doesn't start with a space
92 const sal_uInt32
nLastLength(pLast
->getTextBeforeSpaceHandling().getLength());
93 if(pLast
->getTextBeforeSpaceHandling()[nLastLength
- 1] != ' ' && pCharNode
->getTextBeforeSpaceHandling()[0] != ' ')
96 // With this option a baseline shift between two char parts ('words')
97 // will not add a space 'gap' to the end of the (non-last) word. This
98 // seems to be the standard behaviour, see last bugdoc attached #122524#
99 const svgio::svgreader::SvgStyleAttributes
* pStyleLast
= pLast
->getSvgStyleAttributes();
100 const svgio::svgreader::SvgStyleAttributes
* pStyleCurrent
= pCandidate
->getSvgStyleAttributes();
102 if(pStyleLast
&& pStyleCurrent
&& pStyleLast
->getBaselineShift() != pStyleCurrent
->getBaselineShift())
107 // add in-between whitespace (single space) to last
108 // known character node
115 // remember new last corrected character node
121 case SVGToken::Tspan
:
122 case SVGToken::TextPath
:
125 // recursively clean whitespaces in subhierarchy
126 pLast
= whiteSpaceHandling(pCandidate
, pLast
);
131 OSL_ENSURE(false, "Unexpected token inside SVGTokenText (!)");
141 } // end anonymous namespace
143 SvgDocHdl::SvgDocHdl(const OUString
& aAbsolutePath
)
144 : maDocument(aAbsolutePath
),
150 SvgDocHdl::~SvgDocHdl()
154 OSL_ENSURE(false, "SvgDocHdl destructed with active target (!)");
156 while (mpTarget
->getParent())
157 mpTarget
= const_cast< SvgNode
* >(mpTarget
->getParent());
159 const SvgNodeVector
& rOwnedTopLevels
= maDocument
.getSvgNodeVector();
160 if (std::none_of(rOwnedTopLevels
.begin(), rOwnedTopLevels
.end(),
161 [&](std::unique_ptr
<SvgNode
> const & p
) { return p
.get() == mpTarget
; }))
164 OSL_ENSURE(maCssContents
.empty(), "SvgDocHdl destructed with active css style stack entry (!)");
167 void SvgDocHdl::startDocument( )
169 OSL_ENSURE(!mpTarget
, "Already a target at document start (!)");
170 OSL_ENSURE(maCssContents
.empty(), "SvgDocHdl startDocument with active css style stack entry (!)");
173 void SvgDocHdl::endDocument( )
175 OSL_ENSURE(!mpTarget
, "Still a target at document end (!)");
176 OSL_ENSURE(maCssContents
.empty(), "SvgDocHdl endDocument with active css style stack entry (!)");
179 void SvgDocHdl::startElement( const OUString
& aName
, const uno::Reference
< xml::sax::XAttributeList
>& xAttribs
)
186 const SVGToken
aSVGToken(StrToSVGToken(aName
, false));
190 /// structural elements
191 case SVGToken::Symbol
:
193 /// new basic node for Symbol. Content gets scanned, but
194 /// will not be decomposed (see SvgNode::decomposeSvgNode and bReferenced)
195 mpTarget
= new SvgSymbolNode(maDocument
, mpTarget
);
196 mpTarget
->parseAttributes(xAttribs
);
202 /// new node for Defs/G
203 mpTarget
= new SvgGNode(aSVGToken
, maDocument
, mpTarget
);
204 mpTarget
->parseAttributes(xAttribs
);
210 mpTarget
= new SvgSvgNode(maDocument
, mpTarget
);
211 mpTarget
->parseAttributes(xAttribs
);
217 mpTarget
= new SvgUseNode(maDocument
, mpTarget
);
218 mpTarget
->parseAttributes(xAttribs
);
224 mpTarget
= new SvgANode(maDocument
, mpTarget
);
225 mpTarget
->parseAttributes(xAttribs
);
230 case SVGToken::Circle
:
232 /// new node for Circle
233 mpTarget
= new SvgCircleNode(maDocument
, mpTarget
);
234 mpTarget
->parseAttributes(xAttribs
);
237 case SVGToken::Ellipse
:
239 /// new node for Ellipse
240 mpTarget
= new SvgEllipseNode(maDocument
, mpTarget
);
241 mpTarget
->parseAttributes(xAttribs
);
246 /// new node for Line
247 mpTarget
= new SvgLineNode(maDocument
, mpTarget
);
248 mpTarget
->parseAttributes(xAttribs
);
253 /// new node for Path
254 mpTarget
= new SvgPathNode(maDocument
, mpTarget
);
255 mpTarget
->parseAttributes(xAttribs
);
258 case SVGToken::Polygon
:
260 /// new node for Polygon
261 mpTarget
= new SvgPolyNode(maDocument
, mpTarget
, false);
262 mpTarget
->parseAttributes(xAttribs
);
265 case SVGToken::Polyline
:
267 /// new node for Polyline
268 mpTarget
= new SvgPolyNode(maDocument
, mpTarget
, true);
269 mpTarget
->parseAttributes(xAttribs
);
274 /// new node for Rect
275 mpTarget
= new SvgRectNode(maDocument
, mpTarget
);
276 mpTarget
->parseAttributes(xAttribs
);
279 case SVGToken::Image
:
281 /// new node for Image
282 mpTarget
= new SvgImageNode(maDocument
, mpTarget
);
283 mpTarget
->parseAttributes(xAttribs
);
287 /// title and description
288 case SVGToken::Title
:
291 /// new node for Title and/or Desc
292 mpTarget
= new SvgTitleDescNode(aSVGToken
, maDocument
, mpTarget
);
297 case SVGToken::LinearGradient
:
298 case SVGToken::RadialGradient
:
300 mpTarget
= new SvgGradientNode(aSVGToken
, maDocument
, mpTarget
);
301 mpTarget
->parseAttributes(xAttribs
);
308 mpTarget
= new SvgGradientStopNode(maDocument
, mpTarget
);
309 mpTarget
->parseAttributes(xAttribs
);
316 mpTarget
= new SvgTextNode(maDocument
, mpTarget
);
317 mpTarget
->parseAttributes(xAttribs
);
320 case SVGToken::Tspan
:
322 mpTarget
= new SvgTspanNode(maDocument
, mpTarget
);
323 mpTarget
->parseAttributes(xAttribs
);
328 mpTarget
= new SvgTrefNode(maDocument
, mpTarget
);
329 mpTarget
->parseAttributes(xAttribs
);
332 case SVGToken::TextPath
:
334 mpTarget
= new SvgTextPathNode(maDocument
, mpTarget
);
335 mpTarget
->parseAttributes(xAttribs
);
339 /// styles (as stylesheets)
340 case SVGToken::Style
:
342 SvgStyleNode
* pNew
= new SvgStyleNode(maDocument
, mpTarget
);
345 // #i125326# there are attributes, read them. This will set isTextCss to false if
346 // type attibute is different to "text/css"
347 mpTarget
->parseAttributes(xAttribs
);
349 if(pNew
->isTextCss())
351 // if it is a Css style, allow reading text between the start and end tag (see
352 // SvgDocHdl::characters for details)
353 maCssContents
.emplace_back();
358 /// structural elements clip-path and mask. Content gets scanned, but
359 /// will not be decomposed (see SvgNode::decomposeSvgNode and bReferenced)
360 case SVGToken::ClipPathNode
:
362 /// new node for ClipPath
363 mpTarget
= new SvgClipPathNode(maDocument
, mpTarget
);
364 mpTarget
->parseAttributes(xAttribs
);
369 /// new node for Mask
370 mpTarget
= new SvgMaskNode(maDocument
, mpTarget
);
371 mpTarget
->parseAttributes(xAttribs
);
375 /// structural element marker
376 case SVGToken::Marker
:
378 /// new node for marker
379 mpTarget
= new SvgMarkerNode(maDocument
, mpTarget
);
380 mpTarget
->parseAttributes(xAttribs
);
384 /// structural element pattern
385 case SVGToken::Pattern
:
387 /// new node for pattern
388 mpTarget
= new SvgPatternNode(maDocument
, mpTarget
);
389 mpTarget
->parseAttributes(xAttribs
);
393 // ignore FlowRoot and child nodes
394 case SVGToken::FlowRoot
:
402 /// invalid token, ignore
403 SAL_INFO( "svgio", "Unknown Base SvgToken <" + aName
+ "> (!)" );
409 void SvgDocHdl::endElement( const OUString
& aName
)
414 const SVGToken
aSVGToken(StrToSVGToken(aName
, false));
415 SvgNode
* pWhitespaceCheck(SVGToken::Text
== aSVGToken
? mpTarget
: nullptr);
416 SvgStyleNode
* pCssStyle(SVGToken::Style
== aSVGToken
? static_cast< SvgStyleNode
* >(mpTarget
) : nullptr);
417 SvgTitleDescNode
* pSvgTitleDescNode(SVGToken::Title
== aSVGToken
|| SVGToken::Desc
== aSVGToken
? static_cast< SvgTitleDescNode
* >(mpTarget
) : nullptr);
419 // if we are in skipping mode and we reach the flowRoot end tag: stop skipping mode
420 if(bSkip
&& aSVGToken
== SVGToken::FlowRoot
)
422 // we are in skipping mode: do nothing until we found the flowRoot end tag
428 /// valid tokens for which a new one was created
430 /// structural elements
434 case SVGToken::Symbol
:
439 case SVGToken::Circle
:
440 case SVGToken::Ellipse
:
443 case SVGToken::Polygon
:
444 case SVGToken::Polyline
:
446 case SVGToken::Image
:
448 /// title and description
449 case SVGToken::Title
:
453 case SVGToken::LinearGradient
:
454 case SVGToken::RadialGradient
:
461 case SVGToken::Tspan
:
462 case SVGToken::TextPath
:
465 /// styles (as stylesheets)
466 case SVGToken::Style
:
468 /// structural elements clip-path and mask
469 case SVGToken::ClipPathNode
:
472 /// structural element marker
473 case SVGToken::Marker
:
475 /// structural element pattern
476 case SVGToken::Pattern
:
478 /// content handling after parsing
482 if(!mpTarget
->getParent())
484 // last element closing, save this tree
485 maDocument
.appendNode(std::unique_ptr
<SvgNode
>(mpTarget
));
488 mpTarget
= const_cast< SvgNode
* >(mpTarget
->getParent());
492 OSL_ENSURE(false, "Closing token, but no context (!)");
498 /// invalid token, ignore
502 if(pSvgTitleDescNode
&& mpTarget
)
504 const OUString
& aText(pSvgTitleDescNode
->getText());
508 if(SVGToken::Title
== aSVGToken
)
510 mpTarget
->parseAttribute(getStrTitle(), aSVGToken
, aText
);
512 else // if(SVGTokenDesc == aSVGToken)
514 mpTarget
->parseAttribute(getStrDesc(), aSVGToken
, aText
);
519 if(pCssStyle
&& pCssStyle
->isTextCss())
522 if(!maCssContents
.empty())
524 // need to interpret css styles and remember them as StyleSheets
525 // #125325# Caution! the Css content may contain block comments
526 // (see http://www.w3.org/wiki/CSS_basics#CSS_comments). These need
527 // to be removed first
528 const OUString
aCommentFreeSource(removeBlockComments(*(maCssContents
.end() - 1)));
530 if(aCommentFreeSource
.getLength())
532 pCssStyle
->addCssStyleSheet(aCommentFreeSource
);
535 maCssContents
.pop_back();
539 OSL_ENSURE(false, "Closing CssStyle, but no collector string on stack (!)");
545 // cleanup read strings
546 whiteSpaceHandling(pWhitespaceCheck
, nullptr);
550 void SvgDocHdl::characters( const OUString
& aChars
)
552 const sal_uInt32
nLength(aChars
.getLength());
554 if(!(mpTarget
&& nLength
))
557 switch(mpTarget
->getType())
560 case SVGToken::Tspan
:
561 case SVGToken::TextPath
:
563 const auto& rChilds
= mpTarget
->getChildren();
564 SvgCharacterNode
* pTarget
= nullptr;
568 pTarget
= dynamic_cast< SvgCharacterNode
* >(rChilds
[rChilds
.size() - 1].get());
573 // concatenate to current character span
574 pTarget
->concatenate(aChars
);
578 // add character span as simplified tspan (no arguments)
579 // as direct child of SvgTextNode/SvgTspanNode/SvgTextPathNode
580 new SvgCharacterNode(maDocument
, mpTarget
, aChars
);
584 case SVGToken::Style
:
586 SvgStyleNode
& rSvgStyleNode
= static_cast< SvgStyleNode
& >(*mpTarget
);
588 if(rSvgStyleNode
.isTextCss())
590 // collect characters for css style
591 if(!maCssContents
.empty())
593 const OUString
aTrimmedChars(aChars
.trim());
595 if(!aTrimmedChars
.isEmpty())
597 std::vector
< OUString
>::iterator
aString(maCssContents
.end() - 1);
598 (*aString
) += aTrimmedChars
;
603 OSL_ENSURE(false, "Closing CssStyle, but no collector string on stack (!)");
608 case SVGToken::Title
:
611 SvgTitleDescNode
& rSvgTitleDescNode
= static_cast< SvgTitleDescNode
& >(*mpTarget
);
613 // add text directly to SvgTitleDescNode
614 rSvgTitleDescNode
.concatenate(aChars
);
619 // characters not used by a known node
625 void SvgDocHdl::ignorableWhitespace(const OUString
& /*aWhitespaces*/)
629 void SvgDocHdl::processingInstruction(const OUString
& /*aTarget*/, const OUString
& /*aData*/)
633 void SvgDocHdl::setDocumentLocator(const uno::Reference
< xml::sax::XLocator
>& /*xLocator*/)
636 } // end of namespace svgio
638 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */