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 "xmlExportDocumentHandler.hxx"
21 : #include <com/sun/star/sdb/CommandType.hpp>
22 : #include <com/sun/star/chart2/data/XDatabaseDataProvider.hpp>
23 : #include <com/sun/star/chart/XComplexDescriptionAccess.hpp>
24 : #include <com/sun/star/reflection/ProxyFactory.hpp>
25 : #include <comphelper/sequence.hxx>
26 : #include <comphelper/sequenceashashmap.hxx>
27 : #include <comphelper/documentconstants.hxx>
28 : #include <xmloff/attrlist.hxx>
29 : #include <xmloff/xmltoken.hxx>
30 : #include <xmloff/xmlement.hxx>
31 : #include <xmloff/xmluconv.hxx>
32 : #include <unotools/saveopt.hxx>
33 : #include <rtl/ustrbuf.hxx>
34 : #include <connectivity/dbtools.hxx>
35 :
36 : namespace rptxml
37 : {
38 : using namespace ::com::sun::star;
39 : using namespace ::xmloff::token;
40 :
41 0 : void lcl_exportPrettyPrinting(const uno::Reference< xml::sax::XDocumentHandler >& _xDelegatee)
42 : {
43 0 : SvtSaveOptions aSaveOpt;
44 0 : if ( aSaveOpt.IsPrettyPrinting() )
45 : {
46 0 : static const OUString s_sWhitespaces(" ");
47 0 : _xDelegatee->ignorableWhitespace(s_sWhitespaces);
48 0 : }
49 0 : }
50 :
51 0 : OUString lcl_createAttribute(const xmloff::token::XMLTokenEnum& _eNamespace,const xmloff::token::XMLTokenEnum& _eAttribute)
52 : {
53 0 : OUStringBuffer sQName;
54 : // ...if it's in our map, make the prefix
55 0 : sQName.append ( xmloff::token::GetXMLToken(_eNamespace) );
56 0 : sQName.append ( sal_Unicode(':') );
57 0 : sQName.append ( xmloff::token::GetXMLToken(_eAttribute) );
58 0 : return sQName.makeStringAndClear();
59 : }
60 :
61 0 : void lcl_correctCellAddress(const OUString & _sName, const uno::Reference< xml::sax::XAttributeList > & xAttribs)
62 : {
63 0 : SvXMLAttributeList* pList = SvXMLAttributeList::getImplementation(xAttribs);
64 0 : OUString sCellAddress = pList->getValueByName(_sName);
65 0 : const sal_Int32 nPos = sCellAddress.lastIndexOf('$');
66 0 : if ( nPos != -1 )
67 : {
68 0 : sCellAddress = sCellAddress.copy(0,nPos);
69 0 : sCellAddress += OUString("$65535");
70 0 : pList->RemoveAttribute(_sName);
71 0 : pList->AddAttribute(_sName,sCellAddress);
72 0 : }
73 0 : }
74 :
75 0 : ExportDocumentHandler::ExportDocumentHandler(uno::Reference< uno::XComponentContext > const & context) :
76 : m_xContext(context)
77 : ,m_nCurrentCellIndex(0)
78 : ,m_nColumnCount(0)
79 : ,m_bTableRowsStarted(false)
80 : ,m_bFirstRowExported(false)
81 : ,m_bExportChar(false)
82 0 : ,m_bCountColumnHeader(false)
83 : {
84 0 : }
85 : // -----------------------------------------------------------------------------
86 0 : ExportDocumentHandler::~ExportDocumentHandler()
87 : {
88 0 : if ( m_xProxy.is() )
89 : {
90 0 : m_xProxy->setDelegator( NULL );
91 0 : m_xProxy.clear();
92 : }
93 0 : }
94 0 : IMPLEMENT_GET_IMPLEMENTATION_ID(ExportDocumentHandler)
95 0 : IMPLEMENT_FORWARD_REFCOUNT( ExportDocumentHandler, ExportDocumentHandler_BASE )
96 : //------------------------------------------------------------------------
97 0 : OUString SAL_CALL ExportDocumentHandler::getImplementationName( ) throw(uno::RuntimeException)
98 : {
99 0 : return getImplementationName_Static();
100 : }
101 :
102 : //------------------------------------------------------------------------
103 0 : sal_Bool SAL_CALL ExportDocumentHandler::supportsService( const OUString& ServiceName ) throw(uno::RuntimeException)
104 : {
105 0 : return ::comphelper::existsValue(ServiceName,getSupportedServiceNames_static());
106 : }
107 :
108 : //------------------------------------------------------------------------
109 0 : uno::Sequence< OUString > SAL_CALL ExportDocumentHandler::getSupportedServiceNames( ) throw(uno::RuntimeException)
110 : {
111 0 : uno::Sequence< OUString > aSupported;
112 0 : if ( m_xServiceInfo.is() )
113 0 : aSupported = m_xServiceInfo->getSupportedServiceNames();
114 0 : return ::comphelper::concatSequences(getSupportedServiceNames_static(),aSupported);
115 : }
116 :
117 : //------------------------------------------------------------------------
118 0 : OUString ExportDocumentHandler::getImplementationName_Static( ) throw(uno::RuntimeException)
119 : {
120 0 : return OUString("com.sun.star.comp.report.ExportDocumentHandler");
121 : }
122 :
123 : //------------------------------------------------------------------------
124 0 : uno::Sequence< OUString > ExportDocumentHandler::getSupportedServiceNames_static( ) throw(uno::RuntimeException)
125 : {
126 0 : uno::Sequence< OUString > aSupported(1);
127 0 : aSupported[0] = OUString("com.sun.star.report.ExportDocumentHandler");
128 0 : return aSupported;
129 : }
130 :
131 : //------------------------------------------------------------------------
132 0 : uno::Reference< uno::XInterface > SAL_CALL ExportDocumentHandler::create( const uno::Reference< uno::XComponentContext >& _rxContext )
133 : {
134 0 : return *(new ExportDocumentHandler( _rxContext ));
135 : }
136 : // xml::sax::XDocumentHandler:
137 0 : void SAL_CALL ExportDocumentHandler::startDocument() throw (uno::RuntimeException, xml::sax::SAXException)
138 : {
139 0 : m_xDelegatee->startDocument();
140 0 : }
141 :
142 0 : void SAL_CALL ExportDocumentHandler::endDocument() throw (uno::RuntimeException, xml::sax::SAXException)
143 : {
144 0 : m_xDelegatee->endDocument();
145 0 : }
146 :
147 0 : void SAL_CALL ExportDocumentHandler::startElement(const OUString & _sName, const uno::Reference< xml::sax::XAttributeList > & xAttribs) throw (uno::RuntimeException, xml::sax::SAXException)
148 : {
149 0 : bool bExport = true;
150 0 : if ( _sName == "office:chart" )
151 : {
152 0 : SvXMLAttributeList* pList = new SvXMLAttributeList();
153 0 : uno::Reference< xml::sax::XAttributeList > xNewAttribs = pList;
154 0 : OUStringBuffer sValue;
155 : static SvXMLEnumMapEntry aXML_CommnadTypeEnumMap[] =
156 : {
157 : { XML_TABLE, sdb::CommandType::TABLE },
158 : { XML_QUERY, sdb::CommandType::QUERY },
159 : { XML_TOKEN_INVALID, 0 }
160 : };
161 0 : if ( SvXMLUnitConverter::convertEnum( sValue, static_cast<sal_uInt16>(m_xDatabaseDataProvider->getCommandType()),aXML_CommnadTypeEnumMap ) )
162 : {
163 0 : pList->AddAttribute(lcl_createAttribute(XML_NP_RPT,XML_COMMAND_TYPE),sValue.makeStringAndClear());
164 : }
165 0 : const OUString sComamnd = m_xDatabaseDataProvider->getCommand();
166 0 : if ( !sComamnd.isEmpty() )
167 0 : pList->AddAttribute(lcl_createAttribute(XML_NP_RPT,XML_COMMAND),sComamnd);
168 :
169 0 : const OUString sFilter( m_xDatabaseDataProvider->getFilter() );
170 0 : if ( !sFilter.isEmpty() )
171 0 : pList->AddAttribute(lcl_createAttribute(XML_NP_RPT,XML_FILTER),sFilter);
172 :
173 0 : const sal_Bool bEscapeProcessing( m_xDatabaseDataProvider->getEscapeProcessing() );
174 0 : if ( !bEscapeProcessing )
175 0 : pList->AddAttribute(lcl_createAttribute(XML_NP_RPT,XML_ESCAPE_PROCESSING),::xmloff::token::GetXMLToken( XML_FALSE ));
176 :
177 0 : pList->AddAttribute(lcl_createAttribute(XML_NP_OFFICE,XML_MIMETYPE),MIMETYPE_OASIS_OPENDOCUMENT_CHART);
178 :
179 0 : m_xDelegatee->startElement(lcl_createAttribute(XML_NP_OFFICE,XML_REPORT),xNewAttribs);
180 :
181 0 : const OUString sTableCalc = lcl_createAttribute(XML_NP_TABLE,XML_CALCULATION_SETTINGS);
182 0 : m_xDelegatee->startElement(sTableCalc,NULL);
183 0 : pList = new SvXMLAttributeList();
184 0 : uno::Reference< xml::sax::XAttributeList > xNullAttr = pList;
185 0 : pList->AddAttribute(lcl_createAttribute(XML_NP_TABLE,XML_DATE_VALUE),OUString("1900-01-01"));
186 :
187 0 : const OUString sNullDate = lcl_createAttribute(XML_NP_TABLE,XML_NULL_DATE);
188 0 : m_xDelegatee->startElement(sNullDate,xNullAttr);
189 0 : m_xDelegatee->endElement(sNullDate);
190 0 : m_xDelegatee->endElement(sTableCalc);
191 0 : bExport = false;
192 : }
193 0 : else if ( _sName == "table:table" )
194 : {
195 0 : m_xDelegatee->startElement(lcl_createAttribute(XML_NP_RPT,XML_DETAIL),NULL);
196 0 : lcl_exportPrettyPrinting(m_xDelegatee);
197 : }
198 0 : else if ( _sName == "table:table-header-rows" )
199 : {
200 0 : m_bCountColumnHeader = true;
201 : }
202 0 : else if ( m_bCountColumnHeader && _sName.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("table:table-cell")) )
203 : {
204 0 : ++m_nColumnCount;
205 : }
206 0 : else if ( _sName == "table:table-rows" )
207 : {
208 0 : m_xDelegatee->startElement(_sName,xAttribs);
209 0 : exportTableRows();
210 0 : bExport = false;
211 0 : m_bTableRowsStarted = true;
212 0 : m_bFirstRowExported = true;
213 : }
214 0 : else if ( m_bTableRowsStarted && m_bFirstRowExported && (_sName.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("table:table-row")) || _sName.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("table:table-cell"))) )
215 0 : bExport = false;
216 0 : else if ( _sName == "chart:plot-area" )
217 : {
218 0 : SvXMLAttributeList* pList = SvXMLAttributeList::getImplementation(xAttribs);
219 0 : pList->RemoveAttribute(OUString("table:cell-range-address"));
220 : }
221 0 : else if ( _sName == "chart:categories" )
222 : {
223 0 : static OUString s_sCellAddress(lcl_createAttribute(XML_NP_TABLE,XML_CELL_RANGE_ADDRESS));
224 0 : lcl_correctCellAddress(s_sCellAddress,xAttribs);
225 : }
226 0 : else if ( _sName == "chart:series" )
227 : {
228 0 : static OUString s_sCellAddress(lcl_createAttribute(XML_NP_CHART,XML_VALUES_CELL_RANGE_ADDRESS));
229 0 : lcl_correctCellAddress(s_sCellAddress,xAttribs);
230 : }
231 0 : else if ( m_bTableRowsStarted && !m_bFirstRowExported && _sName.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("table:table-cell")) )
232 : {
233 0 : SvXMLAttributeList* pList = SvXMLAttributeList::getImplementation(xAttribs);
234 0 : static OUString s_sValue(lcl_createAttribute(XML_NP_OFFICE,XML_VALUE));
235 0 : pList->RemoveAttribute(s_sValue);
236 : }
237 0 : else if ( m_bTableRowsStarted && _sName.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("text:p")) )
238 : {
239 0 : bExport = false;
240 : }
241 0 : if ( bExport )
242 0 : m_xDelegatee->startElement(_sName,xAttribs);
243 0 : }
244 : // -----------------------------------------------------------------------------
245 0 : void SAL_CALL ExportDocumentHandler::endElement(const OUString & _sName) throw (uno::RuntimeException, xml::sax::SAXException)
246 : {
247 0 : bool bExport = true;
248 0 : OUString sNewName = _sName;
249 0 : if ( _sName == "office:chart" )
250 : {
251 0 : sNewName = lcl_createAttribute(XML_NP_OFFICE,XML_REPORT);
252 : }
253 0 : else if ( _sName == "table:table" )
254 : {
255 0 : m_xDelegatee->endElement(_sName);
256 0 : lcl_exportPrettyPrinting(m_xDelegatee);
257 0 : sNewName = lcl_createAttribute(XML_NP_RPT,XML_DETAIL);
258 : }
259 0 : else if ( _sName == "table:table-header-rows" )
260 : {
261 0 : m_bCountColumnHeader = false;
262 : }
263 0 : else if ( _sName == "table:table-rows" )
264 0 : m_bTableRowsStarted = false;
265 0 : else if ( m_bTableRowsStarted && m_bFirstRowExported && (_sName.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("table:table-row")) || _sName.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("table:table-cell"))) )
266 0 : bExport = false;
267 0 : else if ( m_bTableRowsStarted && _sName.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("table:table-row")) )
268 0 : m_bFirstRowExported = true;
269 0 : else if ( m_bTableRowsStarted && _sName.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("text:p")) )
270 : {
271 0 : bExport = !m_bFirstRowExported;
272 : }
273 :
274 0 : if ( bExport )
275 0 : m_xDelegatee->endElement(sNewName);
276 0 : }
277 :
278 0 : void SAL_CALL ExportDocumentHandler::characters(const OUString & aChars) throw (uno::RuntimeException, xml::sax::SAXException)
279 : {
280 0 : if ( !(m_bTableRowsStarted || m_bFirstRowExported) )
281 : {
282 0 : m_xDelegatee->characters(aChars);
283 : }
284 0 : else if ( m_bExportChar )
285 : {
286 0 : static const OUString s_sZero("0");
287 0 : m_xDelegatee->characters(s_sZero);
288 : }
289 0 : }
290 :
291 0 : void SAL_CALL ExportDocumentHandler::ignorableWhitespace(const OUString & aWhitespaces) throw (uno::RuntimeException, xml::sax::SAXException)
292 : {
293 0 : m_xDelegatee->ignorableWhitespace(aWhitespaces);
294 0 : }
295 :
296 0 : void SAL_CALL ExportDocumentHandler::processingInstruction(const OUString & aTarget, const OUString & aData) throw (uno::RuntimeException, xml::sax::SAXException)
297 : {
298 0 : m_xDelegatee->processingInstruction(aTarget,aData);
299 0 : }
300 :
301 0 : void SAL_CALL ExportDocumentHandler::setDocumentLocator(const uno::Reference< xml::sax::XLocator > & xLocator) throw (uno::RuntimeException, xml::sax::SAXException)
302 : {
303 0 : m_xDelegatee->setDocumentLocator(xLocator);
304 0 : }
305 0 : void SAL_CALL ExportDocumentHandler::initialize( const uno::Sequence< uno::Any >& _aArguments ) throw (uno::Exception, uno::RuntimeException)
306 : {
307 0 : ::osl::MutexGuard aGuard(m_aMutex);
308 0 : comphelper::SequenceAsHashMap aArgs(_aArguments);
309 0 : m_xDelegatee = aArgs.getUnpackedValueOrDefault("DocumentHandler",m_xDelegatee);
310 0 : m_xModel = aArgs.getUnpackedValueOrDefault("Model",m_xModel);
311 :
312 : OSL_ENSURE(m_xDelegatee.is(),"No document handler avialable!");
313 0 : if ( !m_xDelegatee.is() || !m_xModel.is() )
314 0 : throw uno::Exception();
315 :
316 0 : m_xDatabaseDataProvider.set(m_xModel->getDataProvider(),uno::UNO_QUERY);
317 0 : if ( !m_xDatabaseDataProvider.is() || !m_xDatabaseDataProvider->getActiveConnection().is() )
318 0 : throw uno::Exception();
319 :
320 0 : uno::Reference< reflection::XProxyFactory > xProxyFactory = reflection::ProxyFactory::create( m_xContext );
321 0 : m_xProxy = xProxyFactory->createProxy(m_xDelegatee.get());
322 0 : ::comphelper::query_aggregation(m_xProxy,m_xDelegatee);
323 0 : m_xTypeProvider.set(m_xDelegatee,uno::UNO_QUERY);
324 0 : m_xServiceInfo.set(m_xDelegatee,uno::UNO_QUERY);
325 :
326 : // set ourself as delegator
327 0 : m_xProxy->setDelegator( *this );
328 0 : const OUString sCommand = m_xDatabaseDataProvider->getCommand();
329 0 : if ( !sCommand.isEmpty() )
330 0 : m_aColumns = ::dbtools::getFieldNamesByCommandDescriptor(m_xDatabaseDataProvider->getActiveConnection()
331 0 : ,m_xDatabaseDataProvider->getCommandType()
332 0 : ,sCommand);
333 :
334 0 : uno::Reference< chart::XComplexDescriptionAccess > xDataProvider(m_xDatabaseDataProvider,uno::UNO_QUERY);
335 0 : if ( xDataProvider.is() )
336 : {
337 0 : m_aColumns.realloc(1);
338 0 : uno::Sequence< OUString > aColumnNames = xDataProvider->getColumnDescriptions();
339 0 : for(sal_Int32 i = 0 ; i < aColumnNames.getLength();++i)
340 : {
341 0 : if ( !aColumnNames[i].isEmpty() )
342 : {
343 0 : sal_Int32 nCount = m_aColumns.getLength();
344 0 : m_aColumns.realloc(nCount+1);
345 0 : m_aColumns[nCount] = aColumnNames[i];
346 : }
347 0 : }
348 0 : }
349 0 : }
350 : // --------------------------------------------------------------------------------
351 0 : uno::Any SAL_CALL ExportDocumentHandler::queryInterface( const uno::Type& _rType ) throw (uno::RuntimeException)
352 : {
353 0 : uno::Any aReturn = ExportDocumentHandler_BASE::queryInterface(_rType);
354 0 : return aReturn.hasValue() ? aReturn : (m_xProxy.is() ? m_xProxy->queryAggregation(_rType) : aReturn);
355 : }
356 : // --------------------------------------------------------------------------------
357 0 : uno::Sequence< uno::Type > SAL_CALL ExportDocumentHandler::getTypes( ) throw (uno::RuntimeException)
358 : {
359 0 : if ( m_xTypeProvider.is() )
360 : return ::comphelper::concatSequences(
361 : ExportDocumentHandler_BASE::getTypes(),
362 0 : m_xTypeProvider->getTypes()
363 0 : );
364 0 : return ExportDocumentHandler_BASE::getTypes();
365 : }
366 : // -----------------------------------------------------------------------------
367 0 : void ExportDocumentHandler::exportTableRows()
368 : {
369 0 : const OUString sRow( lcl_createAttribute(XML_NP_TABLE, XML_TABLE_ROW) );
370 0 : m_xDelegatee->startElement(sRow,NULL);
371 :
372 0 : const OUString sValueType( lcl_createAttribute(XML_NP_OFFICE, XML_VALUE_TYPE) );
373 :
374 0 : const static OUString s_sFieldPrefix("field:[");
375 0 : const static OUString s_sFieldPostfix("]");
376 0 : const OUString sCell( lcl_createAttribute(XML_NP_TABLE, XML_TABLE_CELL) );
377 0 : const OUString sP( lcl_createAttribute(XML_NP_TEXT, XML_P) );
378 0 : const OUString sFtext(lcl_createAttribute(XML_NP_RPT,XML_FORMATTED_TEXT) );
379 0 : const OUString sRElement(lcl_createAttribute(XML_NP_RPT,XML_REPORT_ELEMENT) );
380 0 : const OUString sRComponent( lcl_createAttribute(XML_NP_RPT,XML_REPORT_COMPONENT) ) ;
381 0 : const OUString sFormulaAttrib( lcl_createAttribute(XML_NP_RPT,XML_FORMULA) );
382 0 : const static OUString s_sString("string");
383 0 : const static OUString s_sFloat("float");
384 :
385 0 : SvXMLAttributeList* pCellAtt = new SvXMLAttributeList();
386 0 : uno::Reference< xml::sax::XAttributeList > xCellAtt = pCellAtt;
387 0 : pCellAtt->AddAttribute(sValueType,s_sString);
388 :
389 0 : bool bRemoveString = true;
390 0 : OUString sFormula;
391 0 : const sal_Int32 nCount = m_aColumns.getLength();
392 0 : if ( m_nColumnCount > nCount )
393 : {
394 0 : const sal_Int32 nEmptyCellCount = m_nColumnCount - nCount;
395 0 : for(sal_Int32 i = 0; i < nEmptyCellCount ; ++i)
396 : {
397 0 : m_xDelegatee->startElement(sCell,xCellAtt);
398 0 : if ( bRemoveString )
399 : {
400 0 : bRemoveString = false;
401 0 : pCellAtt->RemoveAttribute(sValueType);
402 0 : pCellAtt->AddAttribute(sValueType,s_sFloat);
403 : }
404 0 : m_xDelegatee->startElement(sP,NULL);
405 0 : m_xDelegatee->endElement(sP);
406 0 : m_xDelegatee->endElement(sCell);
407 : }
408 : }
409 0 : for(sal_Int32 i = 0; i < nCount ; ++i)
410 : {
411 0 : sFormula = s_sFieldPrefix;
412 0 : sFormula += m_aColumns[i];
413 0 : sFormula += s_sFieldPostfix;
414 0 : SvXMLAttributeList* pList = new SvXMLAttributeList();
415 0 : uno::Reference< xml::sax::XAttributeList > xAttribs = pList;
416 0 : pList->AddAttribute(sFormulaAttrib,sFormula);
417 :
418 0 : m_xDelegatee->startElement(sCell,xCellAtt);
419 0 : if ( bRemoveString )
420 : {
421 0 : bRemoveString = false;
422 0 : pCellAtt->RemoveAttribute(sValueType);
423 0 : pCellAtt->AddAttribute(sValueType,s_sFloat);
424 : }
425 0 : m_xDelegatee->startElement(sP,NULL);
426 0 : m_xDelegatee->startElement(sFtext,xAttribs);
427 0 : m_xDelegatee->startElement(sRElement,NULL);
428 0 : m_xDelegatee->startElement(sRComponent,NULL);
429 :
430 0 : m_xDelegatee->endElement(sRComponent);
431 0 : m_xDelegatee->endElement(sRElement);
432 0 : m_xDelegatee->endElement(sFtext);
433 0 : m_xDelegatee->endElement(sP);
434 0 : m_xDelegatee->endElement(sCell);
435 0 : }
436 :
437 0 : m_xDelegatee->endElement(sRow);
438 0 : }
439 : // -----------------------------------------------------------------------------
440 : } // namespace rptxml
441 : // -----------------------------------------------------------------------------
442 :
443 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|