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

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

对图、表和公式的自动编号和交叉引用是科研笔记最基本的功能,但当前各种笔记系统均未实现该功能。因此,抽出一天时间,利用 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 回帖
  • 思源笔记

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

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

    19268 引用 • 72670 回帖
  • C#
    28 引用 • 34 回帖 • 5 关注
  • Quicker

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

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

相关帖子

欢迎来到这里!

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

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

    @88250 最近在开发一款 Siyuan 的文献管理插件。已知 Siyuan 可以通过 setBlockAttrs API 函数修改块的属性,通过·updateBlock API 函数更新块的内容,都是基于块的,请问有没有一种 API 函数能更新行内元素的内容和属性?这对开发文献管理插件非常有帮助。

    image.png

    1 回复
  • 其他回帖
  • ttChen

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

    1 回复
  • 88250

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

    1 回复
  • Josepha

    大佬···待会下来看看

  • 查看全部回帖

推荐标签 标签

  • sts
    2 引用 • 2 回帖 • 155 关注
  • 资讯

    资讯是用户因为及时地获得它并利用它而能够在相对短的时间内给自己带来价值的信息,资讯有时效性和地域性。

    53 引用 • 85 回帖
  • 博客

    记录并分享人生的经历。

    271 引用 • 2386 回帖
  • 尊园地产

    昆明尊园房地产经纪有限公司,即:Kunming Zunyuan Property Agency Company Limited(简称“尊园地产”)于 2007 年 6 月开始筹备,2007 年 8 月 18 日正式成立,注册资本 200 万元,公司性质为股份经纪有限公司,主营业务为:代租、代售、代办产权过户、办理银行按揭、担保、抵押、评估等。

    1 引用 • 22 回帖 • 694 关注
  • Mac

    Mac 是苹果公司自 1984 年起以“Macintosh”开始开发的个人消费型计算机,如:iMac、Mac mini、Macbook Air、Macbook Pro、Macbook、Mac Pro 等计算机。

    164 引用 • 594 回帖
  • RabbitMQ

    RabbitMQ 是一个开源的 AMQP 实现,服务器端用 Erlang 语言编写,支持多种语言客户端,如:Python、Ruby、.NET、Java、C、PHP、ActionScript 等。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

    49 引用 • 60 回帖 • 400 关注
  • 服务

    提供一个服务绝不仅仅是简单的把硬件和软件累加在一起,它包括了服务的可靠性、服务的标准化、以及对服务的监控、维护、技术支持等。

    41 引用 • 24 回帖 • 14 关注
  • MongoDB

    MongoDB(来自于英文单词“Humongous”,中文含义为“庞大”)是一个基于分布式文件存储的数据库,由 C++ 语言编写。旨在为应用提供可扩展的高性能数据存储解决方案。MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似 JSON 的 BSON 格式,因此可以存储比较复杂的数据类型。

    90 引用 • 59 回帖 • 4 关注
  • Kafka

    Kafka 是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是现代系统中许多功能的基础。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。

    35 引用 • 35 回帖 • 2 关注
  • OkHttp

    OkHttp 是一款 HTTP & HTTP/2 客户端库,专为 Android 和 Java 应用打造。

    16 引用 • 6 回帖 • 50 关注
  • V2EX

    V2EX 是创意工作者们的社区。这里目前汇聚了超过 400,000 名主要来自互联网行业、游戏行业和媒体行业的创意工作者。V2EX 希望能够成为创意工作者们的生活和事业的一部分。

    17 引用 • 236 回帖 • 408 关注
  • Elasticsearch

    Elasticsearch 是一个基于 Lucene 的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于 RESTful 接口。Elasticsearch 是用 Java 开发的,并作为 Apache 许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。

    116 引用 • 99 回帖 • 266 关注
  • 正则表达式

    正则表达式(Regular Expression)使用单个字符串来描述、匹配一系列遵循某个句法规则的字符串。

    31 引用 • 94 回帖
  • 阿里云

    阿里云是阿里巴巴集团旗下公司,是全球领先的云计算及人工智能科技公司。提供云服务器、云数据库、云安全等云计算服务,以及大数据、人工智能服务、精准定制基于场景的行业解决方案。

    89 引用 • 345 回帖 • 1 关注
  • VirtualBox

    VirtualBox 是一款开源虚拟机软件,最早由德国 Innotek 公司开发,由 Sun Microsystems 公司出品的软件,使用 Qt 编写,在 Sun 被 Oracle 收购后正式更名成 Oracle VM VirtualBox。

    10 引用 • 2 回帖 • 17 关注
  • Sandbox

    如果帖子标签含有 Sandbox ,则该帖子会被视为“测试帖”,主要用于测试社区功能,排查 bug 等,该标签下内容不定期进行清理。

    376 引用 • 1218 回帖 • 580 关注
  • 负能量

    上帝为你关上了一扇门,然后就去睡觉了....努力不一定能成功,但不努力一定很轻松 (° ー °〃)

    86 引用 • 1203 回帖 • 451 关注
  • Firefox

    Mozilla Firefox 中文俗称“火狐”(正式缩写为 Fx 或 fx,非正式缩写为 FF),是一个开源的网页浏览器,使用 Gecko 排版引擎,支持多种操作系统,如 Windows、OSX 及 Linux 等。

    7 引用 • 30 回帖 • 449 关注
  • Hibernate

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

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

    Web Clipper 是一款浏览器剪藏扩展,它可以帮助你把网页内容剪藏到本地。

    3 引用 • 9 回帖 • 3 关注
  • Kotlin

    Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言,由 JetBrains 设计开发并开源。Kotlin 可以编译成 Java 字节码,也可以编译成 JavaScript,方便在没有 JVM 的设备上运行。在 Google I/O 2017 中,Google 宣布 Kotlin 成为 Android 官方开发语言。

    19 引用 • 33 回帖 • 31 关注
  • 钉钉

    钉钉,专为中国企业打造的免费沟通协同多端平台, 阿里巴巴出品。

    15 引用 • 67 回帖 • 364 关注
  • 音乐

    你听到信仰的声音了么?

    59 引用 • 509 回帖 • 2 关注
  • 国际化

    i18n(其来源是英文单词 internationalization 的首末字符 i 和 n,18 为中间的字符数)是“国际化”的简称。对程序来说,国际化是指在不修改代码的情况下,能根据不同语言及地区显示相应的界面。

    7 引用 • 26 回帖 • 1 关注
  • uTools

    uTools 是一个极简、插件化、跨平台的现代桌面软件。通过自由选配丰富的插件,打造你得心应手的工具集合。

    5 引用 • 13 回帖
  • DNSPod

    DNSPod 建立于 2006 年 3 月份,是一款免费智能 DNS 产品。 DNSPod 可以为同时有电信、网通、教育网服务器的网站提供智能的解析,让电信用户访问电信的服务器,网通的用户访问网通的服务器,教育网的用户访问教育网的服务器,达到互联互通的效果。

    6 引用 • 26 回帖 • 522 关注
  • ActiveMQ

    ActiveMQ 是 Apache 旗下的一款开源消息总线系统,它完整实现了 JMS 规范,是一个企业级的消息中间件。

    19 引用 • 13 回帖 • 635 关注