summaryrefslogtreecommitdiff
path: root/xmloff
diff options
context:
space:
mode:
authorTomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>2017-02-26 22:48:06 +0100
committerTomaž Vajngerl <quikee@gmail.com>2017-04-04 13:39:29 +0000
commit9009663deb8f0862f419fd99bf0b761c7f923eff (patch)
treeea25976de0919f9d2161037d83be0eace4c1070b /xmloff
parent1931b5b01c6fdaa204d26ec4b9675dad16373cf2 (diff)
tdf#83257 [API-CHANGE] Pivot chart implementation
This is a squashed commit of the pivot chart implementation. Some of the changes: - Add pivot chart specific (pivot table) data provider which provides the data from a pivot table to the associated chart. - When inserting a chart and the cursor is in a pivot table, in that case insert a pivot chart - Modify the pivot chart when the pivot table changes - Collect and set the number format for the values - isDataFromSpreadsheet check for the creation wizard - In ChartView (and VLegend) check if the data provider is a pivot chart data provider and get the pivot table field names to create the buttons on the UI. - Adds the functionallity to show a filter pop-up (from calc) when clicking on row / column / page field buttons. - Remove (X)PopupRequest as we won't need it. - Add ODF import/export for pivot charts: + Added loext:data-pilot-source attribute on chart:chart which is the internal name of the pivot table with which the pivot chart is associated with. If the element is present, then the it means the chart is a pivot chart, else it is a normal chart + Added service to create pivot chart data provider through UNO + Add new methods to XPivotChartDataProvider to create value and label data sequences separately from the data source, which is needed for pivot chart import + When importing defer setting the data provider until a later time when we know if we are creating a chart od a pivot chart - Pivot chart ODF round-trip test - Add table pivot chart supplier API: This adds the XTablePivotChartSupplier and related interfaces so we can access, create, delete pivot charts from UNO in a sheet document. With this we now distinguish between normal charts and pivot charts. This was mainly needed because we can't extend the "published" interfaces of TableChartSupplier. - Added an extensive test, which uses the API to create a new pivot chart when there was none, and checks that the pivot chart updates when the pivot table updates. Change-Id: Ia9ed96fd6b1d342e61c2f7f9fa33a5e03dda21af Reviewed-on: https://gerrit.libreoffice.org/36023 Reviewed-by: Tomaž Vajngerl <quikee@gmail.com> Tested-by: Tomaž Vajngerl <quikee@gmail.com>
Diffstat (limited to 'xmloff')
-rw-r--r--xmloff/inc/SchXMLImport.hxx3
-rw-r--r--xmloff/source/chart/SchXMLChartContext.cxx72
-rw-r--r--xmloff/source/chart/SchXMLChartContext.hxx2
-rw-r--r--xmloff/source/chart/SchXMLExport.cxx8
-rw-r--r--xmloff/source/chart/SchXMLImport.cxx61
-rw-r--r--xmloff/source/chart/SchXMLSeries2Context.cxx50
-rw-r--r--xmloff/source/chart/SchXMLTools.cxx21
-rw-r--r--xmloff/source/core/xmltoken.cxx1
-rw-r--r--xmloff/source/token/tokens.txt3
9 files changed, 152 insertions, 69 deletions
diff --git a/xmloff/inc/SchXMLImport.hxx b/xmloff/inc/SchXMLImport.hxx
index 233ecde5b6c9..a1c3f698dca3 100644
--- a/xmloff/inc/SchXMLImport.hxx
+++ b/xmloff/inc/SchXMLImport.hxx
@@ -97,7 +97,8 @@ enum SchXMLChartAttrMap
XML_TOK_CHART_HEIGHT,
XML_TOK_CHART_STYLE_NAME,
XML_TOK_CHART_COL_MAPPING,
- XML_TOK_CHART_ROW_MAPPING
+ XML_TOK_CHART_ROW_MAPPING,
+ XML_TOK_CHART_DATA_PILOT_SOURCE,
};
enum SchXMLPlotAreaAttrTokenMap
diff --git a/xmloff/source/chart/SchXMLChartContext.cxx b/xmloff/source/chart/SchXMLChartContext.cxx
index 4ce36805398c..1dc1c145e16e 100644
--- a/xmloff/source/chart/SchXMLChartContext.cxx
+++ b/xmloff/source/chart/SchXMLChartContext.cxx
@@ -52,11 +52,15 @@
#include <com/sun/star/chart2/XChartDocument.hpp>
#include <com/sun/star/chart2/data/XDataSink.hpp>
+#include <com/sun/star/chart2/data/XPivotTableDataProvider.hpp>
#include <com/sun/star/chart2/XDataSeriesContainer.hpp>
#include <com/sun/star/chart2/XCoordinateSystemContainer.hpp>
#include <com/sun/star/chart2/XChartTypeContainer.hpp>
#include <com/sun/star/chart2/XTitled.hpp>
+#include <com/sun/star/container/XChild.hpp>
+#include <com/sun/star/chart2/data/XDataReceiver.hpp>
+
using namespace com::sun::star;
using namespace ::xmloff::token;
using com::sun::star::uno::Reference;
@@ -237,10 +241,67 @@ SchXMLChartContext::SchXMLChartContext( SchXMLImportHelper& rImpHelper,
SchXMLChartContext::~SchXMLChartContext()
{}
+void lcl_setDataProvider(uno::Reference<chart2::XChartDocument> const & xChartDoc, OUString const & sDataPilotSource)
+{
+ if (!xChartDoc.is())
+ return;
+
+ try
+ {
+ uno::Reference<container::XChild> xChild(xChartDoc, uno::UNO_QUERY);
+ uno::Reference<chart2::data::XDataReceiver> xDataReceiver(xChartDoc, uno::UNO_QUERY);
+ if (xChild.is() && xDataReceiver.is())
+ {
+ bool bHasOwnData = true;
+
+ Reference<lang::XMultiServiceFactory> xFact(xChild->getParent(), uno::UNO_QUERY);
+ if (xFact.is())
+ {
+ if (!xChartDoc->getDataProvider().is())
+ {
+ bool bHasDataPilotSource = !sDataPilotSource.isEmpty();
+ OUString aDataProviderServiceName("com.sun.star.chart2.data.DataProvider");
+ if (bHasDataPilotSource)
+ aDataProviderServiceName = "com.sun.star.chart2.data.PivotTableDataProvider";
+
+ const uno::Sequence<OUString> aServiceNames(xFact->getAvailableServiceNames());
+
+ if (std::find(aServiceNames.begin(), aServiceNames.end(), aDataProviderServiceName) != aServiceNames.end())
+ {
+ Reference<chart2::data::XDataProvider> xProvider(xFact->createInstance(aDataProviderServiceName), uno::UNO_QUERY);
+
+ if (xProvider.is())
+ {
+ xDataReceiver->attachDataProvider(xProvider);
+ if (bHasDataPilotSource)
+ {
+ Reference<chart2::data::XPivotTableDataProvider> xPivotTableDataProvider(xProvider, uno::UNO_QUERY);
+ xPivotTableDataProvider->setPivotTableName(sDataPilotSource);
+ }
+ bHasOwnData = false;
+ }
+ }
+ }
+ else
+ bHasOwnData = false;
+ }
+ // else we have no parent => we have our own data
+
+ if (bHasOwnData && ! xChartDoc->hasInternalDataProvider())
+ xChartDoc->createInternalDataProvider(false);
+ }
+ }
+ catch (const uno::Exception & rEx)
+ {
+ OString aBStr(OUStringToOString(rEx.Message, RTL_TEXTENCODING_ASCII_US));
+ SAL_INFO("xmloff.chart", "SchXMLChartContext::StartElement(): Exception caught: " << aBStr);
+ }
+}
+
void SchXMLChartContext::StartElement( const uno::Reference< xml::sax::XAttributeList >& xAttrList )
{
// parse attributes
- sal_Int16 nAttrCount = xAttrList.is()? xAttrList->getLength(): 0;
+ sal_Int16 nAttrCount = xAttrList.is() ? xAttrList->getLength() : 0;
const SvXMLTokenMap& rAttrTokenMap = mrImportHelper.GetChartAttrTokenMap();
uno::Reference< embed::XVisualObject > xVisualObject( mrImportHelper.GetChartDocument(), uno::UNO_QUERY);
@@ -264,10 +325,12 @@ void SchXMLChartContext::StartElement( const uno::Reference< xml::sax::XAttribut
switch( rAttrTokenMap.Get( nPrefix, aLocalName ))
{
+ case XML_TOK_CHART_DATA_PILOT_SOURCE:
+ msDataPilotSource = aValue;
+ break;
case XML_TOK_CHART_HREF:
m_aXLinkHRefAttributeToIndicateDataProvider = aValue;
break;
-
case XML_TOK_CHART_CLASS:
{
OUString sClassName;
@@ -328,6 +391,11 @@ void SchXMLChartContext::StartElement( const uno::Reference< xml::sax::XAttribut
}
}
+ uno::Reference<chart::XChartDocument> xDoc = mrImportHelper.GetChartDocument();
+ uno::Reference<chart2::XChartDocument> xNewDoc(xDoc, uno::UNO_QUERY);
+
+ lcl_setDataProvider(xNewDoc, msDataPilotSource);
+
if( aOldChartTypeName.isEmpty() )
{
SAL_WARN("xmloff.chart", "need a charttype to create a diagram" );
diff --git a/xmloff/source/chart/SchXMLChartContext.hxx b/xmloff/source/chart/SchXMLChartContext.hxx
index 649c9b6cc387..11b69987ac93 100644
--- a/xmloff/source/chart/SchXMLChartContext.hxx
+++ b/xmloff/source/chart/SchXMLChartContext.hxx
@@ -104,6 +104,8 @@ private:
OUString msCategoriesAddress;
OUString msChartAddress;
+ OUString msDataPilotSource;
+
SeriesDefaultsAndStyles maSeriesDefaultsAndStyles;
tSchXMLLSequencesPerIndex maLSequencesPerIndex;
diff --git a/xmloff/source/chart/SchXMLExport.cxx b/xmloff/source/chart/SchXMLExport.cxx
index 9b8c205fa038..de3e32c7cd58 100644
--- a/xmloff/source/chart/SchXMLExport.cxx
+++ b/xmloff/source/chart/SchXMLExport.cxx
@@ -90,6 +90,7 @@
#include <com/sun/star/chart2/data/XDataReceiver.hpp>
#include <com/sun/star/chart2/data/XDataProvider.hpp>
#include <com/sun/star/chart2/data/XDatabaseDataProvider.hpp>
+#include <com/sun/star/chart2/data/XPivotTableDataProvider.hpp>
#include <com/sun/star/chart2/data/XRangeXMLConversion.hpp>
#include <com/sun/star/chart2/data/XTextualDataSequence.hpp>
#include <com/sun/star/chart2/data/XNumericalDataSequence.hpp>
@@ -1213,6 +1214,13 @@ void SchXMLExportHelper_Impl::parseDocument( Reference< chart::XChartDocument >
mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_TYPE, XML_SIMPLE );
}
+ Reference<chart2::data::XPivotTableDataProvider> xPivotTableDataProvider(xNewDoc->getDataProvider(), uno::UNO_QUERY);
+ if (xPivotTableDataProvider.is())
+ {
+ OUString sPivotTableName = xPivotTableDataProvider->getPivotTableName();
+ mrExport.AddAttribute(XML_NAMESPACE_LO_EXT, XML_DATA_PILOT_SOURCE, sPivotTableName);
+ }
+
OUString sChartType( xDiagram->getDiagramType() );
// attributes
diff --git a/xmloff/source/chart/SchXMLImport.cxx b/xmloff/source/chart/SchXMLImport.cxx
index 5d33fc69301b..10d8fc105b39 100644
--- a/xmloff/source/chart/SchXMLImport.cxx
+++ b/xmloff/source/chart/SchXMLImport.cxx
@@ -249,6 +249,7 @@ const SvXMLTokenMap& SchXMLImportHelper::GetChartAttrTokenMap()
{ XML_NAMESPACE_CHART, XML_STYLE_NAME, XML_TOK_CHART_STYLE_NAME },
{ XML_NAMESPACE_CHART, XML_COLUMN_MAPPING, XML_TOK_CHART_COL_MAPPING },
{ XML_NAMESPACE_CHART, XML_ROW_MAPPING, XML_TOK_CHART_ROW_MAPPING },
+ { XML_NAMESPACE_LO_EXT, XML_DATA_PILOT_SOURCE, XML_TOK_CHART_DATA_PILOT_SOURCE },
XML_TOKEN_MAP_END
};
@@ -574,65 +575,37 @@ SvXMLImportContext* SchXMLImport::CreateStylesContext(
return pStylesCtxt;
}
-void SAL_CALL SchXMLImport::setTargetDocument( const uno::Reference< lang::XComponent >& xDoc )
+void SAL_CALL SchXMLImport::setTargetDocument(const uno::Reference<lang::XComponent>& xDoc)
{
- uno::Reference< chart2::XChartDocument > xOldDoc( GetModel(), uno::UNO_QUERY );
- if( xOldDoc.is() && xOldDoc->hasControllersLocked() )
+ uno::Reference<chart2::XChartDocument> xOldDoc(GetModel(), uno::UNO_QUERY);
+ if (xOldDoc.is() && xOldDoc->hasControllersLocked())
xOldDoc->unlockControllers();
- SvXMLImport::setTargetDocument( xDoc );
+ SvXMLImport::setTargetDocument(xDoc);
- //set data provider and number formatter
- // try to get an XDataProvider and set it
- // @todo: if we have our own data, we must not use the parent as data provider
- uno::Reference< chart2::XChartDocument > xChartDoc( GetModel(), uno::UNO_QUERY );
+ uno::Reference<chart2::XChartDocument> xChartDoc(GetModel(), uno::UNO_QUERY);
- if( xChartDoc.is() )
+ if (xChartDoc.is())
try
{
- //prevent rebuild of view during load ( necesarry especially if loaded not via load api, which is the case for example if binary files are loaded )
+ // prevent rebuild of view during load (necesarry especially if loaded not
+ // via load api, which is the case for example if binary files are loaded)
xChartDoc->lockControllers();
- uno::Reference< container::XChild > xChild( xChartDoc, uno::UNO_QUERY );
- uno::Reference< chart2::data::XDataReceiver > xDataReceiver( xChartDoc, uno::UNO_QUERY );
- if( xChild.is() && xDataReceiver.is())
+ uno::Reference<container::XChild> xChild(xChartDoc, uno::UNO_QUERY);
+ uno::Reference<chart2::data::XDataReceiver> xDataReceiver(xChartDoc, uno::UNO_QUERY);
+ if (xChild.is() && xDataReceiver.is())
{
- bool bHasOwnData = true;
-
- Reference< lang::XMultiServiceFactory > xFact( xChild->getParent(), uno::UNO_QUERY );
- if( xFact.is() )
+ Reference<lang::XMultiServiceFactory> xFact(xChild->getParent(), uno::UNO_QUERY);
+ if (xFact.is())
{
//if the parent has a number formatter we will use the numberformatter of the parent
- Reference< util::XNumberFormatsSupplier > xNumberFormatsSupplier( xFact, uno::UNO_QUERY );
- xDataReceiver->attachNumberFormatsSupplier( xNumberFormatsSupplier );
-
- if ( !xChartDoc->getDataProvider().is() )
- {
- const OUString aDataProviderServiceName( "com.sun.star.chart2.data.DataProvider");
- const uno::Sequence< OUString > aServiceNames( xFact->getAvailableServiceNames());
- const OUString * pBegin = aServiceNames.getConstArray();
- const OUString * pEnd = pBegin + aServiceNames.getLength();
- if( ::std::find( pBegin, pEnd, aDataProviderServiceName ) != pEnd )
- {
- Reference< chart2::data::XDataProvider > xProvider(
- xFact->createInstance( aDataProviderServiceName ), uno::UNO_QUERY );
- if( xProvider.is())
- {
- xDataReceiver->attachDataProvider( xProvider );
- bHasOwnData = false;
- }
- }
- }
- else
- bHasOwnData = false;
+ Reference<util::XNumberFormatsSupplier> xNumberFormatsSupplier(xFact, uno::UNO_QUERY);
+ xDataReceiver->attachNumberFormatsSupplier(xNumberFormatsSupplier);
}
-// else we have no parent => we have our own data
-
- if( bHasOwnData && ! xChartDoc->hasInternalDataProvider() )
- xChartDoc->createInternalDataProvider( false );
}
}
- catch( const uno::Exception & rEx )
+ catch (const uno::Exception & rEx)
{
OString aBStr(OUStringToOString(rEx.Message, RTL_TEXTENCODING_ASCII_US));
SAL_INFO("xmloff.chart", "SchXMLChartContext::StartElement(): Exception caught: " << aBStr);
diff --git a/xmloff/source/chart/SchXMLSeries2Context.cxx b/xmloff/source/chart/SchXMLSeries2Context.cxx
index 70eda5253150..82e3a7c67da7 100644
--- a/xmloff/source/chart/SchXMLSeries2Context.cxx
+++ b/xmloff/source/chart/SchXMLSeries2Context.cxx
@@ -30,6 +30,7 @@
#include <com/sun/star/chart2/XRegressionCurveContainer.hpp>
#include <com/sun/star/chart2/data/XDataSink.hpp>
#include <com/sun/star/chart2/data/XDataReceiver.hpp>
+#include <com/sun/star/chart2/data/XPivotTableDataProvider.hpp>
#include <com/sun/star/chart/ChartAxisAssign.hpp>
#include <com/sun/star/chart/ChartSymbolType.hpp>
@@ -407,20 +408,31 @@ void SchXMLSeries2Context::StartElement( const uno::Reference< xml::sax::XAttrib
uno::makeAny( true ));
}
+ Reference<chart2::data::XDataProvider> xDataProvider(mxNewDoc->getDataProvider());
+ Reference<chart2::data::XPivotTableDataProvider> xPivotTableDataProvider(xDataProvider, uno::UNO_QUERY);
+
+ Reference<chart2::data::XDataSequence> xSequenceValues;
+
// values
- Reference< chart2::data::XDataSequence > xSeq;
- if( bHasRange && !m_aSeriesRange.isEmpty() )
- xSeq = SchXMLTools::CreateDataSequence( m_aSeriesRange, mxNewDoc );
+ if (xPivotTableDataProvider.is()) // is pivot chart
+ {
+ xSequenceValues.set(xPivotTableDataProvider->createDataSequenceOfValuesByIndex(mnSeriesIndex));
+ }
+ else
+ {
+ if (bHasRange && !m_aSeriesRange.isEmpty())
+ xSequenceValues = SchXMLTools::CreateDataSequence(m_aSeriesRange, mxNewDoc);
+ }
- Reference< beans::XPropertySet > xSeqProp( xSeq, uno::UNO_QUERY );
- if( xSeqProp.is())
+ Reference<beans::XPropertySet> xSeqProp(xSequenceValues, uno::UNO_QUERY);
+ if (xSeqProp.is())
{
OUString aMainRole("values-y");
- if ( maSeriesChartTypeName == "com.sun.star.chart2.BubbleChartType" )
+ if (maSeriesChartTypeName == "com.sun.star.chart2.BubbleChartType")
aMainRole = "values-size";
- xSeqProp->setPropertyValue("Role", uno::makeAny( aMainRole ));
+ xSeqProp->setPropertyValue("Role", uno::makeAny(aMainRole));
}
- xLabeledSeq->setValues( xSeq );
+ xLabeledSeq->setValues(xSequenceValues);
// register for setting local data if external data provider is not present
maPostponedSequences.insert(
@@ -428,18 +440,24 @@ void SchXMLSeries2Context::StartElement( const uno::Reference< xml::sax::XAttrib
tSchXMLIndexWithPart( m_rGlobalSeriesImportInfo.nCurrentDataIndex, SCH_XML_PART_VALUES ), xLabeledSeq ));
// label
- if( !aSeriesLabelRange.isEmpty() )
+ Reference<chart2::data::XDataSequence> xSequenceLabel;
+
+ if (xPivotTableDataProvider.is())
{
- Reference< chart2::data::XDataSequence > xLabelSequence =
- SchXMLTools::CreateDataSequence( aSeriesLabelRange, mxNewDoc );
- xLabeledSeq->setLabel( xLabelSequence );
+ xSequenceLabel.set(xPivotTableDataProvider->createDataSequenceOfLabelsByIndex(mnSeriesIndex));
}
- else if( !aSeriesLabelString.isEmpty() )
+ else
{
- Reference< chart2::data::XDataSequence > xLabelSequence =
- SchXMLTools::CreateDataSequenceWithoutConvert( aSeriesLabelString, mxNewDoc );
- xLabeledSeq->setLabel( xLabelSequence );
+ if (!aSeriesLabelRange.isEmpty())
+ {
+ xSequenceLabel.set(SchXMLTools::CreateDataSequence(aSeriesLabelRange, mxNewDoc));
+ }
+ else if (!aSeriesLabelString.isEmpty())
+ {
+ xSequenceLabel.set(SchXMLTools::CreateDataSequenceWithoutConvert(aSeriesLabelString, mxNewDoc));
+ }
}
+ xLabeledSeq->setLabel(xSequenceLabel);
// Note: Even if we have no label, we have to register the label
// for creation, because internal data always has labels. If
diff --git a/xmloff/source/chart/SchXMLTools.cxx b/xmloff/source/chart/SchXMLTools.cxx
index 31c1ac161b1a..f00ce12d94f3 100644
--- a/xmloff/source/chart/SchXMLTools.cxx
+++ b/xmloff/source/chart/SchXMLTools.cxx
@@ -36,6 +36,7 @@
#include <com/sun/star/chart2/data/XDataProvider.hpp>
#include <com/sun/star/chart2/data/XDataReceiver.hpp>
#include <com/sun/star/chart2/data/XRangeXMLConversion.hpp>
+#include <com/sun/star/chart2/data/XPivotTableDataProvider.hpp>
#include <com/sun/star/chart2/XChartDocument.hpp>
#include <com/sun/star/chart2/XCoordinateSystemContainer.hpp>
#include <com/sun/star/chart2/XRegressionCurveContainer.hpp>
@@ -488,11 +489,21 @@ void CreateCategories(
bRangeConverted = true;
}
}
- Reference< chart2::data::XDataSequence > xSeq(
- xDataProvider->createDataSequenceByRangeRepresentation( aConvertedRange ));
- xLabeledSeq->setValues( xSeq );
- if( bRangeConverted )
- setXMLRangePropertyAtDataSequence( xSeq, rRangeAddress );
+
+ Reference<chart2::data::XDataSequence> xSequence;
+ Reference<chart2::data::XPivotTableDataProvider> xPivotTableDataProvider(xDataProvider, uno::UNO_QUERY);
+ if (xPivotTableDataProvider.is())
+ {
+ xSequence.set(xPivotTableDataProvider->createDataSequenceOfCategories());
+ }
+ else
+ {
+ xSequence.set(xDataProvider->createDataSequenceByRangeRepresentation(aConvertedRange));
+ if (bRangeConverted)
+ setXMLRangePropertyAtDataSequence(xSequence, rRangeAddress);
+ }
+ xLabeledSeq->setValues(xSequence);
+
}
catch( const lang::IllegalArgumentException & ex )
{
diff --git a/xmloff/source/core/xmltoken.cxx b/xmloff/source/core/xmltoken.cxx
index 5ee0ce8dfa84..166aa91b0a42 100644
--- a/xmloff/source/core/xmltoken.cxx
+++ b/xmloff/source/core/xmltoken.cxx
@@ -549,6 +549,7 @@ namespace xmloff { namespace token {
TOKEN( "data-label-number", XML_DATA_LABEL_NUMBER ),
TOKEN( "data-label-symbol", XML_DATA_LABEL_SYMBOL ),
TOKEN( "data-label-text", XML_DATA_LABEL_TEXT ),
+ TOKEN( "data-pilot-source", XML_DATA_PILOT_SOURCE ),
TOKEN( "data-pilot-field", XML_DATA_PILOT_FIELD ),
TOKEN( "data-pilot-grand-total", XML_DATA_PILOT_GRAND_TOTAL ),
TOKEN( "data-pilot-level", XML_DATA_PILOT_LEVEL ),
diff --git a/xmloff/source/token/tokens.txt b/xmloff/source/token/tokens.txt
index e8878d8ec6c1..70386737e4ed 100644
--- a/xmloff/source/token/tokens.txt
+++ b/xmloff/source/token/tokens.txt
@@ -471,6 +471,7 @@ data-cell-range-address
data-label-number
data-label-symbol
data-label-text
+data-pilot-source
data-pilot-field
data-pilot-grand-total
data-pilot-level
@@ -3050,4 +3051,4 @@ max-numerator-digits
zeros-numerator-digits
zeros-denominator-digits
integer-fraction-delimiter
-TOKEN_END_DUMMY \ No newline at end of file
+TOKEN_END_DUMMY