bump product version to 7.2.5.1
[LibreOffice.git] / svgio / source / svgreader / svgdocumenthandler.cxx
blobccc0bc306c8b32f6ebb3a28933dae4bd09e57f5c
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
50 using namespace com::sun::star;
52 namespace svgio::svgreader
55 namespace
57 svgio::svgreader::SvgCharacterNode* whiteSpaceHandling(svgio::svgreader::SvgNode const * pNode, svgio::svgreader::SvgCharacterNode* pLast)
59 if(pNode)
61 const auto& rChilds = pNode->getChildren();
62 const sal_uInt32 nCount(rChilds.size());
64 for(sal_uInt32 a(0); a < nCount; a++)
66 svgio::svgreader::SvgNode* pCandidate = rChilds[a].get();
68 if(pCandidate)
70 switch(pCandidate->getType())
72 case SVGToken::Character:
74 // clean whitespace in text span
75 svgio::svgreader::SvgCharacterNode* pCharNode = static_cast< svgio::svgreader::SvgCharacterNode* >(pCandidate);
76 pCharNode->whiteSpaceHandling();
78 // pCharNode may have lost all text. If that's the case, ignore
79 // as invalid character node
80 if(!pCharNode->getText().isEmpty())
82 if(pLast)
84 bool bAddGap(true);
85 static bool bNoGapsForBaselineShift(true); // loplugin:constvars:ignore
87 if(bNoGapsForBaselineShift)
89 // With this option a baseline shift between two char parts ('words')
90 // will not add a space 'gap' to the end of the (non-last) word. This
91 // seems to be the standard behaviour, see last bugdoc attached #122524#
92 const svgio::svgreader::SvgStyleAttributes* pStyleLast = pLast->getSvgStyleAttributes();
93 const svgio::svgreader::SvgStyleAttributes* pStyleCurrent = pCandidate->getSvgStyleAttributes();
95 if(pStyleLast && pStyleCurrent && pStyleLast->getBaselineShift() != pStyleCurrent->getBaselineShift())
97 bAddGap = false;
101 // add in-between whitespace (single space) to last
102 // known character node
103 if(bAddGap)
105 pLast->addGap();
109 // remember new last corrected character node
110 pLast = pCharNode;
112 break;
114 case SVGToken::Tspan:
115 case SVGToken::TextPath:
116 case SVGToken::Tref:
118 // recursively clean whitespaces in subhierarchy
119 pLast = whiteSpaceHandling(pCandidate, pLast);
120 break;
122 default:
124 OSL_ENSURE(false, "Unexpected token inside SVGTokenText (!)");
125 break;
132 return pLast;
134 } // end anonymous namespace
136 SvgDocHdl::SvgDocHdl(const OUString& aAbsolutePath)
137 : maDocument(aAbsolutePath),
138 mpTarget(nullptr),
139 maCssContents(),
140 bSkip(false)
144 SvgDocHdl::~SvgDocHdl()
146 if (mpTarget)
148 OSL_ENSURE(false, "SvgDocHdl destructed with active target (!)");
150 while (mpTarget->getParent())
151 mpTarget = const_cast< SvgNode* >(mpTarget->getParent());
153 const SvgNodeVector& rOwnedTopLevels = maDocument.getSvgNodeVector();
154 if (std::none_of(rOwnedTopLevels.begin(), rOwnedTopLevels.end(),
155 [&](std::unique_ptr<SvgNode> const & p) { return p.get() == mpTarget; }))
156 delete mpTarget;
158 OSL_ENSURE(maCssContents.empty(), "SvgDocHdl destructed with active css style stack entry (!)");
161 void SvgDocHdl::startDocument( )
163 OSL_ENSURE(!mpTarget, "Already a target at document start (!)");
164 OSL_ENSURE(maCssContents.empty(), "SvgDocHdl startDocument with active css style stack entry (!)");
167 void SvgDocHdl::endDocument( )
169 OSL_ENSURE(!mpTarget, "Still a target at document end (!)");
170 OSL_ENSURE(maCssContents.empty(), "SvgDocHdl endDocument with active css style stack entry (!)");
173 void SvgDocHdl::startElement( const OUString& aName, const uno::Reference< xml::sax::XAttributeList >& xAttribs )
175 if (bSkip)
176 return;
177 if(aName.isEmpty())
178 return;
180 const SVGToken aSVGToken(StrToSVGToken(aName, false));
182 switch (aSVGToken)
184 /// structural elements
185 case SVGToken::Symbol:
187 /// new basic node for Symbol. Content gets scanned, but
188 /// will not be decomposed (see SvgNode::decomposeSvgNode and bReferenced)
189 mpTarget = new SvgSymbolNode(maDocument, mpTarget);
190 mpTarget->parseAttributes(xAttribs);
191 break;
193 case SVGToken::Defs:
194 case SVGToken::G:
196 /// new node for Defs/G
197 mpTarget = new SvgGNode(aSVGToken, maDocument, mpTarget);
198 mpTarget->parseAttributes(xAttribs);
199 break;
201 case SVGToken::Svg:
203 /// new node for Svg
204 mpTarget = new SvgSvgNode(maDocument, mpTarget);
205 mpTarget->parseAttributes(xAttribs);
206 break;
208 case SVGToken::Use:
210 /// new node for Use
211 mpTarget = new SvgUseNode(maDocument, mpTarget);
212 mpTarget->parseAttributes(xAttribs);
213 break;
215 case SVGToken::A:
217 /// new node for A
218 mpTarget = new SvgANode(maDocument, mpTarget);
219 mpTarget->parseAttributes(xAttribs);
220 break;
223 /// shape elements
224 case SVGToken::Circle:
226 /// new node for Circle
227 mpTarget = new SvgCircleNode(maDocument, mpTarget);
228 mpTarget->parseAttributes(xAttribs);
229 break;
231 case SVGToken::Ellipse:
233 /// new node for Ellipse
234 mpTarget = new SvgEllipseNode(maDocument, mpTarget);
235 mpTarget->parseAttributes(xAttribs);
236 break;
238 case SVGToken::Line:
240 /// new node for Line
241 mpTarget = new SvgLineNode(maDocument, mpTarget);
242 mpTarget->parseAttributes(xAttribs);
243 break;
245 case SVGToken::Path:
247 /// new node for Path
248 mpTarget = new SvgPathNode(maDocument, mpTarget);
249 mpTarget->parseAttributes(xAttribs);
250 break;
252 case SVGToken::Polygon:
254 /// new node for Polygon
255 mpTarget = new SvgPolyNode(maDocument, mpTarget, false);
256 mpTarget->parseAttributes(xAttribs);
257 break;
259 case SVGToken::Polyline:
261 /// new node for Polyline
262 mpTarget = new SvgPolyNode(maDocument, mpTarget, true);
263 mpTarget->parseAttributes(xAttribs);
264 break;
266 case SVGToken::Rect:
268 /// new node for Rect
269 mpTarget = new SvgRectNode(maDocument, mpTarget);
270 mpTarget->parseAttributes(xAttribs);
271 break;
273 case SVGToken::Image:
275 /// new node for Image
276 mpTarget = new SvgImageNode(maDocument, mpTarget);
277 mpTarget->parseAttributes(xAttribs);
278 break;
281 /// title and description
282 case SVGToken::Title:
283 case SVGToken::Desc:
285 /// new node for Title and/or Desc
286 mpTarget = new SvgTitleDescNode(aSVGToken, maDocument, mpTarget);
287 break;
290 /// gradients
291 case SVGToken::LinearGradient:
292 case SVGToken::RadialGradient:
294 mpTarget = new SvgGradientNode(aSVGToken, maDocument, mpTarget);
295 mpTarget->parseAttributes(xAttribs);
296 break;
299 /// gradient stops
300 case SVGToken::Stop:
302 mpTarget = new SvgGradientStopNode(maDocument, mpTarget);
303 mpTarget->parseAttributes(xAttribs);
304 break;
307 /// text
308 case SVGToken::Text:
310 mpTarget = new SvgTextNode(maDocument, mpTarget);
311 mpTarget->parseAttributes(xAttribs);
312 break;
314 case SVGToken::Tspan:
316 mpTarget = new SvgTspanNode(maDocument, mpTarget);
317 mpTarget->parseAttributes(xAttribs);
318 break;
320 case SVGToken::Tref:
322 mpTarget = new SvgTrefNode(maDocument, mpTarget);
323 mpTarget->parseAttributes(xAttribs);
324 break;
326 case SVGToken::TextPath:
328 mpTarget = new SvgTextPathNode(maDocument, mpTarget);
329 mpTarget->parseAttributes(xAttribs);
330 break;
333 /// styles (as stylesheets)
334 case SVGToken::Style:
336 SvgStyleNode* pNew = new SvgStyleNode(maDocument, mpTarget);
337 mpTarget = pNew;
338 const sal_uInt32 nAttributes(xAttribs->getLength());
340 if(0 == nAttributes)
342 // #i125326# no attributes, thus also no type="text/css". This is allowed to be missing,
343 // thus do mark this style as CssStyle. This is required to read the contained
344 // text (which defines the css style)
345 pNew->setTextCss(true);
347 else
349 // #i125326# there are attributes, read them. This will set isTextCss to true if
350 // a type="text/css" is contained as exact match, else not
351 mpTarget->parseAttributes(xAttribs);
354 if(pNew->isTextCss())
356 // if it is a Css style, allow reading text between the start and end tag (see
357 // SvgDocHdl::characters for details)
358 maCssContents.emplace_back();
360 break;
363 /// structural elements clip-path and mask. Content gets scanned, but
364 /// will not be decomposed (see SvgNode::decomposeSvgNode and bReferenced)
365 case SVGToken::ClipPathNode:
367 /// new node for ClipPath
368 mpTarget = new SvgClipPathNode(maDocument, mpTarget);
369 mpTarget->parseAttributes(xAttribs);
370 break;
372 case SVGToken::Mask:
374 /// new node for Mask
375 mpTarget = new SvgMaskNode(maDocument, mpTarget);
376 mpTarget->parseAttributes(xAttribs);
377 break;
380 /// structural element marker
381 case SVGToken::Marker:
383 /// new node for marker
384 mpTarget = new SvgMarkerNode(maDocument, mpTarget);
385 mpTarget->parseAttributes(xAttribs);
386 break;
389 /// structural element pattern
390 case SVGToken::Pattern:
392 /// new node for pattern
393 mpTarget = new SvgPatternNode(maDocument, mpTarget);
394 mpTarget->parseAttributes(xAttribs);
395 break;
398 // ignore FlowRoot and child nodes
399 case SVGToken::FlowRoot:
401 bSkip = true;
402 break;
405 default:
407 /// invalid token, ignore
408 SAL_INFO( "svgio", "Unknown Base SvgToken <" + aName + "> (!)" );
409 break;
414 void SvgDocHdl::endElement( const OUString& aName )
416 if(aName.isEmpty())
417 return;
419 const SVGToken aSVGToken(StrToSVGToken(aName, false));
420 SvgNode* pWhitespaceCheck(SVGToken::Text == aSVGToken ? mpTarget : nullptr);
421 SvgStyleNode* pCssStyle(SVGToken::Style == aSVGToken ? static_cast< SvgStyleNode* >(mpTarget) : nullptr);
422 SvgTitleDescNode* pSvgTitleDescNode(SVGToken::Title == aSVGToken || SVGToken::Desc == aSVGToken ? static_cast< SvgTitleDescNode* >(mpTarget) : nullptr);
424 // if we are in skipping mode and we reach the flowRoot end tag: stop skipping mode
425 if(bSkip && aSVGToken == SVGToken::FlowRoot)
426 bSkip = false;
427 // we are in skipping mode: do nothing until we found the flowRoot end tag
428 else if(bSkip)
429 return;
431 switch (aSVGToken)
433 /// valid tokens for which a new one was created
435 /// structural elements
436 case SVGToken::Defs:
437 case SVGToken::G:
438 case SVGToken::Svg:
439 case SVGToken::Symbol:
440 case SVGToken::Use:
441 case SVGToken::A:
443 /// shape elements
444 case SVGToken::Circle:
445 case SVGToken::Ellipse:
446 case SVGToken::Line:
447 case SVGToken::Path:
448 case SVGToken::Polygon:
449 case SVGToken::Polyline:
450 case SVGToken::Rect:
451 case SVGToken::Image:
453 /// title and description
454 case SVGToken::Title:
455 case SVGToken::Desc:
457 /// gradients
458 case SVGToken::LinearGradient:
459 case SVGToken::RadialGradient:
461 /// gradient stops
462 case SVGToken::Stop:
464 /// text
465 case SVGToken::Text:
466 case SVGToken::Tspan:
467 case SVGToken::TextPath:
468 case SVGToken::Tref:
470 /// styles (as stylesheets)
471 case SVGToken::Style:
473 /// structural elements clip-path and mask
474 case SVGToken::ClipPathNode:
475 case SVGToken::Mask:
477 /// structural element marker
478 case SVGToken::Marker:
480 /// structural element pattern
481 case SVGToken::Pattern:
483 /// content handling after parsing
485 if(mpTarget)
487 if(!mpTarget->getParent())
489 // last element closing, save this tree
490 maDocument.appendNode(std::unique_ptr<SvgNode>(mpTarget));
493 mpTarget = const_cast< SvgNode* >(mpTarget->getParent());
495 else
497 OSL_ENSURE(false, "Closing token, but no context (!)");
499 break;
501 default:
503 /// invalid token, ignore
507 if(pSvgTitleDescNode && mpTarget)
509 const OUString& aText(pSvgTitleDescNode->getText());
511 if(!aText.isEmpty())
513 if(SVGToken::Title == aSVGToken)
515 mpTarget->parseAttribute(getStrTitle(), aSVGToken, aText);
517 else // if(SVGTokenDesc == aSVGToken)
519 mpTarget->parseAttribute(getStrDesc(), aSVGToken, aText);
524 if(pCssStyle && pCssStyle->isTextCss())
526 // css style parsing
527 if(!maCssContents.empty())
529 // need to interpret css styles and remember them as StyleSheets
530 // #125325# Caution! the Css content may contain block comments
531 // (see http://www.w3.org/wiki/CSS_basics#CSS_comments). These need
532 // to be removed first
533 const OUString aCommentFreeSource(removeBlockComments(*(maCssContents.end() - 1)));
535 if(aCommentFreeSource.getLength())
537 pCssStyle->addCssStyleSheet(aCommentFreeSource);
540 maCssContents.pop_back();
542 else
544 OSL_ENSURE(false, "Closing CssStyle, but no collector string on stack (!)");
548 if(pWhitespaceCheck)
550 // cleanup read strings
551 whiteSpaceHandling(pWhitespaceCheck, nullptr);
555 void SvgDocHdl::characters( const OUString& aChars )
557 const sal_uInt32 nLength(aChars.getLength());
559 if(!(mpTarget && nLength))
560 return;
562 switch(mpTarget->getType())
564 case SVGToken::Text:
565 case SVGToken::Tspan:
566 case SVGToken::TextPath:
568 const auto& rChilds = mpTarget->getChildren();
569 SvgCharacterNode* pTarget = nullptr;
571 if(!rChilds.empty())
573 pTarget = dynamic_cast< SvgCharacterNode* >(rChilds[rChilds.size() - 1].get());
576 if(pTarget)
578 // concatenate to current character span
579 pTarget->concatenate(aChars);
581 else
583 // add character span as simplified tspan (no arguments)
584 // as direct child of SvgTextNode/SvgTspanNode/SvgTextPathNode
585 new SvgCharacterNode(maDocument, mpTarget, aChars);
587 break;
589 case SVGToken::Style:
591 SvgStyleNode& rSvgStyleNode = static_cast< SvgStyleNode& >(*mpTarget);
593 if(rSvgStyleNode.isTextCss())
595 // collect characters for css style
596 if(!maCssContents.empty())
598 const OUString aTrimmedChars(aChars.trim());
600 if(!aTrimmedChars.isEmpty())
602 std::vector< OUString >::iterator aString(maCssContents.end() - 1);
603 (*aString) += aTrimmedChars;
606 else
608 OSL_ENSURE(false, "Closing CssStyle, but no collector string on stack (!)");
611 break;
613 case SVGToken::Title:
614 case SVGToken::Desc:
616 SvgTitleDescNode& rSvgTitleDescNode = static_cast< SvgTitleDescNode& >(*mpTarget);
618 // add text directly to SvgTitleDescNode
619 rSvgTitleDescNode.concatenate(aChars);
620 break;
622 default:
624 // characters not used by a known node
625 break;
630 void SvgDocHdl::ignorableWhitespace(const OUString& /*aWhitespaces*/)
634 void SvgDocHdl::processingInstruction(const OUString& /*aTarget*/, const OUString& /*aData*/)
638 void SvgDocHdl::setDocumentLocator(const uno::Reference< xml::sax::XLocator >& /*xLocator*/)
641 } // end of namespace svgio
643 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */