xml之如何从XML字符串中删除XML意向

xiaohuochai 阅读:64 2025-06-02 22:19:02 评论:0

我有一个XML字符串。我无法从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缩进空间?
我想要这个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不是regular language,因此,您将无法使用正则表达式可靠地对其进行处理-无论您将要开发的正则表达式多么复杂。
我将首先从this这个问题的幽默幽默开始,然后阅读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> 
您不能明智地在该句子的两部分之间放置空格,因为这是作者的意图。
好吧,好的,整个故事都在 the section called "White Space Handling" of the XML spec中定义。
外行尝试用XML描述空白处理的尝试如下:
  • XML规范本身没有为空格赋予任何特殊含义:决定XML文档特殊位置的空格取决于该文档的处理器。
    通过扩展,该规范没有规定任何“标签”(那些<foo></bar><quux/>事物-出现在允许XML标记的地方)之间的空格是否有意义:只有您来决定。
    为了更好地了解其原因,请考虑以下文档:
    <p>␣Some text which contains an␣<em>emphasized block</em> 
    which is followed by a linebreak and more text.</p> 
    
    这是一个非常有效的XML,我已经替换了空格字符
    <p>标记之后和Unicode带有“开箱”字符的<em>标记之前,用于显示。
    请注意,整个文本␣Some text which contains an␣出现在两个标签之间,并且包含前导和尾随空格,这显然很重要-如果不是,则强调文本(用<em>…</em>标记的文本将与前面的文本粘合在一起)。
    相同的逻辑适用于换行符和</em>标记后的更多文本。
  • XML规范提示,定义“无关紧要”的空格可能很方便,以表示一对未定义单个元素的相邻标记之间的任何空格。

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

  • 解决方案
    在尝试提出解决方案之前,我们将定义要忽略的空白并丢弃。
    看起来与您的文档类型类似,定义应为:除非以下情况,否则应删除任何两个标签之间的任何字符数据:
  • ,它至少包含一个单一的非空白字符或
  • 它完全定义了单个XML元素的内容。

  • 考虑到这些考虑因素,我们可以编写将输入XML流解析为 token 并将其写入输出XML流的代码,同时将以下逻辑应用于处理 token :
  • 如果看到除字符数据以外的任何XML元素,则将它们编码为输出流。
    另外,如果该元素是一个开始标记,它会通过设置一些标志来记住这一事实。否则清除标志。
  • 如果看到任何字符数据,它将检查该字符数据是否紧跟在起始元素(开始标记)之后,如果是,则将该字符数据块保存下来。
    当已经存在已保存的字符数据块时,也将保存字符数据块,这是需要的,因为在XML中,文档中可能有多个相邻但仍不同的字符数据块。
  • 如果看到任何XML元素,并且检测到它具有一个或多个保存的字符块,则它首先决定是否将其放入输出流:
  • 如果元素是结束元素(结束标记),则所有字符数据块都必须“按原样”放入输出流中,因为它们完全定义了单个元素的内容。
  • 否则,如果至少一个已保存的字符数据块包含至少一个非空白字符,则所有块均按原样写入输出流。
  • 否则,将跳过所有块。


  • 这是实现上述方法的工作代码:
    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
    我不确定它是否能完全涵盖所有可能的怪异案例,但这应该是一个好的开始。


    标签:XML
    声明

    1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

    关注我们

    一个IT知识分享的公众号