如何从 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)

在这里找到这个,https://play.golang.org/p/Orp2RyPbGP2

一些背景

不幸的是,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 还有两个使处理进一步复杂化的特征:

  • 字符实体(那些 &amp;&lt; 东西)允许直接插入任何 Unicode 代码点:例如,&#x000d; 会插入一个换行符。
  • XML 支持特殊的 "CDATA sections",您的解析器表面上对此一无所知。

一种解决方法

在我们尝试提出解决方案之前,我们将定义我们打算视为无关紧要的白色space,然后丢弃。

看起来像你的那种文档,定义应该是:任何两个标签之间的任何字符数据都应该被删除,除非:

  • 它至少包含一个 non-whitespace 字符,或者
  • 它完全定义了单个 XML 元素的内容。

考虑到这些因素,我们可以编写代码将输入 XML 流解析为 令牌 并将它们写入输出 XML 流,同时应用以下逻辑来处理令牌:

  1. 如果它发现除字符数据之外的任何 XML 元素,它会将它们编码到输出流中。

    此外,如果该元素是开始标记,它会通过设置一些标志来记住这一事实;否则标志被清除。

  2. 如果它看到任何字符数据,它会检查此字符数据是否紧跟在起始元素(开始标记)之后,如果是,则此字符数据块被保存。

    当已经存在这样的保存块时,字符数据块也会被保存——这是必需的,因为在 XML 中,文档中可能有几个相邻但仍然不同的字符数据块。

  3. 如果它看到任何 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>&#x000d;&#x0020;&#x000a;&#x0009;<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
}

Playground.

我不确定完全涵盖所有可能的奇怪情况,但这应该是一个好的开始。

您可以简单地删除 new linetab 字符,如下所示:

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 中的可运行示例

https://play.golang.org/p/VS1LRNevicz