Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / sw / source / filter / html / htmlnumreader.cxx
blobc21a45e877684c6b141955f9968d45b2ea4d3fb9
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 <com/sun/star/text/VertOrientation.hpp>
21 #include <hintids.hxx>
22 #include <svtools/htmltokn.h>
23 #include <svtools/htmlkywd.hxx>
24 #include <svl/urihelper.hxx>
25 #include <editeng/brushitem.hxx>
26 #include <editeng/lrspitem.hxx>
27 #include <vcl/svapp.hxx>
28 #include <sal/log.hxx>
29 #include <osl/diagnose.h>
30 #include <numrule.hxx>
31 #include <doc.hxx>
32 #include <docary.hxx>
33 #include <poolfmt.hxx>
34 #include <ndtxt.hxx>
35 #include <paratr.hxx>
37 #include "htmlnum.hxx"
38 #include "swcss1.hxx"
39 #include "swhtml.hxx"
41 using namespace css;
43 // <UL TYPE=...>
44 HTMLOptionEnum<sal_UCS4> const aHTMLULTypeTable[] =
46 { OOO_STRING_SVTOOLS_HTML_ULTYPE_disc, HTML_BULLETCHAR_DISC },
47 { OOO_STRING_SVTOOLS_HTML_ULTYPE_circle, HTML_BULLETCHAR_CIRCLE },
48 { OOO_STRING_SVTOOLS_HTML_ULTYPE_square, HTML_BULLETCHAR_SQUARE },
49 { nullptr, 0 }
53 void SwHTMLParser::NewNumberBulletList( HtmlTokenId nToken )
55 SwHTMLNumRuleInfo& rInfo = GetNumInfo();
57 // Create a new paragraph
58 bool bSpace = (rInfo.GetDepth() + m_nDefListDeep) == 0;
59 if( m_pPam->GetPoint()->GetContentIndex() )
60 AppendTextNode( bSpace ? AM_SPACE : AM_NOSPACE, false );
61 else if( bSpace )
62 AddParSpace();
64 // Increment the numbering depth
65 rInfo.IncDepth();
66 sal_uInt8 nLevel = static_cast<sal_uInt8>( (rInfo.GetDepth() <= MAXLEVEL ? rInfo.GetDepth()
67 : MAXLEVEL) - 1 );
69 // Create rules if needed
70 if( !rInfo.GetNumRule() )
72 sal_uInt16 nPos = m_xDoc->MakeNumRule( m_xDoc->GetUniqueNumRuleName() );
73 rInfo.SetNumRule( m_xDoc->GetNumRuleTable()[nPos] );
76 // Change the format for this level if that hasn't happened yet for this level
77 bool bNewNumFormat = rInfo.GetNumRule()->GetNumFormat( nLevel ) == nullptr;
78 bool bChangeNumFormat = false;
80 // Create the default numbering format
81 SwNumFormat aNumFormat( rInfo.GetNumRule()->Get(nLevel) );
82 rInfo.SetNodeStartValue( nLevel );
83 if( bNewNumFormat )
85 sal_uInt16 nChrFormatPoolId = 0;
86 if( HtmlTokenId::ORDERLIST_ON == nToken )
88 aNumFormat.SetNumberingType(SVX_NUM_ARABIC);
89 nChrFormatPoolId = RES_POOLCHR_NUM_LEVEL;
91 else
93 // We'll set a default style because the UI does the same. This meant a 9pt font, which
94 // was not the case in Netscape. That didn't bother anyone so far
95 // #i63395# - Only apply user defined default bullet font
96 if ( numfunc::IsDefBulletFontUserDefined() )
98 aNumFormat.SetBulletFont( &numfunc::GetDefBulletFont() );
100 aNumFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL);
101 aNumFormat.SetBulletChar( cBulletChar );
102 nChrFormatPoolId = RES_POOLCHR_BULLET_LEVEL;
105 sal_Int32 nAbsLSpace = HTML_NUMBER_BULLET_MARGINLEFT;
107 sal_Int32 nFirstLineIndent = HTML_NUMBER_BULLET_INDENT;
108 if( nLevel > 0 )
110 const SwNumFormat& rPrevNumFormat = rInfo.GetNumRule()->Get( nLevel-1 );
111 nAbsLSpace = nAbsLSpace + rPrevNumFormat.GetAbsLSpace();
112 nFirstLineIndent = rPrevNumFormat.GetFirstLineOffset();
114 aNumFormat.SetAbsLSpace( nAbsLSpace );
115 aNumFormat.SetFirstLineOffset( nFirstLineIndent );
116 aNumFormat.SetCharFormat( m_pCSS1Parser->GetCharFormatFromPool(nChrFormatPoolId) );
118 bChangeNumFormat = true;
120 else if( 1 != aNumFormat.GetStart() )
122 // If the layer has already been used, the start value may need to be set hard to the paragraph.
123 rInfo.SetNodeStartValue( nLevel, 1 );
126 // and set that in the options
127 OUString aId, aStyle, aClass, aLang, aDir;
128 OUString aBulletSrc;
129 sal_Int16 eVertOri = text::VertOrientation::NONE;
130 sal_uInt16 nWidth=USHRT_MAX, nHeight=USHRT_MAX;
131 const HTMLOptions& rHTMLOptions = GetOptions();
132 for (size_t i = rHTMLOptions.size(); i; )
134 const HTMLOption& rOption = rHTMLOptions[--i];
135 switch( rOption.GetToken() )
137 case HtmlOptionId::ID:
138 aId = rOption.GetString();
139 break;
140 case HtmlOptionId::TYPE:
141 if( bNewNumFormat && !rOption.GetString().isEmpty() )
143 switch( nToken )
145 case HtmlTokenId::ORDERLIST_ON:
146 bChangeNumFormat = true;
147 switch( rOption.GetString()[0] )
149 case 'A': aNumFormat.SetNumberingType(SVX_NUM_CHARS_UPPER_LETTER); break;
150 case 'a': aNumFormat.SetNumberingType(SVX_NUM_CHARS_LOWER_LETTER); break;
151 case 'I': aNumFormat.SetNumberingType(SVX_NUM_ROMAN_UPPER); break;
152 case 'i': aNumFormat.SetNumberingType(SVX_NUM_ROMAN_LOWER); break;
153 default: bChangeNumFormat = false;
155 break;
157 case HtmlTokenId::UNORDERLIST_ON:
158 aNumFormat.SetBulletChar( rOption.GetEnum(
159 aHTMLULTypeTable,aNumFormat.GetBulletChar() ) );
160 bChangeNumFormat = true;
161 break;
162 default: break;
165 break;
166 case HtmlOptionId::START:
168 sal_uInt16 nStart = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
169 if( bNewNumFormat )
171 aNumFormat.SetStart( nStart );
172 bChangeNumFormat = true;
174 else
176 rInfo.SetNodeStartValue( nLevel, nStart );
179 break;
180 case HtmlOptionId::STYLE:
181 aStyle = rOption.GetString();
182 break;
183 case HtmlOptionId::CLASS:
184 aClass = rOption.GetString();
185 break;
186 case HtmlOptionId::LANG:
187 aLang = rOption.GetString();
188 break;
189 case HtmlOptionId::DIR:
190 aDir = rOption.GetString();
191 break;
192 case HtmlOptionId::SRC:
193 if( bNewNumFormat )
195 aBulletSrc = rOption.GetString();
196 if( !InternalImgToPrivateURL(aBulletSrc) )
197 aBulletSrc = URIHelper::SmartRel2Abs( INetURLObject( m_sBaseURL ), aBulletSrc, Link<OUString *, bool>(), false );
199 break;
200 case HtmlOptionId::WIDTH:
201 nWidth = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
202 break;
203 case HtmlOptionId::HEIGHT:
204 nHeight = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
205 break;
206 case HtmlOptionId::ALIGN:
207 eVertOri = rOption.GetEnum( aHTMLImgVAlignTable, eVertOri );
208 break;
209 default: break;
213 if( !aBulletSrc.isEmpty() )
215 // A bullet list with graphics
216 aNumFormat.SetNumberingType(SVX_NUM_BITMAP);
218 // Create the graphic as a brush
219 SvxBrushItem aBrushItem( RES_BACKGROUND );
220 aBrushItem.SetGraphicLink( aBulletSrc );
221 aBrushItem.SetGraphicPos( GPOS_AREA );
223 // Only set size if given a width and a height
224 Size aTwipSz( nWidth, nHeight), *pTwipSz=nullptr;
225 if( nWidth!=USHRT_MAX && nHeight!=USHRT_MAX )
227 aTwipSz = o3tl::convert(aTwipSz, o3tl::Length::px, o3tl::Length::twip);
228 pTwipSz = &aTwipSz;
231 // Only set orientation if given one
232 aNumFormat.SetGraphicBrush( &aBrushItem, pTwipSz,
233 text::VertOrientation::NONE!=eVertOri ? &eVertOri : nullptr);
235 // Remember the graphic to not put it into the paragraph
236 m_aBulletGrfs[nLevel] = aBulletSrc;
237 bChangeNumFormat = true;
239 else
240 m_aBulletGrfs[nLevel].clear();
242 // don't number the current paragraph (for now)
244 sal_uInt8 nLvl = nLevel;
245 SetNodeNum( nLvl );
248 // create a new context
249 std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(nToken));
251 // Parse styles
252 if( HasStyleOptions( aStyle, aId, aClass, &aLang, &aDir ) )
254 SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() );
255 SvxCSS1PropertyInfo aPropInfo;
257 if( ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo, &aLang, &aDir ) )
259 if( bNewNumFormat )
261 if( aPropInfo.m_bLeftMargin )
263 // Default indent has already been added
264 tools::Long nAbsLSpace =
265 aNumFormat.GetAbsLSpace() - HTML_NUMBER_BULLET_MARGINLEFT;
266 if( aPropInfo.m_nLeftMargin < 0 &&
267 nAbsLSpace < -aPropInfo.m_nLeftMargin )
268 nAbsLSpace = 0U;
269 else if( aPropInfo.m_nLeftMargin > SHRT_MAX ||
270 nAbsLSpace + aPropInfo.m_nLeftMargin > SHRT_MAX )
271 nAbsLSpace = SHRT_MAX;
272 else
273 nAbsLSpace = nAbsLSpace + aPropInfo.m_nLeftMargin;
275 aNumFormat.SetAbsLSpace( nAbsLSpace );
276 bChangeNumFormat = true;
278 if( aPropInfo.m_bTextIndent )
280 short nTextIndent =
281 aItemSet.Get(RES_MARGIN_FIRSTLINE).GetTextFirstLineOffset();
282 aNumFormat.SetFirstLineOffset( nTextIndent );
283 bChangeNumFormat = true;
285 if( aPropInfo.m_bNumbering )
287 aNumFormat.SetNumberingType(aPropInfo.m_nNumberingType);
288 bChangeNumFormat = true;
290 if( aPropInfo.m_bBullet )
292 aNumFormat.SetBulletChar( aPropInfo.m_cBulletChar );
293 bChangeNumFormat = true;
296 aPropInfo.m_bLeftMargin = aPropInfo.m_bTextIndent = false;
297 if( !aPropInfo.m_bRightMargin )
298 aItemSet.ClearItem(RES_MARGIN_RIGHT); // superfluous?
300 // #i89812# - Perform change to list style before calling <DoPositioning(..)>,
301 // because <DoPositioning(..)> may open a new context and thus may
302 // clear the <SwHTMLNumRuleInfo> instance hold by local variable <rInfo>.
303 if( bChangeNumFormat )
305 rInfo.GetNumRule()->Set( nLevel, aNumFormat );
306 m_xDoc->ChgNumRuleFormats( *rInfo.GetNumRule() );
307 bChangeNumFormat = false;
310 DoPositioning(aItemSet, aPropInfo, xCntxt.get());
312 InsertAttrs(aItemSet, aPropInfo, xCntxt.get());
316 if( bChangeNumFormat )
318 rInfo.GetNumRule()->Set( nLevel, aNumFormat );
319 m_xDoc->ChgNumRuleFormats( *rInfo.GetNumRule() );
322 PushContext(xCntxt);
324 // set attributes to the current template
325 SetTextCollAttrs(m_aContexts.back().get());
328 void SwHTMLParser::EndNumberBulletList( HtmlTokenId nToken )
330 SwHTMLNumRuleInfo& rInfo = GetNumInfo();
332 // A new paragraph needs to be created, when
333 // - the current one isn't empty (it contains text or paragraph-bound objects)
334 // - the current one is numbered
335 bool bAppend = m_pPam->GetPoint()->GetContentIndex() > 0;
336 if( !bAppend )
338 SwTextNode* pTextNode = m_pPam->GetPointNode().GetTextNode();
340 bAppend = (pTextNode && ! pTextNode->IsOutline() && pTextNode->IsCountedInList()) ||
342 HasCurrentParaFlys();
345 bool bSpace = (rInfo.GetDepth() + m_nDefListDeep) == 1;
346 if( bAppend )
347 AppendTextNode( bSpace ? AM_SPACE : AM_NOSPACE, false );
348 else if( bSpace )
349 AddParSpace();
351 // get current context from stack
352 std::unique_ptr<HTMLAttrContext> xCntxt(nToken != HtmlTokenId::NONE ? PopContext(getOnToken(nToken)) : nullptr);
354 // Don't end a list because of a token, if the context wasn't created or mustn't be ended
355 if( rInfo.GetDepth()>0 && (nToken == HtmlTokenId::NONE || xCntxt) )
357 rInfo.DecDepth();
358 if( !rInfo.GetDepth() ) // was that the last level?
360 // The formats not yet modified are now modified, to ease editing
361 const SwNumFormat *pRefNumFormat = nullptr;
362 bool bChanged = false;
363 for( sal_uInt16 i=0; i<MAXLEVEL; i++ )
365 const SwNumFormat *pNumFormat = rInfo.GetNumRule()->GetNumFormat(i);
366 if( pNumFormat )
368 pRefNumFormat = pNumFormat;
370 else if( pRefNumFormat )
372 SwNumFormat aNumFormat( rInfo.GetNumRule()->Get(i) );
373 aNumFormat.SetNumberingType(pRefNumFormat->GetNumberingType() != SVX_NUM_BITMAP
374 ? pRefNumFormat->GetNumberingType() : SVX_NUM_CHAR_SPECIAL);
375 if( SVX_NUM_CHAR_SPECIAL == aNumFormat.GetNumberingType() )
377 // #i63395# - Only apply user defined default bullet font
378 if ( numfunc::IsDefBulletFontUserDefined() )
380 aNumFormat.SetBulletFont( &numfunc::GetDefBulletFont() );
382 aNumFormat.SetBulletChar( cBulletChar );
384 aNumFormat.SetAbsLSpace( (i+1) * HTML_NUMBER_BULLET_MARGINLEFT );
385 aNumFormat.SetFirstLineOffset( HTML_NUMBER_BULLET_INDENT );
386 aNumFormat.SetCharFormat( pRefNumFormat->GetCharFormat() );
387 rInfo.GetNumRule()->Set( i, aNumFormat );
388 bChanged = true;
391 if( bChanged )
392 m_xDoc->ChgNumRuleFormats( *rInfo.GetNumRule() );
394 // On the last append, the NumRule item and NodeNum object were copied.
395 // Now we need to delete them. ResetAttr deletes the NodeNum object as well
396 if (SwTextNode *pTextNode = m_pPam->GetPointNode().GetTextNode())
397 pTextNode->ResetAttr(RES_PARATR_NUMRULE);
399 rInfo.Clear();
401 else
403 // the next paragraph not numbered first
404 SetNodeNum( rInfo.GetLevel() );
408 // end attributes
409 bool bSetAttrs = false;
410 if (xCntxt)
412 EndContext(xCntxt.get());
413 xCntxt.reset();
414 bSetAttrs = true;
417 if( nToken != HtmlTokenId::NONE )
418 SetTextCollAttrs();
420 if( bSetAttrs )
421 SetAttr(); // Set paragraph attributes asap because of Javascript
425 void SwHTMLParser::NewNumberBulletListItem( HtmlTokenId nToken )
427 sal_uInt8 nLevel = GetNumInfo().GetLevel();
428 OUString aId, aStyle, aClass, aLang, aDir;
429 sal_uInt16 nStart = HtmlTokenId::LISTHEADER_ON != nToken
430 ? GetNumInfo().GetNodeStartValue( nLevel )
431 : USHRT_MAX;
432 if( USHRT_MAX != nStart )
433 GetNumInfo().SetNodeStartValue( nLevel );
435 const HTMLOptions& rHTMLOptions = GetOptions();
436 for (size_t i = rHTMLOptions.size(); i; )
438 const HTMLOption& rOption = rHTMLOptions[--i];
439 switch( rOption.GetToken() )
441 case HtmlOptionId::VALUE:
442 nStart = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
443 break;
444 case HtmlOptionId::ID:
445 aId = rOption.GetString();
446 break;
447 case HtmlOptionId::STYLE:
448 aStyle = rOption.GetString();
449 break;
450 case HtmlOptionId::CLASS:
451 aClass = rOption.GetString();
452 break;
453 case HtmlOptionId::LANG:
454 aLang = rOption.GetString();
455 break;
456 case HtmlOptionId::DIR:
457 aDir = rOption.GetString();
458 break;
459 default: break;
463 // create a new paragraph
464 if( m_pPam->GetPoint()->GetContentIndex() )
465 AppendTextNode( AM_NOSPACE, false );
466 m_bNoParSpace = false; // no space in <LI>!
468 SwTextNode* pTextNode = m_pPam->GetPointNode().GetTextNode();
469 if (!pTextNode)
471 SAL_WARN("sw.html", "No Text-Node at PaM-Position");
472 return;
475 const bool bCountedInList = nToken != HtmlTokenId::LISTHEADER_ON;
477 std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(nToken));
479 OUString aNumRuleName;
480 if( GetNumInfo().GetNumRule() )
482 aNumRuleName = GetNumInfo().GetNumRule()->GetName();
484 else
486 aNumRuleName = m_xDoc->GetUniqueNumRuleName();
487 SwNumRule aNumRule( aNumRuleName,
488 SvxNumberFormat::LABEL_WIDTH_AND_POSITION );
489 SwNumFormat aNumFormat( aNumRule.Get( 0 ) );
490 // #i63395# - Only apply user defined default bullet font
491 if ( numfunc::IsDefBulletFontUserDefined() )
493 aNumFormat.SetBulletFont( &numfunc::GetDefBulletFont() );
495 aNumFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL);
496 aNumFormat.SetBulletChar( cBulletChar ); // the bullet character !!
497 aNumFormat.SetCharFormat( m_pCSS1Parser->GetCharFormatFromPool(RES_POOLCHR_BULLET_LEVEL) );
498 aNumFormat.SetFirstLineOffset( HTML_NUMBER_BULLET_INDENT );
499 aNumRule.Set( 0, aNumFormat );
501 m_xDoc->MakeNumRule( aNumRuleName, &aNumRule );
503 OSL_ENSURE( m_nOpenParaToken == HtmlTokenId::NONE,
504 "Now an open paragraph element is lost" );
505 // We'll act like we're in a paragraph. On the next paragraph, at least numbering is gone,
506 // that's gonna be taken over by the next AppendTextNode
507 m_nOpenParaToken = nToken;
510 static_cast<SwContentNode *>(pTextNode)->SetAttr( SwNumRuleItem(aNumRuleName) );
511 pTextNode->SetAttrListLevel(nLevel);
512 // #i57656# - <IsCounted()> state of text node has to be adjusted accordingly.
513 if ( nLevel < MAXLEVEL )
515 pTextNode->SetCountedInList( bCountedInList );
517 // #i57919#
518 // correction of refactoring done by cws swnumtree
519 // - <nStart> contains the start value, if the numbering has to be restarted
520 // at this text node. Value <USHRT_MAX> indicates, that numbering isn't
521 // restarted at this text node
522 if ( nStart != USHRT_MAX )
524 pTextNode->SetListRestart( true );
525 pTextNode->SetAttrListRestartValue( nStart );
528 if( GetNumInfo().GetNumRule() )
529 GetNumInfo().GetNumRule()->SetInvalidRule( true );
531 // parse styles
532 if( HasStyleOptions( aStyle, aId, aClass, &aLang, &aDir ) )
534 SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() );
535 SvxCSS1PropertyInfo aPropInfo;
537 if( ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo, &aLang, &aDir ) )
539 DoPositioning(aItemSet, aPropInfo, xCntxt.get());
540 InsertAttrs(aItemSet, aPropInfo, xCntxt.get());
544 PushContext(xCntxt);
546 // set the new template
547 SetTextCollAttrs(m_aContexts.back().get());
549 // Refresh scroll bar
550 ShowStatline();
553 void SwHTMLParser::EndNumberBulletListItem( HtmlTokenId nToken, bool bSetColl )
555 // Create a new paragraph
556 if( nToken == HtmlTokenId::NONE && m_pPam->GetPoint()->GetContentIndex() )
557 AppendTextNode( AM_NOSPACE );
559 // Get context to that token and pop it from stack
560 std::unique_ptr<HTMLAttrContext> xCntxt;
561 auto nPos = m_aContexts.size();
562 nToken = getOnToken(nToken);
563 while (!xCntxt && nPos>m_nContextStMin)
565 HtmlTokenId nCntxtToken = m_aContexts[--nPos]->GetToken();
566 switch( nCntxtToken )
568 case HtmlTokenId::LI_ON:
569 case HtmlTokenId::LISTHEADER_ON:
570 if( nToken == HtmlTokenId::NONE || nToken == nCntxtToken )
572 xCntxt = std::move(m_aContexts[nPos]);
573 m_aContexts.erase( m_aContexts.begin() + nPos );
575 break;
576 case HtmlTokenId::ORDERLIST_ON:
577 case HtmlTokenId::UNORDERLIST_ON:
578 case HtmlTokenId::MENULIST_ON:
579 case HtmlTokenId::DIRLIST_ON:
580 // Don't care about LI/LH outside the current list
581 nPos = m_nContextStMin;
582 break;
583 default: break;
587 // end attributes
588 if (xCntxt)
590 EndContext(xCntxt.get());
591 SetAttr(); // set paragraph attributes asap because of Javascript
592 xCntxt.reset();
595 // set current template
596 if( bSetColl )
597 SetTextCollAttrs();
600 void SwHTMLParser::SetNodeNum( sal_uInt8 nLevel )
602 SwTextNode* pTextNode = m_pPam->GetPointNode().GetTextNode();
603 if (!pTextNode)
605 SAL_WARN("sw.html", "No Text-Node at PaM-Position");
606 return;
609 OSL_ENSURE( GetNumInfo().GetNumRule(), "No numbering rule" );
610 const OUString& rName = GetNumInfo().GetNumRule()->GetName();
611 static_cast<SwContentNode *>(pTextNode)->SetAttr( SwNumRuleItem(rName) );
613 pTextNode->SetAttrListLevel( nLevel );
614 pTextNode->SetCountedInList( false );
616 // Invalidate NumRule, it may have been set valid because of an EndAction
617 GetNumInfo().GetNumRule()->SetInvalidRule( false );
620 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */