实现 SiYuan 笔记图、表和公式的自动编号和交叉引用

本贴最后更新于 318 天前,其中的信息可能已经时异事殊

对图、表和公式的自动编号和交叉引用是科研笔记最基本的功能,但当前各种笔记系统均未实现该功能。因此,抽出一天时间,利用 Quicker 并结合 C#代码,针对 Siyuan 做了一下尝试。虽然还有待完善,但也基本实现了该功能,见图 11

交叉引用 - 动作信息 - Quicker (getquicker.net)

主要功能:

  1. 实现对图进行自动编号,同时增减图刷新后,编号将进行全自动更新;

  2. 实现对表进行自动编号,同时增减表刷新后,编号将进行全自动更新;

  3. 实现对公式进行自动编号,同时增减公式刷新后,编号将进行全自动更新;

  4. 实现了对图表和公式的交叉引用,同时增删图表和公式后将自动更新交叉引用

image

图 1 界面

主要代码如下:

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

public static string Exec(string paramValue)
{
	JObject Jobj=JObject.Parse(paramValue);
	string block_id = (string)Jobj["block_id"];
	string API_token= (string)Jobj["API_token"];
	string cmd = (string)Jobj["cmd"];
	string data = (string)Jobj["data"];
	if (cmd.ToLower()=="setcaption"){
		SetCaption(data, block_id,API_token);
	} else if (cmd.ToLower()=="clear"){
		ClearCaptionInfo(block_id,API_token);
	} else if (cmd.ToLower()=="update") {
		UpdateCaptions(block_id,API_token);
	}



	return "";
}

private static Dictionary<string, string> ial2JSON(string ial, string mk)
{
	Dictionary<string, string> mydict = new Dictionary<string, string>();
	foreach (Match m in Regex.Matches(ial, "([\\w-]+)=\"([^\"]*)\""))
	{
		mydict.Add(m.Groups[1].Value, m.Groups[2].Value);
	}
	mydict.Add("mk", mk);
	return mydict;
}
public static string ClearCaptionInfo(string block_id, string Token)
{
	SYAPI siyuanAPI = new SYAPI(Token);
	JObject myJobj = new JObject();
	myJobj["custom-type"] = "";
	myJobj["custom-id"] = "";
	myJobj["name"] = "";
	siyuanAPI.SetBlockAttrs(block_id, myJobj);
	siyuanAPI.PushMsg("已完成更新!");
	return "";
}
public static string SetCaption(string CaptionLabel, string block_id, string Token)
{
    SYAPI siyuanAPI = new SYAPI(Token);       
    JObject result=siyuanAPI.SQL_query(string.Format("Select markdown from blocks where id='{0}'", block_id));
    string cmk = (string)result["data"][0]["markdown"];
    Dictionary<string, string> cdict = new Dictionary<string, string>()
    {
        { "custom-type",CaptionLabel},
        { "custom-id","1"},
        { "name",CaptionLabel+" 1"},
        { "id",block_id},
        { "mk",cmk}
    };
    string doc_id = siyuanAPI.GetDocumentIDbyBlockID(block_id);
   result = siyuanAPI.SQL_query(string.Format("select markdown,ial from blocks where id in (SELECT block_id FROM attributes where name='custom-type' and root_id='{0}') ", doc_id));
    List<Dictionary<string, string>> data = result["data"].Select(x => ial2JSON((string)x["ial"], (string)x["markdown"])).ToList<Dictionary<string, string>>();
    data.Add(cdict);
    result = siyuanAPI.GetChildBlocks(doc_id);
    List<string> idx = result["data"].Select(x => (string)x["id"]).ToList<string>();
    data = data.OrderBy(mdict => idx.IndexOf(mdict["id"])).ToList();

    Dictionary<string, int> CaptionDictID = new Dictionary<string, int>();
    foreach (Dictionary<string, string> item in data)
    {
        string captionLabel = item["custom-type"];
        int seqID = int.Parse(item["custom-id"]);
        string id = item["id"];
        string name = item["name"];
        string mk = item["mk"];
        if (CaptionDictID.Keys.Contains(captionLabel))
        {
            CaptionDictID[captionLabel] += 1;
        }
        else
        {
            CaptionDictID[captionLabel] = 1;
        }

        if ((new string[] { "式", "equation", "eq", "eq.", "公式" }).Contains(captionLabel.ToLower()))
        {
            mk = Regex.Replace(mk, "^\\$\\$", "");
            mk = Regex.Replace(mk, "\\$\\$$", "");
            mk = Regex.Replace(mk.Trim(), @"\\tag ?\{ ?(\d+-)?\d+ ?\}", "");
            name = captionLabel + " " + CaptionDictID[captionLabel].ToString();
            siyuanAPI.UpdateBlock(id, string.Format("$${0}\\tag{{{1}{2}}}$$", mk.Trim(), "", CaptionDictID[captionLabel]));
        }
        else
        {
            string pattern = string.Format("^(?<header>[#\\* ]*)(?<CaptionLabel>{0} \\d{{1,3}})?(?<last>.*)$", captionLabel);
            GroupCollection groups = Regex.Match(mk, pattern).Groups;
            mk = groups["header"].ToString().Trim() + captionLabel + " " + CaptionDictID[captionLabel].ToString() + " " + groups["last"].ToString().Trim();
            siyuanAPI.UpdateBlock(id, mk);
        }

        JObject  myJobj = new JObject();
        myJobj["custom-type"] = captionLabel;
        myJobj["custom-id"] = CaptionDictID[captionLabel].ToString();
        myJobj["name"] = captionLabel + " " + CaptionDictID[captionLabel].ToString();
        siyuanAPI.SetBlockAttrs(id, myJobj);
        }

	return "";
}


public static void UpdateCaptions(string block_id, string Token)
{
	SYAPI siyuanAPI = new SYAPI(Token);
	string doc_id = siyuanAPI.GetDocumentIDbyBlockID(block_id);
	JObject result = siyuanAPI.SQL_query(string.Format("select markdown,ial from blocks where id in (SELECT block_id FROM attributes where name='custom-type' and root_id='{0}') ", doc_id));
	List<Dictionary<string, string>> data = result["data"].Select(x => ial2JSON((string)x["ial"], (string)x["markdown"])).ToList<Dictionary<string, string>>();
	result = siyuanAPI.GetChildBlocks(doc_id);
	List<string> idx = result["data"].Select(x => (string)x["id"]).ToList<string>();
	data = data.OrderBy(mdict => idx.IndexOf(mdict["id"])).ToList();

	Dictionary<string, int> CaptionDictID = new Dictionary<string, int>();
	foreach (Dictionary<string, string> item in data)
	{
		string captionLabel = item["custom-type"];
		int seqID = int.Parse(item["custom-id"]);
		string id = item["id"];
		string name = item["name"];
		string mk = item["mk"];
		if (CaptionDictID.Keys.Contains(captionLabel))
		{
			CaptionDictID[captionLabel] += 1;
		}
		else
		{
			CaptionDictID[captionLabel] = 1;
		}

		if ((new string[] { "式", "equation", "eq", "eq.", "公式" }).Contains(captionLabel.ToLower()))
		{
			mk = Regex.Replace(mk, "^\\$\\$", "");
			mk = Regex.Replace(mk, "\\$\\$$", "");
			mk = Regex.Replace(mk.Trim(), @"\\tag ?\{ ?(\d+-)?\d+ ?\}", "");
			name = captionLabel + " " + CaptionDictID[captionLabel].ToString();
			siyuanAPI.UpdateBlock(id, string.Format("$${0}\\tag{{{1}{2}}}$$", mk.Trim(), "", CaptionDictID[captionLabel]));
		}
		else
		{
			string pattern = string.Format("^(?<header>[#\\* ]*)(?<CaptionLabel>{0} \\d{{1,3}})?(?<last>.*)$", captionLabel);
			GroupCollection groups = Regex.Match(mk, pattern).Groups;
			mk = groups["header"].ToString().Trim() + captionLabel + " " + CaptionDictID[captionLabel].ToString() + " " + groups["last"].ToString().Trim();
			siyuanAPI.UpdateBlock(id, mk);
		}

		JObject myJobj = new JObject();
		myJobj["custom-type"] = captionLabel;
		myJobj["custom-id"] = CaptionDictID[captionLabel].ToString();
		myJobj["name"] = captionLabel + " " + CaptionDictID[captionLabel].ToString();
		siyuanAPI.SetBlockAttrs(id, myJobj);
	}

	siyuanAPI.PushMsg("已完成更新!");
}



public class SYAPI
{
	private string _TokenID;
	public SYAPI(string TokenID)
	{
		_TokenID = TokenID;
	}

	private async Task<JObject> CommandAsync(string path, JObject data)
	{
		string url = "http://127.0.0.1:6806";
		url = url + path;
		var body = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json");
		using (HttpClient client = new HttpClient())
		{
			client.DefaultRequestHeaders.Add("Authorization", "Token " + _TokenID);
			HttpResponseMessage response = await client.PostAsync(url, body);
			response.EnsureSuccessStatusCode();
			string responseBody = await response.Content.ReadAsStringAsync();
			return (JObject.Parse(responseBody));
		}

	}
	public JObject lsNotebooksAsync()
	{
		return (CommandAsync("/api/notebook/lsNotebooks", new JObject()).Result);
	}
	public JObject PushMsg(string msg)
	{
		return CommandAsync("/api/notification/pushMsg",
		                    JObject.FromObject(new
		                                       {
		                                       	msg = msg,
		                                       	timeout = 7000
		                                       })).Result;

	}
	public JObject PushErrMsg(string msg)
	{
		return CommandAsync("/api/notification/pushMsg",
		                    JObject.FromObject(new
		                                       {
		                                       	msg = msg,
		                                       	timeout = 7000
		                                       })).Result;

	}
	public JObject SQL_query(string SQL)
	{
		return CommandAsync("/api/query/sql",
		                    JObject.FromObject(new
		                                       {
		                                       	stmt = SQL,
		                                       })).Result;

	}

	#region Block块
	public JObject UpdateBlock(string block_id, string markdown, string dataType = "markdown")
	{
		return CommandAsync("/api/block/updateBlock",
		                    JObject.FromObject(new
		                                       {
		                                       	id = block_id,
		                                       	dataType = dataType,
		                                       	data = markdown
		                                       })).Result;

	}
	public JObject GetBlockInfo(string block_id)
	{
		return CommandAsync("/api/block/getBlockInfo",
		                    JObject.FromObject(new
		                                       {
		                                       	id = block_id,
		                                       })).Result;

	}
	public JObject GetBlockKramdown(string block_id)
	{
		return CommandAsync("/api/block/getBlockKramdown", JObject.FromObject(new
		                                                                      {
		                                                                      	id = block_id,
		                                                                      })).Result;
	}

	public JObject GetChildBlocks(string block_id)
	{
		return CommandAsync("/api/block/getChildBlocks", JObject.FromObject(new
		                                                                    {
		                                                                    	id = block_id
		                                                                    })).Result;
	}

	public JObject SetBlockAttrs(string block_id, object dict)
	{

		return CommandAsync("/api/attr/setBlockAttrs", JObject.FromObject(new
		                                                                  {
		                                                                  	id = block_id,
		                                                                  	attrs = JObject.FromObject(dict),
		                                                                  })).Result;
	}

	public JObject ResetBlockAttrs(string block_id, object dict)
	{

		return CommandAsync("/api/attr/resetBlockAttrs", JObject.FromObject(new
		                                                                    {
		                                                                    	id = block_id,
		                                                                    	attrs = JObject.FromObject(dict),
		                                                                    })).Result;
	}
	#endregion

	#region Document
	public string GetDocumentIDbyBlockID(string block_id)
	{
		JObject result = SQL_query(string.Format(" select root_id from blocks where id='{0}'", block_id));
		return (string)result["data"][0]["root_id"];
	}

	#endregion
}


  1. 图 1 界面

  • 公式
    5 引用 • 24 回帖
  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    19172 引用 • 72130 回帖
  • C#
    28 引用 • 34 回帖 • 5 关注
  • Quicker

    Quicker 您的指尖工具箱!操作更少,收获更多!

    22 引用 • 81 回帖
1 操作
ttChen 在 2023-07-08 20:51:27 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • GloR

    太强了!感谢!

  • 其他回帖
  • 88250

    是的,做不到仅修改部分行级元素,用 kramdown 只能整块更新。

    1 回复
  • ttChen

    也就是说,很难保证块样式(若批注、加底色等)不变的情况,在块中增加或者删除一个简单字符。

    1 回复
  • 88250

    只能把加背景的这几个字改成加粗,比如:

    {
        "id": "20230712190919-obqieuq",
        "dataType": "markdown",
        "data": "foo ((20230712211410-ktc4oa8 'bar')) baz **bazz**{: style=\"background-color: var(--b3-font-background1);\"}^[1]^\n{: updated=\"20230712211420\" id=\"20230712190919-obqieuq\"}"
    }
    
    1 回复
  • 查看全部回帖

推荐标签 标签

  • Electron

    Electron 基于 Chromium 和 Node.js,让你可以使用 HTML、CSS 和 JavaScript 构建应用。它是一个由 GitHub 及众多贡献者组成的活跃社区共同维护的开源项目,兼容 Mac、Windows 和 Linux,它构建的应用可在这三个操作系统上面运行。

    15 引用 • 136 回帖 • 5 关注
  • 禅道

    禅道是一款国产的开源项目管理软件,她的核心管理思想基于敏捷方法 scrum,内置了产品管理和项目管理,同时又根据国内研发现状补充了测试管理、计划管理、发布管理、文档管理、事务管理等功能,在一个软件中就可以将软件研发中的需求、任务、bug、用例、计划、发布等要素有序的跟踪管理起来,完整地覆盖了项目管理的核心流程。

    5 引用 • 15 回帖 • 209 关注
  • Linux

    Linux 是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX 和 Unix 的多用户、多任务、支持多线程和多 CPU 的操作系统。它能运行主要的 Unix 工具软件、应用程序和网络协议,并支持 32 位和 64 位硬件。Linux 继承了 Unix 以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。

    918 引用 • 931 回帖 • 3 关注
  • SEO

    发布对别人有帮助的原创内容是最好的 SEO 方式。

    35 引用 • 200 回帖 • 25 关注
  • Scala

    Scala 是一门多范式的编程语言,集成面向对象编程和函数式编程的各种特性。

    13 引用 • 11 回帖 • 111 关注
  • InfluxDB

    InfluxDB 是一个开源的没有外部依赖的时间序列数据库。适用于记录度量,事件及实时分析。

    2 引用 • 55 关注
  • C

    C 语言是一门通用计算机编程语言,应用广泛。C 语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。

    83 引用 • 165 回帖 • 37 关注
  • 职场

    找到自己的位置,萌新烦恼少。

    126 引用 • 1699 回帖
  • 程序员

    程序员是从事程序开发、程序维护的专业人员。

    539 引用 • 3528 回帖
  • 星云链

    星云链是一个开源公链,业内简单的将其称为区块链上的谷歌。其实它不仅仅是区块链搜索引擎,一个公链的所有功能,它基本都有,比如你可以用它来开发部署你的去中心化的 APP,你可以在上面编写智能合约,发送交易等等。3 分钟快速接入星云链 (NAS) 测试网

    3 引用 • 16 回帖 • 1 关注
  • Gzip

    gzip (GNU zip)是 GNU 自由软件的文件压缩程序。我们在 Linux 中经常会用到后缀为 .gz 的文件,它们就是 Gzip 格式的。现今已经成为互联网上使用非常普遍的一种数据压缩格式,或者说一种文件格式。

    9 引用 • 12 回帖 • 113 关注
  • 笔记

    好记性不如烂笔头。

    306 引用 • 780 回帖 • 1 关注
  • Hibernate

    Hibernate 是一个开放源代码的对象关系映射框架,它对 JDBC 进行了非常轻量级的对象封装,使得 Java 程序员可以随心所欲的使用对象编程思维来操纵数据库。

    39 引用 • 103 回帖 • 687 关注
  • Laravel

    Laravel 是一套简洁、优雅的 PHP Web 开发框架。它采用 MVC 设计,是一款崇尚开发效率的全栈框架。

    19 引用 • 23 回帖 • 694 关注
  • 电影

    这是一个不能说的秘密。

    120 引用 • 597 回帖
  • 游戏

    沉迷游戏伤身,强撸灰飞烟灭。

    170 引用 • 802 回帖 • 1 关注
  • H2

    H2 是一个开源的嵌入式数据库引擎,采用 Java 语言编写,不受平台的限制,同时 H2 提供了一个十分方便的 web 控制台用于操作和管理数据库内容。H2 还提供兼容模式,可以兼容一些主流的数据库,因此采用 H2 作为开发期的数据库非常方便。

    11 引用 • 54 回帖 • 642 关注
  • Eclipse

    Eclipse 是一个开放源代码的、基于 Java 的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。

    75 引用 • 258 回帖 • 629 关注
  • PWL

    组织简介

    用爱发电 (Programming With Love) 是一个以开源精神为核心的民间开源爱好者技术组织,“用爱发电”象征开源与贡献精神,加入组织,代表你将遵守组织的“个人开源爱好者”的各项条款。申请加入:用爱发电组织邀请帖
    用爱发电组织官网:https://programmingwithlove.stackoverflow.wiki/

    用爱发电组织的核心驱动力:

    • 遵守开源守则,体现开源&贡献精神:以分享为目的,拒绝非法牟利。
    • 自我保护:使用适当的 License 保护自己的原创作品。
    • 尊重他人:不以各种理由、各种漏洞进行未经允许的抄袭、散播、洩露;以礼相待,尊重所有对社区做出贡献的开发者;通过他人的分享习得知识,要留下足迹,表示感谢。
    • 热爱编程、热爱学习:加入组织,热爱编程是首当其要的。我们欢迎热爱讨论、分享、提问的朋友,也同样欢迎默默成就的朋友。
    • 倾听:正确并恳切对待、处理问题与建议,及时修复开源项目的 Bug ,及时与反馈者沟通。不抬杠、不无视、不辱骂。
    • 平视:不诋毁、轻视、嘲讽其他开发者,主动提出建议、施以帮助,以和谐为本。只要他人肯努力,你也可能会被昔日小看的人所超越,所以请保持谦虚。
    • 乐观且活跃:你的努力决定了你的高度。不要放弃,多年后回头俯瞰,才会发现自己已经成就往日所仰望的水平。积极地将项目开源,帮助他人学习、改进,自己也会获得相应的提升、成就与成就感。
    1 引用 • 487 回帖 • 2 关注
  • JVM

    JVM(Java Virtual Machine)Java 虚拟机是一个微型操作系统,有自己的硬件构架体系,还有相应的指令系统。能够识别 Java 独特的 .class 文件(字节码),能够将这些文件中的信息读取出来,使得 Java 程序只需要生成 Java 虚拟机上的字节码后就能在不同操作系统平台上进行运行。

    180 引用 • 120 回帖 • 2 关注
  • wolai

    我来 wolai:不仅仅是未来的云端笔记!

    2 引用 • 14 回帖 • 4 关注
  • Logseq

    Logseq 是一个隐私优先、开源的知识库工具。

    Logseq is a joyful, open-source outliner that works on top of local plain-text Markdown and Org-mode files. Use it to write, organize and share your thoughts, keep your to-do list, and build your own digital garden.

    4 引用 • 55 回帖 • 8 关注
  • Chrome

    Chrome 又称 Google 浏览器,是一个由谷歌公司开发的网页浏览器。该浏览器是基于其他开源软件所编写,包括 WebKit,目标是提升稳定性、速度和安全性,并创造出简单且有效率的使用者界面。

    60 引用 • 287 回帖
  • RYMCU

    RYMCU 致力于打造一个即严谨又活泼、专业又不失有趣,为数百万人服务的开源嵌入式知识学习交流平台。

    4 引用 • 6 回帖 • 41 关注
  • Ubuntu

    Ubuntu(友帮拓、优般图、乌班图)是一个以桌面应用为主的 Linux 操作系统,其名称来自非洲南部祖鲁语或豪萨语的“ubuntu”一词,意思是“人性”、“我的存在是因为大家的存在”,是非洲传统的一种价值观,类似华人社会的“仁爱”思想。Ubuntu 的目标在于为一般用户提供一个最新的、同时又相当稳定的主要由自由软件构建而成的操作系统。

    123 引用 • 168 回帖 • 1 关注
  • webpack

    webpack 是一个用于前端开发的模块加载器和打包工具,它能把各种资源,例如 JS、CSS(less/sass)、图片等都作为模块来使用和处理。

    41 引用 • 130 回帖 • 292 关注
  • danl
    77 关注