bump product version to 5.0.4.1
[LibreOffice.git] / starmath / source / ooxmlimport.cxx
blobdaf3f26819324ad535c7c6de2df326d0b370abc7
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/.
8 */
11 #include "ooxmlimport.hxx"
12 #include "types.hxx"
14 #include <oox/token/tokens.hxx>
15 #include <oox/token/namespaces.hxx>
16 #include <rtl/ustring.hxx>
17 #include <rtl/ustrbuf.hxx>
19 using namespace oox;
20 using namespace oox::formulaimport;
23 The primary internal data structure for the formula is the text representation
24 (the SmNode tree is built from it), so read data must be converted into this format.
27 #define M_TOKEN( token ) OOX_TOKEN( officeMath, token )
28 #define OPENING( token ) XML_STREAM_OPENING( token )
29 #define CLOSING( token ) XML_STREAM_CLOSING( token )
31 // TODO create IS_OPENING(), IS_CLOSING() instead of doing 'next == OPENING( next )' ?
33 SmOoxmlImport::SmOoxmlImport( oox::formulaimport::XmlStream& s )
34 : stream( s )
38 OUString SmOoxmlImport::ConvertToStarMath()
40 return handleStream();
43 // "toplevel" of reading, there will be oMath (if there was oMathPara, that was
44 // up to the parent component to handle)
46 // NOT complete
47 OUString SmOoxmlImport::handleStream()
49 stream.ensureOpeningTag( M_TOKEN( oMath ));
50 OUString ret;
51 while( !stream.atEnd() && stream.currentToken() != CLOSING( M_TOKEN( oMath )))
53 // strictly speaking, it is not OMathArg here, but currently supported
54 // functionality is the same like OMathArg, in the future this may need improving
55 OUString item = readOMathArg( M_TOKEN( oMath ));
56 if( item.isEmpty())
57 continue;
58 if( !ret.isEmpty())
59 ret += " ";
60 ret += item;
62 stream.ensureClosingTag( M_TOKEN( oMath ));
63 // Placeholders are written out as nothing (i.e. nothing inside e.g. the <e> element),
64 // which will result in "{}" in the formula text. Fix this up.
65 ret = ret.replaceAll( "{}", "<?>" );
66 // And as a result, empty parts of the formula that are not placeholders are written out
67 // as a single space, so fix that up too.
68 ret = ret.replaceAll( "{ }", "{}" );
69 SAL_INFO( "starmath.ooxml", "Formula: " << ret );
70 return ret;
73 OUString SmOoxmlImport::readOMathArg( int stoptoken )
75 OUString ret;
76 while( !stream.atEnd() && stream.currentToken() != CLOSING( stoptoken ))
78 if( !ret.isEmpty())
79 ret += " ";
80 switch( stream.currentToken())
82 case OPENING( M_TOKEN( acc )):
83 ret += handleAcc();
84 break;
85 case OPENING( M_TOKEN( bar )):
86 ret += handleBar();
87 break;
88 case OPENING( M_TOKEN( box )):
89 ret += handleBox();
90 break;
91 case OPENING( M_TOKEN( borderBox )):
92 ret += handleBorderBox();
93 break;
94 case OPENING( M_TOKEN( d )):
95 ret += handleD();
96 break;
97 case OPENING( M_TOKEN( eqArr )):
98 ret += handleEqArr();
99 break;
100 case OPENING( M_TOKEN( f )):
101 ret += handleF();
102 break;
103 case OPENING( M_TOKEN( func )):
104 ret += handleFunc();
105 break;
106 case OPENING( M_TOKEN( limLow )):
107 ret += handleLimLowUpp( LimLow );
108 break;
109 case OPENING( M_TOKEN( limUpp )):
110 ret += handleLimLowUpp( LimUpp );
111 break;
112 case OPENING( M_TOKEN( groupChr )):
113 ret += handleGroupChr();
114 break;
115 case OPENING( M_TOKEN( m )):
116 ret += handleM();
117 break;
118 case OPENING( M_TOKEN( nary )):
119 ret += handleNary();
120 break;
121 case OPENING( M_TOKEN( r )):
122 ret += handleR();
123 break;
124 case OPENING( M_TOKEN( rad )):
125 ret += handleRad();
126 break;
127 case OPENING( M_TOKEN( sPre )):
128 ret += handleSpre();
129 break;
130 case OPENING( M_TOKEN( sSub )):
131 ret += handleSsub();
132 break;
133 case OPENING( M_TOKEN( sSubSup )):
134 ret += handleSsubsup();
135 break;
136 case OPENING( M_TOKEN( sSup )):
137 ret += handleSsup();
138 break;
139 default:
140 stream.handleUnexpectedTag();
141 break;
144 return ret;
147 OUString SmOoxmlImport::readOMathArgInElement( int token )
149 stream.ensureOpeningTag( token );
150 OUString ret = readOMathArg( token );
151 stream.ensureClosingTag( token );
152 return ret;
155 OUString SmOoxmlImport::handleAcc()
157 stream.ensureOpeningTag( M_TOKEN( acc ));
158 sal_Unicode accChr = 0x302;
159 if( XmlStream::Tag accPr = stream.checkOpeningTag( M_TOKEN( accPr )))
161 if( XmlStream::Tag chr = stream.checkOpeningTag( M_TOKEN( chr )))
163 accChr = chr.attribute( M_TOKEN( val ), accChr );
164 stream.ensureClosingTag( M_TOKEN( chr ));
166 stream.ensureClosingTag( M_TOKEN( accPr ));
168 // see aTokenTable in parse.cxx
169 OUString acc;
170 switch( accChr )
172 case MS_BAR:
173 case MS_COMBBAR:
174 acc = "bar";
175 break;
176 case MS_CHECK:
177 case MS_COMBCHECK:
178 acc = "check";
179 break;
180 case MS_ACUTE:
181 case MS_COMBACUTE:
182 acc = "acute";
183 break;
184 case MS_GRAVE:
185 case MS_COMBGRAVE:
186 acc = "grave";
187 break;
188 case MS_BREVE:
189 case MS_COMBBREVE:
190 acc = "breve";
191 break;
192 case MS_CIRCLE:
193 case MS_COMBCIRCLE:
194 acc = "circle";
195 break;
196 case MS_RIGHTARROW:
197 case MS_VEC:
198 // prefer wide variants for these 3, .docx can't seem to differentiate
199 // between e.g. 'vec' and 'widevec', if whatever the accent is above is short, this
200 // shouldn't matter, but short above a longer expression doesn't look right
201 acc = "widevec";
202 break;
203 case MS_TILDE:
204 case MS_COMBTILDE:
205 acc = "widetilde";
206 break;
207 case MS_HAT:
208 case MS_COMBHAT:
209 acc = "widehat";
210 break;
211 case MS_DOT:
212 acc = "dot";
213 break;
214 case MS_DDOT:
215 acc = "ddot";
216 break;
217 case MS_DDDOT:
218 acc = "dddot";
219 break;
220 default:
221 acc = "acute";
222 SAL_WARN( "starmath.ooxml", "Unknown m:chr in m:acc \'" << accChr << "\'" );
223 break;
225 OUString e = readOMathArgInElement( M_TOKEN( e ));
226 stream.ensureClosingTag( M_TOKEN( acc ));
227 return acc + " {" + e + "}";
230 OUString SmOoxmlImport::handleBar()
232 stream.ensureOpeningTag( M_TOKEN( bar ));
233 enum pos_t { top, bot } topbot = bot;
234 if( stream.checkOpeningTag( M_TOKEN( barPr )))
236 if( XmlStream::Tag pos = stream.checkOpeningTag( M_TOKEN( pos )))
238 if( pos.attribute( M_TOKEN( val )) == "top" )
239 topbot = top;
240 else if( pos.attribute( M_TOKEN( val )) == "bot" )
241 topbot = bot;
242 stream.ensureClosingTag( M_TOKEN( pos ));
244 stream.ensureClosingTag( M_TOKEN( barPr ));
246 OUString e = readOMathArgInElement( M_TOKEN( e ));
247 stream.ensureClosingTag( M_TOKEN( bar ));
248 if( topbot == top )
249 return "overline {" + e + "}";
250 else
251 return "underline {" + e + "}";
254 OUString SmOoxmlImport::handleBox()
256 // there does not seem to be functionality in LO to actually implement this
257 // (or is there), but at least read in the contents instead of ignoring them
258 stream.ensureOpeningTag( M_TOKEN( box ));
259 OUString e = readOMathArgInElement( M_TOKEN( e ));
260 stream.ensureClosingTag( M_TOKEN( box ));
261 return e;
265 OUString SmOoxmlImport::handleBorderBox()
267 stream.ensureOpeningTag( M_TOKEN( borderBox ));
268 bool isStrikeH = false;
269 if( stream.checkOpeningTag( M_TOKEN( borderBoxPr )))
271 if( XmlStream::Tag strikeH = stream.checkOpeningTag( M_TOKEN( strikeH )))
273 if( strikeH.attribute( M_TOKEN( val ), false ))
274 isStrikeH = true;
275 stream.ensureClosingTag( M_TOKEN( strikeH ));
277 stream.ensureClosingTag( M_TOKEN( borderBoxPr ));
279 OUString e = readOMathArgInElement( M_TOKEN( e ));
280 stream.ensureClosingTag( M_TOKEN( borderBox ));
281 if( isStrikeH )
282 return "overstrike {" + e + "}";
283 // LO does not seem to implement anything for handling the other cases
284 return e;
287 OUString SmOoxmlImport::handleD()
289 stream.ensureOpeningTag( M_TOKEN( d ));
290 OUString opening = "(";
291 OUString closing = ")";
292 OUString separator = "|";
293 if( XmlStream::Tag dPr = stream.checkOpeningTag( M_TOKEN( dPr )))
295 if( XmlStream::Tag begChr = stream.checkOpeningTag( M_TOKEN( begChr )))
297 opening = begChr.attribute( M_TOKEN( val ), opening );
298 stream.ensureClosingTag( M_TOKEN( begChr ));
300 if( XmlStream::Tag sepChr = stream.checkOpeningTag( M_TOKEN( sepChr )))
302 separator = sepChr.attribute( M_TOKEN( val ), separator );
303 stream.ensureClosingTag( M_TOKEN( sepChr ));
305 if( XmlStream::Tag endChr = stream.checkOpeningTag( M_TOKEN( endChr )))
307 closing = endChr.attribute( M_TOKEN( val ), closing );
308 stream.ensureClosingTag( M_TOKEN( endChr ));
310 stream.ensureClosingTag( M_TOKEN( dPr ));
312 if( opening == "{" )
313 opening = "left lbrace ";
314 if( closing == "}" )
315 closing = " right rbrace";
316 if( opening == OUString( sal_Unicode( 0x27e6 )))
317 opening = "left ldbracket ";
318 if( closing == OUString( sal_Unicode( 0x27e7 )))
319 closing = " right rdbracket";
320 if( opening == "|" )
321 opening = "left lline ";
322 if( closing == "|" )
323 closing = " right rline";
324 if (opening == OUString(MS_DLINE) || opening == OUString(MS_DVERTLINE))
325 opening = "left ldline ";
326 if (closing == OUString(MS_DLINE) || closing == OUString(MS_DVERTLINE))
327 closing = " right rdline";
328 if (opening == OUString(MS_LANGLE) || opening == OUString(MS_LMATHANGLE))
329 opening = "left langle ";
330 if (closing == OUString(MS_RANGLE) || closing == OUString(MS_RMATHANGLE))
331 closing = " right rangle";
332 // use scalable brackets (the explicit "left" or "right")
333 if( opening == "(" || opening == "[" )
334 opening = "left " + opening;
335 if( closing == ")" || closing == "]" )
336 closing = " right " + closing;
337 if( separator == "|" ) // plain "|" would be actually "V" (logical or)
338 separator = " mline ";
339 if( opening.isEmpty())
340 opening = "left none ";
341 if( closing.isEmpty())
342 closing = " right none";
343 OUStringBuffer ret;
344 ret.append( opening );
345 bool first = true;
346 while( stream.findTag( OPENING( M_TOKEN( e ))))
348 if( !first )
349 ret.append( separator );
350 first = false;
351 ret.append( readOMathArgInElement( M_TOKEN( e )));
353 ret.append( closing );
354 stream.ensureClosingTag( M_TOKEN( d ));
355 return ret.makeStringAndClear();
358 OUString SmOoxmlImport::handleEqArr()
360 stream.ensureOpeningTag( M_TOKEN( eqArr ));
361 OUString ret;
363 { // there must be at least one m:e
364 if( !ret.isEmpty())
365 ret += "#";
366 ret += " ";
367 ret += readOMathArgInElement( M_TOKEN( e ));
368 ret += " ";
369 } while( !stream.atEnd() && stream.findTag( OPENING( M_TOKEN( e ))));
370 stream.ensureClosingTag( M_TOKEN( eqArr ));
371 return "stack {" + ret + "}";
374 OUString SmOoxmlImport::handleF()
376 stream.ensureOpeningTag( M_TOKEN( f ));
377 enum operation_t { bar, lin, noBar } operation = bar;
378 if( stream.checkOpeningTag( M_TOKEN( fPr )))
380 if( XmlStream::Tag type = stream.checkOpeningTag( M_TOKEN( type )))
382 if( type.attribute( M_TOKEN( val )) == "bar" )
383 operation = bar;
384 else if( type.attribute( M_TOKEN( val )) == "lin" )
385 operation = lin;
386 else if( type.attribute( M_TOKEN( val )) == "noBar" )
387 operation = noBar;
388 stream.ensureClosingTag( M_TOKEN( type ));
390 stream.ensureClosingTag( M_TOKEN( fPr ));
392 OUString num = readOMathArgInElement( M_TOKEN( num ));
393 OUString den = readOMathArgInElement( M_TOKEN( den ));
394 stream.ensureClosingTag( M_TOKEN( f ));
395 if( operation == bar )
396 return "{" + num + "} over {" + den + "}";
397 else if( operation == lin )
398 return "{" + num + "} / {" + den + "}";
399 else // noBar
401 return "binom {" + num + "} {" + den + "}";
405 OUString SmOoxmlImport::handleFunc()
407 //lim from{x rightarrow 1} x
408 stream.ensureOpeningTag( M_TOKEN( func ));
409 OUString fname = readOMathArgInElement( M_TOKEN( fName ));
410 // fix the various functions
411 if( fname.startsWith( "lim csub {" ))
412 fname = "lim from {" + fname.copy( 10 );
413 OUString ret = fname + " {" + readOMathArgInElement( M_TOKEN( e )) + "}";
414 stream.ensureClosingTag( M_TOKEN( func ));
415 return ret;
418 OUString SmOoxmlImport::handleLimLowUpp( LimLowUpp_t limlowupp )
420 int token = limlowupp == LimLow ? M_TOKEN( limLow ) : M_TOKEN( limUpp );
421 stream.ensureOpeningTag( token );
422 OUString e = readOMathArgInElement( M_TOKEN( e ));
423 OUString lim = readOMathArgInElement( M_TOKEN( lim ));
424 stream.ensureClosingTag( token );
425 // fix up overbrace/underbrace (use { }, as {} will be converted to a placeholder)
426 if( limlowupp == LimUpp && e.endsWith( " overbrace { }" ))
427 return e.copy( 0, e.getLength() - 2 ) + lim + "}";
428 if( limlowupp == LimLow && e.endsWith( " underbrace { }" ))
429 return e.copy( 0, e.getLength() - 2 ) + lim + "}";
430 return e
431 + ( limlowupp == LimLow ? OUString( " csub {" ) : OUString( " csup {" ))
432 + lim + "}";
435 OUString SmOoxmlImport::handleGroupChr()
437 stream.ensureOpeningTag( M_TOKEN( groupChr ));
438 sal_Unicode chr = 0x23df;
439 enum pos_t { top, bot } pos = bot;
440 if( stream.checkOpeningTag( M_TOKEN( groupChrPr )))
442 if( XmlStream::Tag chrTag = stream.checkOpeningTag( M_TOKEN( chr )))
444 chr = chrTag.attribute( M_TOKEN( val ), chr );
445 stream.ensureClosingTag( M_TOKEN( chr ));
447 if( XmlStream::Tag posTag = stream.checkOpeningTag( M_TOKEN( pos )))
449 if( posTag.attribute( M_TOKEN( val ), OUString( "bot" )) == "top" )
450 pos = top;
451 stream.ensureClosingTag( M_TOKEN( pos ));
453 stream.ensureClosingTag( M_TOKEN( groupChrPr ));
455 OUString e = readOMathArgInElement( M_TOKEN( e ));
456 stream.ensureClosingTag( M_TOKEN( groupChr ));
457 if( pos == top && chr == sal_Unicode( 0x23de ))
458 return "{" + e + "} overbrace { }";
459 if( pos == bot && chr == sal_Unicode( 0x23df ))
460 return "{" + e + "} underbrace { }";
461 if( pos == top )
462 return "{" + e + "} csup {" + OUString( chr ) + "}";
463 else
464 return "{" + e + "} csub {" + OUString( chr ) + "}";
467 OUString SmOoxmlImport::handleM()
469 stream.ensureOpeningTag( M_TOKEN( m ));
470 OUString allrows;
471 do // there must be at least one m:mr
473 stream.ensureOpeningTag( M_TOKEN( mr ));
474 OUString row;
475 do // there must be at least one m:e
477 if( !row.isEmpty())
478 row += " # ";
479 row += readOMathArgInElement( M_TOKEN( e ));
480 } while( !stream.atEnd() && stream.findTag( OPENING( M_TOKEN( e ))));
481 if( !allrows.isEmpty())
482 allrows += " ## ";
483 allrows += row;
484 stream.ensureClosingTag( M_TOKEN( mr ));
485 } while( !stream.atEnd() && stream.findTag( OPENING( M_TOKEN( mr ))));
486 stream.ensureClosingTag( M_TOKEN( m ));
487 return "matrix {" + allrows + "}";
490 OUString SmOoxmlImport::handleNary()
492 stream.ensureOpeningTag( M_TOKEN( nary ));
493 sal_Unicode chr = 0x222b;
494 bool subHide = false;
495 bool supHide = false;
496 if( stream.checkOpeningTag( M_TOKEN( naryPr )))
498 if( XmlStream::Tag chrTag = stream.checkOpeningTag( M_TOKEN( chr )))
500 chr = chrTag.attribute( M_TOKEN( val ), chr );
501 stream.ensureClosingTag( M_TOKEN( chr ));
503 if( XmlStream::Tag subHideTag = stream.checkOpeningTag( M_TOKEN( subHide )))
505 subHide = subHideTag.attribute( M_TOKEN( val ), subHide );
506 stream.ensureClosingTag( M_TOKEN( subHide ));
508 if( XmlStream::Tag supHideTag = stream.checkOpeningTag( M_TOKEN( supHide )))
510 supHide = supHideTag.attribute( M_TOKEN( val ), supHide );
511 stream.ensureClosingTag( M_TOKEN( supHide ));
513 stream.ensureClosingTag( M_TOKEN( naryPr ));
515 OUString sub = readOMathArgInElement( M_TOKEN( sub ));
516 OUString sup = readOMathArgInElement( M_TOKEN( sup ));
517 OUString e = readOMathArgInElement( M_TOKEN( e ));
518 OUString ret;
519 switch( chr )
521 case MS_INT:
522 ret = "int";
523 break;
524 case MS_IINT:
525 ret = "iint";
526 break;
527 case MS_IIINT:
528 ret = "iiint";
529 break;
530 case MS_LINT:
531 ret = "lint";
532 break;
533 case MS_LLINT:
534 ret = "llint";
535 break;
536 case MS_LLLINT:
537 ret = "lllint";
538 break;
539 case MS_PROD:
540 ret = "prod";
541 break;
542 case MS_COPROD:
543 ret = "coprod";
544 break;
545 case MS_SUM:
546 ret = "sum";
547 break;
548 default:
549 SAL_WARN( "starmath.ooxml", "Unknown m:nary chr \'" << chr << "\'" );
550 break;
552 if( !subHide )
553 ret += " from {" + sub + "}";
554 if( !supHide )
555 ret += " to {" + sup + "}";
556 ret += " {" + e + "}";
557 stream.ensureClosingTag( M_TOKEN( nary ));
558 return ret;
561 // NOT complete
562 OUString SmOoxmlImport::handleR()
564 stream.ensureOpeningTag( M_TOKEN( r ));
565 bool normal = false;
566 bool literal = false;
567 if( XmlStream::Tag rPr = stream.checkOpeningTag( M_TOKEN( rPr )))
569 if( XmlStream::Tag litTag = stream.checkOpeningTag( M_TOKEN( lit )))
571 literal = litTag.attribute( M_TOKEN( val ), true );
572 stream.ensureClosingTag( M_TOKEN( lit ));
574 if( XmlStream::Tag norTag = stream.checkOpeningTag( M_TOKEN( nor )))
576 normal = norTag.attribute( M_TOKEN( val ), true );
577 stream.ensureClosingTag( M_TOKEN( nor ));
579 stream.ensureClosingTag( M_TOKEN( rPr ));
581 OUString text;
582 while( !stream.atEnd() && stream.currentToken() != CLOSING( stream.currentToken()))
584 switch( stream.currentToken())
586 case OPENING( M_TOKEN( t )):
588 XmlStream::Tag rtag = stream.ensureOpeningTag( M_TOKEN( t ));
589 if( rtag.attribute( OOX_TOKEN( xml, space )) != "preserve" )
590 text += rtag.text.trim();
591 else
592 text += rtag.text;
593 stream.ensureClosingTag( M_TOKEN( t ));
594 break;
596 default:
597 stream.handleUnexpectedTag();
598 break;
601 stream.ensureClosingTag( M_TOKEN( r ));
602 if( normal || literal )
603 text = "\"" + text + "\"";
604 return text.replaceAll("{", "\\{").replaceAll("}", "\\}");
607 OUString SmOoxmlImport::handleRad()
609 stream.ensureOpeningTag( M_TOKEN( rad ));
610 bool degHide = false;
611 if( stream.checkOpeningTag( M_TOKEN( radPr )))
613 if( XmlStream::Tag degHideTag = stream.checkOpeningTag( M_TOKEN( degHide )))
615 degHide = degHideTag.attribute( M_TOKEN( val ), degHide );
616 stream.ensureClosingTag( M_TOKEN( degHide ));
618 stream.ensureClosingTag( M_TOKEN( radPr ));
620 OUString deg = readOMathArgInElement( M_TOKEN( deg ));
621 OUString e = readOMathArgInElement( M_TOKEN( e ));
622 stream.ensureClosingTag( M_TOKEN( rad ));
623 if( degHide )
624 return "sqrt {" + e + "}";
625 else
626 return "nroot {" + deg + "} {" + e + "}";
629 OUString SmOoxmlImport::handleSpre()
631 stream.ensureOpeningTag( M_TOKEN( sPre ));
632 OUString sub = readOMathArgInElement( M_TOKEN( sub ));
633 OUString sup = readOMathArgInElement( M_TOKEN( sup ));
634 OUString e = readOMathArgInElement( M_TOKEN( e ));
635 stream.ensureClosingTag( M_TOKEN( sPre ));
636 return "{" + e + "} lsub {" + sub + "} lsup {" + sup + "}";
639 OUString SmOoxmlImport::handleSsub()
641 stream.ensureOpeningTag( M_TOKEN( sSub ));
642 OUString e = readOMathArgInElement( M_TOKEN( e ));
643 OUString sub = readOMathArgInElement( M_TOKEN( sub ));
644 stream.ensureClosingTag( M_TOKEN( sSub ));
645 return "{" + e + "} rsub {" + sub + "}";
648 OUString SmOoxmlImport::handleSsubsup()
650 stream.ensureOpeningTag( M_TOKEN( sSubSup ));
651 OUString e = readOMathArgInElement( M_TOKEN( e ));
652 OUString sub = readOMathArgInElement( M_TOKEN( sub ));
653 OUString sup = readOMathArgInElement( M_TOKEN( sup ));
654 stream.ensureClosingTag( M_TOKEN( sSubSup ));
655 return "{" + e + "} rsub {" + sub + "} rsup {" + sup + "}";
658 OUString SmOoxmlImport::handleSsup()
660 stream.ensureOpeningTag( M_TOKEN( sSup ));
661 OUString e = readOMathArgInElement( M_TOKEN( e ));
662 OUString sup = readOMathArgInElement( M_TOKEN( sup ));
663 stream.ensureClosingTag( M_TOKEN( sSup ));
664 return "{" + e + "} ^ {" + sup + "}";
667 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */