2016-06-28 4 views
2

Существует ли простой способ Java способ «перемещать» все объявления пространства имен XML документа XML в корневой элемент? Из-за ошибки в реализации синтаксического анализа неназванной огромной компании мне необходимо программно переписать наши хорошо сформированные и действительные запросы RPC таким образом, чтобы корневой элемент объявлял все используемые пространства имен.Преобразование XML для объявления всех пространств имен в корневом элементе

Не OK:

<document-element xmlns="uri:ns1"> 
    <foo> 
     <bar xmlns="uri:ns2" xmlns:ns3="uri:ns3"> 
      <ns3:foobar/> 
      <ns1:sigh xmlns:ns1="uri:ns1"/> 
     </bar> 
    </foo> 
</document-element> 

OK:

<document-element xmlns="uri:ns1" xmlns:ns1="uri:ns1" xmlns:ns2="uri:ns2" xmlns:ns3="uri:ns3"> 
    <foo> 
     <ns2:bar> 
      <ns3:foobar/> 
      <ns1:sigh/> 
     </ns2:bar> 
    </foo> 
</document-element> 

Родовые имена для отсутствует префиксы являются приемлемыми. По умолчанию пространство имен может оставаться или быть заменено/добавлено до тех пор, пока оно определено в корневом элементе. Я действительно не возражаю против того, какая конкретная технология XML используется для достижения этого (я бы предпочел избежать DOM, хотя).

Чтобы уточнить, this answer относится к тому, что я хотел бы достичь в качестве объявления пространств имен в пределах повторного объявления корневого элемента объема (весь документ) на корневом элементе. По сути, связанный с этим вопрос задает вопрос, почему о, зачем кому-то реализовать то, что мне сейчас нужно, чтобы обойти.

+0

Вероятно, нет «просто» ява функции, потому что в теории не может быть тот же префикс с различными пространствами имен URI, внутри различных поддеревьев документа. Зачем вообще хранить пространства имен? Вы можете использовать преобразование xslt identity, которое создает ' '... во всех местах и ​​имеет документ, который не имеет пространств имен, поэтому по определению все они являются объявлено ;-) –

+0

@Stefan, избавляясь от пространств имен вообще не является вариантом, так как это было бы нестандартно. Я оставлю это в домене неназванной огромной компании. – predi

+0

Термин «метод», который я использовал, понимается как «научный метод», а не как объявление функции. – predi

ответ

0

Написал двухпроходный считыватель/запись StAX, который достаточно прост.

import java.io.*; 
import java.util.*; 
import javax.xml.stream.*; 
import javax.xml.stream.events.*; 

public class NamespacesToRoot { 

    private static final String GENERATED_PREFIX = "pfx"; 

    private final XMLInputFactory inputFact; 
    private final XMLOutputFactory outputFact; 
    private final XMLEventFactory eventFactory; 

    private NamespacesToRoot() { 
     inputFact = XMLInputFactory.newInstance(); 
     outputFact = XMLOutputFactory.newInstance(); 
     eventFactory = XMLEventFactory.newInstance(); 
    } 

    public String transform(String xmlString) throws XMLStreamException { 
     Map<String, String> pfxToNs = new HashMap<String, String>(); 
     XMLEventReader reader = null; 

     // first pass - analyze 
     try { 
      if (xmlString == null || xmlString.isEmpty()) { 
       throw new IllegalArgumentException("xmlString is null or empty"); 
      } 
      StringReader stringReader = new StringReader(xmlString); 
      XMLStreamReader streamReader = inputFact.createXMLStreamReader(stringReader); 
      reader = inputFact.createXMLEventReader(streamReader); 

      while (reader.hasNext()) { 
       XMLEvent event = reader.nextEvent();     
       if (event.isStartElement()) { 
        buildNamespaces(event, pfxToNs); 
       } 
      } 

      System.out.println(pfxToNs); 
     } finally { 
      try { 
       if (reader != null) { 
        reader.close(); 
       } 
      } catch (XMLStreamException ex) { 
      } 
     } 

     // reverse mapping, also gets rid of duplicates 
     Map<String, String> nsToPfx = new HashMap<String, String>(); 
     for (Map.Entry<String, String> entry : pfxToNs.entrySet()) { 
      nsToPfx.put(entry.getValue(), entry.getKey()); 
     } 
     List<Namespace> namespaces = new ArrayList<Namespace>(nsToPfx.size()); 
     for (Map.Entry<String, String> entry : nsToPfx.entrySet()) { 
      namespaces.add(eventFactory.createNamespace(entry.getValue(), entry.getKey())); 
     } 

     // second pass - rewrite 
     XMLEventWriter writer = null; 
     try { 
      StringWriter stringWriter = new StringWriter(); 
      writer = outputFact.createXMLEventWriter(stringWriter); 

      StringReader stringReader = new StringReader(xmlString); 
      XMLStreamReader streamReader = inputFact.createXMLStreamReader(stringReader); 
      reader = inputFact.createXMLEventReader(streamReader); 

      boolean rootElement = true; 
      while (reader.hasNext()) { 
       XMLEvent event = reader.nextEvent();     
       if (event.isStartElement()) { 
        StartElement origStartElement = event.asStartElement(); 
        String prefix = nsToPfx.get(origStartElement.getName().getNamespaceURI()); 
        String namespace = origStartElement.getName().getNamespaceURI(); 
        String localName = origStartElement.getName().getLocalPart(); 
        Iterator attributes = origStartElement.getAttributes(); 
        Iterator namespaces_; 
        if (rootElement) { 
         namespaces_ = namespaces.iterator();       
         rootElement = false; 
        } else { 
         namespaces_ = null; 
        } 
        writer.add(eventFactory.createStartElement(
          prefix, namespace, localName, attributes, namespaces_)); 
       } else { 
        writer.add(event); 
       }     
      } 

      writer.flush(); 

      return stringWriter.toString(); 
     } finally { 
      try { 
       if (reader != null) { 
        reader.close(); 
       } 
      } catch (XMLStreamException ex) { 
      } 
      try { 
       if (writer != null) { 
        writer.close(); 
       } 
      } catch (XMLStreamException ex) { 
      } 
     } 
    } 

    private void buildNamespaces(XMLEvent event, Map<String, String> pfxToNs) { 
     System.out.println("el: " + event); 
     StartElement startElement = event.asStartElement(); 
     Iterator nsIternator = startElement.getNamespaces(); 
     while (nsIternator.hasNext()) { 
      Namespace nsAttr = (Namespace) nsIternator.next(); 
      if (nsAttr.isDefaultNamespaceDeclaration()) { 
       System.out.println("need to generate a prefix for " + nsAttr.getNamespaceURI()); 
       generatePrefix(nsAttr.getNamespaceURI(), pfxToNs); 
      } else { 
       System.out.println("add prefix binding for " + nsAttr.getPrefix() + " --> " + nsAttr.getNamespaceURI()); 
       addPrefix(nsAttr.getPrefix(), nsAttr.getNamespaceURI(), pfxToNs); 
      } 
     } 
    } 

    private void generatePrefix(String namespace, Map<String, String> pfxToNs) { 
     int i = 1; 
     String prefix = GENERATED_PREFIX + i; 
     while (pfxToNs.keySet().contains(prefix)) { 
      i++; 
      prefix = GENERATED_PREFIX + i; 
     } 
     pfxToNs.put(prefix, namespace); 
    } 

    private void addPrefix(String prefix, String namespace, Map<String, String> pfxToNs) { 
     String existingNs = pfxToNs.get(prefix); 
     if (existingNs != null) { 
      if (existingNs.equals(namespace)) { 
       // nothing to do 
      } else { 
       // prefix clash, need to rename this prefix or reuse an existing 
       // one 
       if (pfxToNs.values().contains(namespace)) { 
        // reuse matching prefix 
       } else { 
        // rename 
        generatePrefix(namespace, pfxToNs); 
       }     
      } 
     } else { 
      // need to add this prefix 
      pfxToNs.put(prefix, namespace); 
     }   
    } 

    public static void main(String[] args) throws XMLStreamException { 
     String xmlString = "" + 
"<document-element xmlns=\"uri:ns1\" attr=\"1\">\n" + 
" <foo>\n" + 
"  <bar xmlns=\"uri:ns2\" xmlns:ns3=\"uri:ns3\">\n" + 
"   <ns3:foobar ns3:attr1=\"meh\" />\n" + 
"   <ns1:sigh xmlns:ns1=\"uri:ns1\"/>\n" + 
"  </bar>\n" + 
" </foo>\n" + 
"</document-element>"; 
     System.out.println(xmlString); 
     NamespacesToRoot transformer = new NamespacesToRoot(); 
     System.out.println(transformer.transform(xmlString)); 
    } 
} 

Обратите внимание, что это просто быстрый пример кода, который мог бы использовать некоторые хитрости, но это также хорошее начало для тех, кто с подобной проблемой.

+0

Ищем лучшие альтернативы. – predi

+0

Что делать, если в одном документе есть несколько объявлений xmlns: ns1, вам нужно беспокоиться об этом (в котором XML определенно borken, когда вы перемещаете все объявления в корневой элемент –

+0

@ vtd-xml-author, если несколько пространств имен связанный с одним и тем же префиксом, сначала нужно подать заявку.Неважно, если вместо оригиналов используются родовые префиксы. – predi

1

Ниже приведено простое приложение для повторного объявления пространства имен ... на основе XPath и VTD-XML.

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

public class moveNSDeclaration { 

    public static void main(String[] args) throws IOException, VTDException{ 
     // TODO Auto-generated method stub 
     VTDGen vg = new VTDGen(); 
     String xml="<document-element xmlns=\"uri:ns1\">\n"+ 
       "<foo>\n"+ 
       "<bar xmlns=\"uri:ns2\" xmlns:ns3=\"uri:ns3\">\n"+ 
       "<ns3:foobar/>\n"+ 
       "<ns1:sigh xmlns:ns1=\"uri:ns1\"/>\n"+ 
       "</bar>\n"+ 
       "</foo>\n"+ 
       "</document-element>\n"; 
     vg.setDoc(xml.getBytes()); 
     vg.parse(false); // namespace unaware to all name space nodes addressable using xpath @* 
     VTDNav vn = vg.getNav(); 
     XMLModifier xm = new XMLModifier(vn); 
     FastIntBuffer fib = new FastIntBuffer(); 
     ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
     // get the index value of xmlns declaration of root element 
     AutoPilot ap =new AutoPilot (vn); 
     ap.selectXPath("//@*"); 
     int i=0; 
     //remove all ns node under root element 
     //save those nodes to be re-inserted into the root element up on verification of uniqueness 

     while((i=ap.evalXPath())!=-1){ 
      if (vn.getTokenType(i)==VTDNav.TOKEN_ATTR_NS){ 
       xm.remove(); //remove all ns node 
       fib.append(i); 
      } 
     } 

     //remove redundant ns nodes 
     for (int j=0;j<fib.size();j++){ 
      if (fib.intAt(j)!=-1){ 
       for (i=j+1;i<fib.size();i++){ 
        if (fib.intAt(i)!=-1) 
        if (vn.compareTokens(fib.intAt(j), vn, fib.intAt(i))==0){ 
         fib.modifyEntry(i, -1); 
        } 
       } 
      } 
     } 


     // compose a string to insert back into the root element containing all subordinate ns nodes 

     for (int j=0;j<fib.size();j++){ 
      if (fib.intAt(j)!=-1){ 
       int os = vn.getTokenOffset(fib.intAt(j)); 
       int len = vn.getTokenOffset(fib.intAt(j)+1)+vn.getTokenLength(fib.intAt(j)+1)+1-os; 
       //System.out.println(" os len "+ os + " "+len); 
       //System.out.println(vn.toString(os,len)); 
       baos.write(" ".getBytes()); 
       baos.write(vn.getXML().getBytes(),os,len); 
      } 
     } 
     byte[] attrBytes = baos.toByteArray(); 
     vn.toElement(VTDNav.ROOT); 
     xm.insertAttribute(attrBytes); 
     //System.out.println(baos.toString()); 
     baos.reset(); 
     xm.output(baos); 
     System.out.println(baos.toString()); 
    } 
} 

Выход выглядит

<document-element xmlns="uri:ns2" xmlns:ns3="uri:ns3" xmlns:ns1="uri:ns1" > 
<foo> 
<bar > 
<ns3:foobar/> 
<ns1:sigh /> 
</bar> 
</foo> 
</document-element> 
Смежные вопросы