diff --git a/docs/doxygen/overviews/xrc_format.h b/docs/doxygen/overviews/xrc_format.h
index a23fba23d1..448c32c304 100644
--- a/docs/doxygen/overviews/xrc_format.h
+++ b/docs/doxygen/overviews/xrc_format.h
@@ -341,8 +341,8 @@ or translations are done.
@subsection overview_xrcformat_type_bitmap Bitmap
Bitmap properties contain specification of a single bitmap or icon. In the most
-basic form, their text value is simply a relative filename (or another
-wxFileSystem URL) of the bitmap to use. For example:
+basic form, their text value is simply a relative URL of the bitmap to use.
+For example:
@code
@endcode
The value is interpreted as path relative to the location of XRC file where the
-reference occurs.
+reference occurs, but notice that it is still an URL and not just a filename,
+which means that the characters special in the URLs, such as @c '#' must be
+percent-encoded, e.g. here is the correct way to specify a bitmap with the path
+@c "images/#1/tool.png" in XRC:
+@code
+
+@endcode
Bitmap file paths can include environment variables that are expanded if
wxXRC_USE_ENVVARS was passed to the wxXmlResource constructor.
diff --git a/include/wx/xrc/xmlres.h b/include/wx/xrc/xmlres.h
index 6507afe6d9..603b35eff4 100644
--- a/include/wx/xrc/xmlres.h
+++ b/include/wx/xrc/xmlres.h
@@ -131,6 +131,13 @@ public:
// Loads all XRC files from a directory.
bool LoadAllFiles(const wxString& dirname);
+ // Loads resources from the given XML document, taking ownership of it.
+ //
+ // The name argument is only used to Unload() the document later here and
+ // doesn't need to be an existing filename at all (but should be unique if
+ // specified, otherwise it's just synthesized internally).
+ bool LoadDocument(wxXmlDocument* doc, const wxString& name = wxString());
+
// Unload resource from the given XML file (wildcards not allowed)
bool Unload(const wxString& filename);
@@ -320,6 +327,9 @@ protected:
// wxXmlDocument (which will be owned by caller) on success or NULL.
wxXmlDocument *DoLoadFile(const wxString& file);
+ // Load XRC from the given document and returns true on success.
+ bool DoLoadDocument(const wxXmlDocument& doc);
+
// Scans the resources list for unloaded files and loads them. Also reloads
// files that have been modified since last loading.
bool UpdateResources();
diff --git a/interface/wx/xrc/xmlres.h b/interface/wx/xrc/xmlres.h
index 9feca5980f..9a87d7db3c 100644
--- a/interface/wx/xrc/xmlres.h
+++ b/interface/wx/xrc/xmlres.h
@@ -233,12 +233,58 @@ public:
*/
bool Load(const wxString& filemask);
+ /**
+ Load resources from the XML document containing them.
+
+ This can be useful when XRC contents comes from some place other than a
+ file or, more generally, an URL, as it can still be read into a
+ wxMemoryInputStream and then wxXmlDocument can be created from this
+ stream and used with this function.
+
+ For example:
+ @code
+ const char* const xrc_data = ...; // Retrieve it from wherever.
+ wxMemoryInputStream mis(xrc_data, strlen(xrc_data));
+ wxScopedPtr xmlDoc(new wxXmlDocument(mis, "UTF-8"));
+ if ( !xmlDoc->IsOk() )
+ {
+ ... handle invalid XML here ...
+ return;
+ }
+
+ if ( !wxXmlResource::Get()->LoadDocument(xmlDoc.release()) )
+ {
+ ... handle invalid XRC here ...
+ return;
+ }
+
+ ... use the just loaded XRC as usual ...
+ @endcode
+
+ @param doc A valid, i.e. non-null, document pointer ownership of which
+ is passed to wxXmlResource, i.e. this pointer can't be used after
+ this function rteturns.
+ @param name The name argument is optional, but may be provided if you
+ plan to call Unload() later. It doesn't need to be an existing file
+ or even conform to the usual form of file names as it is not
+ interpreted in any way by wxXmlResource, but it should be unique
+ among the other documents and file names used if specified.
+ @return @true on success, @false if the document couldn't be loaded
+ (note that @a doc is still destroyed in this case to avoid memory
+ leaks).
+
+ @see Load(), LoadFile()
+
+ @since 3.1.6
+ */
+ bool LoadDocument(wxXmlDocument* doc, const wxString& name = wxString());
+
/**
Simpler form of Load() for loading a single XRC file.
@since 2.9.0
- @see Load(), LoadAllFiles()
+ @see Load(), LoadAllFiles(), LoadDocument()
*/
bool LoadFile(const wxFileName& file);
diff --git a/src/common/filesys.cpp b/src/common/filesys.cpp
index 2e2337a198..42b2a66717 100644
--- a/src/common/filesys.cpp
+++ b/src/common/filesys.cpp
@@ -478,16 +478,17 @@ wxFSFile* wxFileSystem::OpenFile(const wxString& location, int flags)
m_LastName.clear();
// try relative paths first :
- if (meta != wxT(':'))
+ if (meta != wxT(':') && !m_Path.empty())
{
+ const wxString fullloc = m_Path + loc;
node = m_Handlers.GetFirst();
while (node)
{
wxFileSystemHandler *h = (wxFileSystemHandler*) node -> GetData();
- if (h->CanOpen(m_Path + loc))
+ if (h->CanOpen(fullloc))
{
- s = MakeLocal(h)->OpenFile(*this, m_Path + loc);
- if (s) { m_LastName = m_Path + loc; break; }
+ s = MakeLocal(h)->OpenFile(*this, fullloc);
+ if (s) { m_LastName = fullloc; break; }
}
node = node->GetNext();
}
diff --git a/src/xrc/xmlres.cpp b/src/xrc/xmlres.cpp
index e6248d4716..65918e929f 100644
--- a/src/xrc/xmlres.cpp
+++ b/src/xrc/xmlres.cpp
@@ -74,17 +74,43 @@ wxDateTime GetXRCFileModTime(const wxString& filename)
// name.
static void XRCID_Assign(const wxString& str_id, int value);
+// Flags indicating whether the XRC contents was loaded from file or URL, more
+// generally: this is usually true but is not set for the records created
+// directly from wxXmlDocument.
+namespace XRCWhence
+{
+
+enum
+{
+ From_URL = 0,
+ From_Doc = 1
+};
+
+} // namespace // XRCWhence
+
class wxXmlResourceDataRecord
{
public:
// Ctor takes ownership of the document pointer.
wxXmlResourceDataRecord(const wxString& File_,
- wxXmlDocument *Doc_
+ wxXmlDocument *Doc_,
+ int flags = XRCWhence::From_URL
)
: File(File_), Doc(Doc_)
{
#if wxUSE_DATETIME
- Time = GetXRCFileModTime(File);
+ switch ( flags )
+ {
+ case XRCWhence::From_URL:
+ Time = GetXRCFileModTime(File);
+ break;
+
+ case XRCWhence::From_Doc:
+ // Leave Time invalid.
+ break;
+ }
+#else
+ wxUnusedVar(flags);
#endif
}
@@ -664,9 +690,14 @@ bool wxXmlResource::UpdateResources()
if ( m_flags & wxXRC_NO_RELOADING )
continue;
+ // And we don't do it for the records that were not loaded from a
+ // file/URI (or at least not directly) in the first place.
+ if ( !rec->Time.IsValid() )
+ continue;
+
// Otherwise check its modification time if we can.
#if wxUSE_DATETIME
- const wxDateTime lastModTime = GetXRCFileModTime(rec->File);
+ wxDateTime lastModTime = GetXRCFileModTime(rec->File);
if ( lastModTime.IsValid() && lastModTime <= rec->Time )
#else // !wxUSE_DATETIME
@@ -749,7 +780,15 @@ wxXmlDocument *wxXmlResource::DoLoadFile(const wxString& filename)
return NULL;
}
- wxXmlNode * const root = doc->GetRoot();
+ if (!DoLoadDocument(*doc))
+ return NULL;
+
+ return doc.release();
+}
+
+bool wxXmlResource::DoLoadDocument(const wxXmlDocument& doc)
+{
+ wxXmlNode * const root = doc.GetRoot();
if (root->GetName() != wxT("resource"))
{
ReportError
@@ -757,7 +796,7 @@ wxXmlDocument *wxXmlResource::DoLoadFile(const wxString& filename)
root,
"invalid XRC resource, doesn't have root node "
);
- return NULL;
+ return false;
}
long version;
@@ -778,7 +817,34 @@ wxXmlDocument *wxXmlResource::DoLoadFile(const wxString& filename)
PreprocessForIdRanges(root);
wxIdRangeManager::Get()->FinaliseRanges(root);
- return doc.release();
+ return true;
+}
+
+bool wxXmlResource::LoadDocument(wxXmlDocument* doc, const wxString& name)
+{
+ wxCHECK_MSG( doc, false, wxS("must have a valid document") );
+
+ if ( !DoLoadDocument(*doc) )
+ {
+ // Still avoid memory leaks.
+ delete doc;
+ return false;
+ }
+
+ // We need to use something instead of the file name, so if we were not
+ // given a name synthesize something ourselves.
+ wxString docname = name;
+ if ( docname.empty() )
+ {
+ static unsigned long s_xrcDocument = 0;
+
+ // Make it look different from any real file name.
+ docname = wxString::Format(wxS(""), ++s_xrcDocument);
+ }
+
+ Data().push_back(new wxXmlResourceDataRecord(docname, doc, XRCWhence::From_Doc));
+
+ return true;
}
wxXmlNode *wxXmlResource::DoFindResource(wxXmlNode *parent,
diff --git a/tests/xml/xrctest.cpp b/tests/xml/xrctest.cpp
index 04e1a435ff..d5a0ee0514 100644
--- a/tests/xml/xrctest.cpp
+++ b/tests/xml/xrctest.cpp
@@ -20,13 +20,18 @@
#if wxUSE_XRC
#include "wx/fs_inet.h"
+#include "wx/imagxpm.h"
#include "wx/xml/xml.h"
+#include "wx/scopedptr.h"
#include "wx/sstream.h"
#include "wx/wfstream.h"
#include "wx/xrc/xmlres.h"
+#include "wx/xrc/xh_bmp.h"
#include
+#include "testfile.h"
+
// ----------------------------------------------------------------------------
// helpers to create/save some xrc
// ----------------------------------------------------------------------------
@@ -36,9 +41,19 @@ namespace
static const char *TEST_XRC_FILE = "test.xrc";
+void LoadXrcFrom(const wxString& xrcText)
+{
+ wxStringInputStream sis(xrcText);
+ wxScopedPtr xmlDoc(new wxXmlDocument(sis, "UTF-8"));
+ REQUIRE( xmlDoc->IsOk() );
+
+ // Load the xrc we've just created
+ REQUIRE( wxXmlResource::Get()->LoadDocument(xmlDoc.release(), TEST_XRC_FILE) );
+}
+
// I'm hard-wiring the xrc into this function for now
// If different xrcs are wanted for future tests, it'll be easy to refactor
-void CreateXrc()
+void LoadTestXrc()
{
const char *xrcText =
""
@@ -116,13 +131,7 @@ void CreateXrc()
""
;
- // afaict there's no elegant way to load xrc direct from a string
- // So save it as a file, from which it can be loaded
- wxStringInputStream sis(xrcText);
- wxFFileOutputStream fos(TEST_XRC_FILE);
- REQUIRE(fos.IsOk());
- fos.Write(sis);
- REQUIRE(fos.Close());
+ LoadXrcFrom(wxString::FromAscii(xrcText));
}
} // anon namespace
@@ -135,8 +144,7 @@ void CreateXrc()
class XrcTestCase
{
public:
- XrcTestCase() { CreateXrc(); }
- ~XrcTestCase() { wxRemoveFile(TEST_XRC_FILE); }
+ XrcTestCase() { }
private:
wxDECLARE_NO_COPY_CLASS(XrcTestCase);
@@ -148,8 +156,7 @@ TEST_CASE_METHOD(XrcTestCase, "XRC::ObjectReferences", "[xrc]")
for ( int n = 0; n < 2; ++n )
{
- // Load the xrc file we're just created
- REQUIRE( wxXmlResource::Get()->Load(TEST_XRC_FILE) );
+ LoadTestXrc();
// In xrc there's now a dialog containing two panels, one an object
// reference of the other
@@ -171,8 +178,7 @@ TEST_CASE_METHOD(XrcTestCase, "XRC::IDRanges", "[xrc]")
// Tests ID ranges
for ( int n = 0; n < 2; ++n )
{
- // Load the xrc file we're just created
- REQUIRE( wxXmlResource::Get()->Load(TEST_XRC_FILE) );
+ LoadTestXrc();
// foo[start] should == foo[0]
CHECK( XRCID("SecondCol[start]") == XRCID("SecondCol[0]") );
@@ -205,6 +211,52 @@ TEST_CASE_METHOD(XrcTestCase, "XRC::IDRanges", "[xrc]")
}
}
+TEST_CASE("XRC::PathWithFragment", "[xrc][uri]")
+{
+ wxXmlResource::Get()->AddHandler(new wxBitmapXmlHandler);
+ wxImage::AddHandler(new wxXPMHandler);
+
+ const wxString filename = "image#1.xpm";
+ TempFile xpmFile(filename);
+
+ // Simplest possible XPM, just to have something to create a bitmap from.
+ static const char* xpm =
+ "/* XPM */\n"
+ "static const char *const xpm[] = {\n"
+ "/* columns rows colors chars-per-pixel */\n"
+ "\"1 1 1 1\",\n"
+ "\" c None\",\n"
+ "/* pixels */\n"
+ "\" \"\n"
+ ;
+
+ wxFFile ff;
+ REQUIRE( ff.Open(filename, "w") );
+ REQUIRE( ff.Write(wxString::FromAscii(xpm)) );
+ REQUIRE( ff.Close() );
+
+ // Opening a percent-encoded URI should work.
+ wxString url = filename;
+ url.Replace("#", "%23");
+
+ LoadXrcFrom
+ (
+ wxString::Format
+ (
+ ""
+ ""
+ " "
+ " "
+ "",
+ filename,
+ url
+ )
+ );
+
+ CHECK( wxXmlResource::Get()->LoadBitmap("good").IsOk() );
+ CHECK( !wxXmlResource::Get()->LoadBitmap("bad").IsOk() );
+}
+
// This test is disabled by default as it requires the environment variable
// below to be defined to point to a HTTP URL with the file to load.
//