2016-07-06 5 views
3

У меня есть объект, который в настоящее время отображается с Java POJO на XML с помощью JAXB. Как только у меня есть этот XML, мне иногда приходится сворачивать его только на избранный набор элементов, основанный на вводе пользователем. Результат должен быть XML с ТОЛЬКО указанными «полями».Как фильтровать строку XML по элементам «пути» в Groovy или Java

Я столкнулся с рядом аналогичных вариантов использования, которые мы использовали SAX Filters, но они кажутся очень сложными, и ответы не дают мне, где мне нужно. Самый близкий пример: this one, что исключает один путь из результата. Я хочу, чтобы наоборот - белый список выбирает список элементов.

Пример объекта: School.xml

<SchoolInfo RefId="34060F68BE3942F1B1264E6D2CC3C353"> 
     <LocalId>57</LocalId> 
     <SchoolName>Foobar School of Technology</SchoolName> 
     <Principal> 
      <FirstName>Bob</FirstName> 
      <LastName>Smith</LastName> 
     </Principal> 
     <StateProvinceId>34573</StateProvinceId> 
     <LEAInfoRefId>340666687E3942F1B1264E1223453C353</LEAInfoRefId> 
     <PhoneNumberList> 
      <PhoneNumber Type="0096"> 
       <Number>555-832-5555</Number> 
      </PhoneNumber> 
      <PhoneNumber Type="0096"> 
       <Number>555-999-5555</Number> 
      </PhoneNumber> 
     </PhoneNumberList> 
    </SchoolInfo> 

задан следующий ввод в качестве "фильтра":

List<String> filter = [ 
    "LocalId", 
    "SchoolName", 
    "Principal/FirstName", 
    "PhoneNumberList/PhoneNumber/Number", 
] 

мне нужно вывод, что:

<SchoolInfo RefId="34060F68BE3942F1B1264E6D2CC3C353"> 
    <LocalId>57</LocalId> 
    <SchoolName>Foobar School of Technology</SchoolName> 
    <Principal> 
     <FirstName>Bob</FirstName> 
    </Principal> 
    <PhoneNumberList> 
     <PhoneNumber Type="0096"> 
      <Number>555-832-5555</Number> 
     </PhoneNumber> 
     <PhoneNumber Type="0096"> 
      <Number>555-999-5555</Number> 
     </PhoneNumber> 
    </PhoneNumberList> 
</SchoolInfo> 

Что лучшая библиотека для достижения этого? SAX Filtering чувствует себя сложной, и XSLT не кажется хорошей подгонкой, учитывая динамическую фильтрацию.

Примеры, которые помогут мне сблизиться, будут высоко оценены.

+0

'Groovy' должно быть хорошо с' XmlParser' или 'MarkupBuild'. См. Пример [здесь] (http://mrhaki.blogspot.in/2011/05/groovy-goodness-change-xml-structure.html) – Rao

+1

Вы ищете образец кода, который выполняет это или просто ищет рекомендации по libs? –

+0

@ vtd-xml-author Образец кода был бы потрясающим, но я не прошу кого-либо сделать эту работу для меня. Совет по правильной библиотеке для использования и метод в этой библиотеке - это то, что я ищу. –

ответ

0

Это код, который выполняет белый листинг ... он основан на XPath и VTD-XML. Его выход имеет проблемы отступов ... это первый проход, который подчеркивает правильность ...

import com.ximpleware.*; 
import java.io.*; 
import java.util.*; 

public class whiteList { 

    public static void main(String[] s) throws VTDException, IOException{ 
     VTDGen vg = new VTDGen(); 
     List <String> filter = Arrays.asList("LocalId", 
       "SchoolName", 
       "Principal/FirstName", 
       "PhoneNumberList/PhoneNumber/Number"); 
     if (!vg.parseFile("d:\\xml\\schoolInfo.xml", false)){ 
      return; 
     } 
     VTDNav vn = vg.getNav(); 
     FastIntBuffer fib = new FastIntBuffer(); 
     // build a bitmap for the entire token pool consisting of elements 
     int i,k; 
     for (i=0;i<vn.getTokenCount();i++){ 
      if (vn.getTokenType(i)==VTDNav.TOKEN_STARTING_TAG){ 
       fib.append(0x1);// b'11 since it is a white list, 
      }else{ 
       fib.append(0); 
      } 
     } 
     AutoPilot ap = new AutoPilot(vn); 
     AutoPilot ap1= new AutoPilot(vn); 
     ap1.selectXPath("descendant::*");// mark descendant as keep 
     for (int j=0;j<filter.size();j++){ 
      ap.selectXPath(filter.get(j)); 
      while((i=ap.evalXPath())!=-1){ 
       fib.modifyEntry(i, 0x3); 
       vn.push(); 
       do{ 
        if(vn.getTokenDepth(vn.getCurrentIndex())>=0) 
         fib.modifyEntry(vn.getCurrentIndex(), 0x3); 
        else 
         break; 
       }while(vn.toElement(VTDNav.P)); 
       vn.pop(); 
       vn.push(); 
       while((k=ap1.evalXPath())!=-1){ 
        fib.modifyEntry(k, 0x3); 
       } 
       ap1.resetXPath(); 
       vn.pop(); 
      } 
      ap.resetXPath(); 
     } 

     //remove those not on the whitelist 
     XMLModifier xm = new XMLModifier(vn); 
     for (int j=0;j<fib.size();j++){ 
      if (fib.intAt(j)==0x1){ 
       vn.recoverNode(j); 
       xm.remove(); 
      } 
     } 
     xm.output("d:\\xml\\newSchoolInfo.xml");      
    } 
} 
+0

Я тестирую это сейчас. Одна немедленная модификация - мне нужно читать из String и выводить как String. Я предполагаю, что могу использовать ByteArrayInputStream и его эквивалент? –

+0

Это действительно приводит к 'NullPointerException' для меня, когда я вызываю xm.output на' ByteArrayOutputStream'. Мне сложно выполнить выполнение, но похоже, что нет токенов VTD, когда он пытается записать вывод? –

+0

Можете ли вы опубликовать код, который у вас есть? Чтобы читать из строки, вам нужно преобразовать в массив байтов, что легко сделать (getBytes)) ... и вот сообщение в блоге, в котором показано, как анализировать xml из массива байтов ... https://ximpleware.wordpress.com/2016/06/02/parsefile-vs-parse-a-quick-comparison/ –

0

Все Groovy:

import groovy.xml.XmlUtil 

def xml = '''<SchoolInfo RefId="34060F68BE3942F1B1264E6D2CC3C353"> 
    <LocalId>57</LocalId> 
    <SchoolName>Foobar School of Technology</SchoolName> 
    <Principal> 
     <FirstName>Bob</FirstName> 
     <LastName>Smith</LastName> 
    </Principal> 
    <StateProvinceId>34573</StateProvinceId> 
    <LEAInfoRefId>340666687E3942F1B1264E1223453C353</LEAInfoRefId> 
    <PhoneNumberList> 
     <PhoneNumber Type="0096"> 
      <Number>555-832-5555</Number> 
     </PhoneNumber> 
     <PhoneNumber Type="0096"> 
      <Number>555-999-5555</Number> 
     </PhoneNumber> 
    </PhoneNumberList> 
</SchoolInfo>''' 

def node = new XmlParser().parseText(xml) 

def whitelist = [ 'LocalId', 'SchoolName', 'Principal/FirstName', "PhoneNumberList/PhoneNumber/Number" ]*.split('/') 

def void loveRemovalMachine(node, whitelist) { 
    def elementNamesToKeep = whitelist*.head() 
    println "Retaining nodes ${elementNamesToKeep} for node $node" 
    def nodesToRemove = node.'*'.findAll { child -> !elementNamesToKeep.contains(child.name()) } 
    nodesToRemove.each { node.remove it } 
    def nextWhitelist = whitelist*.tail().findAll { it } 
    println "Next level: $nextWhitelist" 
    if (!nextWhitelist) { 
     return 
    } 
    // The "*" operator seems to return text nodes...very stupid. 
    node.'*:*'.each { loveRemovalMachine it, nextWhitelist } 
} 

loveRemovalMachine node, whitelist 

XmlUtil.serialize node 
+0

Выход: '' 'Результат: Foobar школа технологии Боб Smith 555-832 -5555 <Номер телефона Type = "0096"> 555-999-5555 '' ' –

+0

Конечно, это может быть сделано на Java; пожалуйста, сообщите, хотите ли вы такой образец кода. Это та же идея: рекурсивный метод, который очищает DOM. Это просто будет многословнее на Java. –