如何从 XML 字符串中删除 XML 意图。?
How to remove XML intendations from XML string.?
我有一个 XML 字符串。我无法从 XML 字符串中删除缩进 space。我替换了换行符。
<person id="13">
<name>
<first>John</first>
<last>Doe</last>
</name>
<age>42</age>
<Married>false</Married>
<City>Hanga Roa</City>
<State>Easter Island</State>
<!-- Need more details. -->
</person>
如何从 GOLANG 中的字符串中删除 XML 缩进 spaces?
我想要这个 XML 像这样的字符串,
<person id="13"><name><first>John</first><last>Doe</last></name><age>42</age><Married>false</Married><City>Hanga Roa</City><State>Easter Island</State><!-- Need more details. --></person>
如何在 GOLANG 中执行此操作?
尤里卡,
首先需要从 XML 中删除缩进,然后需要删除换行符。
// Regex to remove indentation
m1 := regexp.MustCompile(`( *)<`)
newstr := m1.ReplaceAllString(xmlString, "<")
// Replace newline
newLineReplacer := strings.NewReplacer("\n", "", "\r\n", "")
xmlString = newLineReplacer.Replace(newstr)
一些背景
不幸的是,XML 不是 regular language,因此您根本无法使用正则表达式可靠地处理它——无论您能够想出多么复杂的正则表达式。
我会从 this brilliant humorous take on ths issue and then read, say, this 开始。
为了演示,对您的示例进行的简单更改可能会破坏您的处理,例如:
<person id="13">
<name>
<first>John</first>
<last>Doe</last>
</name>
<age>42</age>
<Married>false</Married>
<City><![CDATA[Hanga <<Roa>>]]></City>
<State>Easter Island</State>
<!-- Need more details. -->
</person>
其实,考虑一下这个
<last>Von
Neumann</last>
为什么您认为您可以从该元素的内容中删除换行符?
当然,你会说一个人的姓氏中不能明智地换行。
好的,但是这个呢?
<poem author="Chauser">
<strophe number="1"> The lyf so short,
the craft so long to lerne.</strophe>
</poem>
你不能明智地删除该句子的两个部分之间的白色space——因为拥有它是作者的意图。
好吧,完整的故事定义在the section called "White Space Handling" of the XML spec。
XML中whitespace处理的外行尝试描述如下:
XML规范本身并没有赋予白色任何特殊含义space:决定白色space 的含义[= XML 文档的 特定位置 中的 120=] 由该文档的处理者决定。
推而广之,规范不强制要求任何“标签”(那些 <foo>
和 </bar>
和 <quux/>
之间的白色 space 是否出现在XML 标记是允许的)是否 重要:只有你自己决定。
为了更好地理解其原因,请考虑以下文档:
<p>␣Some text which contains an␣<em>emphasized block</em>
which is followed by a linebreak and more text.</p>
这是完全有效的 XML,我已经替换了 space 个字符
在 <p>
标签之后和 <em>
标签之前,带有用于显示目的的 Unicode“打开框”字符。
请注意,整个文本 ␣Some text which contains an␣
出现在两个标签之间,并且包含前导和尾随的白色 space,这显然 重要 — 如果不是,强调的文本(标有 <em>…</em>
的文本将与前面的文本粘在一起)。
相同的逻辑适用于 </em>
标记后的换行符和更多文本。
XML规范提示将“无足轻重的”白色space定义为任何白色space 在一对相邻的标签之间 没有定义单个元素。
XML 还有两个使处理进一步复杂化的特征:
- 字符实体(那些
&
和 <
东西)允许直接插入任何 Unicode 代码点:例如,
会插入一个换行符。
- XML 支持特殊的 "CDATA sections",您的解析器表面上对此一无所知。
一种解决方法
在我们尝试提出解决方案之前,我们将定义我们打算视为无关紧要的白色space,然后丢弃。
看起来像你的那种文档,定义应该是:任何两个标签之间的任何字符数据都应该被删除,除非:
- 它至少包含一个 non-whitespace 字符,或者
- 它完全定义了单个 XML 元素的内容。
考虑到这些因素,我们可以编写代码将输入 XML 流解析为 令牌 并将它们写入输出 XML 流,同时应用以下逻辑来处理令牌:
如果它发现除字符数据之外的任何 XML 元素,它会将它们编码到输出流中。
此外,如果该元素是开始标记,它会通过设置一些标志来记住这一事实;否则标志被清除。
如果它看到任何字符数据,它会检查此字符数据是否紧跟在起始元素(开始标记)之后,如果是,则此字符数据块被保存。
当已经存在这样的保存块时,字符数据块也会被保存——这是必需的,因为在 XML 中,文档中可能有几个相邻但仍然不同的字符数据块。
如果它看到任何 XML 元素,并检测到它有一个或多个保存的字符块,那么它首先决定是否将它们放入输出流:
如果元素是结束元素(结束标签),所有字符数据块必须“按原样”放入输出流——因为它们完全定义了单个元素的内容.
否则,如果至少一个保存的字符数据块包含至少一个 non-whitespace 字符,则所有块都按原样写入输出流。
否则跳过所有块。
这是实现所描述方法的工作代码:
package main
import (
"encoding/xml"
"errors"
"fmt"
"io"
"os"
"strings"
)
const xmlData = `<?xml version="1.0" encoding="utf-8"?>
<person id="13">
weird text
<name>
<first>John</first>
<last><![CDATA[Johnson & ]]><![CDATA[ <<Johnson>> ]]><![CDATA[ & Doe ]]></last>
</name>
 
	<age>
42
</age>
<Married>false</Married>
<City><![CDATA[Hanga <Roa>]]></City>
<State>Easter Island</State>
<!-- Need more details. --> what?
<foo> more <bar/> text </foo>
</person>
`
func main() {
stripped, err := removeWS(xmlData)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Print(stripped)
}
func removeWS(s string) (string, error) {
dec := xml.NewDecoder(strings.NewReader(s))
var sb strings.Builder
enc := NewSkipWSEncoder(&sb)
for {
tok, err := dec.Token()
if err != nil {
if err == io.EOF {
break
}
return "", fmt.Errorf("failed to decode token: %w", err)
}
err = enc.EncodeToken(tok)
if err != nil {
return "", fmt.Errorf("failed to encode token: %w", err)
}
}
err := enc.Flush()
if err != nil {
return "", fmt.Errorf("failed to flush encoder: %w", err)
}
return sb.String(), nil
}
type SkipWSEncoder struct {
*xml.Encoder
sawStartElement bool
charData []xml.CharData
}
func NewSkipWSEncoder(w io.Writer) *SkipWSEncoder {
return &SkipWSEncoder{
Encoder: xml.NewEncoder(w),
}
}
func (swe *SkipWSEncoder) EncodeToken(tok xml.Token) error {
if cd, isCData := tok.(xml.CharData); isCData {
if len(swe.charData) > 0 || swe.sawStartElement {
swe.charData = append(swe.charData, cd.Copy())
return nil
}
if isWS(cd) {
return nil
}
return swe.Encoder.EncodeToken(tok)
}
if len(swe.charData) > 0 {
_, isEndElement := tok.(xml.EndElement)
err := swe.flushSavedCharData(isEndElement)
if err != nil {
return err
}
}
_, swe.sawStartElement = tok.(xml.StartElement)
return swe.Encoder.EncodeToken(tok)
}
func (swe *SkipWSEncoder) Flush() error {
if len(swe.charData) > 0 {
return errors.New("attempt to flush encoder while having pending cdata")
}
return swe.Encoder.Flush()
}
func (swe *SkipWSEncoder) flushSavedCharData(mustKeep bool) error {
if mustKeep || !allIsWS(swe.charData) {
err := encodeCDataList(swe.Encoder, swe.charData)
if err != nil {
return err
}
}
swe.charData = swe.charData[:0]
return nil
}
func encodeCDataList(enc *xml.Encoder, cdataList []xml.CharData) error {
for _, cd := range cdataList {
err := enc.EncodeToken(cd)
if err != nil {
return err
}
}
return nil
}
func isWS(b []byte) bool {
for _, c := range b {
switch c {
case 0x20, 0x09, 0x0d, 0x0a:
continue
}
return false
}
return true
}
func allIsWS(cdataList []xml.CharData) bool {
for _, cd := range cdataList {
if !isWS(cd) {
return false
}
}
return true
}
我不确定完全涵盖所有可能的奇怪情况,但这应该是一个好的开始。
您可以简单地删除 new line
和 tab
字符,如下所示:
package main
import (
"fmt"
"strings"
)
func main() {
var s = `<person id="13">
<name>
<first>John</first>
<last>Doe</last>
</name>
<age>42</age>
<Married>false</Married>
<City>Hanga Roa</City>
<State>Easter Island</State>
<!-- Need more details. -->
</person>`
for {
if strings.Contains(s, "\n") {
s = strings.ReplaceAll(s, "\n", "")
}
if strings.Contains(s, "\t") {
s = strings.ReplaceAll(s, "\t", "")
}
if !strings.Contains(s, "\n") && !strings.Contains(s, "\t") {
break
}
}
fmt.Println(s)
}
结果:
<person id="13"><name><first>John</first><last>Doe</last></name><age>42</age><Married>false</Married><City>Hanga Roa</City><State>Easter Island</State><!-- Need more details. --></person>
删除白色space-仅 XML 标签之间的序列
func unformatXML(xmlString string) string {
var unformatXMLRegEx = regexp.MustCompile(`>\s+<`)
unformatBetweenTags := unformatXMLRegEx.ReplaceAllString(xmlString, "><") // remove whitespace between XML tags
return strings.TrimSpace(unformatBetweenTags) // remove whitespace before and after XML
}
正则表达式解释
\s - 匹配任何白色space,包括制表符、换行符、换页符、回车return 和space
+ - 匹配一个或多个白色space字符
RegEx 语法参考:https://golang.org/pkg/regexp/syntax/
例子
package main
import (
"fmt"
"regexp"
"strings"
)
func main() {
var s = `
<person id="13">
<name>
<first>John</first>
<last>Doe</last>
</name>
<age>42</age>
<Married>false</Married>
<City>Hanga Roa</City>
<State>Easter Island</State>
<!-- Need more details. -->
</person> `
s = unformatXML(s)
fmt.Println(fmt.Sprintf("'%s'", s)) // single quotes used to confirm no leading or trailing whitespace
}
func unformatXML(xmlString string) string {
var unformatXMLRegEx = regexp.MustCompile(`>\s+<`)
unformatBetweenTags := unformatXMLRegEx.ReplaceAllString(xmlString, "><") // remove whitespace between XML tags
return strings.TrimSpace(unformatBetweenTags) // remove whitespace before and after XML
}
Go Playground 中的可运行示例
我有一个 XML 字符串。我无法从 XML 字符串中删除缩进 space。我替换了换行符。
<person id="13">
<name>
<first>John</first>
<last>Doe</last>
</name>
<age>42</age>
<Married>false</Married>
<City>Hanga Roa</City>
<State>Easter Island</State>
<!-- Need more details. -->
</person>
如何从 GOLANG 中的字符串中删除 XML 缩进 spaces?
我想要这个 XML 像这样的字符串,
<person id="13"><name><first>John</first><last>Doe</last></name><age>42</age><Married>false</Married><City>Hanga Roa</City><State>Easter Island</State><!-- Need more details. --></person>
如何在 GOLANG 中执行此操作?
尤里卡,
首先需要从 XML 中删除缩进,然后需要删除换行符。
// Regex to remove indentation
m1 := regexp.MustCompile(`( *)<`)
newstr := m1.ReplaceAllString(xmlString, "<")
// Replace newline
newLineReplacer := strings.NewReplacer("\n", "", "\r\n", "")
xmlString = newLineReplacer.Replace(newstr)
一些背景
不幸的是,XML 不是 regular language,因此您根本无法使用正则表达式可靠地处理它——无论您能够想出多么复杂的正则表达式。
我会从 this brilliant humorous take on ths issue and then read, say, this 开始。
为了演示,对您的示例进行的简单更改可能会破坏您的处理,例如:
<person id="13">
<name>
<first>John</first>
<last>Doe</last>
</name>
<age>42</age>
<Married>false</Married>
<City><![CDATA[Hanga <<Roa>>]]></City>
<State>Easter Island</State>
<!-- Need more details. -->
</person>
其实,考虑一下这个
<last>Von
Neumann</last>
为什么您认为您可以从该元素的内容中删除换行符?
当然,你会说一个人的姓氏中不能明智地换行。
好的,但是这个呢?
<poem author="Chauser">
<strophe number="1"> The lyf so short,
the craft so long to lerne.</strophe>
</poem>
你不能明智地删除该句子的两个部分之间的白色space——因为拥有它是作者的意图。
好吧,完整的故事定义在the section called "White Space Handling" of the XML spec。
XML中whitespace处理的外行尝试描述如下:
XML规范本身并没有赋予白色任何特殊含义space:决定白色space 的含义[= XML 文档的 特定位置 中的 120=] 由该文档的处理者决定。
推而广之,规范不强制要求任何“标签”(那些
<foo>
和</bar>
和<quux/>
之间的白色 space 是否出现在XML 标记是允许的)是否 重要:只有你自己决定。
为了更好地理解其原因,请考虑以下文档:<p>␣Some text which contains an␣<em>emphasized block</em> which is followed by a linebreak and more text.</p>
这是完全有效的 XML,我已经替换了 space 个字符 在
<p>
标签之后和<em>
标签之前,带有用于显示目的的 Unicode“打开框”字符。请注意,整个文本
␣Some text which contains an␣
出现在两个标签之间,并且包含前导和尾随的白色 space,这显然 重要 — 如果不是,强调的文本(标有<em>…</em>
的文本将与前面的文本粘在一起)。相同的逻辑适用于
</em>
标记后的换行符和更多文本。XML规范提示将“无足轻重的”白色space定义为任何白色space 在一对相邻的标签之间 没有定义单个元素。
XML 还有两个使处理进一步复杂化的特征:
- 字符实体(那些
&
和<
东西)允许直接插入任何 Unicode 代码点:例如,
会插入一个换行符。 - XML 支持特殊的 "CDATA sections",您的解析器表面上对此一无所知。
一种解决方法
在我们尝试提出解决方案之前,我们将定义我们打算视为无关紧要的白色space,然后丢弃。
看起来像你的那种文档,定义应该是:任何两个标签之间的任何字符数据都应该被删除,除非:
- 它至少包含一个 non-whitespace 字符,或者
- 它完全定义了单个 XML 元素的内容。
考虑到这些因素,我们可以编写代码将输入 XML 流解析为 令牌 并将它们写入输出 XML 流,同时应用以下逻辑来处理令牌:
如果它发现除字符数据之外的任何 XML 元素,它会将它们编码到输出流中。
此外,如果该元素是开始标记,它会通过设置一些标志来记住这一事实;否则标志被清除。
如果它看到任何字符数据,它会检查此字符数据是否紧跟在起始元素(开始标记)之后,如果是,则此字符数据块被保存。
当已经存在这样的保存块时,字符数据块也会被保存——这是必需的,因为在 XML 中,文档中可能有几个相邻但仍然不同的字符数据块。
如果它看到任何 XML 元素,并检测到它有一个或多个保存的字符块,那么它首先决定是否将它们放入输出流:
如果元素是结束元素(结束标签),所有字符数据块必须“按原样”放入输出流——因为它们完全定义了单个元素的内容.
否则,如果至少一个保存的字符数据块包含至少一个 non-whitespace 字符,则所有块都按原样写入输出流。
否则跳过所有块。
这是实现所描述方法的工作代码:
package main
import (
"encoding/xml"
"errors"
"fmt"
"io"
"os"
"strings"
)
const xmlData = `<?xml version="1.0" encoding="utf-8"?>
<person id="13">
weird text
<name>
<first>John</first>
<last><![CDATA[Johnson & ]]><![CDATA[ <<Johnson>> ]]><![CDATA[ & Doe ]]></last>
</name>
 
	<age>
42
</age>
<Married>false</Married>
<City><![CDATA[Hanga <Roa>]]></City>
<State>Easter Island</State>
<!-- Need more details. --> what?
<foo> more <bar/> text </foo>
</person>
`
func main() {
stripped, err := removeWS(xmlData)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Print(stripped)
}
func removeWS(s string) (string, error) {
dec := xml.NewDecoder(strings.NewReader(s))
var sb strings.Builder
enc := NewSkipWSEncoder(&sb)
for {
tok, err := dec.Token()
if err != nil {
if err == io.EOF {
break
}
return "", fmt.Errorf("failed to decode token: %w", err)
}
err = enc.EncodeToken(tok)
if err != nil {
return "", fmt.Errorf("failed to encode token: %w", err)
}
}
err := enc.Flush()
if err != nil {
return "", fmt.Errorf("failed to flush encoder: %w", err)
}
return sb.String(), nil
}
type SkipWSEncoder struct {
*xml.Encoder
sawStartElement bool
charData []xml.CharData
}
func NewSkipWSEncoder(w io.Writer) *SkipWSEncoder {
return &SkipWSEncoder{
Encoder: xml.NewEncoder(w),
}
}
func (swe *SkipWSEncoder) EncodeToken(tok xml.Token) error {
if cd, isCData := tok.(xml.CharData); isCData {
if len(swe.charData) > 0 || swe.sawStartElement {
swe.charData = append(swe.charData, cd.Copy())
return nil
}
if isWS(cd) {
return nil
}
return swe.Encoder.EncodeToken(tok)
}
if len(swe.charData) > 0 {
_, isEndElement := tok.(xml.EndElement)
err := swe.flushSavedCharData(isEndElement)
if err != nil {
return err
}
}
_, swe.sawStartElement = tok.(xml.StartElement)
return swe.Encoder.EncodeToken(tok)
}
func (swe *SkipWSEncoder) Flush() error {
if len(swe.charData) > 0 {
return errors.New("attempt to flush encoder while having pending cdata")
}
return swe.Encoder.Flush()
}
func (swe *SkipWSEncoder) flushSavedCharData(mustKeep bool) error {
if mustKeep || !allIsWS(swe.charData) {
err := encodeCDataList(swe.Encoder, swe.charData)
if err != nil {
return err
}
}
swe.charData = swe.charData[:0]
return nil
}
func encodeCDataList(enc *xml.Encoder, cdataList []xml.CharData) error {
for _, cd := range cdataList {
err := enc.EncodeToken(cd)
if err != nil {
return err
}
}
return nil
}
func isWS(b []byte) bool {
for _, c := range b {
switch c {
case 0x20, 0x09, 0x0d, 0x0a:
continue
}
return false
}
return true
}
func allIsWS(cdataList []xml.CharData) bool {
for _, cd := range cdataList {
if !isWS(cd) {
return false
}
}
return true
}
我不确定完全涵盖所有可能的奇怪情况,但这应该是一个好的开始。
您可以简单地删除 new line
和 tab
字符,如下所示:
package main
import (
"fmt"
"strings"
)
func main() {
var s = `<person id="13">
<name>
<first>John</first>
<last>Doe</last>
</name>
<age>42</age>
<Married>false</Married>
<City>Hanga Roa</City>
<State>Easter Island</State>
<!-- Need more details. -->
</person>`
for {
if strings.Contains(s, "\n") {
s = strings.ReplaceAll(s, "\n", "")
}
if strings.Contains(s, "\t") {
s = strings.ReplaceAll(s, "\t", "")
}
if !strings.Contains(s, "\n") && !strings.Contains(s, "\t") {
break
}
}
fmt.Println(s)
}
结果:
<person id="13"><name><first>John</first><last>Doe</last></name><age>42</age><Married>false</Married><City>Hanga Roa</City><State>Easter Island</State><!-- Need more details. --></person>
删除白色space-仅 XML 标签之间的序列
func unformatXML(xmlString string) string {
var unformatXMLRegEx = regexp.MustCompile(`>\s+<`)
unformatBetweenTags := unformatXMLRegEx.ReplaceAllString(xmlString, "><") // remove whitespace between XML tags
return strings.TrimSpace(unformatBetweenTags) // remove whitespace before and after XML
}
正则表达式解释
\s - 匹配任何白色space,包括制表符、换行符、换页符、回车return 和space
+ - 匹配一个或多个白色space字符
RegEx 语法参考:https://golang.org/pkg/regexp/syntax/
例子
package main
import (
"fmt"
"regexp"
"strings"
)
func main() {
var s = `
<person id="13">
<name>
<first>John</first>
<last>Doe</last>
</name>
<age>42</age>
<Married>false</Married>
<City>Hanga Roa</City>
<State>Easter Island</State>
<!-- Need more details. -->
</person> `
s = unformatXML(s)
fmt.Println(fmt.Sprintf("'%s'", s)) // single quotes used to confirm no leading or trailing whitespace
}
func unformatXML(xmlString string) string {
var unformatXMLRegEx = regexp.MustCompile(`>\s+<`)
unformatBetweenTags := unformatXMLRegEx.ReplaceAllString(xmlString, "><") // remove whitespace between XML tags
return strings.TrimSpace(unformatBetweenTags) // remove whitespace before and after XML
}