For this blog post, I'll be using Foundation .NET 5 (CMS 12) and EPPlus nuget package to generate XLSX files. Please note that EPPlus is not free for commercial use.

The code:

[ServiceConfiguration(ServiceType = typeof(DataExporterBase))]
public class CustomExcelDataExporter : XLSXDataExporter
{
    private readonly UrlResolver _urlResolver =
        ServiceLocator.Current.GetInstance<UrlResolver>();

    private readonly IContentLoader _contentLoader =
        ServiceLocator.Current.GetInstance<IContentLoader>();

    // "submitted from" column is language-specific
    private string _submittedFromColumnName = LocalizationService.Current
        .GetString("/episerver/forms/formdataview/hostedpage")
        .Replace(" ", "_");

    public override byte[] ExportBinary(DataTable dataTable)
    {
        var numArray = new byte[0];
        if (dataTable == null)
        {
            return numArray;
        }

        using var memoryStream = new MemoryStream();

        // set correct LicenseContext
        ExcelPackage.LicenseContext = LicenseContext.NonCommercial;

        using var package = new ExcelPackage(memoryStream);

        var sheet = package.Workbook.Worksheets.Add("My Sheet");
        sheet.Cells["A1"].LoadFromDataTable(dataTable, true);

        // optional: create a new style for hyperlinks
        var namedStyle = package.Workbook.Styles.CreateNamedStyle("HyperLink");
        namedStyle.Style.Font.UnderLine = true;
        namedStyle.Style.Font.Color.SetColor(Color.Blue);

        var pageLinkColumnIndex = GetPageLinkColumnIndex(dataTable);

        for (int i = 0; i < dataTable.Rows.Count; i++)
        {
            // fetch PageData and absolute URL from page id
            var (page, absoluteUrl) = FetchPageData(dataTable.Rows[i]);

            // replace page id with page link
            // and apply the HyperLink style
            var cell = sheet.Cells[i + 2, pageLinkColumnIndex + 1];
            cell.Hyperlink = absoluteUrl;
            cell.Value = page.Name;
            cell.StyleName = namedStyle.Name;
        }

        package.Save();

        return memoryStream.ToArray();
    }

    private (PageData page, Uri absoluteUrl) FetchPageData(DataRow dataRow)
    {
        string language = dataRow["SYSTEMCOLUMN_Language"].ToString();
        int pageId = int.Parse(dataRow[_submittedFromColumnName].ToString());

        if (!_contentLoader.TryGet(
            new ContentReference(pageId),
            new CultureInfo(language),
            out PageData page))
        {
            return (null, null);
        }

        var pageUrl = _urlResolver.GetUrl(
            page.ContentLink,
            language,
            new VirtualPathArguments
            {
                ContextMode = ContextMode.Default
            });

        var absoluteUrl = new Uri(SiteDefinition.Current.SiteUrl, pageUrl);

        return (page, absoluteUrl);
    }

    private int GetPageLinkColumnIndex(DataTable dataTable)
    {
        for (var index = 0; index < dataTable.Columns.Count; index++)
        {
            if (string.Equals(
                dataTable.Columns[index].ColumnName,
                _submittedFromColumnName,
                StringComparison.InvariantCultureIgnoreCase))
            {
                return index;
            }
        }

        return int.MinValue;
    }
}

And this is how it looks in action:

Enjoy!