| /************************************************************** |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| |
| |
| // MARKER(update_precomp.py): autogen include statement, do not remove |
| #include "precompiled_xmloff.hxx" |
| |
| #include "SchXMLAxisContext.hxx" |
| #include "SchXMLChartContext.hxx" |
| #include "SchXMLTools.hxx" |
| #include <xmloff/xmlnmspe.hxx> |
| #include <xmloff/xmlement.hxx> |
| #include <xmloff/xmlstyle.hxx> |
| #include <xmloff/prstylei.hxx> |
| #include <xmloff/nmspmap.hxx> |
| #include <xmloff/xmluconv.hxx> |
| |
| #include <tools/debug.hxx> |
| |
| #include <com/sun/star/chart/ChartAxisLabelPosition.hpp> |
| #include <com/sun/star/chart/ChartAxisMarkPosition.hpp> |
| #include <com/sun/star/chart/ChartAxisPosition.hpp> |
| #include <com/sun/star/chart/ChartAxisType.hpp> |
| #include <com/sun/star/chart/TimeIncrement.hpp> |
| #include <com/sun/star/chart/TimeInterval.hpp> |
| #include <com/sun/star/chart/TimeUnit.hpp> |
| #include <com/sun/star/chart/XAxis.hpp> |
| #include <com/sun/star/chart/XAxisSupplier.hpp> |
| #include <com/sun/star/chart2/AxisType.hpp> |
| #include <com/sun/star/chart2/XChartDocument.hpp> |
| #include <com/sun/star/chart2/XCoordinateSystemContainer.hpp> |
| |
| #include <com/sun/star/drawing/LineStyle.hpp> |
| |
| using namespace ::xmloff::token; |
| using namespace com::sun::star; |
| |
| using rtl::OUString; |
| using com::sun::star::uno::Reference; |
| |
| //---------------------------------------- |
| //---------------------------------------- |
| |
| static SvXMLEnumMapEntry aXMLAxisDimensionMap[] = |
| { |
| { XML_X, SCH_XML_AXIS_X }, |
| { XML_Y, SCH_XML_AXIS_Y }, |
| { XML_Z, SCH_XML_AXIS_Z }, |
| { XML_TOKEN_INVALID, 0 } |
| }; |
| |
| static SvXMLEnumMapEntry aXMLAxisTypeMap[] = |
| { |
| { XML_AUTO, ::com::sun::star::chart::ChartAxisType::AUTOMATIC }, |
| { XML_TEXT, ::com::sun::star::chart::ChartAxisType::CATEGORY }, |
| { XML_DATE, ::com::sun::star::chart::ChartAxisType::DATE }, |
| { XML_TOKEN_INVALID, 0 } |
| }; |
| |
| //---------------------------------------- |
| //---------------------------------------- |
| |
| class SchXMLCategoriesContext : public SvXMLImportContext |
| { |
| private: |
| SchXMLImportHelper& m_rImportHelper; |
| OUString& mrAddress; |
| |
| public: |
| SchXMLCategoriesContext( SchXMLImportHelper& rImpHelper, |
| SvXMLImport& rImport, |
| sal_uInt16 nPrefix, |
| const OUString& rLocalName, |
| OUString& rAddress ); |
| virtual ~SchXMLCategoriesContext(); |
| virtual void StartElement( const Reference< ::com::sun::star::xml::sax::XAttributeList >& xAttrList ); |
| }; |
| |
| //---------------------------------------- |
| //---------------------------------------- |
| |
| |
| class DateScaleContext : public SvXMLImportContext |
| { |
| public: |
| DateScaleContext( SchXMLImportHelper& rImpHelper, SvXMLImport& rImport, |
| sal_uInt16 nPrefix, const OUString& rLocalName, |
| const Reference< beans::XPropertySet > xAxisProps ); |
| |
| virtual ~DateScaleContext(); |
| virtual void StartElement( const Reference< ::com::sun::star::xml::sax::XAttributeList >& xAttrList ); |
| |
| private: |
| SchXMLImportHelper& m_rImportHelper; |
| Reference< beans::XPropertySet > m_xAxisProps; |
| }; |
| |
| |
| //---------------------------------------- |
| //---------------------------------------- |
| |
| SchXMLAxisContext::SchXMLAxisContext( SchXMLImportHelper& rImpHelper, |
| SvXMLImport& rImport, const OUString& rLocalName, |
| Reference< chart::XDiagram > xDiagram, |
| std::vector< SchXMLAxis >& rAxes, |
| OUString & rCategoriesAddress, |
| bool bAddMissingXAxisForNetCharts, |
| bool bAdaptWrongPercentScaleValues, |
| bool bAdaptXAxisOrientationForOld2DBarCharts, |
| bool& rbAxisPositionAttributeImported ) : |
| SvXMLImportContext( rImport, XML_NAMESPACE_CHART, rLocalName ), |
| m_rImportHelper( rImpHelper ), |
| m_xDiagram( xDiagram ), |
| m_rAxes( rAxes ), |
| m_rCategoriesAddress( rCategoriesAddress ), |
| m_nAxisType(chart::ChartAxisType::AUTOMATIC), |
| m_bAxisTypeImported(false), |
| m_bDateScaleImported(false), |
| m_bAddMissingXAxisForNetCharts( bAddMissingXAxisForNetCharts ), |
| m_bAdaptWrongPercentScaleValues( bAdaptWrongPercentScaleValues ), |
| m_bAdaptXAxisOrientationForOld2DBarCharts( bAdaptXAxisOrientationForOld2DBarCharts ), |
| m_rbAxisPositionAttributeImported( rbAxisPositionAttributeImported ) |
| { |
| } |
| |
| SchXMLAxisContext::~SchXMLAxisContext() |
| {} |
| |
| Reference< chart::XAxis > lcl_getChartAxis( SchXMLAxis aCurrentAxis, const Reference< chart::XDiagram > xDiagram ) |
| { |
| Reference< chart::XAxis > xAxis; |
| Reference< chart::XAxisSupplier > xAxisSuppl( xDiagram, uno::UNO_QUERY ); |
| if( !xAxisSuppl.is() ) |
| return xAxis; |
| if( aCurrentAxis.nAxisIndex == 0 ) |
| xAxis = xAxisSuppl->getAxis(aCurrentAxis.eDimension); |
| else |
| xAxis = xAxisSuppl->getSecondaryAxis(aCurrentAxis.eDimension); |
| return xAxis; |
| } |
| |
| /* returns a shape for the current axis's title. The property |
| "Has...AxisTitle" is set to "True" to get the shape |
| */ |
| Reference< drawing::XShape > SchXMLAxisContext::getTitleShape() |
| { |
| Reference< drawing::XShape > xResult; |
| Reference< beans::XPropertySet > xDiaProp( m_rImportHelper.GetChartDocument()->getDiagram(), uno::UNO_QUERY ); |
| Reference< chart::XAxis > xAxis( lcl_getChartAxis( m_aCurrentAxis, m_xDiagram ) ); |
| if( !xDiaProp.is() || !xAxis.is() ) |
| return xResult; |
| |
| rtl::OUString aPropName; |
| switch( m_aCurrentAxis.eDimension ) |
| { |
| case SCH_XML_AXIS_X: |
| if( m_aCurrentAxis.nAxisIndex == 0 ) |
| aPropName = OUString::createFromAscii( "HasXAxisTitle" ); |
| else |
| aPropName = OUString::createFromAscii( "HasSecondaryXAxisTitle" ); |
| break; |
| case SCH_XML_AXIS_Y: |
| if( m_aCurrentAxis.nAxisIndex == 0 ) |
| aPropName = OUString::createFromAscii( "HasYAxisTitle" ); |
| else |
| aPropName = OUString::createFromAscii( "HasSecondaryYAxisTitle" ); |
| break; |
| case SCH_XML_AXIS_Z: |
| aPropName = OUString::createFromAscii( "HasZAxisTitle" ); |
| break; |
| case SCH_XML_AXIS_UNDEF: |
| DBG_ERROR( "Invalid axis" ); |
| break; |
| } |
| xDiaProp->setPropertyValue( aPropName, uno::makeAny(sal_True) ); |
| xResult = Reference< drawing::XShape >( xAxis->getAxisTitle(), uno::UNO_QUERY ); |
| return xResult; |
| } |
| |
| void SchXMLAxisContext::CreateGrid( OUString sAutoStyleName, bool bIsMajor ) |
| { |
| Reference< beans::XPropertySet > xDiaProp( m_rImportHelper.GetChartDocument()->getDiagram(), uno::UNO_QUERY ); |
| Reference< chart::XAxis > xAxis( lcl_getChartAxis( m_aCurrentAxis, m_xDiagram ) ); |
| if( !xDiaProp.is() || !xAxis.is() ) |
| return; |
| |
| rtl::OUString aPropName; |
| switch( m_aCurrentAxis.eDimension ) |
| { |
| case SCH_XML_AXIS_X: |
| if( bIsMajor ) |
| aPropName = OUString::createFromAscii("HasXAxisGrid"); |
| else |
| aPropName = OUString::createFromAscii("HasXAxisHelpGrid"); |
| break; |
| case SCH_XML_AXIS_Y: |
| if( bIsMajor ) |
| aPropName = OUString::createFromAscii("HasYAxisGrid"); |
| else |
| aPropName = OUString::createFromAscii("HasYAxisHelpGrid"); |
| break; |
| case SCH_XML_AXIS_Z: |
| if( bIsMajor ) |
| aPropName = OUString::createFromAscii("HasZAxisGrid"); |
| else |
| aPropName = OUString::createFromAscii("HasZAxisHelpGrid"); |
| break; |
| case SCH_XML_AXIS_UNDEF: |
| DBG_ERROR( "Invalid axis" ); |
| break; |
| } |
| xDiaProp->setPropertyValue( aPropName, uno::makeAny(sal_True) ); |
| |
| Reference< beans::XPropertySet > xGridProp; |
| if( bIsMajor ) |
| xGridProp = xAxis->getMajorGrid(); |
| else |
| xGridProp = xAxis->getMinorGrid(); |
| |
| // set properties |
| if( xGridProp.is()) |
| { |
| // the line color is black as default, in the model it is a light gray |
| xGridProp->setPropertyValue( OUString( RTL_CONSTASCII_USTRINGPARAM( "LineColor" )), |
| uno::makeAny( COL_BLACK )); |
| if( sAutoStyleName.getLength()) |
| { |
| const SvXMLStylesContext* pStylesCtxt = m_rImportHelper.GetAutoStylesContext(); |
| if( pStylesCtxt ) |
| { |
| const SvXMLStyleContext* pStyle = pStylesCtxt->FindStyleChildContext( |
| m_rImportHelper.GetChartFamilyID(), sAutoStyleName ); |
| |
| if( pStyle && pStyle->ISA( XMLPropStyleContext )) |
| (( XMLPropStyleContext* )pStyle )->FillPropertySet( xGridProp ); |
| } |
| } |
| } |
| } |
| |
| namespace |
| { |
| enum AxisAttributeTokens |
| { |
| XML_TOK_AXIS_DIMENSION, |
| XML_TOK_AXIS_NAME, |
| XML_TOK_AXIS_STYLE_NAME, |
| XML_TOK_AXIS_TYPE, |
| XML_TOK_AXIS_TYPE_EXT |
| }; |
| |
| SvXMLTokenMapEntry aAxisAttributeTokenMap[] = |
| { |
| { XML_NAMESPACE_CHART, XML_DIMENSION, XML_TOK_AXIS_DIMENSION }, |
| { XML_NAMESPACE_CHART, XML_NAME, XML_TOK_AXIS_NAME }, |
| { XML_NAMESPACE_CHART, XML_STYLE_NAME, XML_TOK_AXIS_STYLE_NAME }, |
| { XML_NAMESPACE_CHART, XML_AXIS_TYPE, XML_TOK_AXIS_TYPE }, |
| { XML_NAMESPACE_CHART_EXT, XML_AXIS_TYPE, XML_TOK_AXIS_TYPE_EXT }, |
| XML_TOKEN_MAP_END |
| }; |
| |
| class AxisAttributeTokenMap : public SvXMLTokenMap |
| { |
| public: |
| AxisAttributeTokenMap(): SvXMLTokenMap( aAxisAttributeTokenMap ) {} |
| virtual ~AxisAttributeTokenMap() {} |
| }; |
| |
| //a AxisAttributeTokenMap Singleton |
| struct theAxisAttributeTokenMap : public rtl::Static< AxisAttributeTokenMap, theAxisAttributeTokenMap > {}; |
| } |
| |
| void SchXMLAxisContext::StartElement( const Reference< xml::sax::XAttributeList >& xAttrList ) |
| { |
| // parse attributes |
| sal_Int16 nAttrCount = xAttrList.is()? xAttrList->getLength(): 0; |
| SchXMLImport& rImport = ( SchXMLImport& )GetImport(); |
| const SvXMLTokenMap& rAttrTokenMap = theAxisAttributeTokenMap::get(); |
| |
| for( sal_Int16 i = 0; i < nAttrCount; i++ ) |
| { |
| OUString sAttrName = xAttrList->getNameByIndex( i ); |
| OUString aLocalName; |
| OUString aValue = xAttrList->getValueByIndex( i ); |
| sal_uInt16 nPrefix = GetImport().GetNamespaceMap().GetKeyByAttrName( sAttrName, &aLocalName ); |
| |
| switch( rAttrTokenMap.Get( nPrefix, aLocalName )) |
| { |
| case XML_TOK_AXIS_DIMENSION: |
| { |
| sal_uInt16 nEnumVal; |
| if( rImport.GetMM100UnitConverter().convertEnum( nEnumVal, aValue, aXMLAxisDimensionMap )) |
| m_aCurrentAxis.eDimension = ( SchXMLAxisDimension )nEnumVal; |
| } |
| break; |
| case XML_TOK_AXIS_NAME: |
| m_aCurrentAxis.aName = aValue; |
| break; |
| case XML_TOK_AXIS_TYPE: |
| case XML_TOK_AXIS_TYPE_EXT: |
| sal_uInt16 nEnumVal; |
| if( rImport.GetMM100UnitConverter().convertEnum( nEnumVal, aValue, aXMLAxisTypeMap )) |
| { |
| m_nAxisType = nEnumVal; |
| m_bAxisTypeImported = true; |
| } |
| break; |
| case XML_TOK_AXIS_STYLE_NAME: |
| m_aAutoStyleName = aValue; |
| break; |
| } |
| } |
| |
| // check for number of axes with same dimension |
| m_aCurrentAxis.nAxisIndex = 0; |
| sal_Int32 nNumOfAxes = m_rAxes.size(); |
| for( sal_Int32 nCurrent = 0; nCurrent < nNumOfAxes; nCurrent++ ) |
| { |
| if( m_rAxes[ nCurrent ].eDimension == m_aCurrentAxis.eDimension ) |
| m_aCurrentAxis.nAxisIndex++; |
| } |
| CreateAxis(); |
| } |
| namespace |
| { |
| |
| Reference< chart2::XAxis > lcl_getAxis( const Reference< frame::XModel >& xChartModel, |
| sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex ) |
| { |
| Reference< chart2::XAxis > xAxis; |
| |
| try |
| { |
| Reference< chart2::XChartDocument > xChart2Document( xChartModel, uno::UNO_QUERY ); |
| if( xChart2Document.is() ) |
| { |
| Reference< chart2::XDiagram > xDiagram( xChart2Document->getFirstDiagram()); |
| Reference< chart2::XCoordinateSystemContainer > xCooSysCnt( xDiagram, uno::UNO_QUERY_THROW ); |
| uno::Sequence< Reference< chart2::XCoordinateSystem > > |
| aCooSysSeq( xCooSysCnt->getCoordinateSystems()); |
| sal_Int32 nCooSysIndex = 0; |
| if( nCooSysIndex < aCooSysSeq.getLength() ) |
| { |
| Reference< chart2::XCoordinateSystem > xCooSys( aCooSysSeq[nCooSysIndex] ); |
| if( xCooSys.is() && nDimensionIndex < xCooSys->getDimension() ) |
| { |
| const sal_Int32 nMaxAxisIndex = xCooSys->getMaximumAxisIndexByDimension(nDimensionIndex); |
| if( nAxisIndex <= nMaxAxisIndex ) |
| xAxis = xCooSys->getAxisByDimension( nDimensionIndex, nAxisIndex ); |
| } |
| } |
| } |
| } |
| catch( uno::Exception & ) |
| { |
| DBG_ERROR( "Couldn't get axis" ); |
| } |
| |
| return xAxis; |
| } |
| |
| bool lcl_divideBy100( uno::Any& rDoubleAny ) |
| { |
| bool bChanged = false; |
| double fValue=0.0; |
| if( (rDoubleAny>>=fValue) && (fValue!=0.0) ) |
| { |
| fValue/=100.0; |
| rDoubleAny = uno::makeAny(fValue); |
| bChanged = true; |
| } |
| return bChanged; |
| } |
| |
| bool lcl_AdaptWrongPercentScaleValues(chart2::ScaleData& rScaleData) |
| { |
| bool bChanged = lcl_divideBy100( rScaleData.Minimum ); |
| bChanged = lcl_divideBy100( rScaleData.Maximum ) || bChanged; |
| bChanged = lcl_divideBy100( rScaleData.Origin ) || bChanged; |
| bChanged = lcl_divideBy100( rScaleData.IncrementData.Distance ) || bChanged; |
| return bChanged; |
| } |
| |
| }//end anonymous namespace |
| |
| void SchXMLAxisContext::CreateAxis() |
| { |
| m_rAxes.push_back( m_aCurrentAxis ); |
| |
| Reference< beans::XPropertySet > xDiaProp( m_rImportHelper.GetChartDocument()->getDiagram(), uno::UNO_QUERY ); |
| if( !xDiaProp.is() ) |
| return; |
| rtl::OUString aPropName; |
| switch( m_aCurrentAxis.eDimension ) |
| { |
| case SCH_XML_AXIS_X: |
| if( m_aCurrentAxis.nAxisIndex == 0 ) |
| aPropName = OUString::createFromAscii("HasXAxis"); |
| else |
| aPropName = OUString::createFromAscii("HasSecondaryXAxis"); |
| break; |
| case SCH_XML_AXIS_Y: |
| if( m_aCurrentAxis.nAxisIndex == 0 ) |
| aPropName = OUString::createFromAscii("HasYAxis"); |
| else |
| aPropName = OUString::createFromAscii("HasSecondaryYAxis"); |
| break; |
| case SCH_XML_AXIS_Z: |
| if( m_aCurrentAxis.nAxisIndex == 0 ) |
| aPropName = OUString::createFromAscii("HasXAxis"); |
| else |
| aPropName = OUString::createFromAscii("HasSecondaryXAxis"); |
| break; |
| case SCH_XML_AXIS_UNDEF: |
| DBG_ERROR( "Invalid axis" ); |
| break; |
| } |
| try |
| { |
| xDiaProp->setPropertyValue( aPropName, uno::makeAny(sal_True) ); |
| } |
| catch( beans::UnknownPropertyException & ) |
| { |
| DBG_ERROR( "Couldn't turn on axis" ); |
| } |
| if( m_aCurrentAxis.eDimension==SCH_XML_AXIS_Z ) |
| { |
| bool bSettingZAxisSuccedded = false; |
| try |
| { |
| xDiaProp->getPropertyValue( aPropName ) >>= bSettingZAxisSuccedded; |
| } |
| catch( beans::UnknownPropertyException & ) |
| { |
| DBG_ERROR( "Couldn't turn on z axis" ); |
| } |
| if( !bSettingZAxisSuccedded ) |
| return; |
| } |
| |
| |
| m_xAxisProps = Reference<beans::XPropertySet>( lcl_getChartAxis( m_aCurrentAxis, m_xDiagram ), uno::UNO_QUERY ); |
| |
| if( m_bAddMissingXAxisForNetCharts && m_aCurrentAxis.eDimension==SCH_XML_AXIS_Y && m_aCurrentAxis.nAxisIndex==0 ) |
| { |
| try |
| { |
| xDiaProp->setPropertyValue( OUString::createFromAscii( "HasXAxis" ), uno::makeAny(sal_True) ); |
| } |
| catch( beans::UnknownPropertyException & ) |
| { |
| DBG_ERROR( "Couldn't turn on x axis" ); |
| } |
| } |
| |
| // set properties |
| if( m_xAxisProps.is()) |
| { |
| uno::Any aTrueBool( uno::makeAny( sal_True )); |
| uno::Any aFalseBool( uno::makeAny( sal_False )); |
| |
| // #i109879# the line color is black as default, in the model it is a light gray |
| m_xAxisProps->setPropertyValue( OUString( RTL_CONSTASCII_USTRINGPARAM( "LineColor" )), |
| uno::makeAny( COL_BLACK )); |
| |
| m_xAxisProps->setPropertyValue( OUString::createFromAscii( "DisplayLabels" ), aFalseBool ); |
| |
| // #88077# AutoOrigin 'on' is default |
| m_xAxisProps->setPropertyValue( OUString::createFromAscii( "AutoOrigin" ), aTrueBool ); |
| |
| if( m_bAxisTypeImported ) |
| m_xAxisProps->setPropertyValue( OUString::createFromAscii( "AxisType" ), uno::makeAny(m_nAxisType) ); |
| |
| if( m_aAutoStyleName.getLength()) |
| { |
| const SvXMLStylesContext* pStylesCtxt = m_rImportHelper.GetAutoStylesContext(); |
| if( pStylesCtxt ) |
| { |
| const SvXMLStyleContext* pStyle = pStylesCtxt->FindStyleChildContext( |
| m_rImportHelper.GetChartFamilyID(), m_aAutoStyleName ); |
| |
| if( pStyle && pStyle->ISA( XMLPropStyleContext )) |
| { |
| // note: SvXMLStyleContext::FillPropertySet is not const |
| XMLPropStyleContext * pPropStyleContext = const_cast< XMLPropStyleContext * >( dynamic_cast< const XMLPropStyleContext * >( pStyle )); |
| if( pPropStyleContext ) |
| pPropStyleContext->FillPropertySet( m_xAxisProps ); |
| |
| if( m_bAdaptWrongPercentScaleValues && m_aCurrentAxis.eDimension==SCH_XML_AXIS_Y ) |
| { |
| //set scale data of added x axis back to default |
| Reference< chart2::XAxis > xAxis( lcl_getAxis( GetImport().GetModel(), |
| m_aCurrentAxis.eDimension, m_aCurrentAxis.nAxisIndex ) ); |
| if( xAxis.is() ) |
| { |
| chart2::ScaleData aScaleData( xAxis->getScaleData()); |
| if( lcl_AdaptWrongPercentScaleValues(aScaleData) ) |
| xAxis->setScaleData( aScaleData ); |
| } |
| } |
| |
| if( m_bAddMissingXAxisForNetCharts ) |
| { |
| //copy style from y axis to added x axis: |
| |
| Reference< chart::XAxisSupplier > xAxisSuppl( xDiaProp, uno::UNO_QUERY ); |
| if( xAxisSuppl.is() ) |
| { |
| Reference< beans::XPropertySet > xXAxisProp( xAxisSuppl->getAxis(0), uno::UNO_QUERY ); |
| (( XMLPropStyleContext* )pStyle )->FillPropertySet( xXAxisProp ); |
| } |
| |
| //set scale data of added x axis back to default |
| Reference< chart2::XAxis > xAxis( lcl_getAxis( GetImport().GetModel(), |
| 0 /*nDimensionIndex*/, 0 /*nAxisIndex*/ ) ); |
| if( xAxis.is() ) |
| { |
| chart2::ScaleData aScaleData; |
| aScaleData.AxisType = chart2::AxisType::CATEGORY; |
| aScaleData.Orientation = chart2::AxisOrientation_MATHEMATICAL; |
| xAxis->setScaleData( aScaleData ); |
| } |
| |
| //set line style of added x axis to invisible |
| Reference< beans::XPropertySet > xNewAxisProp( xAxis, uno::UNO_QUERY ); |
| if( xNewAxisProp.is() ) |
| { |
| xNewAxisProp->setPropertyValue( OUString::createFromAscii("LineStyle") |
| , uno::makeAny(drawing::LineStyle_NONE)); |
| } |
| } |
| |
| if( m_bAdaptXAxisOrientationForOld2DBarCharts && m_aCurrentAxis.eDimension == SCH_XML_AXIS_X ) |
| { |
| bool bIs3DChart = false; |
| if( xDiaProp.is() && ( xDiaProp->getPropertyValue(OUString(RTL_CONSTASCII_USTRINGPARAM("Dim3D"))) >>= bIs3DChart ) |
| && !bIs3DChart ) |
| { |
| Reference< chart2::XChartDocument > xChart2Document( GetImport().GetModel(), uno::UNO_QUERY ); |
| if( xChart2Document.is() ) |
| { |
| Reference< chart2::XCoordinateSystemContainer > xCooSysCnt( xChart2Document->getFirstDiagram(), uno::UNO_QUERY ); |
| if( xCooSysCnt.is() ) |
| { |
| uno::Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq( xCooSysCnt->getCoordinateSystems() ); |
| if( aCooSysSeq.getLength() ) |
| { |
| bool bSwapXandYAxis = false; |
| Reference< chart2::XCoordinateSystem > xCooSys( aCooSysSeq[0] ); |
| Reference< beans::XPropertySet > xCooSysProp( xCooSys, uno::UNO_QUERY ); |
| if( xCooSysProp.is() && ( xCooSysProp->getPropertyValue( OUString(RTL_CONSTASCII_USTRINGPARAM("SwapXAndYAxis"))) >>= bSwapXandYAxis ) |
| && bSwapXandYAxis ) |
| { |
| Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension( 0, m_aCurrentAxis.nAxisIndex ); |
| if( xAxis.is() ) |
| { |
| chart2::ScaleData aScaleData = xAxis->getScaleData(); |
| aScaleData.Orientation = chart2::AxisOrientation_REVERSE; |
| xAxis->setScaleData( aScaleData ); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| m_rbAxisPositionAttributeImported = m_rbAxisPositionAttributeImported || SchXMLTools::getPropertyFromContext( |
| OUString(RTL_CONSTASCII_USTRINGPARAM("CrossoverPosition")), pPropStyleContext, pStylesCtxt ).hasValue(); |
| } |
| } |
| } |
| } |
| } |
| |
| void SchXMLAxisContext::SetAxisTitle() |
| { |
| if( !m_aCurrentAxis.aTitle.getLength() ) |
| return; |
| |
| Reference< chart::XAxis > xAxis( lcl_getChartAxis( m_aCurrentAxis, m_xDiagram ) ); |
| if( !xAxis.is() ) |
| return; |
| |
| Reference< beans::XPropertySet > xTitleProp( xAxis->getAxisTitle() ); |
| if( xTitleProp.is() ) |
| { |
| try |
| { |
| xTitleProp->setPropertyValue( OUString( RTL_CONSTASCII_USTRINGPARAM( "String" )), uno::makeAny(m_aCurrentAxis.aTitle) ); |
| } |
| catch( beans::UnknownPropertyException & ) |
| { |
| DBG_ERROR( "Property String for Title not available" ); |
| } |
| } |
| } |
| |
| //----------------------------------------------------------------------- |
| namespace |
| { |
| enum AxisChildTokens |
| { |
| XML_TOK_AXIS_TITLE, |
| XML_TOK_AXIS_CATEGORIES, |
| XML_TOK_AXIS_GRID, |
| XML_TOK_AXIS_DATE_SCALE, |
| XML_TOK_AXIS_DATE_SCALE_EXT |
| }; |
| |
| SvXMLTokenMapEntry aAxisChildTokenMap[] = |
| { |
| { XML_NAMESPACE_CHART, XML_TITLE, XML_TOK_AXIS_TITLE }, |
| { XML_NAMESPACE_CHART, XML_CATEGORIES, XML_TOK_AXIS_CATEGORIES }, |
| { XML_NAMESPACE_CHART, XML_GRID, XML_TOK_AXIS_GRID }, |
| { XML_NAMESPACE_CHART, XML_DATE_SCALE, XML_TOK_AXIS_DATE_SCALE }, |
| { XML_NAMESPACE_CHART_EXT, XML_DATE_SCALE, XML_TOK_AXIS_DATE_SCALE_EXT }, |
| XML_TOKEN_MAP_END |
| }; |
| |
| class AxisChildTokenMap : public SvXMLTokenMap |
| { |
| public: |
| AxisChildTokenMap(): SvXMLTokenMap( aAxisChildTokenMap ) {} |
| virtual ~AxisChildTokenMap() {} |
| }; |
| |
| //a AxisChildTokenMap Singleton |
| struct theAxisChildTokenMap : public rtl::Static< AxisChildTokenMap, theAxisChildTokenMap > {}; |
| } |
| |
| SvXMLImportContext* SchXMLAxisContext::CreateChildContext( |
| sal_uInt16 p_nPrefix, |
| const OUString& rLocalName, |
| const Reference< xml::sax::XAttributeList >& xAttrList ) |
| { |
| SvXMLImportContext* pContext = 0; |
| const SvXMLTokenMap& rTokenMap = theAxisChildTokenMap::get(); |
| |
| switch( rTokenMap.Get( p_nPrefix, rLocalName )) |
| { |
| case XML_TOK_AXIS_TITLE: |
| { |
| Reference< drawing::XShape > xTitleShape = getTitleShape(); |
| pContext = new SchXMLTitleContext( m_rImportHelper, GetImport(), rLocalName, |
| m_aCurrentAxis.aTitle, |
| xTitleShape ); |
| } |
| break; |
| |
| case XML_TOK_AXIS_CATEGORIES: |
| pContext = new SchXMLCategoriesContext( m_rImportHelper, GetImport(), |
| p_nPrefix, rLocalName, |
| m_rCategoriesAddress ); |
| m_aCurrentAxis.bHasCategories = true; |
| break; |
| |
| case XML_TOK_AXIS_DATE_SCALE: |
| case XML_TOK_AXIS_DATE_SCALE_EXT: |
| pContext = new DateScaleContext( m_rImportHelper, GetImport(), |
| p_nPrefix, rLocalName, m_xAxisProps ); |
| m_bDateScaleImported = true; |
| break; |
| |
| case XML_TOK_AXIS_GRID: |
| { |
| sal_Int16 nAttrCount = xAttrList.is()? xAttrList->getLength(): 0; |
| bool bIsMajor = true; // default value for class is "major" |
| OUString sAutoStyleName; |
| |
| for( sal_Int16 i = 0; i < nAttrCount; i++ ) |
| { |
| OUString sAttrName = xAttrList->getNameByIndex( i ); |
| OUString aLocalName; |
| sal_uInt16 nPrefix = GetImport().GetNamespaceMap().GetKeyByAttrName( sAttrName, &aLocalName ); |
| |
| if( nPrefix == XML_NAMESPACE_CHART ) |
| { |
| if( IsXMLToken( aLocalName, XML_CLASS ) ) |
| { |
| if( IsXMLToken( xAttrList->getValueByIndex( i ), XML_MINOR ) ) |
| bIsMajor = false; |
| } |
| else if( IsXMLToken( aLocalName, XML_STYLE_NAME ) ) |
| sAutoStyleName = xAttrList->getValueByIndex( i ); |
| } |
| } |
| |
| CreateGrid( sAutoStyleName, bIsMajor ); |
| |
| // don't create a context => use default context. grid elements are empty |
| pContext = new SvXMLImportContext( GetImport(), p_nPrefix, rLocalName ); |
| } |
| break; |
| |
| default: |
| pContext = new SvXMLImportContext( GetImport(), p_nPrefix, rLocalName ); |
| break; |
| } |
| |
| return pContext; |
| } |
| |
| void SchXMLAxisContext::EndElement() |
| { |
| if( !m_bDateScaleImported && m_nAxisType==chart::ChartAxisType::AUTOMATIC ) |
| { |
| Reference< chart2::XAxis > xAxis( lcl_getAxis( GetImport().GetModel(), m_aCurrentAxis.eDimension, m_aCurrentAxis.nAxisIndex ) ); |
| if( xAxis.is() ) |
| { |
| chart2::ScaleData aScaleData( xAxis->getScaleData()); |
| aScaleData.AutoDateAxis = false;//different default for older documents |
| xAxis->setScaleData( aScaleData ); |
| } |
| } |
| |
| SetAxisTitle(); |
| } |
| |
| // ======================================== |
| |
| namespace |
| { |
| |
| Reference< chart2::XAxis > lcl_getAxis( const Reference< chart2::XCoordinateSystem > xCooSys, sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex ) |
| { |
| Reference< chart2::XAxis > xAxis; |
| try |
| { |
| xAxis = xCooSys->getAxisByDimension( nDimensionIndex, nAxisIndex ); |
| } |
| catch( uno::Exception & ) |
| { |
| } |
| return xAxis; |
| } |
| |
| } // anonymous namespace |
| |
| void SchXMLAxisContext::CorrectAxisPositions( const Reference< chart2::XChartDocument >& xNewDoc, |
| const OUString& rChartTypeServiceName, |
| const OUString& rODFVersionOfFile, |
| bool bAxisPositionAttributeImported ) |
| { |
| if( ( !rODFVersionOfFile.getLength() || rODFVersionOfFile.equalsAscii("1.0") |
| || rODFVersionOfFile.equalsAscii("1.1") |
| || ( rODFVersionOfFile.equalsAscii("1.2") && !bAxisPositionAttributeImported ) ) ) |
| { |
| try |
| { |
| Reference< chart2::XCoordinateSystemContainer > xCooSysCnt( xNewDoc->getFirstDiagram(), uno::UNO_QUERY_THROW ); |
| uno::Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq( xCooSysCnt->getCoordinateSystems()); |
| if( aCooSysSeq.getLength() ) |
| { |
| Reference< chart2::XCoordinateSystem > xCooSys( aCooSysSeq[0] ); |
| if( xCooSys.is() ) |
| { |
| Reference< chart2::XAxis > xMainXAxis = lcl_getAxis( xCooSys, 0, 0 ); |
| Reference< chart2::XAxis > xMainYAxis = lcl_getAxis( xCooSys, 1, 0 ); |
| //Reference< chart2::XAxis > xMajorZAxis = lcl_getAxis( xCooSys, 2, 0 ); |
| Reference< chart2::XAxis > xSecondaryXAxis = lcl_getAxis( xCooSys, 0, 1 ); |
| Reference< chart2::XAxis > xSecondaryYAxis = lcl_getAxis( xCooSys, 1, 1 ); |
| |
| Reference< beans::XPropertySet > xMainXAxisProp( xMainXAxis, uno::UNO_QUERY ); |
| Reference< beans::XPropertySet > xMainYAxisProp( xMainYAxis, uno::UNO_QUERY ); |
| Reference< beans::XPropertySet > xSecondaryXAxisProp( xSecondaryXAxis, uno::UNO_QUERY ); |
| Reference< beans::XPropertySet > xSecondaryYAxisProp( xSecondaryYAxis, uno::UNO_QUERY ); |
| |
| if( xMainXAxisProp.is() && xMainYAxisProp.is() ) |
| { |
| chart2::ScaleData aMainXScale = xMainXAxis->getScaleData(); |
| if( 0 == rChartTypeServiceName.reverseCompareToAsciiL( RTL_CONSTASCII_STRINGPARAM( "com.sun.star.chart2.ScatterChartType" ) ) ) |
| { |
| xMainYAxisProp->setPropertyValue( OUString::createFromAscii("CrossoverPosition") |
| , uno::makeAny( ::com::sun::star::chart::ChartAxisPosition_VALUE) ); |
| double fCrossoverValue = 0.0; |
| aMainXScale.Origin >>= fCrossoverValue; |
| xMainYAxisProp->setPropertyValue( OUString::createFromAscii("CrossoverValue") |
| , uno::makeAny( fCrossoverValue ) ); |
| |
| if( aMainXScale.Orientation == chart2::AxisOrientation_REVERSE ) |
| { |
| xMainYAxisProp->setPropertyValue( OUString::createFromAscii("LabelPosition") |
| , uno::makeAny( ::com::sun::star::chart::ChartAxisLabelPosition_OUTSIDE_END) ); |
| xMainYAxisProp->setPropertyValue( OUString::createFromAscii("MarkPosition") |
| , uno::makeAny( ::com::sun::star::chart::ChartAxisMarkPosition_AT_LABELS) ); |
| if( xSecondaryYAxisProp.is() ) |
| xSecondaryYAxisProp->setPropertyValue( OUString::createFromAscii("CrossoverPosition") |
| , uno::makeAny( ::com::sun::star::chart::ChartAxisPosition_START) ); |
| } |
| else |
| { |
| xMainYAxisProp->setPropertyValue( OUString::createFromAscii("LabelPosition") |
| , uno::makeAny( ::com::sun::star::chart::ChartAxisLabelPosition_OUTSIDE_START) ); |
| xMainYAxisProp->setPropertyValue( OUString::createFromAscii("MarkPosition") |
| , uno::makeAny( ::com::sun::star::chart::ChartAxisMarkPosition_AT_LABELS) ); |
| if( xSecondaryYAxisProp.is() ) |
| xSecondaryYAxisProp->setPropertyValue( OUString::createFromAscii("CrossoverPosition") |
| , uno::makeAny( ::com::sun::star::chart::ChartAxisPosition_END) ); |
| } |
| } |
| else |
| { |
| if( aMainXScale.Orientation == chart2::AxisOrientation_REVERSE ) |
| { |
| xMainYAxisProp->setPropertyValue( OUString::createFromAscii("CrossoverPosition") |
| , uno::makeAny( ::com::sun::star::chart::ChartAxisPosition_END) ); |
| if( xSecondaryYAxisProp.is() ) |
| xSecondaryYAxisProp->setPropertyValue( OUString::createFromAscii("CrossoverPosition") |
| , uno::makeAny( ::com::sun::star::chart::ChartAxisPosition_START) ); |
| } |
| else |
| { |
| xMainYAxisProp->setPropertyValue( OUString::createFromAscii("CrossoverPosition") |
| , uno::makeAny( ::com::sun::star::chart::ChartAxisPosition_START) ); |
| if( xSecondaryYAxisProp.is() ) |
| xSecondaryYAxisProp->setPropertyValue( OUString::createFromAscii("CrossoverPosition") |
| , uno::makeAny( ::com::sun::star::chart::ChartAxisPosition_END) ); |
| } |
| } |
| |
| chart2::ScaleData aMainYScale = xMainYAxis->getScaleData(); |
| xMainXAxisProp->setPropertyValue( OUString::createFromAscii("CrossoverPosition") |
| , uno::makeAny( ::com::sun::star::chart::ChartAxisPosition_VALUE) ); |
| double fCrossoverValue = 0.0; |
| aMainYScale.Origin >>= fCrossoverValue; |
| xMainXAxisProp->setPropertyValue( OUString::createFromAscii("CrossoverValue") |
| , uno::makeAny( fCrossoverValue ) ); |
| |
| if( aMainYScale.Orientation == chart2::AxisOrientation_REVERSE ) |
| { |
| xMainXAxisProp->setPropertyValue( OUString::createFromAscii("LabelPosition") |
| , uno::makeAny( ::com::sun::star::chart::ChartAxisLabelPosition_OUTSIDE_END) ); |
| xMainXAxisProp->setPropertyValue( OUString::createFromAscii("MarkPosition") |
| , uno::makeAny( ::com::sun::star::chart::ChartAxisMarkPosition_AT_LABELS) ); |
| if( xSecondaryXAxisProp.is() ) |
| xSecondaryXAxisProp->setPropertyValue( OUString::createFromAscii("CrossoverPosition") |
| , uno::makeAny( ::com::sun::star::chart::ChartAxisPosition_START) ); |
| } |
| else |
| { |
| xMainXAxisProp->setPropertyValue( OUString::createFromAscii("LabelPosition") |
| , uno::makeAny( ::com::sun::star::chart::ChartAxisLabelPosition_OUTSIDE_START) ); |
| xMainXAxisProp->setPropertyValue( OUString::createFromAscii("MarkPosition") |
| , uno::makeAny( ::com::sun::star::chart::ChartAxisMarkPosition_AT_LABELS) ); |
| if( xSecondaryXAxisProp.is() ) |
| xSecondaryXAxisProp->setPropertyValue( OUString::createFromAscii("CrossoverPosition") |
| , uno::makeAny( ::com::sun::star::chart::ChartAxisPosition_END) ); |
| } |
| } |
| } |
| } |
| } |
| catch( uno::Exception & ) |
| { |
| } |
| } |
| } |
| |
| // ======================================== |
| |
| SchXMLCategoriesContext::SchXMLCategoriesContext( |
| SchXMLImportHelper& rImpHelper, |
| SvXMLImport& rImport, |
| sal_uInt16 nPrefix, |
| const OUString& rLocalName, |
| OUString& rAddress ) : |
| SvXMLImportContext( rImport, nPrefix, rLocalName ), |
| m_rImportHelper( rImpHelper ), |
| mrAddress( rAddress ) |
| { |
| } |
| |
| SchXMLCategoriesContext::~SchXMLCategoriesContext() |
| { |
| } |
| |
| void SchXMLCategoriesContext::StartElement( const Reference< xml::sax::XAttributeList >& xAttrList ) |
| { |
| sal_Int16 nAttrCount = xAttrList.is()? xAttrList->getLength(): 0; |
| |
| for( sal_Int16 i = 0; i < nAttrCount; i++ ) |
| { |
| OUString sAttrName = xAttrList->getNameByIndex( i ); |
| OUString aLocalName; |
| sal_uInt16 nPrefix = GetImport().GetNamespaceMap().GetKeyByAttrName( sAttrName, &aLocalName ); |
| |
| if( nPrefix == XML_NAMESPACE_TABLE && |
| IsXMLToken( aLocalName, XML_CELL_RANGE_ADDRESS ) ) |
| { |
| Reference< chart2::XChartDocument > xNewDoc( GetImport().GetModel(), uno::UNO_QUERY ); |
| mrAddress = xAttrList->getValueByIndex( i ); |
| } |
| } |
| } |
| |
| // ======================================== |
| |
| DateScaleContext::DateScaleContext( |
| SchXMLImportHelper& rImpHelper, |
| SvXMLImport& rImport, |
| sal_uInt16 nPrefix, |
| const OUString& rLocalName, |
| const Reference< beans::XPropertySet > xAxisProps ) : |
| SvXMLImportContext( rImport, nPrefix, rLocalName ), |
| m_rImportHelper( rImpHelper ), |
| m_xAxisProps( xAxisProps ) |
| { |
| } |
| |
| DateScaleContext::~DateScaleContext() |
| { |
| } |
| |
| namespace |
| { |
| enum DateScaleAttributeTokens |
| { |
| XML_TOK_DATESCALE_BASE_TIME_UNIT, |
| XML_TOK_DATESCALE_MAJOR_INTERVAL_VALUE, |
| XML_TOK_DATESCALE_MAJOR_INTERVAL_UNIT, |
| XML_TOK_DATESCALE_MINOR_INTERVAL_VALUE, |
| XML_TOK_DATESCALE_MINOR_INTERVAL_UNIT |
| }; |
| |
| SvXMLTokenMapEntry aDateScaleAttributeTokenMap[] = |
| { |
| { XML_NAMESPACE_CHART, XML_BASE_TIME_UNIT, XML_TOK_DATESCALE_BASE_TIME_UNIT }, |
| { XML_NAMESPACE_CHART, XML_MAJOR_INTERVAL_VALUE, XML_TOK_DATESCALE_MAJOR_INTERVAL_VALUE }, |
| { XML_NAMESPACE_CHART, XML_MAJOR_INTERVAL_UNIT, XML_TOK_DATESCALE_MAJOR_INTERVAL_UNIT }, |
| { XML_NAMESPACE_CHART, XML_MINOR_INTERVAL_VALUE, XML_TOK_DATESCALE_MINOR_INTERVAL_VALUE }, |
| { XML_NAMESPACE_CHART, XML_MINOR_INTERVAL_UNIT, XML_TOK_DATESCALE_MINOR_INTERVAL_UNIT }, |
| XML_TOKEN_MAP_END |
| }; |
| |
| class DateScaleAttributeTokenMap : public SvXMLTokenMap |
| { |
| public: |
| DateScaleAttributeTokenMap(): SvXMLTokenMap( aDateScaleAttributeTokenMap ) {} |
| virtual ~DateScaleAttributeTokenMap() {} |
| }; |
| |
| struct theDateScaleAttributeTokenMap : public rtl::Static< DateScaleAttributeTokenMap, theDateScaleAttributeTokenMap > {}; |
| |
| sal_Int32 lcl_getTimeUnit( const OUString& rValue ) |
| { |
| sal_Int32 nTimeUnit = ::com::sun::star::chart::TimeUnit::DAY; |
| if( IsXMLToken( rValue, XML_DAYS ) ) |
| nTimeUnit = ::com::sun::star::chart::TimeUnit::DAY; |
| else if( IsXMLToken( rValue, XML_MONTHS ) ) |
| nTimeUnit = ::com::sun::star::chart::TimeUnit::MONTH; |
| else if( IsXMLToken( rValue, XML_YEARS ) ) |
| nTimeUnit = ::com::sun::star::chart::TimeUnit::YEAR; |
| return nTimeUnit; |
| } |
| |
| } |
| |
| void DateScaleContext::StartElement( const Reference< xml::sax::XAttributeList >& xAttrList ) |
| { |
| if( !m_xAxisProps.is() ) |
| return; |
| |
| // parse attributes |
| sal_Int16 nAttrCount = xAttrList.is()? xAttrList->getLength(): 0; |
| const SvXMLTokenMap& rAttrTokenMap = theDateScaleAttributeTokenMap::get(); |
| |
| bool bSetNewIncrement=false; |
| chart::TimeIncrement aIncrement; |
| m_xAxisProps->getPropertyValue( OUString::createFromAscii( "TimeIncrement" )) >>= aIncrement; |
| |
| for( sal_Int16 i = 0; i < nAttrCount; i++ ) |
| { |
| OUString sAttrName = xAttrList->getNameByIndex( i ); |
| OUString aLocalName; |
| OUString aValue = xAttrList->getValueByIndex( i ); |
| sal_uInt16 nPrefix = GetImport().GetNamespaceMap().GetKeyByAttrName( sAttrName, &aLocalName ); |
| |
| switch( rAttrTokenMap.Get( nPrefix, aLocalName )) |
| { |
| case XML_TOK_DATESCALE_BASE_TIME_UNIT: |
| { |
| aIncrement.TimeResolution = uno::makeAny( lcl_getTimeUnit(aValue) ); |
| bSetNewIncrement = true; |
| } |
| break; |
| case XML_TOK_DATESCALE_MAJOR_INTERVAL_VALUE: |
| { |
| chart::TimeInterval aInterval(1,0); |
| aIncrement.MajorTimeInterval >>= aInterval; |
| SvXMLUnitConverter::convertNumber( aInterval.Number, aValue ); |
| aIncrement.MajorTimeInterval = uno::makeAny(aInterval); |
| bSetNewIncrement = true; |
| } |
| break; |
| case XML_TOK_DATESCALE_MAJOR_INTERVAL_UNIT: |
| { |
| chart::TimeInterval aInterval(1,0); |
| aIncrement.MajorTimeInterval >>= aInterval; |
| aInterval.TimeUnit = lcl_getTimeUnit(aValue); |
| aIncrement.MajorTimeInterval = uno::makeAny(aInterval); |
| bSetNewIncrement = true; |
| } |
| break; |
| case XML_TOK_DATESCALE_MINOR_INTERVAL_VALUE: |
| { |
| chart::TimeInterval aInterval(1,0); |
| aIncrement.MinorTimeInterval >>= aInterval; |
| SvXMLUnitConverter::convertNumber( aInterval.Number, aValue ); |
| aIncrement.MinorTimeInterval = uno::makeAny(aInterval); |
| bSetNewIncrement = true; |
| } |
| break; |
| case XML_TOK_DATESCALE_MINOR_INTERVAL_UNIT: |
| { |
| chart::TimeInterval aInterval(1,0); |
| aIncrement.MinorTimeInterval >>= aInterval; |
| aInterval.TimeUnit = lcl_getTimeUnit(aValue); |
| aIncrement.MinorTimeInterval = uno::makeAny(aInterval); |
| bSetNewIncrement = true; |
| } |
| break; |
| } |
| } |
| |
| if( bSetNewIncrement ) |
| m_xAxisProps->setPropertyValue( OUString::createFromAscii( "TimeIncrement" ), uno::makeAny( aIncrement ) ); |
| } |
| |
| // ======================================== |