繁体   English   中英

将xml doc转换为特定的点扩展json结构

[英]Converting an xml doc into a specific dot-expanded json structure

我有以下XML文档:

<Item ID="288917">
  <Main>
    <Platform>iTunes</Platform>
    <PlatformID>353736518</PlatformID>
  </Main>
  <Genres>
    <Genre FacebookID="6003161475030">Comedy</Genre>
    <Genre FacebookID="6003172932634">TV-Show</Genre>
  </Genres>
  <Products>
    <Product Country="CA">
      <URL>https://itunes.apple.com/ca/tv-season/id353187108?i=353736518</URL>
      <Offers>
        <Offer Type="HDBUY">
          <Price>3.49</Price>
          <Currency>CAD</Currency>
        </Offer>
        <Offer Type="SDBUY">
          <Price>2.49</Price>
          <Currency>CAD</Currency>
        </Offer>
      </Offers>
    </Product>
    <Product Country="FR">
      <URL>https://itunes.apple.com/fr/tv-season/id353187108?i=353736518</URL>
      <Rating>Tout public</Rating>
      <Offers>
        <Offer Type="HDBUY">
          <Price>2.49</Price>
          <Currency>EUR</Currency>
        </Offer>
        <Offer Type="SDBUY">
          <Price>1.99</Price>
          <Currency>EUR</Currency>
        </Offer>
      </Offers>
    </Product>
  </Products>
</Item>

目前,为了使它成为json格式,我正在做以下事情:

parser = etree.XMLParser(recover=True)
node = etree.fromstring(s, parser=parser)
data = xmltodict.parse(etree.tostring(node))

当然xmltodict正在进行繁重的工作。 但是,它给了我一种不适合我想要完成的格式。 这是我想要的最终数据:

{
    "Item[@ID]": 288917, # if no preceding element, use the root node tag
    "Main.Platform": "iTunes",
    "Main.PlatformID": "353736518",
    "Genres.Genre": ["Comedy", "TV-Show"] # list of elements if repeated
    "Genres.Genre[@FacebookID]": ["6003161475030", "6003161475030"],
    "Products.Product[@Country]": ["CA", "FR"],
    "Products.Product.URL": ["https://itunes.apple.com/ca/tv-season/id353187108?i=353736518", "https://itunes.apple.com/fr/tv-season/id353187108?i=353736518"],
    "Products.Product.Offers.Offer[@Type]": ["HDBUY", "SDBUY", "HDBUY", "SDBUY"],
    "Products.Product.Offers.Offer.Price": ["3.49", "2.49", "2.49", "1.99"],
    "Products.Product.Offers.Offer.Currency": "EUR"    
}

这有点冗长,但要将其格式化为平坦的字典并不太难。 这是一个例子:

node = etree.fromstring(file_data.encode('utf-8'), parser=parser)
data = OrderedDict()
nodes = [(node, ''),] # format is (node, prefix)

while nodes:

    for sub, prefix in nodes:

        # remove the prefix tag unless its for the first attribute
        tag_prefix = '.'.join(prefix.split('.')[1:]) if ('.' in prefix) else ''
        atr_prefix = sub.tag if (sub == node) else tag_prefix

        # tag
        if sub.text.strip():
            _prefix = tag_prefix + '.' + sub.tag
            _value = sub.text.strip()
            if data.get(_prefix): # convert it to a list if multiple values
                if not isinstance(data[_prefix], list): data[_prefix] = [data[_prefix],]
                data[_prefix].append(_value)
            else:
                data[_prefix] = _value

        # atr
        for k, v in sub.attrib.items():
            _prefix = atr_prefix + '[@%s]' % k
            _value = v
            if data.get(_prefix): # convert it to a list if multiple values
                if not isinstance(data[_prefix], list): data[_prefix] = [data[_prefix],]
                data[_prefix].append(_value)
            else:
                data[_prefix] = _value

        nodes.remove((sub, prefix))

        for s in sub.getchildren():
            _prefix = (prefix + '.' + sub.tag).strip('.')
            nodes.append((s, _prefix))

    if not nodes: break

你可以在这里使用递归。 一种方法是在递归XML文档时逐步存储路径,并在结尾返回结果字典,可以将其序列化为JSON。

下面的演示使用标准库xml.etree.ElementTree来解析XML文档。

演示:

from xml.etree.ElementTree import ElementTree
from pprint import pprint

# Setup XML tree for parsing
tree = ElementTree()
tree.parse("sample.xml")
root = tree.getroot()

def collect_xml_paths(root, path=[], result={}):
    """Collect XML paths into a dictionary"""

    # First collect root items
    if not result:
        root_id, root_value = tuple(root.attrib.items())[0]
        root_key = root.tag + "[@%s]" % root_id
        result[root_key] = root_value

    # Go through each child from root
    for child in root:

        # Extract text
        text = child.text.strip()

        # Update path
        new_path = path[:]
        new_path.append(child.tag)

        # Create dot separated key
        key = ".".join(new_path)

        # Get child attributes
        attributes = child.attrib

        # Ensure we have attributes
        if attributes:

            # Add each attribute to result
            for k, v in attributes.items():
                attrib_key = key + "[@%s]" % k
                result.setdefault(attrib_key, []).append(v)

        # Add text if it exists
        if text:
            result.setdefault(key, []).append(text)

        # Recurse through paths once done iteration
        collect_xml_paths(child, new_path)

    # Separate single values from list values
    return {k: v[0] if len(v) == 1 else v for k, v in result.items()}

pprint(collect_xml_paths(root))

输出:

{'Genres.Genre': ['Comedy', 'TV-Show'],
 'Genres.Genre[@FacebookID]': ['6003161475030', '6003172932634'],
 'Item[@ID]': '288917',
 'Main.Platform': 'iTunes',
 'Main.PlatformID': '353736518',
 'Products.Product.Offers.Offer.Currency': ['CAD', 'CAD', 'EUR', 'EUR'],
 'Products.Product.Offers.Offer.Price': ['3.49', '2.49', '2.49', '1.99'],
 'Products.Product.Offers.Offer[@Type]': ['HDBUY', 'SDBUY', 'HDBUY', 'SDBUY'],
 'Products.Product.Rating': 'Tout public',
 'Products.Product.URL': ['https://itunes.apple.com/ca/tv-season/id353187108?i=353736518',
                      'https://itunes.apple.com/fr/tv-season/id353187108?i=353736518'],
 'Products.Product[@Country]': ['CA', 'FR']}

如果要将此字典序列化为JSON,可以使用json.dumps()

from json import dumps

print(dumps(collect_xml_paths(root)))
# {"Item[@ID]": "288917", "Main.Platform": "iTunes", "Main.PlatformID": "353736518", "Genres.Genre[@FacebookID]": ["6003161475030", "6003172932634"], "Genres.Genre": ["Comedy", "TV-Show"], "Products.Product[@Country]": ["CA", "FR"], "Products.Product.URL": ["https://itunes.apple.com/ca/tv-season/id353187108?i=353736518", "https://itunes.apple.com/fr/tv-season/id353187108?i=353736518"], "Products.Product.Offers.Offer[@Type]": ["HDBUY", "SDBUY", "HDBUY", "SDBUY"], "Products.Product.Offers.Offer.Price": ["3.49", "2.49", "2.49", "1.99"], "Products.Product.Offers.Offer.Currency": ["CAD", "CAD", "EUR", "EUR"], "Products.Product.Rating": "Tout public"}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM