Commit 16ebd261 authored by Mouadh Kaabachi's avatar Mouadh Kaabachi Committed by GitHub

Merge pull request #5 from abilian/xmlwitch

Xmlwitch
parents 9f3a6365 dd49e8ce
......@@ -2,11 +2,13 @@
from __future__ import absolute_import, division, print_function
import HTMLParser
import xmlwitch
import os
from datetime import datetime
from os.path import expanduser
from lxml import etree
from spyne import AnyXml, Application, ServiceBase, rpc, Fault
from spyne.const.http import HTTP_200
from spyne.error import InvalidCredentialsError
......@@ -158,11 +160,13 @@ class XmlaProviderService(ServiceBase):
if request.Command.Statement == '':
# check if command contains a query
return etree.fromstring("""
<return>
<root xmlns="urn:schemas-microsoft-com:xml-analysis:empty"/>
</return>
""")
xml = xmlwitch.Builder()
with xml['return']:
xml.root(xmlns="urn:schemas-microsoft-com:xml-analysis:empty")
return str(xml)
else:
XmlaProviderService.discover_tools.change_catalogue(
request.Properties.PropertyList.Catalog)
......@@ -171,54 +175,45 @@ class XmlaProviderService(ServiceBase):
df = executer.execute_mdx()
xmla_tools = XmlaExecuteTools(executer)
return etree.fromstring("""
<return>
<root xmlns="urn:schemas-microsoft-com:xml-analysis:mddataset"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
{0}
<OlapInfo>
<CubeInfo>
<Cube>
<CubeName>Sales</CubeName>
<LastDataUpdate
xmlns="http://schemas.microsoft.com/analysisservices/2003/engine">{7}</LastDataUpdate>
<LastSchemaUpdate
xmlns="http://schemas.microsoft.com/analysisservices/2003/engine">{7}</LastSchemaUpdate>
</Cube>
</CubeInfo>
<AxesInfo>
{1}
{2}
</AxesInfo>
{3}
</OlapInfo>
<Axes>
{4}
{5}
</Axes>
<CellData>
{6}
</CellData>
</root>
</return>
""".format(execute_xsd,
xml = xmlwitch.Builder()
with xml['return']:
with xml.root(
execute_xsd,
xmlns="urn:schemas-microsoft-com:xml-analysis:mddataset",
**{
'xmlns:xsd': 'http://www.w3.org/2001/XMLSchema',
'xmlns:xsi':
'http://www.w3.org/2001/XMLSchema-instance'
}):
with xml.OlapInfo(xmla_tools.generate_cell_info()):
with xml.CubeInfo:
with xml.Cube:
xml.CubeName('Sales')
xml.LastDataUpdate(
datetime.now().strftime(
'%Y-%m-%dT%H:%M:%S'),
xmlns="http://schemas.microsoft.com/analysisservices/2003/engine"
)
xml.LastSchemaUpdate(
datetime.now().strftime(
'%Y-%m-%dT%H:%M:%S'),
xmlns="http://schemas.microsoft.com/analysisservices/2003/engine"
)
xml.AxesInfo(
xmla_tools.generate_axes_info(df),
xmla_tools.generate_axes_info_slicer(df),
xmla_tools.generate_cell_info(),
xmla_tools.generate_axes_info_slicer(df))
xml.Axes(
xmla_tools.generate_xs0(df),
xmla_tools.generate_slicer_axis(df),
xmla_tools.generate_cell_data(df),
datetime.now().strftime('%Y-%m-%dT%H:%M:%S')).replace(
'&', '&amp;'))
# Problem:
# An XML parser returns the error “xmlParseEntityRef: noname”
#
# Cause:
# There is a stray ‘&’ (ampersand character) somewhere in the XML text eg. some text & some more text
# Solution
# .replace('&', '&amp;')
xmla_tools.generate_slicer_axis(df))
xml.CellData(xmla_tools.generate_cell_data(df))
html_parser = HTMLParser.HTMLParser()
xml = html_parser.unescape(str(xml)).replace('&', '&amp;')
return xml
application = Application(
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -4,6 +4,7 @@ import itertools
from collections import OrderedDict
import numpy as np
import xmlwitch
class XmlaExecuteTools():
......@@ -88,7 +89,8 @@ class XmlaExecuteTools():
:param splited_df:
:return:
"""
axis0 = ""
xml = xmlwitch.Builder()
# only measure selected
if mdx_execution_result['columns_desc'][mdx_query_axis].keys() == [
self.executer.facts
......@@ -128,28 +130,28 @@ class XmlaExecuteTools():
]
first_att = 3
if tuples:
with xml.Axis(name=axis):
with xml.Tuples:
for tupls in itertools.chain(*tuples):
axis0 += "<Tuple>\n"
with xml.Tuple:
if tupls[0][1] in self.executer.measures and len(
self.executer.selected_measures) > 1:
axis0 += """
<Member Hierarchy="[Measures]">
<UName>[Measures].[{0}]</UName>
<Caption>{0}</Caption>
<LName>[Measures]</LName>
<LNum>0</LNum>
<DisplayInfo>0</DisplayInfo>
<HIERARCHY_UNIQUE_NAME>[Measures]</HIERARCHY_UNIQUE_NAME>
</Member>
""".format(tupls[0][1])
with xml.Member(Hierarchy="[Measures]"):
xml.UName(
'[Measures].[{0}]'.format(tupls[0][1]))
xml.Caption('{0}'.format(tupls[0][1]))
xml.LName('[Measures]')
xml.LNum('0')
xml.DisplayInfo('0')
xml.HIERARCHY_UNIQUE_NAME('[Measures]')
if tupls[0][-1] in self.executer.measures:
axis0 += "</Tuple>\n"
continue
for tupl in tupls:
tuple_without_minus_1 = self.get_tuple_without_nan(tupl)
tuple_without_minus_1 = self.get_tuple_without_nan(
tupl)
# french caracteres
# TODO encode dataframe
......@@ -159,47 +161,51 @@ class XmlaExecuteTools():
for x in tuple_without_minus_1
]
axis0 += """
<Member Hierarchy="[{0}].[{0}]">
<UName>[{0}].[{0}].[{1}].{2}</UName>
<Caption>{3}</Caption>
<LName>[{0}].[{0}].[{1}]</LName>
<LNum>{4}</LNum>
<DisplayInfo>131076</DisplayInfo>""".format(
tuple_without_minus_1[0], splited_df[tuple_without_minus_1[
0]].columns[len(tuple_without_minus_1) - first_att],
'.'.join([
# todo ugly !!
with xml.Member(Hierarchy="[{0}].[{0}]".format(
tuple_without_minus_1[0])):
xml.UName('[{0}].[{0}].[{1}].{2}'.format(
tuple_without_minus_1[0], splited_df[
tuple_without_minus_1[0]].columns[
len(tuple_without_minus_1) -
first_att], '.'.join([
'[' + str(i) + ']'
for i in tuple_without_minus_1[first_att - 1:]
]), tuple_without_minus_1[-1],
len(tuple_without_minus_1) - first_att)
# PARENT_UNIQUE_NAME must be before HIERARCHY_UNIQUE_NAME
if len(tuple_without_minus_1[first_att - 1:]) > 1:
axis0 += """
<PARENT_UNIQUE_NAME>[{0}].[{0}].[{1}].{2}</PARENT_UNIQUE_NAME>""".format(
for i in
tuple_without_minus_1[
first_att - 1:]
])))
xml.Caption('{0}'.format(
tuple_without_minus_1[-1]))
xml.LName('[{0}].[{0}].[{1}]'.format(
tuple_without_minus_1[0], splited_df[
tuple_without_minus_1[0]].columns[
len(tuple_without_minus_1) -
first_att]))
xml.LNum('{0}'.format(
len(tuple_without_minus_1) -
first_att))
xml.DisplayInfo('131076')
# PARENT_UNIQUE_NAME must be before HIERARCHY_UNIQUE_NAME (todo change it in xsd)
if len(tuple_without_minus_1[first_att -
1:]) > 1:
xml.PARENT_UNIQUE_NAME(
'[{0}].[{0}].[{1}].{2}'.format(
tuple_without_minus_1[0],
splited_df[tuple_without_minus_1[0]].columns[0],
'.'.join([
splited_df[
tuple_without_minus_1[0]]
.columns[0], '.'.join([
'[' + str(i) + ']'
for i in tuple_without_minus_1[first_att - 1:-1]
]))
axis0 += """
<HIERARCHY_UNIQUE_NAME>[{0}].[{0}]</HIERARCHY_UNIQUE_NAME>
</Member>
""".format(tuple_without_minus_1[0])
axis0 += "</Tuple>\n"
for i in
tuple_without_minus_1[
first_att - 1:-1]
])))
if axis0:
axis0 = """
<Axis name="{0}">
<Tuples>
{1}
</Tuples>
</Axis>
""".format(axis, axis0)
xml.HIERARCHY_UNIQUE_NAME(
'[{0}].[{0}]'.format(
tuple_without_minus_1[0]))
return axis0
return str(xml)
def generate_xs0(self, mdx_execution_result):
"""
......@@ -307,7 +313,6 @@ class XmlaExecuteTools():
:param mdx_execution_result: mdx_execute() result
:return: CellData as string
"""
columns_loop = []
if ((len(mdx_execution_result['columns_desc']['columns'].keys()) == 0)
^
......@@ -331,18 +336,16 @@ class XmlaExecuteTools():
index=False)
])
cell_data = ""
xml = xmlwitch.Builder()
index = 0
for value in columns_loop:
if np.isnan(value):
value = ''
cell_data += """
<Cell CellOrdinal="{0}">
<Value xsi:type="xsi:long">{1}</Value>
</Cell>
""".format(index, value)
with xml.Cell(CellOrdinal=str(index)):
xml.Value(str(value), **{'xsi:type': 'xsi:long'})
index += 1
return cell_data
return str(xml)
def generate_axes_info_slicer(self, mdx_execution_result):
"""
......@@ -377,43 +380,51 @@ class XmlaExecuteTools():
ignore_fact=True)
all_dimensions_names.append('Measures')
hierarchy_info_slicer = ""
xml = xmlwitch.Builder()
slicer_list = list(
set(all_dimensions_names) - set(
table_name
for table_name in mdx_execution_result['columns_desc']['all']))
# we have to write measures after dimensions !
# we have to write measures after dimensions ! (todo change xsd)
if 'Measures' in slicer_list:
slicer_list.insert(
len(slicer_list),
slicer_list.pop(slicer_list.index('Measures')))
if slicer_list:
with xml.AxisInfo(name='SlicerAxis'):
for dim_diff in slicer_list:
to_write = "[{0}].[{0}]".format(dim_diff)
if dim_diff == 'Measures':
# if measures > 1 we don't have to write measure
if self.executer.facts in mdx_execution_result['columns_desc'][
'all'] and len(mdx_execution_result['columns_desc'][
if self.executer.facts in mdx_execution_result[
'columns_desc']['all'] and len(
mdx_execution_result['columns_desc'][
'all'][self.executer.facts]) > 1:
continue
else:
to_write = "[Measures]"
hierarchy_info_slicer += """
<HierarchyInfo name="{0}">
<UName name="{0}.[MEMBER_UNIQUE_NAME]" type="xs:string"/>
<Caption name="{0}.[MEMBER_CAPTION]" type="xs:string"/>
<LName name="{0}.[LEVEL_UNIQUE_NAME]" type="xs:string"/>
<LNum name="{0}.[LEVEL_NUMBER]" type="xs:int"/>
<DisplayInfo name="{0}.[DISPLAY_INFO]" type="xs:unsignedInt"/>
</HierarchyInfo>
""".format(to_write)
if hierarchy_info_slicer:
hierarchy_info_slicer = "<AxisInfo name='SlicerAxis'>\n" + hierarchy_info_slicer + "\n</AxisInfo>\n"
return hierarchy_info_slicer
with xml.HierarchyInfo(name=to_write):
xml.UName(
name="{0}.[MEMBER_UNIQUE_NAME]".format(to_write),
**{'type': 'xs:string'})
xml.Caption(
name="{0}.[MEMBER_CAPTION]".format(to_write),
**{'type': 'xs:string'})
xml.LName(
name="{0}.[LEVEL_UNIQUE_NAME]".format(to_write),
**{'type': 'xs:string'})
xml.LNum(
name="{0}.[LEVEL_NUMBER]".format(to_write),
**{'type': 'xs:int'})
xml.DisplayInfo(
name="{0}.[DISPLAY_INFO]".format(to_write),
**{'type': 'xs:unsignedInt'})
return str(xml)
def generate_one_axis_info(self,
mdx_execution_result,
......@@ -452,45 +463,72 @@ class XmlaExecuteTools():
:param Axis: Axis0 or Axis1 (Axis0 by default)
:return:
"""
hierarchy_info = ""
# measure must be written at the top
if self.executer.facts in mdx_execution_result['columns_desc'][
mdx_query_axis].keys() and len(mdx_execution_result[
'columns_desc'][mdx_query_axis][self.executer.facts]) > 1:
hierarchy_info += """
<HierarchyInfo name="{0}">
<UName name="{0}.[MEMBER_UNIQUE_NAME]" type="xs:string"/>
<Caption name="{0}.[MEMBER_CAPTION]" type="xs:string"/>
<LName name="{0}.[LEVEL_UNIQUE_NAME]" type="xs:string"/>
<LNum name="{0}.[LEVEL_NUMBER]" type="xs:int"/>
<DisplayInfo name="{0}.[DISPLAY_INFO]" type="xs:unsignedInt"/>
<PARENT_UNIQUE_NAME name="{0}.[PARENT_UNIQUE_NAME]" type="xs:string"/>
<HIERARCHY_UNIQUE_NAME name="{0}.[HIERARCHY_UNIQUE_NAME]" type="xs:string"/>
</HierarchyInfo>
""".format('[Measures]')
for table_name in mdx_execution_result['columns_desc'][mdx_query_axis]:
axis_tables = mdx_execution_result['columns_desc'][mdx_query_axis]
xml = xmlwitch.Builder()
# measure must be written at the top
if axis_tables:
with xml.AxisInfo(name=Axis):
# many measures , then write this on the top
if self.executer.facts in axis_tables.keys() and len(
axis_tables[self.executer.facts]) > 1:
with xml.HierarchyInfo(name='[Measures]'):
xml.UName(
name="[Measures].[MEMBER_UNIQUE_NAME]",
**{'type': 'xs:string'})
xml.Caption(
name="[Measures].[MEMBER_CAPTION]",
**{'type': 'xs:string'})
xml.LName(
name="[Measures].[LEVEL_UNIQUE_NAME]",
**{'type': 'xs:string'})
xml.LNum(
name="[Measures].[LEVEL_NUMBER]",
**{'type': 'xs:int'})
xml.DisplayInfo(
name="[Measures].[DISPLAY_INFO]",
**{'type': 'xs:unsignedInt'})
xml.PARENT_UNIQUE_NAME(
name="[Measures].[PARENT_UNIQUE_NAME]",
**{'type': 'xs:string'})
xml.HIERARCHY_UNIQUE_NAME(
name="[Measures].[HIERARCHY_UNIQUE_NAME]",
**{'type': 'xs:string'})
for table_name in axis_tables:
if table_name != self.executer.facts:
hierarchy_info += """
<HierarchyInfo name="[{0}].[{0}]">
<UName name="[{0}].[{0}].[MEMBER_UNIQUE_NAME]" type="xs:string"/>
<Caption name="[{0}].[{0}].[MEMBER_CAPTION]" type="xs:string"/>
<LName name="[{0}].[{0}].[LEVEL_UNIQUE_NAME]" type="xs:string"/>
<LNum name="[{0}].[{0}].[LEVEL_NUMBER]" type="xs:int"/>
<DisplayInfo name="[{0}].[{0}].[DISPLAY_INFO]" type="xs:unsignedInt"/>
<PARENT_UNIQUE_NAME name="[{0}].[{0}].[PARENT_UNIQUE_NAME]" type="xs:string"/>
<HIERARCHY_UNIQUE_NAME name="[{0}].[{0}].[HIERARCHY_UNIQUE_NAME]" type="xs:string"/>
</HierarchyInfo>
""".format(table_name)
if hierarchy_info:
hierarchy_info = """
<AxisInfo name='{0}'>
{1}
</AxisInfo>
""".format(Axis, hierarchy_info)
return hierarchy_info
with xml.HierarchyInfo(
name='[{0}].[{0}]'.format(table_name)):
xml.UName(
name="[{0}].[{0}].[MEMBER_UNIQUE_NAME]".format(
table_name),
**{'type': 'xs:string'})
xml.Caption(
name="[{0}].[{0}].[MEMBER_CAPTION]".format(
table_name),
**{'type': 'xs:string'})
xml.LName(
name="[{0}].[{0}].[LEVEL_UNIQUE_NAME]".format(
table_name),
**{'type': 'xs:string'})
xml.LNum(
name="[{0}].[{0}].[LEVEL_NUMBER]".format(
table_name),
**{'type': 'xs:int'})
xml.DisplayInfo(
name="[{0}].[{0}].[DISPLAY_INFO]".format(
table_name),
**{'type': 'xs:unsignedInt'})
xml.PARENT_UNIQUE_NAME(
name="[{0}].[{0}].[PARENT_UNIQUE_NAME]".format(
table_name),
**{'type': 'xs:string'})
xml.HIERARCHY_UNIQUE_NAME(
name="[{0}].[{0}].[HIERARCHY_UNIQUE_NAME]".
format(table_name),
**{'type': 'xs:string'})
return str(xml)
def generate_axes_info(self, mdx_execution_result):
"""
......@@ -514,15 +552,16 @@ class XmlaExecuteTools():
@staticmethod
def generate_cell_info():
return """
<CellInfo>
<Value name="VALUE"/>
<FormatString name="FORMAT_STRING" type="xs:string"/>
<Language name="LANGUAGE" type="xs:unsignedInt"/>
<BackColor name="BACK_COLOR" type="xs:unsignedInt"/>
<ForeColor name="FORE_COLOR" type="xs:unsignedInt"/>
<FontFlags name="FONT_FLAGS" type="xs:int"/>
</CellInfo>"""
xml = xmlwitch.Builder()
with xml.CellInfo:
xml.Value(name="VALUE")
xml.FormatString(name="FORMAT_STRING", **{'type': 'xs:string'})
xml.Language(name="LANGUAGE", **{'type': 'xs:unsignedInt'})
xml.BackColor(name="BACK_COLOR", **{'type': 'xs:unsignedInt'})
xml.ForeColor(name="FORE_COLOR", **{'type': 'xs:unsignedInt'})
xml.FontFlags(name="FONT_FLAGS", **{'type': 'xs:int'})
return str(xml)
def generate_slicer_axis(self, mdx_execution_result):
"""
......@@ -553,57 +592,51 @@ class XmlaExecuteTools():
:param mdx_execution_result: mdx_execute() result
:return: SlicerAxis as string
"""
tuple = ""
# not used dimensions
for dim_diff in list(
set(self.executer.get_all_tables_names(ignore_fact=True)) -
set(table_name
for table_name in mdx_execution_result['columns_desc'][
'all'])):
unused_dimensions = list(
set(self.executer.get_all_tables_names(ignore_fact=True)) - set(
table_name
for table_name in mdx_execution_result['columns_desc']['all']))
xml = xmlwitch.Builder()
if unused_dimensions:
with xml.Axis(name="SlicerAxis"):
with xml.Tuples:
with xml.Tuple:
for dim_diff in unused_dimensions:
# TODO encode dataframe
# french caracteres
if type(self.executer.tables_loaded[dim_diff].iloc[0][
0]) == unicode:
column_attribut = self.executer.tables_loaded[dim_diff].iloc[
0][0].encode('utf-8', 'replace')
if type(self.executer.tables_loaded[dim_diff].iloc[
0][0]) == unicode:
column_attribut = self.executer.tables_loaded[
dim_diff].iloc[0][0].encode('utf-8',
'replace')
else:
column_attribut = self.executer.tables_loaded[dim_diff].iloc[
0][0]
tuple += """
<Member Hierarchy="[{0}].[{0}]">
<UName>[{0}].[{0}].[{1}].[{2}]</UName>
<Caption>{2}</Caption>
<LName>[{0}].[{0}].[{1}]</LName>
<LNum>0</LNum>
<DisplayInfo>2</DisplayInfo>
</Member>
""".format(dim_diff,
self.executer.tables_loaded[dim_diff].columns[0],
column_attribut)
column_attribut = self.executer.tables_loaded[
dim_diff].iloc[0][0]
with xml.Member(
Hierarchy="[{0}].[{0}]".format(dim_diff)):
xml.UName('[{0}].[{0}].[{1}].[{2}]'.format(
dim_diff, self.executer.tables_loaded[
dim_diff].columns[0], column_attribut))
xml.Caption(str(column_attribut))
xml.LName('[{0}].[{0}].[{1}]'.format(
dim_diff, self.executer.tables_loaded[
dim_diff].columns[0]))
xml.LNum('0')
xml.DisplayInfo('2')
# if we have zero on one only measures used
if len(self.executer.selected_measures) <= 1:
tuple += """
<Member Hierarchy="[Measures]">
<UName>[Measures].[{0}]</UName>
<Caption>{0}</Caption>
<LName>[Measures]</LName>
<LNum>0</LNum>
<DisplayInfo>0</DisplayInfo>
</Member>
""".format(self.executer.measures[0])
if tuple:
tuple = """
<Axis name="SlicerAxis">
<Tuples>
<Tuple>
{0}
</Tuple>
</Tuples>
</Axis>
""".format(tuple)
return tuple
with xml.Member(
Hierarchy="[Measures]".format(dim_diff)):
xml.UName('[Measures].[{0}]'.format(
self.executer.measures[0]))
xml.Caption(
'{0}'.format(self.executer.measures[0]))
xml.LName('[Measures]')
xml.LNum('0')
xml.DisplayInfo('0')
return str(xml)
......@@ -5,6 +5,7 @@ spyne<3
treelib<2
SQLAlchemy
psycopg2
xmlwitch
# Test
werkzeug
......
......@@ -15,5 +15,6 @@ pytz==2017.2 # via pandas, spyne
six==1.10.0 # via python-dateutil
spyne==2.12.14
sqlalchemy==1.1.11
treelib==1.3.5
treelib==1.3.7
werkzeug==0.12.2
xmlwitch==0.3
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment