Example scripts
To homepage
Confluence

Bulk convert migrated built-in macros
App in script

ScriptRunner For Confluence
by Adaptavist
Compatibility

Confluence
Language |
groovy
import java.util.regex.Matcher
def spaceKeys = "ds,ds1" // Replace the spaceKeys to your own value
logger.info("Fetching spaces - ${spaceKeys}")
def spaces = Spaces.search("space.key in (${spaceKeys})")
spaces.each { space ->
logger.info("Fetching all pages for space - ${space.key}")
def pages = Spaces.getByKey(space.key).getAllPages(){
setBodyFormat("storage")
}
pages.each { page ->
logger.info("Extracting body contents for page '${page.title}'")
def storage = page.body.storage.value
if (!storage) {
logger.info("Not updating page '${page.title}' (empty body)")
return
}
def updatedMacro = replaceStructuredMacroAcName(storage)
updatedMacro = replaceEmptyAcNameForParameter(updatedMacro)
updatedMacro = convertMarkdownMacros(updatedMacro)
updatedMacro = convertCreatePageMacros(updatedMacro)
updatedMacro = convertVersionHistoryMacros(updatedMacro)
if (updatedMacro == storage) {
logger.info("Not updating page '${page.title}'")
return
}
logger.info("Updating page '${page.title}' with new body")
page.update(){
title = page.title
status = "current"
body = updatedMacro
}
logger.info("Page '${page.title}' has been updated.")
}
}
static def replaceStructuredMacroAcName(String storage) {
def SEARCH = ["ac:name=\"add-label\"", "ac:name=\"choose-label\"", "ac:name=\"page-info\"",
"ac:name=\"cql-search\""]
def REPLACEMENT = ["ac:name=\"sr-add-labels-macro\"", "ac:name=\"sr-choose-label-macro\"",
"ac:name=\"sr-page-info-macro\"", "ac:name=\"sr-cql-search-macro\""]
def modifiedStorage = storage
SEARCH.eachWithIndex { acName, index ->
modifiedStorage = modifiedStorage.replace(acName, REPLACEMENT[index])
}
modifiedStorage
}
static def replaceEmptyAcNameForParameter(String storage) {
def EMPTY_AC_FORMAT = "ac:name=\"\">"
def SEARCH = ["current-version", "title", "created-by", "created-date", "diffs", "labels", "modified-by",
"modified-date", "modified-users", "page-id", "participants", "versions"]
def REPLACED_AC_FORMAT = "ac:name=\"infoType\">"
def REPLACEMENT = ["Current version", "Title", "Created by", "Created date", "Diffs", "Labels", "Modified by",
"Modified date", "Modified users", "Page id", "Participants", "Versions"]
def modifiedStorage = storage
SEARCH.eachWithIndex { acName, index ->
def fullSearchString = EMPTY_AC_FORMAT + acName
def fullReplacementString = REPLACED_AC_FORMAT + REPLACEMENT[index]
modifiedStorage = modifiedStorage.replace(fullSearchString, fullReplacementString)
}
modifiedStorage
}
static def convertMarkdownMacros(String storage) {
def pattern = ~/(?s)(<ac:structured-macro[^>]*\bac:name=")markdown("[^>]*>)(.*?)(<\/ac:structured-macro>)/
storage.replaceAll(pattern) { full, prefix, suffix, body, close ->
def newBody = body
.replaceAll(
/(?s)(<ac:parameter ac:name="url">)\s*<ri:url\s+ri:value="([^"]+)"[^>]*\/?\s*>(?:\s*<\/ri:url>)?\s*(<\/ac:parameter>)/,
'$1$2$3'
)
.replaceAll(
/(<ac:parameter ac:name="url">)UrlResourceIdentifier\[url=([^\]]+)\](<\/ac:parameter>)/,
'$1$2$3'
)
.replaceAll(/(?s)<ac:plain-text-body>(?:<!\[CDATA\[)?(.*?)(?:\]\]>)?<\/ac:plain-text-body>/) { m, markdown ->
markdown ? "<ac:parameter ac:name=\"markdown\">${escapeXml(markdown)}</ac:parameter>" : ''
}
"${prefix}sr-markdown-macro${suffix}${newBody}${close}"
}
}
static def escapeXml(String value) {
value.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
}
static def convertCreatePageMacros(String storage) {
def pattern = ~/(?s)(<ac:structured-macro[^>]*\bac:name=")create-page("[^>]*>)(.*?)(<\/ac:structured-macro>)/
storage.replaceAll(pattern) { full, prefix, suffix, body, close ->
if (shouldSkipCreatePageConversion(body)) {
return full
}
"${prefix}sr-create-page-macro${suffix}${convertCreatePageBody(body)}${close}"
}
}
static def shouldSkipCreatePageConversion(String body) {
if (hasNonBlankCreatePageParam(body, "fromPage") || hasNonBlankCreatePageParam(body, "template")) {
return true
}
def parent = firstParamValue(body, "parent")
parent && !isCreatePageSelfParent(parent)
}
static def hasNonBlankCreatePageParam(String body, String name) {
def value = firstParamValue(body, name)
value != null && !value.isEmpty()
}
static def firstParamValue(String body, String name) {
def matcher = body =~ /(?s)<ac:parameter ac:name="${name}">(.*?)<\/ac:parameter>/
matcher.find() ? matcher.group(1).trim() : null
}
static def isCreatePageSelfParent(String parent) {
def normalized = parent.toLowerCase().trim()
if (normalized == '$self') {
return true
}
def colonIndex = normalized.indexOf(':')
if (colonIndex <= 0 || colonIndex >= normalized.length() - 1) {
return false
}
def spaceKey = normalized.substring(0, colonIndex).trim()
def title = normalized.substring(colonIndex + 1).trim()
spaceKey && title && !(spaceKey =~ /\s/) && title == '$self'
}
static def convertCreatePageBody(String body) {
def newBody = body
def label = null
newBody = newBody.replaceAll(/(?s)<ac:plain-text-body>(?:<!\[CDATA\[)?(.*?)(?:\]\]>)?<\/ac:plain-text-body>/) { m, text ->
def trimmed = text.trim()
if (trimmed) {
label = trimmed
}
''
}
["parent", "fromPage", "template", "prompt", "auiClass", "css", "suppressNotification"].each { name ->
newBody = newBody.replaceAll(/(?s)\s*<ac:parameter ac:name="${name}">.*?<\/ac:parameter>/, '')
}
newBody = newBody.replaceAll(/<ac:parameter ac:name="addspace">/, '<ac:parameter ac:name="addSpace">')
if (label) {
newBody = "<ac:parameter ac:name=\"label\">${escapeXml(label)}</ac:parameter>" + newBody
}
newBody
}
static def convertVersionHistoryMacros(String storage) {
def pattern = ~/(?s)(<ac:structured-macro[^>]*\bac:name=")version-history("[^>]*>)(.*?)(<\/ac:structured-macro>)/
storage.replaceAll(pattern) { full, prefix, suffix, body, close ->
def page = (firstParamValue(body, "page") ?: "").toLowerCase().trim()
def isParent = page == '$parent'
def isSelf = !page || page == '$self'
if (page && !isSelf && !isParent) {
return Matcher.quoteReplacement(full)
}
def pageJson = escapeXml('{"type":"' + (isParent ? 'parent' : 'self') + '"}')
def newBody = body.replaceAll(/(?s)\s*<ac:parameter ac:name="page">.*?<\/ac:parameter>/, '')
Matcher.quoteReplacement(
prefix + "sr-version-history-macro" + suffix +
'<ac:parameter ac:name="page">' + pageJson + '</ac:parameter>' +
newBody + close
)
}
}
Having an issue with this script?
Report it here