2010-04-01 13 views
10

Sto tentando di eseguire query xpath su un documento xhtml. Utilizzo di .NET 3.5.Si è verificato un errore durante l'apertura di DTD extern (w3.org, xhtml1-transitional.dtd). 503 Server non disponibile

Il documento si presenta così:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 

<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> 
    <head> 
    .... 
    </head> 
    <body> 
    ... 
    </body> 
</html> 

Poiché il documento include varie entità char (&nbsp; e così via), ho bisogno di usare la DTD, al fine di caricarlo con un XmlReader. Quindi il mio codice è simile al seguente:

var s = File.OpenRead(fileToRead) 
var reader = XmlReader.Create(s, new XmlReaderSettings{ ProhibitDtd=false }); 

Ma quando ho eseguito questo, restituisce

An error has occurred while opening external DTD ' http://www.w3.org/TR/xhtml1-transitional.dtd ': The remote server returned an error: (503) Server Unavailable.

Ora, so perché sto ottenendo l'errore 503. W3C explained it very clearly.

Ho visto "soluzioni alternative" in cui le persone disattivano semplicemente il DTD. Questo è ciò che può fare ProhibitDtd=true ed elimina l'errore 503.

Ma nel mio caso ciò porta ad altri problemi: l'app non ottiene le definizioni dell'entità e quindi non è un XML ben formato. Come posso convalidare con il DTD e ottenere le definizioni di entità, senza colpire il sito w3.org?


penso NET 4.0 ha un nifty funzionalità built-in per gestire questa situazione: il XmlPreloadedResolver. Ma ho bisogno di una soluzione per .NET 3.5.


correlato:
- java.io.IOException: Server returned HTTP response code: 503

risposta

7

La risposta è, devo fornire il mio XmlResolver. Non penso che questo sia integrato in .NET 3.5. È sconcertante. È anche sconcertante che mi ci sia voluto così tanto per incappare in questo problema. È anche sconcertante che non sia riuscito a trovare qualcun altro che ha già risolto questo problema?

Ok, quindi .. XmlResolver. Ho creato una nuova classe, derivata da XmlResolver e ho superato tre cose chiave: Credenziali (set), ResolveUri e GetEntity.

public sealed class XhtmlResolver : XmlResolver 
{ 
    public override System.Net.ICredentials Credentials 
    { 
     set { throw new NotSupportedException();} 
    } 

    public override object GetEntity(Uri absoluteUri, string role, Type t) 
    { 
     ... 
    } 

    public override Uri ResolveUri(Uri baseUri, string relativeUri) 
    { 
     ... 
    } 
} 

La documentazione su questa roba è un po 'scarsa, così ti dirò quello che ho imparato. L'operazione di questa classe è così: l'XmlReader chiamerà prima ResolveUri, poi, dato un Uri risolto, chiamerà GetEntity. Si prevede che questo metodo restituisca un oggetto di tipo t (passato come parametro). L'ho visto solo per richiedere un System.IO.Stream.

L'idea è di incorporare le copie locali del DTD e le sue dipendenze per XHTML1.0 nell'assieme, utilizzando l'opzione csc.exe /resource e quindi recuperare il flusso per quel reso.

private System.IO.Stream GetStreamForNamedResource(string resourceName) 
{ 
    Assembly a = Assembly.GetExecutingAssembly(); 
    return a.GetManifestResourceStream(resourceName); 
} 

Abbastanza semplice. Questo viene chiamato da GetEntity().

Ma posso migliorarlo. Invece di incorporare i DTD in testo in chiaro, li ho gzipati per primi.Quindi modificare il metodo di cui sopra in questo modo:

private System.IO.Stream GetStreamForNamedResource(string resourceName) 
{ 
    Assembly a = Assembly.GetExecutingAssembly(); 
    return new System.IO.Compression.GZipStream(a.GetManifestResourceStream(resourceName), System.IO.Compression.CompressionMode.Decompress); 
} 

Questo codice apre il flusso di una risorsa incorporata, e restituisce un GZipStream configurato per la decompressione. Il lettore ottiene il DTD in chiaro.

Quello che volevo fare è risolvere solo gli URI per DTD da Xhtml 1.0. Così ho scritto ResolveUri e GetEntity per cercare quei DTD specifici e rispondere affermativamente solo per loro.

Per un documento XHTML con l'istruzione DTD, il flusso è come questo;

  1. XmlReader chiama ResolveUri con il pubblico URI per l'XHTML DTD, che è "-//W3C//DTD XHTML 1.0 Transitional//EN". Se XmlResolver può risolvere, dovrebbe restituire ... un URI valido. Se non può risolvere, dovrebbe gettare. La mia implementazione getta solo per l'URI pubblico.

  2. XmlReader chiama quindi ResolveUri con l'identificatore di sistema per il DTD, che in questo caso è "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd". In questo caso, XhtmlResolver restituisce un Uri valido.

  3. XmlReader chiama quindi GetEntity con quell'URI. XhtmlResolver afferra il flusso di risorse incorporato e lo restituisce.

La stessa cosa accade per le dipendenze - xhtml_lat1.ent e così via. Affinché il risolutore funzioni, tutte queste cose devono essere incorporate.

E sì, se il resolver non può risolvere un URI, è previsto che lanci un'eccezione. Questo non è ufficialmente documentato per quanto ho potuto vedere. Sembra un po 'sorprendente. (Una violazione egregia di the principle of least astonishment). Se invece ResolveUri restituisce null, l'XmlReader chiamerà GetEntity sull'URI null, che ... ah, è senza speranza.


Questo funziona per me. Dovrebbe funzionare per chiunque sia chi esegue l'elaborazione XML su XHTML da .NET. Se si desidera utilizzarlo nelle proprie applicazioni, grab the DLL. Lo zip include il codice sorgente completo. Autorizzato con il numero MS Public License.

È possibile collegarlo alle app XML che armeggiano con XHTML. Utilizzare in questo modo:

// for an XmlDocument... 
System.Xml.XmlDocument doc = new System.Xml.XmlDocument(); 
doc.XmlResolver = new Ionic.Xml.XhtmlResolver(); 
doc.Load(xhtmlFile); 

// for an XmlReader... 
var xmlReaderSettings = new XmlReaderSettings 
    { 
     ProhibitDtd = false, 
     XmlResolver = new XhtmlResolver() 
    }; 
using (var stream = File.OpenRead(fileToRead)) 
{ 
    XmlReader reader = XmlReader.Create(stream, xmlReaderSettings); 
    while (reader.Read()) 
    { 
    ... 
    } 
+0

+1 Grazie per aver postato questo. – ChrisW

3

È possibile disabilitare un XmlReader di aprire eventuali risorse esterne impostando la proprietà XmlReaderSettings.XmlResolver a null.

System.Xml.XmlReaderSettings xmlReaderSettings = new System.Xml.XmlReaderSettings(); 
xmlReaderSettings.XmlResolver = null; 
System.Xml.XmlReader xmlReader = System.Xml.XmlReader.Create(myUrl, xmlReaderSettings); 
+0

Giusto, ma poi, non posso fare la convalida che voglio fare, e senza definizioni di entità, non posso fare query xpath su xhtml. – Cheeso

2

Quando il metodo ResolveUri riceve una richiesta per un modulo di "pubblico" della URI come -//W3C//ELEMENTS XHTML Images 1.0//EN poi fa il tuo metodo di tiro e attendere il successivo URI web-like, che inizia con http://?

invece di buttare, risolvo il pubblico URI al corrispondente http:// URI (e poi nel mio metodo GetEntity ho intercettare le richieste al http:// URI).

Non ho quindi mai da buttare, che credo sia la soluzione giusta.


That's a smart way to do it. How big is your dictionary? The library I pointed you to handles only XHTML 1.0, and there is just one public URI base that would need to be mapped.

sto usando XHTML 1.1, che è 'modulare' quindi devo mappare circa 40 file.

Attenzione che il comportamento del framework potrebbe essere cambiato! Ho una libreria (inclusa la mia classe XhtmlUrlResolver) che è costruita con .NET Framework 2, ma è invocata in modo diverso a seconda che l'applicazione (che utilizza la libreria) sia compilata per .NET 2 o .NET 4.

con .NET 2, quando il mio metodo ResolveUri sempre solo delegata in modo trasparente ad un XmlUrlResolver, allora sarebbe:

  1. Chiedere al ResolveUri pubblico del DTD.
  2. Prova a GetEntity DTD dal disco (tiri uno DirectoryNotFoundException)
  3. Provate a GetEntity DTD da http (che mi piacerebbe servire da risorse locali)
  4. Prova a GetEntity tutti gli altri file da http (che ho' d servire da risorse locali)

Con .NET 4 ci fu una chiamata in più per ogni risorsa:

  • Chiedere di ResolveUri pubblico del sub-risorsa (ad esempio il file *.mod), WHI ch mia implementazione solo delegato a XmlUrlResolver
  • Chiedere di GetEntity pubblico 'risolto' del sub-risorsa, che non è stato realmente risolto affatto, appena avuto un prefisso http simile aggiunto (tiri WebException)

Lanciare tutte quelle WebException rallentando l'elaborazione molto, motivo per cui l'ho rivisitato per cercare una soluzione.

Il tuo suggerimento, che butto da ResolveUri, ha risolto quel problema, per il quale ti ringrazio; ma invece di lanciare, restituire qualcosa da ResolveUri è più elegante (e un po 'più veloce: 40 meno eccezioni).

Ecco il mio codice sorgente corrente.

using System; 
using System.Collections.Generic; 
using System.Text; 

using System.Reflection; 
using System.IO; 
using System.Xml; 

//don't obfuscate the file names of the embedded resources, 
//which are contained in a "Files" subfolder of the project 
[assembly: Obfuscation(Feature = "Apply to ModelText.ModelXml.Files.*: all", Exclude = true, ApplyToMembers = true)] 

namespace ModelText.ModelXml 
{ 
    /// <summary> 
    /// This class provides local (i.e. faster) access to the XHTML DTD. 
    /// </summary> 
    /// <remarks> 
    /// Another way to implement this class is described in MSDN "Customizing the XmlUrlResolver Class" 
    /// which shows as an example a "class XmlCachingResolver" 
    /// and which is implemented using WebRequest and HttpRequestCachePolicy 
    /// </remarks> 
    [System.Reflection.ObfuscationAttribute(Feature = "renaming", ApplyToMembers = true)] 
    public class XhtmlUrlResolver : XmlResolver 
    { 
     XmlUrlResolver m_xmlUrlResolver = new XmlUrlResolver(); 
     Assembly m_assembly = Assembly.GetExecutingAssembly(); 
     public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn) 
     { 
      string uriString = absoluteUri.ToString(); 
      if (s_resources.uriExists(uriString)) 
      { 
       //Console.WriteLine("XhtmlUrlResolver Found {0} -- {1}", uriString, DateTime.Now); 

       //to get the filename of the embedded resource, remove the http: directory 
       //this is OK because the filenames are unique and map 1-to-1 with resource names 
       string filename = uriString.Substring(uriString.LastIndexOf('/') + 1); 
       Stream stream = m_assembly.GetManifestResourceStream(typeof(XhtmlUrlResolver), "Files." + filename); 
       return stream; 
      } 

      //Console.WriteLine("XhtmlUrlResolver Throwing {0} -- {1}", uriString, DateTime.Now); 
      throw new ArgumentException(); 
      //Console.WriteLine("XhtmlUrlResolver Getting {0} -- {1}", uriString, DateTime.Now); 
      //object o = m_xmlUrlResolver.GetEntity(absoluteUri, role, ofObjectToReturn); 
      //Console.WriteLine("XhtmlUrlResolver Got {0} -- {1}", uriString, DateTime.Now); 
      //return o; 
     } 

     public override Uri ResolveUri(Uri baseUri, string relativeUri) 
     { 
      string resolved = s_resources.resolve(relativeUri); 
      if (resolved != null) 
      { 
       //Console.WriteLine("ResolveUri resolving {0}, {1} -- {2}", baseUri, relativeUri, DateTime.Now); 
       return new Uri(resolved); 
      } 

      //Console.WriteLine("ResolveUri passing {0}, {1} -- {2}", baseUri, relativeUri, DateTime.Now); 
      return m_xmlUrlResolver.ResolveUri(baseUri, relativeUri); 
     } 
     public override System.Net.ICredentials Credentials 
     { 
      set { m_xmlUrlResolver.Credentials = value; } 
     } 

     static Resources s_resources = new Resources(); 

     class Resources 
     { 
      Dictionary<string, string> m_publicToUri = new Dictionary<string, string>(); 

      internal Resources() 
      { 
       for (int i = 0, n = array.GetLength(0); i < n; ++i) 
       { 
        m_publicToUri.Add(array[i, 1], array[i, 0]); 
       } 
      } 

      internal bool uriExists(string absoluteUri) 
      { 
       return m_publicToUri.ContainsValue(absoluteUri); 
      } 

      internal string resolve(string relativeUri) 
      { 
       string resolved; 
       if (m_publicToUri.TryGetValue(relativeUri, out resolved)) 
       { 
        return resolved; 
       } 
       return null; 
      } 

      static string[,] array = { 
       { "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd", "-//W3C//DTD XHTML 1.1//EN" }, 

       { "http://www.w3.org/MarkUp/DTD/xhtml11-model-1.mod", "-//W3C//ENTITIES XHTML 1.1 Document Model 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-attribs-1.mod", "-//W3C//ENTITIES XHTML Common Attributes 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-base-1.mod", "-//W3C//ELEMENTS XHTML Base Element 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-bdo-1.mod", "-//W3C//ELEMENTS XHTML BIDI Override Element 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-blkphras-1.mod", "-//W3C//ELEMENTS XHTML Block Phrasal 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-blkpres-1.mod", "-//W3C//ELEMENTS XHTML Block Presentation 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-blkstruct-1.mod", "-//W3C//ELEMENTS XHTML Block Structural 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-charent-1.mod", "-//W3C//ENTITIES XHTML Character Entities 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-csismap-1.mod", "-//W3C//ELEMENTS XHTML Client-side Image Maps 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-datatypes-1.mod", "-//W3C//ENTITIES XHTML Datatypes 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-edit-1.mod", "-//W3C//ELEMENTS XHTML Editing Elements 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-events-1.mod", "-//W3C//ENTITIES XHTML Intrinsic Events 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-form-1.mod", "-//W3C//ELEMENTS XHTML Forms 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-framework-1.mod", "-//W3C//ENTITIES XHTML Modular Framework 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-hypertext-1.mod", "-//W3C//ELEMENTS XHTML Hypertext 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-image-1.mod", "-//W3C//ELEMENTS XHTML Images 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-inlphras-1.mod", "-//W3C//ELEMENTS XHTML Inline Phrasal 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-inlpres-1.mod", "-//W3C//ELEMENTS XHTML Inline Presentation 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-inlstruct-1.mod", "-//W3C//ELEMENTS XHTML Inline Structural 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-inlstyle-1.mod", "-//W3C//ELEMENTS XHTML Inline Style 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-lat1.ent", "-//W3C//ENTITIES Latin 1 for XHTML//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-link-1.mod", "-//W3C//ELEMENTS XHTML Link Element 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-list-1.mod", "-//W3C//ELEMENTS XHTML Lists 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-meta-1.mod", "-//W3C//ELEMENTS XHTML Metainformation 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-object-1.mod", "-//W3C//ELEMENTS XHTML Embedded Object 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-param-1.mod", "-//W3C//ELEMENTS XHTML Param Element 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-pres-1.mod", "-//W3C//ELEMENTS XHTML Presentation 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-qname-1.mod", "-//W3C//ENTITIES XHTML Qualified Names 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-script-1.mod", "-//W3C//ELEMENTS XHTML Scripting 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-special.ent", "-//W3C//ENTITIES Special for XHTML//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-ssismap-1.mod", "-//W3C//ELEMENTS XHTML Server-side Image Maps 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-struct-1.mod", "-//W3C//ELEMENTS XHTML Document Structure 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-style-1.mod", "-//W3C//ELEMENTS XHTML Style Sheets 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-symbol.ent", "-//W3C//ENTITIES Symbols for XHTML//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-table-1.mod", "-//W3C//ELEMENTS XHTML Tables 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-target-1.mod", "-//W3C//ELEMENTS XHTML Target 1.0//EN" }, 
       { "http://www.w3.org/MarkUp/DTD/xhtml-text-1.mod", "-//W3C//ELEMENTS XHTML Text 1.0//EN" }, 

       { "http://www.w3.org/TR/ruby/xhtml-ruby-1.mod", "-//W3C//ELEMENTS XHTML Ruby 1.0//EN" } 
      }; 
     } 
    } 
} 
+0

sì, il XhtmlResolver I ha scritto quando viene presentato con un URI non http. – Cheeso

+0

@Cheeso Sì. Ho capito che invece di lanciare, 'ResolveUri' può restituire l'Uri di tipo http. Potrebbe anche essere ciò per cui è, e meglio che lanciare. Così come avere una cache locale dei file, tengo un dizionario delle traduzioni Uri da pubblico a http. – ChrisW

+0

Questo è un modo intelligente per farlo. Quanto è grande il tuo dizionario? La libreria ti indicava come gestire solo XHTML 1.0 e c'è solo una base URI pubblica che dovrebbe essere mappata. – Cheeso

Problemi correlati