2013-09-04 2 views
1

У меня есть куча XML-документов, которые содержат личную информацию, которую мне нужно заменить поддельными данными. Узел Person содержит следующие элементы:Быстрая замена значений узлов XML

  • uuid - обязательно, не следует касаться.
  • ПгвЬЫате - опционально
  • LastName - опционально
  • адрес - по желанию
  • PersonId - требуется

Человек может появляться много раз, и в этом случае следует использовать одни и те же поддельные данные, т.е. если два узла Person имеют одинаковый идентификатор personID, они должны получать одинаковый поддельный идентификатор.

Я реализовал некоторый Java-код, который строит дерево DOM из строки XML и заменяет узлы, прежде чем записывать их обратно в строку. Это прекрасно работает, но поскольку у меня так много документов, мне было интересно, есть ли более быстрый подход. Может быть, через регулярные выражения или XSLT или что-то еще?

Вот пример документа:

<ADocument> 
    <Stuff> 
    ... 
    </Stuff> 
    <OtherStuff> 
    ... 
    </OtherStuff> 
    <Person> 
    <uuid>11111111-1111-1111-1111-111111111111</uuid> 
    <firstName>Some</firstName> 
    <lastName>Person</lastName> 
    <personID>111111111111</personID> 
    </Person> 
    <Person> 
    <uuid>22222222-2222-2222-2222-222222222222</uuid> 
    <firstName>Another Person</firstName> 
    <address>Main St. 2</address> 
    <personID>222222222222</personID> 
    </Person> 
    <Person> 
    <uuid>33333333-3333-3333-3333-333333333333</uuid> 
    <firstName>Some</firstName> 
    <lastName>Person</lastName> 
    <personID>111111111111</personID> 
    </Person> 
    <MoreStuff> 
    ... 
    </MoreStuff> 
</ADocument> 

И это моя текущая реализация:

public String replaceWithFalseData(String xmlInstance) { 
    Document dom = toDOM(xmlInstance); 

    XPathExpression xPathExpression = XPathExpressionFactory.createXPathExpression("//Person"); 
    List<Node> nodeList = xPathExpression.evaluateAsNodeList(dom); 

    for(Node personNode : nodeList) { 
     Map<String, Node> childNodes = getChildNodes(personNode); 
     String personID = childNodes.get("personID").getTextContent(); 
     // Retrieve a cached fake person using the ID, or create a new one if none exists. 
     Person fakePerson = getFakePerson(personID); 

     setIfExists(childNodes.get("firstName"), fakePerson.getFirstName()); 
     setIfExists(childNodes.get("lastName"), fakePerson.getLastName()); 
     setIfExists(childNodes.get("address"), fakePerson.getAddress()); 
     setIfExists(childNodes.get("personID"), fakePerson.getPersonID()); 
    } 

    return toString(dom); 
} 

public Map<String, Node> getChildNodes(Node parent) { 
    Map<String, Node> childNodes = new HashMap<String, Node>(); 
    for(Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) { 
     if(child.getLocalName() != null) { 
      childNodes.put(child.getLocalName(), child); 
     } 
    } 
    return childNodes; 
} 

public void setIfExists(Node node, String value) { 
    if(node != null) { 
     node.setTextContent(value); 
    } 
} 
+0

«fast» Вы хотите сказать, более совершенный или элегантный? –

+0

Каковы правила создания фальшивых атрибутов firstName, lastName, address и personId? –

+0

@DioF более совершенный – aznan

ответ

0

Спасибо всем, кто внес свой вклад! Я провел тест производительности на множестве 2000 XML-документов, используя мою реализацию DOM, реализацию StAX Сергея и реализацию XSLT Бена, а также другую собственную реализацию, используя регулярные выражения. Результаты вышли следующим образом:

  • DOM: 23,93s
  • StAX: 20,37s
  • XSLT: 83,52s
  • Regex: 7,83s

И здесь является зарегистрированным победителем:

public String replaceWithFalseData(String xmlInstance) { 
    Pattern personPattern = Pattern.compile("<Person>.*?</Person>", Pattern.DOTALL); 
    Matcher personMatcher = personPattern.matcher(xmlInstance); 
    StringBuffer xmlBuffer = new StringBuffer(); 

    while(personMatcher.find()) { 
     String personXml = personMatcher.group(); 

     Pattern idPattern = Pattern.compile("<personID>(.*)</personID>"); 
     Matcher idMatcher = idPattern.matcher(personXml); 
     idMatcher.find(); 
     String id = idMatcher.group(1); 
     Person fakePerson = getFakePerson(id); 

     personXml = personXml.replaceFirst("<firstName>.*</firstName>", 
       "<firstName>" + fakePerson.getFirstName() + "</firstName>"); 

     personXml = personXml.replaceFirst("<lastName>.*</lastName>", 
       "<lastName>" + fakePerson.getLastName() + "</lastName>"); 

     personXml = personXml.replaceFirst("<address>.*</address>", 
       "<address>" + fakePerson.getAddress() + "</address>"); 

     personXml = personXml.replaceFirst("<personID>.*</personID>", 
       "<personID>" + fakePerson.getPersonID() + "</personID>"); 

     personMatcher.appendReplacement(xmlBuffer, personXml); 
    } 

    personMatcher.appendTail(xmlBuffer); 
    return xmlBuffer.toString(); 
} 
+2

Это может быть быстро, но решения на основе регулярных выражений не являются надежными. Джефф Этвуд объясняет, почему: http://blog.codinghorror.com/parsing-html-the-cthulhu-way/ (HTML или XML не имеет значения) – mindas

2

Вы используете API на основе DOM. Быстрее замена может быть достигнуто с Streaming API для XML (StAX), который во многих случаях может опережать DOM на основе API: StAX versus DOM

DOM API занимает больше памяти, чем StAX, что может снизить производительность, но проще для использования, чем StAX API.

Рабочий раствор для примера - протестирован на 150 МБ XML-файл, замененный через 10 секунд:

import java.io.BufferedInputStream; 
import java.io.BufferedOutputStream; 
import java.io.FileInputStream; 
import java.io.FileNotFoundException; 
import java.io.FileOutputStream; 
import java.io.FileWriter; 
import java.io.IOException; 
import java.util.ArrayList; 
import java.util.Iterator; 
import java.util.List; 
import javax.xml.stream.XMLEventFactory; 
import javax.xml.stream.XMLEventReader; 
import javax.xml.stream.XMLEventWriter; 
import javax.xml.stream.XMLInputFactory; 
import javax.xml.stream.XMLOutputFactory; 
import javax.xml.stream.XMLStreamException; 
import javax.xml.stream.events.XMLEvent; 


public class ReplaceXmlWithFakeUser 
{ 
    public static void main(String[] args) throws XMLStreamException, IOException 
    { 
    XMLInputFactory inFactory = XMLInputFactory.newInstance(); 
    XMLEventReader eventReader = inFactory.createXMLEventReader(new BufferedInputStream(new FileInputStream("c:\\temp\\persons.xml"))); 
    XMLOutputFactory factory = XMLOutputFactory.newInstance(); 
    XMLEventWriter writer = factory.createXMLEventWriter(new BufferedOutputStream(new FileOutputStream("c:\\temp\\fakePersons.xml"))); 
    XMLEventFactory eventFactory = XMLEventFactory.newInstance(); 
    while (eventReader.hasNext()) 
    { 
     XMLEvent event = eventReader.nextEvent(); 

     if (event.getEventType() == XMLEvent.START_ELEMENT && 
     event.asStartElement().getName().toString().equals("Person")) 
     { 
     //write Person startElement: 
     writer.add(event); 


     /* 
     STEP 1: 
     personId is at the end of Person element. Cannot overwrite firstName and address element with fake data yet. Must call getFakePerson() first. 
     Iterate till you read Person END element and just remember all events within person element which we will overwrite with fake data in step 2. 
     */ 
     Person fakePerson=null; 

     List<XMLEvent> eventsWithinPersonElement = new ArrayList<XMLEvent>(); 

     event = eventReader.nextEvent(); 
     while(!(event.getEventType() == XMLEvent.END_ELEMENT && event.asEndElement().getName().toString().equals("Person"))) 
     { 

      eventsWithinPersonElement.add(event); 

      if(event.getEventType() == XMLEvent.START_ELEMENT && 
       event.asStartElement().getName().toString().equals("personID")) 
      { 
      XMLEvent personIDContentEvent = eventReader.nextEvent(); 

      String personId = personIDContentEvent.asCharacters().toString(); 
      fakePerson = getFakePerson(personId); 

      eventsWithinPersonElement.add(personIDContentEvent); 
      } 

      event = eventReader.nextEvent(); 
     } 
     XMLEvent personEndElement=event; 


     //STEP 2: 
     for (Iterator<XMLEvent> eventWithinPersonElementIterator = eventsWithinPersonElement.iterator(); eventWithinPersonElementIterator.hasNext();) 
     { 
      XMLEvent eventWithinPersonElement = eventWithinPersonElementIterator.next(); 

      writer.add(eventWithinPersonElement); 

      if(eventWithinPersonElement.getEventType() == XMLEvent.START_ELEMENT && 
       eventWithinPersonElement.asStartElement().getName().toString().equals("personID")) 
      { 
      writer.add(eventFactory.createCharacters(fakePerson.personId)); 

      //skip personId event 
      eventWithinPersonElementIterator.next(); 
      } 
      if(eventWithinPersonElement.getEventType() == XMLEvent.START_ELEMENT && 
       eventWithinPersonElement.asStartElement().getName().toString().equals("firstName")) 
      { 
      writer.add(eventFactory.createCharacters(fakePerson.firstName)); 

      //skip real firstName 
      eventWithinPersonElementIterator.next(); 
      } 
      if(eventWithinPersonElement.getEventType() == XMLEvent.START_ELEMENT && 
       eventWithinPersonElement.asStartElement().getName().toString().equals("lastName")) 
      { 
      writer.add(eventFactory.createCharacters(fakePerson.lastName)); 

      //skip real firstName 
      eventWithinPersonElementIterator.next(); 
      } 
      else if(eventWithinPersonElement.getEventType() == XMLEvent.START_ELEMENT && 
       eventWithinPersonElement.asStartElement().getName().toString().equals("address")) 
      { 
      writer.add(eventFactory.createCharacters(fakePerson.address)); 

      //skip real address 
      eventWithinPersonElementIterator.next(); 

      } 
     } 

     writer.add(personEndElement); 
     } 
     else 
     { 
     writer.add(event); 
     } 
    } 
    writer.close(); 
    } 

    private static Person getFakePerson(String personId) 
    { 
    //create simple fake user... 

    Person fakePerson = new Person(); 
    fakePerson.personId = personId; 
    fakePerson.firstName = "fake first name: " + Math.random(); 
    fakePerson.lastName = "fake last name: " + Math.random(); 
    fakePerson.address = "fake address: " + Math.random(); 

    return fakePerson; 
    } 

    static class Person 
    { 
    String personId; 
    String firstName; 
    String lastName; 
    String address; 

    } 
} 

Использование persons.xml в качестве входных данных:

<ADocument> 
    <Stuff> 
     <StuffA></StuffA> 
    </Stuff> 
    <OtherStuff> 
     <OtherStuff> 
      <ABC>yada yada</ABC> 
     </OtherStuff> 
    </OtherStuff> 

    <Person> 
     <uuid>11111111-1111-1111-1111-111111111111</uuid> 
     <firstName>Some</firstName> 
     <lastName>Person</lastName> 
     <personID>111111111111</personID> 
    </Person> 
    <Person> 
     <uuid>22222222-2222-2222-2222-222222222222</uuid> 
     <firstName>Another Person</firstName> 
     <address>Main St. 2</address> 
     <personID>222222222222</personID> 
    </Person> 
    <Person> 
     <uuid>33333333-3333-3333-3333-333333333333</uuid> 
     <firstName>Some</firstName> 
     <lastName>Person</lastName> 
     <personID>111111111111</personID> 
    </Person> 

    <MoreStuff> 
     <foo></foo> 
     <foo>fooo</foo> 
     <foo><bar></bar></foo> 
     <foo> 
      <bar></bar> 
      <bar/> 
      <bar>bb</bar> 
     </foo> 
     <bar/> 
    </MoreStuff> 

</ADocument> 

Производить этот fakePersons.xml результат:

<?xml version="1.0" encoding="UTF-8"?><ADocument> 
    <Stuff> 
     <StuffA></StuffA> 
    </Stuff> 
    <OtherStuff> 
     <OtherStuff> 
      <ABC>yada yada</ABC> 
     </OtherStuff> 
    </OtherStuff> 

    <Person> 
     <uuid>11111111-1111-1111-1111-111111111111</uuid> 
     <firstName>fake first name: 0.9518514637129984</firstName> 
     <lastName>fake last name: 0.3495378044884426</lastName> 
     <personID>111111111111</personID> 
    </Person> 
    <Person> 
     <uuid>22222222-2222-2222-2222-222222222222</uuid> 
     <firstName>fake first name: 0.8945739434355868</firstName> 
     <address>fake address: 0.40784763231471777</address> 
     <personID>222222222222</personID> 
    </Person> 
    <Person> 
     <uuid>33333333-3333-3333-3333-333333333333</uuid> 
     <firstName>fake first name: 0.7863207851479257</firstName> 
     <lastName>fake last name: 0.09918620445731652</lastName> 
     <personID>111111111111</personID> 
    </Person> 

    <MoreStuff> 
     <foo></foo> 
     <foo>fooo</foo> 
     <foo><bar></bar></foo> 
     <foo> 
      <bar></bar> 
      <bar></bar> 
      <bar>bb</bar> 
     </foo> 
     <bar></bar> 
    </MoreStuff> 

</ADocument> 
0

Я не уверен, что XSLT может помочь здесь. Возможно, мои знания в XSLT недостаточно глубоки, но XSLT используется для создания новой структуры XML, основанной на данных существующего XML. Похоже, вы хотите сделать обратное здесь: поддерживать ту же структуру, но обновлять данные на основе динамических значений. Возможно, вам сложно создать такой XSLT. Оптимизация может зависеть от довольно некоторых параметров: количества элементов Person для каждого XML, арифметики равных PersonIds в XML, количества обрабатываемых XML-файлов ... Если вы переходите к большим файлам, вы можете переключиться на реализация SAX для оптимизации потребления памяти. Если вы перешли на большое количество равных персональных идентификаторов в одном XML, вы можете создать некоторую структуру кэширования за вашими поддельными данными, которые вы используете для замены, чтобы уменьшить количество обращений к вашему DOM (вы можете напрямую заменить узел на кешированный узел и перезаписать uuid с оригинальным). Если у вас много файлов с похожими личными идентификаторами, вы можете использовать кеширование с перекрестным XML, если допустимо, что одни и те же поддельные данные могут использоваться в нескольких файлах XML.

Кроме того, я считаю, что вы можете отбросить «setIfExists» на PersonID, поскольку он указан как обязательное поле.

0

Я не могу комментировать относительную производительность, но вот решение XSLT для вашей проблемы.

Следующая таблица стилей XSLT:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/> 

    <!-- Find the position of the first Person with the same personID as this Person. 
     This will be used to provide a unique identifier for that person. --> 
    <xsl:template name="get-position-id"> 
    <xsl:value-of select="count(../../Person[personID=current()/../personID][1]/preceding-sibling::Person)"/> 
    </xsl:template> 

    <!-- For personID elements, we will replace the number with a number based on the position of 
     the first Person with the same personId. --> 
    <xsl:template match="personID"> 
    <xsl:copy> 
     <xsl:variable name="position-id"> 
     <xsl:call-template name="get-position-id"/> 
     </xsl:variable> 
     <xsl:call-template name="create-person-id"> 
     <xsl:with-param name="input" select="$position-id"/> 
     </xsl:call-template> 
    </xsl:copy> 
    </xsl:template> 

    <!-- For elements that match this template, we will replace the text with an arbitrary string 
     appended with a number linking them to a particular personID. --> 
    <xsl:template match="firstName|lastName|address"> 
    <xsl:copy> 
     <xsl:variable name="position-id"> 
     <xsl:call-template name="get-position-id"/> 
     </xsl:variable> 
     <xsl:call-template name="create-fake-string"> 
     <xsl:with-param name="input" select="$position-id"/> 
     </xsl:call-template> 
    </xsl:copy>  
    </xsl:template> 

    <!-- The identity transform. --> 
    <xsl:template match="@*|node()"> 
    <xsl:copy> 
     <xsl:apply-templates select="@*|node()"/> 
    </xsl:copy> 
    </xsl:template> 

    <!-- This template generates a number that can be used to replace personID. --> 
    <xsl:template name="create-person-id"> 
    <xsl:param name="input"/> 
    <!-- Turn the input into a 12-digit number padded by zeroes. --> 
    <xsl:value-of select="format-number($input, '000000000000') "/> 
    </xsl:template> 

    <!-- This template generates a string that can be used to replace data. --> 
    <xsl:template name="create-fake-string"> 
    <xsl:param name="input"/> 
    <!-- Create a string to replace data with, appending the input parameter. --> 
    <xsl:text>FAKEDATA</xsl:text> 
    <xsl:value-of select="$input"/> 
    </xsl:template> 

</xsl:stylesheet> 

производит следующий XML при нанесении на вашем примере документа:

<ADocument> 
    <Stuff> 
    ... 
    </Stuff> 
    <OtherStuff> 
    ... 
    </OtherStuff> 
    <Person> 
    <uuid>11111111-1111-1111-1111-111111111111</uuid> 
    <firstName>FAKEDATA0</firstName> 
    <lastName>FAKEDATA0</lastName> 
    <personID>000000000000</personID> 
    </Person> 
    <Person> 
    <uuid>22222222-2222-2222-2222-222222222222</uuid> 
    <firstName>FAKEDATA1</firstName> 
    <address>FAKEDATA1</address> 
    <personID>000000000001</personID> 
    </Person> 
    <Person> 
    <uuid>33333333-3333-3333-3333-333333333333</uuid> 
    <firstName>FAKEDATA0</firstName> 
    <lastName>FAKEDATA0</lastName> 
    <personID>000000000000</personID> 
    </Person> 
    <MoreStuff> 
    ... 
    </MoreStuff> 
</ADocument> 
Смежные вопросы