blackfriday/v2でlinkにrel="noreferrer noopener", target="_blank"を付与する

# 説明
WordPressからオレオレブログ開発&移行しているのですが、blackflidayをv2にして表題の件を試みました。問題はほぼ無いのですが、特定状況下でハマったので合わせてメモしておきます。

# 環境
- Windows10
- Visual Studio Code
- go version go1.15.5 windows/amd64

# Code
```go
package main

import (
    // ...省略
	"github.com/russross/blackfriday/v2"
)

func main() {
    // ...省略

	htmlFlag := blackfriday.NoopenerLinks | blackfriday.NoreferrerLinks | blackfriday.HrefTargetBlank
	renderer := blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{Flags: htmlFlag})
	html := blackfriday.Run([]byte(mdText), blackfriday.WithRenderer(renderer))

    // ...省略
}
```

blackfriday/v2に関してはこれで終わりです。HtmlFlagsをどう設定するのかよくわからなかったのですが(英語プアです)、

<https://pkg.go.dev/gopkg.in/russross/blackfriday.v2#pkg-constants>

ビット演算のようです。10年以上ぶりですね。もうよく覚えていません。

# (おまけ)ハマる
WordPressを現運用していると同時に開発テスト用ニューサイトも同時に動かしているので、WordPressに一件エントリを追加したらNEWサイトのmongodbにも移行するようなツールを用意しています。長いのでたたみます。

<details><summary>オレオレブログ移行ツール</summary><div>

```go
package main

import (
	"context"
	"database/sql"
	"log"
	"os"
	"strings"

	_ "github.com/go-sql-driver/mysql"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

// WpPost struct
type WpPost struct {
	PostDate            string
	PostTitle           string
	PostName            string
	PostContentFiltered string
	postModified        string
	Slug                string
}

// MongoEntry struct
type MongoEntry struct {
	EntryID     int32    `bson:"entryId"`
	EntryCode   string   `bson:"entryCode"`
	PublishDate string   `bson:"publishDate"`
	Title       string   `bson:"title"`
	Content     string   `bson:"content"`
	Category    []string `bson:"category"`
	IsPublished int32    `bson:"isPublished"`
	AuthorID    int32    `bson:"authorId"`
	CreatedAt   string   `bson:"createdAt"`
	UpdatedAt   string   `bson:"updatedAt"`
}

func replaceContentString(val string) string {
	rep := strings.NewReplacer("&gt;", ">", "&lt;", "<", "&quot;", "\"", "&#039;", "=", "&amp;", "&", "\r\n", "\n") // CRLFでblackfridayの挙動がおかしくなるので"\r\n", "\n"を追加
	return rep.Replace(val)
}

func main() {
	// args
	if len(os.Args) < 2 {
		println("invalid args.")
		return
	}
	postName := os.Args[1]
	// mysql
	db, err := sql.Open("mysql", "user:pass@tcp(127.0.0.1:3306)/dobusarai_blog")
	if err != nil {
		log.Fatal(err.Error())
	}
	defer db.Close()
	// get wp-post data with category names
	rows, err := db.Query(`select wp.post_date, wp.post_title, wp.post_name, wp.post_content_filtered, wp.post_modified, wt.slug 
from wp_posts wp
inner join wp_term_relationships wtr on (wp.ID=wtr.object_id)
inner join wp_terms wt on (wtr.term_taxonomy_id=wt.term_id)
where wp.post_name = '` + postName + "'")
	if err != nil {
		log.Fatal(err.Error())
	}
	defer rows.Close()
	// create data for mongodb
	idx := 0
	var category []string
	var entry MongoEntry
	for rows.Next() {
		var post WpPost
		err := rows.Scan(&post.PostDate, &post.PostTitle, &post.PostName, &post.PostContentFiltered, &post.postModified, &post.Slug)
		if err != nil {
			log.Fatal(err.Error())
		}
		if idx == 0 {
			entry = MongoEntry{
				EntryCode:   post.PostName,
				PublishDate: post.PostDate,
				Title:       post.PostTitle,
				Content:     replaceContentString(post.PostContentFiltered),
				IsPublished: 1,
				AuthorID:    1,
				CreatedAt:   post.postModified,
				UpdatedAt:   post.postModified,
			}
		}
		category = append(category, post.Slug)
		idx++
	}
	if entry.EntryCode == "" {
		log.Fatal("entry not found")
	}
	// categoryまとめたのを代入
	entry.Category = category
	// mongodb
	ctx := context.Background()
	credential := options.Credential{
		AuthSource: "doblog",
		Username:   "user",
		Password:   "pass",
	}
	client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://127.0.0.1:27017").SetAuth(credential))
	if err != nil {
		log.Fatal(err.Error())
	}
	// entryIdの最大値を取得(entryIdを降順にソートして1件取得)
	entries := client.Database("doblog").Collection("entries")
	findOption := options.Find().SetSort(bson.D{{Key: "entryId", Value: -1}}).SetLimit(1)
	cur, err := entries.Find(ctx, bson.D{{}}, findOption)
	if err != nil {
		log.Fatal(err.Error())
	}
	for cur.Next(ctx) {
		var result MongoEntry
		err := cur.Decode(&result)
		if err != nil {
			log.Fatal(err)
		}
		entry.EntryID = result.EntryID + 1
	}
	// insert
	res, err := entries.InsertOne(ctx, entry)
	if err != nil {
		log.Fatal(err.Error())
	}
	log.Println(res)
}
```
</div></details>

とある一件のエントリだけがblackfridayで正しくhtml化されませんでした。答えはコードに書いてしまっているのですが、他の期待動作をするエントリはLFだったのにHTML化がうまく動作しなかったエントリの改行コードはCRLFでした。

```text:source
main.go

```go
package main
```

```text:正
<p>main.go</p>

<pre><code class="language-go">package main
```

```text:異常
main.go

<code>go
package main
```

WordPress(PHP)、オレオレブログ(go)の他に、Windows/CentOS, mysql/mongodb, c#(なにかツール作って楽したい時はほぼこれ)と適当にやっているので解決するのに時間がかかったという与太話です。趣味でやっているので時間がかかるのはいいのですが。特にCompass(mongodbのデスクトップアプリケーション)で、「ここの余計な改行を削除かなー」と直接編集したところサラっと期待動作になってしまって逆に慌てたのですが、Updateする際に改行コードを全てLFに変換しているみたいというオチです。