前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >『JSA宏神助攻之五』使用搭建本地web服务让JSA宏连接本地文件读写

『JSA宏神助攻之五』使用搭建本地web服务让JSA宏连接本地文件读写

作者头像
Excel催化剂
发布2024-06-27 20:05:23
660
发布2024-06-27 20:05:23
举报
文章被收录于专栏:Excel催化剂Excel催化剂

在JSA宏中,没有办法对本地的文件进行读写,虽然仿效VBA实现了一个FreeFile的函数来处理读写,但也仅限于文本文件的读写。

如下代码(接录于WPS Office JavaScript 宏教程(JS宏)):

读取可能还算好,写入很坑的,会将本来的内容,在前后加上了一个单引号,将原本好好的文本给破坏了。

在JSA上没有,如何才能创造让它产生有?最有效的方式,就是提供一些外部服务,它来调用就完事了。这样的方式,也是无限扩展JSA宏的能力边界的一种非常有效的手段。

这些外部服务,最简单的,就是给它一个web服务来调用,当然可以自己架设个服务器来部署个web api服务,供JSA使用xmlhttpRequest或fetch来访问。

如果要在极端环境下使用,并且想调用本地资源,那就只有在本地电脑上搭建一个web服务就算事了。

如何搭建这个web服务,可能各路编程神仙又跑出来说用python/nodejs/r等各种方式搭建最方便啦,几句代码就立马开启了一个web服务。这个方便确实是方便,只是对开发者方便,对用户端,也不会太方便,单单满足有各种语言的运行环境就足够头大,就算打包整个环境到用户机器上使用,也是庞然大物。

既然是windows环境,除了.NET,谁还敢来称说比它容易的事。所以gpt时代,理当最合适的语言做最合适的事情。用.NET来搭建这个本地web服务准没错了。

在C#里,有个启动 OWIN 自托管的 Web API 服务,不用部署IIS,直接双击exe就开启了一个web 服务。代码也不复杂,毕竟是gpt吧,直接叫它写就完事了。具体的代码直接贴上,照抄来改进即可。

代码语言:javascript
复制
using System;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;

namespace SelfHostedWebApiConsole
{
    class Program
    {
        private static HttpListener _listener;
        private static readonly string _url = $"http://localhost:{Properties.Settings.Default.WebApiPort}/";

        static void Main(string[] args)
        {
            StartServer();

            while (_listener.IsListening) // Loop to keep the main thread running while the listener is active.
            {
                Thread.Sleep(1000);
            }
        }

        public static void StartServer()
        {
            _listener = new HttpListener();
            _listener.Prefixes.Add(_url);
            _listener.Start();

            Console.WriteLine("Listening for connections on {0}", _url);

            Task.Run(() =>
            {
                while (_listener.IsListening)
                {
                    try
                    {
                        var context = _listener.GetContext();
                        ProcessRequest(context);
                    }
                    catch (HttpListenerException)
                    {
                        // This will occur on _listener.Stop()
                        break;
                    }
                }
            });
        }

        public static void StopServer()
        {
            if (_listener != null)
            {
                _listener.Stop();
                _listener.Close();
            }
        }

        private static void ProcessRequest(HttpListenerContext context)
        {
            var request = context.Request;
            var response = context.Response;

            try
            {
                switch (request.Url.AbsolutePath)
                {
                    case "/api/file/read":
                        ProcessFileRead(request, response);
                        break;
                    case "/api/file/write":
                        ProcessFileWrite(request, response);
                        break;
                    default:
                        response.StatusCode = (int)HttpStatusCode.NotFound;
                        break;
                }
            }
            finally
            {
                response.OutputStream.Close();
            }
        }

        private static void ProcessFileRead(HttpListenerRequest request, HttpListenerResponse response)
        {
            string filePath = GetFilePath(request);
            filePath = HttpUtility.UrlDecode(filePath, System.Text.Encoding.UTF8);
            var isBinary = bool.Parse(request.QueryString["isBinary"] ?? "false");
            var encodingName = request.QueryString["encoding"] ?? "utf-8"; // 默认为 UTF-8


            if (!File.Exists(filePath))
            {
                response.StatusCode = (int)HttpStatusCode.NotFound;
                return;
            }

            if (isBinary)
            {
                var fileBytes = File.ReadAllBytes(filePath);
                response.ContentType = "application/octet-stream";
                response.OutputStream.Write(fileBytes, 0, fileBytes.Length);
            }
            else
            {
                Encoding encoding;
                try
                {
                    encoding = Encoding.GetEncoding(encodingName);
                }
                catch (ArgumentException)
                {
                    response.StatusCode = (int)HttpStatusCode.BadRequest;
                    var errorMessage = Encoding.UTF8.GetBytes("Invalid encoding specified.");
                    response.OutputStream.Write(errorMessage, 0, errorMessage.Length);
                    return;
                }

                var fileContent = File.ReadAllText(filePath, encoding);
                var buffer = Encoding.UTF8.GetBytes(fileContent);
                response.ContentType = "text/plain; charset=utf-8";
                response.OutputStream.Write(buffer, 0, buffer.Length);
            }

            response.StatusCode = (int)HttpStatusCode.OK;
        }

        private static string GetFilePath(HttpListenerRequest request)
        {
            string rawUrl = request.RawUrl;
            Uri uri = new Uri("http://dummy" + rawUrl); // 加前缀是因为Uri需要完整的格式

            // 解析查询字符串
            NameValueCollection queryParameters = HttpUtility.ParseQueryString(uri.Query);
            string encodedFilePath = queryParameters["filePath"];
            if (!string.IsNullOrEmpty(encodedFilePath))
            {
                string decodedFilePath = Uri.UnescapeDataString(encodedFilePath);
                return Environment.ExpandEnvironmentVariables(decodedFilePath);
            }
            else
            {
                return string.Empty;
            }

        }

        private static void ProcessFileWrite(HttpListenerRequest request, HttpListenerResponse response)
        {
            string filePath = GetFilePath(request);
            var isBinary = bool.Parse(request.QueryString["isBinary"] ?? "false");
            var encodingName = request.QueryString["encoding"] ?? "utf-8"; // 默认为 UTF-8

            try
            {
                Encoding encoding = Encoding.GetEncoding(encodingName);
                if (isBinary)
                {
                    // 二进制模式,直接从流中读取并写入文件
                    using (var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
                    {
                        request.InputStream.CopyTo(fs);
                    }
                }
                else
                {
                    // 文本模式,使用指定的编码读取文本内容并写入文件
                    using (var reader = new StreamReader(request.InputStream, encoding))
                    {
                        var content = reader.ReadToEnd();
                        File.WriteAllText(filePath, content, encoding);  // 使用提供的编码写入文件
                    }
                }
                response.StatusCode = (int)HttpStatusCode.OK;
                var responseMessage = "File written successfully";
                var responseBuffer = Encoding.UTF8.GetBytes(responseMessage);
                response.OutputStream.Write(responseBuffer, 0, responseBuffer.Length);
            }
            catch (ArgumentException)
            {
                // 编码不支持或无效
                response.StatusCode = (int)HttpStatusCode.BadRequest;
                var errorMessage = Encoding.UTF8.GetBytes("Invalid encoding specified.");
                response.OutputStream.Write(errorMessage, 0, errorMessage.Length);
            }
        }


    }
}

有了这个读写文件的接口,在JSA上,直接调用这个本地接口就完事了,也送上JSA的代码段:

代码语言:javascript
复制
async function testTextFileOperations() {
    // 定义读取和写入的文件名及其他参数
    const readFileName = 'C:/Users/19026/Desktop/测试文本.txt';
    const writeFileName = 'C:/Users/19026/Desktop/测试文本写入.txt';
    const isBinary = false; // 明确指定处理非二进制(文本)文件

    // 构建读取请求的URL
    const readUrl = `http://localhost:6789/api/file/read?filePath=${encodeURIComponent(readFileName)}&isBinary=${isBinary}`;

    try {
        // 使用fetch读取文本文件
        const readResponse = await fetch(readUrl);
        if (!readResponse.ok) {
            throw new Error('Failed to read file: ' + await readResponse.text());
        }
        const textContent = await readResponse.text();
        console.log('File read successfully:', textContent);

        // 可以在此处修改textContent,例如添加一些文本
        const modifiedContent = textContent + "\nAdditional line added by the script.";

        // 第二步:将修改后的文本内容写入另一个文件
        const writeUrl = `http://localhost:6789/api/file/write?filePath=${encodeURIComponent(writeFileName)}&isBinary=${isBinary}`;

        // 使用fetch写入文本文件
        const writeResponse = await fetch(writeUrl, {
            method: 'POST',
            headers: {
                'Content-Type': 'text/plain; charset=utf-8'
            },
            body: modifiedContent
        });

        if (!writeResponse.ok) {
            throw new Error('Failed to write file: ' + await writeResponse.text());
        }
        console.log('File written successfully');
    } catch (error) {
        console.error('Error:', error);
    }
}

笔者本来接口实现的是读取文件,可以是二进制,也可以是文本,遗憾的是二进制文件的读写,在JSA上失败了,还在找官方问原因,希望未来可以修复吧。

最后,在JSA中,使用Shell函数,来启用这个本地Web服务,启用完后,就可以使用http请求的方式,进行get/post请求,访问上面的读写文件的接口了(WPS最新版本竟然又有bug,这个Shell函数用不了!!!等他们修复好再尝试吧,先手动双击下exe运行测试)。

总结

现在的程序交互中,大量使用了web服务来作为程序间交互通信的手段。一般很少会自己和自己玩,在本地建立个web服务来访问。

但因为WPS的弱鸡性,只能用各种方式来增强它,使用web服务,是个不错的选择,特别是JSA现在原生支持发起http请求。

在web服务的搭建上,强烈建议使用.NET来完成,简单快捷,发布时的文件足够小,充分利用windows的现有环境跑起来(其他语言来搭建单单在用户机器上弄个环境头都大)。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-06-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Excel催化剂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
API 网关
腾讯云 API 网关(API Gateway)是腾讯云推出的一种 API 托管服务,能提供 API 的完整生命周期管理,包括创建、维护、发布、运行、下线等。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档