LibreOffice Module svgio (master) 1
svgdocumenthandler.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
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>
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>
46#include <svgfefloodnode.hxx>
47#include <svgfeimagenode.hxx>
49#include <svgfeoffsetnode.hxx>
50#include <svgfilternode.hxx>
51#include <svgmasknode.hxx>
52#include <svgmarkernode.hxx>
53#include <svgpatternnode.hxx>
54#include <svgtitledescnode.hxx>
55#include <sal/log.hxx>
56#include <osl/diagnose.h>
57#include <o3tl/string_view.hxx>
58
59using namespace com::sun::star;
60
61namespace svgio::svgreader
62{
63
64namespace
65{
67 {
68 if(pNode)
69 {
70 const auto& rChilds = pNode->getChildren();
71 const sal_uInt32 nCount(rChilds.size());
72
73 for(sal_uInt32 a(0); a < nCount; a++)
74 {
75 svgio::svgreader::SvgNode* pCandidate = rChilds[a].get();
76
77 if(pCandidate)
78 {
79 switch(pCandidate->getType())
80 {
82 {
83 // clean whitespace in text span
84 svgio::svgreader::SvgCharacterNode* pCharNode = static_cast< svgio::svgreader::SvgCharacterNode* >(pCandidate);
85
86 pCharNode->whiteSpaceHandling();
87
88 // pCharNode may have lost all text. If that's the case, ignore
89 // as invalid character node
90 // Also ignore if textBeforeSpaceHandling just have spaces
91 if(!pCharNode->getText().isEmpty() && !o3tl::trim(pCharNode->getTextBeforeSpaceHandling()).empty())
92 {
93 if(pLast)
94 {
95 bool bAddGap(true);
96
97 // Do not add a gap if last node doesn't end with a space and
98 // current note doesn't start with a space
99 const sal_uInt32 nLastLength(pLast->getTextBeforeSpaceHandling().getLength());
100 if(pLast->getTextBeforeSpaceHandling()[nLastLength - 1] != ' ' && pCharNode->getTextBeforeSpaceHandling()[0] != ' ')
101 bAddGap = false;
102
103 // With this option a baseline shift between two char parts ('words')
104 // will not add a space 'gap' to the end of the (non-last) word. This
105 // seems to be the standard behaviour, see last bugdoc attached #122524#
107 const svgio::svgreader::SvgStyleAttributes* pStyleCurrent = pCandidate->getSvgStyleAttributes();
108
109 if(pStyleLast && pStyleCurrent && pStyleLast->getBaselineShift() != pStyleCurrent->getBaselineShift())
110 {
111 bAddGap = false;
112 }
113
114 // add in-between whitespace (single space) to last
115 // known character node
116 if(bAddGap)
117 {
118 pLast->addGap();
119 }
120 }
121
122 // remember new last corrected character node
123 pLast = pCharNode;
124 }
125
126 break;
127 }
128 case SVGToken::Tspan:
130 case SVGToken::Tref:
131 {
132 // recursively clean whitespaces in subhierarchy
133 pLast = whiteSpaceHandling(pCandidate, pLast);
134 break;
135 }
136 default:
137 {
138 OSL_ENSURE(false, "Unexpected token inside SVGTokenText (!)");
139 break;
140 }
141 }
142 }
143 }
144 }
145
146 return pLast;
147 }
148} // end anonymous namespace
149
150 SvgDocHdl::SvgDocHdl(const OUString& aAbsolutePath)
151 : maDocument(aAbsolutePath),
152 mpTarget(nullptr),
153 bSkip(false)
154 {
155 }
156
158 {
159 if (mpTarget)
160 {
161 OSL_ENSURE(false, "SvgDocHdl destructed with active target (!)");
162
163 while (mpTarget->getParent())
164 mpTarget = const_cast< SvgNode* >(mpTarget->getParent());
165
166 const SvgNodeVector& rOwnedTopLevels = maDocument.getSvgNodeVector();
167 if (std::none_of(rOwnedTopLevels.begin(), rOwnedTopLevels.end(),
168 [&](std::unique_ptr<SvgNode> const & p) { return p.get() == mpTarget; }))
169 delete mpTarget;
170 }
171 OSL_ENSURE(maCssContents.empty(), "SvgDocHdl destructed with active css style stack entry (!)");
172 }
173
175 {
176 OSL_ENSURE(!mpTarget, "Already a target at document start (!)");
177 OSL_ENSURE(maCssContents.empty(), "SvgDocHdl startDocument with active css style stack entry (!)");
178 }
179
181 {
182 OSL_ENSURE(!mpTarget, "Still a target at document end (!)");
183 OSL_ENSURE(maCssContents.empty(), "SvgDocHdl endDocument with active css style stack entry (!)");
184 }
185
186 void SvgDocHdl::startElement( const OUString& aName, const uno::Reference< xml::sax::XAttributeList >& xAttribs )
187 {
188 if (bSkip)
189 return;
190 if(aName.isEmpty())
191 return;
192
193 const SVGToken aSVGToken(StrToSVGToken(aName, false));
194
195 switch (aSVGToken)
196 {
198 case SVGToken::Symbol:
199 {
203 mpTarget->parseAttributes(xAttribs);
204 break;
205 }
206 case SVGToken::Defs:
207 case SVGToken::G:
208 {
210 mpTarget = new SvgGNode(aSVGToken, maDocument, mpTarget);
211 mpTarget->parseAttributes(xAttribs);
212 break;
213 }
214 case SVGToken::Svg:
215 {
218 mpTarget->parseAttributes(xAttribs);
219 break;
220 }
221 case SVGToken::Use:
222 {
225 mpTarget->parseAttributes(xAttribs);
226 break;
227 }
228 case SVGToken::A:
229 {
232 mpTarget->parseAttributes(xAttribs);
233 break;
234 }
235
237 case SVGToken::Circle:
238 {
241 mpTarget->parseAttributes(xAttribs);
242 break;
243 }
245 {
248 mpTarget->parseAttributes(xAttribs);
249 break;
250 }
251 case SVGToken::Line:
252 {
255 mpTarget->parseAttributes(xAttribs);
256 break;
257 }
258 case SVGToken::Path:
259 {
262 mpTarget->parseAttributes(xAttribs);
263 break;
264 }
266 {
268 mpTarget = new SvgPolyNode(aSVGToken, maDocument, mpTarget);
269 mpTarget->parseAttributes(xAttribs);
270 break;
271 }
273 {
275 mpTarget = new SvgPolyNode(aSVGToken, maDocument, mpTarget);
276 mpTarget->parseAttributes(xAttribs);
277 break;
278 }
279 case SVGToken::Rect:
280 {
283 mpTarget->parseAttributes(xAttribs);
284 break;
285 }
286 case SVGToken::Image:
287 {
290 mpTarget->parseAttributes(xAttribs);
291 break;
292 }
293
295 case SVGToken::Title:
296 case SVGToken::Desc:
297 {
299 mpTarget = new SvgTitleDescNode(aSVGToken, maDocument, mpTarget);
300 break;
301 }
302
306 {
307 mpTarget = new SvgGradientNode(aSVGToken, maDocument, mpTarget);
308 mpTarget->parseAttributes(xAttribs);
309 break;
310 }
311
313 case SVGToken::Stop:
314 {
316 mpTarget->parseAttributes(xAttribs);
317 break;
318 }
319
321 case SVGToken::Text:
322 {
324 mpTarget->parseAttributes(xAttribs);
325 break;
326 }
327 case SVGToken::Tspan:
328 {
330 mpTarget->parseAttributes(xAttribs);
331 break;
332 }
333 case SVGToken::Tref:
334 {
336 mpTarget->parseAttributes(xAttribs);
337 break;
338 }
340 {
342 mpTarget->parseAttributes(xAttribs);
343 break;
344 }
345
347 case SVGToken::Style:
348 {
350 mpTarget = pNew;
351
352 // #i125326# there are attributes, read them. This will set isTextCss to false if
353 // type attribute is different to "text/css"
354 mpTarget->parseAttributes(xAttribs);
355
356 if(pNew->isTextCss())
357 {
358 // if it is a Css style, allow reading text between the start and end tag (see
359 // SvgDocHdl::characters for details)
360 maCssContents.emplace_back();
361 }
362 break;
363 }
364
368 {
371 mpTarget->parseAttributes(xAttribs);
372 break;
373 }
374 case SVGToken::Mask:
375 {
378 mpTarget->parseAttributes(xAttribs);
379 break;
380 }
382 {
385 mpTarget->parseAttributes(xAttribs);
386 break;
387 }
389 {
392 mpTarget->parseAttributes(xAttribs);
393 break;
394 }
396 {
399 mpTarget->parseAttributes(xAttribs);
400 break;
401 }
403 {
406 mpTarget->parseAttributes(xAttribs);
407 break;
408 }
410 {
413 mpTarget->parseAttributes(xAttribs);
414 break;
415 }
417 {
420 mpTarget->parseAttributes(xAttribs);
421 break;
422 }
423 case SVGToken::Filter:
424 {
426 mpTarget = new SvgFilterNode(aSVGToken, maDocument, mpTarget);
427 mpTarget->parseAttributes(xAttribs);
428 break;
429 }
430
432 case SVGToken::Marker:
433 {
436 mpTarget->parseAttributes(xAttribs);
437 break;
438 }
439
442 {
445 mpTarget->parseAttributes(xAttribs);
446 break;
447 }
448
449 // ignore FlowRoot and child nodes
451 {
452 bSkip = true;
453 break;
454 }
455
456 default:
457 {
459 break;
460 }
461 }
462 }
463
464 void SvgDocHdl::endElement( const OUString& aName )
465 {
466 if(aName.isEmpty())
467 return;
468
469 const SVGToken aSVGToken(StrToSVGToken(aName, false));
470 SvgNode* pWhitespaceCheck(SVGToken::Text == aSVGToken ? mpTarget : nullptr);
471 SvgStyleNode* pCssStyle(SVGToken::Style == aSVGToken ? static_cast< SvgStyleNode* >(mpTarget) : nullptr);
472 SvgTitleDescNode* pSvgTitleDescNode(SVGToken::Title == aSVGToken || SVGToken::Desc == aSVGToken ? static_cast< SvgTitleDescNode* >(mpTarget) : nullptr);
473
474 // if we are in skipping mode and we reach the flowRoot end tag: stop skipping mode
475 if(bSkip && aSVGToken == SVGToken::FlowRoot)
476 bSkip = false;
477 // we are in skipping mode: do nothing until we found the flowRoot end tag
478 else if(bSkip)
479 return;
480
481 switch (aSVGToken)
482 {
484
486 case SVGToken::Defs:
487 case SVGToken::G:
488 case SVGToken::Svg:
489 case SVGToken::Symbol:
490 case SVGToken::Use:
491 case SVGToken::A:
492
494 case SVGToken::Circle:
496 case SVGToken::Line:
497 case SVGToken::Path:
500 case SVGToken::Rect:
501 case SVGToken::Image:
502
504 case SVGToken::Title:
505 case SVGToken::Desc:
506
510
512 case SVGToken::Stop:
513
515 case SVGToken::Text:
516 case SVGToken::Tspan:
518 case SVGToken::Tref:
519
521 case SVGToken::Style:
522
525 case SVGToken::Mask:
526
534 case SVGToken::Filter:
535
537 case SVGToken::Marker:
538
541
542 default:
543
545 {
546 if(mpTarget)
547 {
548 if(!mpTarget->getParent())
549 {
550 // last element closing, save this tree
551 maDocument.appendNode(std::unique_ptr<SvgNode>(mpTarget));
552 }
553
554 mpTarget = const_cast< SvgNode* >(mpTarget->getParent());
555 }
556 else
557 {
558 OSL_ENSURE(false, "Closing token, but no context (!)");
559 }
560 break;
561 }
562 }
563
564 if(pSvgTitleDescNode && mpTarget)
565 {
566 const OUString& aText(pSvgTitleDescNode->getText());
567
568 if(!aText.isEmpty())
569 {
570 mpTarget->parseAttribute(SVGTokenToStr(aSVGToken), aSVGToken, aText);
571 }
572 }
573
574 if(pCssStyle && pCssStyle->isTextCss())
575 {
576 // css style parsing
577 if(!maCssContents.empty())
578 {
579 // need to interpret css styles and remember them as StyleSheets
580 // #125325# Caution! the Css content may contain block comments
581 // (see http://www.w3.org/wiki/CSS_basics#CSS_comments). These need
582 // to be removed first
583 const OUString aCommentFreeSource(removeBlockComments(*(maCssContents.end() - 1)));
584
585 if(aCommentFreeSource.getLength())
586 {
587 pCssStyle->addCssStyleSheet(aCommentFreeSource);
588 }
589
590 maCssContents.pop_back();
591 }
592 else
593 {
594 OSL_ENSURE(false, "Closing CssStyle, but no collector string on stack (!)");
595 }
596 }
597
598 if(pWhitespaceCheck)
599 {
600 // cleanup read strings
601 whiteSpaceHandling(pWhitespaceCheck, nullptr);
602 }
603 }
604
605 void SvgDocHdl::characters( const OUString& aChars )
606 {
607 const sal_uInt32 nLength(aChars.getLength());
608
609 if(!(mpTarget && nLength))
610 return;
611
612 switch(mpTarget->getType())
613 {
614 case SVGToken::Text:
615 case SVGToken::Tspan:
617 {
618 const auto& rChilds = mpTarget->getChildren();
619 SvgCharacterNode* pTarget = nullptr;
620
621 if(!rChilds.empty())
622 {
623 pTarget = dynamic_cast< SvgCharacterNode* >(rChilds[rChilds.size() - 1].get());
624 }
625
626 if(pTarget)
627 {
628 // concatenate to current character span
629 pTarget->concatenate(aChars);
630 }
631 else
632 {
633 // add character span as simplified tspan (no arguments)
634 // as direct child of SvgTextNode/SvgTspanNode/SvgTextPathNode
636 }
637 break;
638 }
639 case SVGToken::Style:
640 {
641 SvgStyleNode& rSvgStyleNode = static_cast< SvgStyleNode& >(*mpTarget);
642
643 if(rSvgStyleNode.isTextCss())
644 {
645 // collect characters for css style
646 if(!maCssContents.empty())
647 {
648 const OUString aTrimmedChars(aChars.trim());
649
650 if(!aTrimmedChars.isEmpty())
651 {
652 std::vector< OUString >::iterator aString(maCssContents.end() - 1);
653 (*aString) += aTrimmedChars;
654 }
655 }
656 else
657 {
658 OSL_ENSURE(false, "Closing CssStyle, but no collector string on stack (!)");
659 }
660 }
661 break;
662 }
663 case SVGToken::Title:
664 case SVGToken::Desc:
665 {
666 SvgTitleDescNode& rSvgTitleDescNode = static_cast< SvgTitleDescNode& >(*mpTarget);
667
668 // add text directly to SvgTitleDescNode
669 rSvgTitleDescNode.concatenate(aChars);
670 break;
671 }
672 default:
673 {
674 // characters not used by a known node
675 break;
676 }
677 }
678 }
679
680 void SvgDocHdl::ignorableWhitespace(const OUString& /*aWhitespaces*/)
681 {
682 }
683
684 void SvgDocHdl::processingInstruction(const OUString& /*aTarget*/, const OUString& /*aData*/)
685 {
686 }
687
688 void SvgDocHdl::setDocumentLocator(const uno::Reference< xml::sax::XLocator >& /*xLocator*/)
689 {
690 }
691} // end of namespace svgio
692
693/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
void concatenate(std::u16string_view rText)
const OUString & getTextBeforeSpaceHandling() const
virtual const SvgStyleAttributes * getSvgStyleAttributes() const override
const OUString & getText() const
Text content.
SvgDocHdl(const OUString &rAbsolutePath)
virtual void SAL_CALL setDocumentLocator(const css::uno::Reference< css::xml::sax::XLocator > &xLocator) override
virtual void SAL_CALL ignorableWhitespace(const OUString &aWhitespaces) override
virtual void SAL_CALL endElement(const OUString &aName) override
virtual void SAL_CALL endDocument() override
virtual void SAL_CALL startElement(const OUString &aName, const css::uno::Reference< css::xml::sax::XAttributeList > &xAttribs) override
virtual void SAL_CALL characters(const OUString &aChars) override
virtual void SAL_CALL processingInstruction(const OUString &aTarget, const OUString &aData) override
virtual void SAL_CALL startDocument() override
std::vector< OUString > maCssContents
void appendNode(std::unique_ptr< SvgNode > pNode)
append another root node, ownership changes
Definition: svgdocument.cxx:34
const SvgNodeVector & getSvgNodeVector() const
data read access
Definition: svgdocument.hxx:76
void parseAttributes(const css::uno::Reference< css::xml::sax::XAttributeList > &xAttribs)
style helpers
Definition: svgnode.cxx:436
const std::vector< std::unique_ptr< SvgNode > > & getChildren() const
Definition: svgnode.hxx:159
SVGToken getType() const
basic data read access
Definition: svgnode.hxx:156
const SvgNode * getParent() const
Definition: svgnode.hxx:158
virtual void parseAttribute(const OUString &rTokenName, SVGToken aSVGToken, const OUString &aContent)
Definition: svgnode.cxx:534
virtual const SvgStyleAttributes * getSvgStyleAttributes() const
Definition: svgnode.cxx:37
void addCssStyleSheet(std::u16string_view aSelectors, const SvgStyleAttributes &rNewStyle)
CssStyleSheet add helpers.
bool isTextCss() const
textCss access
const OUString & getText() const
x content, set if found in current context
void concatenate(std::u16string_view rChars)
add new chars
int nCount
OUString aName
void * p
uno_Any a
std::basic_string_view< charT, traits > trim(std::basic_string_view< charT, traits > str)
OUString SVGTokenToStr(const SVGToken &rToken)
Definition: svgtoken.cxx:369
SVGToken StrToSVGToken(const OUString &rStr, bool bCaseIndependent)
Definition: svgtoken.cxx:343
OUString removeBlockComments(const OUString &rCandidate)
Definition: svgtools.cxx:1402
std::vector< std::unique_ptr< SvgNode > > SvgNodeVector
Definition: svgdocument.hxx:29
T * mpTarget
sal_Int32 nLength