1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 // MARKER(update_precomp.py): autogen include statement, do not remove
23 #include "precompiled_svgio.hxx"
24 
25 #include <svgio/svgreader/svgdocumenthandler.hxx>
26 #include <svgio/svgreader/svgtoken.hxx>
27 #include <svgio/svgreader/svgsvgnode.hxx>
28 #include <svgio/svgreader/svggnode.hxx>
29 #include <svgio/svgreader/svgnode.hxx>
30 #include <svgio/svgreader/svgpathnode.hxx>
31 #include <svgio/svgreader/svgrectnode.hxx>
32 #include <svgio/svgreader/svggradientnode.hxx>
33 #include <svgio/svgreader/svggradientstopnode.hxx>
34 #include <svgio/svgreader/svgsymbolnode.hxx>
35 #include <svgio/svgreader/svgusenode.hxx>
36 #include <svgio/svgreader/svgcirclenode.hxx>
37 #include <svgio/svgreader/svgellipsenode.hxx>
38 #include <svgio/svgreader/svglinenode.hxx>
39 #include <svgio/svgreader/svgpolynode.hxx>
40 #include <svgio/svgreader/svgsymbolnode.hxx>
41 #include <svgio/svgreader/svgtextnode.hxx>
42 #include <svgio/svgreader/svgcharacternode.hxx>
43 #include <svgio/svgreader/svgtspannode.hxx>
44 #include <svgio/svgreader/svgtrefnode.hxx>
45 #include <svgio/svgreader/svgtextpathnode.hxx>
46 #include <svgio/svgreader/svgstylenode.hxx>
47 #include <svgio/svgreader/svgimagenode.hxx>
48 #include <svgio/svgreader/svgclippathnode.hxx>
49 #include <svgio/svgreader/svgmasknode.hxx>
50 #include <svgio/svgreader/svgmarkernode.hxx>
51 #include <svgio/svgreader/svgpatternnode.hxx>
52 #include <svgio/svgreader/svgtitledescnode.hxx>
53 
54 //////////////////////////////////////////////////////////////////////////////
55 
56 using namespace com::sun::star;
57 
58 //////////////////////////////////////////////////////////////////////////////
59 
60 namespace
61 {
62     svgio::svgreader::SvgCharacterNode* whiteSpaceHandling(svgio::svgreader::SvgNode* pNode, svgio::svgreader::SvgCharacterNode* pLast)
63     {
64         if(pNode)
65         {
66             const svgio::svgreader::SvgNodeVector& rChilds = pNode->getChildren();
67             const sal_uInt32 nCount(rChilds.size());
68 
69             for(sal_uInt32 a(0); a < nCount; a++)
70             {
71                 svgio::svgreader::SvgNode* pCandidate = rChilds[a];
72 
73                 if(pCandidate)
74                 {
75                     switch(pCandidate->getType())
76                     {
77                         case svgio::svgreader::SVGTokenCharacter:
78                         {
79                             // clean whitespace in text span
80                             svgio::svgreader::SvgCharacterNode* pCharNode = static_cast< svgio::svgreader::SvgCharacterNode* >(pCandidate);
81                             pCharNode->whiteSpaceHandling();
82 
83                             // pCharNode may have lost all text. If that's the case, ignore
84                             // as invalid character node
85                             if(pCharNode->getText().getLength())
86                             {
87                                 if(pLast)
88                                 {
89                                     bool bAddGap(true);
90                                     static bool bNoGapsForBaselineShift(true);
91 
92                                     if(bNoGapsForBaselineShift)
93                                     {
94                                         // With this option a baseline shift between two char parts ('words')
95                                         // will not add a space 'gap' to the end of the (non-last) word. This
96                                         // seems to be the standard behaviour, see last bugdoc attached #122524#
97                                         const svgio::svgreader::SvgStyleAttributes* pStyleLast = pLast->getSvgStyleAttributes();
98                                         const svgio::svgreader::SvgStyleAttributes* pStyleCurrent = pCandidate->getSvgStyleAttributes();
99 
100                                         if(pStyleLast && pStyleCurrent && pStyleLast->getBaselineShift() != pStyleCurrent->getBaselineShift())
101                                         {
102                                             bAddGap = false;
103                                         }
104                                     }
105 
106                                     // add in-between whitespace (single space) to last
107                                     // known character node
108                                     if(bAddGap)
109                                     {
110                                         pLast->addGap();
111                                     }
112                                 }
113 
114                                 // remember new last corected character node
115                                 pLast = pCharNode;
116                             }
117                             break;
118                         }
119                         case svgio::svgreader::SVGTokenTspan:
120                         case svgio::svgreader::SVGTokenTextPath:
121                         case svgio::svgreader::SVGTokenTref:
122                         {
123                             // recursively clean whitespaces in subhierarchy
124                             pLast = whiteSpaceHandling(pCandidate, pLast);
125                             break;
126                         }
127                         default:
128                         {
129                             OSL_ENSURE(false, "Unexpected token inside SVGTokenText (!)");
130                             break;
131                         }
132                     }
133                 }
134             }
135         }
136 
137         return pLast;
138     }
139 }
140 
141 //////////////////////////////////////////////////////////////////////////////
142 
143 namespace svgio
144 {
145     namespace svgreader
146     {
147         SvgDocHdl::SvgDocHdl(const rtl::OUString& aAbsolutePath)
148         :   maDocument(aAbsolutePath),
149             mpTarget(0),
150             maCssContents()
151         {
152         }
153 
154         SvgDocHdl::~SvgDocHdl()
155         {
156 #ifdef DBG_UTIL
157             if(mpTarget)
158             {
159                 OSL_ENSURE(false, "SvgDocHdl destructed with active target (!)");
160                 delete mpTarget;
161             }
162             OSL_ENSURE(!maCssContents.size(), "SvgDocHdl destructed with active css style stack entry (!)");
163 #endif
164         }
165 
166         void SvgDocHdl::startDocument(  ) throw (xml::sax::SAXException, uno::RuntimeException)
167         {
168             OSL_ENSURE(!mpTarget, "Already a target at document start (!)");
169             OSL_ENSURE(!maCssContents.size(), "SvgDocHdl startDocument with active css style stack entry (!)");
170         }
171 
172         void SvgDocHdl::endDocument(  ) throw (xml::sax::SAXException, uno::RuntimeException)
173         {
174             OSL_ENSURE(!mpTarget, "Still a target at document end (!)");
175             OSL_ENSURE(!maCssContents.size(), "SvgDocHdl endDocument with active css style stack entry (!)");
176         }
177 
178         void SvgDocHdl::startElement( const ::rtl::OUString& aName, const uno::Reference< xml::sax::XAttributeList >& xAttribs ) throw (xml::sax::SAXException, uno::RuntimeException)
179         {
180             if(aName.getLength())
181             {
182                 const SVGToken aSVGToken(StrToSVGToken(aName, false));
183 
184                 switch(aSVGToken)
185                 {
186                     /// structural elements
187                     case SVGTokenSymbol:
188                     {
189                         /// new basic node for Symbol. Content gets scanned, but
190                         /// will not be decomposed (see SvgNode::decomposeSvgNode and bReferenced)
191                         mpTarget = new SvgSymbolNode(maDocument, mpTarget);
192                         mpTarget->parseAttributes(xAttribs);
193                         break;
194                     }
195                     case SVGTokenDefs:
196                     case SVGTokenG:
197                     {
198                         /// new node for Defs/G
199                         mpTarget = new SvgGNode(aSVGToken, maDocument, mpTarget);
200                         mpTarget->parseAttributes(xAttribs);
201                         break;
202                     }
203                     case SVGTokenSvg:
204                     {
205                         /// new node for Svg
206                         mpTarget = new SvgSvgNode(maDocument, mpTarget);
207                         mpTarget->parseAttributes(xAttribs);
208                         break;
209                     }
210                     case SVGTokenUse:
211                     {
212                         /// new node for Use
213                         mpTarget = new SvgUseNode(maDocument, mpTarget);
214                         mpTarget->parseAttributes(xAttribs);
215                         break;
216                     }
217 
218                     /// shape elements
219                     case SVGTokenCircle:
220                     {
221                         /// new node for Circle
222                         mpTarget = new SvgCircleNode(maDocument, mpTarget);
223                         mpTarget->parseAttributes(xAttribs);
224                         break;
225                     }
226                     case SVGTokenEllipse:
227                     {
228                         /// new node for Ellipse
229                         mpTarget = new SvgEllipseNode(maDocument, mpTarget);
230                         mpTarget->parseAttributes(xAttribs);
231                         break;
232                     }
233                     case SVGTokenLine:
234                     {
235                         /// new node for Line
236                         mpTarget = new SvgLineNode(maDocument, mpTarget);
237                         mpTarget->parseAttributes(xAttribs);
238                         break;
239                     }
240                     case SVGTokenPath:
241                     {
242                         /// new node for Path
243                         mpTarget = new SvgPathNode(maDocument, mpTarget);
244                         mpTarget->parseAttributes(xAttribs);
245                         break;
246                     }
247                     case SVGTokenPolygon:
248                     {
249                         /// new node for Polygon
250                         mpTarget = new SvgPolyNode(maDocument, mpTarget, false);
251                         mpTarget->parseAttributes(xAttribs);
252                         break;
253                     }
254                     case SVGTokenPolyline:
255                     {
256                         /// new node for Polyline
257                         mpTarget = new SvgPolyNode(maDocument, mpTarget, true);
258                         mpTarget->parseAttributes(xAttribs);
259                         break;
260                     }
261                     case SVGTokenRect:
262                     {
263                         /// new node for Rect
264                         mpTarget = new SvgRectNode(maDocument, mpTarget);
265                         mpTarget->parseAttributes(xAttribs);
266                         break;
267                     }
268                     case SVGTokenImage:
269                     {
270                         /// new node for Image
271                         mpTarget = new SvgImageNode(maDocument, mpTarget);
272                         mpTarget->parseAttributes(xAttribs);
273                         break;
274                     }
275 
276                     /// title and description
277                     case SVGTokenTitle:
278                     case SVGTokenDesc:
279                     {
280                         /// new node for Title and/or Desc
281                         mpTarget = new SvgTitleDescNode(aSVGToken, maDocument, mpTarget);
282                         break;
283                     }
284 
285                     /// gradients
286                     case SVGTokenLinearGradient:
287                     case SVGTokenRadialGradient:
288                     {
289                         mpTarget = new SvgGradientNode(aSVGToken, maDocument, mpTarget);
290                         mpTarget->parseAttributes(xAttribs);
291                         break;
292                     }
293 
294                     /// gradient stops
295                     case SVGTokenStop:
296                     {
297                         mpTarget = new SvgGradientStopNode(maDocument, mpTarget);
298                         mpTarget->parseAttributes(xAttribs);
299                         break;
300                     }
301 
302                     /// text
303                     case SVGTokenText:
304                     {
305                         mpTarget = new SvgTextNode(maDocument, mpTarget);
306                         mpTarget->parseAttributes(xAttribs);
307                         break;
308                     }
309                     case SVGTokenTspan:
310                     {
311                         mpTarget = new SvgTspanNode(maDocument, mpTarget);
312                         mpTarget->parseAttributes(xAttribs);
313                         break;
314                     }
315                     case SVGTokenTref:
316                     {
317                         mpTarget = new SvgTrefNode(maDocument, mpTarget);
318                         mpTarget->parseAttributes(xAttribs);
319                         break;
320                     }
321                     case SVGTokenTextPath:
322                     {
323                         mpTarget = new SvgTextPathNode(maDocument, mpTarget);
324                         mpTarget->parseAttributes(xAttribs);
325                         break;
326                     }
327 
328                     /// styles (as stylesheets)
329                     case SVGTokenStyle:
330                     {
331                         SvgStyleNode* pNew = new SvgStyleNode(maDocument, mpTarget);
332                         mpTarget = pNew;
333                         const sal_uInt32 nAttributes(xAttribs->getLength());
334 
335                         if(0 == nAttributes)
336                         {
337                             // #125326# no attributes, thus also no type="text/css". This is allowed to be missing,
338                             // thus do mark this style as CssStyle. This is required to read the contained
339                             // text (which defines the css style)
340                             pNew->setTextCss(true);
341                         }
342                         else
343                         {
344                             // #125326# there are attributes, read them. This will set isTextCss to true if
345                             // a type="text/css" is contained as exact match, else not
346                             mpTarget->parseAttributes(xAttribs);
347                         }
348 
349                         if(pNew->isTextCss())
350                         {
351                             // if it is a Css style, allow reading text between the start and end tag (see
352                             // SvgDocHdl::characters for details)
353                             maCssContents.push_back(rtl::OUString());
354                         }
355                         break;
356                     }
357 
358                     /// structural elements clip-path and mask. Content gets scanned, but
359                     /// will not be decomposed (see SvgNode::decomposeSvgNode and bReferenced)
360                     case SVGTokenClipPathNode:
361                     {
362                         /// new node for ClipPath
363                         mpTarget = new SvgClipPathNode(maDocument, mpTarget);
364                         mpTarget->parseAttributes(xAttribs);
365                         break;
366                     }
367                     case SVGTokenMask:
368                     {
369                         /// new node for Mask
370                         mpTarget = new SvgMaskNode(maDocument, mpTarget);
371                         mpTarget->parseAttributes(xAttribs);
372                         break;
373                     }
374 
375                     /// structural element marker
376                     case SVGTokenMarker:
377                     {
378                         /// new node for marker
379                         mpTarget = new SvgMarkerNode(maDocument, mpTarget);
380                         mpTarget->parseAttributes(xAttribs);
381                         break;
382                     }
383 
384                     /// structural element pattern
385                     case SVGTokenPattern:
386                     {
387                         /// new node for pattern
388                         mpTarget = new SvgPatternNode(maDocument, mpTarget);
389                         mpTarget->parseAttributes(xAttribs);
390                         break;
391                     }
392 
393                     default:
394                     {
395                         /// invalid token, ignore
396 #ifdef DBG_UTIL
397                         myAssert(
398                             rtl::OUString::createFromAscii("Unknown Base SvgToken <") +
399                             aName +
400                             rtl::OUString::createFromAscii("> (!)"));
401 #endif
402                         break;
403                     }
404                 }
405             }
406         }
407 
408         void SvgDocHdl::endElement( const ::rtl::OUString& aName ) throw (xml::sax::SAXException, uno::RuntimeException)
409         {
410             if(aName.getLength())
411             {
412                 const SVGToken aSVGToken(StrToSVGToken(aName, false));
413                 SvgNode* pWhitespaceCheck(SVGTokenText == aSVGToken ? mpTarget : 0);
414                 SvgStyleNode* pCssStyle(SVGTokenStyle == aSVGToken ? static_cast< SvgStyleNode* >(mpTarget) : 0);
415                 SvgTitleDescNode* pSvgTitleDescNode(SVGTokenTitle == aSVGToken || SVGTokenDesc == aSVGToken ? static_cast< SvgTitleDescNode* >(mpTarget) : 0);
416 
417                 switch(aSVGToken)
418                 {
419                     /// valid tokens for which a new one was created
420 
421                     /// structural elements
422                     case SVGTokenDefs:
423                     case SVGTokenG:
424                     case SVGTokenSvg:
425                     case SVGTokenSymbol:
426                     case SVGTokenUse:
427 
428                     /// shape elements
429                     case SVGTokenCircle:
430                     case SVGTokenEllipse:
431                     case SVGTokenLine:
432                     case SVGTokenPath:
433                     case SVGTokenPolygon:
434                     case SVGTokenPolyline:
435                     case SVGTokenRect:
436                     case SVGTokenImage:
437 
438                     /// title and description
439                     case SVGTokenTitle:
440                     case SVGTokenDesc:
441 
442                     /// gradients
443                     case SVGTokenLinearGradient:
444                     case SVGTokenRadialGradient:
445 
446                     /// gradient stops
447                     case SVGTokenStop:
448 
449                     /// text
450                     case SVGTokenText:
451                     case SVGTokenTspan:
452                     case SVGTokenTextPath:
453                     case SVGTokenTref:
454 
455                     /// styles (as stylesheets)
456                     case SVGTokenStyle:
457 
458                     /// structural elements clip-path and mask
459                     case SVGTokenClipPathNode:
460                     case SVGTokenMask:
461 
462                     /// structural element marker
463                     case SVGTokenMarker:
464 
465                     /// structural element pattern
466                     case SVGTokenPattern:
467 
468                     /// content handling after parsing
469                     {
470                         if(mpTarget)
471                         {
472                             if(!mpTarget->getParent())
473                             {
474                                 // last element closing, save this tree
475                                 maDocument.appendNode(mpTarget);
476                             }
477 
478                             mpTarget = const_cast< SvgNode* >(mpTarget->getParent());
479                         }
480                         else
481                         {
482                             OSL_ENSURE(false, "Closing token, but no context (!)");
483                         }
484                         break;
485                     }
486                     default:
487                     {
488                         /// invalid token, ignore
489                     }
490                 }
491 
492                 if(pSvgTitleDescNode && mpTarget)
493                 {
494                     const rtl::OUString aText(pSvgTitleDescNode->getText());
495 
496                     if(aText.getLength())
497                     {
498                         if(SVGTokenTitle == aSVGToken)
499                         {
500                             mpTarget->parseAttribute(getStrTitle(), aSVGToken, aText);
501                         }
502                         else // if(SVGTokenDesc == aSVGToken)
503                         {
504                             mpTarget->parseAttribute(getStrDesc(), aSVGToken, aText);
505                         }
506                     }
507                 }
508 
509                 if(pCssStyle && pCssStyle->isTextCss())
510                 {
511                     // css style parsing
512                     if(maCssContents.size())
513                     {
514                         // need to interpret css styles and remember them as StyleSheets
515                         pCssStyle->addCssStyleSheet(*(maCssContents.end() - 1));
516                         maCssContents.pop_back();
517                     }
518                     else
519                     {
520                         OSL_ENSURE(false, "Closing CssStyle, but no collector string on stack (!)");
521                     }
522                 }
523 
524                 if(pWhitespaceCheck)
525                 {
526                     // cleanup read strings
527                     whiteSpaceHandling(pWhitespaceCheck, 0);
528                 }
529             }
530         }
531 
532         void SvgDocHdl::characters( const ::rtl::OUString& aChars ) throw (xml::sax::SAXException, uno::RuntimeException)
533         {
534             const sal_uInt32 nLength(aChars.getLength());
535 
536             if(mpTarget && nLength)
537             {
538                 switch(mpTarget->getType())
539                 {
540                     case SVGTokenText:
541                     case SVGTokenTspan:
542                     case SVGTokenTextPath:
543                     {
544                         const SvgNodeVector& rChilds = mpTarget->getChildren();
545                         SvgCharacterNode* pTarget = 0;
546 
547                         if(rChilds.size())
548                         {
549                             pTarget = dynamic_cast< SvgCharacterNode* >(rChilds[rChilds.size() - 1]);
550                         }
551 
552                         if(pTarget)
553                         {
554                             // concatenate to current character span
555                             pTarget->concatenate(aChars);
556                         }
557                         else
558                         {
559                             // add character span as simplified tspan (no arguments)
560                             // as direct child of SvgTextNode/SvgTspanNode/SvgTextPathNode
561                             new SvgCharacterNode(maDocument, mpTarget, aChars);
562                         }
563                         break;
564                     }
565                     case SVGTokenStyle:
566                     {
567                         SvgStyleNode& rSvgStyleNode = static_cast< SvgStyleNode& >(*mpTarget);
568 
569                         if(rSvgStyleNode.isTextCss())
570                         {
571                             // collect characters for css style
572                             if(maCssContents.size())
573                             {
574                                 const ::rtl::OUString aTrimmedChars(aChars.trim());
575 
576                                 if(aTrimmedChars.getLength())
577                                 {
578                                     std::vector< rtl::OUString >::iterator aString(maCssContents.end() - 1);
579                                     (*aString) += aTrimmedChars;
580                                 }
581                             }
582                             else
583                             {
584                                 OSL_ENSURE(false, "Closing CssStyle, but no collector string on stack (!)");
585                             }
586                         }
587                         break;
588                     }
589                     case SVGTokenTitle:
590                     case SVGTokenDesc:
591                     {
592                         SvgTitleDescNode& rSvgTitleDescNode = static_cast< SvgTitleDescNode& >(*mpTarget);
593 
594                         // add text directly to SvgTitleDescNode
595                         rSvgTitleDescNode.concatenate(aChars);
596                         break;
597                     }
598                     default:
599                     {
600                         // characters not used by a known node
601                         break;
602                     }
603                 }
604             }
605         }
606 
607         void SvgDocHdl::ignorableWhitespace(const ::rtl::OUString& /*aWhitespaces*/) throw (xml::sax::SAXException, uno::RuntimeException)
608         {
609         }
610 
611         void SvgDocHdl::processingInstruction(const ::rtl::OUString& /*aTarget*/, const ::rtl::OUString& /*aData*/) throw (xml::sax::SAXException, uno::RuntimeException)
612         {
613         }
614 
615         void SvgDocHdl::setDocumentLocator(const uno::Reference< xml::sax::XLocator >& /*xLocator*/) throw (xml::sax::SAXException, uno::RuntimeException)
616         {
617         }
618     } // end of namespace svgreader
619 } // end of namespace svgio
620 
621 //////////////////////////////////////////////////////////////////////////////
622 // eof
623