summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorchai <chaifix@163.com>2021-10-26 11:27:58 +0800
committerchai <chaifix@163.com>2021-10-26 11:27:58 +0800
commit32345800737b668011a87328cd3dcce59ec2934c (patch)
treee1bbd47ae775f1268447f1c1011ab10492ee9197
parentef7aedf5f272c52247d8ee9522d7b2896d21af63 (diff)
*misc
-rw-r--r--Editor/EditorApplication.cpp1
-rw-r--r--Editor/Scripting/EditorScripting.cpp4
-rw-r--r--Projects/VisualStudio/Editor/Editor.vcxproj4
-rw-r--r--Projects/VisualStudio/Editor/Editor.vcxproj.filters18
-rw-r--r--Resources/Scripts/EditorApplication.lua18
-rw-r--r--Runtime/Common/DataBuffer.h16
-rw-r--r--Runtime/FileSystem/File.h2
-rw-r--r--Runtime/FileSystem/FileJobs.h5
-rw-r--r--Runtime/FileSystem/Unzip.h7
-rw-r--r--Runtime/Lua/LuaBind/LuaBindUtility.h4
-rw-r--r--Runtime/Scripting/Common/Common.bind.cpp14
-rw-r--r--Runtime/Scripting/Common/DataBuffer.bind.cpp19
-rw-r--r--Runtime/Scripting/Path/Path.bind.cpp (renamed from Runtime/Scripting/Path.bind.cpp)0
-rw-r--r--Tools/XlsToCsv/.gitignore46
-rw-r--r--Tools/XlsToCsv/CHANGELOG97
-rw-r--r--Tools/XlsToCsv/COPYING339
-rw-r--r--Tools/XlsToCsv/LICENSE.txt339
-rw-r--r--Tools/XlsToCsv/MANIFEST.in5
-rw-r--r--Tools/XlsToCsv/README.md119
-rw-r--r--Tools/XlsToCsv/man/Makefile14
-rw-r--r--Tools/XlsToCsv/man/pod2man.mk71
-rw-r--r--Tools/XlsToCsv/man/xlsx2csv.1.pod120
-rw-r--r--Tools/XlsToCsv/setup.py55
-rw-r--r--Tools/XlsToCsv/sheets.xlsxbin0 -> 16053 bytes
-rw-r--r--Tools/XlsToCsv/test/datetime.csv1
-rw-r--r--Tools/XlsToCsv/test/datetime.xlsxbin0 -> 32751 bytes
-rw-r--r--Tools/XlsToCsv/test/empty_row.csv3
-rw-r--r--Tools/XlsToCsv/test/empty_row.xlsxbin0 -> 6716 bytes
-rw-r--r--Tools/XlsToCsv/test/escape.csv1
-rw-r--r--Tools/XlsToCsv/test/escape.xlsxbin0 -> 7880 bytes
-rw-r--r--Tools/XlsToCsv/test/float.csv5
-rw-r--r--Tools/XlsToCsv/test/float.xlsxbin0 -> 9472 bytes
-rw-r--r--Tools/XlsToCsv/test/hyperlinks.csv1
-rw-r--r--Tools/XlsToCsv/test/hyperlinks.xlsmbin0 -> 9385 bytes
-rw-r--r--Tools/XlsToCsv/test/hyperlinks_continous.csv18
-rw-r--r--Tools/XlsToCsv/test/hyperlinks_continous.xlsmbin0 -> 9796 bytes
-rw-r--r--Tools/XlsToCsv/test/input-weird.csv2
-rw-r--r--Tools/XlsToCsv/test/input-weird.xlsxbin0 -> 8303 bytes
-rw-r--r--Tools/XlsToCsv/test/junk-small.csv1
-rw-r--r--Tools/XlsToCsv/test/junk-small.xlsxbin0 -> 5729 bytes
-rw-r--r--Tools/XlsToCsv/test/last-column-empty.csv6
-rw-r--r--Tools/XlsToCsv/test/last-column-empty.xlsxbin0 -> 31719 bytes
-rw-r--r--Tools/XlsToCsv/test/namespace.csv7
-rw-r--r--Tools/XlsToCsv/test/namespace.xlsxbin0 -> 5881 bytes
-rw-r--r--Tools/XlsToCsv/test/no_cell_ids.csv3
-rw-r--r--Tools/XlsToCsv/test/no_cell_ids.xlsxbin0 -> 6534 bytes
-rw-r--r--Tools/XlsToCsv/test/run63
-rw-r--r--Tools/XlsToCsv/test/sheets.csv28
-rw-r--r--Tools/XlsToCsv/test/sheets.xlsxbin0 -> 16053 bytes
-rw-r--r--Tools/XlsToCsv/test/sheets_order.csv47
-rw-r--r--Tools/XlsToCsv/test/sheets_order.xlsxbin0 -> 12473 bytes
-rw-r--r--Tools/XlsToCsv/test/skip_empty_lines.csv3
-rw-r--r--Tools/XlsToCsv/test/skip_empty_lines.xlsxbin0 -> 23354 bytes
-rw-r--r--Tools/XlsToCsv/test/timeformat.csv3
-rw-r--r--Tools/XlsToCsv/test/timeformat.xlsxbin0 -> 5432 bytes
-rw-r--r--Tools/XlsToCsv/test/twolettercolumns.csv2
-rw-r--r--Tools/XlsToCsv/test/twolettercolumns.xlsxbin0 -> 6496 bytes
-rw-r--r--Tools/XlsToCsv/test/utf8.csv5
-rw-r--r--Tools/XlsToCsv/test/utf8.xlsxbin0 -> 8990 bytes
-rw-r--r--Tools/XlsToCsv/test/variousdelim.csv1
-rw-r--r--Tools/XlsToCsv/test/variousdelim.xlsxbin0 -> 9872 bytes
-rw-r--r--Tools/XlsToCsv/test/xlsx2csv-test-file.csv44
-rw-r--r--Tools/XlsToCsv/test/xlsx2csv-test-file.xlsxbin0 -> 39063 bytes
-rw-r--r--Tools/XlsToCsv/xlsx2csv.py1203
64 files changed, 2743 insertions, 21 deletions
diff --git a/Editor/EditorApplication.cpp b/Editor/EditorApplication.cpp
index 5315e77..f45a2fc 100644
--- a/Editor/EditorApplication.cpp
+++ b/Editor/EditorApplication.cpp
@@ -16,7 +16,6 @@ EditorApplication::EditorApplication(LuaBind::VM* vm)
EditorApplication::~EditorApplication()
{
-
}
void EditorApplication::SetMainWindow(ContainerWindow* wnd)
diff --git a/Editor/Scripting/EditorScripting.cpp b/Editor/Scripting/EditorScripting.cpp
index 9cd80cc..ad8ccd6 100644
--- a/Editor/Scripting/EditorScripting.cpp
+++ b/Editor/Scripting/EditorScripting.cpp
@@ -1,6 +1,8 @@
#include "EditorScripting.h"
#include "Runtime/Debug/Log.h"
+extern int luaopen_GameLab(lua_State* L); // GameLab
+
extern int luaopen_GameLab_Debug(lua_State* L); // GameLab.Debug
extern int luaopen_GameLab_IO(lua_State* L); // GameLab.IO
extern int luaopen_GameLab_Path(lua_State* L); // GameLab.Path
@@ -33,6 +35,8 @@ bool SetupGameLabEditorScripting(lua_State* L)
{
log_info("Scripting", "SetupGameLabEditorScripting()");
+ openlib(luaopen_GameLab);
+
openlib(luaopen_GameLab_Debug);
openlib(luaopen_GameLab_Path);
openlib(luaopen_GameLab_IO);
diff --git a/Projects/VisualStudio/Editor/Editor.vcxproj b/Projects/VisualStudio/Editor/Editor.vcxproj
index 48e86e6..39a9cf0 100644
--- a/Projects/VisualStudio/Editor/Editor.vcxproj
+++ b/Projects/VisualStudio/Editor/Editor.vcxproj
@@ -202,10 +202,12 @@
<ClCompile Include="..\..\..\Runtime\Math\Vector2.cpp" />
<ClCompile Include="..\..\..\Runtime\Math\Vector3.cpp" />
<ClCompile Include="..\..\..\Runtime\Profiling\FrameStats.cpp" />
+ <ClCompile Include="..\..\..\Runtime\Scripting\Common\Common.bind.cpp" />
+ <ClCompile Include="..\..\..\Runtime\Scripting\Common\DataBuffer.bind.cpp" />
<ClCompile Include="..\..\..\Runtime\Scripting\Debug\Debug.bind.cpp" />
<ClCompile Include="..\..\..\Runtime\Scripting\GL\GL.bind.cpp" />
<ClCompile Include="..\..\..\Runtime\Scripting\IO\IO.bind.cpp" />
- <ClCompile Include="..\..\..\Runtime\Scripting\Path.bind.cpp" />
+ <ClCompile Include="..\..\..\Runtime\Scripting\Path\Path.bind.cpp" />
<ClCompile Include="..\..\..\Runtime\Scripting\Rendering\GPUDataBuffer.bind.cpp" />
<ClCompile Include="..\..\..\Runtime\Scripting\Rendering\Rendering.bind.cpp" />
<ClCompile Include="..\..\..\Runtime\Scripting\Rendering\Shader.bind.cpp" />
diff --git a/Projects/VisualStudio/Editor/Editor.vcxproj.filters b/Projects/VisualStudio/Editor/Editor.vcxproj.filters
index 30edbd9..29505ea 100644
--- a/Projects/VisualStudio/Editor/Editor.vcxproj.filters
+++ b/Projects/VisualStudio/Editor/Editor.vcxproj.filters
@@ -100,6 +100,12 @@
<Filter Include="Runtime\Scripting\IO">
<UniqueIdentifier>{350338b7-1176-4edc-9cc7-553a02d69895}</UniqueIdentifier>
</Filter>
+ <Filter Include="Runtime\Scripting\Common">
+ <UniqueIdentifier>{d7f5479d-f669-434b-bb49-ad652755ab89}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Runtime\Scripting\Path">
+ <UniqueIdentifier>{e9a9d1a8-f637-407d-83f7-99dc9da72b8f}</UniqueIdentifier>
+ </Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\..\Editor\GUI\Dock.cpp">
@@ -231,9 +237,6 @@
<ClCompile Include="..\..\..\Runtime\FileSystem\Path.cpp">
<Filter>Runtime\FileSystem</Filter>
</ClCompile>
- <ClCompile Include="..\..\..\Runtime\Scripting\Path.bind.cpp">
- <Filter>Runtime\Scripting</Filter>
- </ClCompile>
<ClCompile Include="..\..\..\Editor\GUI\ContainerWindow.cpp">
<Filter>Editor\GUI</Filter>
</ClCompile>
@@ -324,6 +327,15 @@
<ClCompile Include="..\..\..\Runtime\Scripting\IO\IO.bind.cpp">
<Filter>Runtime\Scripting\IO</Filter>
</ClCompile>
+ <ClCompile Include="..\..\..\Runtime\Scripting\Common\Common.bind.cpp">
+ <Filter>Runtime\Scripting\Common</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\Runtime\Scripting\Path\Path.bind.cpp">
+ <Filter>Runtime\Scripting\Path</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\Runtime\Scripting\Common\DataBuffer.bind.cpp">
+ <Filter>Runtime\Scripting\Common</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\..\Editor\GUI\Dock.h">
diff --git a/Resources/Scripts/EditorApplication.lua b/Resources/Scripts/EditorApplication.lua
index 10ccbc8..d9f8b1f 100644
--- a/Resources/Scripts/EditorApplication.lua
+++ b/Resources/Scripts/EditorApplication.lua
@@ -48,15 +48,15 @@ GL.ClearColor({1,1,1,1})
GL.Clear(GL.EBufferType.ColorBuffer)
local files = {
-"README.txt",
-"README.txt",
-"README.txt",
-"README.txt",
-"README.txt",
-"README.txt",
-"README.txt",
-"README.txt",
-"README.txt",
+ "README.txt",
+ "README.txt",
+ "README.txt",
+ "README.txt",
+ "README.txt",
+ "README.txt",
+ "README.txt",
+ "README.txt",
+ "README.txt",
}
GameLab.IO.ReadFilesAsync(files, function()
diff --git a/Runtime/Common/DataBuffer.h b/Runtime/Common/DataBuffer.h
index 330128f..591fe89 100644
--- a/Runtime/Common/DataBuffer.h
+++ b/Runtime/Common/DataBuffer.h
@@ -6,9 +6,21 @@
class DataBuffer : public LuaBind::NativeClass<DataBuffer>
{
public:
- DataBuffer(LuaBind::VM* vm);
- ~DataBuffer();
+ DataBuffer(LuaBind::VM* vm)
+ :NativeClass<DataBuffer>(vm)
+ {};
+ ~DataBuffer()
+ {
+ delete data;
+ }
char* data;
int length;
+
+private:
+
+ LUA_BIND_DECL_CLASS(DataBuffer);
+
+ LUA_BIND_DECL_METHOD(_GetLength);
+
}; \ No newline at end of file
diff --git a/Runtime/FileSystem/File.h b/Runtime/FileSystem/File.h
index b0d720c..e64a135 100644
--- a/Runtime/FileSystem/File.h
+++ b/Runtime/FileSystem/File.h
@@ -6,7 +6,5 @@ class File : public LuaBind::NativeClass<File>
public:
File(LuaBind::VM* vm);
- char* data;
- int length;
}; \ No newline at end of file
diff --git a/Runtime/FileSystem/FileJobs.h b/Runtime/FileSystem/FileJobs.h
index 2e53a39..0970c54 100644
--- a/Runtime/FileSystem/FileJobs.h
+++ b/Runtime/FileSystem/FileJobs.h
@@ -8,7 +8,10 @@
class ReadFilesJob : public Job
{
public:
- ReadFilesJob(LuaBind::VM* vm) : callback(vm), cur(0) {}
+ ReadFilesJob(LuaBind::VM* vm)
+ : callback(vm), cur(0)
+ {
+ }
~ReadFilesJob() {}
void Dispacth(void* param) override;
diff --git a/Runtime/FileSystem/Unzip.h b/Runtime/FileSystem/Unzip.h
index 45dcbb0..ca38193 100644
--- a/Runtime/FileSystem/Unzip.h
+++ b/Runtime/FileSystem/Unzip.h
@@ -1,3 +1,10 @@
#pragma once
+#include "Runtime/Threading/Job.h"
+class UnzipFilesJob : public Job
+{
+public:
+
+
+};
diff --git a/Runtime/Lua/LuaBind/LuaBindUtility.h b/Runtime/Lua/LuaBind/LuaBindUtility.h
index 7e6eb77..9cc4802 100644
--- a/Runtime/Lua/LuaBind/LuaBindUtility.h
+++ b/Runtime/Lua/LuaBind/LuaBindUtility.h
@@ -26,8 +26,8 @@
#define LUA_BIND_IMPL_METHOD(type, f) int type::f(lua_State* L)
// 由应用程序实现的两个接口。上下文里有一个state。
-#define LUA_BIND_REGISTRY(type) void type::RegisterClass(::State& state)
-#define LUA_BIND_POSTPROCESS(type) void type::RegisterPostprocess(::State& state)
+#define LUA_BIND_REGISTRY(type) void type::RegisterClass(LuaBind::State& state)
+#define LUA_BIND_POSTPROCESS(type) void type::RegisterPostprocess(LuaBind::State& state)
// 用来注册的宏。之前这里忘了用可变宏,导致没有luaclastable ref没有注册对。
#define LUA_BIND_REGISTER_CLASS(state, param) state.RegisterNativeClass<param>()
diff --git a/Runtime/Scripting/Common/Common.bind.cpp b/Runtime/Scripting/Common/Common.bind.cpp
new file mode 100644
index 0000000..081426d
--- /dev/null
+++ b/Runtime/Scripting/Common/Common.bind.cpp
@@ -0,0 +1,14 @@
+#include "Runtime/Lua/LuaHelper.h"
+#include "Runtime/Common/DataBuffer.h"
+
+int luaopen_GameLab(lua_State* L)
+{
+ LUA_BIND_STATE(L);
+
+ state.PushGlobalNamespace();
+ state.PushNamespace("GameLab");
+
+ state.RegisterNativeClass<DataBuffer>();
+
+ return 1;
+} \ No newline at end of file
diff --git a/Runtime/Scripting/Common/DataBuffer.bind.cpp b/Runtime/Scripting/Common/DataBuffer.bind.cpp
new file mode 100644
index 0000000..5a5c30a
--- /dev/null
+++ b/Runtime/Scripting/Common/DataBuffer.bind.cpp
@@ -0,0 +1,19 @@
+#include "Runtime/Common/DataBuffer.h"
+
+LUA_BIND_REGISTRY(DataBuffer)
+{
+ LUA_BIND_REGISTER_METHODS(state,
+ { "GetLength", _GetLength }
+ );
+}
+
+LUA_BIND_POSTPROCESS(DataBuffer)
+{
+}
+
+LUA_BIND_IMPL_METHOD(DataBuffer, _GetLength)
+{
+ LUA_BIND_PREPARE(L, DataBuffer);
+ state.Push(self->length);
+ return 1;
+} \ No newline at end of file
diff --git a/Runtime/Scripting/Path.bind.cpp b/Runtime/Scripting/Path/Path.bind.cpp
index 920f586..920f586 100644
--- a/Runtime/Scripting/Path.bind.cpp
+++ b/Runtime/Scripting/Path/Path.bind.cpp
diff --git a/Tools/XlsToCsv/.gitignore b/Tools/XlsToCsv/.gitignore
new file mode 100644
index 0000000..7852db8
--- /dev/null
+++ b/Tools/XlsToCsv/.gitignore
@@ -0,0 +1,46 @@
+*.py[cod]
+
+# C extensions
+*.so
+
+# Packages
+*.egg
+*.egg-info
+MANIFEST
+scripts
+dist
+build
+eggs
+parts
+bin
+var
+sdist
+develop-eggs
+.installed.cfg
+lib
+lib64
+__pycache__
+
+# Installer logs
+pip-log.txt
+
+# Unit test / coverage reports
+.coverage
+.tox
+nosetests.xml
+
+# Translations
+*.mo
+
+# Mr Developer
+.mr.developer.cfg
+.project
+.pydevproject
+
+*.swp
+
+# Test
+*.csv-test
+
+# PyCharm
+.idea
diff --git a/Tools/XlsToCsv/CHANGELOG b/Tools/XlsToCsv/CHANGELOG
new file mode 100644
index 0000000..3c586fa
--- /dev/null
+++ b/Tools/XlsToCsv/CHANGELOG
@@ -0,0 +1,97 @@
+version 0.7.9 (2021-Aug-29)
+ * add support for sheetname argument to convert function
+
+version 0.7.8 (2021-Apr-19):
+ * bug fixes
+
+version 0.7.7 (2020-Jun-23):
+version 0.7.6 (2019-Mar-21):
+ * reverting id field for sheet indexing
+
+version 0.7.5 (2019-Mar-7):
+ * Passing "sheets_order" test case. Added support for workbook relations.
+ * add floadformat to README
+ * Updated the date formats to current ms excel
+ * read and use [Content_Types].xml
+ * Fix for missing cell ids ("r" attribute in <c/>)
+ * Refactoring, etc.
+
+version 0.7.4 (2018-Jun-5):
+
+version 0.7.3 (2017-May-20):
+ * Support for "xl/worksheets/worksheet.xml"
+ * Date format "float" leaves value as simple numeric.
+ * bug fixes by Eudes du Rivau
+
+version 0.7.2 (2015-Apr-17):
+ * bug fixes
+
+version 0.7.1 (2015-Feb-10):
+ * more date formats
+ * other fixes
+
+version 0.7 (2014-Jan-28):
+ * hyperlinks support
+
+version 0.6 (2013-Dec-7):
+ * python 2.4 support
+ * python 3.3 support
+ * setuptools
+ * escape \t\r\n characters option
+ * bug fixes
+
+version
+ * seperate csv files for each sheet (#37)
+ * #38 issue fix
+
+version 0.5 (2013-Jun-6):
+ * Select EOL terminator according to OS (#32)
+ * floating errors (#28)
+ * inlineStr support (#24)
+ * Add man/ directory for manual page (#23)
+ * Misinterpretation of & (#15)
+ and other fixes
+
+version 0.20 (2012-Aug-7)
+
+version 0.19 (2012-May-17):
+ * issue #12 fix, some kind of unknown excel app version
+
+version 0.18 (2012-Feb-19):
+ * Support for multi-region shared-strings (merge #11)
+
+version 0.17 (2012-Feb-14):
+ * issue #10 fix
+
+version 0.16 (2011-Oct-26):
+ * new date format
+
+version 0.15 (2011-Sep-16):
+ * datetime 1904 format support
+ * datetime format bug fix
+
+version 0.14 (2011-jul-15):
+ * recursively convert the xlsx files in a directory to csv (Zhehao Mao's patch)
+
+version 0.131 (2011-apr-19):
+ * skip empty lines option
+
+version 0.13 (2011-jan-16):
+ * sheet no bug fix
+
+version 0.12 (2010-dec-21):
+ * fix last column empty bug
+
+version 0.11 (2010-sep-13):
+ * no numFmt bugfix
+
+version 0.1 (2010-sep-12):
+ * better support for date/time formats
+ * two letter columns bug fixed
+ * sheets support added
+ * unicode fix
+ * boolean type
+ - thanks to Neil Killeen
+
+version 0.0 (2010-jun-11):
+ * xlsx to csv converter first release
diff --git a/Tools/XlsToCsv/COPYING b/Tools/XlsToCsv/COPYING
new file mode 100644
index 0000000..ecbc059
--- /dev/null
+++ b/Tools/XlsToCsv/COPYING
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. \ No newline at end of file
diff --git a/Tools/XlsToCsv/LICENSE.txt b/Tools/XlsToCsv/LICENSE.txt
new file mode 100644
index 0000000..ecbc059
--- /dev/null
+++ b/Tools/XlsToCsv/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. \ No newline at end of file
diff --git a/Tools/XlsToCsv/MANIFEST.in b/Tools/XlsToCsv/MANIFEST.in
new file mode 100644
index 0000000..ad047cc
--- /dev/null
+++ b/Tools/XlsToCsv/MANIFEST.in
@@ -0,0 +1,5 @@
+include test/*
+include man/*
+include README
+include LICENSE.txt
+include COPYING
diff --git a/Tools/XlsToCsv/README.md b/Tools/XlsToCsv/README.md
new file mode 100644
index 0000000..e7bd612
--- /dev/null
+++ b/Tools/XlsToCsv/README.md
@@ -0,0 +1,119 @@
+
+# xlsx2csv
+
+> xlsx to csv converter (http://github.com/dilshod/xlsx2csv)
+
+Converts xlsx files to csv format.
+Handles large XLSX files. Fast and easy to use.
+
+## Supported python versions:
+ - 2.4
+ - 2.7
+ - 3.4
+
+## Installation:
+
+```sh
+sudo easy_install xlsx2csv
+```
+ or
+
+```sh
+pip install xlsx2csv
+```
+
+
+ Also, works standalone with only the *xlsx2csv.py* script
+
+**Usage:**
+```
+ xlsx2csv.py [-h] [-v] [-a] [-c OUTPUTENCODING] [-s SHEETID]
+ [-n SHEETNAME] [-d DELIMITER] [-l LINETERMINATOR]
+ [-f DATEFORMAT] [--floatformat FLOATFORMAT]
+ [-i] [-e] [-p SHEETDELIMITER]
+ [--hyperlinks]
+ [-I INCLUDE_SHEET_PATTERN [INCLUDE_SHEET_PATTERN ...]]
+ [-E EXCLUDE_SHEET_PATTERN [EXCLUDE_SHEET_PATTERN ...]] [-m]
+ xlsxfile [outfile]
+```
+**positional arguments:**
+```
+ xlsxfile xlsx file path
+ outfile output csv file path, or directory if -s 0 is specified
+```
+**optional arguments:**
+```
+ -h, --help show this help message and exit
+ -v, --version show program's version number and exit
+ -a, --all export all sheets
+ -c OUTPUTENCODING, --outputencoding OUTPUTENCODING
+ encoding of output csv ** Python 3 only ** (default: utf-8)
+ -s SHEETID, --sheet SHEETID
+ sheet number to convert, 0 for all
+ -n SHEETNAME, --sheetname SHEETNAME
+ sheet name to convert
+ -d DELIMITER, --delimiter DELIMITER
+ delimiter - columns delimiter in csv, 'tab' or 'x09'
+ for a tab (default: comma ',')
+ -l LINETERMINATOR, --lineterminator LINETERMINATOR
+ line terminator - lines terminator in csv, '\n' '\r\n'
+ or '\r' (default: os.linesep)
+ -f DATEFORMAT, --dateformat DATEFORMAT
+ override date/time format (ex. %Y/%m/%d)
+ --floatformat FLOATFORMAT
+ override float format (ex. %.15f)
+ -i, --ignoreempty skip empty lines
+ -e, --escape Escape \r\n\t characters
+ -p SHEETDELIMITER, --sheetdelimiter SHEETDELIMITER
+ sheet delimiter used to separate sheets, pass '' if
+ you do not need delimiter, or 'x07' or '\\f' for form
+ feed (default: '--------')
+ -q QUOTING, --quoting QUOTING
+ field quoting, 'none' 'minimal' 'nonnumeric' or 'all' (default: 'minimal')
+ --hyperlinks, --hyperlinks
+ include hyperlinks
+ -I INCLUDE_SHEET_PATTERN [INCLUDE_SHEET_PATTERN ...], --include_sheet_pattern INCLUDE_SHEET_PATTERN [INCLUDE_SHEET_PATTERN ...]
+ only include sheets named matching given pattern, only
+ effects when -a option is enabled.
+ -E EXCLUDE_SHEET_PATTERN [EXCLUDE_SHEET_PATTERN ...], --exclude_sheet_pattern EXCLUDE_SHEET_PATTERN [EXCLUDE_SHEET_PATTERN ...]
+ exclude sheets named matching given pattern, only
+ effects when -a option is enabled.
+ -m, --merge-cells merge cells
+```
+
+Usage from within Python:
+```
+ from xlsx2csv import Xlsx2csv
+ Xlsx2csv("myfile.xlsx", outputencoding="utf-8").convert("myfile.csv")
+```
+
+Expat SAX parser used for xml parsing.
+
+See alternatives:
+
+Bash:
+http://kirk.webfinish.com/?p=91
+
+Python:
+http://github.com/staale/python-xlsx
+http://github.com/leegao/pyXLSX
+
+Ruby:
+http://roo.rubyforge.org/
+
+Java:
+http://poi.apache.org/
+
+
+All programs in this directory and subdirectories are published under
+license GNU GPL version 2 or (at your option) any later version. For
+more information, see COPYING or visit <https://www.gnu.org/licenses/old-licenses/gpl-2.0.html>.
+
+
+## Meta
+
+ Dilshod Temirkhdojaev 鈥 tdilshod@gmail.com
+
+Distributed under the GNU GENERAL PUBLIC LICENSE. See ``LICENSE`` for more information.
+
+[https://github.com/dilshod](https://github.com/dilshod)
diff --git a/Tools/XlsToCsv/man/Makefile b/Tools/XlsToCsv/man/Makefile
new file mode 100644
index 0000000..14afe2a
--- /dev/null
+++ b/Tools/XlsToCsv/man/Makefile
@@ -0,0 +1,14 @@
+# Makefile
+
+PACKAGE = xlsx2csv
+
+all: man
+
+clean:
+ rm -f *.1
+
+man:
+
+ $(MAKE) -f pod2man.mk PACKAGE=$(PACKAGE) makeman
+
+# End of file
diff --git a/Tools/XlsToCsv/man/pod2man.mk b/Tools/XlsToCsv/man/pod2man.mk
new file mode 100644
index 0000000..7c114d7
--- /dev/null
+++ b/Tools/XlsToCsv/man/pod2man.mk
@@ -0,0 +1,71 @@
+# pod2man.mk -- Makefile portion to convert *.pod files to manual pages
+#
+# Copyright information
+#
+# Copyright (C) 2008-2012 Jari Aalto
+#
+# License
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# Description
+#
+# Convert *.pod files to manual pages. Add this to Makefile:
+#
+# PACKAGE = package
+#
+# man:
+# make -f pod2man.mk PACKAGE=$(PACKAGE) makeman
+#
+# build: man
+
+ifneq (,)
+ This makefile requires GNU Make.
+endif
+
+# This variable *must* be set when called
+PACKAGE ?= package
+
+# Optional variables to set
+MANSECT ?= 1
+PODCENTER ?= User Commands
+PODDATE ?= $$(date "+%Y-%m-%d")
+
+# Directories
+MANSRC ?=
+MANDEST ?= $(MANSRC)
+
+MANPOD ?= $(MANSRC)$(PACKAGE).$(MANSECT).pod
+MANPAGE ?= $(MANDEST)$(PACKAGE).$(MANSECT)
+
+POD2MAN ?= pod2man
+POD2MAN_FLAGS ?= --utf8
+
+makeman: $(MANPAGE)
+
+
+$(MANPAGE): $(MANPOD)
+ # make target - create manual page from a *.pod page
+ podchecker $(MANPOD)
+ LC_ALL= LANG=C $(POD2MAN) $(POD2MAN_FLAGS) \
+ --center="$(PODCENTER)" \
+ --date="$(PODDATE)" \
+ --name="$(PACKAGE)" \
+ --section="$(MANSECT)" \
+ $(MANPOD) \
+ | sed 's,[Pp]erl v[0-9.]\+,$(PACKAGE),' \
+ > $(MANPAGE) && \
+ rm -f pod*.tmp
+
+# End of of Makefile part
diff --git a/Tools/XlsToCsv/man/xlsx2csv.1.pod b/Tools/XlsToCsv/man/xlsx2csv.1.pod
new file mode 100644
index 0000000..788f3a8
--- /dev/null
+++ b/Tools/XlsToCsv/man/xlsx2csv.1.pod
@@ -0,0 +1,120 @@
+# Copyright
+#
+# Copyright (C) 2011-2012 Jari Aalto
+#
+# License
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# Description
+#
+# To learn what TOP LEVEL sections to use in manual page,
+# see POSIX/Susv standard about "Utility Description Defaults" at
+# http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap01.html#tag_01_11
+#
+# This manual page in Perl POD format. Read more at
+# http://perldoc.perl.org/perlpod.html or run command:
+#
+# perldoc perlpod | less
+#
+# To check the syntax:
+#
+# podchecker *.pod
+#
+# To create manual:
+#
+# pod2man PROGRAM.N.pod > PROGRAM.N
+
+=pod
+
+=head1 NAME
+
+xlsx2csv - Convert xlsx xml files to csv format
+
+=head1 SYNOPSIS
+
+ xlsx2csv [options] INFILE [OUTPUT FILE]
+
+=head1 DESCRIPTION
+
+The conversion uses Expat SAX parser for xml processing.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-d DELIMITER, --delimiter=DELIMITER>
+
+Output csv columns delimiter. Use "tab" or "x09" for
+tab. Defaults to comma.
+
+=item B<-f DATEFORMAT, --dateformat=DATEFORMAT>
+
+Set date/time format. See strftime(3) for %-format specifiers. An
+example "%Y-%m-%d".
+
+=item B<-i, --ignoreempty>
+
+Skip empty lines.
+
+=item B<-p SHEETDELIMITER, --sheetdelimiter=SHEETDELIMITER>
+
+Sheet delimiter used to separate sheets, pass "" if you don't want
+delimiters. DEfaults to "--------".
+
+=item B<-q QUOTING, --quoting=QUOTING>
+
+Output csv fields quoting. Use "none" "minimal" "nonnumeric" or "all".
+Defaults to none.
+
+=item B<-r, --recursive>
+
+Convert recursively.
+
+=item B<-s SHEETID, --sheet=SHEETID>
+
+Sheet to convert (0 for all sheets).
+
+=item B<-h, --help>
+
+Display short help and exit.
+
+=item B<--version>
+
+Display program's version number and exit.
+
+=back
+
+=head1 ENVIRONMENT
+
+None.
+
+=head1 FILES
+
+None.
+
+=head1 SEE ALSO
+
+catdoc(1)
+
+=head1 AUTHORS
+
+Program was written by Dilshod Temirkhodjaev <tdilshod@gmail.com>
+
+This manual page was written by Jari Aalto <jari.aalto@cante.net>. Released
+under license GNU GPL version 2 or (at your option) any later
+version. For more information about the license, visit
+<http://www.gnu.org/copyleft/gpl.html>.
+
+=cut
diff --git a/Tools/XlsToCsv/setup.py b/Tools/XlsToCsv/setup.py
new file mode 100644
index 0000000..fe5ddb7
--- /dev/null
+++ b/Tools/XlsToCsv/setup.py
@@ -0,0 +1,55 @@
+
+import os
+import shutil
+from distutils.core import setup
+
+if not os.path.exists('scripts'):
+ os.makedirs('scripts')
+shutil.copyfile('xlsx2csv.py', 'scripts/xlsx2csv')
+
+scripts = ["scripts/xlsx2csv"]
+
+name = "xlsx2csv"
+version = "0.7.8"
+author = "Dilshod Temirkhdojaev"
+author_email = "tdilshod@gmail.com"
+desc = "xlsx to csv converter"
+long_desc = "xlsx to csv converter"
+url = "http://github.com/dilshod/xlsx2csv"
+classifiers=[
+ "Development Status :: 5 - Production/Stable",
+ "Environment :: Console",
+ "Intended Audience :: End Users/Desktop",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: GNU General Public License (GPL)",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 2",
+ "Programming Language :: Python :: 2.4",
+ "Programming Language :: Python :: 2.5",
+ "Programming Language :: Python :: 2.6",
+ "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.0",
+ "Programming Language :: Python :: 3.1",
+ "Programming Language :: Python :: 3.2",
+ "Programming Language :: Python :: 3.3",
+ "Programming Language :: Python :: 3.4",
+ "Topic :: Office/Business",
+ "Topic :: Utilities"
+]
+data_files=[
+]
+
+setup(
+ name='xlsx2csv',
+ version='0.7.8',
+ description=desc,
+ author=author,
+ author_email=author_email,
+ classifiers=classifiers,
+ py_modules=['xlsx2csv'],
+ data_files=data_files,
+ url=url,
+ scripts=scripts
+)
diff --git a/Tools/XlsToCsv/sheets.xlsx b/Tools/XlsToCsv/sheets.xlsx
new file mode 100644
index 0000000..70d1a20
--- /dev/null
+++ b/Tools/XlsToCsv/sheets.xlsx
Binary files differ
diff --git a/Tools/XlsToCsv/test/datetime.csv b/Tools/XlsToCsv/test/datetime.csv
new file mode 100644
index 0000000..a789017
--- /dev/null
+++ b/Tools/XlsToCsv/test/datetime.csv
@@ -0,0 +1 @@
+2011-09-15 15:22:00
diff --git a/Tools/XlsToCsv/test/datetime.xlsx b/Tools/XlsToCsv/test/datetime.xlsx
new file mode 100644
index 0000000..340fa78
--- /dev/null
+++ b/Tools/XlsToCsv/test/datetime.xlsx
Binary files differ
diff --git a/Tools/XlsToCsv/test/empty_row.csv b/Tools/XlsToCsv/test/empty_row.csv
new file mode 100644
index 0000000..9557b99
--- /dev/null
+++ b/Tools/XlsToCsv/test/empty_row.csv
@@ -0,0 +1,3 @@
+,,,,,,,,,,,,
+Date,Agency,Customer,Campaign,Publisher,Format,Inventory,Impressions,Clicks,CTR (%),Price,Price model,Revenue
+At the moment no data for report,,,,,,,,,,,,
diff --git a/Tools/XlsToCsv/test/empty_row.xlsx b/Tools/XlsToCsv/test/empty_row.xlsx
new file mode 100644
index 0000000..fb7e1cf
--- /dev/null
+++ b/Tools/XlsToCsv/test/empty_row.xlsx
Binary files differ
diff --git a/Tools/XlsToCsv/test/escape.csv b/Tools/XlsToCsv/test/escape.csv
new file mode 100644
index 0000000..a33b914
--- /dev/null
+++ b/Tools/XlsToCsv/test/escape.csv
@@ -0,0 +1 @@
+,,,,Hello\nWorld\t!,FALSE
diff --git a/Tools/XlsToCsv/test/escape.xlsx b/Tools/XlsToCsv/test/escape.xlsx
new file mode 100644
index 0000000..57707fe
--- /dev/null
+++ b/Tools/XlsToCsv/test/escape.xlsx
Binary files differ
diff --git a/Tools/XlsToCsv/test/float.csv b/Tools/XlsToCsv/test/float.csv
new file mode 100644
index 0000000..f0723b1
--- /dev/null
+++ b/Tools/XlsToCsv/test/float.csv
@@ -0,0 +1,5 @@
+
+0.10300
+0.27600
+0.10300
+0.27600
diff --git a/Tools/XlsToCsv/test/float.xlsx b/Tools/XlsToCsv/test/float.xlsx
new file mode 100644
index 0000000..9c43167
--- /dev/null
+++ b/Tools/XlsToCsv/test/float.xlsx
Binary files differ
diff --git a/Tools/XlsToCsv/test/hyperlinks.csv b/Tools/XlsToCsv/test/hyperlinks.csv
new file mode 100644
index 0000000..4b8aaf7
--- /dev/null
+++ b/Tools/XlsToCsv/test/hyperlinks.csv
@@ -0,0 +1 @@
+<a href='https://www.google.com/'>google</a>,<a href='https://www.yahoo.com/'>yahoo</a>,<a href='https://www.gmail.com/'>gmail</a>,<a href='http://www.reddit.com/'>reddit</a>,<a href='https://github.com/'>github</a>
diff --git a/Tools/XlsToCsv/test/hyperlinks.xlsm b/Tools/XlsToCsv/test/hyperlinks.xlsm
new file mode 100644
index 0000000..d028025
--- /dev/null
+++ b/Tools/XlsToCsv/test/hyperlinks.xlsm
Binary files differ
diff --git a/Tools/XlsToCsv/test/hyperlinks_continous.csv b/Tools/XlsToCsv/test/hyperlinks_continous.csv
new file mode 100644
index 0000000..81fc63c
--- /dev/null
+++ b/Tools/XlsToCsv/test/hyperlinks_continous.csv
@@ -0,0 +1,18 @@
+<a href='http://google.com/'>google</a>,<a href='http://google.com/'>test</a>,<a href='http://yahoo.com/'>yahoo</a>,<a href='http://reddit.com/'>reddit</a>
+<a href='http://google.com/'>google</a>,<a href='http://google.com/'>test</a>,<a href='http://yahoo.com/'>yahoo</a>,<a href='http://reddit.com/'>reddit</a>
+<a href='http://google.com/'>google</a>,<a href='http://google.com/'>test</a>,<a href='http://yahoo.com/'>yahoo</a>,<a href='http://reddit.com/'>reddit</a>
+<a href='http://google.com/'>google</a>,<a href='http://google.com/'>test</a>,<a href='http://yahoo.com/'>yahoo</a>,<a href='http://reddit.com/'>reddit</a>
+<a href='http://google.com/'>google</a>,<a href='http://google.com/'>test</a>,<a href='http://yahoo.com/'>yahoo</a>,<a href='http://reddit.com/'>reddit</a>
+<a href='http://google.com/'>google</a>,<a href='http://google.com/'>test</a>,<a href='http://yahoo.com/'>yahoo</a>,<a href='http://reddit.com/'>reddit</a>
+<a href='http://google.com/'>google</a>,<a href='http://google.com/'>test</a>,<a href='http://yahoo.com/'>yahoo</a>,<a href='http://reddit.com/'>reddit</a>
+<a href='http://google.com/'>google</a>,<a href='http://google.com/'>test</a>,<a href='http://yahoo.com/'>yahoo</a>,<a href='http://reddit.com/'>reddit</a>
+<a href='http://google.com/'>google</a>,<a href='http://google.com/'>test</a>,<a href='http://yahoo.com/'>yahoo</a>,<a href='http://reddit.com/'>reddit</a>
+<a href='http://reddit.com/'>reddit</a>,<a href='http://yahoo.com/'>yahoo</a>,<a href='http://google.com/'>test</a>,<a href='http://google.com/'>google</a>
+<a href='http://reddit.com/'>reddit</a>,<a href='http://yahoo.com/'>yahoo</a>,<a href='http://google.com/'>test</a>,<a href='http://google.com/'>google</a>
+<a href='http://reddit.com/'>reddit</a>,<a href='http://yahoo.com/'>yahoo</a>,<a href='http://google.com/'>test</a>,<a href='http://google.com/'>google</a>
+<a href='http://reddit.com/'>reddit</a>,<a href='http://yahoo.com/'>yahoo</a>,<a href='http://google.com/'>test</a>,<a href='http://google.com/'>google</a>
+<a href='http://reddit.com/'>reddit</a>,<a href='http://yahoo.com/'>yahoo</a>,<a href='http://google.com/'>test</a>,<a href='http://google.com/'>google</a>
+<a href='http://reddit.com/'>reddit</a>,<a href='http://yahoo.com/'>yahoo</a>,<a href='http://google.com/'>test</a>,<a href='http://google.com/'>google</a>
+<a href='http://reddit.com/'>reddit</a>,<a href='http://yahoo.com/'>yahoo</a>,<a href='http://google.com/'>test</a>,<a href='http://google.com/'>google</a>
+<a href='http://reddit.com/'>reddit</a>,<a href='http://yahoo.com/'>yahoo</a>,<a href='http://google.com/'>test</a>,<a href='http://google.com/'>google</a>
+<a href='http://reddit.com/'>reddit</a>,<a href='http://yahoo.com/'>yahoo</a>,<a href='http://google.com/'>test</a>,<a href='http://google.com/'>google</a>
diff --git a/Tools/XlsToCsv/test/hyperlinks_continous.xlsm b/Tools/XlsToCsv/test/hyperlinks_continous.xlsm
new file mode 100644
index 0000000..ddea849
--- /dev/null
+++ b/Tools/XlsToCsv/test/hyperlinks_continous.xlsm
Binary files differ
diff --git a/Tools/XlsToCsv/test/input-weird.csv b/Tools/XlsToCsv/test/input-weird.csv
new file mode 100644
index 0000000..bdc150a
--- /dev/null
+++ b/Tools/XlsToCsv/test/input-weird.csv
@@ -0,0 +1,2 @@
+,,,
+Some data,,,
diff --git a/Tools/XlsToCsv/test/input-weird.xlsx b/Tools/XlsToCsv/test/input-weird.xlsx
new file mode 100644
index 0000000..c8f74a2
--- /dev/null
+++ b/Tools/XlsToCsv/test/input-weird.xlsx
Binary files differ
diff --git a/Tools/XlsToCsv/test/junk-small.csv b/Tools/XlsToCsv/test/junk-small.csv
new file mode 100644
index 0000000..5212eac
--- /dev/null
+++ b/Tools/XlsToCsv/test/junk-small.csv
@@ -0,0 +1 @@
+29-Mar-1940,25-Jul-2008,08-07-25,08-Apr-2009,test,FALSE
diff --git a/Tools/XlsToCsv/test/junk-small.xlsx b/Tools/XlsToCsv/test/junk-small.xlsx
new file mode 100644
index 0000000..ba720df
--- /dev/null
+++ b/Tools/XlsToCsv/test/junk-small.xlsx
Binary files differ
diff --git a/Tools/XlsToCsv/test/last-column-empty.csv b/Tools/XlsToCsv/test/last-column-empty.csv
new file mode 100644
index 0000000..0b90178
--- /dev/null
+++ b/Tools/XlsToCsv/test/last-column-empty.csv
@@ -0,0 +1,6 @@
+A,B,C
+stuff,more stuff,
+things,more things,even more things
+a,b,
+one,two,
+1,2,3
diff --git a/Tools/XlsToCsv/test/last-column-empty.xlsx b/Tools/XlsToCsv/test/last-column-empty.xlsx
new file mode 100644
index 0000000..4fbaf40
--- /dev/null
+++ b/Tools/XlsToCsv/test/last-column-empty.xlsx
Binary files differ
diff --git a/Tools/XlsToCsv/test/namespace.csv b/Tools/XlsToCsv/test/namespace.csv
new file mode 100644
index 0000000..eff7861
--- /dev/null
+++ b/Tools/XlsToCsv/test/namespace.csv
@@ -0,0 +1,7 @@
+Case # (aka tissue code):,,,,SW101014-03,,,,,,,,,,,+,very light
+Injection Site Location (PHAL/CTB):,,,,VISC Visceral Cortex (VISC) encroaching on the Gustatory Cortex (GU),,,,,,,,,,,++,light
+Injection Site Location (BDA/FG):,,,,Ssp Primary Somatosensory Cortex (SSp) Layers 4 and 5,,,,,,,,,,,+++,moderate
+,,,,,,,,,,,,,,,++++,strong
+Summary Notes:,,,,CTb and BDA did not work,,,,,,,,,,,,
+Atlas level,Data section (File Name),Data section (LIMS),Anatomical Abbr,Anatomical Structure,PHAL,,,CTB,,,BDA,,,FG,,
+,,,,,Contra,Ipsi,Notes,Contra,Ipsi,Notes,Contra,Ipsi,Notes,Contra,Ipsi,Notes
diff --git a/Tools/XlsToCsv/test/namespace.xlsx b/Tools/XlsToCsv/test/namespace.xlsx
new file mode 100644
index 0000000..4f92bee
--- /dev/null
+++ b/Tools/XlsToCsv/test/namespace.xlsx
Binary files differ
diff --git a/Tools/XlsToCsv/test/no_cell_ids.csv b/Tools/XlsToCsv/test/no_cell_ids.csv
new file mode 100644
index 0000000..9557b99
--- /dev/null
+++ b/Tools/XlsToCsv/test/no_cell_ids.csv
@@ -0,0 +1,3 @@
+,,,,,,,,,,,,
+Date,Agency,Customer,Campaign,Publisher,Format,Inventory,Impressions,Clicks,CTR (%),Price,Price model,Revenue
+At the moment no data for report,,,,,,,,,,,,
diff --git a/Tools/XlsToCsv/test/no_cell_ids.xlsx b/Tools/XlsToCsv/test/no_cell_ids.xlsx
new file mode 100644
index 0000000..59c3d64
--- /dev/null
+++ b/Tools/XlsToCsv/test/no_cell_ids.xlsx
Binary files differ
diff --git a/Tools/XlsToCsv/test/run b/Tools/XlsToCsv/test/run
new file mode 100644
index 0000000..990421c
--- /dev/null
+++ b/Tools/XlsToCsv/test/run
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+
+import os
+import sys
+import subprocess
+from io import open
+
+PYTHON_VERSIONS = ["2", "3"]
+
+"""
+This test uses sys.stdout.
+That means this test doesn't verify:
+ - file output process
+ - differences from sys.stdout like line terminater
+"""
+
+def compare(case, arguments=[]):
+ failed = False
+ for pyver in PYTHON_VERSIONS:
+ ext = "xlsx"
+ if os.path.exists("test/%s.xlsm" % case):
+ ext = "xlsm"
+
+ if os.name == 'posix':# in case of Linux
+ left = subprocess.check_output(["python%s" %pyver, "./xlsx2csv.py"] + arguments + ["test/%s.%s" %(case, ext)]).decode('utf-8').replace('\r','')
+ elif os.name == 'nt':# in case of Windows
+ # Use py.exe http://blog.python.org/2011/07/python-launcher-for-windows_11.html on Windows
+ left = subprocess.check_output(["py", "-%s" %pyver, "./xlsx2csv.py"] + arguments + ["test/%s.%s" %(case, ext)]).decode('utf-8').replace('\r','')
+ else:
+ print("os.name is unexpected: "+os.name)
+ sys.exit(1)
+
+ f = open("test/%s.csv" %case, "r", encoding="utf-8", newline="")
+ right = f.read().replace('\r','')
+ f.close()
+
+ if left != right:
+ print("FAILED: %s %s" %(case, pyver))
+ print(" actual:", left.replace("\r", "\\r").replace("\n", "\\n"))
+ print(" expected:", right.replace("\r", "\\r").replace("\n", "\\n"))
+ failed = True
+ else:
+ print("OK: %s %s" %(case, pyver))
+ if failed:
+ sys.exit(1)
+
+compare("datetime", ["--dateformat=%Y-%m-%d %H:%M:%S"])
+compare("empty_row")
+compare("junk-small")
+compare("last-column-empty")
+compare("sheets", ["-a"])
+compare("skip_empty_lines", ["-i"])
+compare("twolettercolumns")
+compare("xlsx2csv-test-file")
+compare("escape", ["-e"])
+compare("hyperlinks", ["--hyperlinks"])
+compare("hyperlinks_continous", ["--hyperlinks"])
+compare("namespace")
+compare("float")
+compare("variousdelim", ["--all","--sheetdelimiter=x33", "--lineterminator=\\r", "--delimiter=\\t"])
+compare("utf8")
+compare("no_cell_ids")
+compare("sheets_order", ["-a"])
diff --git a/Tools/XlsToCsv/test/sheets.csv b/Tools/XlsToCsv/test/sheets.csv
new file mode 100644
index 0000000..a0fb509
--- /dev/null
+++ b/Tools/XlsToCsv/test/sheets.csv
@@ -0,0 +1,28 @@
+-------- 1 - 袪械械褋褌褉
+鈩,URL,袧邪蟹胁邪薪懈械,袙械褉.,小芯褋褌.,袗薪邪谢懈褌懈泻,袟邪泻邪蟹褔懈泻
+1,url,<<楔邪斜谢芯薪 褋褑械薪邪褉懈褟>>,1.0,袩芯写锌.,肖邪屑懈谢懈褟 ,肖邪屑懈谢懈褟
+2,,,,,,
+3,,,,,,
+4,,,,,,
+5,,,,,,
+-------- 2 - 袙邪褉懈邪薪褌 懈褋锌芯谢褜蟹芯胁邪薪懈褟
+鈩,协谢械屑械薪褌,袨锌懈褋邪薪懈械,袪械蟹褍谢褜褌邪褌 褕邪谐邪 (袙褘褏芯写),小褋褘谢泻懈
+1,袧芯屑械褉,袩芯谢薪褘泄 泻芯写 (薪芯屑械褉) 褋褑械薪邪褉懈褟,,
+2,袧邪蟹胁邪薪懈械,袩芯谢薪芯械 薪邪蟹胁邪薪懈械 褋褑械薪邪褉懈褟,,
+3,袨锌懈褋邪薪懈械,袣褉邪褌泻芯械 芯锌懈褋邪薪懈械 褋褍褌懈 褋褑械薪邪褉懈褟,,
+4,孝懈锌,孝懈锌 褋褑械薪邪褉懈褟 / 校褉芯胁械薪褜 褋褑械薪邪褉懈褟 - 屑芯卸薪芯 芯锌褍褋褌懈褌褜 懈蟹 芯锌懈褋邪薪懈褟,,
+5,袧邪褋谢械写褍械褌,袣邪泻芯泄 褋褑械薪邪褉懈泄 褟胁谢褟械褌褋褟 褉芯写懈褌械谢褜褋泻懈屑 (斜邪蟹芯胁褘屑) 写谢褟 写邪薪薪芯谐芯 褋褑械薪邪褉懈褟,,
+6.1,袗泻褌械褉 ,袣褌芯 芯褋薪芯胁薪芯械 写械泄褋褌胁褍褞褖械械 谢懈褑芯. 袝褋谢懈 械褋褌褜 械褖械 - 写芯斜邪胁谢褟械屑,,
+6.2,小懈褋褌械屑邪,袣褌芯 芯褋薪芯胁薪芯械 写械泄褋褌胁褍褞褖械械 谢懈褑芯 褋 锌芯蟹懈褑懈懈 褋懈褋褌械屑褘 (锌褉芯写褍泻褌邪). 袝褋谢懈 械褋褌褜 械褖械 - 写芯斜邪胁谢褟械屑,,
+7.1,笑械谢褜,袨写薪邪 懈谢懈 薪械褋泻芯谢褜泻芯 芯锌褉械写械谢械薪薪褘褏 褑械谢械泄 写谢褟 褋褑械薪邪褉懈褟,笑械谢械胁芯泄 锌芯泻邪蟹邪褌械谢褜,
+7.2,笑械谢褜,,,
+7.3,笑械谢褜,,,
+8.1,楔邪谐,袨锌懈褋邪薪懈械 褕邪谐邪 - 泻褌芯 褔褌芯 写械谢邪械褌 / 褋 泻械屑-褔械屑 胁蟹邪懈屑芯写械泄褋褌胁褍械褌,袗褉褌械褎邪泻褌 薪邪 胁褘褏芯写械,
+8.2,楔邪谐,,,
+8.褏,楔邪谐,,,
+9,袗谢褜褌/袠褋泻谢.,薪邪蟹胁邪薪懈械 邪谢褜褌械褉薪邪褌懈胁薪芯谐芯 锌芯褌芯泻邪 / 懈褋泻谢褞褔械薪懈褟,,
+9.1,楔邪谐,,,
+9.褏,楔邪谐,,,
+10,袗谢褜褌/袠褋泻谢.,薪邪蟹胁邪薪懈械 邪谢褜褌械褉薪邪褌懈胁薪芯谐芯 锌芯褌芯泻邪 / 懈褋泻谢褞褔械薪懈褟,,
+10.1,楔邪谐,,,
+10.褏,楔邪谐,,,
diff --git a/Tools/XlsToCsv/test/sheets.xlsx b/Tools/XlsToCsv/test/sheets.xlsx
new file mode 100644
index 0000000..70d1a20
--- /dev/null
+++ b/Tools/XlsToCsv/test/sheets.xlsx
Binary files differ
diff --git a/Tools/XlsToCsv/test/sheets_order.csv b/Tools/XlsToCsv/test/sheets_order.csv
new file mode 100644
index 0000000..e5820c4
--- /dev/null
+++ b/Tools/XlsToCsv/test/sheets_order.csv
@@ -0,0 +1,47 @@
+-------- 1 - b
+x,y
+-10,-1000
+-9,-729
+-8,-512
+-7,-343
+-6,-216
+-5,-125
+-4,-64
+-3,-27
+-2,-8
+-1,-1
+0,0
+1,1
+2,8
+3,27
+4,64
+5,125
+6,216
+7,343
+8,512
+9,729
+10,1000
+11,1331
+12,1728
+13,2197
+14,2744
+-------- 2 - e
+EEEEE
+EEEE
+EEE
+EE
+E
+-------- 3 - d
+DDDD
+DDD
+DD
+D
+-------- 4 - a
+AAAAAAA
+AAAA
+AAA
+AA
+AA
+AAA
+AAAA
+AAAAAAA
diff --git a/Tools/XlsToCsv/test/sheets_order.xlsx b/Tools/XlsToCsv/test/sheets_order.xlsx
new file mode 100644
index 0000000..ba782c3
--- /dev/null
+++ b/Tools/XlsToCsv/test/sheets_order.xlsx
Binary files differ
diff --git a/Tools/XlsToCsv/test/skip_empty_lines.csv b/Tools/XlsToCsv/test/skip_empty_lines.csv
new file mode 100644
index 0000000..4362b53
--- /dev/null
+++ b/Tools/XlsToCsv/test/skip_empty_lines.csv
@@ -0,0 +1,3 @@
+鈩,URL,袧邪蟹胁邪薪懈械,袙械褉.,小芯褋褌.,袗薪邪谢懈褌懈泻,袟邪泻邪蟹褔懈泻
+1,url,<<楔邪斜谢芯薪 褋褑械薪邪褉懈褟>>,1.0,袩芯写锌.,肖邪屑懈谢懈褟 ,肖邪屑懈谢懈褟
+3,,,,,,
diff --git a/Tools/XlsToCsv/test/skip_empty_lines.xlsx b/Tools/XlsToCsv/test/skip_empty_lines.xlsx
new file mode 100644
index 0000000..82c6600
--- /dev/null
+++ b/Tools/XlsToCsv/test/skip_empty_lines.xlsx
Binary files differ
diff --git a/Tools/XlsToCsv/test/timeformat.csv b/Tools/XlsToCsv/test/timeformat.csv
new file mode 100644
index 0000000..472fd3b
--- /dev/null
+++ b/Tools/XlsToCsv/test/timeformat.csv
@@ -0,0 +1,3 @@
+"03""-""08""-""2017"" ""14:35:00",14:40
+"03""-""08""-""2017"" ""00:00:00",11:30
+"03""-""08""-""2017"" ""15:40:00",00:01
diff --git a/Tools/XlsToCsv/test/timeformat.xlsx b/Tools/XlsToCsv/test/timeformat.xlsx
new file mode 100644
index 0000000..4e8f218
--- /dev/null
+++ b/Tools/XlsToCsv/test/timeformat.xlsx
Binary files differ
diff --git a/Tools/XlsToCsv/test/twolettercolumns.csv b/Tools/XlsToCsv/test/twolettercolumns.csv
new file mode 100644
index 0000000..c9e3abb
--- /dev/null
+++ b/Tools/XlsToCsv/test/twolettercolumns.csv
@@ -0,0 +1,2 @@
+1,2,3,4,5,6,7,8,9,,,,,,,,,,,,,,,,,10,11,12
+a,b,c,d,e,f,g,,,,,,,,,,,,,,,,,,,h,I,j
diff --git a/Tools/XlsToCsv/test/twolettercolumns.xlsx b/Tools/XlsToCsv/test/twolettercolumns.xlsx
new file mode 100644
index 0000000..b25cbff
--- /dev/null
+++ b/Tools/XlsToCsv/test/twolettercolumns.xlsx
Binary files differ
diff --git a/Tools/XlsToCsv/test/utf8.csv b/Tools/XlsToCsv/test/utf8.csv
new file mode 100644
index 0000000..2b4212b
--- /dev/null
+++ b/Tools/XlsToCsv/test/utf8.csv
@@ -0,0 +1,5 @@
+喔抚喔编釜喔斷傅 喔勦福喔编笟,Thai language
+銇撱倱銇仭銇,Japanese language
+袟写褉邪胁褋褌胁褍泄褌械,Russian language
+啶ㄠぎ啶膏啶む,Hindi
+丕賱爻賱丕賲 毓賱賷賰賲,Arabic
diff --git a/Tools/XlsToCsv/test/utf8.xlsx b/Tools/XlsToCsv/test/utf8.xlsx
new file mode 100644
index 0000000..509d5e4
--- /dev/null
+++ b/Tools/XlsToCsv/test/utf8.xlsx
Binary files differ
diff --git a/Tools/XlsToCsv/test/variousdelim.csv b/Tools/XlsToCsv/test/variousdelim.csv
new file mode 100644
index 0000000..10e5ae3
--- /dev/null
+++ b/Tools/XlsToCsv/test/variousdelim.csv
@@ -0,0 +1 @@
+! 1 - Sheet1 1 2 3 a b c ! 2 - Sheet2 4 5 6 d e f \ No newline at end of file
diff --git a/Tools/XlsToCsv/test/variousdelim.xlsx b/Tools/XlsToCsv/test/variousdelim.xlsx
new file mode 100644
index 0000000..fc2382b
--- /dev/null
+++ b/Tools/XlsToCsv/test/variousdelim.xlsx
Binary files differ
diff --git a/Tools/XlsToCsv/test/xlsx2csv-test-file.csv b/Tools/XlsToCsv/test/xlsx2csv-test-file.csv
new file mode 100644
index 0000000..ec474c4
--- /dev/null
+++ b/Tools/XlsToCsv/test/xlsx2csv-test-file.csv
@@ -0,0 +1,44 @@
+A,B,C
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+,MSP,
+blah,PPS,
+blah,PPS,
+blah,PPS,
+blah,PPS,
+blah,PPS,
+blah,PPS,
+blah,PPS,
+blah,PPS,
+blah,PPS,
+blah,PPS,
+blah,PPS,
+blah,PPS,
diff --git a/Tools/XlsToCsv/test/xlsx2csv-test-file.xlsx b/Tools/XlsToCsv/test/xlsx2csv-test-file.xlsx
new file mode 100644
index 0000000..828cfd7
--- /dev/null
+++ b/Tools/XlsToCsv/test/xlsx2csv-test-file.xlsx
Binary files differ
diff --git a/Tools/XlsToCsv/xlsx2csv.py b/Tools/XlsToCsv/xlsx2csv.py
new file mode 100644
index 0000000..fe7874b
--- /dev/null
+++ b/Tools/XlsToCsv/xlsx2csv.py
@@ -0,0 +1,1203 @@
+#!/usr/bin/env python
+#
+# Copyright information
+#
+# Copyright (C) 2010-2018 Dilshod Temirkhodjaev <tdilshod@gmail.com>
+#
+# License
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+from __future__ import print_function
+
+__author__ = "Dilshod Temirkhodjaev <tdilshod@gmail.com>"
+__license__ = "GPL-2+"
+__version__ = "0.7.9"
+
+import csv, datetime, zipfile, string, sys, os, re, signal
+import xml.parsers.expat
+from xml.dom import minidom
+
+try:
+ # python2.4
+ from cStringIO import StringIO
+except:
+ pass
+try:
+ from argparse import ArgumentParser
+except:
+ # python2.4
+ from optparse import OptionParser
+
+# see also ruby-roo lib at: http://github.com/hmcgowan/roo
+FORMATS = {
+ 'general': 'float',
+ '0': 'float',
+ '0.00': 'float',
+ '#,##0': 'float',
+ '#,##0.00': 'float',
+ '0%': 'percentage',
+ '0.00%': 'percentage',
+ '0.00e+00': 'float',
+ 'mm-dd-yy': 'date',
+ 'd-mmm-yy': 'date',
+ 'd-mmm': 'date',
+ 'mmm-yy': 'date',
+ 'h:mm am/pm': 'date',
+ 'h:mm:ss am/pm': 'date',
+ 'h:mm': 'time',
+ 'h:mm:ss': 'time',
+ 'm/d/yy h:mm': 'date',
+ '#,##0 ;(#,##0)': 'float',
+ '#,##0 ;[red](#,##0)': 'float',
+ '#,##0.00;(#,##0.00)': 'float',
+ '#,##0.00;[red](#,##0.00)': 'float',
+ 'mm:ss': 'time',
+ '[h]:mm:ss': 'time',
+ 'mmss.0': 'time',
+ '##0.0e+0': 'float',
+ '@': 'float',
+ 'yyyy\\-mm\\-dd': 'date',
+ 'dd/mm/yy': 'date',
+ 'hh:mm:ss': 'time',
+ "dd/mm/yy\\ hh:mm": 'date',
+ 'dd/mm/yyyy hh:mm:ss': 'date',
+ 'yy-mm-dd': 'date',
+ 'd-mmm-yyyy': 'date',
+ 'm/d/yy': 'date',
+ 'm/d/yyyy': 'date',
+ 'dd-mmm-yyyy': 'date',
+ 'dd/mm/yyyy': 'date',
+ 'mm/dd/yy h:mm am/pm': 'date',
+ 'mm/dd/yy hh:mm': 'date',
+ 'mm/dd/yyyy h:mm am/pm': 'date',
+ 'mm/dd/yyyy hh:mm:ss': 'date',
+ 'yyyy-mm-dd hh:mm:ss': 'date',
+ '#,##0;(#,##0)': 'float',
+ '_(* #,##0_);_(* (#,##0);_(* "-"??_);_(@_)': 'float',
+ '_(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(@_)': 'float'
+}
+STANDARD_FORMATS = {
+ 0: 'general',
+ 1: '0',
+ 2: '0.00',
+ 3: '#,##0',
+ 4: '#,##0.00',
+ 9: '0%',
+ 10: '0.00%',
+ 11: '0.00e+00',
+ 12: '# ?/?',
+ 13: '# ??/??',
+ 14: 'mm-dd-yy',
+ 15: 'd-mmm-yy',
+ 16: 'd-mmm',
+ 17: 'mmm-yy',
+ 18: 'h:mm am/pm',
+ 19: 'h:mm:ss am/pm',
+ 20: 'h:mm',
+ 21: 'h:mm:ss',
+ 22: 'm/d/yy h:mm',
+ 37: '#,##0 ;(#,##0)',
+ 38: '#,##0 ;[red](#,##0)',
+ 39: '#,##0.00;(#,##0.00)',
+ 40: '#,##0.00;[red](#,##0.00)',
+ 45: 'mm:ss',
+ 46: '[h]:mm:ss',
+ 47: 'mmss.0',
+ 48: '##0.0e+0',
+ 49: '@',
+}
+CONTENT_TYPES = {
+ 'shared_strings',
+ 'styles',
+ 'workbook',
+ 'worksheet',
+ 'relationships',
+}
+
+DEFAULT_APP_PATH = "/xl"
+DEFAULT_WORKBOOK_PATH = DEFAULT_APP_PATH + "/workbook.xml"
+
+def eprint(*args, **kwargs):
+ print(*args, file=sys.stderr, **kwargs)
+
+class XlsxException(Exception):
+ pass
+
+
+class InvalidXlsxFileException(XlsxException):
+ pass
+
+
+class SheetNotFoundException(XlsxException):
+ pass
+
+
+class OutFileAlreadyExistsException(XlsxException):
+ pass
+
+
+class Xlsx2csv:
+ """
+ Usage: Xlsx2csv("test.xslx", **params).convert("test.csv", sheetid=1)
+ Input:
+ xlsxfile - path to file or filehandle
+ options:
+ sheetid - sheet no to convert (0 for all sheets)
+ sheetname - sheet name to convert
+ dateformat - override date/time format
+ timeformat - override time format
+ floatformat - override float format
+ quoting - if and how to quote
+ delimiter - csv columns delimiter symbol
+ sheetdelimiter - sheets delimiter used when processing all sheets
+ skip_empty_lines - skip empty lines
+ skip_trailing_columns - skip trailing columns
+ hyperlinks - include hyperlinks
+ include_sheet_pattern - only include sheets named matching given pattern
+ exclude_sheet_pattern - exclude sheets named matching given pattern
+ exclude_hidden_sheets - exclude hidden sheets
+ """
+
+ def __init__(self, xlsxfile, **options):
+ options.setdefault("delimiter", ",")
+ options.setdefault("quoting", csv.QUOTE_MINIMAL)
+ options.setdefault("sheetdelimiter", "--------")
+ options.setdefault("dateformat", None)
+ options.setdefault("timeformat", None)
+ options.setdefault("floatformat", None)
+ options.setdefault("scifloat", False)
+ options.setdefault("skip_empty_lines", False)
+ options.setdefault("skip_trailing_columns", False)
+ options.setdefault("escape_strings", False)
+ options.setdefault("no_line_breaks", False)
+ options.setdefault("hyperlinks", False)
+ options.setdefault("include_sheet_pattern", ["^.*$"])
+ options.setdefault("exclude_sheet_pattern", [])
+ options.setdefault("exclude_hidden_sheets", False)
+ options.setdefault("merge_cells", False)
+ options.setdefault("ignore_formats", [''])
+ options.setdefault("lineterminator", "\n")
+ options.setdefault("outputencoding", "utf-8")
+
+ self.options = options
+ try:
+ self.ziphandle = zipfile.ZipFile(xlsxfile)
+ except (zipfile.BadZipfile, IOError):
+ raise InvalidXlsxFileException("Invalid xlsx file: " + str(xlsxfile))
+
+ self.py3 = sys.version_info[0] == 3
+
+ self.content_types = self._parse(ContentTypes, "/[Content_Types].xml")
+ self.shared_strings = self._parse(SharedStrings, self.content_types.types["shared_strings"])
+ self.styles = self._parse(Styles, self.content_types.types["styles"])
+ self.workbook = self._parse(Workbook, self.content_types.types["workbook"])
+ workbook_relationships = list(filter(lambda r: "book" in r, self.content_types.types["relationships"]))[0]
+ self.workbook.relationships = self._parse(Relationships, workbook_relationships)
+ if self.options['no_line_breaks']:
+ self.shared_strings.replace_line_breaks()
+ elif self.options['escape_strings']:
+ self.shared_strings.escape_strings()
+
+ def __del__(self):
+ # make sure to close zip file, ziphandler does have a close() method
+ self.ziphandle.close()
+
+ def getSheetIdByName(self, name):
+ for s in self.workbook.sheets:
+ if s['name'] == name:
+ return s['index']
+ return None
+
+ def convert(self, outfile, sheetid=1, sheetname=None):
+ """outfile - path to file or filehandle"""
+ if sheetname:
+ sheetid = self.getSheetIdByName(sheetname)
+ if not sheetid:
+ raise XlsxException("Sheet '%s' not found" % sheetname)
+ if sheetid > 0:
+ self._convert(sheetid, outfile)
+ else:
+ if isinstance(outfile, str):
+ if not os.path.exists(outfile):
+ os.makedirs(outfile)
+ elif os.path.isfile(outfile):
+ raise OutFileAlreadyExistsException("File " + str(outfile) + " already exists!")
+ for s in self.workbook.sheets:
+ sheetname = s['name']
+ sheetstate = s['state']
+
+ # filter hidden sheets
+ if sheetstate in ('hidden', 'veryHidden') and self.options['exclude_hidden_sheets']:
+ continue
+
+ # filter sheets by include pattern
+ include_sheet_pattern = self.options['include_sheet_pattern']
+ if type(include_sheet_pattern) == type(""): # optparser lib fix
+ include_sheet_pattern = [include_sheet_pattern]
+ if len(include_sheet_pattern) > 0:
+ include = False
+ for pattern in include_sheet_pattern:
+ include = pattern and len(pattern) > 0 and re.match(pattern, sheetname)
+ if include:
+ break
+ if not include:
+ continue
+
+ # filter sheets by exclude pattern
+ exclude_sheet_pattern = self.options['exclude_sheet_pattern']
+ if type(exclude_sheet_pattern) == type(""): # optparser lib fix
+ exclude_sheet_pattern = [exclude_sheet_pattern]
+ exclude = False
+ for pattern in exclude_sheet_pattern:
+ exclude = pattern and len(pattern) > 0 and re.match(pattern, sheetname)
+ if exclude:
+ break
+ if exclude:
+ continue
+
+ if not self.py3:
+ sheetname = sheetname.encode('utf-8')
+ of = outfile
+ if isinstance(outfile, str):
+ of = os.path.join(outfile, sheetname + '.csv')
+ elif self.options['sheetdelimiter'] and len(self.options['sheetdelimiter']):
+ of.write(self.options['sheetdelimiter'] + " " + str(s['index']) + " - " + sheetname + self.options['lineterminator'])
+ self._convert(s['index'], of)
+
+ def _convert(self, sheet_index, outfile):
+ closefile = False
+ if isinstance(outfile, str):
+ if sys.version_info[0] == 2:
+ outfile = open(outfile, 'wb+')
+ elif sys.version_info[0] == 3:
+ outfile = open(outfile, 'w+', encoding=self.options['outputencoding'], newline="")
+ else:
+ sys.stderr.write("error: version of your python is not supported: " + str(sys.version_info) + "\n")
+ sys.exit(1)
+ closefile = True
+ try:
+ writer = csv.writer(outfile, quoting=self.options['quoting'], delimiter=self.options['delimiter'],
+ lineterminator=self.options['lineterminator'])
+
+ sheets_filtered = list(filter(lambda s: s['index'] == sheet_index, self.workbook.sheets))
+ if len(sheets_filtered) == 0:
+ eprint("Sheet with index %i not found or can't be handled" % sheet_index)
+ return 1
+
+ sheet_path = None
+ # using sheet relation information
+ if 'relation_id' in sheets_filtered[0] and sheets_filtered[0]['relation_id'] is not None:
+
+ relation_id = sheets_filtered[0]['relation_id']
+ if relation_id in self.workbook.relationships.relationships and \
+ 'target' in self.workbook.relationships.relationships[relation_id]:
+ relationship = self.workbook.relationships.relationships[relation_id]
+ sheet_path = relationship['target']
+ if not (sheet_path.startswith("/xl/") or sheet_path.startswith("xl/")):
+ sheet_path = "/xl/" + sheet_path
+
+ sheet_file = None
+ if sheet_path is None:
+ sheet_path = "/xl/worksheets/sheet%i.xml" % sheet_index
+ sheet_file = self._filehandle(sheet_path)
+ if sheet_file is None:
+ sheet_path = None
+ if sheet_path is None:
+ sheet_path = "/xl/worksheets/worksheet%i.xml" % sheet_index
+ sheet_file = self._filehandle(sheet_path)
+ if sheet_file is None:
+ sheet_path = None
+ if sheet_path is None and sheet_index == 1:
+ sheet_path = self.content_types.types["worksheet"]
+ sheet_file = self._filehandle(sheet_path)
+ if sheet_file is None:
+ sheet_path = None
+ if sheet_file is None and sheet_path is not None:
+ sheet_file = self._filehandle(sheet_path)
+ if sheet_file is None:
+ raise SheetNotFoundException("Sheet %i not found" % sheet_index)
+ sheet = Sheet(self.workbook, self.shared_strings, self.styles, sheet_file)
+ try:
+ relationships_path = os.path.join(os.path.dirname(sheet_path),
+ "_rels",
+ os.path.basename(sheet_path) + ".rels")
+ sheet.relationships = self._parse(Relationships, relationships_path)
+ sheet.set_dateformat(self.options['dateformat'])
+ sheet.set_timeformat(self.options['timeformat'])
+ sheet.set_floatformat(self.options['floatformat'])
+ sheet.set_skip_empty_lines(self.options['skip_empty_lines'])
+ sheet.set_skip_trailing_columns(self.options['skip_trailing_columns'])
+ sheet.set_include_hyperlinks(self.options['hyperlinks'])
+ sheet.set_merge_cells(self.options['merge_cells'])
+ sheet.set_scifloat(self.options['scifloat'])
+ sheet.set_ignore_formats(self.options['ignore_formats'])
+ if self.options['escape_strings'] and sheet.filedata:
+ sheet.filedata = re.sub(r"(<v>[^<>]+)&#10;([^<>]+</v>)", r"\1\\n\2",
+ re.sub(r"(<v>[^<>]+)&#9;([^<>]+</v>)", r"\1\\t\2",
+ re.sub(r"(<v>[^<>]+)&#13;([^<>]+</v>)", r"\1\\r\2", sheet.filedata)))
+ sheet.to_csv(writer)
+ finally:
+ sheet_file.close()
+ sheet.close()
+ finally:
+ if closefile:
+ outfile.close()
+
+ def _filehandle(self, filename):
+ for name in filter(lambda f: filename and f.lower() == filename.lower()[1:], self.ziphandle.namelist()):
+ # python2.4 fix
+ if not hasattr(self.ziphandle, "open"):
+ return StringIO(self.ziphandle.read(name))
+ return self.ziphandle.open(name, "r")
+ return None
+
+ def _parse(self, klass, filename):
+ instance = klass()
+ filehandle = self._filehandle(filename)
+ if filehandle:
+ instance.parse(filehandle)
+ filehandle.close()
+ return instance
+
+
+class Workbook:
+ def __init__(self):
+ self.sheets = list()
+ self.date1904 = False
+
+ def parse(self, filehandle):
+ workbookDoc = minidom.parseString(filehandle.read())
+ if workbookDoc.firstChild.namespaceURI:
+ fileVersion = workbookDoc.firstChild.getElementsByTagNameNS(workbookDoc.firstChild.namespaceURI,
+ "fileVersion")
+ else:
+ fileVersion = workbookDoc.firstChild.getElementsByTagName("fileVersion")
+ if len(fileVersion) == 0:
+ self.appName = DEFAULT_APP_PATH
+ else:
+ try:
+ if workbookDoc.firstChild.namespaceURI:
+ self.appName = \
+ workbookDoc.firstChild.getElementsByTagNameNS(
+ workbookDoc.firstChild.namespaceURI, "fileVersion")[0]._attrs['appName'].value
+ else:
+ self.appName = workbookDoc.firstChild.getElementsByTagName("fileVersion")[0]._attrs['appName'].value
+ except KeyError:
+ # no app name
+ self.appName = DEFAULT_APP_PATH
+ try:
+ if workbookDoc.firstChild.namespaceURI:
+ self.date1904 = \
+ workbookDoc.firstChild.getElementsByTagNameNS(
+ workbookDoc.firstChild.namespaceURI, "workbookPr")[0]._attrs['date1904'].value.lower().strip() \
+ != "false"
+ else:
+ self.date1904 = \
+ workbookDoc.firstChild.getElementsByTagName("workbookPr")[0] \
+ ._attrs['date1904'].value.lower().strip() \
+ != "false"
+ except:
+ pass
+
+ if workbookDoc.firstChild.namespaceURI:
+ sheets = workbookDoc.firstChild.getElementsByTagNameNS(workbookDoc.firstChild.namespaceURI, "sheets")[0]
+ else:
+ sheets = workbookDoc.firstChild.getElementsByTagName("sheets")[0]
+ if workbookDoc.firstChild.namespaceURI:
+ sheetNodes = sheets.getElementsByTagNameNS(workbookDoc.firstChild.namespaceURI, "sheet")
+ else:
+ sheetNodes = sheets.getElementsByTagName("sheet")
+ for i, sheetNode in enumerate(sheetNodes):
+ attrs = sheetNode._attrs
+ name = attrs["name"].value
+ state = None
+ if 'state' in attrs:
+ state = attrs["state"].value
+ relation_id = None
+ if 'r:id' in attrs:
+ relation_id = attrs['r:id'].value
+ self.sheets.append(
+ {
+ 'name': name,
+ 'relation_id': relation_id,
+ 'index': i + 1,
+ 'id': i + 1, # remove id starting 0.8.0 version
+ 'state': state
+ }
+ )
+
+
+class ContentTypes:
+ def __init__(self):
+ self.types = {}
+ for type in CONTENT_TYPES:
+ self.types[type] = None
+
+ def parse(self, filehandle):
+ types = minidom.parseString(filehandle.read()).firstChild
+ if not types:
+ return
+ if types.namespaceURI:
+ overrideNodes = types.getElementsByTagNameNS(types.namespaceURI, "Override")
+ else:
+ overrideNodes = types.getElementsByTagName("Override")
+ for override in overrideNodes:
+ attrs = override._attrs
+ type = attrs.get('ContentType').value
+ name = attrs.get('PartName').value
+ if type == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml":
+ self.types["workbook"] = name
+ elif type == "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml":
+ self.types["styles"] = name
+ elif type == "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml":
+ # BUG preserved only last sheet
+ self.types["worksheet"] = name
+ elif type == "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml":
+ self.types["shared_strings"] = name
+ elif type == "application/vnd.openxmlformats-package.relationships+xml":
+ if self.types["relationships"] is None:
+ self.types["relationships"] = list()
+ self.types["relationships"].append(name)
+
+ if self.types["workbook"] is None:
+ self.types["workbook"] = DEFAULT_WORKBOOK_PATH
+ if self.types["relationships"] is None:
+ self.types["relationships"] = [os.path.dirname(self.types["workbook"]) + "/_rels/" + \
+ os.path.basename(self.types["workbook"]) + ".rels"]
+
+
+class Relationships:
+ def __init__(self):
+ self.relationships = {}
+
+ def parse(self, filehandle):
+ doc = minidom.parseString(filehandle.read())
+ if doc.namespaceURI:
+ relationships = doc.getElementsByTagNameNS(doc.namespaceURI, "Relationships")
+ else:
+ relationships = doc.getElementsByTagName("Relationships")
+ if not relationships:
+ return
+ if doc.namespaceURI:
+ relationshipNodes = relationships[0].getElementsByTagNameNS(doc.namespaceURI, "Relationship")
+ else:
+ relationshipNodes = relationships[0].getElementsByTagName("Relationship")
+ for rel in relationshipNodes:
+ attrs = rel._attrs
+ rId = attrs.get('Id')
+ if rId:
+ vtype = attrs.get('Type')
+ target = attrs.get('Target')
+ self.relationships[str(rId.value)] = {
+ "type": vtype and str(vtype.value) or None,
+ "target": target and str(target.value) or None
+ }
+
+
+class Styles:
+ def __init__(self):
+ self.numFmts = {}
+ self.cellXfs = []
+
+ def parse(self, filehandle):
+ styles = minidom.parseString(filehandle.read()).firstChild
+ # numFmts
+ if styles.namespaceURI:
+ numFmtsElement = styles.getElementsByTagNameNS(styles.namespaceURI, "numFmts")
+ else:
+ numFmtsElement = styles.getElementsByTagName("numFmts")
+ if len(numFmtsElement) == 1:
+ for numFmt in numFmtsElement[0].childNodes:
+ if numFmt.nodeType == minidom.Node.ELEMENT_NODE:
+ numFmtId = int(numFmt._attrs['numFmtId'].value)
+ formatCode = numFmt._attrs['formatCode'].value.lower().replace('\\', '')
+ self.numFmts[numFmtId] = formatCode
+
+ if styles.namespaceURI:
+ cellXfsElement = styles.getElementsByTagNameNS(styles.namespaceURI, "cellXfs")
+ else:
+ cellXfsElement = styles.getElementsByTagName("cellXfs")
+ if len(cellXfsElement) == 1:
+ for cellXfs in cellXfsElement[0].childNodes:
+ if cellXfs.nodeType != minidom.Node.ELEMENT_NODE or not (
+ cellXfs.nodeName == "xf" or cellXfs.nodeName.endswith(":xf")):
+ continue
+ if cellXfs._attrs and 'numFmtId' in cellXfs._attrs:
+ numFmtId = int(cellXfs._attrs['numFmtId'].value)
+ if self.chk_exists(numFmtId) == None:
+ numFmtId = int(cellXfs._attrs['applyNumberFormat'].value)
+ self.cellXfs.append(numFmtId)
+ else:
+ self.cellXfs.append(None)
+
+ # When Unknown Numformat ID assign applyNumberFormat
+ def chk_exists(self, numFmtId):
+ xfs_numfmt = numFmtId
+ format_str = None
+ if xfs_numfmt in self.numFmts:
+ format_str = self.numFmts[xfs_numfmt]
+ elif xfs_numfmt in STANDARD_FORMATS:
+ format_str = STANDARD_FORMATS[xfs_numfmt]
+ return format_str
+
+
+class SharedStrings:
+ def __init__(self):
+ self.parser = None
+ self.strings = []
+ self.si = False
+ self.t = False
+ self.rPh = False
+ self.value = ""
+
+ def parse(self, filehandle):
+ self.parser = xml.parsers.expat.ParserCreate()
+ self.parser.CharacterDataHandler = self.handleCharData
+ self.parser.StartElementHandler = self.handleStartElement
+ self.parser.EndElementHandler = self.handleEndElement
+ self.parser.ParseFile(filehandle)
+
+ def escape_strings(self):
+ for i in range(0, len(self.strings)):
+ self.strings[i] = self.strings[i].replace("\r", "\\r").replace("\n", "\\n").replace("\t", "\\t")
+
+ def replace_line_breaks(self):
+ for i in range(0, len(self.strings)):
+ self.strings[i] = self.strings[i].replace("\r", " ").replace("\n", " ").replace("\t", " ")
+
+ def handleCharData(self, data):
+ if self.t:
+ self.value += data
+
+ def handleStartElement(self, name, attrs):
+ # ignore namespace
+ i = name.find(":")
+ if i >= 0:
+ name = name[i + 1:]
+
+ if name == 'si':
+ self.si = True
+ self.value = ""
+ elif name == 't' and self.rPh:
+ self.t = False
+ elif name == 't' and self.si:
+ self.t = True
+ elif name == 'rPh':
+ self.rPh = True
+
+ def handleEndElement(self, name):
+ # ignore namespace
+ i = name.find(":")
+ if i >= 0:
+ name = name[i + 1:]
+
+ if name == 'si':
+ self.si = False
+ self.strings.append(self.value)
+ elif name == 't':
+ self.t = False
+ elif name == 'rPh':
+ self.rPh = False
+
+
+class Sheet:
+ def __init__(self, workbook, sharedString, styles, filehandle):
+ self.py3 = sys.version_info[0] == 3
+ self.parser = None
+ self.writer = None
+ self.sharedString = None
+ self.styles = None
+ self.relationships = None
+ self.columns_count = -1
+
+ self.in_sheet = False
+ self.in_row = False
+ self.in_cell = False
+ self.in_cell_value = False
+
+ self.columns = {}
+ self.lastRowNum = 0
+ self.rowNum = None
+ self.colType = None
+ self.cellId = None
+ self.s_attr = None
+ self.data = None
+ self.max_columns = -1
+
+ self.dateformat = None
+ self.timeformat = "%H:%M" # default time format
+ self.floatformat = None
+ self.skip_empty_lines = False
+ self.skip_trailing_columns = False
+
+ self.filedata = None
+ self.filehandle = filehandle
+ self.workbook = workbook
+ self.sharedStrings = sharedString.strings
+ self.styles = styles
+
+ self.hyperlinks = {}
+ self.mergeCells = {}
+ self.ignore_formats = []
+
+ self.colIndex = 0
+ self.colNum = ""
+
+ def close(self):
+ # Make sure Worksheet is closed, parsers lib does not have a close() function, so simply delete it
+ self.parser = None
+
+ def set_dateformat(self, dateformat):
+ self.dateformat = dateformat
+
+ def set_timeformat(self, timeformat):
+ if timeformat:
+ self.timeformat = timeformat
+
+ def set_floatformat(self, floatformat):
+ self.floatformat = floatformat
+
+ def set_skip_empty_lines(self, skip):
+ self.skip_empty_lines = skip
+
+ def set_skip_trailing_columns(self, skip):
+ self.skip_trailing_columns = skip
+
+ def set_ignore_formats(self, ignore_formats):
+ self.ignore_formats = ignore_formats
+
+ def set_merge_cells(self, mergecells):
+ if not mergecells:
+ return
+ if not self.filedata:
+ self.filedata = self.filehandle.read()
+ data = str(self.filedata) # python3: convert byte buffer to string
+
+ # find worksheet tag, we need namespaces from it
+ start = data.find("<worksheet")
+ if start < 0:
+ return
+ end = data.find(">", start)
+ worksheet = data[start: end + 1]
+
+ # find hyperlinks part
+ start = data.find("<mergeCells")
+ if start < 0:
+ # hyperlinks not found
+ return
+ end = data.find("</mergeCells>")
+ data = data[start: end + 13]
+
+ # parse hyperlinks
+ doc = minidom.parseString(worksheet + data + "</worksheet>").firstChild
+
+ if doc.namespaceURI:
+ mergeCells = doc.getElementsByTagNameNS(doc.namespaceURI, "mergeCell")
+ else:
+ mergeCells = doc.getElementsByTagName("mergeCell")
+ for mergeCell in mergeCells:
+ attrs = mergeCell._attrs
+ if 'ref' in attrs.keys():
+ rangeStr = attrs['ref'].value
+ rng = rangeStr.split(":")
+ if len(rng) > 1:
+ for cell in self._range(rangeStr):
+ self.mergeCells[cell] = {}
+ self.mergeCells[cell]['copyFrom'] = rng[0]
+
+ def set_scifloat(self, scifloat):
+ self.scifloat = scifloat
+
+ def set_include_hyperlinks(self, hyperlinks):
+ if not hyperlinks or not self.relationships or not self.relationships.relationships:
+ return
+ # we must read file first to get hyperlinks, but we don't wont to parse whole file
+ if not self.filedata:
+ self.filedata = self.filehandle.read()
+ data = str(self.filedata) # python3: convert byte buffer to string
+
+ # find worksheet tag, we need namespaces from it
+ start = data.find("<worksheet")
+ if start < 0:
+ return
+ end = data.find(">", start)
+ worksheet = data[start: end + 1]
+
+ # find hyperlinks part
+ start = data.find("<hyperlinks>")
+ if start < 0:
+ # hyperlinks not found
+ return
+ end = data.find("</hyperlinks>")
+ data = data[start: end + 13]
+
+ # parse hyperlinks
+ doc = minidom.parseString(worksheet + data + "</worksheet>").firstChild
+ if doc.namespaceURI:
+ hiperlinkNodes = doc.getElementsByTagNameNS(doc.namespaceURI, "hyperlink")
+ else:
+ hiperlinkNodes = doc.getElementsByTagName("hyperlink")
+ for hlink in hiperlinkNodes:
+ attrs = hlink._attrs
+ ref = rId = None
+ for k in attrs.keys():
+ if k == "ref":
+ ref = str(attrs[k].value)
+ if k.endswith(":id"):
+ rId = str(attrs[k].value)
+ if not ref or not rId:
+ continue
+ rel = self.relationships.relationships.get(rId)
+ if not rel:
+ continue
+ target = rel.get('target')
+ for cell in self._range(ref):
+ self.hyperlinks[cell] = target
+
+ def to_csv(self, writer):
+ self.writer = writer
+ self.parser = xml.parsers.expat.ParserCreate()
+ self.parser.buffer_text = True
+ self.parser.CharacterDataHandler = self.handleCharData
+ self.parser.StartElementHandler = self.handleStartElement
+ self.parser.EndElementHandler = self.handleEndElement
+ if self.filedata:
+ self.parser.Parse(self.filedata)
+ else:
+ self.parser.ParseFile(self.filehandle)
+
+ def handleCharData(self, data):
+ if self.in_cell_value:
+ format_type = None
+ format_str = "general"
+ self.collected_string += data
+ self.data = self.collected_string
+ if self.colType == "s": # shared string
+ format_type = "string"
+ self.data = self.sharedStrings[int(self.data)]
+ elif self.colType == "b": # boolean
+ format_type = "boolean"
+ self.data = (int(data) == 1 and "TRUE") or (int(data) == 0 and "FALSE") or data
+ elif self.colType == "str" or self.colType == "inlineStr":
+ format_type = "string"
+ self.data = data
+ elif self.s_attr:
+ s = int(self.s_attr)
+
+ # get cell format
+ xfs_numfmt = None
+ if s < len(self.styles.cellXfs):
+ xfs_numfmt = self.styles.cellXfs[s]
+ if xfs_numfmt in self.styles.numFmts:
+ format_str = self.styles.numFmts[xfs_numfmt]
+ elif xfs_numfmt in STANDARD_FORMATS:
+ format_str = STANDARD_FORMATS[xfs_numfmt]
+
+ # get format type
+ if not format_str:
+ eprint("unknown format %s at %d" % (format_str, xfs_numfmt))
+ return
+
+ if format_str in FORMATS:
+ format_type = FORMATS[format_str]
+ elif re.match("^\d+(\.\d+)?$", self.data) and re.match(".*[hsmdyY]", format_str) and not re.match(
+ '.*\[.*[dmhys].*\]', format_str):
+ # it must be date format
+ if float(self.data) < 1:
+ format_type = "time"
+ else:
+ format_type = "date"
+ elif re.match("^-?\d+(.\d+)?$", self.data) or (
+ self.scifloat and re.match("^-?\d+(.\d+)?([eE]-?\d+)?$", self.data)):
+ format_type = "float"
+ if format_type == 'date' and self.dateformat == 'float':
+ format_type = "float"
+ elif self.colType == "n":
+ format_type = "float"
+
+ if format_type and not format_type in self.ignore_formats:
+ try:
+ if format_type == 'date': # date/time
+ if self.workbook.date1904:
+ date = datetime.datetime(1904, 1, 1) + datetime.timedelta(float(self.data))
+ else:
+ date = datetime.datetime(1899, 12, 30) + datetime.timedelta(float(self.data))
+ if self.dateformat:
+ # str(dateformat) - python2.5 bug, see: http://bugs.python.org/issue2782
+ self.data = date.strftime(str(self.dateformat))
+ else:
+ # ignore ";@", don't know what does it mean right now
+ # ignore "[$-409], [$-f409], [$-16001]" and similar format codes
+ dateformat = re.sub(r"\[\$\-[A-z0-9]*\]", "", format_str, 1) \
+ .replace(";@", "").replace("yyyy", "%Y").replace("yy", "%y") \
+ .replace("hh:mm", "%H:%M").replace("h", "%I").replace("%H%H", "%H") \
+ .replace("ss", "%S").replace("dddd", "d").replace("dd", "d").replace("d", "%d") \
+ .replace("am/pm", "%p").replace("mmmm", "%B").replace("mmm", "%b") \
+ .replace(":mm", ":%M").replace("m", "%m").replace("%m%m", "%m")
+ self.data = date.strftime(str(dateformat)).strip()
+ elif format_type == 'time': # time
+ t = int(round((float(self.data) % 1) * 24 * 60 * 60, 6)) # it should be in seconds
+ d = datetime.time(int((t // 3600) % 24), int((t // 60) % 60), int(t % 60))
+ self.data = d.strftime(self.timeformat)
+ elif format_type == 'float' and ('E' in self.data or 'e' in self.data):
+ self.data = str(self.floatformat or '%f') % float(self.data)
+ # if cell is general, be aggressive about stripping any trailing 0s, decimal points, etc.
+ elif format_type == 'float' and format_str == 'general':
+ self.data = ("%f" % (float(self.data))).rstrip('0').rstrip('.')
+ elif format_type == 'float' and format_str[0:3] == '0.0':
+ if self.floatformat:
+ self.data = str(self.floatformat) % float(self.data)
+ else:
+ L = len(format_str.split(".")[1])
+ if '%' in format_str:
+ L += 1
+ self.data = ("%." + str(L) + "f") % float(self.data)
+ elif format_type == 'float':
+ # unsupported float formatting
+ self.data = ("%f" % (float(self.data))).rstrip('0').rstrip('.')
+
+ except (ValueError, OverflowError): # this catch must be removed, it's hiding potential problems
+ eprint("Error: potential invalid date format.")
+ # invalid date format
+ pass
+
+ def handleStartElement(self, name, attrs):
+ has_namespace = name.find(":") > 0
+ if self.in_row and (name == 'c' or (has_namespace and name.endswith(':c'))):
+ self.colType = attrs.get("t")
+ self.s_attr = attrs.get("s")
+ self.cellId = attrs.get("r")
+ if self.cellId:
+ self.colNum = self.cellId[:len(self.cellId) - len(self.rowNum)]
+ self.colIndex = 0
+ else:
+ self.colIndex += 1
+ self.data = ""
+ self.in_cell = True
+ elif self.in_cell and (
+ (name == 'v' or name == 'is') or (has_namespace and (name.endswith(':v') or name.endswith(':is')))):
+ self.in_cell_value = True
+ self.collected_string = ""
+ elif self.in_sheet and (name == 'row' or (has_namespace and name.endswith(':row'))) and ('r' in attrs):
+ self.rowNum = attrs['r']
+ self.in_row = True
+ self.colIndex = 0
+ self.colNum = ""
+ self.columns = {}
+ self.spans = None
+ if 'spans' in attrs:
+ self.spans = [int(i) for i in attrs['spans'].split(" ")[-1].split(":")]
+ elif name == 't':
+ # reset collected string
+ self.collected_string = ""
+
+ elif name == 'sheetData' or (has_namespace and name.endswith(':sheetData')):
+ self.in_sheet = True
+ elif name == 'dimension':
+ rng = attrs.get("ref").split(":")
+ if len(rng) > 1:
+ start = re.match("^([A-Z]+)(\d+)$", rng[0])
+ if (start):
+ end = re.match("^([A-Z]+)(\d+)$", rng[1])
+ startCol = start.group(1)
+ endCol = end.group(1)
+ self.columns_count = 0
+ for cell in self._range(startCol + "1:" + endCol + "1"):
+ self.columns_count += 1
+
+ def handleEndElement(self, name):
+ has_namespace = name.find(":") > 0
+ if self.in_cell and ((name == 'v' or name == 'is' or name == 't') or (
+ has_namespace and (name.endswith(':v') or name.endswith(':is')))):
+ self.in_cell_value = False
+ elif self.in_cell and (name == 'c' or (has_namespace and name.endswith(':c'))):
+ t = 0
+ for i in self.colNum: t = t * 26 + ord(i) - 64
+ d = self.data
+ if self.hyperlinks:
+ hyperlink = self.hyperlinks.get(self.cellId)
+ if hyperlink:
+ d = "<a href='" + hyperlink + "'>" + d + "</a>"
+ if self.colNum + self.rowNum in self.mergeCells.keys():
+ if 'copyFrom' in self.mergeCells[self.colNum + self.rowNum].keys() and \
+ self.mergeCells[self.colNum + self.rowNum]['copyFrom'] == self.colNum + self.rowNum:
+ self.mergeCells[self.colNum + self.rowNum]['value'] = d
+ else:
+ d = self.mergeCells[self.mergeCells[self.colNum + self.rowNum]['copyFrom']]['value']
+
+ self.columns[t - 1 + self.colIndex] = d
+
+ if self.in_row and (name == 'row' or (has_namespace and name.endswith(':row'))):
+ if len(self.columns.keys()) > 0:
+ if min(self.columns.keys()) < 0: # Weird
+ d = []
+ keys = self.columns.keys()
+ keys.sort()
+ for k in keys:
+ val = self.columns[k]
+ if not self.py3:
+ val = val.encode("utf-8")
+ d.append(val)
+ else:
+ d = [""] * (max(self.columns.keys()) + 1)
+ for k in self.columns.keys():
+ val = self.columns[k]
+ if not self.py3:
+ val = val.encode("utf-8")
+ d[k] = val
+ if self.spans:
+ l = self.spans[1]
+ if len(d) < l:
+ d += (l - len(d)) * ['']
+
+ # write empty lines
+ if not self.skip_empty_lines:
+ for i in range(self.lastRowNum, int(self.rowNum) - 1):
+ self.writer.writerow([])
+ self.lastRowNum = int(self.rowNum)
+
+ # write line to csv
+ if not self.skip_empty_lines or d.count('') != len(d):
+ while len(d) < self.columns_count:
+ d.append("")
+
+ if self.skip_trailing_columns:
+ if self.max_columns < 0:
+ self.max_columns = len(d)
+ while len(d) > 0 and d[-1] == "":
+ d = d[0:-1]
+ self.max_columns = self.max_columns - 1
+ elif self.max_columns > 0:
+ d = d[0:self.max_columns]
+ self.writer.writerow(d)
+
+ self.in_row = False
+ elif self.in_sheet and (name == 'sheetData' or (has_namespace and name.endswith(':sheetData'))):
+ self.in_sheet = False
+
+ # rangeStr: "A3:C12" or "D5"
+ # example: for cell in _range("A1:Z12"): print cell
+ def _range(self, rangeStr):
+ rng = rangeStr.split(":")
+ if len(rng) == 1:
+ yield rangeStr
+ else:
+ start = re.match("^([A-Z]+)(\d+)$", rng[0])
+ end = re.match("^([A-Z]+)(\d+)$", rng[1])
+ if not start or not end:
+ return
+ startCol = start.group(1)
+ startRow = int(start.group(2))
+ endCol = end.group(1)
+ endRow = int(end.group(2))
+ col = startCol
+ while True:
+ for row in range(startRow, endRow + 1):
+ yield col + str(row)
+ if col == endCol:
+ break
+ t = 0
+ for i in col: t = t * 26 + ord(i) - 64
+ col = ""
+ while t >= 0:
+ col = chr(t % 26 + 65) + col
+ t = t // 26 - 1
+
+
+def convert_recursive(path, sheetid, outfile, kwargs):
+ for name in os.listdir(path):
+ fullpath = os.path.join(path, name)
+ if os.path.isdir(fullpath):
+ convert_recursive(fullpath, sheetid, outfile, kwargs)
+ else:
+ outfilepath = outfile
+ if len(outfilepath) == 0 and fullpath.lower().endswith(".xlsx"):
+ outfilepath = fullpath[:-4] + 'csv'
+
+ print("Converting %s to %s" % (fullpath, outfilepath))
+ try:
+ Xlsx2csv(fullpath, **kwargs).convert(outfilepath, sheetid)
+ except zipfile.BadZipfile:
+ print("File %s is not a zip file" % fullpath)
+
+
+if __name__ == "__main__":
+ try:
+ signal.signal(signal.SIGPIPE, signal.SIG_DFL)
+ signal.signal(signal.SIGINT, signal.SIG_DFL)
+ except AttributeError:
+ pass
+
+ if "ArgumentParser" in globals():
+ parser = ArgumentParser(description="xlsx to csv converter")
+ parser.add_argument('infile', metavar='xlsxfile', help="xlsx file path")
+ parser.add_argument('outfile', metavar='outfile', nargs='?', help="output csv file path")
+ parser.add_argument('-v', '--version', action='version', version=__version__)
+ nargs_plus = "+"
+ argparser = True
+ else:
+ parser = OptionParser(usage="%prog [options] infile [outfile]", version=__version__)
+ parser.add_argument = parser.add_option
+ nargs_plus = 1
+ argparser = False
+
+ if sys.version_info[0] == 2 and sys.version_info[1] < 5:
+ inttype = "int"
+ else:
+ inttype = int
+ parser.add_argument("-a", "--all", dest="all", default=False, action="store_true",
+ help="export all sheets")
+ parser.add_argument("-c", "--outputencoding", dest="outputencoding", default="utf-8", action="store",
+ help="encoding of output csv ** Python 3 only ** (default: utf-8)")
+ parser.add_argument("-d", "--delimiter", dest="delimiter", default=",",
+ help="delimiter - columns delimiter in csv, 'tab' or 'x09' for a tab (default: comma ',')")
+ parser.add_argument("--hyperlinks", "--hyperlinks", dest="hyperlinks", action="store_true", default=False,
+ help="include hyperlinks")
+ parser.add_argument("-e", "--escape", dest='escape_strings', default=False, action="store_true",
+ help="Escape \\r\\n\\t characters")
+ parser.add_argument("--no-line-breaks", "--no-line-breaks", dest='no_line_breaks', default=False, action="store_true",
+ help="Replace \\r\\n\\t with space")
+ parser.add_argument("-E", "--exclude_sheet_pattern", nargs=nargs_plus, dest="exclude_sheet_pattern", default="",
+ help="exclude sheets named matching given pattern, only effects when -a option is enabled.")
+ parser.add_argument("-f", "--dateformat", dest="dateformat",
+ help="override date/time format (ex. %%Y/%%m/%%d)")
+ parser.add_argument("-t", "--timeformat", dest="timeformat",
+ help="override time format (ex. %%H/%%M/%%S)")
+ parser.add_argument("--floatformat", dest="floatformat",
+ help="override float format (ex. %%.15f)")
+ parser.add_argument("--sci-float", dest="scifloat", default=False, action="store_true",
+ help="force scientific notation to float")
+ parser.add_argument("-I", "--include_sheet_pattern", nargs=nargs_plus, dest="include_sheet_pattern", default="^.*$",
+ help="only include sheets named matching given pattern, only effects when -a option is enabled.")
+ parser.add_argument("--exclude_hidden_sheets", default=False, action="store_true",
+ help="Exclude hidden sheets from the output, only effects when -a option is enabled.")
+ parser.add_argument("--ignore-formats", nargs=nargs_plus, type=str, dest="ignore_formats", default=[''],
+ help="Ignores format for specific data types.")
+ parser.add_argument("-l", "--lineterminator", dest="lineterminator", default="\n",
+ help="line terminator - lines terminator in csv, '\\n' '\\r\\n' or '\\r' (default: \\n)")
+ parser.add_argument("-m", "--merge-cells", dest="merge_cells", default=False, action="store_true",
+ help="merge cells")
+ parser.add_argument("-n", "--sheetname", dest="sheetname", default=None,
+ help="sheet name to convert")
+ parser.add_argument("-i", "--ignoreempty", dest="skip_empty_lines", default=False, action="store_true",
+ help="skip empty lines")
+ parser.add_argument("--skipemptycolumns", dest="skip_trailing_columns", default=False, action="store_true",
+ help="skip trailing empty columns")
+ parser.add_argument("-p", "--sheetdelimiter", dest="sheetdelimiter", default="--------",
+ help="sheet delimiter used to separate sheets, pass '' if you do not need delimiter, or 'x07' "
+ "or '\\f' for form feed (default: '--------')")
+ parser.add_argument("-q", "--quoting", dest="quoting", default="minimal",
+ help="quoting - fields quoting in csv, 'none' 'minimal' 'nonnumeric' or 'all' (default: minimal)")
+ parser.add_argument("-s", "--sheet", dest="sheetid", default=1, type=inttype,
+ help="sheet number to convert")
+
+ if argparser:
+ options = parser.parse_args()
+ else:
+ (options, args) = parser.parse_args()
+ if len(args) < 1:
+ parser.print_usage()
+ sys.stderr.write("error: too few arguments" + os.linesep)
+ sys.exit(1)
+ options.infile = args[0]
+ options.outfile = len(args) > 1 and args[1] or None
+
+ if len(options.delimiter) == 1:
+ pass
+ elif options.delimiter == 'tab' or options.delimiter == '\\t':
+ options.delimiter = '\t'
+ elif options.delimiter == 'comma':
+ options.delimiter = ','
+ elif options.delimiter[0] == 'x':
+ options.delimiter = chr(int(options.delimiter[1:]))
+ else:
+ sys.stderr.write("error: invalid delimiter\n")
+ sys.exit(1)
+
+ if options.quoting == 'none':
+ options.quoting = csv.QUOTE_NONE
+ elif options.quoting == 'minimal':
+ options.quoting = csv.QUOTE_MINIMAL
+ elif options.quoting == 'nonnumeric':
+ options.quoting = csv.QUOTE_NONNUMERIC
+ elif options.quoting == 'all':
+ options.quoting = csv.QUOTE_ALL
+ else:
+ sys.stderr.write("error: invalid quoting\n")
+ sys.exit(1)
+
+ if options.lineterminator == '\n':
+ pass
+ elif options.lineterminator == '\\n':
+ options.lineterminator = '\n'
+ elif options.lineterminator == '\\r':
+ options.lineterminator = '\r'
+ elif options.lineterminator == '\\r\\n':
+ options.lineterminator = '\r\n'
+ else:
+ sys.stderr.write("error: invalid line terminator\n")
+ sys.exit(1)
+
+ if options.sheetdelimiter == '--------':
+ pass
+ elif options.sheetdelimiter == '':
+ pass
+ elif options.sheetdelimiter == '\\f':
+ options.sheetdelimiter = '\f'
+ elif options.sheetdelimiter[0] == 'x':
+ options.sheetdelimiter = chr(int(options.sheetdelimiter[1:]))
+ else:
+ sys.stderr.write("error: invalid sheet delimiter\n")
+ sys.exit(1)
+
+ kwargs = {
+ 'delimiter': options.delimiter,
+ 'quoting': options.quoting,
+ 'sheetdelimiter': options.sheetdelimiter,
+ 'dateformat': options.dateformat,
+ 'timeformat': options.timeformat,
+ 'floatformat': options.floatformat,
+ 'scifloat': options.scifloat,
+ 'skip_empty_lines': options.skip_empty_lines,
+ 'skip_trailing_columns': options.skip_trailing_columns,
+ 'escape_strings': options.escape_strings,
+ 'no_line_breaks': options.no_line_breaks,
+ 'hyperlinks': options.hyperlinks,
+ 'include_sheet_pattern': options.include_sheet_pattern,
+ 'exclude_sheet_pattern': options.exclude_sheet_pattern,
+ 'exclude_hidden_sheets': options.exclude_hidden_sheets,
+ 'merge_cells': options.merge_cells,
+ 'outputencoding': options.outputencoding,
+ 'lineterminator': options.lineterminator,
+ 'ignore_formats': options.ignore_formats
+ }
+ sheetid = options.sheetid
+ if options.all:
+ sheetid = 0
+
+ outfile = options.outfile or sys.stdout
+ try:
+ if os.path.isdir(options.infile):
+ convert_recursive(options.infile, sheetid, outfile, kwargs)
+ else:
+ xlsx2csv = Xlsx2csv(options.infile, **kwargs)
+ if options.sheetname:
+ sheetid = xlsx2csv.getSheetIdByName(options.sheetname)
+ if not sheetid:
+ raise XlsxException("Sheet '%s' not found" % options.sheetname)
+ xlsx2csv.convert(outfile, sheetid)
+ except XlsxException:
+ _, e, _ = sys.exc_info()
+ sys.stderr.write(str(e) + "\n")
+ sys.exit(1)