Please help me adapt the Dataview code to count words and symbols in notes

What I’m trying to do

I found this solution:

But I have very little understanding of programming and was only able to achieve deleting the task column.
I need:

  1. The columns “Words” and “Symbols” are side by side and working (and not in percentages).
  2. The “Share” column after the “Words” and “Symbols” columns.
  3. Remove the “Tasks” column and everything associated with it from the code, because I’m not sure how I did it.
  4. Leave the “Status” column.
  5. Explain to me how words and characters are counted in this code (optional).
  6. Set a limit on the number of displayed files to 15 files.
  7. Ability to separately specify the purpose for each note (optional).

Here is the code from that link. CSS styles are not important, they just position the text inside the table a little more beautifully.

---
cssclass: wordcountTable
---

%%

__Notes to display__
*Gets either notes in a folder or notes with a certain tag. Leave one of them empty.*
sourceFolder:: 
sourceTag::

__Notes to exclude__
*Leave empty to disable. Notes with the yaml-key `status` and value `exclude` for that key are also excluded.)*
excludeTag:: 

__Counting Settings__
*"chars" or "words"*
toCount:: chars
target:: 70000

*words or characters per page, depending on setting above. Set to zero to ignore.*
wordsPerPage:: 1000
charsPerPage:: 2000

includeFootnotes:: true
charactersIncludeSpaces:: true
excludeComments:: true
cumulativeShare:: false
groupedCount:: true

__Bibliography Estimate for Pandoc Citations__
includeBibliographyEstimate:: true
wordsPerCitation:: 22
charsPerCitation:: 155

__Longform Plugin__
*Leave empty to sort alphabetically. Enter the path to the index file of a longform project to order sections by their order in the longform plugin. (The `sourceFolder` setting further above has to be a Longform Drafts folder. )*
pathToIndexFile::

*Begin a filename with this character and it will be treated as subsection*
subsectionStartChar:: _

__Purely visual__
useThousandSeperator:: true
thousandSeperator:: .
naChar:: —
mostRecentIcon:: 🕙

%%
```dataviewjs
// Word Count Dashboard
// a dataviewjs snippet by @pseudometa, https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99
// version 1.10.2
// last update: 2022-01-25

//----------------------------------------------------
// Import configuration
//----------------------------------------------------
const source = dv.current();
const sourceFolder = source.sourceFolder;
const target = source.target;
const toCount = source.toCount;
const includeFootnotes = source.includeFootnotes;
const charactersIncludeSpaces = source.charactersIncludeSpaces;
const excludeComments = source.excludeComments;
const includeBibliographyEstimate = source.includeBibliographyEstimate;
const wordsPerCitation = source.wordsPerCitation;
const charsPerCitation = source.charsPerCitation;
const thousandSeperator = source.thousandSeperator;
const useThousandSeperator = source.useThousandSeperator;
const naChar = source.naChar;
const subsectionStartChar = source.subsectionStartChar;
const wordsPerPage = source.wordsPerPage;
const charsPerPage = source.charsPerPage;
const pathToIndexFile = source.pathToIndexFile;
const cumulativeShare = source.cumulativeShare;
const groupedCount = source.groupedCount;
const mostRecentIcon = source.mostRecentIcon;

let sourceTag = source.sourceTag;
let excludeTag = source.excludeTag;
// prepend hashtags for tags
if (sourceTag) if (!sourceTag.startsWith("#")) sourceTag = "#" + sourceTag;
if (excludeTag) if (!excludeTag.startsWith("#")) excludeTag = "#" + excludeTag;

//----------------------------------------------------
// Functions
//----------------------------------------------------

function getWordCount(text) {
	// Regex from BetterWordCount Plugin
	const spaceDelimitedChars = /A-Za-z\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B4\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AD\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC/
		.source;
	const nonSpaceDelimitedWords = /[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u4E00-\u9FD5]{1}/
		.source;
	const pattern = new RegExp([
		"(?:[0-9]+(?:(?:,|\\.)[0-9]+)*|[\\-" + spaceDelimitedChars + "])+",
		nonSpaceDelimitedWords
	].join("|"), "g");
	return (text.match(pattern) || []).length;
}

function getCharacterCount(text) {
	if (charactersIncludeSpaces)	return text.length;
	return text.replaceAll(" ", "").length;
}

function insert1000sep (num) {
	let numText = String(num);
	if (!useThousandSeperator) return numText;
	if (num >= 10000) numText = numText.slice(0, -3) + thousandSeperator + numText.slice (-3); // eslint-disable-line no-magic-numbers
	return numText;
}

String.prototype.strong = function () {
	if (this === " ") return " ";
	return "**" + this + "**";
};

function removeMarkdown (text) {
	let plaintext = text
		.replace(/`\$?=[^`]+`/g, "") // inline dataview
		.replace(/^---\n.*?\n---\n/s, "") // YAML Header
		.replace(/!?\[(.+)\]\(.+\)/g, "$1") // URLs & Image Captions
		.replace(/\*|_|\[\[|\]\]|\||==|~~|---|#|> |`/g, ""); // Markdown Syntax

	if (excludeComments) {
		plaintext = plaintext
			.replace(/<!--.*?-->/sg, "")
			.replace(/%%.*?%%/sg, "");
	}
	else {
		plaintext = plaintext
			.replace(/%%|<!--|-->/g, ""); // remove only comment syntax
	}

	return plaintext;
}

function removeFootnotes (text) {
	return text
		.replace(/^\[\^[A-Za-z0-9-]+\]:.*$/gm, "") // footnote at the end
		.replace(/\[\^[A-Za-z0-9-]+\]/g, ""); // footnote reference inline
}

function countPandocCitations (text) {
	const citations = text.match(/@[A-Za-z0-9-]+(?=[,;\] ])/gi);
	if (!citations) return 0;
	const uniqCitations = [...new Set(citations)]; // only unique citations
	return uniqCitations.length;
}

function toPercentStr (share) {
	return (share * 100).toFixed(0).toString() + " %";
}

//----------------------------------------------------
// Table Construction
//----------------------------------------------------
async function getTableContents () {
	const output = [];
	let completeText = "";
	let total = 0;
	let share = 0;
	let sectionCounter = 0;
	let subsectionCounter = 0;
	let totalTasks = 0;

	// get sections via folder or via tag
	let sections;
	if (sourceFolder) sections = dv.pages("\"" + sourceFolder + "\"");
	else sections = dv.pages(sourceTag);

	// exclude certain notes
	numExcludeStatus = sections.filter(t => t.status === "exclude").length;
	sections = sections.filter(t => t.status !== "exclude");
	if (excludeTag !== "") {
		numExcludedNotes = sections.filter(t => t.file.tags.includes(excludeTag)).length;
		sections = sections.filter(t => !t.file.tags.includes(excludeTag));
	}

	// most recent note
	sections = sections.sort (s => s.file.mtime, "desc");
	const mostRecentNote = sections[0].file.name;

	// SORT sections
	if (pathToIndexFile) {
		const draftName = sourceFolder.split("/").pop();
		const longformOrder =
			dv.page(pathToIndexFile) // do not wrap in ", as with db.pages
			.drafts
			.filter(d => d.name === draftName)
			.scenes;

		sections = sections.sort(
			s => s.file.name,
			"desc",
			(a, b) => longformOrder.indexOf(b) - longformOrder.indexOf(a)
		);
	} else {
		sections = sections.sort(s => s.file.name);
	}


	//-------------------------------------------------
	// SECTIONS LOOP
	//-------------------------------------------------
	for (const section of sections) {

		// read page content
		let content = await dv.io.load(section.file.path); // eslint-disable-line no-await-in-loop

		// count markdown tasks
		// need to be counted before cleanup
		let tasks = content.match(/- \[ ] /g);
		let taskNum = 0;
		let taskStr = "";
		if (tasks) {
			taskNum = tasks.length;
			taskStr = taskNum.toString();
		}

		// clean up
		content = removeMarkdown (content);
		if (!includeFootnotes) content = removeFootnotes (content);
		content = content
			.replace(/(^\s*)|(\s*$)/g, "") // remove the start and end spaces of the given string
			.replace(/ {2,}/g, " "); // reduce multiple spaces to a single space

		// Table Values: Count & Share
		let wcCount = 0;
		if (toCount === "words") wcCount = getWordCount(content);
		if (toCount === "chars") wcCount = getCharacterCount(content);

		if (cumulativeShare) share += (wcCount / target);
		else share = (wcCount / target);

		// Status
		let status = section.status;
		if (!status) status = " ";

		// Section numbering
		const isSubsection = section.file.name.startsWith(subsectionStartChar);
		let sectionNumbering;
		let sectionLink;
		if (isSubsection) {
			subsectionCounter++;
			sectionNumbering = "<small>" + sectionCounter.toString() + "." + subsectionCounter.toString() + "</small>";
			sectionLink =
				"<small>[["
				+ section.file.path
				+ "|"
				+ section.file.name.slice(1)
				+ "]]</small>";
		} else {
			subsectionCounter = 0;
			sectionCounter++;
			sectionNumbering = sectionCounter.toString().strong();
			sectionLink = "__" + section.file.link + "__";
		}

		// Most Recent Note
		if (section.file.name === mostRecentNote) sectionLink += "&nbsp;&nbsp;&nbsp;" + mostRecentIcon;

		// push table values
		output.push([
			sectionNumbering,
			sectionLink,
			wcCount,
			share,
			taskStr,
			status
		]);

		// add to totals & bibliography calculation
		totalTasks += taskNum;
		total += wcCount;
		if (includeBibliographyEstimate) completeText += content;
	}

	// Add Subsections counts to the sections
	//-------------------------------------------------

	if (groupedCount) {
		let upperSectionID = -1;
		for (var i = 0; i < output.length; i++) {
			let isSubsection = output[i][0].includes(".");
			let firstSectionFound = (upperSectionID !== -1);

			if (!firstSectionFound && isSubsection) continue;

			if (isSubsection) {
				output[upperSectionID][2] += output[i][2]; // add count
				if (!cumulativeShare) output[upperSectionID][3] += output[i][3]; // add share
			}

			if (!isSubsection) upperSectionID = i;
		}

		output.map(row => {
			row[2] = insert1000sep(row[2]);
			row[3] = toPercentStr(row[3]);

			let isSubsection = row[0].includes(".");
			if (isSubsection) {
				row[2] = "<small>" + row[2] + "</small>";
				if (!cumulativeShare) row[3] = "<small>" + row[3] + "</small>";
			} else {
				row[2] = "<u>" + row[2] + "</u>" ;
				if (!cumulativeShare)  row[3] = "<u>" + row[3] + "</u>";
			}
			return row;
		});
	}

	//-------------------------------------------------
	// OVERALL
	//-------------------------------------------------

	// Bibliography Estimate
	if (includeBibliographyEstimate) {
		const citationCount = countPandocCitations(completeText);
		let wcCount = 0;
		if (toCount === "words") wcCount = citationCount * wordsPerCitation;
		if (toCount === "chars") wcCount = citationCount * charsPerCitation;
		if (!charactersIncludeSpaces && toCount === "chars") wcCount = citationCount * (charsPerCitation - 20); // eslint-disable-line no-magic-numbers

		if (cumulativeShare === "true") share += (wcCount / target);
		else share = (wcCount / target);

		output.push([
			"",
			"Bibliography (" + citationCount + " citations)",
			"~" + insert1000sep(wcCount),
			toPercentStr(share),
			naChar,
			naChar
		]);

		total += wcCount;
	}

	// Totals calculation
	const totalShare = total / target;
	let totalTitle = "Total";
	if (wordsPerPage && toCount === "words") {
		const totalPages = (total / wordsPerPage).toFixed(1);
		totalTitle += "&nbsp;&nbsp;&nbsp;(~" + totalPages + " Pages)";
	}
	if (charsPerPage && toCount === "chars") {
		const totalPages = (total / charsPerPage).toFixed(1);
		totalTitle += "&nbsp;&nbsp;&nbsp;(~" + totalPages + " Pages)";
	}
	output.push([
		"",
		totalTitle.strong(),
		insert1000sep(total).strong(),
		toPercentStr(totalShare).strong(),
		totalTasks.toString().strong(),
		naChar.strong()
	]);

	// Target & Progress Bar
	const progressBar =
		"&nbsp;&nbsp;&nbsp;&nbsp;"
		+ " <progress max=\"100\" value=\""
		+ (totalShare * 100).toFixed().toString()
		+ "\"> </progress>";

	output.push([
		"",
		"Target".strong() + progressBar,
		insert1000sep(target).strong(),
		naChar.strong(),
		naChar.strong(),
		naChar.strong()
	]);

	return output;
}

//----------------------------------------------------
// Main
//----------------------------------------------------

// Print Table
let numExcludedNotes = 0;
let numExcludeStatus = 0;
let countedEntity = "Words";
if (toCount === "chars") countedEntity = "Chars";
let typeOfShare = "Share";
if (cumulativeShare === "true") countedEntity = "Target";

const tcontent = await getTableContents();
dv.table(["⟡", "Section", countedEntity, typeOfShare, "Tasks" ,"Status"], tcontent);

// Append Settings Info
let settingFt = "Footnotes excluded. ";
let settingExcludeTag = "";
let settingExcludeStatus = "";
let settingBibliography = "";
let settingCharSpaces = "Character Count includes Spaces. ";
let settingComments = "Comments included. ";
let settingPages = "";

if (includeFootnotes) settingFt = "Footnotes included. ";
if (!includeBibliographyEstimate) settingBibliography = "Bibliography excluded. ";
if (!charactersIncludeSpaces) settingCharSpaces = "Character Count without Spaces. ";
if (toCount === "words") settingCharSpaces = "";
if (excludeComments) settingComments = "Comments excluded. ";
if (wordsPerPage && toCount === "words") settingPages = "Assuming " + wordsPerPage.toString() + " words per page. ";
if (wordsPerPage && toCount === "chars") settingPages = "Assuming " + charsPerPage.toString() + " characters per page. ";

if (numExcludeStatus) {
	let plural = "s";
	if (numExcludeStatus === 1) plural = "";
	const excludedQuery = "\"status: exclude\" path:(" + sourceFolder + ")";
	settingExcludeStatus =
		"[" + numExcludeStatus.toString() + " Section" + plural + "]"
		+ "(obsidian://search?query=" + encodeURIComponent(excludedQuery) + ")"
		+ " with the status \"exclude\" omitted. ";
}

if (numExcludedNotes) {
	let plural = "s";
	if (numExcludedNotes === 1) plural = "";
	const excludedQuery = "tag:" + excludeTag + " path:(" + sourceFolder + ")";
	settingExcludeTag =
		"[" + numExcludedNotes.toString() + " Section" + plural + "]"
		+ "(obsidian://search?query=" + encodeURIComponent(excludedQuery) + ")"
		+ " tagged with " + excludeTag
		+ " omitted. ";
}

dv.span(
	"<small>"
	+ "Settings: ".strong()
	+ settingExcludeStatus
	+ settingExcludeTag
	+ settingFt
	+ settingBibliography
	+ settingComments
	+ settingCharSpaces
	+ settingPages
	+ "</small>"
);

Things I have tried

I tried to figure out how this code works, but I could only delete the “Tasks” column, but the values simply got mixed up from one to another… I looked for many solutions, I wanted to do it through regular Dataview code, but I found information about that it is not available yet. I found the code for DataviewJS, but I didn’t understand anything in it…

Thanks in advance to anyone who tries to help

Oh, and it would also be cool to move the sum totals to the top.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.