Line data Source code
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 :
20 : #include <basegfx/polygon/b2dpolypolygontools.hxx>
21 : #include <svgio/svgreader/svgdocument.hxx>
22 : #include <svgio/svgreader/svgnode.hxx>
23 : #include <svgio/svgreader/svgstyleattributes.hxx>
24 : #include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx>
25 : #include <tools/urlobj.hxx>
26 :
27 :
28 : namespace svgio
29 : {
30 : namespace svgreader
31 : {
32 : /// #i125258#
33 135930 : bool SvgNode::supportsParentStyle() const
34 : {
35 135930 : return true;
36 : }
37 :
38 391 : const SvgStyleAttributes* SvgNode::getSvgStyleAttributes() const
39 : {
40 391 : return 0;
41 : }
42 :
43 10060 : void SvgNode::fillCssStyleVectorUsingHierarchyAndSelectors(
44 : const OUString& rClassStr,
45 : const SvgNode& rCurrent,
46 : const OUString& aConcatenated)
47 : {
48 10060 : const SvgDocument& rDocument = getDocument();
49 :
50 10060 : if(rDocument.hasGlobalCssStyleAttributes())
51 : {
52 8 : const SvgNode* pParent = rCurrent.getParent();
53 :
54 : // check for ID (highest priority)
55 8 : if(rCurrent.getId())
56 : {
57 0 : const OUString& rId = *rCurrent.getId();
58 :
59 0 : if(rId.getLength())
60 : {
61 : const OUString aNewConcatenated(
62 0 : "#" + rId + aConcatenated);
63 :
64 0 : if(pParent)
65 : {
66 : // check for combined selectors at parent firstso that higher specificity will be in front
67 0 : fillCssStyleVectorUsingHierarchyAndSelectors(rClassStr, *pParent, aNewConcatenated);
68 : }
69 :
70 0 : const SvgStyleAttributes* pNew = rDocument.findGlobalCssStyleAttributes(aNewConcatenated);
71 :
72 0 : if(pNew)
73 : {
74 : // add CssStyle if found
75 0 : maCssStyleVector.push_back(pNew);
76 0 : }
77 : }
78 : }
79 :
80 : // check for 'class' references (a list of entries is allowed)
81 8 : if(rCurrent.getClass())
82 : {
83 0 : const OUString& rClassList = *rCurrent.getClass();
84 0 : const sal_Int32 nLen(rClassList.getLength());
85 :
86 0 : if(nLen)
87 : {
88 0 : std::vector< OUString > aParts;
89 0 : sal_Int32 nPos(0);
90 0 : OUStringBuffer aToken;
91 :
92 0 : while(nPos < nLen)
93 : {
94 0 : const sal_Int32 nInitPos(nPos);
95 0 : copyToLimiter(rClassList, sal_Unicode(' '), nPos, aToken, nLen);
96 0 : skip_char(rClassList, sal_Unicode(' '), nPos, nLen);
97 0 : const OUString aPart(aToken.makeStringAndClear().trim());
98 :
99 0 : if(aPart.getLength())
100 : {
101 0 : aParts.push_back(aPart);
102 : }
103 :
104 0 : if(nInitPos == nPos)
105 : {
106 : OSL_ENSURE(false, "Could not interpret on current position (!)");
107 0 : nPos++;
108 : }
109 0 : }
110 :
111 0 : for(size_t a(0); a < aParts.size(); a++)
112 : {
113 : const OUString aNewConcatenated(
114 0 : "." + aParts[a] + aConcatenated);
115 :
116 0 : if(pParent)
117 : {
118 : // check for combined selectors at parent firstso that higher specificity will be in front
119 0 : fillCssStyleVectorUsingHierarchyAndSelectors(rClassStr, *pParent, aNewConcatenated);
120 : }
121 :
122 0 : const SvgStyleAttributes* pNew = rDocument.findGlobalCssStyleAttributes(aNewConcatenated);
123 :
124 0 : if(pNew)
125 : {
126 : // add CssStyle if found
127 0 : maCssStyleVector.push_back(pNew);
128 : }
129 0 : }
130 : }
131 : }
132 :
133 : // check for class-dependent references to CssStyles
134 8 : if(!rClassStr.isEmpty())
135 : {
136 8 : OUString aNewConcatenated(aConcatenated);
137 :
138 8 : if(!rCurrent.getId() && !rCurrent.getClass() && 0 == aConcatenated.indexOf(rClassStr))
139 : {
140 : // no new CssStyle Selector and already starts with rClassStr, do not concatenate;
141 : // we pass an 'empty' node (in the sense of CssStyle Selector)
142 : }
143 : else
144 : {
145 4 : aNewConcatenated = rClassStr + aConcatenated;
146 : }
147 :
148 8 : if(pParent)
149 : {
150 : // check for combined selectors at parent firstso that higher specificity will be in front
151 4 : fillCssStyleVectorUsingHierarchyAndSelectors(rClassStr, *pParent, aNewConcatenated);
152 : }
153 :
154 8 : const SvgStyleAttributes* pNew = rDocument.findGlobalCssStyleAttributes(aNewConcatenated);
155 :
156 8 : if(pNew)
157 : {
158 : // add CssStyle if found
159 3 : maCssStyleVector.push_back(pNew);
160 8 : }
161 : }
162 : }
163 10060 : }
164 :
165 10056 : void SvgNode::fillCssStyleVector(const OUString& rClassStr)
166 : {
167 : OSL_ENSURE(!mbCssStyleVectorBuilt, "OOps, fillCssStyleVector called double ?!?");
168 10056 : mbCssStyleVectorBuilt = true;
169 :
170 : // #i125293# If we have CssStyles we need to buuild a linked list of SvgStyleAttributes
171 : // which represent this for the current object. There are various methods to
172 : // specify CssStyles which need to be taken into account in a given order:
173 : // - local CssStyle (independent from global CssStyles at SvgDocument)
174 : // - 'id' CssStyle
175 : // - 'class' CssStyle(s)
176 : // - type-dependent elements (e..g. 'rect' for all rect elements)
177 : // - local attributes (rOriginal)
178 : // - inherited attributes (up the hierarchy)
179 : // The first four will be collected in maCssStyleVector for the current element
180 : // (once, this will not change) and be linked in the needed order using the
181 : // get/setCssStyleParent at the SvgStyleAttributes which will be used preferred in
182 : // member evaluation over the existing parent hierarchy
183 :
184 : // check for local CssStyle with highest priority
185 10056 : if(mpLocalCssStyle)
186 : {
187 : // if we have one, use as first entry
188 3496 : maCssStyleVector.push_back(mpLocalCssStyle);
189 : }
190 :
191 : // check the hierarchy for concatenated patterns of Selectors
192 10056 : fillCssStyleVectorUsingHierarchyAndSelectors(rClassStr, *this, OUString());
193 :
194 : // #i125329# find Css selector '*', add as last element if found
195 10056 : const SvgStyleAttributes* pNew = getDocument().findGlobalCssStyleAttributes("*");
196 :
197 10056 : if(pNew)
198 : {
199 : // add CssStyle for selector '*' if found
200 0 : maCssStyleVector.push_back(pNew);
201 : }
202 10056 : }
203 :
204 127244 : const SvgStyleAttributes* SvgNode::checkForCssStyle(const OUString& rClassStr, const SvgStyleAttributes& rOriginal) const
205 : {
206 127244 : if(!mbCssStyleVectorBuilt)
207 : {
208 : // build needed CssStyleVector for local node
209 10056 : const_cast< SvgNode* >(this)->fillCssStyleVector(rClassStr);
210 : }
211 :
212 127244 : if(maCssStyleVector.empty())
213 : {
214 : // return given original if no CssStlyes found
215 119208 : return &rOriginal;
216 : }
217 : else
218 : {
219 : // #i125293# rOriginal will be the last element in the linked list; use no CssStyleParent
220 : // there (reset it) to ensure that the parent hierarchy will be used when it's base
221 : // is referenced. This new chaining inserts the CssStyles before the original style,
222 : // this makes the whole process much safer since the original style when used will
223 : // be not different to the situation without CssStyles; thus loops which may be caused
224 : // by trying to use the parent hierarchy of the owner of the style will be avoided
225 : // already in this mechanism. It's still good to keep the supportsParentStyle
226 : // from #i125258# in place, though.
227 : // This chain building using pointers will be done every time when checkForCssStyle
228 : // is used (not the search, only the chaining). This is needed since the CssStyles
229 : // themselves will be potentially used multiple times. It is not expensive since it's
230 : // only changing some pointers.
231 : // The alternative would be to create the style hierarchy for every element (or even
232 : // for the element containing the hierarchy) in a vector of pointers and to use that.
233 : // Resetting the CssStyleParent on rOriginal is probably not needeed
234 : // but simply safer to do.
235 8036 : const_cast< SvgStyleAttributes& >(rOriginal).setCssStyleParent(0);
236 :
237 : // loop over the existing CssStyles and link them. There is a first one, take
238 : // as current
239 8036 : SvgStyleAttributes* pCurrent = const_cast< SvgStyleAttributes* >(maCssStyleVector[0]);
240 :
241 8052 : for(size_t a(1); a < maCssStyleVector.size(); a++)
242 : {
243 16 : SvgStyleAttributes* pNext = const_cast< SvgStyleAttributes* >(maCssStyleVector[a]);
244 :
245 16 : pCurrent->setCssStyleParent(pNext);
246 16 : pCurrent = pNext;
247 : }
248 :
249 : // pCurrent is the last used CssStyle, let it point to the original style
250 8036 : pCurrent->setCssStyleParent(&rOriginal);
251 :
252 : // return 1st CssStyle as style chain start element (only for the
253 : // local element, still no hierarchy used here)
254 8036 : return maCssStyleVector[0];
255 : }
256 : }
257 :
258 10777 : SvgNode::SvgNode(
259 : SVGToken aType,
260 : SvgDocument& rDocument,
261 : SvgNode* pParent)
262 : : maType(aType),
263 : mrDocument(rDocument),
264 : mpParent(pParent),
265 : mpAlternativeParent(0),
266 : maChildren(),
267 : mpId(0),
268 : mpClass(0),
269 : maXmlSpace(XmlSpace_notset),
270 : maDisplay(Display_inline),
271 : maCssStyleVector(),
272 : mpLocalCssStyle(0),
273 10777 : mbCssStyleVectorBuilt(false)
274 : {
275 : OSL_ENSURE(SVGTokenUnknown != maType, "SvgNode with unknown type created (!)");
276 :
277 10777 : if(pParent)
278 : {
279 10478 : pParent->maChildren.push_back(this);
280 : }
281 : else
282 : {
283 : #ifdef DBG_UTIL
284 : if(SVGTokenSvg != getType())
285 : {
286 : OSL_ENSURE(false, "No parent for this node (!)");
287 : }
288 : #endif
289 : }
290 10777 : }
291 :
292 21554 : SvgNode::~SvgNode()
293 : {
294 32032 : while(maChildren.size())
295 : {
296 10478 : delete maChildren[maChildren.size() - 1];
297 10478 : maChildren.pop_back();
298 : }
299 :
300 10777 : if(mpId)
301 : {
302 2311 : delete mpId;
303 : }
304 :
305 10777 : if(mpClass)
306 : {
307 0 : delete mpClass;
308 : }
309 :
310 10777 : if(mpLocalCssStyle)
311 : {
312 3569 : delete mpLocalCssStyle;
313 : }
314 10777 : }
315 :
316 3569 : void SvgNode::readLocalCssStyle(const OUString& aContent)
317 : {
318 3569 : if(!mpLocalCssStyle)
319 : {
320 : // create LocalCssStyle if needed but not yet added
321 3569 : mpLocalCssStyle = new SvgStyleAttributes(*this);
322 : }
323 : else
324 : {
325 : // 2nd fill would be an error
326 : OSL_ENSURE(false, "Svg node has two local CssStyles, this may lead to problems (!)");
327 : }
328 :
329 3569 : if(mpLocalCssStyle)
330 : {
331 : // parse and set values to it
332 3569 : mpLocalCssStyle->readCssStyle(aContent);
333 : }
334 : else
335 : {
336 : OSL_ENSURE(false, "Could not get/create a local CssStyle for a node (!)");
337 : }
338 3569 : }
339 :
340 10777 : void SvgNode::parseAttributes(const com::sun::star::uno::Reference< com::sun::star::xml::sax::XAttributeList >& xAttribs)
341 : {
342 : // no longer need to pre-sort moving 'style' entries to the back so that
343 : // values get overwritten - that was the previous, not complete solution for
344 : // handling the priorities between svg and Css properties
345 10777 : const sal_uInt32 nAttributes(xAttribs->getLength());
346 :
347 40151 : for(sal_uInt32 a(0); a < nAttributes; a++)
348 : {
349 29374 : const OUString aTokenName(xAttribs->getNameByIndex(a));
350 29374 : const SVGToken aSVGToken(StrToSVGToken(aTokenName, false));
351 :
352 29374 : parseAttribute(aTokenName, aSVGToken, xAttribs->getValueByIndex(a));
353 29374 : }
354 10777 : }
355 :
356 0 : Display getDisplayFromContent(const OUString& aContent)
357 : {
358 0 : if(!aContent.isEmpty())
359 : {
360 0 : if(aContent.startsWith("inline"))
361 : {
362 0 : return Display_inline;
363 : }
364 0 : else if(aContent.startsWith("none"))
365 : {
366 0 : return Display_none;
367 : }
368 0 : else if(aContent.startsWith("inherit"))
369 : {
370 0 : return Display_inherit;
371 : }
372 0 : else if(aContent.startsWith("block"))
373 : {
374 0 : return Display_block;
375 : }
376 0 : else if(aContent.startsWith("list-item"))
377 : {
378 0 : return Display_list_item;
379 : }
380 0 : else if(aContent.startsWith("run-in"))
381 : {
382 0 : return Display_run_in;
383 : }
384 0 : else if(aContent.startsWith("compact"))
385 : {
386 0 : return Display_compact;
387 : }
388 0 : else if(aContent.startsWith("marker"))
389 : {
390 0 : return Display_marker;
391 : }
392 0 : else if(aContent.startsWith("table"))
393 : {
394 0 : return Display_table;
395 : }
396 0 : else if(aContent.startsWith("inline-table"))
397 : {
398 0 : return Display_inline_table;
399 : }
400 0 : else if(aContent.startsWith("table-row-group"))
401 : {
402 0 : return Display_table_row_group;
403 : }
404 0 : else if(aContent.startsWith("table-header-group"))
405 : {
406 0 : return Display_table_header_group;
407 : }
408 0 : else if(aContent.startsWith("table-footer-group"))
409 : {
410 0 : return Display_table_footer_group;
411 : }
412 0 : else if(aContent.startsWith("table-row"))
413 : {
414 0 : return Display_table_row;
415 : }
416 0 : else if(aContent.startsWith("table-column-group"))
417 : {
418 0 : return Display_table_column_group;
419 : }
420 0 : else if(aContent.startsWith("table-column"))
421 : {
422 0 : return Display_table_column;
423 : }
424 0 : else if(aContent.startsWith("table-cell"))
425 : {
426 0 : return Display_table_cell;
427 : }
428 0 : else if(aContent.startsWith("table-caption"))
429 : {
430 0 : return Display_table_caption;
431 : }
432 : }
433 :
434 : // return the default
435 0 : return Display_inline;
436 : }
437 :
438 29374 : void SvgNode::parseAttribute(const OUString& /*rTokenName*/, SVGToken aSVGToken, const OUString& aContent)
439 : {
440 29374 : switch(aSVGToken)
441 : {
442 : case SVGTokenId:
443 : {
444 2311 : if(!aContent.isEmpty())
445 : {
446 2311 : setId(&aContent);
447 : }
448 2311 : break;
449 : }
450 : case SVGTokenClass:
451 : {
452 0 : if(!aContent.isEmpty())
453 : {
454 0 : setClass(&aContent);
455 : }
456 0 : break;
457 : }
458 : case SVGTokenXmlSpace:
459 : {
460 295 : if(!aContent.isEmpty())
461 : {
462 295 : if(aContent.startsWith("default"))
463 : {
464 0 : setXmlSpace(XmlSpace_default);
465 : }
466 295 : else if(aContent.startsWith("preserve"))
467 : {
468 295 : setXmlSpace(XmlSpace_preserve);
469 : }
470 : }
471 295 : break;
472 : }
473 : case SVGTokenDisplay:
474 : {
475 0 : if(!aContent.isEmpty())
476 : {
477 0 : setDisplay(getDisplayFromContent(aContent));
478 : }
479 0 : break;
480 : }
481 : default:
482 : {
483 26768 : break;
484 : }
485 : }
486 29374 : }
487 :
488 7436 : void SvgNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DSequence& rTarget, bool bReferenced) const
489 : {
490 7436 : if(Display_none == getDisplay())
491 : {
492 0 : return;
493 : }
494 :
495 7436 : if(!bReferenced)
496 : {
497 19475 : if(SVGTokenDefs == getType() ||
498 12858 : SVGTokenSymbol == getType() ||
499 12765 : SVGTokenClipPathNode == getType() ||
500 12483 : SVGTokenMask == getType() ||
501 18817 : SVGTokenMarker == getType() ||
502 6147 : SVGTokenPattern == getType())
503 : {
504 : // do not decompose defs or symbol nodes (these hold only style-like
505 : // objects which may be used by referencing them) except when doing
506 : // so controlled referenced
507 :
508 : // also do not decompose ClipPaths and Masks. These should be embedded
509 : // in a defs node (which gets not decomposed by itself), but you never
510 : // know
511 :
512 : // also not directly used are Markers and Patterns, only indirecty used
513 : // by reference
514 :
515 : // #i121656# also do not decompose nodes which have display="none" set
516 : // as property
517 376 : return;
518 : }
519 : }
520 :
521 7060 : const SvgNodeVector& rChildren = getChildren();
522 :
523 7060 : if(!rChildren.empty())
524 : {
525 3683 : const sal_uInt32 nCount(rChildren.size());
526 :
527 13949 : for(sal_uInt32 a(0); a < nCount; a++)
528 : {
529 10266 : SvgNode* pCandidate = rChildren[a];
530 :
531 10266 : if(pCandidate && Display_none != pCandidate->getDisplay())
532 : {
533 10266 : const SvgNodeVector& rGrandChildren = pCandidate->getChildren();
534 10266 : const SvgStyleAttributes* pChildStyles = pCandidate->getSvgStyleAttributes();
535 : // decompose:
536 : // - visible terminal nodes
537 : // - all non-terminal nodes (might contain visible nodes down the hierarchy)
538 10266 : if( !rGrandChildren.empty() || ( pChildStyles && (Visibility_visible == pChildStyles->getVisibility())) )
539 : {
540 10062 : drawinglayer::primitive2d::Primitive2DSequence aNewTarget;
541 10062 : pCandidate->decomposeSvgNode(aNewTarget, bReferenced);
542 :
543 10062 : if(aNewTarget.hasElements())
544 : {
545 5004 : drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aNewTarget);
546 10062 : }
547 : }
548 : }
549 : else if(!pCandidate)
550 : {
551 : OSL_ENSURE(false, "Null-Pointer in child node list (!)");
552 : }
553 : }
554 :
555 3683 : if(rTarget.hasElements())
556 : {
557 2399 : const SvgStyleAttributes* pStyles = getSvgStyleAttributes();
558 2399 : if(pStyles)
559 : {
560 : // check if we have Title or Desc
561 2399 : const OUString& rTitle = pStyles->getTitle();
562 2399 : const OUString& rDesc = pStyles->getDesc();
563 :
564 2399 : if(!rTitle.isEmpty() || !rDesc.isEmpty())
565 : {
566 : // default object name is empty
567 0 : OUString aObjectName;
568 :
569 : // use path as object name when outmost element
570 0 : if(SVGTokenSvg == getType())
571 : {
572 0 : aObjectName = getDocument().getAbsolutePath();
573 :
574 0 : if(!aObjectName.isEmpty())
575 : {
576 0 : INetURLObject aURL(aObjectName);
577 :
578 0 : aObjectName = aURL.getName(
579 : INetURLObject::LAST_SEGMENT,
580 : true,
581 0 : INetURLObject::DECODE_WITH_CHARSET);
582 : }
583 : }
584 :
585 : // pack in ObjectInfoPrimitive2D group
586 : const drawinglayer::primitive2d::Primitive2DReference xRef(
587 : new drawinglayer::primitive2d::ObjectInfoPrimitive2D(
588 : rTarget,
589 : aObjectName,
590 : rTitle,
591 0 : rDesc));
592 :
593 0 : rTarget = drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1);
594 : }
595 : }
596 : }
597 : }
598 : }
599 :
600 0 : const basegfx::B2DRange SvgNode::getCurrentViewPort() const
601 : {
602 0 : if(getParent())
603 : {
604 0 : return getParent()->getCurrentViewPort();
605 : }
606 : else
607 : {
608 0 : return basegfx::B2DRange(); // return empty B2DRange
609 : }
610 : }
611 :
612 0 : double SvgNode::getCurrentFontSizeInherited() const
613 : {
614 0 : if(getParent())
615 : {
616 0 : return getParent()->getCurrentFontSize();
617 : }
618 : else
619 : {
620 0 : return 0.0;
621 : }
622 : }
623 :
624 0 : double SvgNode::getCurrentFontSize() const
625 : {
626 0 : if(getSvgStyleAttributes())
627 0 : return getSvgStyleAttributes()->getFontSize().solve(*this, xcoordinate);
628 :
629 0 : return getCurrentFontSizeInherited();
630 : }
631 :
632 0 : double SvgNode::getCurrentXHeightInherited() const
633 : {
634 0 : if(getParent())
635 : {
636 0 : return getParent()->getCurrentXHeight();
637 : }
638 : else
639 : {
640 0 : return 0.0;
641 : }
642 : }
643 :
644 0 : double SvgNode::getCurrentXHeight() const
645 : {
646 0 : if(getSvgStyleAttributes())
647 : // for XHeight, use FontSize currently
648 0 : return getSvgStyleAttributes()->getFontSize().solve(*this, ycoordinate);
649 :
650 0 : return getCurrentXHeightInherited();
651 : }
652 :
653 2311 : void SvgNode::setId(const OUString* pfId)
654 : {
655 2311 : if(mpId)
656 : {
657 0 : mrDocument.removeSvgNodeFromMapper(*mpId);
658 0 : delete mpId;
659 0 : mpId = 0;
660 : }
661 :
662 2311 : if(pfId)
663 : {
664 2311 : mpId = new OUString(*pfId);
665 2311 : mrDocument.addSvgNodeToMapper(*mpId, *this);
666 : }
667 2311 : }
668 :
669 0 : void SvgNode::setClass(const OUString* pfClass)
670 : {
671 0 : if(mpClass)
672 : {
673 0 : mrDocument.removeSvgNodeFromMapper(*mpClass);
674 0 : delete mpClass;
675 0 : mpClass = 0;
676 : }
677 :
678 0 : if(pfClass)
679 : {
680 0 : mpClass = new OUString(*pfClass);
681 0 : mrDocument.addSvgNodeToMapper(*mpClass, *this);
682 : }
683 0 : }
684 :
685 0 : XmlSpace SvgNode::getXmlSpace() const
686 : {
687 0 : if(maXmlSpace != XmlSpace_notset)
688 : {
689 0 : return maXmlSpace;
690 : }
691 :
692 0 : if(getParent())
693 : {
694 0 : return getParent()->getXmlSpace();
695 : }
696 :
697 : // default is XmlSpace_default
698 0 : return XmlSpace_default;
699 : }
700 :
701 : } // end of namespace svgreader
702 : } // end of namespace svgio
703 :
704 :
705 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|