diff --git a/py-et/.vscode/settings.json b/py-et/.vscode/settings.json
new file mode 100644
index 0000000..728e7f9
--- /dev/null
+++ b/py-et/.vscode/settings.json
@@ -0,0 +1,8 @@
+{
+ "python.testing.pytestArgs": [
+ "tests"
+ ],
+ "python.testing.unittestEnabled": false,
+ "python.testing.pytestEnabled": true,
+ "python.testing.autoTestDiscoverOnSaveEnabled": true,
+}
diff --git a/py-et/README b/py-et/README
new file mode 100644
index 0000000..8e9c8a9
--- /dev/null
+++ b/py-et/README
@@ -0,0 +1 @@
+# WPS 表格自动化(插件版)
diff --git a/py-et/invoke-et.py b/py-et/invoke-et.py
deleted file mode 100644
index 4952359..0000000
--- a/py-et/invoke-et.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from ET import ET
-
-if __name__ == "__main__":
-
- et1 = ET()
- sheets_name = et1.get_sheets()
- print(f"et1: {sheets_name}")
-
- et2 = ET()
- et2.workbook_name = "工作簿2"
- sheets_name = et2.get_sheets()
- print(f"et2:{sheets_name}")
diff --git a/py-et/src/ET.py b/py-et/src/ET.py
new file mode 100644
index 0000000..3079539
--- /dev/null
+++ b/py-et/src/ET.py
@@ -0,0 +1,46 @@
+import os
+
+
+from .client import Client
+
+
+class ET(Client):
+ def __init__(self):
+ super().__init__()
+ self.workbook_name = "" # 用于判断当前打开的表格
+ # self.check_addon_install() # 调试模式暂时不判断
+ self.inject_flag = self._inject_js_code()
+
+ def open(self, file_path):
+ """open et"""
+ pass
+
+ def get_sheets(self) -> list:
+ """get all sheets"""
+ func = "getSheets"
+ sheets_name = self._wrap_invoke_func(func)
+
+ return sheets_name
+
+ def _wrap_invoke_func(self, func, args={}):
+ """将工作簿的名称自动带进去"""
+ if self.workbook_name:
+ args["workbook_name"] = self.workbook_name
+ res = self.invoke_func(func, args)
+ if res.get("code") != 0:
+ raise Exception(f"调用函数 {func} 失败, 错误信息: {res.get('msg')}")
+ return res.get("data")
+
+ def _inject_js_code(self) -> bool:
+ """注入js脚本"""
+ current_path = os.path.abspath(__file__)
+ js_code_path = os.path.join(os.path.dirname(current_path), "et.js")
+ if not os.path.exists(js_code_path):
+ raise Exception(f"js code not found: {js_code_path}")
+ # 读取js代码
+ with open(js_code_path, encoding="utf8") as f:
+ code = f.read()
+ func = "inject"
+ args = {"code": code}
+ res = self.invoke_func(func, args)
+ return res.get("code") == 0
diff --git a/py-et/src/__pycache__/ET.cpython-313.pyc b/py-et/src/__pycache__/ET.cpython-313.pyc
new file mode 100644
index 0000000..e9029fc
Binary files /dev/null and b/py-et/src/__pycache__/ET.cpython-313.pyc differ
diff --git a/py-et/src/__pycache__/client.cpython-313.pyc b/py-et/src/__pycache__/client.cpython-313.pyc
new file mode 100644
index 0000000..eca9574
Binary files /dev/null and b/py-et/src/__pycache__/client.cpython-313.pyc differ
diff --git a/py-et/ET.py b/py-et/src/client.py
similarity index 75%
rename from py-et/ET.py
rename to py-et/src/client.py
index 38304f1..eb6b0ae 100644
--- a/py-et/ET.py
+++ b/py-et/src/client.py
@@ -1,22 +1,124 @@
-import base64
+import os
+import sys
import uuid
import json
-import http.client
-import webbrowser
-import os
-import platform
-import xml.dom.minidom as minidom
-import sys
-import shutil
-import zipfile
import time
+import shutil
+import base64
+import zipfile
+import platform
+import webbrowser
+import logging
+
+logger = logging.getLogger(__file__)
+
+import http.client
+import xml.dom.minidom as minidom
+
+from xml.etree import ElementTree
class Client:
def __init__(self):
- self.addon_name = "XbotWps"
+ self.addon_name = "xbot-et"
self.addon_type = "et"
self.addon_version = "1.0.0"
+ self.url = "http://127.0.0.1:3889/"
+ self.start_http_server()
+
+ def start_http_server(self) -> bool:
+ for _ in range(10):
+ if self._get_version():
+ print("Wps server started")
+ return True
+ self._start_http_server()
+ time.sleep(1)
+ raise ("Wps server not started")
+
+ def get_addon_list(self) -> list:
+ """获取插件列表"""
+ path = "/publishlist"
+ conn = http.client.HTTPConnection("127.0.0.1", 58890)
+ conn.request("GET", path, body="{}", headers={})
+ response = conn.getresponse()
+ data = response.read()
+ conn.close()
+ json_data = json.loads(data.decode("utf-8"))
+ return json_data
+
+ def verify_addon_status(self) -> bool:
+ """验证插件状态"""
+ url = self.url + "ribbon.xml"
+ data = {"method": "get", "url": url, "data": ""}
+ payload = base64.b64encode(json.dumps(data).encode("utf-8")).decode()
+ print(f"payload: {payload}")
+ conn = http.client.HTTPConnection("127.0.0.1", 58890)
+ conn.request("POST", "/redirect/runParams", payload, {})
+ response = conn.getresponse()
+ data = response.read()
+ conn.close()
+ try:
+ root = ElementTree.fromstring(data)
+ # 检查根标签名是否包含 customUI(包含命名空间)
+ return root.tag.endswith("customUI")
+ except ElementTree.ParseError:
+ return False
+
+ def enable_addon(self) -> bool:
+ """开启插件"""
+ data = {
+ "cmd": "enable",
+ "name": self.addon_name,
+ "url": self.url,
+ "addonType": "et",
+ "online": "true",
+ "time": int(time.time()),
+ }
+ path = "/deployaddons/runParams"
+ payload = base64.b64encode(json.dumps(data).encode("utf-8")).decode()
+ conn = http.client.HTTPConnection("127.0.0.1", 58890)
+ conn.request("POST", path, payload, {})
+ response = conn.getresponse()
+ data = response.read()
+ conn.close()
+ return data.decode() == "OK"
+
+ def disable_addon(self) -> bool:
+ """禁用插件"""
+ data = {
+ "cmd": "disable",
+ "name": self.addon_name,
+ "url": self.url,
+ "addonType": "et",
+ "online": "true",
+ "time": int(time.time()),
+ }
+ path = "/deployaddons/runParams"
+ payload = base64.b64encode(json.dumps(data).encode("utf-8")).decode()
+ conn = http.client.HTTPConnection("127.0.0.1", 58890)
+ conn.request("POST", path, payload, {})
+
+ response = conn.getresponse()
+ data = response.read()
+ conn.close()
+ return data.decode() == "OK"
+
+ def is_client_running(self) -> bool:
+ """检查客户端是否运行"""
+ # --data-raw '{"app":"et"}'
+ path = "/isRunning"
+ data = {
+ "app": self.addon_type,
+ }
+ payload = json.dumps(data)
+
+ conn = http.client.HTTPConnection("127.0.0.1", 58890)
+ conn.request("POST", path, payload, {})
+
+ response = conn.getresponse()
+ data = response.read()
+ conn.close()
+ return data.decode() == "Client is running."
def invoke_func(self, func, args={}):
@@ -35,7 +137,7 @@ class Client:
xhr.send(JSON.stringify({{id: '{req_uui}', response: res}}));void(0"
data = {
- "name": "xbot-et",
+ "name": self.addon_name,
"function": "var res = InvokeFromXbot",
"info": info,
"showToFront": True,
@@ -95,26 +197,6 @@ class Client:
print("Installation completed successfully.")
- def check_addon_install(self):
- """通过文件夹是否存在判断插件是否安装"""
- install_dir = self._get_install_directory()
- addon_name_with_version = f"{self.addon_name}_{self.addon_version}"
- file_path = os.path.join(install_dir, addon_name_with_version)
-
- if os.path.exists(file_path):
- return True
- else:
- return False
-
- def check_http_status(self):
- for _ in range(10):
- if self._get_version():
- print("Wps server started")
- return
- self._start_http_server()
- time.sleep(1)
- raise ("Wps server not started")
-
def _start_http_server(self):
"""start debug"""
webbrowser.open("ksowpscloudsvr://start=RelayHttpServer")
@@ -289,37 +371,3 @@ class Client:
except Exception as e:
print(f"Error extracting archive: {e}")
return False
-
-
-class ET(Client):
- def __init__(self):
- super().__init__()
- self.workbook_name = "" # 用于判断当前打开的表格
- self.check_http_status()
- # self.check_addon_install() # 调试模式暂时不判断
- self._inject_js_code()
-
- def open(self, file_path):
- """open et"""
- pass
-
- def get_sheets(self) -> list:
- """get all sheets"""
- func = "getSheets"
- sheets_name = self._wrap_invoke_func(func)
- return sheets_name
-
- def _wrap_invoke_func(self, func, args={}):
- """将工作簿的名称自动带进去"""
- if self.workbook_name:
- args["workbook_name"] = self.workbook_name
- return self.invoke_func(func, args)
-
- def _inject_js_code(self):
- path = r"C:\Users\32075\OneDrive\文档\Code\RPA_Script\wps-js-extension\XbotWps\py-et\et.js"
- with open(path, encoding="utf8") as f:
- code = f.read()
- func = "inject"
- args = {"code": code}
- res = self.invoke_func(func, args)
- return res
diff --git a/py-et/et.js b/py-et/src/et.js
similarity index 100%
rename from py-et/et.js
rename to py-et/src/et.js
diff --git a/py-et/tests/__pycache__/test_client.cpython-313-pytest-8.3.5.pyc b/py-et/tests/__pycache__/test_client.cpython-313-pytest-8.3.5.pyc
new file mode 100644
index 0000000..f0f505c
Binary files /dev/null and b/py-et/tests/__pycache__/test_client.cpython-313-pytest-8.3.5.pyc differ
diff --git a/py-et/tests/__pycache__/test_et.cpython-313-pytest-8.3.5.pyc b/py-et/tests/__pycache__/test_et.cpython-313-pytest-8.3.5.pyc
new file mode 100644
index 0000000..b6dce82
Binary files /dev/null and b/py-et/tests/__pycache__/test_et.cpython-313-pytest-8.3.5.pyc differ
diff --git a/py-et/tests/__pycache__/test_xx.cpython-313-pytest-8.3.5.pyc b/py-et/tests/__pycache__/test_xx.cpython-313-pytest-8.3.5.pyc
new file mode 100644
index 0000000..f746425
Binary files /dev/null and b/py-et/tests/__pycache__/test_xx.cpython-313-pytest-8.3.5.pyc differ
diff --git a/py-et/tests/test_client.py b/py-et/tests/test_client.py
new file mode 100644
index 0000000..4352449
--- /dev/null
+++ b/py-et/tests/test_client.py
@@ -0,0 +1,70 @@
+# test src/et.py client
+
+from src.ET import Client
+import pytest
+
+
+@pytest.fixture(scope="module")
+def test_create_client():
+ """创建测试用的client"""
+ client = Client()
+ yield client
+
+
+def test_client_get_addon_list(test_create_client):
+ """测试获取插件列表"""
+ client = test_create_client
+ print("Getting addon list...")
+ addon_list = client.get_addon_list()
+ print(f"Retrieved {len(addon_list)} addons")
+ assert isinstance(addon_list, list)
+ assert len(addon_list) > 0
+ assert all(isinstance(addon, dict) for addon in addon_list)
+ # 遍历所有插件, 并输出
+ for i, addon in enumerate(addon_list):
+ print(f"Addon {i}: {addon}")
+ print("First addon in list:", addon_list[0] if addon_list else "None")
+ print("Addon list test passed!")
+
+
+def test_client_verify_addon_status(test_create_client):
+ """测试插件状态"""
+ client = test_create_client
+ print("Verifying addon status...")
+ status = client.verify_addon_status()
+ print(f"Addon status: {status}")
+ assert status is True, "Addon status is not verified successfully"
+ print("Addon status test passed!")
+
+
+def test_client_enable_addon(test_create_client):
+ """测试启用插件"""
+ client = test_create_client
+ print("Enabling addon...")
+ status = client.enable_addon()
+ print(f"Addon enabled: {status}")
+ assert status is True, "Failed to enable addon"
+ print("Enable addon test passed!")
+
+
+def test_client_disable_addon(test_create_client):
+ """测试禁用插件"""
+ client = test_create_client
+ print("Disabling addon...")
+ status = client.disable_addon()
+ print(f"Addon disabled: {status}")
+ assert status is True, "Failed to disable addon"
+ print("Disable addon test passed!")
+
+def test_client_is_client_running(test_create_client):
+ """测试客户端是否运行"""
+ client = test_create_client
+ print("Checking if client is running...")
+ is_running = client.is_client_running()
+ print(f"Client is running: {is_running}")
+ assert isinstance(is_running, bool), "Client running status is not a boolean"
+ print("Client running status test passed!")
+
+if __name__ == "__main__":
+ # Add direct test execution for better debugging
+ pass
diff --git a/py-et/tests/test_et.py b/py-et/tests/test_et.py
new file mode 100644
index 0000000..c959e55
--- /dev/null
+++ b/py-et/tests/test_et.py
@@ -0,0 +1,39 @@
+import pytest
+
+from src.ET import ET
+
+
+@pytest.fixture(scope="module")
+def test_create_client():
+ """创建测试用的client"""
+ client = ET()
+ yield client
+
+
+def test_et_check_inject():
+ """测试注入"""
+ et = ET()
+ print("Injecting...")
+ assert et.inject_flag is True, "Injection failed"
+ print("Inject test passed!")
+
+
+def test_et_get_sheets():
+ """测试获取工作表"""
+ et = ET()
+ print("Getting sheets...")
+ sheets_name = et.get_sheets()
+ print(f"Retrieved {len(sheets_name)} sheets")
+ print(f"Sheets_name: {sheets_name}")
+ assert isinstance(sheets_name, list)
+ assert len(sheets_name) > 0
+ assert all(isinstance(sheet, str) for sheet in sheets_name)
+ # 遍历所有工作表, 并输出
+ for i, sheet in enumerate(sheets_name):
+ print(f"Sheet {i}: {sheet}")
+ print("First sheet in list:", sheets_name[0] if sheets_name else "None")
+ print("Get sheets test passed!")
+
+
+if __name__ == "__main__":
+ pass
diff --git a/xbot-et/wps-addon-build/xbot-et.7z b/xbot-et/wps-addon-build/xbot-et.7z
index 6443525..dafc6e4 100644
Binary files a/xbot-et/wps-addon-build/xbot-et.7z and b/xbot-et/wps-addon-build/xbot-et.7z differ
diff --git a/xbot-et/wps-addon-build/xbot-et_1.0.0/README.md b/xbot-et/wps-addon-build/xbot-et_1.0.0/README.md
new file mode 100644
index 0000000..bf03dc2
--- /dev/null
+++ b/xbot-et/wps-addon-build/xbot-et_1.0.0/README.md
@@ -0,0 +1 @@
+# WPS 表格自动化
diff --git a/xbot-et/wps-addon-build/xbot-et_1.0.0/images/about.svg b/xbot-et/wps-addon-build/xbot-et_1.0.0/images/about.svg
new file mode 100644
index 0000000..82718c1
--- /dev/null
+++ b/xbot-et/wps-addon-build/xbot-et_1.0.0/images/about.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/xbot-et/wps-addon-build/xbot-et_1.0.0/images/newFromTemp.svg b/xbot-et/wps-addon-build/xbot-et_1.0.0/images/newFromTemp.svg
new file mode 100644
index 0000000..f6c70b3
--- /dev/null
+++ b/xbot-et/wps-addon-build/xbot-et_1.0.0/images/newFromTemp.svg
@@ -0,0 +1,22 @@
+
+
\ No newline at end of file
diff --git a/xbot-et/wps-addon-build/xbot-et_1.0.0/index.html b/xbot-et/wps-addon-build/xbot-et_1.0.0/index.html
new file mode 100644
index 0000000..2efb298
--- /dev/null
+++ b/xbot-et/wps-addon-build/xbot-et_1.0.0/index.html
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/xbot-et/wps-addon-build/xbot-et_1.0.0/js/ribbon.js b/xbot-et/wps-addon-build/xbot-et_1.0.0/js/ribbon.js
new file mode 100644
index 0000000..cc40e95
--- /dev/null
+++ b/xbot-et/wps-addon-build/xbot-et_1.0.0/js/ribbon.js
@@ -0,0 +1,43 @@
+
+//这个函数在整个wps加载项中是第一个执行的
+function OnAddinLoad(ribbonUI) {
+ if (typeof (window.Application.ribbonUI) != "object") {
+ window.Application.ribbonUI = ribbonUI
+ }
+
+ if (typeof (window.Application.Enum) != "object") { // 如果没有内置枚举值
+ window.Application.Enum = WPS_Enum
+ }
+ return true
+}
+
+
+function OnAction(control) {
+ const eleId = control.Id
+ switch (eleId) {
+ case "btnShowAbout":
+ {
+ alert("关于我们\n\n作者:Jianxin\n\n版本:1.0.0\n\n说明:自动化插件\n\n感谢使用!")
+ }
+ break;
+ case "contactAuthor":
+ {
+ wps.ShowDialog(GetUrlPath() + "/ui/author.html", "联系作者", 400 * window.devicePixelRatio, 400 * window.devicePixelRatio, false)
+ }
+ default:
+ break
+ }
+ return true
+}
+
+
+function GetImage(control) {
+ const eleId = control.Id
+ switch (eleId) {
+ case "btnShowAbout":
+ return "images/about.svg"
+ default:
+ ;
+ }
+ return "images/newFromTemp.svg"
+}
diff --git a/xbot-et/wps-addon-build/xbot-et_1.0.0/js/util.js b/xbot-et/wps-addon-build/xbot-et_1.0.0/js/util.js
new file mode 100644
index 0000000..8c991a1
--- /dev/null
+++ b/xbot-et/wps-addon-build/xbot-et_1.0.0/js/util.js
@@ -0,0 +1,19 @@
+//在后续的wps版本中,wps的所有枚举值都会通过wps.Enum对象来自动支持,现阶段先人工定义
+var WPS_Enum = {
+ msoCTPDockPositionLeft: 0,
+ msoCTPDockPositionRight: 2
+}
+
+function GetUrlPath() {
+ let e = document.location.toString()
+ return -1 != (e = decodeURI(e)).indexOf("/") && (e = e.substring(0, e.lastIndexOf("/"))), e
+}
+/**
+ * 通过wps提供的接口执行一段脚本
+ * @param {*} param 需要执行的脚本
+ */
+function shellExecuteByOAAssist(param) {
+ if (wps != null) {
+ wps.OAAssist.ShellExecute(param)
+ }
+}
\ No newline at end of file
diff --git a/xbot-et/wps-addon-build/xbot-et_1.0.0/js/xbot.js b/xbot-et/wps-addon-build/xbot-et_1.0.0/js/xbot.js
new file mode 100644
index 0000000..1928235
--- /dev/null
+++ b/xbot-et/wps-addon-build/xbot-et_1.0.0/js/xbot.js
@@ -0,0 +1,64 @@
+let wpsInstance = null;
+
+
+function InvokeFromXbot(param) {
+ // 1. 解析参数, 如果不是字符串, 则转换为字符串, 并用json解析, 解析失败直接retrun
+ if (typeof param !== 'string') {
+ param = JSON.stringify(param);
+ }
+ try {
+ param = JSON.parse(param);
+ } catch (e) {
+ return { msg: '参数解析失败', code: -1 };
+ }
+
+ // 2. 判断是否有method字段, 如果没有直接return
+ if (!param.method) {
+ return { msg: '参数错误', code: -2 };
+ }
+
+ // 3. 获取 method 字段对应的方法和 args 参数
+ let method = param.method;
+
+ let args = null;
+
+ try {
+ args = JSON.parse(param.args);
+ } catch (e) {
+ args = {};
+ }
+
+
+ console.log("method:", method);
+ console.log("args:", args);
+ // confsole.log("args", typeof args);
+
+ // 4. 判断 method 是否为 inject, 如果是则执行注入方法, 其他走默认方法执行调用函数
+ if (method === 'inject') {
+ let code = args.code;
+ console.log("start eval code");
+ console.log(code);
+ console.log(new Function(code)());
+ wpsInstance = new Function(code)(); // 执行并获取返回值
+ console.log("success eval code");
+ return { message: "inject ok", code: 0 }
+ }
+
+ // 5. 判断是否有 wpsInstance, 如果没有直接return
+ if (!wpsInstance) {
+ return { msg: '未注入', code: -3 };
+ }
+
+ // 6. 判断 wpsInstance 是否有 method 方法, 如果没有直接return
+ if (!wpsInstance[method]) {
+ return { msg: '方法不存在', code: -4 };
+ }
+
+ // 7. 执行 wpsInstance 的 method 方法, 并返回结果
+ try {
+ const result = wpsInstance[method](args ?? null);
+ return { msg: '执行成功', code: 0, data: result };
+ } catch (e) {
+ return { msg: '执行失败', code: -5 };
+ }
+}
diff --git a/xbot-et/wps-addon-build/xbot-et_1.0.0/main.js b/xbot-et/wps-addon-build/xbot-et_1.0.0/main.js
new file mode 100644
index 0000000..fc183d6
--- /dev/null
+++ b/xbot-et/wps-addon-build/xbot-et_1.0.0/main.js
@@ -0,0 +1,4 @@
+//这个文件由index.html包含
+document.write("");
+document.write("");
+document.write("");
diff --git a/xbot-et/wps-addon-build/xbot-et_1.0.0/ribbon.xml b/xbot-et/wps-addon-build/xbot-et_1.0.0/ribbon.xml
new file mode 100644
index 0000000..cdaa597
--- /dev/null
+++ b/xbot-et/wps-addon-build/xbot-et_1.0.0/ribbon.xml
@@ -0,0 +1,15 @@
+
+