Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/docs/annotation/metadata-pages.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Dataview automatically adds a large amount of metadata to each page. These impli
| `file.aliases` | List | A list of all aliases for the note as defined via the [YAML frontmatter](https://help.obsidian.md/How+to/Add+aliases+to+note). |
| `file.tasks` | List | A list of all tasks (I.e., `| [ ] some task`) in this file. |
| `file.lists` | List | A list of all list elements in the file (including tasks); these elements are effectively tasks and can be rendered in task views. |
| `file.tables` | List | A list of all table in this file. |
| `file.frontmatter` | List | Contains the raw values of all frontmatter in form of `key | value` text values; mainly useful for checking raw frontmatter values or for dynamically listing frontmatter keys. |
| `file.day` | Date | Only available if the file has a date inside its file name (of form `yyyy-mm-dd` or `yyyymmdd`), or has a `Date` field/inline field. |
| `file.starred` | Boolean | If this file has been bookmarked via the Obsidian Core Plugin "Bookmarks". |
Expand Down
42 changes: 39 additions & 3 deletions src/data-import/markdown-file.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/** Importer for markdown documents. */

import { extractFullLineField, extractInlineFields, parseInlineValue, InlineField } from "data-import/inline-field";
import { ListItem, PageMetadata } from "data-model/markdown";
import { ListItem, PageMetadata, TableItem } from "data-model/markdown";
import { Literal, Link, Values } from "data-model/value";
import { EXPRESSION } from "expression/parse";
import { DateTime } from "luxon";
Expand Down Expand Up @@ -78,6 +78,7 @@ export function parsePage(path: string, contents: string, stat: FileStats, metad
links,
lists: markdownData.lists,
fields: finalizeInlineFields(fields),
tables: markdownData.tables,
frontmatter: frontmatter,
ctime: DateTime.fromMillis(stat.ctime),
mtime: DateTime.fromMillis(stat.mtime),
Expand Down Expand Up @@ -136,11 +137,13 @@ export function parseMarkdown(
contents: string[],
metadata: CachedMetadata,
linksByLine: Record<number, Link[]>
): { fields: Map<string, Literal[]>; lists: ListItem[] } {
): { fields: Map<string, Literal[]>; lists: ListItem[], tables: TableItem[] } {
let fields: Map<string, Literal[]> = new Map();

// Extract task data and append the global data extracted from them to our fields.
let [lists, extraData] = parseLists(path, contents, metadata, linksByLine);
let tables = parseTables(contents, metadata);

for (let [key, values] of extraData.entries()) {
if (!fields.has(key)) fields.set(key, values);
else fields.set(key, fields.get(key)!!.concat(values));
Expand Down Expand Up @@ -176,7 +179,7 @@ export function parseMarkdown(
}
}

return { fields, lists };
return { fields, lists, tables };
}

// TODO: Consider using an actual parser in lieu of a more expensive regex.
Expand Down Expand Up @@ -304,6 +307,39 @@ export function parseLists(
return [Object.values(cache), literals];
}

/**
* Parse table from content and meta data. The implementation will not include column spans. It only work for
* one column/one cell value.
*/
export function parseTables(
content: string[],
metadata: CachedMetadata,
): TableItem[] {
const result: TableItem[] = [];
metadata.sections?.forEach(section => {
if (section.type === 'table') {
const startLine = section.position.start.line;
const endLine = section.position.end.line;

const headers = content[startLine]
.split('|')
.slice(1, -1)
.map(header => header.trim())
const rows = content
.slice(startLine + 2, endLine + 1)
.map(row => row
.split('|')
.slice(1, -1)
.map(val => val.trim()));

const json = TableItem.serialize(headers, rows);

result.push(new TableItem({ headers, rows, json }))
}
});
return result;
}

/** Attempt to find a date associated with the given page from metadata or filenames. */
function findDate(file: string, fields: Map<string, Literal>): DateTime | undefined {
for (let key of fields.keys()) {
Expand Down
44 changes: 44 additions & 0 deletions src/data-model/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,16 @@ export class PageMetadata {
/** The raw frontmatter for this document. */
public frontmatter: Record<string, Literal>;

public tables: TableItem[];

public constructor(path: string, init?: Partial<PageMetadata>) {
this.path = path;
this.fields = new Map<string, Literal>();
this.frontmatter = {};
this.tags = new Set<string>();
this.aliases = new Set<string>();
this.links = [];
this.tables = [];

Object.assign(this, init);

Expand Down Expand Up @@ -136,6 +139,7 @@ export class PageMetadata {
aliases: Array.from(this.aliases),
lists: this.lists.map(l => realCache.get(l.line)),
tasks: this.lists.filter(l => !!l.task).map(l => realCache.get(l.line)),
tables: this.tables,
ctime: this.ctime,
cday: stripTime(this.ctime),
mtime: this.mtime,
Expand Down Expand Up @@ -301,6 +305,46 @@ export class ListItem {
}
}

/** A table item to represent the table */
export class TableItem {
headers: string[];
rows: any[][];
json: Map<string, any>[];

constructor(init?: Partial<TableItem>) {
Object.assign(this, init);

this.headers = init?.headers || [];
this.rows = init?.rows || [];
this.json = init?.json || [];
}

// make table data into array of object
public static serialize(headers: string[], rows: any[][]): Map<string, any>[] {
// show header to empty value for empty rows.
if (rows.length === 0) {
return [{
headers,
rows,
}] as any;
}

const result: Map<string, any>[] = [];
rows.forEach(row => {
if (row.length === headers.length) {
// only include for row that has the same amount of column
const record = headers.reduce((prev, key, index) => {
prev[key] = row[index];
return prev;
}, {} as any);
result.push(record);
}
});

return result;
}
}

//////////////////////////////////////////
// Conversion / Serialization Utilities //
//////////////////////////////////////////
Expand Down
2 changes: 2 additions & 0 deletions src/data-model/serialized/markdown.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/** Serialized / API facing data types for Dataview objects. */

import { TableItem } from "data-model/markdown";
import { Link, Literal } from "data-model/value";
import { DateTime } from "luxon";
import { Pos } from "obsidian";
Expand All @@ -17,6 +18,7 @@ export interface SMarkdownPage {
aliases: string[];
lists: SListItem[];
tasks: STask[];
tables: TableItem[];
ctime: DateTime;
cday: DateTime;
mtime: DateTime;
Expand Down
5 changes: 5 additions & 0 deletions src/data-model/value.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { DateTime, Duration } from "luxon";
import { DEFAULT_QUERY_SETTINGS, QuerySettings } from "settings";
import { getFileTitle, normalizeHeaderForLink, renderMinimalDuration } from "util/normalize";
import { TableItem } from "./markdown";

/** Shorthand for a mapping from keys to values. */
export type DataObject = { [key: string]: Literal };
Expand Down Expand Up @@ -347,6 +348,10 @@ export namespace Values {
return val instanceof Link;
}

export function isTableItem(val: any): val is TableItem {
return val?.json?.length > 0;
}

export function isWidget(val: any): val is Widget {
return val instanceof Widget;
}
Expand Down
4 changes: 4 additions & 0 deletions src/ui/markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { renderMinimalDate, renderMinimalDuration } from "util/normalize";
import { currentLocale } from "util/locale";
import { DataArray } from "api/data-array";
import { extractImageDimensions, isImageEmbed } from "util/media";
import { TableGrouping } from "./views/table-view";

export type MarkdownProps = { contents: string; sourcePath: string };
export type MarkdownContext = { component: Component };
Expand Down Expand Up @@ -134,6 +135,9 @@ export function RawLit({
}

return <Markdown content={value.markdown()} sourcePath={sourcePath} />;
} else if (Values.isTableItem(value)) {
const { headers, rows } = value;
return <TableGrouping headings={headers} values={rows} sourcePath={sourcePath} />
} else if (Values.isHtml(value)) {
return <EmbedHtml element={value} />;
} else if (Values.isWidget(value)) {
Expand Down
17 changes: 17 additions & 0 deletions test-vault/example read tables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

```dataview
TABLE file.tables WHERE length(file.tables) > 0
```

```dataviewjs

const tables = dv.pages('"tables"').file.tables

// show table for the last 5 days
for (let table of tables) {
dv.table(
table.headers,
table.rows
)
}
```
15 changes: 15 additions & 0 deletions test-vault/tables/Other type of Table.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

## Header only tables

| Empty | Table |
| --- | --- |


## Table with empty value

| Head 1 | Head 2 | Head 3 |
| --- | --- | --- |
| item 1 | | item 3 |
| item 2 || item 4 |


5 changes: 5 additions & 0 deletions test-vault/tables/git commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

| Command | Description |
| --- | --- |
| `git status` | List all *new or modified* files |
| `git diff` | Show file differences that **haven't been** staged |