Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 10583] ファイルの概要

このコミットは、Go言語の html パッケージにおけるHTMLパーサーの挙動を改善するものです。具体的には、<head> タグと <body> タグの間に存在する空白文字を適切に処理し、また </head> の後に誤って出現する <head> タグを無視するように変更されています。これにより、より堅牢で標準に準拠したHTMLパースが可能になります。

コミット

commit a5d300862b683e6a6d0e503c213d191155d1f63b
Author: Andrew Balholm <andybalholm@gmail.com>
Date:   Fri Dec 2 11:46:24 2011 +1100

    html: allow whitespace between head and body
    
    Also ignore <head> tag after </head>.
    
    Pass tests6.dat, test 0:
    <!doctype html></head> <head>
    
    | <!DOCTYPE html>
    | <html>
    |   <head>
    |   " "
    |   <body>
    
    Also pass tests through test 6:
    <body>
    <div>
    
    R=nigeltao
    CC=golang-dev
    https://golang.org/cl/5447064

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a5d300862b683e6a6d0e503c213d191155d1f63b

元コミット内容

html: allow whitespace between head and body Also ignore <head> tag after </head>.

Pass tests6.dat, test 0: <!doctype html></head> <head>

| <!DOCTYPE html> | <html> | <head> | " " | <body>

Also pass tests through test 6: <body> <div>

変更の背景

HTMLのパースは、ウェブブラウザがウェブページを表示するために不可欠なプロセスです。しかし、HTMLの仕様は非常に柔軟であり、多くの「不正な」HTMLもブラウザによって寛容に解釈されます。この寛容性は、開発者が厳密なHTMLを書かなくてもウェブページが表示されるという利点がある一方で、パーサーの実装を複雑にします。

このコミットの背景には、主に以下の2つの問題がありました。

  1. <head><body> 間の空白文字の扱い: HTML5のパース仕様では、<head> 要素の終了タグ </head> の直後に空白文字が続き、その後に <body> 要素が続く場合、その空白文字は <body> のコンテンツとして扱われるべきです。しかし、以前のパーサーではこの空白文字が適切に処理されず、パース結果が期待と異なる可能性がありました。これは、ブラウザの挙動との不一致を引き起こし、Goの html パッケージが生成するDOMツリーが、実際のブラウザのレンダリングと異なる原因となる可能性がありました。

  2. </head> 後の不正な <head> タグの扱い: HTMLの構造上、<head> 要素は文書のメタデータを含む部分であり、通常は <body> 要素の前に一度だけ出現します。</head> の後に再度 <head> タグが出現することは不正なHTMLですが、ブラウザはこのような場合でもエラーとせず、通常は後続の <head> タグを無視してパースを続行します。Goの html パッケージも同様の寛容性を持つべきであり、不正なタグを適切に無視することで、より多くの現実世界のHTMLドキュメントを正確にパースできるようになります。

これらの変更は、Goの html パッケージがより多くの「現実世界」のHTMLドキュメントを、主要なウェブブラウザが解釈するのと同様の方法で正確にパースできるようにするために行われました。これにより、HTML処理の堅牢性と互換性が向上します。

前提知識の解説

HTMLの基本構造

HTMLドキュメントは、主に以下の要素で構成されます。

  • <!DOCTYPE html>: ドキュメントタイプ宣言。HTML5ドキュメントであることを示します。
  • <html>: HTMLドキュメントのルート要素。
  • <head>: ドキュメントのメタデータ(タイトル、文字エンコーディング、スタイルシート、スクリプトなど)を含むセクション。ブラウザには表示されません。
  • <body>: ドキュメントの可視コンテンツ(テキスト、画像、リンクなど)を含むセクション。ブラウザに表示されます。

HTML5のパースアルゴリズム

HTML5のパースアルゴリズムは、非常に詳細かつ複雑な仕様であり、ウェブブラウザがどのようにHTMLを解釈し、DOMツリーを構築するかを定義しています。このアルゴリズムは、エラー耐性(error handling)に重点を置いており、不正なHTMLに対しても可能な限りDOMツリーを構築しようとします。

重要な概念として「挿入モード (Insertion Mode)」があります。パーサーは、現在のトークンと現在の挿入モードに基づいて、次のアクションを決定します。例えば、<head> 要素のパース中は「"in head" 挿入モード」にあり、<body> 要素のパース中は「"in body" 挿入モード」にあります。

このコミットで関連するのは、「"after head" 挿入モード」です。これは、</head> タグが処理された直後の状態を指します。このモードでは、次にどのようなトークンが来るかによって、パーサーの挙動が異なります。

Go言語の html パッケージ

Go言語の標準ライブラリには、HTMLのパースとレンダリングを行うための html パッケージが含まれています。このパッケージは、HTML5のパースアルゴリズムに準拠することを目指しており、ウェブスクレイピング、HTMLテンプレート処理、HTMLのサニタイズなど、様々な用途で利用されます。

html パッケージのパーサーは、入力されたHTMLをトークン(タグ、テキスト、コメントなど)に分解し、それらのトークンを基にDOM (Document Object Model) ツリーを構築します。DOMツリーは、HTMLドキュメントの論理的な構造を表現するツリー構造です。

TextTokenStartTagToken

  • TextToken: HTMLドキュメント内のテキストコンテンツを表すトークンです。例えば、<p>Hello</p>HelloTextToken になります。
  • StartTagToken: HTML要素の開始タグを表すトークンです。例えば、<p><div> などです。

impliedframesetOK

Goの html パッケージのパーサー内部で使われるフラグで、HTML5のパースアルゴリズムにおける特定の状態や挙動を制御します。

  • implied: 特定の要素が明示的に記述されていないが、HTMLの構造上存在すると見なされる場合に設定されるフラグです。例えば、<html><body> タグが省略されていても、パーサーはこれらが存在すると「暗黙的に」解釈します。
  • framesetOK: frameset 要素が許可されるかどうかを示すフラグです。HTML5では frameset は非推奨ですが、古いHTMLのパースにおいては考慮されることがあります。

技術的詳細

このコミットの技術的詳細は、src/pkg/html/parse.go 内の afterHeadIM 関数に集約されています。この関数は、パーサーが "after head" 挿入モードにあるときに呼び出されます。

afterHeadIM 関数の役割

afterHeadIM 関数は、</head> タグが処理された直後に、次に現れるトークン(p.tok.Type)の種類に応じて、パーサーの挙動を制御します。

変更点とHTML5パース仕様への準拠

  1. <head><body> 間の空白文字の処理:

    • 変更前は、TextToken が来た場合、単純に implied = trueframesetOK = true を設定していました。
    • 変更後は、TextToken のデータ(p.tok.Data)から先頭の空白文字(whitespace)をトリムする処理が追加されました。
    • もし先頭に空白文字が存在した場合、その空白文字は現在のノード(この場合は head 要素の後に続くテキストノード)に追加されます。
    • その後、残りの非空白文字が p.tok.Data に再設定され、パースが続行されます。これにより、</head> <head> のようなケースで、</head><head> の間の空白が適切に処理され、DOMツリーに反映されるようになります。HTML5の仕様では、after head 挿入モードで空白文字が来た場合、それは body 要素のコンテンツとして扱われるため、この変更はその挙動に近づけるものです。
  2. </head> 後の不正な <head> タグの無視:

    • StartTagToken でタグ名が "head" の場合、変更前は // TODO. とコメントされており、適切な処理が実装されていませんでした。
    • 変更後は、// Ignore the token. とコメントされ、return true が追加されました。これは、この <head> トークンを単に無視し、パースを続行することを意味します。HTML5のパース仕様では、after head 挿入モードで <head> 開始タグが来た場合、それは無視されるべきとされています。

これらの変更により、Goの html パッケージは、HTML5のパースアルゴリズムにおける特定のコーナーケース(特に不正なHTMLや、ブラウザが寛容に扱うHTML)に対して、より正確かつ堅牢な挙動を示すようになりました。

コアとなるコードの変更箇所

src/pkg/html/parse.go

--- a/src/pkg/html/parse.go
+++ b/src/pkg/html/parse.go
@@ -515,7 +515,19 @@ func afterHeadIM(p *parser) bool {
 	im.implied    bool
 	)
 	switch p.tok.Type {
-	case ErrorToken, TextToken:
+	case ErrorToken:
+		im.implied = true
+		framesetOK = true
+	case TextToken:
+		s := strings.TrimLeft(p.tok.Data, whitespace)
+		if len(s) < len(p.tok.Data) {
+			// Add the initial whitespace to the current node.
+			p.addText(p.tok.Data[:len(p.tok.Data)-len(s)])
+			if s == "" {
+				return true
+			}
+			p.tok.Data = s
+		}
 		im.implied = true
 		framesetOK = true
 	case StartTagToken:
@@ -535,7 +547,8 @@ func afterHeadIM(p *parser) bool {
 			defer p.oe.pop()
 			return inHeadIM(p)
 		case "head":
-			// TODO.
+			// Ignore the token.
+			return true
 		default:
 			im.implied = true
 			framesetOK = true

src/pkg/html/parse_test.go

--- a/src/pkg/html/parse_test.go
+++ b/src/pkg/html/parse_test.go
@@ -167,6 +167,7 @@ func TestParser(t *testing.T) {
 		{"tests3.dat", -1},
 		{"tests4.dat", -1},
 		{"tests5.dat", -1},
+		{"tests6.dat", 7},
 	}
 	for _, tf := range testFiles {
 		f, err := os.Open("testdata/webkit/" + tf.filename)

コアとなるコードの解説

src/pkg/html/parse.go の変更点

afterHeadIM 関数内の switch p.tok.Type ブロックが変更されています。

  1. TextToken の処理の追加:

    • 以前は ErrorToken と同じ case で処理されていましたが、TextToken 専用の処理が追加されました。
    • s := strings.TrimLeft(p.tok.Data, whitespace): 現在のトークンデータ p.tok.Data の先頭から空白文字を削除し、その結果を s に格納します。whitespace は、Goの html パッケージ内で定義されている空白文字の集合です。
    • if len(s) < len(p.tok.Data): もし元のトークンデータに先頭の空白文字が含まれていた場合(つまり、s の長さが元のデータより短い場合)に以下の処理を実行します。
      • p.addText(p.tok.Data[:len(p.tok.Data)-len(s)]): 削除された空白文字部分(p.tok.Data の先頭から len(p.tok.Data)-len(s) までの部分)を、現在のノードにテキストとして追加します。これにより、</head><body> の間の空白がDOMツリーに正しく反映されます。
      • if s == "" { return true }: もし空白文字をトリムした結果、トークンデータが完全に空になった場合(つまり、トークンが空白文字のみで構成されていた場合)、そのトークンの処理を終了し、次のトークンに進みます。
      • p.tok.Data = s: トリムされた非空白文字部分を、現在のトークンデータとして再設定します。これにより、後続のパース処理が正しいデータで行われます。
    • im.implied = trueframesetOK = true は、TextToken の処理後も引き続き設定されます。
  2. StartTagToken での "head" タグの処理:

    • case "head": のブロックが変更されました。
    • // Ignore the token. とコメントが追加され、return true が記述されました。これは、</head> の後に <head> 開始タグが出現した場合、そのタグを無視して、現在の挿入モード("after head")を維持し、次のトークンの処理に進むことを意味します。これにより、不正なHTMLに対するパーサーの寛容性が向上します。

src/pkg/html/parse_test.go の変更点

  • TestParser 関数内の testFiles スライスに、{"tests6.dat", 7} という新しいテストケースが追加されました。
  • これは、tests6.dat というテストデータファイルを使用し、特定のテスト(おそらくインデックス7のテスト)を実行することを意味します。このテストケースは、おそらく <head><body> 間の空白文字の処理や、</head> 後の不正な <head> タグの無視といった、このコミットで修正された挙動を検証するためのものです。

これらの変更により、Goの html パッケージは、より多くの現実世界のHTMLドキュメントを、主要なウェブブラウザが解釈するのと同様の方法で正確にパースできるようになり、堅牢性と互換性が向上しました。

関連リンク

参考にした情報源リンク

  • 上記のHTML5 Parsing Algorithmの仕様書
  • Go言語の html パッケージのソースコード
  • 一般的なHTMLパースに関する知識
  • コミットメッセージに記載されているテストケースの例
  • https://golang.org/cl/5447064 (Goのコードレビューシステムへのリンク)

[インデックス 10583] ファイルの概要

このコミットは、Go言語の html パッケージにおけるHTMLパーサーの挙動を改善するものです。具体的には、<head> タグと <body> タグの間に存在する空白文字を適切に処理し、また </head> の後に誤って出現する <head> タグを無視するように変更されています。これにより、より堅牢で標準に準拠したHTMLパースが可能になります。

コミット

commit a5d300862b683e6a6d0e503c213d191155d1f63b
Author: Andrew Balholm <andybalholm@gmail.com>
Date:   Fri Dec 2 11:46:24 2011 +1100

    html: allow whitespace between head and body
    
    Also ignore <head> tag after </head>.
    
    Pass tests6.dat, test 0:
    <!doctype html></head> <head>
    
    | <!DOCTYPE html>
    | <html>
    |   <head>
    |   " "
    |   <body>
    
    Also pass tests through test 6:
    <body>
    <div>
    
    R=nigeltao
    CC=golang-dev
    https://golang.org/cl/5447064

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a5d300862b683e6a6d0e503c213d191155d1f63b

元コミット内容

html: allow whitespace between head and body Also ignore <head> tag after </head>.

変更の背景

HTMLのパースは、ウェブブラウザがウェブページを表示するために不可欠なプロセスです。しかし、HTMLの仕様は非常に柔軟であり、多くの「不正な」HTMLもブラウザによって寛容に解釈されます。この寛容性は、開発者が厳密なHTMLを書かなくてもウェブページが表示されるという利点がある一方で、パーサーの実装を複雑にします。

このコミットの背景には、主に以下の2つの問題がありました。

  1. <head><body> 間の空白文字の扱い: HTML5のパース仕様では、<head> 要素の終了タグ </head> の直後に空白文字が続き、その後に <body> 要素が続く場合、その空白文字はDOMツリーにおいてテキストノードとして表現され、<body> のコンテンツとして扱われるべきです。以前のパーサーではこの空白文字が適切に処理されず、パース結果が期待と異なる可能性がありました。これは、ブラウザの挙動との不一致を引き起こし、Goの html パッケージが生成するDOMツリーが、実際のブラウザのレンダリングと異なる原因となる可能性がありました。

  2. </head> 後の不正な <head> タグの扱い: HTMLの構造上、<head> 要素は文書のメタデータを含む部分であり、通常は <body> 要素の前に一度だけ出現します。</head> の後に再度 <head> タグが出現することは不正なHTMLですが、ブラウザはこのような場合でもエラーとせず、通常は後続の <head> タグを無視してパースを続行します。Goの html パッケージも同様の寛容性を持つべきであり、不正なタグを適切に無視することで、より多くの現実世界のHTMLドキュメントを正確にパースできるようになります。

これらの変更は、Goの html パッケージがより多くの「現実世界」のHTMLドキュメントを、主要なウェブブラウザが解釈するのと同様の方法で正確にパースできるようにするために行われました。これにより、HTML処理の堅牢性と互換性が向上します。

前提知識の解説

HTMLの基本構造

HTMLドキュメントは、主に以下の要素で構成されます。

  • <!DOCTYPE html>: ドキュメントタイプ宣言。HTML5ドキュメントであることを示します。
  • <html>: HTMLドキュメントのルート要素。
  • <head>: ドキュメントのメタデータ(タイトル、文字エンコーディング、スタイルシート、スクリプトなど)を含むセクション。ブラウザには表示されません。
  • <body>: ドキュメントの可視コンテンツ(テキスト、画像、リンクなど)を含むセクション。ブラウザに表示されます。

HTML5のパースアルゴリズム

HTML5のパースアルゴリズムは、非常に詳細かつ複雑な仕様であり、ウェブブラウザがどのようにHTMLを解釈し、DOMツリーを構築するかを定義しています。このアルゴリズムは、エラー耐性(error handling)に重点を置いており、不正なHTMLに対しても可能な限りDOMツリーを構築しようとします。

重要な概念として「挿入モード (Insertion Mode)」があります。パーサーは、現在のトークンと現在の挿入モードに基づいて、次のアクションを決定します。例えば、<head> 要素のパース中は「"in head" 挿入モード」にあり、<body> 要素のパース中は「"in body" 挿入モード」にあります。

このコミットで関連するのは、「"after head" 挿入モード」です。これは、</head> タグが処理された直後の状態を指します。このモードでは、次にどのようなトークンが来るかによって、パーサーの挙動が異なります。HTML5の仕様では、このモードで空白文字が来た場合、それは body 要素のコンテンツとして扱われるべきとされています。

Go言語の html パッケージ

Go言語の標準ライブラリには、HTMLのパースとレンダリングを行うための html パッケージが含まれています。このパッケージは、HTML5のパースアルゴリズムに準拠することを目指しており、ウェブスクレイピング、HTMLテンプレート処理、HTMLのサニタイズなど、様々な用途で利用されます。

html パッケージのパーサーは、入力されたHTMLをトークン(タグ、テキスト、コメントなど)に分解し、それらのトークンを基にDOM (Document Object Model) ツリーを構築します。DOMツリーは、HTMLドキュメントの論理的な構造を表現するツリー構造です。

TextTokenStartTagToken

  • TextToken: HTMLドキュメント内のテキストコンテンツを表すトークンです。例えば、<p>Hello</p>HelloTextToken になります。
  • StartTagToken: HTML要素の開始タグを表すトークンです。例えば、<p><div> などです。

impliedframesetOK

Goの html パッケージのパーサー内部で使われるフラグで、HTML5のパースアルゴリズムにおける特定の状態や挙動を制御します。

  • implied: 特定の要素が明示的に記述されていないが、HTMLの構造上存在すると見なされる場合に設定されるフラグです。例えば、<html><body> タグが省略されていても、パーサーはこれらが存在すると「暗黙的に」解釈します。
  • framesetOK: frameset 要素が許可されるかどうかを示すフラグです。HTML5では frameset は非推奨ですが、古いHTMLのパースにおいては考慮されることがあります。

技術的詳細

このコミットの技術的詳細は、src/pkg/html/parse.go 内の afterHeadIM 関数に集約されています。この関数は、パーサーが "after head" 挿入モードにあるときに呼び出されます。

afterHeadIM 関数の役割

afterHeadIM 関数は、</head> タグが処理された直後に、次に現れるトークン(p.tok.Type)の種類に応じて、パーサーの挙動を制御します。

変更点とHTML5パース仕様への準拠

  1. <head><body> 間の空白文字の処理:

    • 変更前は、TextToken が来た場合、単純に implied = trueframesetOK = true を設定していました。
    • 変更後は、TextToken のデータ(p.tok.Data)から先頭の空白文字(whitespace)をトリムする処理が追加されました。
    • もし先頭に空白文字が存在した場合、その空白文字は現在のノード(この場合は head 要素の後に続くテキストノード)に追加されます。これは、HTML5の仕様で、after head 挿入モードで空白文字が来た場合、それは body 要素のコンテンツとして扱われるという挙動に準拠するためです。
    • その後、残りの非空白文字が p.tok.Data に再設定され、パースが続行されます。これにより、</head> <head> のようなケースで、</head><head> の間の空白が適切に処理され、DOMツリーに反映されるようになります。
  2. </head> 後の不正な <head> タグの無視:

    • StartTagToken でタグ名が "head" の場合、変更前は // TODO. とコメントされており、適切な処理が実装されていませんでした。
    • 変更後は、// Ignore the token. とコメントされ、return true が追加されました。これは、この <head> トークンを単に無視し、パースを続行することを意味します。HTML5のパース仕様では、after head 挿入モードで <head> 開始タグが来た場合、それは無視されるべきとされています。

これらの変更により、Goの html パッケージは、HTML5のパースアルゴリズムにおける特定のコーナーケース(特に不正なHTMLや、ブラウザが寛容に扱うHTML)に対して、より正確かつ堅牢な挙動を示すようになりました。

コアとなるコードの変更箇所

src/pkg/html/parse.go

--- a/src/pkg/html/parse.go
+++ b/src/pkg/html/parse.go
@@ -515,7 +515,19 @@ func afterHeadIM(p *parser) bool {
 	im.implied    bool
 	)
 	switch p.tok.Type {
-	case ErrorToken, TextToken:
+	case ErrorToken:
+		im.implied = true
+		framesetOK = true
+	case TextToken:
+		s := strings.TrimLeft(p.tok.Data, whitespace)
+		if len(s) < len(p.tok.Data) {
+			// Add the initial whitespace to the current node.
+			p.addText(p.tok.Data[:len(p.tok.Data)-len(s)])
+			if s == "" {
+				return true
+			}
+			p.tok.Data = s
+		}
 		im.implied = true
 		framesetOK = true
 	case StartTagToken:
@@ -535,7 +547,8 @@ func afterHeadIM(p *parser) bool {
 			defer p.oe.pop()
 			return inHeadIM(p)
 		case "head":
-			// TODO.
+			// Ignore the token.
+			return true
 		default:
 			im.implied = true
 			framesetOK = true

src/pkg/html/parse_test.go

--- a/src/pkg/html/parse_test.go
+++ b/src/pkg/html/parse_test.go
@@ -167,6 +167,7 @@ func TestParser(t *testing.T) {
 		{"tests3.dat", -1},
 		{"tests4.dat", -1},
 		{"tests5.dat", -1},
+		{"tests6.dat", 7},
 	}
 	for _, tf := range testFiles {
 		f, err := os.Open("testdata/webkit/" + tf.filename)

コアとなるコードの解説

src/pkg/html/parse.go の変更点

afterHeadIM 関数内の switch p.tok.Type ブロックが変更されています。

  1. TextToken の処理の追加:

    • 以前は ErrorToken と同じ case で処理されていましたが、TextToken 専用の処理が追加されました。
    • s := strings.TrimLeft(p.tok.Data, whitespace): 現在のトークンデータ p.tok.Data の先頭から空白文字を削除し、その結果を s に格納します。whitespace は、Goの html パッケージ内で定義されている空白文字の集合です。
    • if len(s) < len(p.tok.Data): もし元のトークンデータに先頭の空白文字が含まれていた場合(つまり、s の長さが元のデータより短い場合)に以下の処理を実行します。
      • p.addText(p.tok.Data[:len(p.tok.Data)-len(s)]): 削除された空白文字部分(p.tok.Data の先頭から len(p.tok.Data)-len(s) までの部分)を、現在のノードにテキストとして追加します。これにより、</head><body> の間の空白がDOMツリーに正しく反映されます。
      • if s == "" { return true }: もし空白文字をトリムした結果、トークンデータが完全に空になった場合(つまり、トークンが空白文字のみで構成されていた場合)、そのトークンの処理を終了し、次のトークンに進みます。
      • p.tok.Data = s: トリムされた非空白文字部分を、現在のトークンデータとして再設定します。これにより、後続のパース処理が正しいデータで行われます。
    • im.implied = trueframesetOK = true は、TextToken の処理後も引き続き設定されます。
  2. StartTagToken での "head" タグの処理:

    • case "head": のブロックが変更されました。
    • // Ignore the token. とコメントが追加され、return true が記述されました。これは、</head> の後に <head> 開始タグが出現した場合、そのタグを無視して、現在の挿入モード("after head")を維持し、次のトークンの処理に進むことを意味します。これにより、不正なHTMLに対するパーサーの寛容性が向上します。

src/pkg/html/parse_test.go の変更点

  • TestParser 関数内の testFiles スライスに、{"tests6.dat", 7} という新しいテストケースが追加されました。
  • これは、tests6.dat というテストデータファイルを使用し、特定のテスト(おそらくインデックス7のテスト)を実行することを意味します。このテストケースは、おそらく <head><body> 間の空白文字の処理や、</head> 後の不正な <head> タグの無視といった、このコミットで修正された挙動を検証するためのものです。

これらの変更により、Goの html パッケージは、より多くの現実世界のHTMLドキュメントを、主要なウェブブラウザが解釈するのと同様の方法で正確にパースできるようになり、堅牢性と互換性が向上しました。

関連リンク

参考にした情報源リンク

  • 上記のHTML5 Parsing Algorithmの仕様書
  • Go言語の html パッケージのソースコード
  • 一般的なHTMLパースに関する知識
  • コミットメッセージに記載されているテストケースの例
  • https://golang.org/cl/5447064 (Goのコードレビューシステムへのリンク)
  • Web search results for "HTML5 parsing whitespace between head and body" (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF63w06d3E2EOyaAAufDkzOB_rdJjpgiLF8_n5q9qQBGaZe6UTZr4JweXWVIyuZofgaCnI3KbaWu6zBP9MbIvMTsTo-a4nwp74qsNuAWZ9BRQz2oznjscUVdlFMB8eKRk68lIiDMGhev5xPcPcbre_8ijHbAkUgItJ8ESv5600vkVQaGogsmc0Gb6qQcw==)