Migrating from zkn3 to obsidian, using R

This is an alternative approach to the exporting problem also discussed in this post, but for a Windows system and with a different focus.

Up to now, my knowledge management system has been Daniel Lüdecke’s Zettelkasten software, also known as ZKN3. I have rarely used links between notes, but sequences (Folgezettel), tags (Schlagwörter) and the integration of a bibtex file are core elements of my system. As of yet, I have too little knowledge of Obsidian, but I believe that I have more or less solved how to get my Zettels to Obsidian including their sequences and the tags. The problem with the latter is that the tags in ZKN3 are much more flexible than tags in Obsidian (you can use any sequence of characters as a ZKN3 tag).

I exported all my notes to md files. Then I applied the R script below. Any feedback is welcome. (You can of course delete all the “print” lines, they are for debugging.

library(stringr)
library(fs)

# This function should make sure that every file is edited.
process_file <- function(file_path) {
  # Read the file content
  content <- readLines(file_path, encoding = "UTF-8")
  
  # Defining signal words between you will find the list of tags
  first_signal <- "## Schlagwörter" # Since I use the German version of ZKN, tags are Schlagwörter
  second_signal <- if (any(grepl("## Folgezettel", content))) {
    "## Folgezettel"  # and sequence notes are Folgezettel
  } else {
    "Erstellt am"
  }
  
  # Find the positions of signal words, use the first match only
  first_signal_pos <- grep(first_signal, content)[1]
  second_signal_pos <- grep(second_signal, content)[1]
  
  # Check whether positions have been found correctly 
  print(paste("First signal position:", first_signal_pos))
  print(paste("Second signal position:", second_signal_pos))
  
  # If no signal words are found, skip the file 
  if (is.na(first_signal_pos) || is.na(second_signal_pos)) {
    cat("Signalwörter nicht gefunden, überspringe Datei:", file_path, "\n")
    return()
  }
  
  # Extract the relevant part between signal words 
  lines_to_modify <- content[(first_signal_pos + 1):(second_signal_pos - 1)]
  
  # Check the lines to be modified 
  print("Lines to modify:")
  print(lines_to_modify)
  
  # Modify lines that contain text
  modified_lines <- sapply(lines_to_modify, function(line) {
    if (nchar(trimws(line)) == 0) return(line)  # Leere Zeilen überspringen
    
    line <- gsub(" ", "_", line)               # Replace all blanks with "_" 
    line <- paste0("#", line)                  # Add # at the beginning of the line (which is at the beginning of the tag words
    line <- gsub("#/_", "#xx/", line)           # Some of my tags started with a slash, so I replaced them
    line <- gsub(":_", "/", line)              # replace ":_" with "/" 
    line <- gsub(";_", "_-_", line)              # replace ";_" with "/" 
    line <- gsub("\\(", "/", line)             # replace "(" with "/" 
    line <- gsub("\\)", "", line)              # delete ")" 
    line <- gsub("vs.", "vs", line)              # some tags contain a period, this needs to be deleted
    line <- gsub("'", "", line)              # same for apostrophe
    line <- gsub("/_", "/", line)              # more things that need to be replaced by a slah
    line <- gsub(",_", "/", line)              # more things that need to be replaced by a slah
    
    return(line)
  }, USE.NAMES = FALSE)
  
  # Check the modified lines
  print("Modified lines:")
  print(modified_lines)
  
  # update the file content
  content[(first_signal_pos + 1):(second_signal_pos - 1)] <- modified_lines
  
  # If the second signal word is "## Folgezettel" apply specific editing
  if (second_signal == "## Folgezettel" && (second_signal_pos + 2) <= length(content)) {
    folgezettel_line <- content[second_signal_pos + 2]
    matches <- gregexpr("\\b\\d+\\b", folgezettel_line)
    regmatches(folgezettel_line, matches) <- lapply(regmatches(folgezettel_line, matches), function(x) {
      paste0("[[zkn_", x, "]]")
    })
    
    # Update the edited line in the file
    content[second_signal_pos + 2] <- folgezettel_line
    
    # Check the line after editing
    print("Line after processing for ## Folgezettel:")
    print(content[second_signal_pos + 2])
  }
  
  # New filename - let it start with "zkn_" and end before the first blank
  new_file_name <- strsplit(basename(file_path), " ")[[1]][1]
  new_file_name <- paste0("zkn_", new_file_name, ".md")
  new_file_path <- file.path(dirname(file_path), new_file_name)
  
  # Check the new filename
  print(paste("Neuer Dateiname:", new_file_path))
  
  # Save the file
  writeLines(content, new_file_path)
  
  cat("File saved:", new_file_path, "\n")
}

# The directory containing the files
directory_path <- "enter your directory here"

# Read the files with file extension .md in that directory
files <- dir_ls(directory_path, regexp = "\\.md$")

# Edit all files
lapply(files, process_file)

Then copy the files into a folder in your Obsidian directory and start Obsidian.

Some manual editing of the tags will be necessary, depending on how you used tags in ZKN. Use the community extension Tag Wrangler to edit the tags.

This seems to work quite well.

What I am still missing is the simple way that I could insert citations from a simple bibtex file in ZKN3.

How interesting.
I started to look at this problem only a few days ago and like you used the original post as my starting point with the intention of doing a version for Windows. I use the English language version of Zkn3 and am currently happy with using it for my Zettelkasten. However as part of my disaster planning, I wanted an alternative to migrate to in case of future problems and Obsidian looked like a good option. I have redcorded my progress in my blog here Zettelkasten Zkn3 English Language Manual - Part 4: Doomsday Planning — History of Military Logistics (hgwdavie.com).

I felt that the original post did not cover all the possible links between Notes (Zettel) and so sought a wider scope. To my mind the export from Zkn3 to Obsidian should cover the following features and needs to change the following:

  1. Rename Keywords (Schlagwörter) to the Obsidian format as Tags #Keyword-name which seems to work quite well as described in the original post using Zkn3
  2. Export to Markdown as individual files using Zkn3
  3. Rename Note (Zettels) file to just a number [[3]] which I have done in Visual Studio to enable links to function in Obsidian
  4. Rename Note Links within the Main Text Body from [z 123]text[/z] to [[123]]
  5. Rename Image Links within the Main Text Body so that they point to a file (to be done)
  6. Rename Attachment Links witin the Main Text Body so that they point to a file (to be done)
  7. Rename Note sequences (Folgezettel) under the Heading ##Note Sequences (blank line) 1, 2, 3, 4, 5, 6, to make them links [[1]] [[2]] [[3]] [[4]] [[5]] [[6]] (in progress)
  8. Rename Cross References under the Heading ##Cross references (blank line) 1, 2, 3, 4, 5, 6, to make them links [[1]] [[2]] [[3]] [[4]] [[5]] [[6]] (in progress)
  9. Make sure that weblinks transfer across entact (to be done)

Currently I am working on item 8 and have this expression to find the Heading “Cross references” the blank line underneath and the line of numbers separated by commas underneath that. But I cannot find an expression to remove the commas and insert [[ and ]] around the numbers on that third line only.

I am no programmer and part of my approach was to find a way that could be used by anyone fairly simply.

I am afraid that I have no experience of ‘R’.

Images
The Zkn3 Text for images is: ![Bild](image name.jpg)

Attachments
Have a heading, blank line and third line contains the file name so

Attachments

document title.pdf