diff --git a/README.md b/README.md
index 4d26bfe..f628e8a 100644
--- a/README.md
+++ b/README.md
@@ -219,6 +219,45 @@ options := xmlquery.ParserOptions{
doc, err := xmlquery.ParseWithOptions(utf8Reader, options)
```
+### Parse non-ASCII characters and custom entity names.
+
+UTF-8 XML can contain characters such as `ä` directly, or use a numeric
+character reference such as `ä` or `ä`. These work with `Parse()`.
+HTML entity names such as `ä` are not predefined in XML, so `Parse()`
+reports an unknown entity. A character encoding conversion does not define
+those entity names.
+
+If the input uses known custom names, provide their replacements through
+`DecoderOptions.Entity`:
+
+```go
+s := `ä & ä`
+options := xmlquery.ParserOptions{
+ Decoder: &xmlquery.DecoderOptions{
+ Strict: true,
+ Entity: map[string]string{"auml": "ä"},
+ },
+}
+doc, err := xmlquery.ParseWithOptions(strings.NewReader(s), options)
+if err != nil {
+ panic(err)
+}
+fmt.Println(xmlquery.FindOne(doc, "//name").InnerText()) // ä & ä
+```
+
+Set `Strict: true` explicitly when supplying `DecoderOptions`: its zero value
+is false, unlike the default XML decoder. This keeps malformed XML and unknown
+entity names as errors. The entity map above defines only `auml`; it does not
+load an HTML entity set or declarations from a DTD.
+
+For XML bytes in another character encoding, such as ISO-8859-1, use
+`DecoderOptions.CharsetReader`. `Parse()` already uses
+`golang.org/x/net/html/charset.NewReaderLabel` for an XML encoding declaration.
+Supplying `DecoderOptions` replaces these decoder defaults, so when combining
+custom entities with a non-UTF-8 document, also set
+`CharsetReader: charset.NewReaderLabel` (from `golang.org/x/net/html/charset`).
+An entity map and a charset reader solve different problems.
+
### Query with custom namespace prefix.
```go
diff --git a/doc_test.go b/doc_test.go
index ce1e903..e3c485b 100644
--- a/doc_test.go
+++ b/doc_test.go
@@ -6,6 +6,7 @@ import (
"github.com/antchfx/xmlquery"
"github.com/antchfx/xpath"
+ "golang.org/x/net/html/charset"
)
func Example() {
@@ -62,3 +63,51 @@ func Example() {
prices := xpath.MustCompile(`sum(//book/price)`).Evaluate(nav).(float64)
fmt.Println(prices) // > Output: 56.85
}
+
+func ExampleParseWithOptions_customEntities() {
+ s := `ä & ä`
+ options := xmlquery.ParserOptions{
+ Decoder: &xmlquery.DecoderOptions{
+ Strict: true,
+ Entity: map[string]string{"auml": "ä"},
+ },
+ }
+ doc, err := xmlquery.ParseWithOptions(strings.NewReader(s), options)
+ if err != nil {
+ panic(err)
+ }
+ fmt.Println(xmlquery.FindOne(doc, "//name").InnerText())
+
+ for _, input := range []string{`ä`, `ä`, `ä`} {
+ doc, err := xmlquery.Parse(strings.NewReader(input))
+ if err != nil {
+ panic(err)
+ }
+ fmt.Println(xmlquery.FindOne(doc, "//name").InnerText())
+ }
+
+ _, err = xmlquery.Parse(strings.NewReader(`ä`))
+ fmt.Println(err != nil)
+ _, err = xmlquery.ParseWithOptions(strings.NewReader(`&unknown;`), options)
+ fmt.Println(err != nil)
+ _, err = xmlquery.ParseWithOptions(strings.NewReader(``), options)
+ fmt.Println(err != nil)
+
+ options.Decoder.CharsetReader = charset.NewReaderLabel
+ latin1 := "\xe4 ä"
+ doc, err = xmlquery.ParseWithOptions(strings.NewReader(latin1), options)
+ if err != nil {
+ panic(err)
+ }
+ fmt.Println(xmlquery.FindOne(doc, "//name").InnerText())
+
+ // Output:
+ // ä & ä
+ // ä
+ // ä
+ // ä
+ // true
+ // true
+ // true
+ // ä ä
+}