Jelajahi Sumber

5.6.0.0 release

Gaudenz Alder 9 tahun lalu
induk
melakukan
648b0f02cb
100 mengubah file dengan 25762 tambahan dan 684 penghapusan
  1. 4287 0
      ChangeLog
  2. 0 674
      LICENSE
  3. 34 10
      README.md
  4. 1 0
      VERSION
  5. 8 0
      etc/build/build.properties
  6. TEMPAT SAMPAH
      etc/build/compiler.jar
  7. 13 0
      etc/embed/dev.js
  8. 109 0
      etc/embed/index.html
  9. 17 0
      etc/embed/main.js
  10. 16 0
      etc/embed/static.html
  11. 10 0
      etc/embed/test/iframe.html
  12. 10 0
      etc/embed/test/iframe2.html
  13. 1782 0
      etc/mxgraph/mxClient.js
  14. 0 0
      etc/unicode-tests/emptyfile.xml
  15. 2 0
      etc/unicode-tests/special'filename.xml
  16. TEMPAT SAMPAH
      etc/unicode-tests/utf16.xml
  17. 2 0
      etc/unicode-tests/utf8-uncompressed.xml
  18. 1 0
      etc/unicode-tests/utf8.gliffy
  19. TEMPAT SAMPAH
      etc/unicode-tests/utf8.png
  20. 3 0
      etc/unicode-tests/utf8.svg
  21. 436 0
      etc/unicode-tests/utf8.vdx
  22. TEMPAT SAMPAH
      etc/unicode-tests/utf8.vsdx
  23. 1 0
      etc/unicode-tests/utf8.xml
  24. 12 0
      src/META-INF/jdoconfig.xml
  25. 70 0
      src/com/mxgraph/io/gliffy/importer/ArrowMapping.java
  26. 14 0
      src/com/mxgraph/io/gliffy/importer/DashStyleMapping.java
  27. 448 0
      src/com/mxgraph/io/gliffy/importer/GliffyDiagramConverter.java
  28. 28 0
      src/com/mxgraph/io/gliffy/importer/LineMapping.java
  29. 36 0
      src/com/mxgraph/io/gliffy/importer/StencilTranslator.java
  30. 1114 0
      src/com/mxgraph/io/gliffy/importer/gliffyTranslation.properties
  31. 97 0
      src/com/mxgraph/io/gliffy/model/Constraint.java
  32. 47 0
      src/com/mxgraph/io/gliffy/model/Constraints.java
  33. 29 0
      src/com/mxgraph/io/gliffy/model/Diagram.java
  34. 60 0
      src/com/mxgraph/io/gliffy/model/EmbeddedResources.java
  35. 116 0
      src/com/mxgraph/io/gliffy/model/GliffyText.java
  36. 137 0
      src/com/mxgraph/io/gliffy/model/Graphic.java
  37. 12 0
      src/com/mxgraph/io/gliffy/model/LinkMap.java
  38. 23 0
      src/com/mxgraph/io/gliffy/model/Metadata.java
  39. 320 0
      src/com/mxgraph/io/gliffy/model/Object.java
  40. 95 0
      src/com/mxgraph/io/gliffy/model/Stage.java
  41. 96 0
      src/com/mxgraph/io/graphml/mxGraphMlConstants.java
  42. 174 0
      src/com/mxgraph/io/graphml/mxGraphMlData.java
  43. 222 0
      src/com/mxgraph/io/graphml/mxGraphMlEdge.java
  44. 426 0
      src/com/mxgraph/io/graphml/mxGraphMlGraph.java
  45. 377 0
      src/com/mxgraph/io/graphml/mxGraphMlKey.java
  46. 78 0
      src/com/mxgraph/io/graphml/mxGraphMlKeyManager.java
  47. 158 0
      src/com/mxgraph/io/graphml/mxGraphMlNode.java
  48. 88 0
      src/com/mxgraph/io/graphml/mxGraphMlPort.java
  49. 135 0
      src/com/mxgraph/io/graphml/mxGraphMlShapeEdge.java
  50. 183 0
      src/com/mxgraph/io/graphml/mxGraphMlShapeNode.java
  51. 252 0
      src/com/mxgraph/io/graphml/mxGraphMlUtils.java
  52. 377 0
      src/com/mxgraph/io/mxGraphMlCodec.java
  53. 1145 0
      src/com/mxgraph/io/mxVsdxCodec.java
  54. 917 0
      src/com/mxgraph/io/vsdx/Shape.java
  55. 55 0
      src/com/mxgraph/io/vsdx/ShapePageId.java
  56. 1129 0
      src/com/mxgraph/io/vsdx/Style.java
  57. 2005 0
      src/com/mxgraph/io/vsdx/VsdxShape.java
  58. 465 0
      src/com/mxgraph/io/vsdx/mxMasterShape.java
  59. 45 0
      src/com/mxgraph/io/vsdx/mxPathDebug.java
  60. 219 0
      src/com/mxgraph/io/vsdx/mxPropertiesManager.java
  61. 193 0
      src/com/mxgraph/io/vsdx/mxStyleSheet.java
  62. 133 0
      src/com/mxgraph/io/vsdx/mxVsdxConnect.java
  63. 143 0
      src/com/mxgraph/io/vsdx/mxVsdxConstants.java
  64. 194 0
      src/com/mxgraph/io/vsdx/mxVsdxMaster.java
  65. 314 0
      src/com/mxgraph/io/vsdx/mxVsdxModel.java
  66. 346 0
      src/com/mxgraph/io/vsdx/mxVsdxPage.java
  67. 1093 0
      src/com/mxgraph/io/vsdx/mxVsdxTextParser.java
  68. 326 0
      src/com/mxgraph/io/vsdx/mxVsdxUtils.java
  69. 5 0
      src/com/mxgraph/io/vsdx/resources/edgeNameU.properties
  70. 1 0
      src/com/mxgraph/io/vsdx/resources/nameU.properties
  71. 2290 0
      src/com/mxgraph/io/vsdx/resources/nameU.tmp
  72. 42 0
      src/com/mxgraph/online/AppShortcutServlet.java
  73. 62 0
      src/com/mxgraph/online/ConnectImageServlet.java
  74. 53 0
      src/com/mxgraph/online/ConnectRenderServlet.java
  75. 22 0
      src/com/mxgraph/online/Constants.java
  76. 120 0
      src/com/mxgraph/online/EmailServlet.java
  77. 407 0
      src/com/mxgraph/online/EmbedServlet.java
  78. 325 0
      src/com/mxgraph/online/EmbedServlet2.java
  79. 254 0
      src/com/mxgraph/online/GoogleGadgetServlet.java
  80. 100 0
      src/com/mxgraph/online/IconFinderServlet.java
  81. 51 0
      src/com/mxgraph/online/ImgurRedirectServlet.java
  82. 192 0
      src/com/mxgraph/online/ImgurServlet.java
  83. 72 0
      src/com/mxgraph/online/LicenseInitServlet.java
  84. 138 0
      src/com/mxgraph/online/LicenseServlet.java
  85. 366 0
      src/com/mxgraph/online/OpenServlet.java
  86. 80 0
      src/com/mxgraph/online/ProxyServlet.java
  87. 181 0
      src/com/mxgraph/online/SaveServlet.java
  88. 117 0
      src/com/mxgraph/online/Utils.java
  89. 151 0
      src/com/mxgraph/view/mxGraphHeadless.java
  90. 24 0
      src/log4j.properties
  91. TEMPAT SAMPAH
      war/WEB-INF/appengine-generated/local_db.bin
  92. 29 0
      war/WEB-INF/appengine-web.xml
  93. 1 0
      war/WEB-INF/iconfinder_key
  94. 1 0
      war/WEB-INF/imgur_client_id
  95. TEMPAT SAMPAH
      war/WEB-INF/lib/appengine-api-1.0-sdk-1.9.34.jar
  96. TEMPAT SAMPAH
      war/WEB-INF/lib/appengine-api-labs.jar
  97. TEMPAT SAMPAH
      war/WEB-INF/lib/appengine-endpoints-deps.jar
  98. TEMPAT SAMPAH
      war/WEB-INF/lib/appengine-endpoints.jar
  99. TEMPAT SAMPAH
      war/WEB-INF/lib/appengine-jsr107cache-1.9.34.jar
  100. 0 0
      war/WEB-INF/lib/commons-codec-1.10.jar

File diff ditekan karena terlalu besar
+ 4287 - 0
ChangeLog


+ 0 - 674
LICENSE

@@ -1,674 +0,0 @@
-                    GNU GENERAL PUBLIC LICENSE
-                       Version 3, 29 June 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-                            Preamble
-
-  The GNU General Public License is a free, copyleft license for
-software and other kinds of works.
-
-  The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works.  By contrast,
-the GNU General Public License is intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users.  We, the Free Software Foundation, use the
-GNU General Public License for most of our software; it applies also to
-any other work released this way by its authors.  You can apply it to
-your programs, too.
-
-  When we speak of free software, we are referring to freedom, not
-price.  Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
-
-  To protect your rights, we need to prevent others from denying you
-these rights or asking you to surrender the rights.  Therefore, you have
-certain responsibilities if you distribute copies of the software, or if
-you modify it: responsibilities to respect the freedom of others.
-
-  For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must pass on to the recipients the same
-freedoms that you received.  You must make sure that they, too, receive
-or can get the source code.  And you must show them these terms so they
-know their rights.
-
-  Developers that use the GNU GPL protect your rights with two steps:
-(1) assert copyright on the software, and (2) offer you this License
-giving you legal permission to copy, distribute and/or modify it.
-
-  For the developers' and authors' protection, the GPL clearly explains
-that there is no warranty for this free software.  For both users' and
-authors' sake, the GPL requires that modified versions be marked as
-changed, so that their problems will not be attributed erroneously to
-authors of previous versions.
-
-  Some devices are designed to deny users access to install or run
-modified versions of the software inside them, although the manufacturer
-can do so.  This is fundamentally incompatible with the aim of
-protecting users' freedom to change the software.  The systematic
-pattern of such abuse occurs in the area of products for individuals to
-use, which is precisely where it is most unacceptable.  Therefore, we
-have designed this version of the GPL to prohibit the practice for those
-products.  If such problems arise substantially in other domains, we
-stand ready to extend this provision to those domains in future versions
-of the GPL, as needed to protect the freedom of users.
-
-  Finally, every program is threatened constantly by software patents.
-States should not allow patents to restrict development and use of
-software on general-purpose computers, but in those that do, we wish to
-avoid the special danger that patents applied to a free program could
-make it effectively proprietary.  To prevent this, the GPL assures that
-patents cannot be used to render the program non-free.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.
-
-                       TERMS AND CONDITIONS
-
-  0. Definitions.
-
-  "This License" refers to version 3 of the GNU General Public License.
-
-  "Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
-
-  "The Program" refers to any copyrightable work licensed under this
-License.  Each licensee is addressed as "you".  "Licensees" and
-"recipients" may be individuals or organizations.
-
-  To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy.  The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
-
-  A "covered work" means either the unmodified Program or a work based
-on the Program.
-
-  To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy.  Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
-  To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies.  Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
-  An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License.  If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
-  1. Source Code.
-
-  The "source code" for a work means the preferred form of the work
-for making modifications to it.  "Object code" means any non-source
-form of a work.
-
-  A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
-  The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form.  A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
-  The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities.  However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work.  For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
-  The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
-  The Corresponding Source for a work in source code form is that
-same work.
-
-  2. Basic Permissions.
-
-  All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met.  This License explicitly affirms your unlimited
-permission to run the unmodified Program.  The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work.  This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
-  You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force.  You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright.  Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
-  Conveying under any other circumstances is permitted solely under
-the conditions stated below.  Sublicensing is not allowed; section 10
-makes it unnecessary.
-
-  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
-  No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
-  When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
-  4. Conveying Verbatim Copies.
-
-  You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
-  You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
-  5. Conveying Modified Source Versions.
-
-  You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
-    a) The work must carry prominent notices stating that you modified
-    it, and giving a relevant date.
-
-    b) The work must carry prominent notices stating that it is
-    released under this License and any conditions added under section
-    7.  This requirement modifies the requirement in section 4 to
-    "keep intact all notices".
-
-    c) You must license the entire work, as a whole, under this
-    License to anyone who comes into possession of a copy.  This
-    License will therefore apply, along with any applicable section 7
-    additional terms, to the whole of the work, and all its parts,
-    regardless of how they are packaged.  This License gives no
-    permission to license the work in any other way, but it does not
-    invalidate such permission if you have separately received it.
-
-    d) If the work has interactive user interfaces, each must display
-    Appropriate Legal Notices; however, if the Program has interactive
-    interfaces that do not display Appropriate Legal Notices, your
-    work need not make them do so.
-
-  A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit.  Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
-  6. Conveying Non-Source Forms.
-
-  You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
-    a) Convey the object code in, or embodied in, a physical product
-    (including a physical distribution medium), accompanied by the
-    Corresponding Source fixed on a durable physical medium
-    customarily used for software interchange.
-
-    b) Convey the object code in, or embodied in, a physical product
-    (including a physical distribution medium), accompanied by a
-    written offer, valid for at least three years and valid for as
-    long as you offer spare parts or customer support for that product
-    model, to give anyone who possesses the object code either (1) a
-    copy of the Corresponding Source for all the software in the
-    product that is covered by this License, on a durable physical
-    medium customarily used for software interchange, for a price no
-    more than your reasonable cost of physically performing this
-    conveying of source, or (2) access to copy the
-    Corresponding Source from a network server at no charge.
-
-    c) Convey individual copies of the object code with a copy of the
-    written offer to provide the Corresponding Source.  This
-    alternative is allowed only occasionally and noncommercially, and
-    only if you received the object code with such an offer, in accord
-    with subsection 6b.
-
-    d) Convey the object code by offering access from a designated
-    place (gratis or for a charge), and offer equivalent access to the
-    Corresponding Source in the same way through the same place at no
-    further charge.  You need not require recipients to copy the
-    Corresponding Source along with the object code.  If the place to
-    copy the object code is a network server, the Corresponding Source
-    may be on a different server (operated by you or a third party)
-    that supports equivalent copying facilities, provided you maintain
-    clear directions next to the object code saying where to find the
-    Corresponding Source.  Regardless of what server hosts the
-    Corresponding Source, you remain obligated to ensure that it is
-    available for as long as needed to satisfy these requirements.
-
-    e) Convey the object code using peer-to-peer transmission, provided
-    you inform other peers where the object code and Corresponding
-    Source of the work are being offered to the general public at no
-    charge under subsection 6d.
-
-  A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
-  A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling.  In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage.  For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product.  A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
-  "Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source.  The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
-  If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information.  But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
-  The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed.  Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
-  Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
-  7. Additional Terms.
-
-  "Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law.  If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
-  When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it.  (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.)  You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
-  Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
-    a) Disclaiming warranty or limiting liability differently from the
-    terms of sections 15 and 16 of this License; or
-
-    b) Requiring preservation of specified reasonable legal notices or
-    author attributions in that material or in the Appropriate Legal
-    Notices displayed by works containing it; or
-
-    c) Prohibiting misrepresentation of the origin of that material, or
-    requiring that modified versions of such material be marked in
-    reasonable ways as different from the original version; or
-
-    d) Limiting the use for publicity purposes of names of licensors or
-    authors of the material; or
-
-    e) Declining to grant rights under trademark law for use of some
-    trade names, trademarks, or service marks; or
-
-    f) Requiring indemnification of licensors and authors of that
-    material by anyone who conveys the material (or modified versions of
-    it) with contractual assumptions of liability to the recipient, for
-    any liability that these contractual assumptions directly impose on
-    those licensors and authors.
-
-  All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10.  If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term.  If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
-  If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
-  Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
-  8. Termination.
-
-  You may not propagate or modify a covered work except as expressly
-provided under this License.  Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
-  However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
-  Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
-  Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License.  If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
-  9. Acceptance Not Required for Having Copies.
-
-  You are not required to accept this License in order to receive or
-run a copy of the Program.  Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance.  However,
-nothing other than this License grants you permission to propagate or
-modify any covered work.  These actions infringe copyright if you do
-not accept this License.  Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
-  10. Automatic Licensing of Downstream Recipients.
-
-  Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License.  You are not responsible
-for enforcing compliance by third parties with this License.
-
-  An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations.  If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
-  You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License.  For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
-  11. Patents.
-
-  A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based.  The
-work thus licensed is called the contributor's "contributor version".
-
-  A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version.  For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
-this License.
-
-  Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
-  In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement).  To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
-  If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients.  "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
-  If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
-  A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License.  You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
-  Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
-  12. No Surrender of Others' Freedom.
-
-  If conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License.  If you cannot convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all.  For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
-  13. Use with the GNU Affero General Public License.
-
-  Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU Affero General Public License into a single
-combined work, and to convey the resulting work.  The terms of this
-License will continue to apply to the part which is the covered work,
-but the special requirements of the GNU Affero General Public License,
-section 13, concerning interaction through a network will apply to the
-combination as such.
-
-  14. Revised Versions of this License.
-
-  The Free Software Foundation may publish revised and/or new versions of
-the GNU General Public License from time to time.  Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-  Each version is given a distinguishing version number.  If the
-Program specifies that a certain numbered version of the GNU General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation.  If the Program does not specify a version number of the
-GNU General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
-  If the Program specifies that a proxy can decide which future
-versions of the GNU General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
-  Later license versions may give you additional or different
-permissions.  However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
-  15. Disclaimer of Warranty.
-
-  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
-  16. Limitation of Liability.
-
-  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
-  17. Interpretation of Sections 15 and 16.
-
-  If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
-
-                     END OF TERMS AND CONDITIONS
-
-            How to Apply These Terms to Your New Programs
-
-  If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
-  To do so, attach the following notices to the program.  It is safest
-to attach them to the start of each source file to most effectively
-state the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-    {one line to give the program's name and a brief idea of what it does.}
-    Copyright (C) {year}  {name of author}
-
-    This program is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-Also add information on how to contact you by electronic and paper mail.
-
-  If the program does terminal interaction, make it output a short
-notice like this when it starts in an interactive mode:
-
-    {project}  Copyright (C) {year}  {fullname}
-    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
-    This is free software, and you are welcome to redistribute it
-    under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License.  Of course, your program's commands
-might be different; for a GUI interface, you would use an "about box".
-
-  You should also get your employer (if you work as a programmer) or school,
-if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU GPL, see
-<http://www.gnu.org/licenses/>.
-
-  The GNU General Public License does not permit incorporating your program
-into proprietary programs.  If your program is a subroutine library, you
-may consider it more useful to permit linking proprietary applications with
-the library.  If this is what you want to do, use the GNU Lesser General
-Public License instead of this License.  But first, please read
-<http://www.gnu.org/philosophy/why-not-lgpl.html>.

+ 34 - 10
README.md

@@ -1,13 +1,37 @@
-About
------
-[draw.io](https://www.draw.io) is an online diagramming web site that delivers the source in this project.
+## draw.io project sources
 
-draw.io uses the [mxGraph library](https://github.com/jgraph/mxgraph) as the base of the stack, with the [GraphEditor example](https://github.com/jgraph/mxgraph/tree/master/javascript/examples/grapheditor) from mxGraph as the base of the application part. The mxGraph library build used is stored under /etc/mxgraph/mxClient.js.
+draw.io comprises three parts:
 
-License
--------
-draw.io is dual licensed under either the GPL v3 or the [mxGraph Commerical License](https://www.jgraph.com/Licenses/JGraph_Software_Licence.pdf). To use the commercial license, you must license from [JGraph](https://www.jgraph.com/purchase-mxgraph.html) for payment.
+1. The static web content under the `war/` directory.
+2. The open, save, embed, proxy and iconfinder servlets as one servlet war. These are located under `src/com/mxgraph/online/`.
+3. The image export servlet. This is located under `etc/imageexport/`.
 
-Supported Browsers
-------------------
-draw.io supports IE 9+, Chrome 30+, Firefox 31+, Safari versions actively patched by Apple (6.2.x, 7.1.x, 8.0.x and 9.x at time of writing), Opera 20+, Native Android browser 5.x+, the default browser in the current and previous major iOS versions (e.g. 9.x and 8.x) and Edge 20+.
+Prior to invoking any builds, search `war/index.html` for *CUSTOM_PARAMETERS*. The image *EXPORT_URL* is the most important variable to change. 
+Without that the export to image will use our server, which somewhat defeats the point of hosting it. The servlet is mapped to *export* and the default name 
+of the war is *Imageexport* so `/Imageexport/export` is the value if you deploy that war as-is and the servlet engine is on the root of the domain.
+
+* `/etc/build/build.xml` is the ant build file for the main war. Invoking `ant war` will generate `build/draw.war`.
+
+* `/etc/imagexport/build.xml` is the ant build file for the image export war. Invoking `ant war` will generate `etc/imageexport/imageexport.war`.
+
+Deploy these wars on a servlet engine and navigate to the location of the first war, you will get the draw.io application.
+
+## Licensing
+
+The underlying mxGraph library is licensed to you either the [CCNC](http://creativecommons.org/licenses/by-nc-sa/3.0/) or the 
+[mxGraph commercial license](http://www.jgraph.com/mxlicense.html) (if you're a paying customer). Please understand that we make 
+a living selling the mxGraph library commercially, we need to avoid draw.io being 
+used freely where mxGraph would previously have been sold.
+
+The source to draw.io itself is Apache 2.0 licensed (or mxGraph Commercial License), but obviously the mxGraph license dominates this for commercial 
+purposes.
+
+We separate between developing against the mxGraph API and just deploying draw.io for general usage. Thus, we make exceptions to the CCNC license 
+if you want to take draw.io as-is and deploy it for your own use (note this only affects commercial users). Below, we will build up a list of alterations 
+you can make to draw.io without being subject to the mxGraph commercial license (note this only affects commercial users):
+
+* You can alter draw.io paths so that the functionality works correctly in your deployment environment
+
+* You can add and remove menu options and library stencil sets to/from the UI
+
+If you require more exceptions, please add to issue tracker.

+ 1 - 0
VERSION

@@ -0,0 +1 @@
+5.6.0.0

+ 8 - 0
etc/build/build.properties

@@ -0,0 +1,8 @@
+jscompiler=${basedir}/compiler.jar
+grapheditor.dir=${basedir}/../../war/js/mxgraph
+src.dir=${basedir}/../../src
+war.dir=${basedir}/../../war
+javac.dir=${war.dir}/WEB-INF/classes
+build.dir=${basedir}/../../build
+war.name=draw.war
+imageexport.dir=${basedir}/../imageexport

TEMPAT SAMPAH
etc/build/compiler.jar


+ 13 - 0
etc/embed/dev.js

@@ -0,0 +1,13 @@
+mxscript(geBasePath + '/Editor.js');
+mxscript(geBasePath + '/EditorUi.js');
+mxscript(geBasePath + '/Sidebar.js');
+mxscript(geBasePath + '/Graph.js');
+mxscript(geBasePath + '/Shapes.js');
+mxscript(geBasePath + '/Actions.js');
+mxscript(drawDevUrl + 'js/diagramly/sidebar/Sidebar.js');
+mxscript(drawDevUrl + 'js/diagramly/DrawioFile.js');
+mxscript(drawDevUrl + 'js/diagramly/LocalFile.js');
+mxscript(drawDevUrl + 'js/diagramly/EditorUi.js');
+mxscript(drawDevUrl + 'js/diagramly/Pages.js');
+mxscript(drawDevUrl + 'js/diagramly/GraphViewer.js');
+mxscript('main.js');

File diff ditekan karena terlalu besar
+ 109 - 0
etc/embed/index.html


File diff ditekan karena terlalu besar
+ 17 - 0
etc/embed/main.js


File diff ditekan karena terlalu besar
+ 16 - 0
etc/embed/static.html


+ 10 - 0
etc/embed/test/iframe.html

@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Testing and Development</title>
+<meta charset="utf-8"/>
+</head>
+<body>
+<iframe src="../index.html?dev=1" width="800" height="600"></iframe>
+</body>
+</html>

+ 10 - 0
etc/embed/test/iframe2.html

@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Testing and Development</title>
+<meta charset="utf-8"/>
+</head>
+<body>
+<iframe src="https://test.draw.io/?dev=1&lightbox=1&target=self&highlight=0000ff&chrome=0#G0B2QEO36liL4FWlgtVzY4d2JHUjQ" width="800" height="600"></iframe>
+</body>
+</html>

File diff ditekan karena terlalu besar
+ 1782 - 0
etc/mxgraph/mxClient.js


+ 0 - 0
etc/unicode-tests/emptyfile.xml


File diff ditekan karena terlalu besar
+ 2 - 0
etc/unicode-tests/special'filename.xml


TEMPAT SAMPAH
etc/unicode-tests/utf16.xml


File diff ditekan karena terlalu besar
+ 2 - 0
etc/unicode-tests/utf8-uncompressed.xml


File diff ditekan karena terlalu besar
+ 1 - 0
etc/unicode-tests/utf8.gliffy


TEMPAT SAMPAH
etc/unicode-tests/utf8.png


File diff ditekan karena terlalu besar
+ 3 - 0
etc/unicode-tests/utf8.svg


File diff ditekan karena terlalu besar
+ 436 - 0
etc/unicode-tests/utf8.vdx


TEMPAT SAMPAH
etc/unicode-tests/utf8.vsdx


File diff ditekan karena terlalu besar
+ 1 - 0
etc/unicode-tests/utf8.xml


+ 12 - 0
src/META-INF/jdoconfig.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8" standalone="no"?>
+<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">
+
+   <persistence-manager-factory name="transactions-optional">
+       <property name="javax.jdo.PersistenceManagerFactoryClass" value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>
+       <property name="javax.jdo.option.ConnectionURL" value="appengine"/>
+       <property name="javax.jdo.option.NontransactionalRead" value="true"/>
+       <property name="javax.jdo.option.NontransactionalWrite" value="true"/>
+       <property name="javax.jdo.option.RetainValues" value="true"/>
+       <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/>
+   </persistence-manager-factory>
+</jdoconfig>

+ 70 - 0
src/com/mxgraph/io/gliffy/importer/ArrowMapping.java

@@ -0,0 +1,70 @@
+package com.mxgraph.io.gliffy.importer;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.mxgraph.util.mxConstants;
+
+public class ArrowMapping
+{
+
+	static
+	{
+		init();
+	}
+
+	public static class ArrowStyle
+	{
+
+		public String name;
+
+		public Boolean fill;
+
+		public ArrowStyle(String name, boolean fill)
+		{
+			super();
+			this.name = name;
+			this.fill = fill;
+		}
+
+		public String toString(boolean start)
+		{
+			int intFill = fill ? 1 : 0;
+			return start ? "startArrow=" + name + ";startFill=" + intFill
+					: "endArrow=" + name + ";endFill=" + intFill;
+		}
+
+	}
+
+	private static Map<Integer, ArrowStyle> mapping;
+
+	private static void init()
+	{
+		mapping = new HashMap<Integer, ArrowStyle>();
+		mapping.put(0, new ArrowStyle("none", false));
+		mapping.put(1, new ArrowStyle(mxConstants.ARROW_OPEN, false));
+		mapping.put(2, new ArrowStyle(mxConstants.ARROW_BLOCK, true));
+		mapping.put(3, new ArrowStyle(mxConstants.ARROW_BLOCK, false));
+		mapping.put(4, new ArrowStyle(mxConstants.ARROW_BLOCK, false));
+		mapping.put(5, new ArrowStyle(mxConstants.ARROW_DIAMOND, false));
+		mapping.put(6, new ArrowStyle(mxConstants.ARROW_CLASSIC, false));
+		mapping.put(7, new ArrowStyle(mxConstants.ARROW_DIAMOND, true));
+		mapping.put(8, new ArrowStyle(mxConstants.ARROW_CLASSIC, true));
+		mapping.put(9, new ArrowStyle("ERzeroToMany", true));
+		mapping.put(10, new ArrowStyle("ERoneToMany", true));
+		mapping.put(11, new ArrowStyle("ERmandOne", true));
+		mapping.put(12, new ArrowStyle("ERone", true));
+		mapping.put(13, new ArrowStyle("ERzeroToOne", true));
+		mapping.put(14, new ArrowStyle("ERmany", true));
+		mapping.put(15, new ArrowStyle(mxConstants.ARROW_OVAL, false));
+		mapping.put(16, new ArrowStyle(mxConstants.ARROW_OVAL, true));
+		mapping.put(17, new ArrowStyle(mxConstants.ARROW_BLOCK, true));
+		mapping.put(18, new ArrowStyle(mxConstants.ARROW_CLASSIC, true));
+		mapping.put(19, new ArrowStyle(mxConstants.ARROW_CLASSIC, true));
+	}
+
+	public static ArrowStyle get(Integer gliffyId)
+	{
+		return mapping.get(gliffyId);
+	}
+}

+ 14 - 0
src/com/mxgraph/io/gliffy/importer/DashStyleMapping.java

@@ -0,0 +1,14 @@
+package com.mxgraph.io.gliffy.importer;
+
+public class DashStyleMapping
+{
+	public static String get(String value)
+	{
+		if (value == null)
+		{
+			return "";
+		}
+
+		return "dashed=1;dashPattern=" + value.replace(",", " ") + ";";
+	}
+}

+ 448 - 0
src/com/mxgraph/io/gliffy/importer/GliffyDiagramConverter.java

@@ -0,0 +1,448 @@
+package com.mxgraph.io.gliffy.importer;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import com.google.gson.GsonBuilder;
+import com.mxgraph.io.mxCodec;
+import com.mxgraph.io.gliffy.model.Constraint;
+import com.mxgraph.io.gliffy.model.Constraint.ConstraintData;
+import com.mxgraph.io.gliffy.model.Constraints;
+import com.mxgraph.io.gliffy.model.Diagram;
+import com.mxgraph.io.gliffy.model.EmbeddedResources.Resource;
+import com.mxgraph.io.gliffy.model.Graphic;
+import com.mxgraph.io.gliffy.model.Graphic.GliffyImage;
+import com.mxgraph.io.gliffy.model.Graphic.GliffyLine;
+import com.mxgraph.io.gliffy.model.Graphic.GliffyMindmap;
+import com.mxgraph.io.gliffy.model.Graphic.GliffyShape;
+import com.mxgraph.io.gliffy.model.Graphic.GliffySvg;
+import com.mxgraph.io.gliffy.model.Object;
+import com.mxgraph.model.mxCell;
+import com.mxgraph.model.mxGeometry;
+import com.mxgraph.model.mxICell;
+import com.mxgraph.util.mxDomUtils;
+import com.mxgraph.util.mxPoint;
+import com.mxgraph.util.mxXmlUtils;
+import com.mxgraph.view.mxGraphHeadless;
+
+/**
+ * Performs a conversion of a Gliffy diagram into a Draw.io diagram
+ * <p>
+ * Example :
+ * <p>
+ * <code><i>
+ * GliffyDiagramConverter converter = new GliffyDiagramConverter(gliffyJsonString);<br>
+ * String drawioXml = converter.getGraphXml();</i>
+ * </code>
+ * 
+ * 
+ */
+public class GliffyDiagramConverter {
+	Logger logger = Logger.getLogger("GliffyDiagramConverter");
+
+	private String diagramString;
+
+	private Diagram gliffyDiagram;
+
+	private mxGraphHeadless drawioDiagram;
+
+	private Map<Integer, Object> vertices;
+
+	/**
+	 * Constructs a new converter and starts a conversion.
+	 * 
+	 * @param gliffyDiagramString
+	 *            JSON string of a gliffy diagram
+	 */
+	public GliffyDiagramConverter(String gliffyDiagramString) {
+		vertices = new LinkedHashMap<Integer, Object>();
+
+		this.diagramString = gliffyDiagramString;
+
+		drawioDiagram = new mxGraphHeadless();
+
+		start();
+	}
+
+	private void start() {
+		// creates a diagram object from the JSON string
+		this.gliffyDiagram = new GsonBuilder().create().fromJson(diagramString, Diagram.class);
+
+		collectVerticesAndConvert(vertices, gliffyDiagram.stage.getObjects(), null);
+
+		drawioDiagram.getModel().beginUpdate();
+
+		try {
+			// sort objects by the order specified in the Gliffy diagram
+			sortObjectsByOrder(gliffyDiagram.stage.getObjects());
+
+			for (Object obj : gliffyDiagram.stage.getObjects()) {
+				importObject(obj, null);
+			}
+
+		} finally {
+			drawioDiagram.getModel().endUpdate();
+		}
+
+	}
+
+	@SuppressWarnings("unused")
+	private void correctLineEndings() {
+		java.lang.Object[] edges = drawioDiagram.getAllEdges(new java.lang.Object[] { drawioDiagram.getDefaultParent() });
+		for (int i = 0; i < edges.length; i++) {
+			mxCell edge = (mxCell) edges[i];
+
+			mxICell source = edge.getTerminal(true);
+			mxICell target = edge.getTerminal(false);
+			mxPoint srcP = edge.getGeometry().getSourcePoint();
+			mxPoint trgtP = edge.getGeometry().getTargetPoint();
+
+			if (target != null) {
+				if (trgtP != null)
+					System.out.println(target.getGeometry().contains(trgtP.getX(), trgtP.getY()));
+				if (srcP != null)
+					System.out.println(source.getGeometry().contains(srcP.getX(), srcP.getY()));
+
+			}
+		}
+	}
+
+	/**
+	 * Imports the objects into the draw.io diagram. Recursively adds the
+	 * children of groups and swimlanes
+	 * 
+	 */
+	private void importObject(Object obj, mxCell parent) {
+		
+		if (obj.isGroup() || obj.isMindmap() || obj.isShape() || obj.isText() || obj.isImage() || obj.isSwimlane() || obj.isSvg()) {
+			drawioDiagram.addCell(obj.mxObject, parent);
+
+			if (obj.isGroup() || obj.isSwimlane()) {
+				if (!obj.isSwimlane())// sort the children except for swimlanes, // their order value is "auto"
+					sortObjectsByOrder(obj.children);
+
+				for (Object go : obj.children) {
+					importObject(go, go.parent.mxObject);
+				}
+			}
+		} else if(obj.isLine()) {
+			// gets the terminal cells for the edge
+			mxCell startTerminal = getTerminalCell(obj, true);
+			mxCell endTerminal = getTerminalCell(obj, false);
+
+			drawioDiagram.addCell(obj.getMxObject(), parent, null, startTerminal, endTerminal);
+
+			applyControlPoints(obj, startTerminal, endTerminal);
+		}
+		else 
+		{
+			logger.warning("Unrecognized object, uid : " + obj.uid);
+		}
+	}
+
+	private void sortObjectsByOrder(Collection<Object> values) {
+		Collections.sort((List<Object>) values, new Comparator<Object>() {
+			public int compare(Object o1, Object o2) {
+				Integer o1o;
+				Integer o2o;
+				try {
+					o1o = Integer.parseInt(o1.order);
+					o2o = Integer.parseInt(o2.order);
+					return o1o.compareTo(o2o);
+				} catch (NumberFormatException e) {
+					return o1.order.compareTo(o2.order);
+				}
+
+			}
+		});
+	}
+
+	private mxCell getTerminalCell(Object gliffyEdge, boolean start) {
+		Constraints cons = gliffyEdge.getConstraints();
+
+		if (cons == null) {
+			return null;
+		}
+		Constraint con = start ? cons.getStartConstraint() : cons.getEndConstraint();
+
+		if (con == null) {
+			return null;
+		}
+
+		ConstraintData cst = start ? con.getStartPositionConstraint() : con.getEndPositionConstraint();
+		int nodeId = cst.getNodeId();
+		Object gliffyEdgeTerminal = vertices.get(nodeId);
+		mxCell mxEdgeTerminal = gliffyEdgeTerminal.getMxObject();
+		
+		return mxEdgeTerminal;
+	}
+
+	/**
+	 * 
+	 */
+	private void applyControlPoints(Object object, mxCell startTerminal, mxCell endTerminal) {
+		mxCell cell = object.getMxObject();
+		mxGeometry geo = drawioDiagram.getModel().getGeometry(cell);
+		geo.setRelative(true);
+
+		List<float[]> points = object.getGraphic().getLine().controlPath;
+		List<mxPoint> mxPoints = new ArrayList<mxPoint>();
+
+		for (float[] point : points) {
+			mxPoints.add(new mxPoint((int) point[0] + (int) object.x, (int) point[1] + (int) object.y));
+		}
+
+		if (startTerminal == null) {
+			mxPoint first = mxPoints.get(0);
+			geo.setTerminalPoint(first, true);
+			mxPoints.remove(first);// remove first so it doesn't become a waypoint
+		}
+
+		if (endTerminal == null) {
+			mxPoint last = mxPoints.get(mxPoints.size() - 1);
+			geo.setTerminalPoint(last, false);
+			mxPoints.remove(last);// remove last so it doesn't become a waypoint
+		}
+
+		if (!mxPoints.isEmpty())
+			geo.setPoints(mxPoints);
+
+		drawioDiagram.getModel().setGeometry(cell, geo);
+	}
+
+	/**
+	 * Creates a map of all vertices so they can be easily accessed when looking
+	 * up terminal cells for edges
+	 */
+	private void collectVerticesAndConvert(Map<Integer, Object> vertices, Collection<Object> objects, Object parent) {
+		for (Object object : objects) {
+			object.mxObject = convertGliffyObject(object, null);
+			object.parent = parent;
+
+			if (object.isGroup())// only do this recursively for groups, swimlanes have children w/o uid
+			{
+				vertices.put(object.id, object);
+				collectVerticesAndConvert(vertices, object.children, object);
+			} else if (object.isShape() || object.isText() || object.isImage() || object.isSwimlane() || object.isSvg() || object.isMindmap()) {
+				vertices.put(object.id, object);
+			} 
+		}
+	}
+
+	/**
+	 * Converts the mxGraph to xml string
+	 * 
+	 * @return
+	 * @throws UnsupportedEncodingException
+	 */
+	public String getGraphXml() {
+		mxCodec codec = new mxCodec();
+		Element node = (Element) codec.encode(drawioDiagram.getModel());
+		node.setAttribute("style", "default-style2");
+		node.setAttribute("background", gliffyDiagram.stage.getBackgroundColor());
+		node.setAttribute("grid", gliffyDiagram.stage.isGridOn() ? "1" : "0");
+		node.setAttribute("guides", gliffyDiagram.stage.isDrawingGuidesOn() ? "1" : "0");
+		String xml = mxXmlUtils.getXml(node);
+		return xml;
+	}
+
+	/**
+	 * Performs the object conversion
+	 * 
+	 * 
+	 */
+	private mxCell convertGliffyObject(Object gliffyObject, mxCell parent) {
+		mxCell cell = new mxCell();
+		cell.setParent(parent);
+		StringBuilder style = new StringBuilder();
+
+		mxGeometry geometry = new mxGeometry((int) gliffyObject.x, (int) gliffyObject.y, (int) gliffyObject.width, (int) gliffyObject.height);
+		cell.setGeometry(geometry);
+
+		Graphic graphic = null;
+		if (gliffyObject.isGroup()) {
+			style.append("group;");
+			cell.setVertex(true);
+		} else {
+			// groups don't have graphic
+			graphic = gliffyObject.getGraphic();
+		}
+
+		String text = null;
+		Object textObject = null;
+		
+		String link = null;
+
+		if (graphic != null) {
+			textObject = gliffyObject.getTextObject();
+			link = gliffyObject.getLink();
+
+			if (gliffyObject.isShape()) {
+				GliffyShape shape = graphic.Shape;
+				
+				cell.setVertex(true);
+				style.append("shape=" + StencilTranslator.translate(gliffyObject.uid)).append(";");
+				style.append("shadow=" + (shape.dropShadow ? 1 : 0)).append(";");
+				style.append("strokeWidth=" + shape.strokeWidth).append(";");
+				style.append("fillColor=" + shape.fillColor).append(";");
+				style.append("strokeColor=" + shape.strokeColor).append(";");
+
+				if (shape.gradient)
+					style.append("gradientColor=#FFFFFF;gradientDirection=north;");
+
+				// opacity value is wrong for venn circles, so ignore it and use the one in the mapping
+				if (!gliffyObject.isVennsCircle())
+					style.append("opacity=" + shape.opacity * 100).append(";");
+
+				style.append(DashStyleMapping.get(shape.dashStyle));
+				style.append("whiteSpace=wrap;");
+				text = gliffyObject.getTextRecursively();
+
+			} else if (gliffyObject.isLine()) {
+				GliffyLine line = graphic.getLine();
+				cell.setEdge(true);
+				style.append("strokeWidth=" + line.strokeWidth).append(";");
+				style.append("strokeColor=" + line.strokeColor).append(";");
+				style.append(ArrowMapping.get(line.startArrow).toString(true)).append(";");
+				style.append(ArrowMapping.get(line.endArrow).toString(false)).append(";");
+				style.append(DashStyleMapping.get(line.dashStyle));
+				style.append(LineMapping.get(line.interpolationType));
+
+				geometry.setX(0);
+				geometry.setY(0);
+
+				text = gliffyObject.getText();
+			} else if (gliffyObject.isText()) {
+				textObject = gliffyObject;
+				cell.setVertex(true);
+				style.append("text;whiteSpace=wrap;");
+				text = gliffyObject.getText();
+			} else if (gliffyObject.isImage()) {
+				GliffyImage image = graphic.getImage();
+				cell.setVertex(true);
+				style.append("shape=" + StencilTranslator.translate(gliffyObject.uid)).append(";");
+				style.append("image=" + image.getUrl());
+
+				text = gliffyObject.getText();
+			}
+			else if (gliffyObject.isSvg()) {
+				GliffySvg svg = graphic.Svg;
+				cell.setVertex(true);
+				style.append("shape=image;aspect=fixed;");
+				Resource res = gliffyDiagram.embeddedResources.get(svg.embeddedResourceId);
+
+				style.append("image=data:image/svg+xml,").append(res.getBase64EncodedData()).append(";");
+			} 
+		} 
+		// swimlanes have children w/o uid so their children are converted here ad hoc
+		else if (gliffyObject.isSwimlane()) {
+			cell.setVertex(true);
+			style.append(StencilTranslator.translate(gliffyObject.uid)).append(";");
+
+			boolean vertical = true;
+			gliffyObject.rotation = 0;
+			if (gliffyObject.uid.startsWith(Object.H_SWIMLANE)) {
+				vertical = false;
+				cell.getGeometry().setWidth(gliffyObject.height);
+				cell.getGeometry().setHeight(gliffyObject.width);
+				style.append("horizontal=0;");
+			}
+
+			Object header = gliffyObject.children.get(0);// first child is the header of the swimlane
+			gliffyObject.children.remove(header);
+
+			GliffyShape shape = header.graphic.getShape();
+			style.append("strokeWidth=" + shape.strokeWidth).append(";");
+			style.append("shadow=" + (shape.dropShadow ? 1 : 0)).append(";");
+			style.append("fillColor=" + shape.fillColor).append(";");
+			style.append("strokeColor=" + shape.strokeColor).append(";");
+			style.append("whiteSpace=wrap;");
+
+			text = header.getText();
+
+			for (int i = 0; i < gliffyObject.children.size(); i++) // rest of the children are lanes
+			{
+				Object gLane = gliffyObject.children.get(i);
+				gLane.parent = gliffyObject;
+
+				GliffyShape gs = gLane.graphic.getShape();
+				StringBuilder laneStyle = new StringBuilder();
+				laneStyle.append("swimlane;swimlaneLine=0;" + (vertical ? "" : "horizontal=0;"));
+				laneStyle.append("strokeWidth=" + gs.strokeWidth).append(";");
+				laneStyle.append("shadow=" + (gs.dropShadow ? 1 : 0)).append(";");
+				laneStyle.append("fillColor=" + gs.fillColor).append(";");
+				laneStyle.append("strokeColor=" + gs.strokeColor).append(";");
+				laneStyle.append("whiteSpace=wrap;html=1;");
+
+				mxCell mxLane = new mxCell();
+				mxLane.setVertex(true);
+				cell.insert(mxLane);
+				mxLane.setValue(gLane.getText());
+				mxLane.setStyle(laneStyle.toString());
+				mxGeometry childGeometry = new mxGeometry(gLane.x, gLane.y, vertical ? gLane.width : gLane.height, vertical ? gLane.height : gLane.width);
+				mxLane.setGeometry(childGeometry);
+				gLane.mxObject = mxLane;
+			}
+		} else if (gliffyObject.isMindmap()) {
+			Object child = gliffyObject.children.get(0);
+			GliffyMindmap mindmap = child.graphic.Mindmap;
+			
+			style.append("shape=" + StencilTranslator.translate(gliffyObject.uid)).append(";");
+			style.append("shadow=" + (mindmap.dropShadow ? 1 : 0)).append(";");
+			style.append("strokeWidth=" + mindmap.strokeWidth).append(";");
+			style.append("fillColor=" + mindmap.fillColor).append(";");
+			style.append("strokeColor=" + mindmap.strokeColor).append(";");
+			style.append(DashStyleMapping.get(mindmap.dashStyle));
+
+			if (mindmap.gradient)
+				style.append("gradientColor=#FFFFFF;gradientDirection=north;");
+
+			cell.setVertex(true);
+			
+			text = child.getTextRecursively();
+		}
+
+		if (gliffyObject.rotation != 0) {
+			style.append("rotation=" + gliffyObject.rotation + ";");
+		}
+
+		if (!gliffyObject.isLine() && textObject != null) {
+			style.append(textObject.graphic.getText().getStyle());
+		}
+
+		if (text != null && !text.equals("")) {
+			style.append("html=1;nl2Br=0;");// nl2Br=0 stops newline from becoming <br>
+		}
+		
+		if(link != null) 
+		{
+			Document doc = mxDomUtils.createDocument();
+			Element uo = doc.createElement("UserObject");
+			uo.setAttribute("link", link);
+			drawioDiagram.getModel().setValue(cell, uo);
+			
+			if(text != null && !text.equals(""))
+				uo.setAttribute("label", text);
+		}
+		else if(text != null && !text.equals("")) 
+		{
+			cell.setValue(text);
+		}
+
+		cell.setStyle(style.toString());
+		gliffyObject.mxObject = cell;
+
+		return cell;
+	}
+
+}

+ 28 - 0
src/com/mxgraph/io/gliffy/importer/LineMapping.java

@@ -0,0 +1,28 @@
+package com.mxgraph.io.gliffy.importer;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class LineMapping
+{
+
+	private static Map<String, String> mapping = new HashMap<String, String>();
+
+	static
+	{
+		init();
+	}
+
+	private static void init()
+	{
+		mapping.put("linear", "");
+		mapping.put("orthogonal", "edgeStyle=orthogonal;");
+		mapping.put("quadratic", "curved=1;edgeStyle=orthogonalEdgeStyle;");
+
+	}
+
+	public static String get(String style)
+	{
+		return mapping.get(style);
+	}
+}

+ 36 - 0
src/com/mxgraph/io/gliffy/importer/StencilTranslator.java

@@ -0,0 +1,36 @@
+package com.mxgraph.io.gliffy.importer;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.PropertyResourceBundle;
+import java.util.ResourceBundle;
+import java.util.logging.Logger;
+
+public class StencilTranslator
+{
+	private static Logger logger = Logger.getLogger("StencilTranslator");
+
+	private static Map<String, String> translationTable = new HashMap<String, String>();
+
+	static
+	{
+		init();
+	};
+
+	private static void init()
+	{
+		ResourceBundle rb = PropertyResourceBundle
+				.getBundle("com/mxgraph/io/gliffy/importer/gliffyTranslation");
+		for (String key : rb.keySet())
+		{
+			translationTable.put(key, rb.getString(key));
+		}
+	}
+
+	public static String translate(String gliffyShapeKey)
+	{
+		String shape = translationTable.get(gliffyShapeKey);
+		logger.info(gliffyShapeKey + " -> " + shape);
+		return shape;
+	}
+}

File diff ditekan karena terlalu besar
+ 1114 - 0
src/com/mxgraph/io/gliffy/importer/gliffyTranslation.properties


+ 97 - 0
src/com/mxgraph/io/gliffy/model/Constraint.java

@@ -0,0 +1,97 @@
+package com.mxgraph.io.gliffy.model;
+
+import com.google.gson.annotations.SerializedName;
+
+public class Constraint
+{
+
+	public static enum ConstraintType
+	{
+		@SerializedName("StartPositionConstraint") START_POSITION_CONSTRAINT,
+		@SerializedName("EndPositionConstraint") END_POSITION_CONSTRAINT,
+		@SerializedName("HeightConstraint") HEIGHT_CONSTRAINT;
+
+		public String toString()
+		{
+			return this.name();
+		}
+	}
+
+	static public class ConstraintData
+	{
+		private int nodeId;
+
+		private float px;
+
+		private float py;
+
+		public int getNodeId()
+		{
+			return nodeId;
+		}
+
+		public void setNodeId(int nodeId)
+		{
+			this.nodeId = nodeId;
+		}
+
+		public float getPx()
+		{
+			return px;
+		}
+
+		public void setPx(float px)
+		{
+			this.px = px;
+		}
+
+		public float getPy()
+		{
+			return py;
+		}
+
+		public void setPy(float py)
+		{
+			this.py = py;
+		}
+
+	}
+
+	private ConstraintType type;
+
+	private ConstraintData StartPositionConstraint;
+
+	private ConstraintData EndPositionConstraint;
+
+	public ConstraintType getType()
+	{
+		return type;
+	}
+
+	public void setType(ConstraintType type)
+	{
+		this.type = type;
+	}
+
+	public ConstraintData getStartPositionConstraint()
+	{
+		return StartPositionConstraint;
+	}
+
+	public void setStartPositionConstraint(
+			ConstraintData startPositionConstraint)
+	{
+		StartPositionConstraint = startPositionConstraint;
+	}
+
+	public ConstraintData getEndPositionConstraint()
+	{
+		return EndPositionConstraint;
+	}
+
+	public void setEndPositionConstraint(ConstraintData endPositionConstraint)
+	{
+		EndPositionConstraint = endPositionConstraint;
+	}
+
+}

+ 47 - 0
src/com/mxgraph/io/gliffy/model/Constraints.java

@@ -0,0 +1,47 @@
+package com.mxgraph.io.gliffy.model;
+
+import java.util.List;
+
+public class Constraints
+{
+	private List<Constraint> constraints;
+
+	private Constraint startConstraint;
+
+	private Constraint endConstraint;
+
+	public Constraints()
+	{
+	}
+
+	public List<Constraint> getConstraints()
+	{
+		return constraints;
+	}
+
+	public void setConstraints(List<Constraint> constraints)
+	{
+		this.constraints = constraints;
+	}
+
+	public Constraint getStartConstraint()
+	{
+		return startConstraint;
+	}
+
+	public void setStartConstraint(Constraint startConstraint)
+	{
+		this.startConstraint = startConstraint;
+	}
+
+	public Constraint getEndConstraint()
+	{
+		return endConstraint;
+	}
+
+	public void setEndConstraint(Constraint endConstraint)
+	{
+		this.endConstraint = endConstraint;
+	}
+
+}

+ 29 - 0
src/com/mxgraph/io/gliffy/model/Diagram.java

@@ -0,0 +1,29 @@
+package com.mxgraph.io.gliffy.model;
+
+public class Diagram
+{
+
+	private String version;
+
+	public Stage stage;
+
+	public Metadata metadata;
+
+	public EmbeddedResources embeddedResources;
+
+	public Diagram()
+	{
+		super();
+	}
+
+	public String getVersion()
+	{
+		return version;
+	}
+
+	public void setVersion(String version)
+	{
+		this.version = version;
+	}
+
+}

+ 60 - 0
src/com/mxgraph/io/gliffy/model/EmbeddedResources.java

@@ -0,0 +1,60 @@
+package com.mxgraph.io.gliffy.model;
+
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.codec.binary.Base64;
+
+public class EmbeddedResources
+{
+
+	public static class Resource
+	{
+		public Integer id;
+
+		public String mimeType;
+
+		public String data;
+
+		public Resource() {
+		}
+
+		public String getBase64EncodedData()
+		{
+			try
+			{
+				return Base64.encodeBase64String(data.getBytes("UTF-8"));
+			}
+			catch (UnsupportedEncodingException e)
+			{
+				throw new RuntimeException(e);
+			}
+		}
+	}
+
+	public List<Resource> resources;
+
+	public Map<Integer, Resource> resourceMap;
+
+	public void setResources(List<Resource> resources)
+	{
+		this.resources = resources;
+	}
+
+	public Resource get(Integer id)
+	{
+		if (resourceMap == null)
+		{
+			resourceMap = new HashMap<Integer, Resource>();
+			for (Resource r : resources)
+			{
+				resourceMap.put(r.id, r);
+			}
+		}
+
+		return resourceMap.get(id);
+	}
+
+}

+ 116 - 0
src/com/mxgraph/io/gliffy/model/GliffyText.java

@@ -0,0 +1,116 @@
+package com.mxgraph.io.gliffy.model;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class GliffyText
+{
+
+	private String html;
+
+	private String valign;
+
+	private String vposition;
+
+	private String hposition;
+
+	private Integer paddingLeft;
+
+	private Integer paddingRight;
+
+	private Integer paddingBottom;
+
+	private Integer paddingTop;
+
+	public double lineTValue;
+
+	public Integer linePerpValue;
+
+	private static Pattern pattern = Pattern.compile("<p(.*?)<\\/p>");
+
+	private static Pattern textAlignPattern = Pattern.compile(
+			".*text-align: ?(left|center|right).*", Pattern.DOTALL);
+
+	public GliffyText()
+	{
+	}
+
+	public String getHtml()
+	{
+		return replaceParagraphWithDiv(html);
+	}
+
+	public void setHtml(String html)
+	{
+		this.html = html;
+	}
+
+	public String getStyle()
+	{
+		StringBuilder sb = new StringBuilder();
+
+		if (vposition.equals("above"))
+		{
+			sb.append("verticalLabelPosition=top;").append(
+					"verticalAlign=bottom;");
+		}
+		else if (vposition.equals("below"))
+		{
+			sb.append("verticalLabelPosition=bottom;").append(
+					"verticalAlign=top;");
+		}
+		else if (vposition.equals("none"))
+		{
+			sb.append("verticalAlign=").append(valign).append(";");
+		}
+
+		if (hposition.equals("left"))
+		{
+			sb.append("labelPosition=left;").append("align=right;");
+		}
+		else if (hposition.equals("right"))
+		{
+			sb.append("labelPosition=right;").append("align=left;");
+		}
+		else if (hposition.equals("none"))
+		{
+			String hAlign = getHorizontalTextAlignment();
+			if (hAlign != null)
+			{
+				sb.append("align=").append(hAlign).append(";");
+			}
+		}
+
+		sb.append("spacingLeft=").append(paddingLeft).append(";");
+		sb.append("spacingRight=").append(paddingRight).append(";");
+		sb.append("spacingTop=").append(paddingTop).append(";");
+		sb.append("spacingBottom=").append(paddingBottom).append(";");
+
+		return sb.toString();
+	}
+
+	private String replaceParagraphWithDiv(String html)
+	{
+		Matcher m = pattern.matcher(html);
+		StringBuilder sb = new StringBuilder();
+		while (m.find())
+		{
+			sb.append("<div" + m.group(1) + "</div>");
+		}
+
+		return sb.length() > 0 ? sb.toString() : html;
+	}
+
+	private String getHorizontalTextAlignment()
+	{
+		Matcher m = textAlignPattern.matcher(html);
+
+		if (m.matches())
+		{
+			return m.group(1);
+		}
+
+		return null;
+	}
+
+}

+ 137 - 0
src/com/mxgraph/io/gliffy/model/Graphic.java

@@ -0,0 +1,137 @@
+package com.mxgraph.io.gliffy.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.google.gson.annotations.SerializedName;
+
+public class Graphic
+{
+
+	public static enum Type
+	{
+		@SerializedName("Svg") SVG,
+		@SerializedName("Line") LINE,
+		@SerializedName("Shape") SHAPE,
+		@SerializedName("Text") TEXT,
+		@SerializedName("Image") IMAGE,
+		@SerializedName("Link") LINK,
+		@SerializedName("Mindmap") MINDMAP;
+		
+
+		public String toString()
+		{
+			return this.name();
+		}
+	}
+
+	public static abstract class GliffyAbstractShape
+	{
+		public int strokeWidth;
+
+		public String strokeColor;
+
+		public String fillColor;
+
+		public String dashStyle;
+	}
+
+	public static class GliffyLine extends GliffyAbstractShape
+	{
+		public Integer startArrow;
+
+		public Integer endArrow;
+
+		public String interpolationType;
+
+		public List<float[]> controlPath = new ArrayList<float[]>();
+	}
+
+	public static class GliffyShape extends GliffyAbstractShape
+	{
+		public String tid;
+
+		public boolean gradient;
+
+		public boolean dropShadow;
+
+		public int state;
+
+		public int shadowX;
+
+		public int shadowY;
+
+		public float opacity;
+
+	}
+
+	public static class GliffyImage extends GliffyShape
+	{
+		private String url;
+
+		public String getUrl()
+		{
+			return url.replace(";base64", "");
+		}
+	}
+
+	public static class GliffySvg extends GliffyShape
+	{
+		public Integer embeddedResourceId;
+	}
+	
+	public static class GliffyMindmap extends GliffyShape
+	{
+		
+	}
+
+	public Type type;
+
+	public GliffyText Text;
+
+	public GliffyLine Line;
+
+	public GliffyShape Shape;
+
+	public GliffyImage Image;
+
+	public GliffySvg Svg;
+	
+	public GliffyMindmap Mindmap;
+
+	public Graphic()
+	{
+		super();
+	}
+
+	public Type getType()
+	{
+		return type;
+	}
+
+	public GliffyText getText()
+	{
+		return Text;
+	}
+
+	public GliffyLine getLine()
+	{
+		return Line;
+	}
+
+	public GliffyShape getShape()
+	{
+		return Shape;
+	}
+
+	public GliffyImage getImage()
+	{
+		return Image;
+	}
+	
+	public GliffyMindmap getMindmap() 
+	{
+		return Mindmap;
+	}
+
+}

+ 12 - 0
src/com/mxgraph/io/gliffy/model/LinkMap.java

@@ -0,0 +1,12 @@
+package com.mxgraph.io.gliffy.model;
+
+public class LinkMap {
+
+	public String url;
+
+	public LinkMap() {
+		super();
+	}
+	
+	
+}

+ 23 - 0
src/com/mxgraph/io/gliffy/model/Metadata.java

@@ -0,0 +1,23 @@
+package com.mxgraph.io.gliffy.model;
+
+public class Metadata
+{
+
+	private String title;
+
+	public Metadata()
+	{
+		super();
+	}
+
+	public String getTitle()
+	{
+		return title;
+	}
+
+	public void setTitle(String title)
+	{
+		this.title = title;
+	}
+
+}

+ 320 - 0
src/com/mxgraph/io/gliffy/model/Object.java

@@ -0,0 +1,320 @@
+package com.mxgraph.io.gliffy.model;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import com.mxgraph.model.mxCell;
+
+/**
+ * Class representing Gliffy diagram object
+ * 
+ */
+public class Object
+{
+	public static String SWIMLANE = "com.gliffy.shape.swimlanes.swimlanes_v1.default";
+
+	public static String V_SWIMLANE = "com.gliffy.shape.swimlanes.swimlanes_v1.default.vertical";
+
+	public static String H_SWIMLANE = "com.gliffy.shape.swimlanes.swimlanes_v1.default.horizontal";
+
+	public static String H_SINGLE_SWIMLANE = "com.gliffy.shape.swimlanes.swimlanes_v1.default.horizontal_single_lane_pool";
+
+	public static String V_SINGLE_SWIMLANE = "com.gliffy.shape.swimlanes.swimlanes_v1.default.vertical_single_lane_pool";
+
+	private static Set<String> GRAPHICLESS_SHAPES = new HashSet<String>();
+
+	private static Set<String> GROUP_SHAPES = new HashSet<String>();
+	
+	private static Set<String> MINDMAP_SHAPES = new HashSet<>();
+
+	public float x;
+
+	public float y;
+
+	public int id;
+
+	public float width;
+
+	public float height;
+
+	public float rotation;
+
+	public String uid;
+	
+	public String tid;
+
+	public String order;
+
+	public boolean lockshape;
+
+	public Graphic graphic;
+
+	public List<Object> children;
+
+	public Constraints constraints;
+	
+	public List<LinkMap> linkMap;
+
+	public mxCell mxObject;// the mxCell this gliffy object got converted into
+
+	public Object parent = null;
+
+	static
+	{
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v1.default.package");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v1.default.class");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v1.default.simple_class");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v1.default.object_timeline");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v1.default.lifeline");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v1.default.use_case");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v1.default.actor");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v1.default.use_case");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v1.default.self_message");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v1.default.message");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v1.default.activation");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v1.default.dependency");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v1.default.dependency");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v1.default.composition");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v1.default.aggregation");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v1.default.association");
+		
+		
+
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v2.class.package");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v2.class.simple_class");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v2.class.class");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v2.class.class2");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v2.class.interface");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v2.class.enumeration");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v2.sequence.lifeline");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v2.sequence.boundary_lifeline");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v2.sequence.control_lifeline");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v2.sequence.entity_lifeline");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v2.deployment.package");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v2.component.package");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.uml.uml_v2.use_case.package");
+
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.erd.erd_v1.default.entity_with_attributes");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.erd.erd_v1.default.entity_with_multiple_attributes");
+
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.bpmn.bpmn_v1.data_artifacts.annotation");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.erd.erd_v1.default.entity_with_multiple_attributes");
+
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.ui.ui_v3.navigation.navbar");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.ui.ui_v3.forms_controls.combo_box");
+
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.ui.ui_v3.containers_content.tooltip_top");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.ui.ui_v3.containers_content.popover_bottom");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.ui.ui_v3.forms_controls.selector");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_left");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_right");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_top");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.ui.ui_v3.containers_content.popover_top");
+
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.sitemap.sitemap_v2.page");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.sitemap.sitemap_v2.home");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.sitemap.sitemap_v2.gliffy");
+		GRAPHICLESS_SHAPES.add("com.gliffy.shape.sitemap.sitemap_v2.form");
+
+		/*GRAPHICLESS_SHAPES.add("com.gliffy.shape.sitemap.sitemap_v2.page");
+		*/
+
+		GROUP_SHAPES.add("com.gliffy.shape.basic.basic_v1.default.group");
+		
+		MINDMAP_SHAPES.add("com.gliffy.shape.mindmap.mindmap_v1.default.main_topic");
+		MINDMAP_SHAPES.add("com.gliffy.shape.mindmap.mindmap_v1.default.subtopic");
+		MINDMAP_SHAPES.add("com.gliffy.shape.mindmap.mindmap_v1.default.child_node");
+		
+	}
+
+	public Object()
+	{
+	}
+
+	public Graphic getGraphic()
+	{
+		if(graphic != null)
+			return graphic;
+		else if(isUml() || GRAPHICLESS_SHAPES.contains(uid))
+			return getFirstChildGraphic();
+		else return null;
+	}
+
+	public mxCell getMxObject()
+	{
+		return mxObject;
+	}
+
+	/**
+	 * Returns the object that represents the caption for this object
+	 * 
+	 * @return
+	 */
+	public Object getTextObject()
+	{
+
+		if (isText())
+		{
+			return this;
+		}
+		if (children == null)
+		{
+			return null;
+		}
+
+		for (Object child : children)
+		{
+			if (child.getGraphic() != null && child.getGraphic().getType().equals(Graphic.Type.TEXT))
+			{
+				return child;
+			}
+			else
+			{
+				return child.getTextObject();
+			}
+		}
+
+		return null;
+	}
+
+	public String getTextRecursively()
+	{
+		StringBuilder sb = new StringBuilder();
+
+		List<Object> textObjs = new ArrayList<Object>();
+		getTextObjects(this, textObjs);
+
+		Iterator<Object> it = textObjs.iterator();
+
+		while (it.hasNext())
+		{
+			Object to = it.next();
+			sb.append(to.graphic.getText().getHtml());
+			if (it.hasNext())
+				sb.append("<hr>");
+		}
+
+		return sb.toString();
+	}
+
+	public String getText()
+	{
+		if (isText())
+			return graphic.getText().getHtml();
+
+		Object to = getTextObject();
+
+		return to != null ? to.graphic.getText().getHtml() : null;
+
+	}
+
+	private void getTextObjects(Object obj, List<Object> objects)
+	{
+		if (obj.isText())
+			objects.add(obj);
+		else if (obj.children != null)
+		{
+			for (Object ob : obj.children)
+			{
+				getTextObjects(ob, objects);
+			}
+		}
+	}
+	
+	public String getLink() 
+	{
+		if(linkMap != null && !linkMap.isEmpty()) 
+			return linkMap.get(0).url;
+		
+		return null;
+	}
+
+	/**
+	 * Some shapes like UML package, class and interface do not have a graphic object but instead rely on graphic of their children.
+	 * In that case, graphic is the same for all children
+	 * @return graphic of the first child or null of there are no children
+	 */
+	public Graphic getFirstChildGraphic()
+	{
+		return children.size() > 0 ? children.get(0).graphic : null;
+	}
+
+	public boolean isGroup()
+	{
+		return uid != null && GROUP_SHAPES.contains(uid);
+	}
+	
+	public boolean isMindmap()
+	{
+		return uid != null && MINDMAP_SHAPES.contains(uid);
+	}
+
+	public boolean isLine()
+	{
+		return graphic != null && graphic.getType().equals(Graphic.Type.LINE);
+	}
+	
+	private boolean isUml() 
+	{
+		return uid != null && (uid.startsWith("com.gliffy.shape.uml.uml")); 
+	}
+
+	public boolean isShape()
+	{
+		if (graphic != null)
+		{
+			return graphic.getType().equals(Graphic.Type.SHAPE) || graphic.getType().equals(Graphic.Type.MINDMAP);
+		}
+		else 
+		{
+			//some UML shapes do not have a graphic,instead their graphic type is determined by their first child
+			Graphic g = getFirstChildGraphic();
+			return  g != null && g.getType().equals(Graphic.Type.SHAPE);
+		}
+
+	}
+
+	public boolean isSvg()
+	{
+		return graphic != null && graphic.type.equals(Graphic.Type.SVG);
+	}
+
+	public boolean isSwimlane()
+	{
+		return uid != null && uid.contains(SWIMLANE);
+	}
+
+	public boolean isText()
+	{
+		return graphic != null && graphic.getType().equals(Graphic.Type.TEXT);
+	}
+
+	public boolean isImage()
+	{
+		return graphic != null && graphic.getType().equals(Graphic.Type.IMAGE);
+	}
+
+	public boolean isVennsCircle()
+	{
+		return uid != null && uid.contains("com.gliffy.shape.venn");
+	}
+
+	public Constraints getConstraints()
+	{
+		return constraints;
+	}
+
+	public boolean hasChildren()
+	{
+		return children != null && children.size() > 0;
+	}
+
+	@Override
+	public String toString()
+	{
+		return uid != null ? uid : tid;
+	}
+}

+ 95 - 0
src/com/mxgraph/io/gliffy/model/Stage.java

@@ -0,0 +1,95 @@
+package com.mxgraph.io.gliffy.model;
+
+import java.util.List;
+
+public class Stage
+{
+
+	private String background;
+
+	private float width;
+
+	private float height;
+
+	private boolean autofit;
+
+	private boolean gridOn;
+
+	private boolean drawingGuidesOn;
+
+	private List<Object> objects;
+
+	public Stage()
+	{
+	}
+
+	public String getBackgroundColor()
+	{
+		return background;
+	}
+
+	public void setBackground(String background)
+	{
+		this.background = background;
+	}
+
+	public float getWidth()
+	{
+		return width;
+	}
+
+	public void setWidth(float width)
+	{
+		this.width = width;
+	}
+
+	public float getHeight()
+	{
+		return height;
+	}
+
+	public void setHeight(float height)
+	{
+		this.height = height;
+	}
+
+	public boolean isAutofit()
+	{
+		return autofit;
+	}
+
+	public void setAutofit(boolean autofit)
+	{
+		this.autofit = autofit;
+	}
+
+	public boolean isGridOn()
+	{
+		return gridOn;
+	}
+
+	public void setGridOn(boolean gridOn)
+	{
+		this.gridOn = gridOn;
+	}
+
+	public boolean isDrawingGuidesOn()
+	{
+		return drawingGuidesOn;
+	}
+
+	public void setDrawingGuidesOn(boolean drawingGuidesOn)
+	{
+		this.drawingGuidesOn = drawingGuidesOn;
+	}
+
+	public List<Object> getObjects()
+	{
+		return objects;
+	}
+
+	public void setObjects(List<Object> objects)
+	{
+		this.objects = objects;
+	}
+}

+ 96 - 0
src/com/mxgraph/io/graphml/mxGraphMlConstants.java

@@ -0,0 +1,96 @@
+/**
+ * Copyright (c) 2010 David Benson, Gaudenz Alder
+ */
+package com.mxgraph.io.graphml;
+
+/**
+ * This class contains constants used in the Import of gml documents.
+ */
+public class mxGraphMlConstants
+{
+	public static String ID = "id";
+
+	public static String KEY_FOR = "for";
+
+	public static String KEY_NAME = "attr.name";
+
+	public static String KEY_TYPE = "attr.type";
+
+	public static String GRAPH = "graph";
+
+	public static String GRAPHML = "graphml";
+
+	public static String NODE = "node";
+
+	public static String EDGE = "edge";
+
+	public static String HYPEREDGE = "hyperedge";
+
+	public static String PORT = "port";
+
+	public static String ENDPOINT = "endpoint";
+
+	public static String KEY = "key";
+
+	public static String DATA = "data";
+
+	public static String ALL = "all";
+
+	public static String EDGE_SOURCE = "source";
+
+	public static String EDGE_SOURCE_PORT = "sourceport";
+
+	public static String EDGE_TARGET = "target";
+
+	public static String EDGE_TARGET_PORT = "targetport";
+
+	public static String EDGE_DIRECTED = "directed";
+
+	public static String EDGE_UNDIRECTED = "undirected";
+
+	public static String EDGE_DEFAULT = "edgedefault";
+
+	public static String PORT_NAME = "name";
+
+	public static String HEIGHT = "height";
+
+	public static String WIDTH = "width";
+
+	public static String X = "x";
+
+	public static String Y = "y";
+
+	public static String JGRAPH = "jGraph:";
+
+	public static String GEOMETRY = "Geometry";
+
+	public static String FILL = "Fill";
+
+	public static String SHAPENODE = "ShapeNode";
+
+	public static String SHAPEEDGE = "ShapeEdge";
+
+	public static String JGRAPH_URL = "http://www.jgraph.com/";
+
+	public static String KEY_NODE_ID = "d0";
+
+	public static String KEY_NODE_NAME = "nodeData";
+
+	public static String KEY_EDGE_ID = "d1";
+
+	public static String KEY_EDGE_NAME = "edgeData";
+
+	public static String STYLE = "Style";
+
+	public static String SHAPE = "Shape";
+
+	public static String TYPE = "type";
+
+	public static String LABEL = "label";
+
+	public static String TEXT = "text";
+
+	public static String PROPERTIES = "properties";
+
+	public static String SOURCETARGET = "SourceTarget";
+}

+ 174 - 0
src/com/mxgraph/io/graphml/mxGraphMlData.java

@@ -0,0 +1,174 @@
+/**
+ * Copyright (c) 2010 David Benson, Gaudenz Alder
+ */
+package com.mxgraph.io.graphml;
+
+import java.util.List;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * Represents a Data element in the GML Structure.
+ */
+public class mxGraphMlData
+{
+	private String dataId = "";
+
+	private String dataKey = "";
+
+	private String dataValue = "";//not using
+
+	private mxGraphMlShapeNode dataShapeNode;
+
+	private mxGraphMlShapeEdge dataShapeEdge;
+
+	/**
+	 * Construct a data with the params values.
+	 * @param dataId Data's ID
+	 * @param dataKey Reference to a Key Element ID
+	 * @param dataValue Value of the data Element
+	 * @param dataShapeEdge JGraph specific edge properties.
+	 * @param dataShapeNode JGraph specific node properties.
+	 */
+	public mxGraphMlData(String dataId, String dataKey, String dataValue,
+			mxGraphMlShapeEdge dataShapeEdge, mxGraphMlShapeNode dataShapeNode)
+	{
+		this.dataId = dataId;
+		this.dataKey = dataKey;
+		this.dataValue = dataValue;
+		this.dataShapeNode = dataShapeNode;
+		this.dataShapeEdge = dataShapeEdge;
+	}
+
+	/**
+	 * Construct a data from one xml data element.
+	 * @param dataElement Xml Data Element.
+	 */
+	public mxGraphMlData(Element dataElement)
+	{
+		this.dataId = dataElement.getAttribute(mxGraphMlConstants.ID);
+		this.dataKey = dataElement.getAttribute(mxGraphMlConstants.KEY);
+
+		this.dataValue = "";
+
+		Element shapeNodeElement = mxGraphMlUtils.childsTag(dataElement,
+				mxGraphMlConstants.JGRAPH + mxGraphMlConstants.SHAPENODE);
+		Element shapeEdgeElement = mxGraphMlUtils.childsTag(dataElement,
+				mxGraphMlConstants.JGRAPH + mxGraphMlConstants.SHAPEEDGE);
+		
+		if (shapeNodeElement != null)
+		{
+			this.dataShapeNode = new mxGraphMlShapeNode(shapeNodeElement);
+		}
+		else if (shapeEdgeElement != null)
+		{
+			this.dataShapeEdge = new mxGraphMlShapeEdge(shapeEdgeElement);
+		}
+		else
+		{
+			NodeList childs = dataElement.getChildNodes();
+			List<Node> childrens = mxGraphMlUtils.copyNodeList(childs);
+			
+			for (Node n : childrens)
+			{
+				if (n.getNodeName().equals("#text"))
+				{
+
+					this.dataValue += n.getNodeValue();
+				}
+			}
+			this.dataValue = this.dataValue.trim();
+		}
+	}
+
+	/**
+	 * Construct an empty data.
+	 */
+	public mxGraphMlData()
+	{
+	}
+
+	public String getDataId()
+	{
+		return dataId;
+	}
+
+	public void setDataId(String dataId)
+	{
+		this.dataId = dataId;
+	}
+
+	public String getDataKey()
+	{
+		return dataKey;
+	}
+
+	public void setDataKey(String dataKey)
+	{
+		this.dataKey = dataKey;
+	}
+
+	public String getDataValue()
+	{
+		return dataValue;
+	}
+
+	public void setDataValue(String dataValue)
+	{
+		this.dataValue = dataValue;
+	}
+
+	public mxGraphMlShapeNode getDataShapeNode()
+	{
+		return dataShapeNode;
+	}
+
+	public void setDataShapeNode(mxGraphMlShapeNode dataShapeNode)
+	{
+		this.dataShapeNode = dataShapeNode;
+	}
+
+	public mxGraphMlShapeEdge getDataShapeEdge()
+	{
+		return dataShapeEdge;
+	}
+
+	public void setDataShapeEdge(mxGraphMlShapeEdge dataShapeEdge)
+	{
+		this.dataShapeEdge = dataShapeEdge;
+	}
+
+	/**
+	 * Generates an Node Data Element from this class.
+	 * @param document Document where the key Element will be inserted.
+	 * @return Returns the generated Elements.
+	 */
+	public Element generateNodeElement(Document document)
+	{
+		Element data = document.createElement(mxGraphMlConstants.DATA);
+		data.setAttribute(mxGraphMlConstants.KEY, dataKey);
+
+		Element shapeNodeElement = dataShapeNode.generateElement(document);
+		data.appendChild(shapeNodeElement);
+
+		return data;
+	}
+
+	/**
+	 * Generates an Edge Data Element from this class.
+	 * @param document Document where the key Element will be inserted.
+	 * @return Returns the generated Elements.
+	 */
+	public Element generateEdgeElement(Document document)
+	{
+		Element data = document.createElement(mxGraphMlConstants.DATA);
+		data.setAttribute(mxGraphMlConstants.KEY, dataKey);
+
+		Element shapeEdgeElement = dataShapeEdge.generateElement(document);
+		data.appendChild(shapeEdgeElement);
+
+		return data;
+	}
+}

+ 222 - 0
src/com/mxgraph/io/graphml/mxGraphMlEdge.java

@@ -0,0 +1,222 @@
+/**
+ * Copyright (c) 2010 David Benson, Gaudenz Alder
+ */
+package com.mxgraph.io.graphml;
+
+import com.mxgraph.util.mxConstants;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * Represents a Data element in the GML Structure.
+ */
+public class mxGraphMlEdge
+{
+	private String edgeId;
+
+	private String edgeSource;
+
+	private String edgeSourcePort;
+
+	private String edgeTarget;
+
+	private String edgeTargetPort;
+
+	private String edgeDirected;
+
+	private mxGraphMlData edgeData;
+
+	/**
+	 * Map with the data. The key is the key attribute
+	 */
+	private HashMap<String, mxGraphMlData> edgeDataMap = new HashMap<String, mxGraphMlData>();
+
+	/**
+	 * Construct an edge with source and target.
+	 * @param edgeSource Source Node's ID.
+	 * @param edgeTarget Target Node's ID.
+	 */
+	public mxGraphMlEdge(String edgeSource, String edgeTarget,
+			String edgeSourcePort, String edgeTargetPort)
+	{
+		this.edgeId = "";
+		this.edgeSource = edgeSource;
+		this.edgeSourcePort = edgeSourcePort;
+		this.edgeTarget = edgeTarget;
+		this.edgeTargetPort = edgeTargetPort;
+		this.edgeDirected = "";
+	}
+
+	/**
+	 * Construct an edge from a xml edge element.
+	 * @param edgeElement Xml edge element.
+	 */
+	public mxGraphMlEdge(Element edgeElement)
+	{
+		this.edgeId = edgeElement.getAttribute(mxGraphMlConstants.ID);
+		this.edgeSource = edgeElement.getAttribute(mxGraphMlConstants.EDGE_SOURCE);
+		this.edgeSourcePort = edgeElement
+				.getAttribute(mxGraphMlConstants.EDGE_SOURCE_PORT);
+		this.edgeTarget = edgeElement.getAttribute(mxGraphMlConstants.EDGE_TARGET);
+		this.edgeTargetPort = edgeElement
+				.getAttribute(mxGraphMlConstants.EDGE_TARGET_PORT);
+		this.edgeDirected = edgeElement
+				.getAttribute(mxGraphMlConstants.EDGE_DIRECTED);
+
+		List<Element> dataList = mxGraphMlUtils.childsTags(edgeElement,
+				mxGraphMlConstants.DATA);
+
+		for (Element dataElem : dataList)
+		{
+			mxGraphMlData data = new mxGraphMlData(dataElem);
+			String key = data.getDataKey();
+			edgeDataMap.put(key, data);
+		}
+	}
+
+	public String getEdgeDirected()
+	{
+		return edgeDirected;
+	}
+
+	public void setEdgeDirected(String edgeDirected)
+	{
+		this.edgeDirected = edgeDirected;
+	}
+
+	public String getEdgeId()
+	{
+		return edgeId;
+	}
+
+	public void setEdgeId(String edgeId)
+	{
+		this.edgeId = edgeId;
+	}
+
+	public String getEdgeSource()
+	{
+		return edgeSource;
+	}
+
+	public void setEdgeSource(String edgeSource)
+	{
+		this.edgeSource = edgeSource;
+	}
+
+	public String getEdgeSourcePort()
+	{
+		return edgeSourcePort;
+	}
+
+	public void setEdgeSourcePort(String edgeSourcePort)
+	{
+		this.edgeSourcePort = edgeSourcePort;
+	}
+
+	public String getEdgeTarget()
+	{
+		return edgeTarget;
+	}
+
+	public void setEdgeTarget(String edgeTarget)
+	{
+		this.edgeTarget = edgeTarget;
+	}
+
+	public String getEdgeTargetPort()
+	{
+		return edgeTargetPort;
+	}
+
+	public void setEdgeTargetPort(String edgeTargetPort)
+	{
+		this.edgeTargetPort = edgeTargetPort;
+	}
+
+	public HashMap<String, mxGraphMlData> getEdgeDataMap()
+	{
+		return edgeDataMap;
+	}
+
+	public void setEdgeDataMap(HashMap<String, mxGraphMlData> nodeEdgeMap)
+	{
+		this.edgeDataMap = nodeEdgeMap;
+	}
+
+	public mxGraphMlData getEdgeData()
+	{
+		return edgeData;
+	}
+
+	public void setEdgeData(mxGraphMlData egdeData)
+	{
+		this.edgeData = egdeData;
+	}
+
+	/**
+	 * Generates a Edge Element from this class.
+	 * @param document Document where the key Element will be inserted.
+	 * @return Returns the generated Elements.
+	 */
+	public Element generateElement(Document document)
+	{
+		Element edge = document.createElement(mxGraphMlConstants.EDGE);
+		
+		if (!edgeId.equals(""))
+		{
+			edge.setAttribute(mxGraphMlConstants.ID, edgeId);
+		}
+		edge.setAttribute(mxGraphMlConstants.EDGE_SOURCE, edgeSource);
+		edge.setAttribute(mxGraphMlConstants.EDGE_TARGET, edgeTarget);
+
+		if (!edgeSourcePort.equals(""))
+		{
+			edge.setAttribute(mxGraphMlConstants.EDGE_SOURCE_PORT, edgeSourcePort);
+		}
+		
+		if (!edgeTargetPort.equals(""))
+		{
+			edge.setAttribute(mxGraphMlConstants.EDGE_TARGET_PORT, edgeTargetPort);
+		}
+		
+		if (!edgeDirected.equals(""))
+		{
+			edge.setAttribute(mxGraphMlConstants.EDGE_DIRECTED, edgeDirected);
+		}
+
+		Element dataElement = edgeData.generateEdgeElement(document);
+		edge.appendChild(dataElement);
+
+		return edge;
+	}
+
+	/**
+	 * Returns if the edge has end arrow.
+	 * @return style that indicates the end arrow type(CLASSIC or NONE).
+	 */
+	public String getEdgeStyle()
+	{
+		String style = "";
+		Hashtable<String, Object> styleMap = new Hashtable<String, Object>();
+
+		//Defines style of the edge.
+		if (edgeDirected.equals("true"))
+		{
+			styleMap.put(mxConstants.STYLE_ENDARROW, mxConstants.ARROW_CLASSIC);
+
+			style = mxGraphMlUtils.getStyleString(styleMap, "=");
+		}
+		else if (edgeDirected.equals("false"))
+		{
+			styleMap.put(mxConstants.STYLE_ENDARROW, mxConstants.NONE);
+
+			style = mxGraphMlUtils.getStyleString(styleMap, "=");
+		}
+
+		return style;
+	}
+}

+ 426 - 0
src/com/mxgraph/io/graphml/mxGraphMlGraph.java

@@ -0,0 +1,426 @@
+/**
+ * Copyright (c) 2010 David Benson, Gaudenz Alder
+ */
+package com.mxgraph.io.graphml;
+
+import com.mxgraph.model.mxCell;
+import com.mxgraph.util.mxPoint;
+import com.mxgraph.view.mxConnectionConstraint;
+import com.mxgraph.view.mxGraph;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * Represents a Graph element in the GML Structure.
+ */
+public class mxGraphMlGraph
+{
+	/**
+	 * Map with the vertex cells added in the addNode method.
+	 */
+	private static HashMap<String, Object> cellsMap = new HashMap<String, Object>();
+
+	private String id = "";
+
+	private String edgedefault = "";
+
+	private List<mxGraphMlNode> nodes = new ArrayList<mxGraphMlNode>();
+
+	private List<mxGraphMlEdge> edges = new ArrayList<mxGraphMlEdge>();
+
+	/**
+	 * Constructs a graph with id and edge default direction.
+	 * @param id Graph's ID
+	 * @param edgedefault Edge Default direction.("directed" or "undirected")
+	 */
+	public mxGraphMlGraph(String id, String edgedefault)
+	{
+		this.id = id;
+		this.edgedefault = edgedefault;
+	}
+
+	/**
+	 * Constructs an empty graph.
+	 */
+	public mxGraphMlGraph()
+	{
+	}
+
+	/**
+	 * Constructs a graph from a xml graph element.
+	 * @param graphElement Xml graph element.
+	 */
+	public mxGraphMlGraph(Element graphElement)
+	{
+		this.id = graphElement.getAttribute(mxGraphMlConstants.ID);
+		this.edgedefault = graphElement
+				.getAttribute(mxGraphMlConstants.EDGE_DEFAULT);
+
+		//Adds node elements
+		List<Element> nodeElements = mxGraphMlUtils.childsTags(graphElement,
+				mxGraphMlConstants.NODE);
+
+		for (Element nodeElem : nodeElements)
+		{
+			mxGraphMlNode node = new mxGraphMlNode(nodeElem);
+
+			nodes.add(node);
+		}
+
+		//Adds edge elements
+		List<Element> edgeElements = mxGraphMlUtils.childsTags(graphElement,
+				mxGraphMlConstants.EDGE);
+
+		for (Element edgeElem : edgeElements)
+		{
+			mxGraphMlEdge edge = new mxGraphMlEdge(edgeElem);
+
+			if (edge.getEdgeDirected().equals(""))
+			{
+				if (edgedefault.equals(mxGraphMlConstants.EDGE_DIRECTED))
+				{
+					edge.setEdgeDirected("true");
+				}
+				else if (edgedefault.equals(mxGraphMlConstants.EDGE_UNDIRECTED))
+				{
+					edge.setEdgeDirected("false");
+				}
+			}
+
+			edges.add(edge);
+		}
+	}
+
+	/**
+	 * Adds the elements represented for this graph model into the given graph.
+	 * @param graph Graph where the elements will be located
+	 * @param parent Parent of the cells to be added.
+	 */
+	public void addGraph(mxGraph graph, Object parent)
+	{
+		List<mxGraphMlNode> nodeList = getNodes();
+
+		for (mxGraphMlNode node : nodeList)
+		{
+			addNode(graph, parent, node);
+		}
+		List<mxGraphMlEdge> edgeList = getEdges();
+
+		for (mxGraphMlEdge edge : edgeList)
+		{
+			addEdge(graph, parent, edge);
+		}
+	}
+
+	/**
+	 * Checks if the node has data elements inside.
+	 * @param node Gml node element.
+	 * @return Returns <code>true</code> if the node has data elements inside.
+	 */
+	public static boolean hasData(mxGraphMlNode node)
+	{
+		boolean ret = false;
+		if (node.getNodeDataMap() == null)
+		{
+			ret = false;
+		}
+		else
+		{
+			ret = true;
+		}
+		return ret;
+	}
+
+	/**
+	 * Returns the data element inside the node that references to the key element
+	 * with name = KEY_NODE_NAME.
+	 * @param node Gml Node element.
+	 * @return The required data. null if not found.
+	 */
+	public static mxGraphMlData dataNodeKey(mxGraphMlNode node)
+	{
+		String keyId = "";
+		HashMap<String, mxGraphMlKey> keyMap = mxGraphMlKeyManager.getInstance()
+				.getKeyMap();
+		
+		for (mxGraphMlKey key : keyMap.values())
+		{
+			if (key.getKeyName().equals(mxGraphMlConstants.KEY_NODE_NAME))
+			{
+				keyId = key.getKeyId();
+			}
+		}
+
+		mxGraphMlData data = null;
+		HashMap<String, mxGraphMlData> nodeDataMap = node.getNodeDataMap();
+		data = nodeDataMap.get(keyId);
+
+		return data;
+	}
+
+	/**
+	 * Returns the data element inside the edge that references to the key element
+	 * with name = KEY_EDGE_NAME.
+	 * @param edge Gml Edge element.
+	 * @return The required data. null if not found.
+	 */
+	public static mxGraphMlData dataEdgeKey(mxGraphMlEdge edge)
+	{
+		String keyId = "";
+		HashMap<String, mxGraphMlKey> keyMap = mxGraphMlKeyManager.getInstance()
+				.getKeyMap();
+		for (mxGraphMlKey key : keyMap.values())
+		{
+			if (key.getKeyName().equals(mxGraphMlConstants.KEY_EDGE_NAME))
+			{
+				keyId = key.getKeyId();
+			}
+		}
+
+		mxGraphMlData data = null;
+		HashMap<String, mxGraphMlData> nodeDataMap = edge.getEdgeDataMap();
+		data = nodeDataMap.get(keyId);
+
+		return data;
+	}
+
+	/**
+	 * Adds the vertex represented for the gml node into the graph with the given parent.
+	 * @param graph Graph where the vertex will be added.
+	 * @param parent Parent's cell.
+	 * @param node Gml Node
+	 * @return The inserted Vertex cell.
+	 */
+	private mxCell addNode(mxGraph graph, Object parent, mxGraphMlNode node)
+	{
+		mxCell v1;
+		String id = node.getNodeId();
+
+		mxGraphMlData data = dataNodeKey(node);
+
+		if (data != null && data.getDataShapeNode() != null)
+		{
+			Double x = Double.valueOf(data.getDataShapeNode().getDataX());
+			Double y = Double.valueOf(data.getDataShapeNode().getDataY());
+			Double h = Double.valueOf(data.getDataShapeNode().getDataHeight());
+			Double w = Double.valueOf(data.getDataShapeNode().getDataWidth());
+			String label = data.getDataShapeNode().getDataLabel();
+			String style = data.getDataShapeNode().getDataStyle();
+			v1 = (mxCell) graph.insertVertex(parent, id, label, x, y, w, h,
+					style);
+		}
+		else
+		{
+			v1 = (mxCell) graph.insertVertex(parent, id, "", 0, 0, 100, 100);
+		}
+
+		cellsMap.put(id, v1);
+		List<mxGraphMlGraph> graphs = node.getNodeGraph();
+
+		for (mxGraphMlGraph gmlGraph : graphs)
+		{
+			gmlGraph.addGraph(graph, v1);
+		}
+		return v1;
+	}
+
+	/**
+	 * Returns the point represented for the port name.
+	 * The specials names North, NorthWest, NorthEast, East, West, South, SouthEast and SouthWest.
+	 * are accepted. Else, the values acepted follow the pattern "double,double".
+	 * where double must be in the range 0..1
+	 * @param source Port Name.
+	 * @return point that represent the port value.
+	 */
+	private static mxPoint portValue(String source)
+	{
+		mxPoint fromConstraint = null;
+
+		if (source != null && !source.equals(""))
+		{
+
+			if (source.equals("North"))
+			{
+				fromConstraint = new mxPoint(0.5, 0);
+			}
+			else if (source.equals("South"))
+			{
+				fromConstraint = new mxPoint(0.5, 1);
+
+			}
+			else if (source.equals("East"))
+			{
+				fromConstraint = new mxPoint(1, 0.5);
+
+			}
+			else if (source.equals("West"))
+			{
+				fromConstraint = new mxPoint(0, 0.5);
+
+			}
+			else if (source.equals("NorthWest"))
+			{
+				fromConstraint = new mxPoint(0, 0);
+			}
+			else if (source.equals("SouthWest"))
+			{
+				fromConstraint = new mxPoint(0, 1);
+			}
+			else if (source.equals("SouthEast"))
+			{
+				fromConstraint = new mxPoint(1, 1);
+			}
+			else if (source.equals("NorthEast"))
+			{
+				fromConstraint = new mxPoint(1, 0);
+			}
+			else
+			{
+				try
+				{
+					String[] s = source.split(",");
+					Double x = Double.valueOf(s[0]);
+					Double y = Double.valueOf(s[1]);
+					fromConstraint = new mxPoint(x, y);
+				}
+				catch (Exception e)
+				{
+					e.printStackTrace();
+				}
+			}
+		}
+		return fromConstraint;
+	}
+
+	/**
+	 * Adds the edge represented for the gml edge into the graph with the given parent.
+	 * @param graph Graph where the vertex will be added.
+	 * @param parent Parent's cell.
+	 * @param edge Gml Edge
+	 * @return The inserted edge cell.
+	 */
+	private static mxCell addEdge(mxGraph graph, Object parent, mxGraphMlEdge edge)
+	{
+		//Get source and target vertex
+		mxPoint fromConstraint = null;
+		mxPoint toConstraint = null;
+		Object source = cellsMap.get(edge.getEdgeSource());
+		Object target = cellsMap.get(edge.getEdgeTarget());
+		String sourcePort = edge.getEdgeSourcePort();
+		String targetPort = edge.getEdgeTargetPort();
+
+		fromConstraint = portValue(sourcePort);
+
+		toConstraint = portValue(targetPort);
+
+		mxGraphMlData data = dataEdgeKey(edge);
+
+		String style = "";
+		String label = "";
+
+		if (data != null)
+		{
+			mxGraphMlShapeEdge shEdge = data.getDataShapeEdge();
+			style = shEdge.getStyle();
+			label = shEdge.getText();
+		}
+		else
+		{
+			style = edge.getEdgeStyle();
+		}
+
+		//Insert new edge.
+		mxCell e = (mxCell) graph.insertEdge(parent, null, label, source,
+				target, style);
+		graph.setConnectionConstraint(e, source, true,
+				new mxConnectionConstraint(fromConstraint, false));
+		graph.setConnectionConstraint(e, target, false,
+				new mxConnectionConstraint(toConstraint, false));
+		return e;
+	}
+
+	public String getEdgedefault()
+	{
+		return edgedefault;
+	}
+
+	public void setEdgedefault(String edgedefault)
+	{
+		this.edgedefault = edgedefault;
+	}
+
+	public String getId()
+	{
+		return id;
+	}
+
+	public void setId(String id)
+	{
+		this.id = id;
+	}
+
+	public List<mxGraphMlNode> getNodes()
+	{
+		return nodes;
+	}
+
+	public void setNodes(List<mxGraphMlNode> node)
+	{
+		this.nodes = node;
+	}
+
+	public List<mxGraphMlEdge> getEdges()
+	{
+		return edges;
+	}
+
+	public void setEdges(List<mxGraphMlEdge> edge)
+	{
+		this.edges = edge;
+	}
+
+	/**
+	 * Checks if the graph has child nodes or edges.
+	 * @return Returns <code>true</code> if the graph hasn't child nodes or edges.
+	 */
+	public boolean isEmpty()
+	{
+		return nodes.size() == 0 && edges.size() == 0;
+	}
+
+	/**
+	 * Generates a Key Element from this class.
+	 * @param document Document where the key Element will be inserted.
+	 * @return Returns the generated Elements.
+	 */
+	public Element generateElement(Document document)
+	{
+		Element graph = document.createElement(mxGraphMlConstants.GRAPH);
+
+		if (!id.equals(""))
+		{
+			graph.setAttribute(mxGraphMlConstants.ID, id);
+		}
+		if (!edgedefault.equals(""))
+		{
+			graph.setAttribute(mxGraphMlConstants.EDGE_DEFAULT, edgedefault);
+		}
+
+		for (mxGraphMlNode node : nodes)
+		{
+			Element nodeElement = node.generateElement(document);
+			graph.appendChild(nodeElement);
+		}
+
+		for (mxGraphMlEdge edge : edges)
+		{
+			Element edgeElement = edge.generateElement(document);
+			graph.appendChild(edgeElement);
+		}
+
+		return graph;
+	}
+}

+ 377 - 0
src/com/mxgraph/io/graphml/mxGraphMlKey.java

@@ -0,0 +1,377 @@
+/**
+ * Copyright (c) 2010 David Benson, Gaudenz Alder
+ */
+package com.mxgraph.io.graphml;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * Represents a Key element in the GML Structure.
+ */
+public class mxGraphMlKey
+{
+	/**
+	 * Possibles values for the keyFor Attribute
+	 */
+	public enum keyForValues
+	{
+		GRAPH, NODE, EDGE, HYPEREDGE, PORT, ENDPOINT, ALL
+	}
+
+	/**
+	 * Possibles values for the keyType Attribute.
+	 */
+	public enum keyTypeValues
+	{
+		BOOLEAN, INT, LONG, FLOAT, DOUBLE, STRING
+	}
+
+	private String keyDefault;
+
+	private String keyId;
+
+	private keyForValues keyFor;
+
+	private String keyName;
+
+	private keyTypeValues keyType;
+
+	/**
+	 * Construct a key with the given parameters.
+	 * @param keyId Key's ID
+	 * @param keyFor Scope of the key.
+	 * @param keyName Key Name
+	 * @param keyType Type of the values represented for this key.
+	 */
+	public mxGraphMlKey(String keyId, keyForValues keyFor, String keyName,
+			keyTypeValues keyType)
+	{
+		this.keyId = keyId;
+		this.keyFor = keyFor;
+		this.keyName = keyName;
+		this.keyType = keyType;
+		this.keyDefault = defaultValue();
+	}
+
+	/**
+	 * Construct a key from a xml key element.
+	 * @param keyElement Xml key element.
+	 */
+	public mxGraphMlKey(Element keyElement)
+	{
+		this.keyId = keyElement.getAttribute(mxGraphMlConstants.ID);
+		this.keyFor = enumForValue(keyElement
+				.getAttribute(mxGraphMlConstants.KEY_FOR));
+		this.keyName = keyElement.getAttribute(mxGraphMlConstants.KEY_NAME);
+		this.keyType = enumTypeValue(keyElement
+				.getAttribute(mxGraphMlConstants.KEY_TYPE));
+		this.keyDefault = defaultValue();
+	}
+
+	public String getKeyDefault()
+	{
+		return keyDefault;
+	}
+
+	public void setKeyDefault(String keyDefault)
+	{
+		this.keyDefault = keyDefault;
+	}
+
+	public keyForValues getKeyFor()
+	{
+		return keyFor;
+	}
+
+	public void setKeyFor(keyForValues keyFor)
+	{
+		this.keyFor = keyFor;
+	}
+
+	public String getKeyId()
+	{
+		return keyId;
+	}
+
+	public void setKeyId(String keyId)
+	{
+		this.keyId = keyId;
+	}
+
+	public String getKeyName()
+	{
+		return keyName;
+	}
+
+	public void setKeyName(String keyName)
+	{
+		this.keyName = keyName;
+	}
+
+	public keyTypeValues getKeyType()
+	{
+		return keyType;
+	}
+
+	public void setKeyType(keyTypeValues keyType)
+	{
+		this.keyType = keyType;
+	}
+
+	/**
+	 * Returns the default value of the keyDefault attribute according
+	 * the keyType.
+	 */
+	private String defaultValue()
+	{
+		String val = "";
+		switch (this.keyType)
+		{
+			case BOOLEAN:
+			{
+				val = "false";
+				break;
+			}
+			case DOUBLE:
+			{
+				val = "0";
+				break;
+			}
+			case FLOAT:
+			{
+				val = "0";
+				break;
+			}
+			case INT:
+			{
+				val = "0";
+				break;
+			}
+			case LONG:
+			{
+				val = "0";
+				break;
+			}
+			case STRING:
+			{
+				val = "";
+				break;
+			}
+		}
+		return val;
+	}
+
+	/**
+	 * Generates a Key Element from this class.
+	 * @param document Document where the key Element will be inserted.
+	 * @return Returns the generated Elements.
+	 */
+	public Element generateElement(Document document)
+	{
+		Element key = document.createElement(mxGraphMlConstants.KEY);
+		
+		if (!keyName.equals(""))
+		{
+			key.setAttribute(mxGraphMlConstants.KEY_NAME, keyName);
+		}
+		key.setAttribute(mxGraphMlConstants.ID, keyId);
+		
+		if (!keyName.equals(""))
+		{
+			key.setAttribute(mxGraphMlConstants.KEY_FOR, stringForValue(keyFor));
+		}
+		
+		if (!keyName.equals(""))
+		{
+			key.setAttribute(mxGraphMlConstants.KEY_TYPE, stringTypeValue(keyType));
+		}
+		
+		if (!keyName.equals(""))
+		{
+			key.setTextContent(keyDefault);
+		}
+		
+		return key;
+	}
+
+	/**
+	 * Converts a String value in its corresponding enum value for the
+	 * keyFor attribute.
+	 * @param value Value in String representation.
+	 * @return Returns the value in its enum representation.
+	 */
+	public keyForValues enumForValue(String value)
+	{
+		keyForValues enumVal = keyForValues.ALL;
+		
+		if (value.equals(mxGraphMlConstants.GRAPH))
+		{
+			enumVal = keyForValues.GRAPH;
+		}
+		else if (value.equals(mxGraphMlConstants.NODE))
+		{
+			enumVal = keyForValues.NODE;
+		}
+		else if (value.equals(mxGraphMlConstants.EDGE))
+		{
+			enumVal = keyForValues.EDGE;
+		}
+		else if (value.equals(mxGraphMlConstants.HYPEREDGE))
+		{
+			enumVal = keyForValues.HYPEREDGE;
+		}
+		else if (value.equals(mxGraphMlConstants.PORT))
+		{
+			enumVal = keyForValues.PORT;
+		}
+		else if (value.equals(mxGraphMlConstants.ENDPOINT))
+		{
+			enumVal = keyForValues.ENDPOINT;
+		}
+		else if (value.equals(mxGraphMlConstants.ALL))
+		{
+			enumVal = keyForValues.ALL;
+		}
+		
+		return enumVal;
+	}
+
+	/**
+	 * Converts a enum value in its corresponding String value for the
+	 * keyFor attribute.
+	 * @param value Value in enum representation.
+	 * @return Returns the value in its String representation.
+	 */
+	public String stringForValue(keyForValues value)
+	{
+
+		String val = mxGraphMlConstants.ALL;
+		
+		switch (value)
+		{
+			case GRAPH:
+			{
+				val = mxGraphMlConstants.GRAPH;
+				break;
+			}
+			case NODE:
+			{
+				val = mxGraphMlConstants.NODE;
+				break;
+			}
+			case EDGE:
+			{
+				val = mxGraphMlConstants.EDGE;
+				break;
+			}
+			case HYPEREDGE:
+			{
+				val = mxGraphMlConstants.HYPEREDGE;
+				break;
+			}
+			case PORT:
+			{
+				val = mxGraphMlConstants.PORT;
+				break;
+			}
+			case ENDPOINT:
+			{
+				val = mxGraphMlConstants.ENDPOINT;
+				break;
+			}
+			case ALL:
+			{
+				val = mxGraphMlConstants.ALL;
+				break;
+			}
+		}
+
+		return val;
+	}
+
+	/**
+	 * Converts a String value in its corresponding enum value for the
+	 * keyType attribute.
+	 * @param value Value in String representation.
+	 * @return Returns the value in its enum representation.
+	 */
+	public keyTypeValues enumTypeValue(String value)
+	{
+		keyTypeValues enumVal = keyTypeValues.STRING;
+		
+		if (value.equals("boolean"))
+		{
+			enumVal = keyTypeValues.BOOLEAN;
+		}
+		else if (value.equals("double"))
+		{
+			enumVal = keyTypeValues.DOUBLE;
+		}
+		else if (value.equals("float"))
+		{
+			enumVal = keyTypeValues.FLOAT;
+		}
+		else if (value.equals("int"))
+		{
+			enumVal = keyTypeValues.INT;
+		}
+		else if (value.equals("long"))
+		{
+			enumVal = keyTypeValues.LONG;
+		}
+		else if (value.equals("string"))
+		{
+			enumVal = keyTypeValues.STRING;
+		}
+		
+		return enumVal;
+	}
+
+	/**
+	 * Converts a enum value in its corresponding string value for the
+	 * keyType attribute.
+	 * @param value Value in enum representation.
+	 * @return Returns the value in its String representation.
+	 */
+	public String stringTypeValue(keyTypeValues value)
+	{
+		String val = "string";
+		
+		switch (value)
+		{
+			case BOOLEAN:
+			{
+				val = "boolean";
+				break;
+			}
+			case DOUBLE:
+			{
+				val = "double";
+				break;
+			}
+			case FLOAT:
+			{
+				val = "float";
+				break;
+			}
+			case INT:
+			{
+				val = "int";
+				break;
+			}
+			case LONG:
+			{
+				val = "long";
+				break;
+			}
+			case STRING:
+			{
+				val = "string";
+				break;
+			}
+		}
+
+		return val;
+	}
+}

+ 78 - 0
src/com/mxgraph/io/graphml/mxGraphMlKeyManager.java

@@ -0,0 +1,78 @@
+/**
+ * Copyright (c) 2010 David Benson, Gaudenz Alder
+ */
+package com.mxgraph.io.graphml;
+
+import java.util.HashMap;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+/**
+ * This is a singleton class that contains a map with the key elements of the
+ * document. The key elements are wrapped in instances of mxGmlKey and
+ * may to be access by ID.
+ */
+public class mxGraphMlKeyManager
+{
+	/**
+	 * Map with the key elements of the document.<br/>
+	 * The key is the key's ID.
+	 */
+	private HashMap<String, mxGraphMlKey> keyMap = new HashMap<String, mxGraphMlKey>();
+
+	private static mxGraphMlKeyManager keyManager = null;
+
+	/**
+	 * Singleton pattern requires private constructor.
+	 */
+	private mxGraphMlKeyManager()
+	{
+	}
+
+	/**
+	 * Returns the instance of mxGmlKeyManager.
+	 * If no instance has been created until the moment, a new instance is
+	 * returned.
+	 * This method don't load the map.
+	 * @return An instance of mxGmlKeyManager.
+	 */
+	public static mxGraphMlKeyManager getInstance()
+	{
+		if (keyManager == null)
+		{
+			keyManager = new mxGraphMlKeyManager();
+		}
+		return keyManager;
+	}
+
+	/**
+	 * Load the map with the key elements in the document.<br/>
+	 * The keys are wrapped for instances of mxGmlKey.
+	 * @param doc Document with the keys.
+	 */
+	public void initialise(Document doc)
+	{
+		NodeList gmlKeys = doc.getElementsByTagName(mxGraphMlConstants.KEY);
+
+		int keyLength = gmlKeys.getLength();
+
+		for (int i = 0; i < keyLength; i++)
+		{
+			Element key = (Element) gmlKeys.item(i);
+			String keyId = key.getAttribute(mxGraphMlConstants.ID);
+			mxGraphMlKey keyElement = new mxGraphMlKey(key);
+			keyMap.put(keyId, keyElement);
+		}
+	}
+
+	public HashMap<String, mxGraphMlKey> getKeyMap()
+	{
+		return keyMap;
+	}
+
+	public void setKeyMap(HashMap<String, mxGraphMlKey> keyMap)
+	{
+		this.keyMap = keyMap;
+	}
+}

+ 158 - 0
src/com/mxgraph/io/graphml/mxGraphMlNode.java

@@ -0,0 +1,158 @@
+/**
+ * Copyright (c) 2010 David Benson, Gaudenz Alder
+ */
+package com.mxgraph.io.graphml;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * Represents a Data element in the GML Structure.
+ */
+public class mxGraphMlNode
+{
+	private String nodeId;
+
+	private mxGraphMlData nodeData;
+
+	private List<mxGraphMlGraph> nodeGraphList = new ArrayList<mxGraphMlGraph>();
+
+	private HashMap<String, mxGraphMlData> nodeDataMap = new HashMap<String, mxGraphMlData>();
+
+	private HashMap<String, mxGraphMlPort> nodePortMap = new HashMap<String, mxGraphMlPort>();
+
+	/**
+	 * Construct a node with Id and one data element
+	 * @param nodeId Node`s ID
+	 * @param nodeData Gml Data.
+	 */
+	public mxGraphMlNode(String nodeId, mxGraphMlData nodeData)
+	{
+		this.nodeId = nodeId;
+		this.nodeData = nodeData;
+	}
+
+	/**
+	 * Construct a Node from a xml Node Element.
+	 * @param nodeElement Xml Node Element.
+	 */
+	public mxGraphMlNode(Element nodeElement)
+	{
+		this.nodeId = nodeElement.getAttribute(mxGraphMlConstants.ID);
+
+		//Add data elements
+		List<Element> dataList = mxGraphMlUtils.childsTags(nodeElement,
+				mxGraphMlConstants.DATA);
+
+		for (Element dataElem : dataList)
+		{
+			mxGraphMlData data = new mxGraphMlData(dataElem);
+			String key = data.getDataKey();
+			nodeDataMap.put(key, data);
+		}
+
+		//Add graph elements
+		List<Element> graphList = mxGraphMlUtils.childsTags(nodeElement,
+				mxGraphMlConstants.GRAPH);
+
+		for (Element graphElem : graphList)
+		{
+			mxGraphMlGraph graph = new mxGraphMlGraph(graphElem);
+			nodeGraphList.add(graph);
+		}
+
+		//Add port elements
+		List<Element> portList = mxGraphMlUtils.childsTags(nodeElement,
+				mxGraphMlConstants.PORT);
+
+		for (Element portElem : portList)
+		{
+			mxGraphMlPort port = new mxGraphMlPort(portElem);
+			String name = port.getName();
+			nodePortMap.put(name, port);
+		}
+	}
+
+	public String getNodeId()
+	{
+		return nodeId;
+	}
+
+	public void setNodeId(String nodeId)
+	{
+		this.nodeId = nodeId;
+	}
+
+	public HashMap<String, mxGraphMlData> getNodeDataMap()
+	{
+		return nodeDataMap;
+	}
+
+	public void setNodeDataMap(HashMap<String, mxGraphMlData> nodeDataMap)
+	{
+		this.nodeDataMap = nodeDataMap;
+	}
+
+	public List<mxGraphMlGraph> getNodeGraph()
+	{
+		return nodeGraphList;
+	}
+
+	public void setNodeGraph(List<mxGraphMlGraph> nodeGraph)
+	{
+		this.nodeGraphList = nodeGraph;
+	}
+
+	public HashMap<String, mxGraphMlPort> getNodePort()
+	{
+		return nodePortMap;
+	}
+
+	public void setNodePort(HashMap<String, mxGraphMlPort> nodePort)
+	{
+		this.nodePortMap = nodePort;
+	}
+
+	/**
+	 * Generates a Key Element from this class.
+	 * @param document Document where the key Element will be inserted.
+	 * @return Returns the generated Elements.
+	 */
+	public Element generateElement(Document document)
+	{
+		Element node = document.createElement(mxGraphMlConstants.NODE);
+
+		node.setAttribute(mxGraphMlConstants.ID, nodeId);
+
+		Element dataElement = nodeData.generateNodeElement(document);
+		node.appendChild(dataElement);
+
+		for (mxGraphMlPort port : nodePortMap.values())
+		{
+			Element portElement = port.generateElement(document);
+			node.appendChild(portElement);
+		}
+
+		for (mxGraphMlGraph graph : nodeGraphList)
+		{
+			Element graphElement = graph.generateElement(document);
+			node.appendChild(graphElement);
+		}
+
+		return node;
+	}
+
+	public mxGraphMlData getNodeData()
+	{
+		return nodeData;
+	}
+
+	public void setNodeData(mxGraphMlData nodeData)
+	{
+		this.nodeData = nodeData;
+	}
+
+}

+ 88 - 0
src/com/mxgraph/io/graphml/mxGraphMlPort.java

@@ -0,0 +1,88 @@
+/**
+ * Copyright (c) 2010 David Benson, Gaudenz Alder
+ */
+package com.mxgraph.io.graphml;
+
+import java.util.HashMap;
+import java.util.List;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * Represents a Port element in the GML Structure.
+ */
+public class mxGraphMlPort
+{
+	private String name;
+
+	private HashMap<String, mxGraphMlData> portDataMap = new HashMap<String, mxGraphMlData>();
+
+	/**
+	 * Construct a Port with name.
+	 * @param name Port Name
+	 */
+	public mxGraphMlPort(String name)
+	{
+		this.name = name;
+	}
+
+	/**
+	 * Construct a Port from a xml port Element.
+	 * @param portElement Xml port Element.
+	 */
+	public mxGraphMlPort(Element portElement)
+	{
+		this.name = portElement.getAttribute(mxGraphMlConstants.PORT_NAME);
+
+		//Add data elements
+		List<Element> dataList = mxGraphMlUtils.childsTags(portElement,
+				mxGraphMlConstants.DATA);
+
+		for (Element dataElem : dataList)
+		{
+			mxGraphMlData data = new mxGraphMlData(dataElem);
+			String key = data.getDataKey();
+			portDataMap.put(key, data);
+		}
+	}
+
+	public String getName()
+	{
+		return name;
+	}
+
+	public void setName(String name)
+	{
+		this.name = name;
+	}
+
+	public HashMap<String, mxGraphMlData> getPortDataMap()
+	{
+		return portDataMap;
+	}
+
+	public void setPortDataMap(HashMap<String, mxGraphMlData> nodeDataMap)
+	{
+		this.portDataMap = nodeDataMap;
+	}
+
+	/**
+	 * Generates a Key Element from this class.
+	 * @param document Document where the key Element will be inserted.
+	 * @return Returns the generated Elements.
+	 */
+	public Element generateElement(Document document)
+	{
+		Element node = document.createElement(mxGraphMlConstants.PORT);
+
+		node.setAttribute(mxGraphMlConstants.PORT_NAME, name);
+
+		for (mxGraphMlData data : portDataMap.values())
+		{
+			Element dataElement = data.generateNodeElement(document);
+			node.appendChild(dataElement);
+		}
+
+		return node;
+	}
+}

+ 135 - 0
src/com/mxgraph/io/graphml/mxGraphMlShapeEdge.java

@@ -0,0 +1,135 @@
+/**
+ * Copyright (c) 2010 David Benson, Gaudenz Alder
+ */
+package com.mxgraph.io.graphml;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * This class represents the properties of a JGraph edge.
+ */
+public class mxGraphMlShapeEdge
+{
+	private String text = "";
+
+	private String style = "";
+
+	private String edgeSource;
+
+	private String edgeTarget;
+
+	/**
+	 * Construct a Shape Edge with text and style.
+	 * @param text
+	 * @param style
+	 */
+	public mxGraphMlShapeEdge(String text, String style)
+	{
+		this.text = text;
+		this.style = style;
+	}
+
+	/**
+	 * Constructs a ShapeEdge from a xml shapeEdgeElement.
+	 * @param shapeEdgeElement
+	 */
+	public mxGraphMlShapeEdge(Element shapeEdgeElement)
+	{
+		Element labelElement = mxGraphMlUtils.childsTag(shapeEdgeElement,
+				mxGraphMlConstants.JGRAPH + mxGraphMlConstants.LABEL);
+		
+		if (labelElement != null)
+		{
+			this.text = labelElement.getAttribute(mxGraphMlConstants.TEXT);
+		}
+
+		Element styleElement = mxGraphMlUtils.childsTag(shapeEdgeElement,
+				mxGraphMlConstants.JGRAPH + mxGraphMlConstants.STYLE);
+		
+		if (styleElement != null)
+		{
+			this.style = styleElement.getAttribute(mxGraphMlConstants.PROPERTIES);
+
+		}
+	}
+
+	/**
+	 * Construct an empty Shape Edge Element.
+	 */
+	public mxGraphMlShapeEdge()
+	{
+	}
+
+	/**
+	 * Generates a ShapeEdge Element from this class.
+	 * @param document Document where the key Element will be inserted.
+	 * @return Returns the generated Elements.
+	 */
+	public Element generateElement(Document document)
+	{
+		Element dataEdge = document.createElementNS(mxGraphMlConstants.JGRAPH_URL,
+				mxGraphMlConstants.JGRAPH + mxGraphMlConstants.SHAPEEDGE);
+
+		if (!this.text.equals(""))
+		{
+			Element dataEdgeLabel = document.createElementNS(
+					mxGraphMlConstants.JGRAPH_URL, mxGraphMlConstants.JGRAPH
+							+ mxGraphMlConstants.LABEL);
+			dataEdgeLabel.setAttribute(mxGraphMlConstants.TEXT, this.text);
+			dataEdge.appendChild(dataEdgeLabel);
+		}
+		
+		if (!this.style.equals(""))
+		{
+			Element dataEdgeStyle = document.createElementNS(
+					mxGraphMlConstants.JGRAPH_URL, mxGraphMlConstants.JGRAPH
+							+ mxGraphMlConstants.STYLE);
+
+			dataEdgeStyle.setAttribute(mxGraphMlConstants.PROPERTIES, this.style);
+			dataEdge.appendChild(dataEdgeStyle);
+		}
+
+		return dataEdge;
+	}
+
+	public String getText()
+	{
+		return text;
+	}
+
+	public void setText(String text)
+	{
+		this.text = text;
+	}
+
+	public String getStyle()
+	{
+		return style;
+	}
+
+	public void setStyle(String style)
+	{
+		this.style = style;
+	}
+
+	public String getEdgeSource()
+	{
+		return edgeSource;
+	}
+
+	public void setEdgeSource(String edgeSource)
+	{
+		this.edgeSource = edgeSource;
+	}
+
+	public String getEdgeTarget()
+	{
+		return edgeTarget;
+	}
+
+	public void setEdgeTarget(String edgeTarget)
+	{
+		this.edgeTarget = edgeTarget;
+	}
+}

+ 183 - 0
src/com/mxgraph/io/graphml/mxGraphMlShapeNode.java

@@ -0,0 +1,183 @@
+/**
+ * Copyright (c) 2010 David Benson, Gaudenz Alder
+ */
+package com.mxgraph.io.graphml;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class mxGraphMlShapeNode
+{
+	private String dataHeight = "";
+
+	private String dataWidth = "";
+
+	private String dataX = "";
+
+	private String dataY = "";
+
+	private String dataLabel = "";
+
+	private String dataStyle = "";
+
+	/**
+	 * Construct a shape Node with the given parameters
+	 * @param dataHeight Node's Height
+	 * @param dataWidth Node's Width
+	 * @param dataX Node's X coordinate.
+	 * @param dataY Node's Y coordinate.
+	 * @param dataStyle Node's style.
+	 */
+	public mxGraphMlShapeNode(String dataHeight, String dataWidth, String dataX,
+			String dataY, String dataStyle)
+	{
+		this.dataHeight = dataHeight;
+		this.dataWidth = dataWidth;
+		this.dataX = dataX;
+		this.dataY = dataY;
+		this.dataStyle = dataStyle;
+	}
+
+	/**
+	 * Construct an empty shape Node
+	 */
+	public mxGraphMlShapeNode()
+	{
+	}
+
+	/**
+	 * Construct a Shape Node from a xml Shape Node Element.
+	 * @param shapeNodeElement Xml Shape Node Element.
+	 */
+	public mxGraphMlShapeNode(Element shapeNodeElement)
+	{
+		//Defines Geometry
+		Element geometryElement = mxGraphMlUtils.childsTag(shapeNodeElement,
+				mxGraphMlConstants.JGRAPH + mxGraphMlConstants.GEOMETRY);
+		this.dataHeight = geometryElement.getAttribute(mxGraphMlConstants.HEIGHT);
+		this.dataWidth = geometryElement.getAttribute(mxGraphMlConstants.WIDTH);
+		this.dataX = geometryElement.getAttribute(mxGraphMlConstants.X);
+		this.dataY = geometryElement.getAttribute(mxGraphMlConstants.Y);
+
+		Element styleElement = mxGraphMlUtils.childsTag(shapeNodeElement,
+				mxGraphMlConstants.JGRAPH + mxGraphMlConstants.STYLE);
+		
+		if (styleElement != null)
+		{
+			this.dataStyle = styleElement
+					.getAttribute(mxGraphMlConstants.PROPERTIES);
+		}
+		//Defines Label
+		Element labelElement = mxGraphMlUtils.childsTag(shapeNodeElement,
+				mxGraphMlConstants.JGRAPH + mxGraphMlConstants.LABEL);
+		
+		if (labelElement != null)
+		{
+			this.dataLabel = labelElement.getAttribute(mxGraphMlConstants.TEXT);
+		}
+	}
+
+	/**
+	 * Generates a Shape Node Element from this class.
+	 * @param document Document where the key Element will be inserted.
+	 * @return Returns the generated Elements.
+	 */
+	public Element generateElement(Document document)
+	{
+		Element dataShape = document.createElementNS(mxGraphMlConstants.JGRAPH_URL,
+				mxGraphMlConstants.JGRAPH + mxGraphMlConstants.SHAPENODE);
+
+		Element dataShapeGeometry = document.createElementNS(
+				mxGraphMlConstants.JGRAPH_URL, mxGraphMlConstants.JGRAPH
+						+ mxGraphMlConstants.GEOMETRY);
+		dataShapeGeometry.setAttribute(mxGraphMlConstants.HEIGHT, dataHeight);
+		dataShapeGeometry.setAttribute(mxGraphMlConstants.WIDTH, dataWidth);
+		dataShapeGeometry.setAttribute(mxGraphMlConstants.X, dataX);
+		dataShapeGeometry.setAttribute(mxGraphMlConstants.Y, dataY);
+
+		dataShape.appendChild(dataShapeGeometry);
+
+		if (!this.dataStyle.equals(""))
+		{
+			Element dataShapeStyle = document.createElementNS(
+					mxGraphMlConstants.JGRAPH_URL, mxGraphMlConstants.JGRAPH
+							+ mxGraphMlConstants.STYLE);
+			dataShapeStyle.setAttribute(mxGraphMlConstants.PROPERTIES, dataStyle);
+			dataShape.appendChild(dataShapeStyle);
+		}
+
+		//Sets Label
+		if (!this.dataLabel.equals(""))
+		{
+
+			Element dataShapeLabel = document.createElementNS(
+					mxGraphMlConstants.JGRAPH_URL, mxGraphMlConstants.JGRAPH
+							+ mxGraphMlConstants.LABEL);
+			dataShapeLabel.setAttribute(mxGraphMlConstants.TEXT, dataLabel);
+
+			dataShape.appendChild(dataShapeLabel);
+		}
+		
+		return dataShape;
+	}
+
+	public String getDataHeight()
+	{
+		return dataHeight;
+	}
+
+	public void setDataHeight(String dataHeight)
+	{
+		this.dataHeight = dataHeight;
+	}
+
+	public String getDataWidth()
+	{
+		return dataWidth;
+	}
+
+	public void setDataWidth(String dataWidth)
+	{
+		this.dataWidth = dataWidth;
+	}
+
+	public String getDataX()
+	{
+		return dataX;
+	}
+
+	public void setDataX(String dataX)
+	{
+		this.dataX = dataX;
+	}
+
+	public String getDataY()
+	{
+		return dataY;
+	}
+
+	public void setDataY(String dataY)
+	{
+		this.dataY = dataY;
+	}
+
+	public String getDataLabel()
+	{
+		return dataLabel;
+	}
+
+	public void setDataLabel(String dataLabel)
+	{
+		this.dataLabel = dataLabel;
+	}
+
+	public String getDataStyle()
+	{
+		return dataStyle;
+	}
+
+	public void setDataStyle(String dataStyle)
+	{
+		this.dataStyle = dataStyle;
+	}
+}

+ 252 - 0
src/com/mxgraph/io/graphml/mxGraphMlUtils.java

@@ -0,0 +1,252 @@
+/**
+ * Copyright (c) 2010 David Benson, Gaudenz Alder
+ */
+package com.mxgraph.io.graphml;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * This class implements several GML utility methods.
+ */
+public class mxGraphMlUtils
+{
+	/**
+	 * Checks if the NodeList has a Node with name = tag.
+	 * @param nl NodeList
+	 * @param tag Name of the node.
+	 * @return Returns <code>true</code> if the Node List has a Node with name = tag.
+	 */
+	public static boolean nodeListHasTag(NodeList nl, String tag)
+	{
+		boolean has = false;
+
+		if (nl != null)
+		{
+			int length = nl.getLength();
+
+			for (int i = 0; (i < length) && !has; i++)
+			{
+				has = (nl.item(i)).getNodeName().equals(tag);
+			}
+		}
+		
+		return has;
+	}
+
+	/**
+	 * Returns the first Element that has name = tag in Node List.
+	 * @param nl NodeList
+	 * @param tag Name of the Element
+	 * @return Element with name = 'tag'.
+	 */
+	public static Element nodeListTag(NodeList nl, String tag)
+	{
+		if (nl != null)
+		{
+			int length = nl.getLength();
+			boolean has = false;
+
+			for (int i = 0; (i < length) && !has; i++)
+			{
+				has = (nl.item(i)).getNodeName().equals(tag);
+
+				if (has)
+				{
+					return (Element) nl.item(i);
+				}
+			}
+		}
+		
+		return null;
+	}
+
+	/**
+	 * Returns a list with the elements included in the Node List that have name = tag.
+	 * @param nl NodeList
+	 * @param tag name of the Element.
+	 * @return List with the indicated elements.
+	 */
+	public static List<Element> nodeListTags(NodeList nl, String tag)
+	{
+		ArrayList<Element> ret = new ArrayList<Element>();
+
+		if (nl != null)
+		{
+			int length = nl.getLength();
+
+			for (int i = 0; i < length; i++)
+			{
+				if (tag.equals((nl.item(i)).getNodeName()))
+				{
+					ret.add((Element) nl.item(i));
+				}
+			}
+		}
+		return ret;
+	}
+
+	/**
+	 * Checks if the childrens of element has a Node with name = tag.
+	 * @param element Element
+	 * @param tag Name of the node.
+	 * @return Returns <code>true</code> if the childrens of element has a Node with name = tag.
+	 */
+	public static boolean childsHasTag(Element element, String tag)
+	{
+		NodeList nl = element.getChildNodes();
+
+		boolean has = false;
+
+		if (nl != null)
+		{
+			int length = nl.getLength();
+
+			for (int i = 0; (i < length) && !has; i++)
+			{
+				has = (nl.item(i)).getNodeName().equals(tag);
+			}
+		}
+		return has;
+	}
+
+	/**
+	 * Returns the first Element that has name = tag in the childrens of element.
+	 * @param element Element
+	 * @param tag Name of the Element
+	 * @return Element with name = 'tag'.
+	 */
+	public static Element childsTag(Element element, String tag)
+	{
+		NodeList nl = element.getChildNodes();
+
+		if (nl != null)
+		{
+			int length = nl.getLength();
+			boolean has = false;
+
+			for (int i = 0; (i < length) && !has; i++)
+			{
+				has = (nl.item(i)).getNodeName().equals(tag);
+
+				if (has)
+				{
+					return (Element) nl.item(i);
+				}
+			}
+		}
+		
+		return null;
+	}
+
+	/**
+	 * Returns a list with the elements included in the childrens of element
+	 * that have name = tag.
+	 * @param element Element
+	 * @param tag name of the Element.
+	 * @return List with the indicated elements.
+	 */
+	public static List<Element> childsTags(Element element, String tag)
+	{
+		NodeList nl = element.getChildNodes();
+
+		ArrayList<Element> ret = new ArrayList<Element>();
+		
+		if (nl != null)
+		{
+			int length = nl.getLength();
+
+			for (int i = 0; i < length; i++)
+			{
+				if (tag.equals((nl.item(i)).getNodeName()))
+				{
+					ret.add((Element) nl.item(i));
+				}
+			}
+		}
+		return ret;
+	}
+
+	/**
+	 * Copy a given NodeList into a List<Element>
+	 * @param nodeList Node List.
+	 * @return List with the elements of nodeList.
+	 */
+	public static List<Node> copyNodeList(NodeList nodeList)
+	{
+		ArrayList<Node> copy = new ArrayList<Node>();
+		int length = nodeList.getLength();
+
+		for (int i = 0; i < length; i++)
+		{
+			copy.add((Node) nodeList.item(i));
+		}
+		
+		return copy;
+	}
+
+	/**
+	 * Create a style map from a String with style definitions.
+	 * @param style Definition of the style.
+	 * @param asig Asignation simbol used in 'style'.
+	 * @return Map with the style properties.
+	 */
+	public static HashMap<String, Object> getStyleMap(String style, String asig)
+	{
+		HashMap<String, Object> styleMap = new HashMap<String, Object>();
+		String key = "";
+		String value = "";
+		int index = 0;
+		
+		if (!style.equals(""))
+		{
+			String[] entries = style.split(";");
+
+			for (String entry : entries)
+			{
+				index = entry.indexOf(asig);
+				
+				if (index == -1)
+				{
+					key = "";
+					value = entry;
+					styleMap.put(key, value);
+				}
+				else
+				{
+					key = entry.substring(0, index);
+					value = entry.substring(index + 1);
+					styleMap.put(key, value);
+				}
+			}
+		}
+		return styleMap;
+	}
+
+	/**
+	 * Returns the string that represents the content of a given style map.
+	 * @param styleMap Map with the styles values
+	 * @return string that represents the style.
+	 */
+	public static String getStyleString(Map<String, Object> styleMap,
+			String asig)
+	{
+		String style = "";
+		Iterator<Object> it = styleMap.values().iterator();
+		Iterator<String> kit = styleMap.keySet().iterator();
+
+		while (kit.hasNext())
+		{
+			String key = kit.next();
+			Object value = it.next();
+			style = style + key + asig + value + ";";
+		}
+		return style;
+	}
+}

+ 377 - 0
src/com/mxgraph/io/mxGraphMlCodec.java

@@ -0,0 +1,377 @@
+/**
+ * Copyright (c) 2010-2012, JGraph Ltd
+ */
+package com.mxgraph.io;
+
+import com.mxgraph.io.graphml.mxGraphMlConstants;
+import com.mxgraph.io.graphml.mxGraphMlData;
+import com.mxgraph.io.graphml.mxGraphMlEdge;
+import com.mxgraph.io.graphml.mxGraphMlGraph;
+import com.mxgraph.io.graphml.mxGraphMlKey;
+import com.mxgraph.io.graphml.mxGraphMlKeyManager;
+import com.mxgraph.io.graphml.mxGraphMlNode;
+import com.mxgraph.io.graphml.mxGraphMlShapeEdge;
+import com.mxgraph.io.graphml.mxGraphMlShapeNode;
+import com.mxgraph.io.graphml.mxGraphMlUtils;
+import com.mxgraph.model.mxCell;
+import com.mxgraph.util.mxConstants;
+import com.mxgraph.util.mxDomUtils;
+import com.mxgraph.util.mxPoint;
+import com.mxgraph.view.mxCellState;
+import com.mxgraph.view.mxConnectionConstraint;
+
+import com.mxgraph.view.mxGraph;
+import com.mxgraph.view.mxGraphView;
+import java.util.HashMap;
+import java.util.List;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+/**
+ * Parses a GraphML .graphml file and imports it in the given graph.<br/>
+ * 
+ * See wikipedia.org/wiki/GraphML for more on GraphML.
+ * 
+ * This class depends from the classes contained in
+ * com.mxgraph.io.gmlImplements.
+ */
+public class mxGraphMlCodec
+{
+	/**
+	 * Receives a GraphMl document and parses it generating a new graph that is inserted in graph.
+	 * @param document XML to be parsed
+	 * @param graph Graph where the parsed graph is included.
+	 */
+	public static void decode(Document document, mxGraph graph)
+	{
+		Object parent = graph.getDefaultParent();
+
+		graph.getModel().beginUpdate();
+
+		// Initialise the key properties.
+		mxGraphMlKeyManager.getInstance().initialise(document);
+
+		NodeList graphs = document.getElementsByTagName(mxGraphMlConstants.GRAPH);
+		if (graphs.getLength() > 0)
+		{
+
+			Element graphElement = (Element) graphs.item(0);
+
+			//Create the graph model.
+			mxGraphMlGraph gmlGraph = new mxGraphMlGraph(graphElement);
+
+			gmlGraph.addGraph(graph, parent);
+		}
+
+		graph.getModel().endUpdate();
+		cleanMaps();
+	}
+
+	/**
+	 * Remove all the elements in the Defined Maps.
+	 */
+	private static void cleanMaps()
+	{
+		mxGraphMlKeyManager.getInstance().getKeyMap().clear();
+	}
+
+	/**
+	 * Generates a Xml document with the gmlGraph.
+	 * @param gmlGraph Graph model.
+	 * @return The Xml document generated.
+	 */
+	public static Document encodeXML(mxGraphMlGraph gmlGraph)
+	{
+		Document doc = mxDomUtils.createDocument();
+
+		Element graphml = doc.createElement(mxGraphMlConstants.GRAPHML);
+
+		graphml.setAttribute("xmlns", "http://graphml.graphdrawing.org/xmlns");
+		graphml.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xsi",
+				"http://www.w3.org/2001/XMLSchema-instance");
+		graphml.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:jGraph",
+				mxGraphMlConstants.JGRAPH_URL);
+		graphml.setAttributeNS(
+				"http://www.w3.org/2001/XMLSchema-instance",
+				"xsi:schemaLocation",
+				"http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd");
+
+		HashMap<String, mxGraphMlKey> keyMap = mxGraphMlKeyManager.getInstance()
+				.getKeyMap();
+
+		for (mxGraphMlKey key : keyMap.values())
+		{
+			Element keyElement = key.generateElement(doc);
+			graphml.appendChild(keyElement);
+		}
+
+		Element graphE = gmlGraph.generateElement(doc);
+		graphml.appendChild(graphE);
+
+		doc.appendChild(graphml);
+		cleanMaps();
+		return doc;
+
+	}
+
+	/**
+	 * Generates a Xml document with the cells in the graph.
+	 * @param graph Graph with the cells.
+	 * @return The Xml document generated.
+	 */
+	public static Document encode(mxGraph graph)
+	{
+		mxGraphMlGraph gmlGraph = new mxGraphMlGraph();
+		Object parent = graph.getDefaultParent();
+
+		createKeyElements();
+
+		gmlGraph = decodeGraph(graph, parent, gmlGraph);
+		gmlGraph.setEdgedefault(mxGraphMlConstants.EDGE_DIRECTED);
+
+		Document document = encodeXML(gmlGraph);
+
+		return document;
+	}
+
+	/**
+	 * Creates the key elements for the encode.
+	 */
+	private static void createKeyElements()
+	{
+		HashMap<String, mxGraphMlKey> keyMap = mxGraphMlKeyManager.getInstance()
+				.getKeyMap();
+		mxGraphMlKey keyNode = new mxGraphMlKey(mxGraphMlConstants.KEY_NODE_ID,
+				mxGraphMlKey.keyForValues.NODE, mxGraphMlConstants.KEY_NODE_NAME,
+				mxGraphMlKey.keyTypeValues.STRING);
+		keyMap.put(mxGraphMlConstants.KEY_NODE_ID, keyNode);
+		mxGraphMlKey keyEdge = new mxGraphMlKey(mxGraphMlConstants.KEY_EDGE_ID,
+				mxGraphMlKey.keyForValues.EDGE, mxGraphMlConstants.KEY_EDGE_NAME,
+				mxGraphMlKey.keyTypeValues.STRING);
+		keyMap.put(mxGraphMlConstants.KEY_EDGE_ID, keyEdge);
+		mxGraphMlKeyManager.getInstance().setKeyMap(keyMap);
+	}
+
+	/**
+	 * Returns a Gml graph with the data of the vertexes and edges in the graph.
+	 * @param gmlGraph Gml document where the elements are put.
+	 * @param parent Parent cell of the vertexes and edges to be added.
+	 * @param graph Graph that contains the vertexes and edges.
+	 * @return Returns the document with the elements added.
+	 */
+	public static mxGraphMlGraph decodeGraph(mxGraph graph, Object parent,
+			mxGraphMlGraph gmlGraph)
+	{
+		Object[] vertexes = graph.getChildVertices(parent);
+		List<mxGraphMlEdge> gmlEdges = gmlGraph.getEdges();
+		gmlEdges = encodeEdges(gmlEdges, parent, graph);
+		gmlGraph.setEdges(gmlEdges);
+
+		for (Object vertex : vertexes)
+		{
+			List<mxGraphMlNode> Gmlnodes = gmlGraph.getNodes();
+
+			mxCell v = (mxCell) vertex;
+			String id = v.getId();
+
+			mxGraphMlNode gmlNode = new mxGraphMlNode(id, null);
+			addNodeData(gmlNode, v);
+			Gmlnodes.add(gmlNode);
+			gmlGraph.setNodes(Gmlnodes);
+			mxGraphMlGraph gmlGraphx = new mxGraphMlGraph();
+
+			gmlGraphx = decodeGraph(graph, vertex, gmlGraphx);
+
+			if (!gmlGraphx.isEmpty())
+			{
+				List<mxGraphMlGraph> nodeGraphs = gmlNode.getNodeGraph();
+				nodeGraphs.add(gmlGraphx);
+				gmlNode.setNodeGraph(nodeGraphs);
+			}
+		}
+
+		return gmlGraph;
+	}
+
+	/**
+	 * Add the node data in the gmlNode.
+	 * @param gmlNode Gml node where the data add.
+	 * @param v mxCell where data are obtained.
+	 */
+	public static void addNodeData(mxGraphMlNode gmlNode, mxCell v)
+	{
+		mxGraphMlData data = new mxGraphMlData();
+		mxGraphMlShapeNode dataShapeNode = new mxGraphMlShapeNode();
+
+		data.setDataKey(mxGraphMlConstants.KEY_NODE_ID);
+		dataShapeNode
+				.setDataHeight(String.valueOf(v.getGeometry().getHeight()));
+		dataShapeNode.setDataWidth(String.valueOf(v.getGeometry().getWidth()));
+		dataShapeNode.setDataX(String.valueOf(v.getGeometry().getX()));
+		dataShapeNode.setDataY(String.valueOf(v.getGeometry().getY()));
+		dataShapeNode.setDataLabel(v.getValue() != null ? v.getValue()
+				.toString() : "");
+		dataShapeNode.setDataStyle(v.getStyle() != null ? v.getStyle() : "");
+
+		data.setDataShapeNode(dataShapeNode);
+		gmlNode.setNodeData(data);
+	}
+
+	/**
+	 * Add the edge data in the gmlEdge.
+	 * @param gmlEdge Gml edge where the data add.
+	 * @param v mxCell where data are obtained.
+	 */
+	public static void addEdgeData(mxGraphMlEdge gmlEdge, mxCell v)
+	{
+		mxGraphMlData data = new mxGraphMlData();
+		mxGraphMlShapeEdge dataShapeEdge = new mxGraphMlShapeEdge();
+
+		data.setDataKey(mxGraphMlConstants.KEY_EDGE_ID);
+		dataShapeEdge.setText(v.getValue() != null ? v.getValue().toString()
+				: "");
+		dataShapeEdge.setStyle(v.getStyle() != null ? v.getStyle() : "");
+
+		data.setDataShapeEdge(dataShapeEdge);
+		gmlEdge.setEdgeData(data);
+	}
+
+	/**
+	 * Converts a connection point in the string representation of a port.
+	 * The specials names North, NorthWest, NorthEast, East, West, South, SouthEast and SouthWest
+	 * may be returned. Else, the values returned follows the pattern "double,double"
+	 * where double must be in the range 0..1
+	 * @param point mxPoint
+	 * @return Name of the port
+	 */
+	private static String pointToPortString(mxPoint point)
+	{
+		String port = "";
+		if (point != null)
+		{
+			double x = point.getX();
+			double y = point.getY();
+
+			if (x == 0 && y == 0)
+			{
+				port = "NorthWest";
+			}
+			else if (x == 0.5 && y == 0)
+			{
+				port = "North";
+			}
+			else if (x == 1 && y == 0)
+			{
+				port = "NorthEast";
+			}
+			else if (x == 1 && y == 0.5)
+			{
+				port = "East";
+			}
+			else if (x == 1 && y == 1)
+			{
+				port = "SouthEast";
+			}
+			else if (x == 0.5 && y == 1)
+			{
+				port = "South";
+			}
+			else if (x == 0 && y == 1)
+			{
+				port = "SouthWest";
+			}
+			else if (x == 0 && y == 0.5)
+			{
+				port = "West";
+			}
+			else
+			{
+				port = "" + x + "," + y;
+			}
+		}
+		return port;
+	}
+
+	/**
+	 * Returns a list of mxGmlEdge with the data of the edges in the graph.
+	 * @param Gmledges List where the elements are put.
+	 * @param parent Parent cell of the edges to be added.
+	 * @param graph Graph that contains the edges.
+	 * @return Returns the list Gmledges with the elements added.
+	 */
+	private static List<mxGraphMlEdge> encodeEdges(List<mxGraphMlEdge> Gmledges,
+			Object parent, mxGraph graph)
+	{
+		Object[] edges = graph.getChildEdges(parent);
+		for (Object edge : edges)
+		{
+			mxCell e = (mxCell) edge;
+			mxCell source = (mxCell) e.getSource();
+			mxCell target = (mxCell) e.getTarget();
+
+			String sourceName = "";
+			String targetName = "";
+			String sourcePort = "";
+			String targetPort = "";
+			sourceName = source != null ? source.getId() : "";
+			targetName = target != null ? target.getId() : "";
+
+			//Get the graph view that contains the states
+			mxGraphView view = graph.getView();
+			mxPoint sourceConstraint = null;
+			mxPoint targetConstraint = null;
+			if (view != null)
+			{
+				mxCellState edgeState = view.getState(edge);
+				mxCellState sourceState = view.getState(source);
+				mxConnectionConstraint scc = graph.getConnectionConstraint(
+						edgeState, sourceState, true);
+				if (scc != null)
+				{
+					sourceConstraint = scc.getPoint();
+				}
+
+				mxCellState targetState = view.getState(target);
+				mxConnectionConstraint tcc = graph.getConnectionConstraint(
+						edgeState, targetState, false);
+				if (tcc != null)
+				{
+					targetConstraint = tcc.getPoint();
+				}
+			}
+
+			//gets the port names
+			targetPort = pointToPortString(targetConstraint);
+			sourcePort = pointToPortString(sourceConstraint);
+
+			mxGraphMlEdge Gmledge = new mxGraphMlEdge(sourceName, targetName,
+					sourcePort, targetPort);
+
+			String style = e.getStyle();
+
+			if (style == null)
+			{
+				style = "horizontal";
+
+			}
+
+			HashMap<String, Object> styleMap = mxGraphMlUtils.getStyleMap(style,
+					"=");
+			String endArrow = (String) styleMap.get(mxConstants.STYLE_ENDARROW);
+			if ((endArrow != null && !endArrow.equals(mxConstants.NONE))
+					|| endArrow == null)
+			{
+				Gmledge.setEdgeDirected("true");
+			}
+			else
+			{
+				Gmledge.setEdgeDirected("false");
+			}
+			addEdgeData(Gmledge, e);
+			Gmledges.add(Gmledge);
+		}
+
+		return Gmledges;
+	}
+}

File diff ditekan karena terlalu besar
+ 1145 - 0
src/com/mxgraph/io/mxVsdxCodec.java


+ 917 - 0
src/com/mxgraph/io/vsdx/Shape.java

@@ -0,0 +1,917 @@
+package com.mxgraph.io.vsdx;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import com.mxgraph.io.mxVsdxCodec;
+
+public class Shape extends Style {
+
+	protected double width = 0;
+	
+	protected double height = 0;
+	protected double lastX = 0;
+	
+	protected double lastY = 0;
+	
+	protected double lastMoveX = 0;
+	
+	protected double lastMoveY = 0;
+
+	protected double lastKnot = -1;
+	
+	protected List<Element> geom;
+	
+	protected Map<String, String> imageData;
+
+	public mxPathDebug debug = null;
+
+	public Shape(Element shape, mxVsdxModel model)
+	{
+		super(shape, model);
+		this.width = getScreenNumericalValue(this.cellElements.get(mxVsdxConstants.WIDTH), 0);
+		this.height = getScreenNumericalValue(this.cellElements.get(mxVsdxConstants.HEIGHT), 0);
+	}
+
+	/**
+	 * Caches the specified element
+	 * @param elem the element to cache
+	 */
+	protected void parseShapeElem(Element elem, mxVsdxModel model)
+	{
+		super.parseShapeElem(elem, model);
+		
+		String childName = elem.getNodeName();
+
+		if (childName.equals("ForeignData"))
+		{
+			String filename = elem.getOwnerDocument().getDocumentURI();
+			String iType = elem.getAttribute("ForeignType");
+			String compression = elem.getAttribute("CompressionType");
+			
+			if (iType.equals("Bitmap"))
+			{
+				compression = compression.toLowerCase();
+			}
+			else if (iType.equals("MetaFile"))
+			{
+				compression = "x-wmf";
+			}
+			else if (iType.equals("Enhanced Metafile"))
+			{
+				compression = "x-emf";
+			}
+			else
+			{
+				//TODO log and unsupported type
+				return;
+			}
+			
+			Node fdChild = elem.getFirstChild();
+			
+			if (fdChild != null)
+			{
+				if (fdChild instanceof Element)
+				{
+					Element fdElem = (Element) fdChild;
+					String grandchildName = fdElem.getNodeName();
+					
+					if (grandchildName.toLowerCase().equals("rel"))
+					{
+						String rid = fdElem.getAttribute("r:id");
+						
+						if (rid != null && !rid.isEmpty())
+						{
+							// insert "_rel" into the path
+							int index = filename.lastIndexOf('/');
+							String pre = "";
+							String post = "";
+
+							try
+							{
+								pre = filename.substring(0, index);
+								post = filename.substring(index, filename.length());
+							}
+							catch (IndexOutOfBoundsException e)
+							{
+								return;
+							}
+							
+							Element relElem = model.getRelationship(rid, pre + "/_rels" + post + ".rels");
+							
+							if (relElem != null)
+							{
+								String target = relElem.getAttribute("Target");
+								String type = relElem.getAttribute("Type");
+								index = target.lastIndexOf('/');
+								
+								try
+								{
+									target = target.substring(index + 1, target.length());
+								}
+								catch (IndexOutOfBoundsException e)
+								{
+									return;
+								}
+								
+								if (type != null && type.endsWith("image"))
+								{
+									this.imageData = new HashMap<String, String>();
+									this.imageData.put("iData", model.getMedia(mxVsdxCodec.vsdxPlaceholder + "/media/" + target));
+									this.imageData.put("iType", compression);
+								}
+							}
+							else
+							{
+								//TODO log path issue
+							}
+							
+							// more than one rel would break things
+							return;
+						}
+						
+
+					}
+				}
+				
+				fdChild = fdChild.getNextSibling();
+			}
+		}
+	}
+
+	/**
+	 * Caches the specific section element
+	 * @param elem the element to cache
+	 */
+	protected void parseSection(Element elem)
+	{
+		super.parseSection(elem);
+		String n = elem.getAttribute("N");
+		
+		if (geom == null)
+		{
+			geom = new ArrayList<Element>();
+		}
+
+		if (n.equals("Geometry"))
+		{
+			this.geom.add(elem);
+		}
+	}
+
+	/**
+	 * 
+	 * @return mxGraph stencil XML or null or there is no displayed geometry
+	 */
+	protected String parseGeom()
+	{
+		if (!hasGeom())
+		{
+			return "";
+		}
+		
+		String parsedGeom = "";
+		double h = this.getHeight();
+		double w = this.getWidth();
+		double x = 0, y = 0;
+		
+		for (int i = 0; i < geom.size(); i++)
+		{
+			boolean noFill = false;
+			boolean noLine = false;
+			boolean noShow = false;
+			boolean noSnap = false;
+			String geomElemParsed = "";
+
+			Node child = geom.get(i).getFirstChild();
+			
+			while (child != null)
+			{
+				if (child instanceof Element)
+				{
+					Element childElem = (Element) child;
+					String childName = childElem.getNodeName();
+					String value = null;
+					
+					if (childName.equals("Cell"))
+					{
+						childName = childElem.getAttribute("N");
+						value = childElem.getAttribute("V");
+					}
+					else if (childName.equals("Row"))
+					{
+						childName = childElem.getAttribute("T");
+					}
+					else
+					{
+						value = childElem.getTextContent();
+					}
+					
+					switch (childName)
+					{
+						case "NoFill":
+							if (value != null && value.equals("1"))
+							{
+								noFill = true;
+							}
+							break;
+						case "NoLine":
+							if (value != null && value.equals("1"))
+							{
+								noLine = true;
+							}
+							break;
+						case "NoShow":
+							if (value != null && value.equals("1"))
+							{
+								noShow = true;
+							}
+							break;
+						case "NoSnap":
+							if (value != null && value.equals("1"))
+							{
+								noSnap = true;
+							}
+							break;
+						case "MoveTo":
+							Map <String, String> children = getChildValues(childElem, null);
+							String xValue = children.get("X");
+							String yValue = children.get("Y");
+								
+							if (xValue != null && yValue != null)
+							{
+								x = Double.parseDouble(xValue) * mxVsdxUtils.conversionFactor;
+								y = Double.parseDouble(yValue) * mxVsdxUtils.conversionFactor;
+							}
+							
+							x = x * 100.0 / w;
+							y = y * 100.0 / h;
+							y = 100 - y;
+
+							x = Math.round(x * 100.0) / 100.0;
+							y = Math.round(y * 100.0) / 100.0;
+							
+							this.lastX = x;
+							this.lastY = y;
+							this.lastMoveX = x;
+							this.lastMoveY = y;
+
+							geomElemParsed += "<" + "move" + " x=\"" + String.valueOf(x) + "\" y=\"" + String.valueOf(y) + "\"/>";
+							break;
+						case "RelMoveTo":
+							children = getChildValues(childElem, null);
+							xValue = children.get("X");
+							yValue = children.get("Y");
+								
+							if (xValue != null && yValue != null)
+							{
+								x = Double.parseDouble(xValue) * 100;
+								y = 100 - Double.parseDouble(yValue) * 100;
+							}
+							
+							x = Math.round(x * 100.0) / 100.0;
+							y = Math.round(y * 100.0) / 100.0;
+							
+							this.lastX = x;
+							this.lastY = y;
+							this.lastMoveX = x;
+							this.lastMoveY = y;
+
+							geomElemParsed += "<" + "move" + " x=\"" + String.valueOf(x) + "\" y=\"" + String.valueOf(y) + "\"/>";
+							break;
+						case "LineTo":
+							children = getChildValues(childElem, null);
+							xValue = children.get("X");
+							yValue = children.get("Y");
+								
+							if (xValue != null && yValue != null)
+							{
+								x = Double.parseDouble(xValue) * mxVsdxUtils.conversionFactor;
+								y = Double.parseDouble(yValue) * mxVsdxUtils.conversionFactor;
+							}
+	
+							x = x * 100.0 / w;
+							y = y * 100.0 / h;
+							y = 100 - y;
+
+							x = Math.round(x * 100.0) / 100.0;
+							y = Math.round(y * 100.0) / 100.0;
+							
+							this.lastX = x;
+							this.lastY = y;
+
+							geomElemParsed += "<" + "line" + " x=\"" + String.valueOf(x) + "\" y=\"" + String.valueOf(y) + "\"/>";
+							break;
+						case "RelLineTo":
+							children = getChildValues(childElem, null);
+							xValue = children.get("X");
+							yValue = children.get("Y");
+								
+							if (xValue != null && yValue != null)
+							{
+								x = Double.parseDouble(xValue) * 100;
+								y = 100 - Double.parseDouble(yValue) * 100;
+							}
+							
+							x = Math.round(x * 100.0) / 100.0;
+							y = Math.round(y * 100.0) / 100.0;
+							
+							this.lastX = x;
+							this.lastY = y;
+
+							geomElemParsed += "<" + "line" + " x=\"" + String.valueOf(x) + "\" y=\"" + String.valueOf(y) + "\"/>";
+							break;
+						case "NURBSTo":
+							geomElemParsed += nurbsPath(childElem, "curve");
+							break;
+						case "ArcTo":
+							geomElemParsed += arcPath(childElem, "arc");
+							break;
+						case "InfiniteLine":
+							//xyElem(childElem, "line");
+							break;
+						case "Ellipse":
+							geomElemParsed += ellipsePath(childElem, "ellipse");
+							break;
+						case "EllipticalArcTo":
+							geomElemParsed += ellArcPath(childElem, "arc");
+							break;
+						case "SplineStart":
+							geomElemParsed += splineStartPath(childElem, "splineStart");
+							break;
+						case "SplineKnot":
+							geomElemParsed += splinePath(childElem, "splineKnot");
+							break;
+						case "PolylineTo":
+							geomElemParsed += polyPath(childElem, "poly");
+							break;
+						default:
+							this.styleDebug("ERROR: geom type not understood  - " + childName);
+							break;
+							
+					}
+				}
+
+				child = child.getNextSibling();
+			}
+			
+			if (!noShow && !geomElemParsed.equals(""))
+			{
+				if (noFill)
+				{
+					geomElemParsed += "</path><stroke/>";
+				}
+				else
+				{
+					geomElemParsed += "</path><fillstroke/>";
+				}
+				
+				parsedGeom += "<path>" + geomElemParsed;
+			}
+		}
+
+		if (parsedGeom.equals(""))
+		{
+			return "";
+		}
+		
+		//System.out.println(parsedGeom);
+
+		return "<shape strokewidth=\"inherit\"><foreground>" + parsedGeom + "</foreground></shape>";
+	}
+
+	protected String arcPath(Element arcElem, String command)
+	{
+		Map <String, String> children = getChildValues(arcElem, null);
+		String xValue = children.get("X");
+		String yValue = children.get("Y");
+		String aValue = children.get("A");
+			
+		if (xValue != null && yValue != null && aValue != null)
+		{
+			double h = this.getHeight();
+			double w = this.getWidth();
+			double x0 = Math.round(this.lastX * w) / 100;
+			double y0 = Math.round(this.lastY * h) / 100;
+			double x = Double.parseDouble(xValue) * mxVsdxUtils.conversionFactor;
+			
+			double y = Double.parseDouble(yValue) * mxVsdxUtils.conversionFactor;
+			y = h - y;
+			
+			double a = Double.parseDouble(aValue) * mxVsdxUtils.conversionFactor;
+
+			double dx = Math.abs(x - x0);
+			double dy = Math.abs(y - y0);
+
+			double rx = (a * 0.5) + (dx * dx + dy * dy) / (8.0 * a);
+			double ry = rx;
+			double r0 = Math.abs(rx);
+			
+			rx = rx * 100 / w;
+			ry = ry * 100 / h;
+			x = x * 100 / w;
+			y = y * 100 / h;
+			rx = Math.round(rx * 100.0) / 100.0;
+			ry = Math.round(ry * 100.0) / 100.0;
+			x = Math.round(x * 100.0) / 100.0;
+			y = Math.round(y * 100.0) / 100.0;
+
+			a = Math.round(a * 100.0) / 100.0;
+			rx = Math.abs(rx);
+			ry = Math.abs(ry);
+			
+			//determine sweep and large-arc flag
+			String sf = (a < 0) ? "1" : "0";
+			String laf = (r0 < Math.abs(a)) ? "1" : "0";
+
+			if (debug != null)
+			{
+				debug.drawLine(x0, y0, x, y, "");
+			}
+			
+			this.lastX = x;
+			this.lastY = y;
+
+			return "<" + command + 
+					" rx=\"" + String.valueOf(rx) + 
+					"\" ry=\"" + String.valueOf(ry) + 
+					"\" x=\"" + String.valueOf(x) + 
+					"\" y=\"" + String.valueOf(y) + 
+					"\" x-axis-rotation=\"0" + 
+					"\" large-arc-flag=\"" + laf + 
+					"\" sweep-flag=\"" + sf + 
+					"\"/>";
+		}
+		
+		return "";
+	}
+	
+	protected String ellipsePath(Element ellipseElem, String command)
+	{
+		Map <String, String> children = getChildValues(ellipseElem, null);
+		String xValue = children.get("X");
+		String yValue = children.get("Y");
+		String aValue = children.get("A");
+		String bValue = children.get("B");
+		String cValue = children.get("C");
+		String dValue = children.get("D");
+
+		if (xValue != null && yValue != null && aValue != null && bValue != null && cValue != null && dValue != null)
+		{
+			double h = this.getHeight();
+			double w = this.getWidth();
+			double x = Double.parseDouble(xValue) * mxVsdxUtils.conversionFactor;
+			double y = Double.parseDouble(yValue) * mxVsdxUtils.conversionFactor;
+			y = h - y;
+			double a = Double.parseDouble(aValue) * mxVsdxUtils.conversionFactor;
+			double b = Double.parseDouble(bValue) * mxVsdxUtils.conversionFactor;
+			b = h - b;
+			double c = Double.parseDouble(cValue) * mxVsdxUtils.conversionFactor;
+			double d = Double.parseDouble(dValue) * mxVsdxUtils.conversionFactor;
+			d = h - d;
+			
+			double dx1 = Math.abs(a - x);
+			double dy1 = Math.abs(b - y);
+			double r1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
+
+			double dx2 = Math.abs(c - x);
+			double dy2 = Math.abs(d - y);
+			double r2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
+			double newX = (x - r1) * 100 / w;
+			double newY = (y - r2) * 100 / h;
+			double newW = 2 * r1 * 100 / w;
+			double newH = 2 * r2 * 100 / h;
+			newH = Math.round(newH * 100.0) / 100.0;
+			newW = Math.round(newW * 100.0) / 100.0;
+			newX = Math.round(newX * 100.0) / 100.0;
+			newY = Math.round(newY * 100.0) / 100.0;
+			
+			return "<" + command + 
+					" x=\"" + String.valueOf(newX) + 
+					"\" y=\"" + String.valueOf(newY) + 
+					"\" w=\"" + String.valueOf(newW) + 
+					"\" h=\"" + String.valueOf(newH) + 
+					"\"/>";
+		}
+		
+		return "";
+	}
+	
+	protected String ellArcPath(Element ellArcElem, String command)
+	{
+		Map <String, String> children = getChildValues(ellArcElem, null);
+		String xValue = children.get("X");
+		String yValue = children.get("Y");
+		String aValue = children.get("A");
+		String bValue = children.get("B");
+		String cValue = children.get("C");
+		String dValue = children.get("D");
+		
+		if (xValue != null && yValue != null && aValue != null && bValue != null && cValue != null && dValue != null)
+		{
+			double h = this.getHeight();
+			double w = this.getWidth();
+			double x = Double.parseDouble(xValue) * mxVsdxUtils.conversionFactor;
+			double y = Double.parseDouble(yValue) * mxVsdxUtils.conversionFactor;
+			y = h - y;
+			double a = Double.parseDouble(aValue) * mxVsdxUtils.conversionFactor;
+			double b = Double.parseDouble(bValue) * mxVsdxUtils.conversionFactor;
+			double c = Double.parseDouble(cValue);
+			double d = Double.parseDouble(dValue);
+			
+			x = x * 100.0 / w;
+			y = y * 100.0 / h;
+			
+			double x1 = this.lastX * w / 100.0;
+			double y1 = this.lastY * h / 100.0;
+			
+			double x2 = x * w / 100.0;
+			double y2 = y * h / 100.0;
+			
+			double x3 = a;
+			double y3 = h - b;
+
+			double ang = -c;
+			
+			double p1x = Math.sqrt(x1 * x1 + y1 * y1) * Math.cos(Math.atan2(y1, x1) - ang);
+			double p1y = Math.sqrt(x1 * x1 + y1 * y1) * Math.sin(Math.atan2(y1, x1) - ang);
+            
+			double p2x = Math.sqrt(x2 * x2 + y2 * y2) * Math.cos(Math.atan2(y2, x2) - ang);
+			double p2y = Math.sqrt(x2 * x2 + y2 * y2) * Math.sin(Math.atan2(y2, x2) - ang);
+            
+			double p3x = Math.sqrt(x3 * x3 + y3 * y3) * Math.cos(Math.atan2(y3, x3) - ang);
+			double p3y = Math.sqrt(x3 * x3 + y3 * y3) * Math.sin(Math.atan2(y3, x3) - ang);
+			
+			double p0x = ((p1x-p2x)*(p1x+p2x)*(p2y-p3y)-(p2x-p3x)*(p2x+p3x)*(p1y-p2y)+d*d*(p1y-p2y)*(p2y-p3y)*(p1y-p3y))/(2*((p1x-p2x)*(p2y-p3y)-(p2x-p3x)*(p1y-p2y)));
+			double p0y = ((p1x-p2x)*(p2x-p3x)*(p1x-p3x)/(d*d)+(p2x-p3x)*(p1y-p2y)*(p1y+p2y)-(p1x-p2x)*(p2y-p3y)*(p2y+p3y))/(2*((p2x-p3x)*(p1y-p2y)-(p1x-p2x)*(p2y-p3y)));
+			
+			double newX = Math.sqrt(p0x * p0x + p0y * p0y) * Math.cos(Math.atan2(p0y, p0x) + ang);
+			double newY = Math.sqrt(p0x * p0x + p0y * p0y) * Math.sin(Math.atan2(p0y, p0x) + ang);
+			
+			newX = newX * w / 100.0;
+			newY = newY * h / 100.0;
+			
+			double dx = p1x - p0x;
+			double dy = p1y - p0y;
+			double rx = Math.sqrt(dx * dx + dy * dy * d * d);
+			double ry = rx / d;
+			double rot = Math.toDegrees(ang);
+			
+			rx = rx * 100.0 / w;
+			ry = ry * 100.0 / h;
+			
+			x = Math.round(x * 100.0) / 100.0;
+			y = Math.round(y * 100.0) / 100.0;
+			rx = Math.round(rx * 100.0) / 100.0;
+			ry = Math.round(ry * 100.0) / 100.0;
+			rot = Math.round(rot * 100.0) / 100.0;
+
+			//determine sweep
+			//TODO fix rare error (file "1 Supported Forms" shape "storeddata" on page 5)
+			double sweep = (x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1); 
+			String sf = (sweep > 0) ? "0" : "1"; 
+			
+			//determine large arc flag
+			String laf = "0";
+
+			if (mxVsdxUtils.isInsideTriangle(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) && 
+					isReflexAngle(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y))
+			{
+				laf = "1";
+			}
+			
+			if (debug != null)
+			{
+				debug.drawRect(p0x, p0y, "P0");
+				debug.drawRect(p1x, p1y, "P1");
+				debug.drawRect(p2x, p2y, "P2");
+				debug.drawRect(p3x, p3y, "P3");
+				debug.drawRect(newX, newY, "X");
+				debug.drawRect(x3, y3, "CP");
+				debug.drawLine(x1, y1, x2, y2, "");
+			}
+			
+			this.lastX = x;
+			this.lastY = y;
+			
+			return "<" + command + 
+			" rx=\"" + String.valueOf(rx) + 
+			"\" ry=\"" + String.valueOf(ry) + 
+			"\" x=\"" + String.valueOf(x) + 
+			"\" y=\"" + String.valueOf(y) + 
+			"\" x-axis-rotation=\"" + String.valueOf(rot) + 
+			"\" large-arc-flag=\"" + laf + 
+			"\" sweep-flag=\"" + sf + 
+			"\"/>";
+		}
+		
+		return "";
+	}
+
+	/**
+	 * @param x0 y0 center point of ellipse containing the arc
+	 * @param x1 y1 starting point of the arc
+	 * @param x2 y2 endpoint of the arc
+	 * @param x3 y3 control point
+	 * @return true if the start to end angle that contains the control point is a reflex angle 
+	 */
+	private boolean isReflexAngle(double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3)
+	{
+		x1 = x1 - x0;
+		y1 = y1 - y0;
+		x2 = x2 - x0;
+		y2 = y2 - y0;
+		x2 = x3 - x0;
+		y3 = y3 - y0;
+		x0 = 0;
+		y0 = 0;
+
+		double aStart = Math.toDegrees(Math.atan2(y1, x1) - Math.atan2(y0, x0));
+		double aEnd = Math.toDegrees(Math.atan2(y2, x2) - Math.atan2(y0, x0));
+		double aCP = Math.toDegrees(Math.atan2(y3, x3) - Math.atan2(y0, x0));
+		
+		aStart = (aStart - aCP) % 360;
+		aEnd = (aEnd - aCP) % 360;
+
+		if (aStart > 180)
+		{
+			aStart = aStart - 360;
+		}
+		else if (aStart < -180)
+		{
+			aStart = aStart + 360;
+		}
+		
+		if (aEnd > 180)
+		{
+			aEnd = aEnd - 360;
+		}
+		else if (aEnd < -180)
+		{
+			aEnd = aEnd + 360;
+		}
+		
+		if ((aStart > 0 && aEnd < 0) || (aStart < 0 && aEnd > 0))
+		{
+			if (Math.abs(aStart - aEnd) > 180)
+			{
+				return true;
+			}
+		}
+		
+		return false;
+	}
+
+	protected String polyPath(Element polyElem, String command)
+	{
+		Map <String, String> nodeValues = new HashMap<String, String>();
+		nodeValues.put("A", "F");
+		Map <String, String> children = getChildValues(polyElem, nodeValues);
+		String xValue = children.get("X");
+		String yValue = children.get("Y");
+		String aValue = children.get("A");
+		String result = "";
+		
+		if (xValue != null && yValue != null && aValue != null)
+		{
+			double h = this.getHeight();
+			double w = this.getWidth();
+			double x = Double.parseDouble(xValue) * mxVsdxUtils.conversionFactor;
+			double y = Double.parseDouble(yValue) * mxVsdxUtils.conversionFactor;
+			x = x * 100.0 / w;
+			y = y * 100.0 / h;
+			y = 100 - y;
+			x = Math.round(x * 100.0) / 100.0;
+			y = Math.round(y * 100.0) / 100.0;
+			
+			aValue = aValue.replaceAll("\\s","").toLowerCase().replaceAll("polyline\\(","").replaceAll("\\)", "");
+			
+			LinkedList<String> polyEntriesList = new LinkedList<String>(Arrays.asList(aValue.split(",")));
+			
+			polyEntriesList.remove(0);
+			polyEntriesList.remove(0);
+			double currX = 0;
+			double currY = 0;
+
+			while (polyEntriesList.size() > 0)
+			{
+				currX = Double.valueOf(polyEntriesList.remove(0)) * mxVsdxUtils.conversionFactor;
+				currY = Double.valueOf(polyEntriesList.remove(0)) * mxVsdxUtils.conversionFactor;
+				currY = 100 - currY;
+				
+				currX = Math.round(currX * 100.0) / 100.0;
+				currY = Math.round(currY * 100.0) / 100.0;
+
+				this.lastX = currX;
+				this.lastY = currY;
+				
+				result += "<line x=\"" + String.valueOf(currX) + "\" y=\"" + String.valueOf(currY) + "\"/>";
+			}
+
+			if (this.lastMoveX == x && this.lastMoveY == y)
+			{
+				result += "<close/>";
+			}
+		}
+		
+		return result;
+	}
+
+	protected String splineStartPath(Element splineElem, String command)
+	{
+		Map <String, String> children = getChildValues(splineElem, null);
+		String xValue = children.get("X");
+		String yValue = children.get("Y");
+		String aValue = children.get("A");
+		String bValue = children.get("B");
+		String cValue = children.get("C");
+		String dValue = children.get("D");
+		String result = "";
+		
+		if (xValue != null && yValue != null && aValue != null && bValue != null && cValue != null && dValue != null)
+		{
+			double h = this.getHeight();
+			double w = this.getWidth();
+			double x = Double.parseDouble(xValue) * mxVsdxUtils.conversionFactor;
+			double y = Double.parseDouble(yValue) * mxVsdxUtils.conversionFactor;
+			//double a = Double.parseDouble(aValue);
+			//double b = Double.parseDouble(bValue);
+			double c = Double.parseDouble(cValue);
+			int d = Integer.parseInt(dValue);
+
+			//double firstKnot = b;
+			//double secondKnot = a;
+			double lastKnot = c;
+			this.lastKnot = lastKnot;
+			int degree = d;
+//				x = x * 100.0 / w;
+//				y = y * 100.0 / h;
+			y = 100 - y;
+			x = Math.round(x * 100.0) / 100.0;
+			y = Math.round(y * 100.0) / 100.0;
+			lastKnot = Math.round(lastKnot * 100.0) / 100.0;
+			double x0 = this.lastX * w / 100.0;
+			double y0 = this.lastY * h / 100.0;
+			
+			result = "<curve ";
+
+			if (debug != null)
+			{
+				debug.drawRect(x0, y0 , "0, " + Integer.toString(degree));
+				debug.drawRect(x, y , Double.toString(lastKnot));
+				debug.drawLine(x0, y0, x, y, "");
+			}
+
+			this.lastX = x;
+			this.lastY = y;
+
+		}
+		
+		return result;
+	}
+
+	protected String splinePath(Element splineElem, String command)
+	{
+		Map <String, String> children = getChildValues(splineElem, null);
+		String xValue = children.get("X");
+		String yValue = children.get("Y");
+		String aValue = children.get("A");
+		String result = "";
+		
+		if (xValue != null && yValue != null && aValue != null)
+		{
+			//double h = this.getHeight();
+			//double w = this.getWidth();
+			double x = Double.parseDouble(xValue) * mxVsdxUtils.conversionFactor;
+			double y = Double.parseDouble(yValue) * mxVsdxUtils.conversionFactor;
+			double a = Double.parseDouble(aValue);
+
+			double knot = a;
+//				x = x * 100.0 / w;
+//				y = y * 100.0 / h;
+			y = 100 - y;
+			x = Math.round(x * 100.0) / 100.0;
+			y = Math.round(y * 100.0) / 100.0;
+			knot = Math.round(knot * 100.0) / 100.0;
+			
+
+			if (debug != null)
+			{
+				debug.drawRect(x, y, Double.toString(knot));
+				debug.drawLine(this.lastX, this.lastY, x, y, "");
+			}
+			
+			this.lastX = x;
+			this.lastY = y;
+		}
+		
+		return result;
+	}
+
+	protected String nurbsPath(Element nurbsElem, String command)
+	{
+		Map <String, String> children = getChildValues(nurbsElem, null);
+		String xValue = children.get("X");
+		String yValue = children.get("Y");
+		String eValue = children.get("E");
+		String result = "";
+
+		if (xValue != null && yValue != null && eValue != null)
+		{
+			double h = this.getHeight();
+			double w = this.getWidth();
+			double x = Double.parseDouble(xValue) * mxVsdxUtils.conversionFactor;
+			double y = Double.parseDouble(yValue) * mxVsdxUtils.conversionFactor;
+			eValue = eValue.replace("NURBS(", "");
+			eValue = eValue.replace(")", "");
+			
+			List<String> nurbsValues = Arrays.asList(eValue.split("\\s*,\\s*"));
+			
+			if (nurbsValues.size() >= 10)
+			{
+				double x1 = Double.parseDouble(nurbsValues.get(4)) * 100.0;
+				double y1 = 100 - Double.parseDouble(nurbsValues.get(5)) * 100.0;
+				double x2 = Double.parseDouble(nurbsValues.get(8)) * 100.0;
+				double y2 = 100 - Double.parseDouble(nurbsValues.get(9)) * 100.0;
+	
+				y = y * 100.0 / h;
+				x = x * 100.0 / w;
+				y = 100 - y;
+				x = Math.round(x * 100.0) / 100.0;
+				y = Math.round(y * 100.0) / 100.0;
+				x1 = Math.round(x1 * 100.0) / 100.0;
+				y1 = Math.round(y1 * 100.0) / 100.0;
+				x2 = Math.round(x2 * 100.0) / 100.0;
+				y2 = Math.round(y2 * 100.0) / 100.0;
+	
+				if (debug != null)
+				{
+					debug.drawRect(x, y, "");
+					debug.drawLine(this.lastX, this.lastY, x, y, "");
+				}
+				
+				this.lastX = x;
+				this.lastY = y;
+				
+				result += "<curve x1=\"" + String.valueOf(x1) + "\" y1=\"" + String.valueOf(y1) + 
+						      "\" x2=\"" + String.valueOf(x2) + "\" y2=\"" + String.valueOf(y2) + 
+						      "\" x3=\"" + String.valueOf(x) + "\" y3=\"" + String.valueOf(y) + "\"/>";
+			}
+		}
+
+		return result;
+	}
+
+	/**
+	 * Checks if the shape has defined a width element.
+	 * @return Returns <code>true</code> if the shape has defined a width element.
+	 */
+	public boolean hasWidth()
+	{
+		return hasProperty(mxVsdxConstants.X_FORM, mxVsdxConstants.WIDTH);
+	}
+
+	/**
+	 * Returns the value of the width element in pixels.
+	 * @return Numerical value of the width element.
+	 */
+	public double getWidth()
+	{
+		return this.width;
+	}
+
+	/**
+	 * Checks if the shape has defined a height element.
+	 * @return Returns <code>true</code> if the shape has defined a height element.
+	 */
+	public boolean hasHeight()
+	{
+		return hasProperty(mxVsdxConstants.X_FORM, mxVsdxConstants.HEIGHT);
+	}
+
+	/**
+	 * Returns the value of the height element in pixels.
+	 * @return Numerical value of the height element.
+	 */
+	public double getHeight()
+	{
+		return this.height;
+	}
+	
+	/**
+	 * Returns whether or not this shape has a geometry defined, locally
+	 * or inherited
+	 * @return whether the shape has a geometry
+	 */
+	public boolean hasGeom()
+	{
+		return !(this.geom == null || this.geom.isEmpty());
+	}
+}

+ 55 - 0
src/com/mxgraph/io/vsdx/ShapePageId.java

@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2010-2016, JGraph Ltd
+ * Copyright (c) 2010-2016, Gaudenz Alder
+ */
+package com.mxgraph.io.vsdx;
+
+/**
+ * Wraps the page and shape ID within that page to create a unique ID
+ */
+public class ShapePageId
+{
+	private int pageNumber;
+
+	private int Id;
+
+	public ShapePageId(int pageNumber, int Id)
+	{
+		this.pageNumber = pageNumber;
+		this.Id = Id;
+	}
+
+	public int getId()
+	{
+		return Id;
+	}
+
+	public int getPageNumber()
+	{
+		return pageNumber;
+	}
+
+	@Override
+	public boolean equals(Object obj)
+	{
+		if (obj == null || getClass() != obj.getClass())
+		{
+			return false;
+		}
+		
+		final ShapePageId other = (ShapePageId) obj;
+		
+		if (this.pageNumber != other.pageNumber || this.Id != other.Id)
+		{
+			return false;
+		}
+		
+		return true;
+	}
+
+	@Override
+	public int hashCode()
+	{
+		return 100000 * this.pageNumber + this.Id;
+	}
+}

File diff ditekan karena terlalu besar
+ 1129 - 0
src/com/mxgraph/io/vsdx/Style.java


File diff ditekan karena terlalu besar
+ 2005 - 0
src/com/mxgraph/io/vsdx/VsdxShape.java


+ 465 - 0
src/com/mxgraph/io/vsdx/mxMasterShape.java

@@ -0,0 +1,465 @@
+package com.mxgraph.io.vsdx;
+
+import org.w3c.dom.Element;
+
+/**
+ * This class is a wrapper for a shape element.<br/>
+ * Contains references to the stylesheets indicated in the shape.<br/>
+ * If a property is not found in the shape Element but it may be found in a stylesheet,
+ * the property is searched in such stylesheet.
+ */
+public class mxMasterShape extends Shape
+{
+	public mxMasterShape(Element s, mxVsdxModel model)
+	{
+		super(s, model);
+	}
+
+	@Override
+	public String getTextColor(String charIX)
+	{
+		if (super.hasTextColor(charIX))
+		{
+			return super.getTextColor(charIX);
+		}
+		else if (textParent != null)
+		{
+			return textParent.getTextColor(charIX);
+		}
+		
+		return "#000000";
+	}
+
+	@Override
+	public boolean hasTextColor(String charIX)
+	{
+		boolean has = super.hasTextColor(charIX);
+		
+		if ((textParent != null) && !has)
+		{
+			has = textParent.hasTextColor(charIX);
+		}
+		
+		return has;
+	}
+
+	@Override
+	public String getTextFont(String charIX)
+	{
+		if (super.hasTextFont(charIX))
+		{
+			return super.getTextFont(charIX);
+		}
+		else if (textParent != null)
+		{
+			return textParent.getTextFont(charIX);
+		}
+		
+		return "";
+	}
+
+	@Override
+	public String getTextSize(String charIX)
+	{
+		if (super.hasTextSize(charIX))
+		{
+			return super.getTextSize(charIX);
+		}
+		else if (textParent != null)
+		{
+			return textParent.getTextSize(charIX);
+		}
+		
+		return "";
+	}
+
+	@Override
+	public boolean hasTextFont(String charIX)
+	{
+		boolean has = super.hasTextFont(charIX);
+		
+		if ((textParent != null) && !has)
+		{
+			has = textParent.hasTextFont(charIX);
+		}
+		
+		return has;
+	}
+
+	@Override
+	public boolean hasTextSize(String charIX)
+	{
+		boolean has = super.hasTextSize(charIX);
+		
+		if ((textParent != null) && !has)
+		{
+			has = textParent.hasTextSize(charIX);
+		}
+		
+		return has;
+	}
+
+	@Override
+	public int getTextPos(String charIX)
+	{
+		if (super.hasTextPos(charIX))
+		{
+			return super.getTextPos(charIX);
+		}
+		else if (textParent != null)
+		{
+			return textParent.getTextPos(charIX);
+		}
+		
+		return 0;
+	}
+
+	@Override
+	public boolean getTextStrike(String charIX)
+	{
+		if (super.hasTextStrike(charIX))
+		{
+			return super.getTextStrike(charIX);
+		}
+		else if (textParent != null)
+		{
+			return textParent.getTextStrike(charIX);
+		}
+		
+		return false;
+	}
+
+	@Override
+	public String getTextStyle(String charIX)
+	{
+		if (super.hasTextStyle(charIX))
+		{
+			return super.getTextStyle(charIX);
+		}
+		else if (textParent != null)
+		{
+			return textParent.getTextStyle(charIX);
+		}
+		
+		return "";
+	}
+
+	@Override
+	public boolean hasTextPos(String charIX)
+	{
+		boolean has = super.hasTextPos(charIX);
+		
+		if ((textParent != null) && !has)
+		{
+			has = textParent.hasTextPos(charIX);
+		}
+		
+		return has;
+	}
+
+	@Override
+	public boolean hasTextStrike(String charIX)
+	{
+		boolean has = super.hasTextPos(charIX);
+		
+		if ((textParent != null) && !has)
+		{
+			has = textParent.hasTextPos(charIX);
+		}
+		
+		return has;
+	}
+
+	@Override
+	public boolean hasTextStyle(String charIX)
+	{
+		boolean has = super.hasTextStyle(charIX);
+		
+		if ((textParent != null) && !has)
+		{
+			has = textParent.hasTextStyle(charIX);
+		}
+		
+		return has;
+	}
+
+	@Override
+	public int getHorizontalAlign(String paraIX)
+	{
+		if (super.hasHorizontalAlign(paraIX))
+		{
+			return super.getHorizontalAlign(paraIX);
+		}
+		else if (textParent != null)
+		{
+			return textParent.getHorizontalAlign(paraIX);
+		}
+		
+		return 0;
+	}
+
+	@Override
+	public String getIndentFirst(String paraIX)
+	{
+		if (super.hasIndentFirst(paraIX))
+		{
+			return super.getIndentFirst(paraIX);
+		}
+		else if (textParent != null)
+		{
+			return textParent.getIndentFirst(paraIX);
+		}
+		
+		return "0";
+	}
+
+	@Override
+	public boolean hasHorizontalAlign(String paraIX)
+	{
+		boolean has = super.hasHorizontalAlign(paraIX);
+		
+		if ((textParent != null) && !has)
+		{
+			has = textParent.hasHorizontalAlign(paraIX);
+		}
+		
+		return has;
+	}
+
+	@Override
+	public boolean hasIndentFirst(String paraIX)
+	{
+		boolean has = super.hasHorizontalAlign(paraIX);
+		
+		if ((textParent != null) && !has)
+		{
+			has = textParent.hasHorizontalAlign(paraIX);
+		}
+		
+		return has;
+	}
+
+	@Override
+	public String getIndentLeft(String paraIX)
+	{
+		if (super.hasIndentLeft(paraIX))
+		{
+			return super.getIndentLeft(paraIX);
+		}
+		else if (textParent != null)
+		{
+			return textParent.getIndentLeft(paraIX);
+		}
+		
+		return "0";
+	}
+
+	@Override
+	public boolean hasIndentLeft(String paraIX)
+	{
+		boolean has = super.hasIndentLeft(paraIX);
+		
+		if ((textParent != null) && !has)
+		{
+			has = textParent.hasIndentLeft(paraIX);
+		}
+		
+		return has;
+	}
+
+	@Override
+	public String getIndentRight(String paraIX)
+	{
+		if (super.hasIndentRight(paraIX))
+		{
+			return super.getIndentRight(paraIX);
+		}
+		else if (textParent != null)
+		{
+			return textParent.getIndentRight(paraIX);
+		}
+		
+		return "0";
+	}
+
+	@Override
+	public boolean hasIndentRight(String paraIX)
+	{
+		boolean has = super.hasIndentRight(paraIX);
+		
+		if ((textParent != null) && !has)
+		{
+			has = textParent.hasIndentRight(paraIX);
+		}
+		
+		return has;
+	}
+
+	@Override
+	public String getSpAfter(String paraIX)
+	{
+		if (super.hasSpAfter(paraIX))
+		{
+			return super.getSpAfter(paraIX);
+		}
+		else if (textParent != null)
+		{
+			return textParent.getSpAfter(paraIX);
+		}
+		
+		return "0";
+	}
+
+	@Override
+	public String getSpBefore(String paraIX)
+	{
+		if (super.hasSpBefore(paraIX))
+		{
+			return super.getSpBefore(paraIX);
+		}
+		else if (textParent != null)
+		{
+			return textParent.getSpBefore(paraIX);
+		}
+		
+		return "0";
+	}
+
+	@Override
+	public boolean hasSpAfter(String paraIX)
+	{
+		boolean has = super.hasSpAfter(paraIX);
+		
+		if ((textParent != null) && !has)
+		{
+			has = textParent.hasSpAfter(paraIX);
+		}
+		
+		return has;
+	}
+
+	@Override
+	public boolean hasSpBefore(String paraIX)
+	{
+		boolean has = super.hasSpBefore(paraIX);
+		
+		if ((textParent != null) && !has)
+		{
+			has = textParent.hasSpBefore(paraIX);
+		}
+		
+		return has;
+	}
+
+	@Override
+	public double getSpLine(String paraIX)
+	{
+		if (super.hasSpLine(paraIX))
+		{
+			return super.getSpLine(paraIX);
+		}
+		else if (textParent != null)
+		{
+			return textParent.getSpLine(paraIX);
+		}
+		
+		return 0;
+	}
+
+	@Override
+	public boolean hasSpLine(String paraIX)
+	{
+		boolean has = super.hasSpLine(paraIX);
+		
+		if ((textParent != null) && !has)
+		{
+			has = textParent.hasSpLine(paraIX);
+		}
+		
+		return has;
+	}
+
+	@Override
+	public String getRTLText(String paraIX)
+	{
+		if (super.hasRTLText(paraIX))
+		{
+			return super.getRTLText(paraIX);
+		}
+		else if (textParent != null)
+		{
+			return textParent.getRTLText(paraIX);
+		}
+		
+		return "ltr";
+	}
+
+	@Override
+	public boolean hasRTLText(String paraIX)
+	{
+		boolean has = super.hasRTLText(paraIX);
+		
+		if ((textParent != null) && !has)
+		{
+			has = textParent.hasRTLText(paraIX);
+		}
+		
+		return has;
+	}
+
+	@Override
+	public String getFlags(String paraIX)
+	{
+		if (super.hasFlags(paraIX))
+		{
+			return super.getFlags(paraIX);
+		}
+		else if (textParent != null)
+		{
+			return textParent.getFlags(paraIX);
+		}
+		
+		return "ltr";
+	}
+
+	@Override
+	public boolean hasFlags(String paraIX)
+	{
+		boolean has = super.hasFlags(paraIX);
+		
+		if ((textParent != null) && !has)
+		{
+			has = textParent.hasFlags(paraIX);
+		}
+		
+		return has;
+	}
+
+	@Override
+	public String getLetterSpace(String paraIX)
+	{
+		if (super.hasLetterSpace(paraIX))
+		{
+			return super.getLetterSpace(paraIX);
+		}
+		else if (textParent != null)
+		{
+			return textParent.getLetterSpace(paraIX);
+		}
+		
+		return "";
+	}
+
+	@Override
+	public boolean hasLetterSpace(String paraIX)
+	{
+		boolean has = super.hasLetterSpace(paraIX);
+		
+		if ((textParent != null) && !has)
+		{
+			has = textParent.hasLetterSpace(paraIX);
+		}
+		
+		return has;
+	}
+}

+ 45 - 0
src/com/mxgraph/io/vsdx/mxPathDebug.java

@@ -0,0 +1,45 @@
+package com.mxgraph.io.vsdx;
+
+import com.mxgraph.model.mxCell;
+import com.mxgraph.util.mxPoint;
+import com.mxgraph.view.mxGraph;
+
+public class mxPathDebug
+{
+	boolean draw = false;
+	double rectSize = 6;
+	mxGraph graph = null;
+	VsdxShape shape = null;
+	double parentHeight;
+	String rectStyle = "fillColor=#00ff00;strokeColor=#ff0000;gradientColor=none;verticalLabelPosition=top;labelPosition=center;align=center;verticalAlign=bottom;";
+	String lineStyle = "strokeColor=#0000ff;endArrow=none;";
+	
+	public mxPathDebug(boolean draw, mxGraph graph, VsdxShape shape, double parentHeight)
+	{
+		this.draw = draw;
+		this.graph = graph;
+		this.shape = shape;
+		this.parentHeight = parentHeight;
+	}
+	
+	public void drawRect(double x, double y, String label)
+	{
+		mxPoint origin = shape.getOriginPoint(parentHeight, false);
+		double x0 = origin.getX() + x - rectSize * 0.5;
+		double y0 = origin.getY() + y - rectSize * 0.5;
+		graph.insertVertex(null, null, label, x0, y0, rectSize, rectSize, rectStyle);
+	}
+
+	public void drawLine(double x0, double y0, double x1, double y1, String label)
+	{
+		mxPoint origin = shape.getOriginPoint(parentHeight, false);
+		x0 = origin.getX() + x0;
+		y0 = origin.getY() + y0;
+		x1 = origin.getX() + x1;
+		y1 = origin.getY() + y1;
+
+		mxCell edge = (mxCell) graph.insertEdge(null, null, label, null, null, lineStyle);
+		edge.getGeometry().setTerminalPoint(new mxPoint(x0, y0), true);
+		edge.getGeometry().setTerminalPoint(new mxPoint(x1, y1), false);
+	}
+}

+ 219 - 0
src/com/mxgraph/io/vsdx/mxPropertiesManager.java

@@ -0,0 +1,219 @@
+package com.mxgraph.io.vsdx;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+/**
+ * This is a singleton class that stores various global properties to document.<br/>
+ * The properties are:
+ * <ul>
+ * <li>
+ * document's colors
+ * </li>
+ * <li>
+ * document's fonts
+ * </li>
+ * <li>
+ * default text style
+ * </li>
+ * <li>
+ * default line style
+ * </li>
+ * <li>
+ * default fill style
+ * </li>
+ * </ul>
+ */
+public class mxPropertiesManager
+{
+	/**
+	 * Map with the document's colors.<br/>
+	 * The key is the index number and the value is the hex representation of the color.
+	 */
+	private HashMap<String, String> colorElementMap = new HashMap<String, String>();
+
+	/**
+	 * Map with the document's fonts.<br/>
+	 * The key is the ID and the value is the name of the font.
+	 */
+	private HashMap<String, String> fontElementMap = new HashMap<String, String>();
+
+	/**
+	 * Stylesheet with the default text style.
+	 */
+	private mxStyleSheet textStyle;
+
+	/**
+	 * Stylesheet with the default line style.
+	 */
+	private mxStyleSheet lineStyle;
+
+	/**
+	 * Stylesheet with the default fill style.
+	 */
+	private mxStyleSheet fillStyle;
+
+	/**
+	 * Best guess at default colors if 0-23 are missing in the document (seems to always be the case for vsdx)
+	 */
+	private static final Map<String, String> defaultColors = new HashMap<String, String>();
+	
+	static
+	{
+		defaultColors.put("0", "#000000");
+		defaultColors.put("1", "#FFFFFF");
+		defaultColors.put("2", "#FF0000");
+		defaultColors.put("3", "#00FF00");
+		defaultColors.put("4", "#0000FF");
+		defaultColors.put("5", "#FFFF00");
+		defaultColors.put("6", "#FF00FF");
+		defaultColors.put("7", "#00FFFF");
+		defaultColors.put("8", "#800000");
+		defaultColors.put("9", "#008000");
+		defaultColors.put("10", "#000080");
+		defaultColors.put("11", "#808000");
+		defaultColors.put("12", "#800080");
+		defaultColors.put("13", "#008080");
+		defaultColors.put("14", "#C0C0C0");
+		defaultColors.put("15", "#FFFFFF");
+		defaultColors.put("16", "#CDCDCD");
+		defaultColors.put("17", "#B3B3B3");
+		defaultColors.put("18", "#9A9A9A");
+		defaultColors.put("19", "#808080");
+		defaultColors.put("20", "#666666");
+		defaultColors.put("21", "#4D4D4D");
+		defaultColors.put("22", "#333333");
+		defaultColors.put("23", "#1A1A1A");
+	}
+	
+	/**
+	 * Loads the properties of the document.
+	 * @param doc Document with the properties.
+	 */
+	public void initialise(Element elem, mxVsdxModel model)
+	{
+		//Loads the colors
+		if (elem != null)
+		{
+			NodeList vdxColors = elem.getElementsByTagName(mxVsdxConstants.COLORS);
+	
+			if (vdxColors.getLength() > 0)
+			{
+				Element colors = (Element) vdxColors.item(0);
+				NodeList colorList = colors.getElementsByTagName(mxVsdxConstants.COLOR_ENTRY);
+				int colorLength = colorList.getLength();
+	
+				for (int i = 0; i < colorLength; i++)
+				{
+					Element color = (Element) colorList.item(i);
+					String colorId = color.getAttribute(mxVsdxConstants.INDEX);
+					String colorValue = color.getAttribute(mxVsdxConstants.RGB);
+					colorElementMap.put(colorId, colorValue);
+				}
+			}
+	
+			//Loads the fonts
+			NodeList vdxFonts = elem.getElementsByTagName(mxVsdxConstants.FACE_NAMES);
+	
+			if (vdxFonts.getLength() > 0)
+			{
+				Element fonts = (Element) vdxFonts.item(0);
+				NodeList fontList = fonts.getElementsByTagName(mxVsdxConstants.FACE_NAME);
+				int fontLength = fontList.getLength();
+	
+				for (int i = 0; i < fontLength; i++)
+				{
+					Element font = (Element) fontList.item(i);
+					String fontId = font.getAttribute(mxVsdxConstants.ID);
+					String fontValue = font.getAttribute(mxVsdxConstants.FONT_NAME);
+					fontElementMap.put(fontId, fontValue);
+				}
+			}
+	
+			//Loads the defaults documents styles.
+			NodeList vdxDocumentStyle = elem.getElementsByTagName(mxVsdxConstants.DOCUMENT_SHEET);
+			
+			if (vdxDocumentStyle.getLength() > 0)
+			{
+				Element defaultStyle = (Element) vdxDocumentStyle.item(0);
+				String lineId = defaultStyle.getAttribute(mxVsdxConstants.LINE_STYLE);
+				String fillId = defaultStyle.getAttribute(mxVsdxConstants.FILL_STYLE);
+				String textId = defaultStyle.getAttribute(mxVsdxConstants.TEXT_STYLE);
+				lineStyle = model.getStylesheet(lineId);
+				fillStyle = model.getStylesheet(fillId);
+				textStyle = model.getStylesheet(textId);
+			}
+		}
+	}
+
+	/**
+	 * Returns the color of index indicated in 'ix'.
+	 * @param ix Index of the color.
+	 * @return Hexadecimal representation of the color.
+	 */
+	public String getColor(String ix)
+	{
+		String color = colorElementMap.get(ix);
+		
+		if (color == null)
+		{
+			color = mxPropertiesManager.defaultColors.get(ix);
+			
+			if (color == null)
+			{
+				return "";
+			}
+		}
+
+		return color;
+	}
+
+	/**
+	 * Returns the font of id indicated in 'id'
+	 * @param id font's ID
+	 * @return Name of the font.
+	 */
+	public String getFont(String id)
+	{
+		String font = fontElementMap.get(id);
+		
+		if (font == null)
+		{
+			return "";
+		}
+		else
+		{
+			return font;
+		}
+	}
+
+	/**
+	 * Returns the default fill style.
+	 * @return Stylesheet with the default fill style wrapped in an instance of mxStyleSheet.
+	 */
+	public mxStyleSheet getFillStyle()
+	{
+		return fillStyle;
+	}
+
+	/**
+	 * Returns the default line style.
+	 * @return Stylesheet with the default line style wrapped in an instance of mxStyleSheet.
+	 */
+	public mxStyleSheet getLineStyle()
+	{
+		return lineStyle;
+	}
+
+	/**
+	 * Returns the default text style.
+	 * @return Stylesheet with the default text style wrapped in an instance of mxStyleSheet.
+	 */
+	public mxStyleSheet getTextStyle()
+	{
+		return textStyle;
+	}
+}

+ 193 - 0
src/com/mxgraph/io/vsdx/mxStyleSheet.java

@@ -0,0 +1,193 @@
+package com.mxgraph.io.vsdx;
+
+import org.w3c.dom.Element;
+
+/**
+ * This class is a wrapper for a stylesheet element.<br/>
+ * The Stylesheet element is treated like a shape element.<br/>
+ * If a property is not found in the shape element but it may be found in a stylesheet,
+ * the property is searched in such stylesheet.<br/>
+ * All  method that recieve a index like param ignores it. Stylesheets only have
+ * one element of each class.
+ */
+public class mxStyleSheet extends mxMasterShape
+{
+	public mxStyleSheet(Element s, mxVsdxModel model)
+	{
+		super(s, model);
+	}
+
+	@Override
+	public String getFlags(String paraIX)
+	{
+		return super.getFlags("0");
+	}
+
+	@Override
+	public int getHorizontalAlign(String paraIX)
+	{
+		return super.getHorizontalAlign("0");
+	}
+
+	@Override
+	public String getIndentFirst(String paraIX)
+	{
+		return super.getIndentFirst("0");
+	}
+
+	@Override
+	public String getIndentLeft(String paraIX)
+	{
+		return super.getIndentLeft("0");
+	}
+
+	@Override
+	public String getIndentRight(String paraIX)
+	{
+		return super.getIndentRight("0");
+	}
+
+	@Override
+	public String getLetterSpace(String paraIX)
+	{
+		return super.getLetterSpace("0");
+	}
+
+	@Override
+	public String getRTLText(String paraIX)
+	{
+		return super.getRTLText("0");
+	}
+
+	@Override
+	public String getSpAfter(String paraIX)
+	{
+		return super.getSpAfter("0");
+	}
+
+	@Override
+	public String getSpBefore(String paraIX)
+	{
+		return super.getSpBefore("0");
+	}
+
+	@Override
+	public double getSpLine(String paraIX)
+	{
+		return super.getSpLine("0");
+	}
+
+	@Override
+	public String getTextColor(String charIX)
+	{
+		return super.getTextColor("0");
+	}
+
+	@Override
+	public String getTextFont(String charIX)
+	{
+		return super.getTextFont("0");
+	}
+
+	@Override
+	public String getTextSize(String charIX)
+	{
+		return super.getTextSize("0");
+	}
+
+	@Override
+	public boolean getTextStrike(String charIX)
+	{
+		return super.getTextStrike("0");
+	}
+
+	@Override
+	public String getTextStyle(String charIX)
+	{
+		return super.getTextStyle("0");
+	}
+
+	@Override
+	public boolean hasFlags(String paraIX)
+	{
+		return super.hasFlags("0");
+	}
+
+	@Override
+	public boolean hasHorizontalAlign(String paraIX)
+	{
+		return super.hasHorizontalAlign("0");
+	}
+
+	@Override
+	public boolean hasIndentFirst(String paraIX)
+	{
+		return super.hasIndentFirst("0");
+	}
+
+	@Override
+	public boolean hasIndentLeft(String paraIX)
+	{
+		return super.hasIndentLeft("0");
+	}
+
+	@Override
+	public boolean hasIndentRight(String paraIX)
+	{
+		return super.hasIndentRight("0");
+	}
+
+	@Override
+	public boolean hasLetterSpace(String paraIX)
+	{
+		return super.hasLetterSpace("0");
+	}
+
+	@Override
+	public boolean hasSpAfter(String paraIX)
+	{
+		return super.hasSpAfter("0");
+	}
+
+	@Override
+	public boolean hasSpBefore(String paraIX)
+	{
+		return super.hasSpBefore("0");
+	}
+
+	@Override
+	public boolean hasSpLine(String paraIX)
+	{
+		return super.hasSpLine("0");
+	}
+
+	@Override
+	public boolean hasTextFont(String charIX)
+	{
+		return super.hasTextFont("0");
+	}
+
+	@Override
+	public boolean hasTextPos(String charIX)
+	{
+		return super.hasTextPos("0");
+	}
+
+	@Override
+	public boolean hasTextSize(String charIX)
+	{
+		return super.hasTextSize("0");
+	}
+
+	@Override
+	public boolean hasTextStrike(String charIX)
+	{
+		return super.hasTextStrike("0");
+	}
+
+	@Override
+	public boolean hasTextStyle(String charIX)
+	{
+		return super.hasTextStyle("0");
+	}
+}

+ 133 - 0
src/com/mxgraph/io/vsdx/mxVsdxConnect.java

@@ -0,0 +1,133 @@
+/**
+ * Copyright (c) 2015, JGraph Ltd
+ */
+package com.mxgraph.io.vsdx;
+
+import org.w3c.dom.Element;
+
+/**
+ * Wrapper for connect element
+ * See https://msdn.microsoft.com/en-us/library/office/ff768299%28v=office.14%29.aspx
+ *
+ */
+public class mxVsdxConnect
+{
+	protected Element endShape;
+	
+	/**
+	 * ID of edge
+	 */
+	protected Integer fromSheet = null;
+
+	/**
+	 * ID of source
+	 */
+	protected Integer sourceToSheet = null;
+	
+	/**
+	 * Where connection is made to source
+	 */
+	protected Integer sourceToPart = -1;
+	
+	/**
+	 * ID of target
+	 */
+	protected Integer targetToSheet = null;
+	
+	/**
+	 * Where connection is made to target
+	 */
+	protected Integer targetToPart = -1;
+	
+	protected String fromCell = null;
+
+	public mxVsdxConnect(Element connectElem)
+	{
+		String fromSheet = connectElem.getAttribute(mxVsdxConstants.FROM_SHEET);
+		this.fromSheet = (fromSheet != null && !fromSheet.isEmpty()) ? Integer.valueOf(fromSheet) : -1;
+		
+		String fromCell = connectElem.getAttribute(mxVsdxConstants.FROM_CELL);
+		addFromCell(connectElem, fromCell);
+	}
+
+	protected void addFromCell(Element connectElem, String fromCell)
+	{
+		String toSheet = connectElem.getAttribute(mxVsdxConstants.TO_SHEET);
+		boolean source = true;
+
+		if (fromCell != null && fromCell.equals(mxVsdxConstants.BEGIN_X))
+		{
+			this.sourceToSheet = (toSheet != null && !toSheet.isEmpty()) ? Integer.valueOf(toSheet) : -1;
+			source = true;
+		}
+		else if (fromCell != null && fromCell.equals(mxVsdxConstants.END_X))
+		{
+			this.targetToSheet = (toSheet != null && !toSheet.isEmpty()) ? Integer.valueOf(toSheet) : -1;
+			source = false;
+		}
+		else if (this.sourceToSheet == null)
+		{
+			this.sourceToSheet = (toSheet != null && !toSheet.isEmpty()) ? Integer.valueOf(toSheet) : -1;
+			source = true;
+		}
+		else if (this.targetToSheet == null)
+		{
+			this.targetToSheet = (toSheet != null && !toSheet.isEmpty()) ? Integer.valueOf(toSheet) : -1;
+			source = false;
+		}
+		
+		findToPart(connectElem, source);
+	}
+	
+	protected void findToPart(Element connectElem, boolean source)
+	{
+		String toPartString = connectElem.getAttribute(mxVsdxConstants.TO_PART);
+		Integer toPart = (toPartString != null && !toPartString.isEmpty()) ? Integer.valueOf(toPartString) : -1;
+		
+		if (source)
+		{
+			sourceToPart = toPart;
+		}
+		else
+		{
+			targetToPart = toPart;
+		}
+	}
+
+	public Integer getFromSheet()
+	{
+		return this.fromSheet;
+	}
+	
+	public Integer getSourceToSheet()
+	{
+		return this.sourceToSheet;
+	}
+	
+	public Integer getTargetToSheet()
+	{
+		return this.targetToSheet;
+	}
+	
+	public Integer getSourceToPart()
+	{
+		return this.sourceToPart;
+	}
+	
+	public Integer getTargetToPart()
+	{
+		return this.targetToPart;
+	}
+
+	/**
+	 * 
+	 * @param connectElem
+	 */
+	public void addConnect(Element connectElem)
+	{
+		this.endShape = connectElem;
+		String fromCell = connectElem.getAttribute(mxVsdxConstants.FROM_CELL);
+		addFromCell(connectElem, fromCell);
+	}
+
+}

+ 143 - 0
src/com/mxgraph/io/vsdx/mxVsdxConstants.java

@@ -0,0 +1,143 @@
+package com.mxgraph.io.vsdx;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This class contains constants used in the Import of .vdx documents.
+ */
+public class mxVsdxConstants
+{
+	public static String ANGLE = "Angle";
+	public static String ARC_TO = "ArcTo";
+	public static String BACKGROUND = "Background";
+	public static String BACK_PAGE = "BackPage";
+	public static String BEGIN_ARROW = "BeginArrow";
+	public static String BEGIN_ARROW_SIZE = "BeginArrowSize";
+	public static String BEGIN_X = "BeginX";
+	public static String BEGIN_Y = "BeginY";
+	public static String BOTTOM_MARGIN = "BottomMargin";
+	public static String BULLET = "Bullet";
+	public static String CASE = "Case";
+	public static String CHAR = "Char";
+	public static String COLOR = "Color";
+	public static String COLOR_ENTRY = "ColorEntry";
+	public static String COLORS = "Colors";
+	public static String CONNECT = "Connect";
+	public static String CONNECTS = "Connects";
+	public static String CONNECTION = "Connection";
+	public static String CONTROL = "Control";
+	public static String DELETED = "Del";
+	public static String DOCUMENT_SHEET = "DocumentSheet";
+	public static String ELLIPSE = "Ellipse";
+	public static String ELLIPTICAL_ARC_TO = "EllipticalArcTo";
+	public static String END_ARROW = "EndArrow";
+	public static String END_ARROW_SIZE = "EndArrowSize";
+	public static String END_X = "EndX";
+	public static String END_Y = "EndY";
+	public static String FACE_NAME = "FaceName";
+	public static String FACE_NAMES = "FaceNames";
+	public static String FALSE = "0";
+	public static String FILL = "Fill";
+	public static String FILL_BKGND = "FillBkgnd";
+	public static String FILL_BKGND_TRANS = "FillBkgndTrans";
+	public static String FILL_FOREGND = "FillForegnd";
+	public static String FILL_FOREGND_TRANS = "FillForegndTrans";
+	public static String FILL_PATTERN = "FillPattern";
+	public static String FILL_STYLE = "FillStyle";
+	public static String FLAGS = "Flags";
+	public static String FLIP_X = "FlipX";
+	public static String FLIP_Y = "FlipY";
+	public static String FONT = "Font";
+	public static String FONT_NAME = "Name";
+	public static String FOREIGN = "Foreign";
+	public static String FROM_CELL = "FromCell";
+	public static String FROM_SHEET = "FromSheet";
+	public static String GEOM = "Geom";
+	public static String HEIGHT = "Height";
+	public static String HORIZONTAL_ALIGN = "HorzAlign";
+	public static String ID = "ID";
+	public static String INDENT_FIRST = "IndFirst";
+	public static String INDENT_LEFT = "IndLeft";
+	public static String INDENT_RIGHT = "IndRight";
+	public static String INDEX = "IX";
+	public static String LEFT_MARGIN = "LeftMargin";
+	public static String LETTER_SPACE = "Letterspace";
+	public static String LINE = "Line";
+	public static String LINE_COLOR = "LineColor";
+	public static String LINE_COLOR_TRANS = "LineColorTrans";
+	public static String LINE_PATTERN = "LinePattern";
+	public static String LINE_STYLE = "LineStyle";
+	public static String LINE_TO = "LineTo";
+	public static String LINE_WEIGHT = "LineWeight";
+	public static String LOC_PIN_X = "LocPinX";
+	public static String LOC_PIN_Y = "LocPinY";
+	public static String MASTER = "Master";
+	public static String MASTER_SHAPE = "MasterShape";
+	public static String MASTERS = "Masters";
+	public static String MOVE_TO = "MoveTo";
+	public static String NAME = "Name";
+	public static String NAME_U = "NameU";
+	public static String NO_LINE = "NoLine";
+	public static String NURBS_TO = "NURBSTo";
+	public static String PAGE = "Page";
+	public static String PAGE_HEIGHT = "PageHeight";
+	public static String PAGE_WIDTH = "PageWidth";
+	public static String PAGES = "Pages";
+	public static String PARAGRAPH = "Para";
+	public static String PIN_X = "PinX";
+	public static String PIN_Y = "PinY";
+	public static String POS = "Pos";
+	public static String RGB = "RGB";
+	public static String RIGHT_MARGIN = "RightMargin";
+	public static String ROUNDING = "Rounding";
+	public static String RTL_TEXT = "RTLText";
+	public static String SIZE = "Size";
+	public static String SHAPE = "Shape";
+	public static String SHAPES = "Shapes";
+	public static String SHAPE_SHDW_SHOW = "ShapeShdwShow";
+	public static String SHDW_PATTERN = "ShdwPattern";
+	public static String SPACE_AFTER = "SpAfter";
+	public static String SPACE_BEFORE = "SpBefore";
+	public static String SPACE_LINE = "SpLine";
+	public static String STRIKETHRU = "Strikethru";
+	public static String STYLE = "Style";
+	public static String STYLE_SHEET = "StyleSheet";
+	public static String STYLE_SHEETS = "StyleSheets";
+	public static String TEXT = "Text";
+	public static String TEXT_BKGND = "TextBkgnd";
+	public static String TEXT_BLOCK = "TextBlock";
+	public static String TEXT_STYLE = "TextStyle";
+	public static String TEXT_X_FORM = "TextXForm";
+	public static String TO_PART = "ToPart";
+	public static String TO_SHEET = "ToSheet";
+	public static String TOP_MARGIN = "TopMargin";
+	public static String TRUE = "1";
+	public static String TXT_ANGLE = "TxtAngle";
+	public static String TXT_HEIGHT = "TxtHeight";
+	public static String TXT_LOC_PIN_X = "TxtLocPinX";
+	public static String TXT_LOC_PIN_Y = "TxtLocPinY";
+	public static String TXT_PIN_X = "TxtPinX";
+	public static String TXT_PIN_Y = "TxtPinY";
+	public static String TXT_WIDTH = "TxtWidth";
+	public static String TYPE = "Type";
+	public static String TYPE_GROUP = "Group";
+	public static String TYPE_SHAPE = "Shape";
+	public static String UNIQUE_ID = "UniqueID";
+	public static String VERTICAL_ALIGN = "VerticalAlign";
+	public static String WIDTH = "Width";
+	public static String X_CON = "XCon";
+	public static String X_DYN = "XDyn";
+	public static String X_FORM = "XForm";
+	public static String X_FORM_1D = "XForm1D";
+	public static String X = "X";
+	public static String Y_CON = "YCon";
+	public static String Y_DYN = "YDyn";
+	public static String Y = "Y";
+	
+	public static int CONNECT_TO_PART_WHOLE_SHAPE = 3;
+	
+	public static final String[] SET_VALUES = new String[] { "a", "b" };
+	public static final Set<String> MY_SET = new HashSet<String>(Arrays.asList(SET_VALUES));
+}

+ 194 - 0
src/com/mxgraph/io/vsdx/mxVsdxMaster.java

@@ -0,0 +1,194 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.mxgraph.io.vsdx;
+
+import java.util.HashMap;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import com.mxgraph.io.mxVsdxCodec;
+
+/**
+ * This class is a wrapper for a Master element.<br/>
+ * Contains a map with the shapes contained in the Master element
+ * and allows access these by ID.
+ */
+public class mxVsdxMaster
+{
+	protected Element master;
+	
+	/**
+	 * Unique ID of the element within its parent element
+	 */
+	protected String Id = null;
+	
+	protected mxMasterShape masterShape = null;
+	
+	/*
+	 * Map that contains the shapes in Master element wrapped for instances of mxDelegateShape.
+	 * The key is the shape's ID.
+	 */
+	protected HashMap<String, mxMasterShape> childShapes = new HashMap<String, mxMasterShape>();
+
+	/**
+	 * Create a new instance of mxMasterElement and retrieves all the shapes contained
+	 * in the Master element.
+	 * @param m Master Element to be wrapped.
+	 */
+	public mxVsdxMaster(Element m, mxVsdxModel model)
+	{
+		this.master = m;
+		this.Id = m.getAttribute(mxVsdxConstants.ID);
+		processMasterShapes(model);
+	}
+
+	/**
+	 * Retrieves and wraps all the shapes contained in the 'shape' param.<br/>
+	 * This method is recursive, it retrieves the subshapes of the shapes too.
+	 * @param shape Shape from which the subshapes are retrieved.
+	 * @return Map with the shapes wrapped in instances of mxMasterShape.
+	 */
+	protected void processMasterShapes(mxVsdxModel model)
+	{
+		Node child = this.master.getFirstChild();
+		
+		while (child != null)
+		{
+			if (child instanceof Element && ((Element)child).getNodeName().equals("Rel"))
+			{
+				Element relElem = model.getRelationship(((Element) child).getAttribute("r:id"), mxVsdxCodec.vsdxPlaceholder + "/masters/" + "_rels/masters.xml.rels");
+				String target = relElem.getAttribute("Target");
+				String type = relElem.getAttribute("Type");
+				Document masterDoc = null;
+				
+				if (type != null && type.endsWith("master"))
+				{
+					masterDoc = model.getXmlDoc(mxVsdxCodec.vsdxPlaceholder + "/masters/" + target);
+				}
+
+				if (masterDoc != null)
+				{
+					Node masterChild = masterDoc.getFirstChild();
+					
+					while (masterChild != null)
+					{
+						if (masterChild instanceof Element && ((Element)masterChild).getNodeName().equals("MasterContents"))
+						{
+							processMasterShape((Element)masterChild, model);
+							break;
+						}
+						
+						masterChild = masterChild.getNextSibling();
+					}
+				}
+			}
+			
+			child = child.getNextSibling();
+		}
+	}
+
+	/**
+	 * Retrieves and wraps all the shapes contained in the 'shape' param.<br/>
+	 * This method is recursive, it retrieves the subshapes of the shapes too.
+	 * @param shape Shape from which the subshapes are retrieved.
+	 * @return Map with the shapes wrapped in instances of mxMasterShape.
+	 */
+	protected void processMasterShape(Element shapeElem, mxVsdxModel model)
+	{
+		Node shapeChild = shapeElem.getFirstChild();
+		
+		while (shapeChild != null)
+		{
+			if (shapeChild instanceof Element && ((Element)shapeChild).getNodeName().equals("Shapes"))
+			{
+				Node shapesChild = shapeChild.getFirstChild();
+
+				while (shapesChild != null)
+				{
+					if (shapesChild instanceof Element && ((Element)shapesChild).getNodeName().equals("Shape"))
+					{
+						Element shape = (Element)shapesChild;
+						String shapeId = shape.getAttribute("ID");
+						mxMasterShape masterShape = new mxMasterShape(shape, model);
+						this.masterShape = (this.masterShape == null) ? masterShape : this.masterShape;
+						childShapes.put(shapeId, masterShape);
+						processMasterShape(shape, model);
+					}
+					
+					shapesChild = shapesChild.getNextSibling();
+				}
+				
+				break;
+			}
+			
+			shapeChild = shapeChild.getNextSibling();
+		}
+	}
+
+	/**
+	 * Returns the first shape in the Master
+	 * @return First shape in the Master wrapped in a instance of mxMasterShape
+	 */
+	public mxMasterShape getMasterShape()
+	{
+		return this.masterShape;
+	}
+
+	/**
+	 * Returns the shape in the master element with ID = 'id'.
+	 * @param id Shape's ID
+	 * @return The shape in the master element with ID = 'id' wrapped in a instance of mxMasterShape
+	 */
+	public mxMasterShape getSubShape(String id)
+	{
+		return childShapes.get(id);
+	}
+
+	/**
+	 * Returns the NameU attribute.
+	 * @return Value of the NameU attribute.
+	 */
+	public String getNameU()
+	{
+		return master.getAttribute("NameU");
+	}
+
+	/**
+	 * Returns the NameU attribute.
+	 * @return Value of the NameU attribute.
+	 */
+	public String getName()
+	{
+		return master.getAttribute("Name");
+	}
+
+	/**
+	 * Returns the UniqueID attribute.
+	 * @return Value of the UniqueID attribute.
+	 */
+	public String getUniqueID()
+	{
+		String uniqueID = "";
+		
+		if (master.hasAttribute("UniqueID"))
+		{
+			uniqueID = master.getAttribute("UniqueID");
+		}
+		
+		return uniqueID;
+	}
+
+	public String getId()
+	{
+		return this.Id;
+	}
+	
+	public Element getMasterElement()
+	{
+		return master;
+	}
+}

+ 314 - 0
src/com/mxgraph/io/vsdx/mxVsdxModel.java

@@ -0,0 +1,314 @@
+/**
+ * Copyright (c) 2006-2016, JGraph Ltd
+ * Copyright (c) 2006-2016, Gaudenz Alder
+ */
+package com.mxgraph.io.vsdx;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import com.mxgraph.io.mxVsdxCodec;
+
+/**
+ * 
+ * A model representing vsdx files. As well as being a programmatic model, the XML DOMs of the unzipped
+ * files are held to enable round-tripping
+ *
+ */
+public class mxVsdxModel {
+
+	/**
+	 * A map of Documents created by reading the XML files, indexed by the path to those files
+	 */
+	protected Map<String, Document> xmlDocs = null;
+	
+	/**
+	 * Collection of media files encoded in Base64, indexed by the path to those files
+	 */
+	protected Map<String, String> media = null;
+
+	/**
+	 * The document from .../document.xml
+	 */
+	protected Element rootElement;
+	
+	/**
+	 * Map of page objects indexed by their ID.
+	 */
+	protected Map<Integer, mxVsdxPage> pages = null;
+
+	/**
+	 * Map of master objects indexed by their ID. Before you think you're being clever by making
+	 * the index an Integer as for pages, don't, there are reasons.
+	 */
+	protected Map<String, mxVsdxMaster> masters = new HashMap<String, mxVsdxMaster>();
+	
+	/**
+	 * Map stylesheets indexed by their ID
+	 */
+	protected Map<String, mxStyleSheet> stylesheets = new HashMap<String, mxStyleSheet>();
+	
+	mxPropertiesManager pm;
+
+	public mxVsdxModel(Document doc, Map<String, Document> docData, Map<String, String> mediaData)
+	{
+		this.xmlDocs = docData;
+		this.media = mediaData;
+	
+		Node childNode = doc.getFirstChild();
+		
+		while (childNode != null)
+		{
+			if (childNode instanceof Element && ((Element)childNode).getTagName().toLowerCase().equals(mxVsdxCodec.vsdxPlaceholder + "document"))
+			{
+				this.rootElement = (Element)childNode;
+				break;
+			}
+			
+			childNode = childNode.getNextSibling();
+		}
+	
+		this.pm = new mxPropertiesManager();
+		this.pm.initialise(rootElement, this);
+		initStylesheets();
+		initMasters();
+		initPages();
+	}
+
+	/**
+	 * Load the map with the stylesheets elements in the document.<br/>
+	 * The masters are wrapped for instances of mxStyleSheet.
+	 * @param doc Document with the stylesheets.
+	 */
+	public void initStylesheets()
+	{
+		NodeList vdxSheets = rootElement.getElementsByTagName(mxVsdxConstants.STYLE_SHEETS);
+
+		if (vdxSheets.getLength() > 0)
+		{
+			Element sheets = (Element) vdxSheets.item(0);
+			NodeList sheetList = sheets.getElementsByTagName(mxVsdxConstants.STYLE_SHEET);
+			int sheetLength = sheetList.getLength();
+
+			for (int i = 0; i < sheetLength; i++)
+			{
+				Element sheet = (Element) sheetList.item(i);
+				String sheetId = sheet.getAttribute(mxVsdxConstants.ID);
+				mxStyleSheet sheetElement = new mxStyleSheet(sheet, this);
+				stylesheets.put(sheetId, sheetElement);
+			}
+		}
+		
+		Collection <mxStyleSheet> sheets = stylesheets.values();
+		Iterator<mxStyleSheet> iter = sheets.iterator();
+		
+		while (iter.hasNext())
+		{
+			mxStyleSheet sheet = iter.next();
+			sheet.stylesheetRefs(this);
+		}
+	}
+
+	/**
+	 * Initialize master objects from the XML files
+	 */
+	public void initMasters()
+	{
+		// Lazy build up the master structure
+		if (this.xmlDocs != null)
+		{
+			String path = mxVsdxCodec.vsdxPlaceholder + "/masters/masters.xml";
+			Document masterDoc = this.xmlDocs.get(path);
+
+			if (masterDoc != null)
+			{
+				Node child = masterDoc.getFirstChild();
+				
+				while (child != null)
+				{
+					if (child instanceof Element && ((Element)child).getTagName().equals(mxVsdxConstants.MASTERS))
+					{
+						Node grandChild = child.getFirstChild();
+						
+						while (grandChild != null)
+						{
+							if (grandChild instanceof Element && ((Element)grandChild).getTagName().equals("Master"))
+							{
+								Element masterElement = (Element)grandChild;
+								mxVsdxMaster master = new mxVsdxMaster(masterElement, this);
+								this.masters.put(master.getId(), master);
+							}
+							
+							grandChild = grandChild.getNextSibling();
+						}
+						
+						break;
+
+					}
+					
+					child = child.getNextSibling();
+				}
+			}
+		}
+	}
+	
+	/**
+	 * Initialize page objects from the XML files
+	 */
+	public void initPages()
+	{
+		// Lazy build up the pages structure
+		if (this.xmlDocs != null)
+		{
+			String path = mxVsdxCodec.vsdxPlaceholder + "/pages/pages.xml";
+			Document pageDoc = this.xmlDocs.get(path);
+
+			if (pageDoc != null)
+			{
+				Node child = pageDoc.getFirstChild();
+				
+				while (child != null)
+				{
+					if (child instanceof Element && ((Element)child).getTagName().equals(mxVsdxConstants.PAGES))
+					{
+						Element pages = (Element)child;
+						
+						NodeList pageList = pages.getElementsByTagName(mxVsdxConstants.PAGE);
+						
+						if (pageList != null && pageList.getLength() > 0)
+						{
+							this.pages = new LinkedHashMap<Integer, mxVsdxPage>();
+							
+							HashMap<Integer, mxVsdxPage> backgroundMap = new HashMap<Integer, mxVsdxPage>();
+							int pageListLen = pageList.getLength();
+							
+							//Find the background pages while creating all the pages
+							for (int i = 0; i < pageListLen; i++)
+							{
+								Element pageEle = (Element) pageList.item(i);
+								mxVsdxPage page = createPage(pageEle);
+								
+								if (page.isBackground())
+								{
+									backgroundMap.put(page.getId(), page);
+								}
+								
+								this.pages.put(page.getId(), page);
+							}
+		
+							Collection<mxVsdxPage> pagesCollection = this.pages.values();
+							Iterator<mxVsdxPage> iter = pagesCollection.iterator();
+		
+							// Iterate again, assigning background pages
+							while (iter.hasNext())
+							{
+								mxVsdxPage page = iter.next();
+		
+								if (!page.isBackground())
+								{
+									Integer backId = page.getBackPageId();
+		
+									if (backId != null)
+									{
+										//Import the background.
+										mxVsdxPage background = backgroundMap.get(backId);
+										page.setBackPage(background);;
+									}
+								}
+							}
+						}
+
+						break; // MS defines there can only be 0 or 1 PAGES element, don't process second
+					}
+					
+					child = child.getNextSibling();
+				}
+			}
+		}
+	}
+	
+	public Map<Integer, mxVsdxPage> getPages()
+	{
+		return this.pages;
+	}
+
+	protected Element getRelationship(String rid, String path)
+	{
+		Document relsDoc = this.xmlDocs.get(path);
+
+		if (relsDoc == null || rid == null || rid.isEmpty())
+		{
+			// Valid to not have a rels for an XML file
+			return null;
+		}
+		
+		NodeList rels = relsDoc.getElementsByTagName("Relationship");
+
+		for (int i = 0; i < rels.getLength(); i++)
+		{
+			Element currElem = (Element) rels.item(i);
+			String id = currElem.getAttribute("Id");
+
+			if (id.equals(rid))
+			{
+				return currElem;
+			}
+		}
+
+		return null;
+	}
+
+	public mxVsdxMaster getMaster(String masterId)
+	{
+		return this.masters.get(masterId);
+	}
+
+	protected mxVsdxPage createPage(Element pageEle)
+	{
+		return new mxVsdxPage(pageEle, this);
+	}
+	
+	public mxPropertiesManager getPropertiesManager() {
+		return pm;
+	}
+
+	public void setPropertiesManager(mxPropertiesManager pm) {
+		this.pm = pm;
+	}
+
+	public Map<String, mxVsdxMaster> getMasterShapes() {
+		return masters;
+	}
+
+	public void setMasterShapes(Map<String, mxVsdxMaster> mm) {
+		this.masters = mm;
+	}
+	
+	/**
+	 * Returns the wrapper of the stylesheet element with id indicated by 'id'
+	 * @param id StyleSheet's ID.
+	 * @return StyleSheet element with id = 'id' wrapped in an instance of mxStyleSheet.
+	 */
+	public mxStyleSheet getStylesheet(String id)
+	{
+		return stylesheets.get(id);
+	}
+	
+	public Document getXmlDoc(String path)
+	{
+		return this.xmlDocs.get(path);
+	}
+	
+	public String getMedia(String path)
+	{
+		return this.media.get(path);
+	}
+}

+ 346 - 0
src/com/mxgraph/io/vsdx/mxVsdxPage.java

@@ -0,0 +1,346 @@
+package com.mxgraph.io.vsdx;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import com.mxgraph.io.mxVsdxCodec;
+import com.mxgraph.util.mxPoint;
+
+public class mxVsdxPage {
+
+	/**
+	 * Unique ID of the element within its parent element
+	 */
+	protected Integer Id = null;
+	
+	protected boolean isBackground = false;
+	
+	protected Integer backPageId = null;
+	
+	protected mxVsdxPage backPage = null;
+	
+	protected Element pageElement = null;
+	
+	protected Element pageSheet = null;
+	
+	protected mxVsdxModel model = null;
+	
+	protected Map<Integer, VsdxShape> shapes = new LinkedHashMap<Integer, VsdxShape>();
+
+	protected Map<Integer, mxVsdxConnect> connects = new LinkedHashMap<Integer, mxVsdxConnect>();
+
+	// cell in the PageSheet
+	protected Map<String, Element> cellElements = new HashMap<String, Element>();
+
+	public mxVsdxPage(Element pageElem, mxVsdxModel model) {
+		this.model = model;
+		this.pageElement = pageElem;
+
+		String backGround = pageElem.getAttribute(mxVsdxConstants.BACKGROUND);
+		this.isBackground = (backGround != null && backGround.equals(mxVsdxConstants.TRUE)) ? true : false;
+		String back = pageElem.getAttribute(mxVsdxConstants.BACK_PAGE);
+		
+		if (!isBackground && back != null && back.length() > 0)
+		{
+			this.backPageId = Integer.valueOf(back);
+		}
+
+		this.Id = Integer.valueOf(pageElem.getAttribute(mxVsdxConstants.ID));
+		
+		parseNodes(pageElem, model, "pages");
+		
+		Node child = pageElem.getFirstChild();
+		
+		while (child != null)
+		{
+			if (child instanceof Element && ((Element)child).getTagName().equals("PageSheet"))
+			{
+				Node childNode = child.getFirstChild();
+				
+				while (childNode != null)
+				{
+					if (childNode instanceof Element && ((Element)childNode).getTagName().equals("Cell"))
+					{
+						Element childElem = (Element) childNode;
+						String n = childElem.getAttribute("N");
+						cellElements.put(n, childElem);
+					}
+					
+					childNode = childNode.getNextSibling();
+				}
+				
+				break;
+			}
+			
+			child = child.getNextSibling();
+		}
+	}
+
+	/**
+	 * Parses the child nodes of the given element
+	 * @param pageElem the parent whose children to parse
+	 * @param model the model of the vsdx file
+	 * @param pageName page information is split across pages.xml and pageX.xml where X is any number. We have to know which we're currently parsing to use the correct relationships file.
+	 */
+	protected void parseNodes(Node pageElem, mxVsdxModel model, String pageName)
+	{
+		Node pageChild = pageElem.getFirstChild();
+		
+		while (pageChild != null)
+		{
+			if (pageChild instanceof Element)
+			{
+				Element pageChildElem = (Element) pageChild;
+				String childName = pageChildElem.getNodeName();
+				
+				if (childName.equals("Rel"))
+				{
+					resolveRel(pageChildElem, model, pageName);
+				}
+				else if (childName.equals("Shapes"))
+				{
+					this.shapes = parseShapes(pageChildElem, null, false);
+				}
+				else if (childName.equals("Connects"))
+				{
+					NodeList connectList = pageChildElem.getElementsByTagName(mxVsdxConstants.CONNECT);
+					Node connectNode = (connectList != null && connectList.getLength() > 0) ? connectList.item(0) : null;
+					//mxVdxConnect currentConnect = null;
+		
+					while (connectNode != null)
+					{
+						if (connectNode instanceof Element)
+						{
+							Element connectElem = (Element) connectNode;
+							mxVsdxConnect connect = new mxVsdxConnect(connectElem);
+							Integer fromSheet = connect.getFromSheet();
+							mxVsdxConnect previousConnect = (fromSheet != null && fromSheet > -1) ? connects.get(fromSheet) : null;
+							
+							if (previousConnect != null)
+							{
+								previousConnect.addConnect(connectElem);
+							}
+							else
+							{
+								connects.put(connect.getFromSheet(), connect);
+							}
+						}
+						
+						connectNode = connectNode.getNextSibling();	
+					}
+				}
+				else if (childName.equals("PageSheet"))
+				{
+					this.pageSheet = pageChildElem;
+				}
+			}
+			
+			pageChild = pageChild.getNextSibling();
+		}
+	}
+
+	/**
+	 * 
+	 * @param relNode
+	 * @param model
+	 * @param pageName
+	 */
+	protected void resolveRel(Element relNode, mxVsdxModel model, String pageName)
+	{
+		Element relElem = model.getRelationship(relNode.getAttribute("r:id"), mxVsdxCodec.vsdxPlaceholder + "/pages/" + "_rels/" + pageName + ".xml.rels");
+		
+		String target = relElem.getAttribute("Target");
+		String type = relElem.getAttribute("Type");
+		
+		if (String.valueOf(type).endsWith("page"))
+		{
+			Document pageDoc = null;
+			
+			if (type != null && type.endsWith("page"))
+			{
+				pageDoc = model.getXmlDoc(mxVsdxCodec.vsdxPlaceholder + "/pages/" + target);
+			}
+			
+			if (pageDoc != null)
+			{
+				Node child = pageDoc.getFirstChild();
+				
+				while (child != null)
+				{
+					if (child instanceof Element && ((Element)child).getTagName().equals("PageContents"))
+					{
+						int index = target.indexOf('.');
+						
+						if (index != -1)
+						{
+							parseNodes(child, model, target.substring(0, index));
+						}
+
+						break;
+					}
+					
+					child = child.getNextSibling();
+				}
+			}
+		}
+	}
+	
+	public Map<Integer, VsdxShape> parseShapes(Element shapesElement, mxVsdxMaster master, boolean recurse)
+	{
+		Map<Integer, VsdxShape> shapes = new LinkedHashMap<Integer, VsdxShape>();
+		NodeList shapeList = shapesElement.getElementsByTagName(mxVsdxConstants.SHAPE);
+
+		Node shapeNode = (shapeList != null && shapeList.getLength() > 0) ? shapeList.item(0) : null;
+
+		while (shapeNode != null)
+		{
+			if (shapeNode instanceof Element)
+			{
+				Element shapeElem = (Element) shapeNode;
+				mxVsdxMaster masterTmp = master;
+
+				// Work out node type
+				if (masterTmp == null)
+				{
+					//If the shape has the Master attribute the master shape is the first
+					//shape of the master element.
+					String masterId = shapeElem.getAttribute(mxVsdxConstants.MASTER);
+					
+					if (masterId != null && !masterId.equals(""))
+					{
+						masterTmp = model.getMaster(masterId);
+					}
+					else
+					{
+						masterId = shapeElem.getAttribute(mxVsdxConstants.MASTER_SHAPE);
+	
+						if (masterId != null && !masterId.equals(""))
+						{
+							masterTmp = model.getMaster(masterId);
+						}					
+					}
+				}
+				
+				boolean isEdge = isEdge(shapeElem);
+				//String type = mxVdxShape.getType(shapeElem);
+				
+				// If the master of the shape has an xform1D, it's an edge
+				if (!isEdge && masterTmp != null)
+				{
+					isEdge = isEdge(masterTmp.getMasterElement());
+				}
+				
+				VsdxShape shape = this.createCell(shapeElem, !isEdge, masterTmp);
+				
+				shapes.put(shape.getId(), shape);
+			}
+
+			shapeNode = shapeNode.getNextSibling();
+		}
+		
+		return shapes;
+	}
+
+	protected VsdxShape createCell(Element shapeElem, boolean vertex, mxVsdxMaster masterTmp)
+	{
+		return new VsdxShape(this, shapeElem, vertex, this.model.getMasterShapes(), masterTmp, this.model);
+	}
+
+	protected boolean isEdge(Element shape)
+	{
+		if (shape != null)
+		{
+			NodeList children = shape.getChildNodes();
+			
+			if (children != null)
+			{
+				Node childNode = children.item(0);
+				
+				while (childNode != null)
+				{
+					if (childNode instanceof Element)
+					{
+						Element childElem = (Element) childNode;
+
+						if (childElem.getNodeName().equals("Cell"))
+						{
+							String n = childElem.getAttribute("N");
+							
+							if (n.equals("BeginX") || n.equals("BeginY") || n.equals("EndY") || n.equals("EndX"))
+							{
+								return true;
+							}
+						}
+					}
+					
+					childNode = childNode.getNextSibling();
+				}
+			}
+		}
+
+		return false;
+	}
+
+	/**
+	 * Returns the width and height of a Page expressed as an mxPoint.
+	 * @return mxPoint that represents the dimensions of the shape
+	 */
+	public mxPoint getPageDimensions()
+	{
+		double pageH = 0;
+		double pageW = 0;
+
+		Element height = cellElements.get("PageHeight");
+		Element width = cellElements.get("PageWidth");
+		
+		if (height != null)
+		{
+			pageH = Double.valueOf(height.getAttribute("V")) * mxVsdxUtils.conversionFactor;
+			pageH = Math.round(pageH * 100.0) / 100.0;
+		}
+		
+		if (width != null)
+		{
+			pageW = Double.valueOf(width.getAttribute("V")) * mxVsdxUtils.conversionFactor;
+			pageW = Math.round(pageW * 100.0) / 100.0;
+		}
+
+		return new mxPoint(pageW, pageH);
+	}
+	
+	public Integer getId()
+	{
+		return this.Id;
+	}
+	
+	public Map<Integer, VsdxShape> getShapes()
+	{
+		return this.shapes;
+	}
+	
+	public Map<Integer, mxVsdxConnect> getConnects()
+	{
+		return this.connects;
+	}
+	
+	public boolean isBackground()
+	{
+		return this.isBackground;
+	}
+	
+	public Integer getBackPageId()
+	{
+		return this.backPageId;
+	}
+	
+	public void setBackPage(mxVsdxPage page)
+	{
+		this.backPage = page;
+	}
+}

File diff ditekan karena terlalu besar
+ 1093 - 0
src/com/mxgraph/io/vsdx/mxVsdxTextParser.java


+ 326 - 0
src/com/mxgraph/io/vsdx/mxVsdxUtils.java

@@ -0,0 +1,326 @@
+package com.mxgraph.io.vsdx;
+
+import com.mxgraph.util.mxConstants;
+import com.mxgraph.util.mxPoint;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * General utilities for .vdx format support
+ */
+public class mxVsdxUtils
+{
+	private static double screenCoordinatesPerCm = 40;
+
+	private static final double CENTIMETERS_PER_INCHES = 2.54;
+	
+	public static final double conversionFactor = screenCoordinatesPerCm * CENTIMETERS_PER_INCHES;
+
+	/**
+	 * Checks if the NodeList has a Node with name = tag.
+	 * @param nl NodeList
+	 * @param tag Name of the node.
+	 * @return Returns <code>true</code> if the Node List has a Node with name = tag.
+	 */
+	public static boolean nodeListHasTag(NodeList nl, String tag)
+	{
+		boolean has = false;
+
+		if (nl != null)
+		{
+			int length = nl.getLength();
+
+			for (int i = 0; (i < length) && !has; i++)
+			{
+				has = (nl.item(i)).getNodeName().equals(tag);
+			}
+		}
+
+		return has;
+	}
+
+	/**
+	 * Returns the first Element that has name = tag in Node List.
+	 * @param nl NodeList
+	 * @param tag Name of the Element
+	 * @return Element with name = 'tag'.
+	 */
+	public static Element nodeListTag(NodeList nl, String tag)
+	{
+		if (nl != null)
+		{
+			int length = nl.getLength();
+			boolean has = false;
+
+			for (int i = 0; (i < length) && !has; i++)
+			{
+				has = (nl.item(i)).getNodeName().equals(tag);
+
+				if (has)
+				{
+					return (Element) nl.item(i);
+				}
+			}
+		}
+
+		return null;
+	}
+
+	/**
+	 * Returns the  Element that has name = tag and Index = ix in Node List.
+	 * @param nl NodeList
+	 * @param tag name of the Element
+	 * @return Element that has name = tag and Index = ix in Node List..
+	 */
+	public static Element nodeListTagIndexed(NodeList nl, String tag, String ix)
+	{
+		if (nl != null)
+		{
+			int length = nl.getLength();
+			boolean has = false;
+
+			for (int i = 0; (i < length) && !has; i++)
+			{
+				has = (nl.item(i)).getNodeName().equals(tag) && ((Element) (nl.item(i))).getAttribute("IX").equals(ix);
+
+				if (has)
+				{
+					return (Element) nl.item(i);
+				}
+			}
+		}
+
+		return null;
+	}
+
+	/**
+	 * Returns a list with the elements included in the Node List that have name = tag.
+	 * @param nl NodeList
+	 * @param tag name of the Element.
+	 * @return List with the indicated elements.
+	 */
+	public static List<Element> nodeListTags(NodeList nl, String tag)
+	{
+		ArrayList<Element> ret = new ArrayList<Element>();
+
+		if (nl != null)
+		{
+			int length = nl.getLength();
+
+			for (int i = 0; i < length; i++)
+			{
+				if (tag.equals((nl.item(i)).getNodeName()))
+				{
+					ret.add((Element) nl.item(i));
+				}
+			}
+		}
+
+		return ret;
+	}
+
+	/**
+	 * Map the child of parent using node name as key
+	 * @param parent parent whose children will be mapped
+	 * @return Map of (NodeName, child)
+	 */
+	public static List<Node> copyNodeList(NodeList nodeList)
+	{
+		ArrayList<Node> copy = new ArrayList<Node>();
+		int length = nodeList.getLength();
+
+		for (int i = 0; i < length; i++)
+		{
+			copy.add((Node) nodeList.item(i));
+		}
+
+		return copy;
+	}
+
+	/**
+	 * Returns the string that represents the content of a given style map.
+	 * @param styleMap Map with the styles values
+	 * @return string that represents the style.
+	 */
+	public static String getStyleString(Map<String, String> styleMap, String asig)
+	{
+		String style = "";
+		Iterator<String> it = styleMap.values().iterator();
+		Iterator<String> kit = styleMap.keySet().iterator();
+
+		while (kit.hasNext())
+		{
+			String key = kit.next();
+			Object value = it.next();
+
+			if(!key.equals(mxConstants.STYLE_SHAPE) || (!styleMap.get(key).startsWith("image") && !styleMap.get(key).startsWith("rounded=")))
+			{
+				style = style + key + asig;
+			}
+
+			style = style + value + ";";
+		}
+
+		return style;
+	}
+
+	/**
+	 * Returns a text surrounded by tags html.
+	 * @param text Text to be surrounded.
+	 * @param tag Name of the tag.
+	 * @return &lt tag &gt text &lt /tag &gt
+	 */
+	public static String surroundByTags(String text, String tag)
+	{
+		return "<" + tag + ">" + text + "</" + tag + ">";
+	}
+
+	/**
+	 * Converts the initial letter  of each word in text to uppercase
+	 * @param text Text to be transformed.
+	 * @return Text with initial capitals.
+	 */
+	public static String toInitialCapital(String text)
+	{
+		String[] words = text.split(" ");
+		String ret = "";
+
+		for (String word : words)
+		{
+			String begin = word.substring(0, 1);
+			word = word.substring(1);
+			begin = begin.toUpperCase();
+			ret += begin + word;
+		}
+
+		return ret.substring(0, ret.length());
+	}
+
+	/**
+	 * Trnsforms each lower case letter in text to small capital.
+	 * @param text Text to be transformed.
+	 * @param size Size of the original text.
+	 * @return Text in small capitals.
+	 */
+	public static String toSmallCaps(String text, String size)
+	{
+		String ret = "";
+
+		if (!size.equals(ret))
+		{
+			char a = 'a';
+			char z = 'z';
+			char[] letters = text.toCharArray();
+
+			for (char c : letters)
+			{
+				if (c >= a && c <= z)
+				{
+					String s = String.valueOf(c);
+					s = s.toUpperCase();
+					ret += "<font style=\"font-size:" + Double.valueOf(size) / 1.28 + "px\">" + s + "</font>";
+				}
+				else
+				{
+					ret += c;
+				}
+			}
+		}
+		else
+		{
+			ret = text;
+		}
+
+		return ret;
+	}
+
+	/**
+	 * Create a style map from a String with style definitions.
+	 * @param style Definition of the style.
+	 * @param asig Asignation simbol used in 'style'.
+	 * @return Map with the style properties.
+	 */
+	public static HashMap<String, Object> getStyleMap(String style, String asig)
+	{
+		HashMap<String, Object> styleMap = new HashMap<String, Object>();
+
+		String[] entries = style.split(";");
+
+		for (String entry : entries)
+		{
+			int index = entry.indexOf(asig);
+			String key = entry.substring(0, index);
+			String value = entry.substring(index + 1);
+			styleMap.put(key, value);
+		}
+
+		return styleMap;
+	}
+
+	/**
+	 * Print a list of mxPoint in the standard output.
+	 * @param list Lis of mxPoint.
+	 */
+	public static void printPointList(List<mxPoint> list)
+	{
+		int i = 0;
+
+		for (mxPoint p : list)
+		{
+			i++;
+			System.out.println("Point " + i + " X=" + p.getX() + ", Y="	+ p.getY());
+		}
+	}
+
+	/**
+	 * Creates an array with the cells contained in the map, ordered according
+	 * the order of the keys in orderList.
+	 * @param orderList List of keys in the order desired.
+	 * @param map Map with the object to be put in the array.
+	 * @return Array with the cells.
+	 */
+	public static Object[] getOrderArray(List<ShapePageId> orderList, HashMap<ShapePageId, Object> map)
+	{
+		int length = orderList.size();
+		Object[] array = new Object[length];
+		int i = 0;
+
+		for (ShapePageId key : orderList)
+		{
+			array[i] = map.get(key);
+			i++;
+		}
+
+		return array;
+	}
+	
+	public static boolean isInsideTriangle(double x, double y, double ax, double ay, double bx, double by, double cx,  double cy)
+	{
+		bx = bx - ax;
+		by = by - ay;
+		cx = cx - ax;
+		cy = cy - ay;
+		ax = 0;
+		ay = 0;
+		
+		double d = bx * cy - cx * by;
+		double wa = (x * (by - cy) + y * (cx - bx) + bx * cy - cx * by) / d;
+		double wb = (x * cy - y * cx) / d;
+		double wc = (y * bx - x * by) / d;
+		
+		if(wa > 0 && wa < 1 && wb > 0 && wb < 1 && wc > 0 && wc < 1)
+		{
+			return true;
+		}
+		
+		return false;
+	}
+}

+ 5 - 0
src/com/mxgraph/io/vsdx/resources/edgeNameU.properties

@@ -0,0 +1,5 @@
+Comm-link=mxgraph.lean_mapping.comm_link_edge
+Comm-link\ 1=mxgraph.lean_mapping.comm_link_edge
+Comm-link\ 2=mxgraph.lean_mapping.comm_link_edge
+Electronic\ information=mxgraph.lean_mapping.electronic_info_flow_edge
+Generalization=connector;endArrow\=block;endSize\=16;endFill\=0

+ 1 - 0
src/com/mxgraph/io/vsdx/resources/nameU.properties

@@ -0,0 +1 @@
+Swimlane=swimlane;horizontal\=0;

File diff ditekan karena terlalu besar
+ 2290 - 0
src/com/mxgraph/io/vsdx/resources/nameU.tmp


+ 42 - 0
src/com/mxgraph/online/AppShortcutServlet.java

@@ -0,0 +1,42 @@
+/**
+ * $Id: ErrorServlet.java,v 1.6 2014/02/21 12:01:30 gaudenz Exp $
+ * Copyright (c) 2014, JGraph Ltd
+ */
+package com.mxgraph.online;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Servlet implementation class OpenServlet
+ */
+public class AppShortcutServlet extends HttpServlet
+{
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * @see HttpServlet#HttpServlet()
+	 */
+	public AppShortcutServlet()
+	{
+		super();
+	}
+
+	/**
+	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
+	 */
+	protected void doGet(HttpServletRequest request,
+			HttpServletResponse response) throws ServletException, IOException
+	{
+		response.setHeader("Location", "index.html?offline=1");
+		response.setStatus(HttpServletResponse.SC_FOUND);
+	}
+
+}

+ 62 - 0
src/com/mxgraph/online/ConnectImageServlet.java

@@ -0,0 +1,62 @@
+package com.mxgraph.online;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 
+ * Implements returning a 301 for the connect image placeholder to point
+ * at the relative PNG attachment of the diagram
+ * 
+ *
+ */
+public class ConnectImageServlet extends HttpServlet
+{
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 3143318789617797083L;
+
+	public void doGet(HttpServletRequest request, HttpServletResponse response)
+	{
+		String redirect = "https://www.draw.io/images/logo-flat.png";
+		String diagramName = request.getParameter("diagramName");
+		String baseUrl = request.getParameter("baseUrl");
+		String pageId = request.getParameter("pageId");
+		String revision = request.getParameter("revision");
+
+		if (pageId != null && baseUrl != null && diagramName != null)
+		{
+			try
+			{
+				redirect = baseUrl + "/download/attachments/" + pageId + "/"
+						+ URLEncoder.encode(diagramName, "UTF-8")
+								.replaceAll("\\+", "%20")
+						+ ".png?api=v2" + (revision != null ? "&version=" + revision : "");
+			}
+			catch (UnsupportedEncodingException e)
+			{
+				// Ignore
+			}
+		}
+
+		response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
+		response.setHeader("Location", redirect);
+
+		try
+		{
+			response.getOutputStream().flush();
+			response.getOutputStream().close();
+		}
+		catch (Exception e)
+		{
+			System.out.println(e.getMessage());
+			e.printStackTrace();
+		}
+	}
+
+}

+ 53 - 0
src/com/mxgraph/online/ConnectRenderServlet.java

@@ -0,0 +1,53 @@
+package com.mxgraph.online;
+
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.logging.Logger;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 
+ * Returns XHTML in Confluence Storage Format indicating to display the PNG
+ * attachment of the diagram
+ *
+ */
+public class ConnectRenderServlet extends HttpServlet
+{
+	private static final long serialVersionUID = 161100757439732089L;
+
+	public void doGet(HttpServletRequest request, HttpServletResponse response)
+	{
+		String pageId = request.getParameter("pageId");
+		String diagramName = request.getParameter("diagramName");
+		String filenameIntact = request.getParameter("filenameIntact");
+
+		try
+		{
+			diagramName = URLEncoder.encode(diagramName, "UTF-8");
+		}
+		catch (UnsupportedEncodingException e1)
+		{
+			e1.printStackTrace();
+		}
+
+		String fileSuffix = filenameIntact != null ? "" : ".png";
+		String staticMacro = "<ac:image><ri:url ri:value=\"/download/attachments/" + pageId + "/" + diagramName + fileSuffix + "?api=v2\"/></ac:image>";
+
+		try
+		{
+			byte[] data = staticMacro.getBytes("UTF-8");
+			OutputStream out = response.getOutputStream();
+			out.write(data);
+			out.flush();
+			out.close();
+		}
+		catch (Exception e)
+		{
+			e.printStackTrace();
+		}
+	}
+}

+ 22 - 0
src/com/mxgraph/online/Constants.java

@@ -0,0 +1,22 @@
+package com.mxgraph.online;
+
+public class Constants
+{
+
+	/**
+	 * Maximum size (in bytes) for request payloads. Default is 52428800 (50MB).
+	 */
+	public static final int MAX_REQUEST_SIZE = 52428800;
+
+	/**
+	 * Maximum are for exports. Default assumes the area taken by a 
+	 * 10000px by 10000px image.
+	 */
+	public static final int MAX_AREA = 10000 * 10000;
+
+	/**
+	 * The domain where legacy images are stored.
+	 */
+	public static final String IMAGE_DOMAIN = "http://img.diagramly.com/";
+
+}

+ 120 - 0
src/com/mxgraph/online/EmailServlet.java

@@ -0,0 +1,120 @@
+/**
+ * $Id: ErrorServlet.java,v 1.6 2014/02/21 12:01:30 gaudenz Exp $
+ * Copyright (c) 2014, JGraph Ltd
+ */
+package com.mxgraph.online;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.Properties;
+import java.util.logging.Logger;
+
+import javax.mail.BodyPart;
+import javax.mail.Message;
+import javax.mail.Multipart;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Servlet implementation class OpenServlet
+ */
+public class EmailServlet extends HttpServlet
+{
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+
+	private static final Logger log = Logger.getLogger(HttpServlet.class
+			.getName());
+
+	/**
+	 * @see HttpServlet#HttpServlet()
+	 */
+	public EmailServlet()
+	{
+		super();
+	}
+
+	/**
+	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
+	 */
+	protected void doPost(HttpServletRequest request,
+			HttpServletResponse response) throws ServletException, IOException
+	{
+		request.setCharacterEncoding("UTF-8");
+
+		try
+		{
+			String data = request.getParameter("data");
+			String body = request.getParameter("body");
+
+			if (data != null || body != null)
+			{
+				if ((data != null && data.length() > 140) || body != null)
+				{
+					Properties props = new Properties();
+					Session session = Session.getDefaultInstance(props, null);
+					Message msg = new MimeMessage(session);
+
+					String email = request.getParameter("email");
+					String version = request.getParameter("version");
+					String url = request.getParameter("url");
+
+					if (email != null)
+					{
+						msg.setReplyTo(new InternetAddress[] { new InternetAddress(
+								email) });
+					}
+
+					msg.setFrom(new InternetAddress("davidjgraph@gmail.com",
+							"Draw.io"));
+					msg.addRecipient(Message.RecipientType.TO,
+							((body != null) ? new InternetAddress(
+									"feedback@jgraph.com", "Draw.io feedback")
+									: new InternetAddress("zlib@jgraph.com",
+											"Draw.io error report")));
+					msg.setSubject("Draw.io "
+							+ ((body != null) ? "Feedback" : "Error Report"));
+
+					BodyPart messageBodyPart = new MimeBodyPart();
+					messageBodyPart.setText(((body != null) ? body : data)
+							+ "\n\n----"
+							+ ((url != null) ? "\nURL: " + url : "")
+							+ ((email != null) ? "\nEmail: " + email : "")
+							+ "\nRemote Address: " + request.getRemoteAddr()
+							+ ((version != null) ? "\nVersion: " + version : ""));
+
+					Multipart multipart = new MimeMultipart();
+					multipart.addBodyPart(messageBodyPart);
+
+					msg.setContent(multipart);
+					Transport.send(msg);
+				}
+				else
+				{
+					log.severe("CRITICAL : " + data);
+				}
+
+				response.setStatus(HttpServletResponse.SC_OK);
+			}
+			else
+			{
+				response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+			}
+		}
+		catch (Exception e)
+		{
+			response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+			e.printStackTrace(new PrintStream(response.getOutputStream()));
+		}
+	}
+}

+ 407 - 0
src/com/mxgraph/online/EmbedServlet.java

@@ -0,0 +1,407 @@
+/**
+ * $Id: EmbedServlet.java,v 1.18 2014/01/31 22:27:07 gaudenz Exp $
+ * Copyright (c) 2011-2012, JGraph Ltd
+ * 
+ * TODO
+ * 
+ * We could split the static part and the stencils into two separate requests
+ * in order for multiple graphs in the pages to not load the static part
+ * multiple times. This is only relevant if the embed arguments are different,
+ * in which case there is a problem with parsin the graph model too soon, ie.
+ * before certain stencils become available.
+ * 
+ * Easier solution is for the user to move the embed script to after the last
+ * graph in the page and merge the stencil arguments.
+ * 
+ * Note: The static part is roundly 105K, the stencils are much smaller in size.
+ * This means if the embed function is widely used, it will make sense to factor
+ * out the static part because only stencils will change between pages.
+ */
+package com.mxgraph.online;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.zip.GZIPOutputStream;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.google.appengine.api.utils.SystemProperty;
+
+/**
+ * Servlet implementation class OpenServlet
+ */
+public class EmbedServlet extends HttpServlet
+{
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 
+	 */
+	protected static String reader = null;
+
+	/**
+	 * 
+	 */
+	protected static String embed = null;
+	
+	/**
+	 * 
+	 */
+	protected static String embedDev = null;
+	
+	/**
+	 * 
+	 */
+	protected static String stylesheet = null;
+
+	/**
+	 * 
+	 */
+	protected static String lastModified = null;
+
+	/**
+	 * 
+	 */
+	protected HashMap<String, String> stencils = new HashMap<String, String>();
+
+	/**
+	 * 
+	 */
+	protected HashMap<String, String[]> libraries = new HashMap<String, String[]>();
+
+	/**
+	 * @see HttpServlet#HttpServlet()
+	 */
+	public EmbedServlet()
+	{
+		if (lastModified == null)
+		{
+			// Uses deployment date as lastModified header
+			String applicationVersion = SystemProperty.applicationVersion.get();
+			Date uploadDate = new Date(Long.parseLong(applicationVersion
+					.substring(applicationVersion.lastIndexOf(".") + 1))
+					/ (2 << 27) * 1000);
+
+			DateFormat httpDateFormat = new SimpleDateFormat(
+					"EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
+			lastModified = httpDateFormat.format(uploadDate);
+		}
+
+		initLibraries(libraries);
+	}
+
+	/**
+	 * @see HttpServlet#HttpServlet()
+	 */
+	public static void initLibraries(HashMap<String, String[]> libraries)
+	{
+		libraries.put("bpmn", new String[] { "/shapes/bpmn/mxBpmnShape2.js",
+				"/stencils/bpmn.xml" });
+		libraries.put("er", new String[] { "/shapes/er/mxER.js" });
+		libraries.put("ios", new String[] { "/shapes/mockup/mxMockupiOS.js" });
+		libraries.put("ios7", new String[] { "/stencils/ios7.xml" });
+		libraries.put("android", new String[] { "/shapes/mxAndroid.js",
+				"/stencils/android/android.xml" });
+		libraries.put("lean_mapping", new String[] { "/shapes/mxLeanMap.js",
+				"/stencils/lean_mapping.xml" });
+		// Required for anchor shape which follows non-standard naming scheme (see Sidebar.js)
+		libraries.put("mockup",
+				new String[] { "/shapes/mockup/mxMockupButtons.js" });
+		libraries.put("mockup/buttons",
+				new String[] { "/shapes/mockup/mxMockupButtons.js" });
+		libraries.put("mockup/containers",
+				new String[] { "/shapes/mockup/mxMockupContainers.js" });
+		libraries.put("mockup/forms",
+				new String[] { "/shapes/mockup/mxMockupForms.js" });
+		libraries.put("mockup/graphics", new String[] {
+				"/shapes/mockup/mxMockupGraphics.js",
+				"/stencils/mockup/misc.xml" });
+		libraries.put("mockup/markup",
+				new String[] { "/shapes/mockup/mxMockupMarkup.js" });
+		libraries
+				.put("mockup/misc", new String[] {
+						"/shapes/mockup/mxMockupMisc.js",
+						"/stencils/mockup/misc.xml" });
+		libraries.put("mockup/navigation", new String[] {
+				"/shapes/mockup/mxMockupNavigation.js",
+				"/stencils/mockup/misc.xml" });
+		libraries.put("mockup/text",
+				new String[] { "/shapes/mockup/mxMockupText.js" });
+		libraries.put("pid2inst",
+				new String[] { "/shapes/pid2/mxPidInstruments.js" });
+		libraries.put("pid2misc", new String[] { "/shapes/pid2/mxPidMisc.js",
+				"/stencils/pid/misc.xml" });
+		libraries.put("pid2valves",
+				new String[] { "/shapes/pid2/mxPidValves.js" });
+		libraries.put("floorplan", new String[] { "/shapes/mxFloorplan.js",
+				"/stencils/floorplan.xml" });
+		libraries.put("archimate", new String[] { "/shapes/mxArchiMate.js" });
+		libraries.put("azure", new String[] { "/stencils/azure.xml" });
+	}
+
+	/**
+	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
+	 */
+	protected void doGet(HttpServletRequest request,
+			HttpServletResponse response) throws ServletException, IOException
+	{
+		try
+		{
+			String qs = request.getQueryString();
+
+			if (qs != null && qs.equals("stats"))
+			{
+				writeStats(response);
+			}
+			else
+			{
+				if (reader == null)
+				{
+					reader = readFile("/js/reader.min.js");
+				}
+
+				if (embed == null)
+				{
+					embed = readFile("/js/embed.min.js");
+				}
+				
+				if (embedDev == null)
+				{
+					embedDev = readFile("/js/embed.dev.js");
+				}
+
+				if (stylesheet == null)
+				{
+					stylesheet = readXmlFile("/styles/default.xml", true);
+				}
+
+				// Checks or sets last modified date of delivered content.
+				// Date comparison not needed. Only return 304 if
+				// delivered by this servlet instance.
+				String modSince = request.getHeader("If-Modified-Since");
+
+				if (modSince != null && modSince.equals(lastModified))
+				{
+					response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+				}
+				else
+				{
+					writeEmbedResponse(request, response);
+				}
+			}
+		}
+		catch (Exception e)
+		{
+			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+		}
+	}
+
+	public void writeEmbedResponse(HttpServletRequest request,
+			HttpServletResponse response) throws IOException
+	{
+		response.setCharacterEncoding("UTF-8");
+		response.setContentType("application/javascript; charset=UTF-8");
+		response.setHeader("Last-Modified", lastModified);
+
+		OutputStream out = response.getOutputStream();
+
+		// FIXME: Accept-encoding header is missing
+		String encoding = request.getHeader("Accept-Encoding");
+
+		// Supports GZIP content encoding
+		if (encoding != null && encoding.indexOf("gzip") >= 0)
+		{
+			response.setHeader("Content-Encoding", "gzip");
+			out = new GZIPOutputStream(out);
+		}
+
+		// Creates XML for stencils
+		PrintWriter writer = new PrintWriter(out);
+
+		// Writes JavaScript and adds function call with
+		// stylesheet and stencils as arguments 
+		writer.println(createEmbedJavaScript(request.getParameter("s"), request.getParameter("dev")));
+		response.setStatus(HttpServletResponse.SC_OK);
+
+		writer.flush();
+		writer.close();
+	}
+
+	public String createEmbedJavaScript(String sparam, String dev) throws IOException
+	{
+		StringBuffer result = new StringBuffer("[");
+		StringBuffer js = new StringBuffer("");
+
+		// Processes each stencil only once
+		HashSet<String> done = new HashSet<String>();
+		
+		// Processes each lib only once
+		HashSet<String> libsLoaded = new HashSet<String>();
+
+		if (sparam != null)
+		{
+			String[] names = sparam.split(";");
+
+			for (int i = 0; i < names.length; i++)
+			{
+				if (names[i].indexOf("..") < 0 && !done.contains(names[i]))
+				{
+					if (names[i].equals("*"))
+					{
+						js.append(readXmlFile("/js/shapes.min.js", false));
+						result.append("'" + readXmlFile("/stencils.xml", true)
+								+ "'");
+					}
+					else
+					{
+						// Checks if any JS files are associated with the library
+						// name and injects the JS into the page
+						String[] libs = libraries.get(names[i]);
+
+						if (libs != null)
+						{
+							for (int j = 0; j < libs.length; j++)
+							{
+								if (!libsLoaded.contains(libs[j]))
+								{
+									String tmp = stencils.get(libs[j]);
+									libsLoaded.add(libs[j]);
+									
+									if (tmp == null)
+									{
+										try
+										{
+											tmp = readXmlFile(libs[j], !libs[j]
+													.toLowerCase().endsWith(".js"));
+	
+											// Cache for later use
+											if (tmp != null)
+											{
+												stencils.put(libs[j], tmp);
+											}
+										}
+										catch (NullPointerException e)
+										{
+											// This seems possible according to access log so ignore stencil
+										}
+									}
+	
+									if (tmp != null)
+									{
+										// TODO: Add JS to Javascript code inline. This had to be done to quickly
+										// add JS-based dynamic loading to the existing embed setup where everything
+										// dynamic is passed via function call, so an indirection via eval must be
+										// used even though the JS could be parsed directly by adding it to JS.
+										if (libs[j].toLowerCase().endsWith(".js"))
+										{
+											js.append(tmp);
+										}
+										else
+										{
+											if (result.length() > 1)
+											{
+												result.append(",");
+											}
+											
+											result.append("'" + tmp + "'");
+										}
+									}
+								}
+							}
+						}
+						else
+						{
+							String tmp = stencils.get(names[i]);
+
+							if (tmp == null)
+							{
+								try
+								{
+									tmp = readXmlFile("/stencils/" + names[i]
+											+ ".xml", true);
+
+									// Cache for later use
+									if (tmp != null)
+									{
+										stencils.put(names[i], tmp);
+									}
+								}
+								catch (NullPointerException e)
+								{
+									// This seems possible according to access log so ignore stencil
+								}
+							}
+
+							if (tmp != null)
+							{
+								if (result.length() > 1)
+								{
+									result.append(",");
+								}
+								
+								result.append("'" + tmp + "'");
+							}
+						}
+					}
+
+					done.add(names[i]);
+				}
+			}
+		}
+
+		result.append("]");
+		
+		String tmp = (dev != null && dev.equals("1")) ? embedDev : embed;
+
+		// JS must be executed after core but before embed function
+		return reader + "\n" + js + tmp + "})('" + stylesheet + "',"
+				+ result.toString() + ");";
+	}
+
+	public void writeStats(HttpServletResponse response) throws IOException
+	{
+		PrintWriter writer = new PrintWriter(response.getOutputStream());
+		writer.println("<html>");
+		writer.println("<body>");
+		writer.println("Deployed: " + lastModified);
+		writer.println("</body>");
+		writer.println("</html>");
+		writer.flush();
+	}
+
+	public String readXmlFile(String filename, boolean xmlContent)
+			throws IOException
+	{
+		String result = readFile(filename);
+
+		if (xmlContent)
+		{
+			result = result.replaceAll("'", "\\\\'").replaceAll("\t", "")
+					.replaceAll("\n", "");
+		}
+
+		return result;
+	}
+
+	public String readFile(String filename) throws IOException
+	{
+		InputStream is = getServletContext().getResourceAsStream(filename);
+
+		return Utils.readInputStream(is);
+	}
+
+}

+ 325 - 0
src/com/mxgraph/online/EmbedServlet2.java

@@ -0,0 +1,325 @@
+/**
+ * $Id: EmbedServlet.java,v 1.18 2014/01/31 22:27:07 gaudenz Exp $
+ * Copyright (c) 2011-2012, JGraph Ltd
+ * 
+ * TODO
+ * 
+ * We could split the static part and the stencils into two separate requests
+ * in order for multiple graphs in the pages to not load the static part
+ * multiple times. This is only relevant if the embed arguments are different,
+ * in which case there is a problem with parsin the graph model too soon, ie.
+ * before certain stencils become available.
+ * 
+ * Easier solution is for the user to move the embed script to after the last
+ * graph in the page and merge the stencil arguments.
+ * 
+ * Note: The static part is roundly 105K, the stencils are much smaller in size.
+ * This means if the embed function is widely used, it will make sense to factor
+ * out the static part because only stencils will change between pages.
+ */
+package com.mxgraph.online;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.google.appengine.api.utils.SystemProperty;
+
+/**
+ * Servlet implementation class OpenServlet
+ */
+public class EmbedServlet2 extends HttpServlet
+{
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 
+	 */
+	protected static String lastModified = null;
+
+	/**
+	 * 
+	 */
+	protected HashMap<String, String> stencils = new HashMap<String, String>();
+
+	/**
+	 * 
+	 */
+	protected HashMap<String, String[]> libraries = new HashMap<String, String[]>();
+
+	/**
+	 * @see HttpServlet#HttpServlet()
+	 */
+	public EmbedServlet2()
+	{
+		if (lastModified == null)
+		{
+			// Uses deployment date as lastModified header
+			String applicationVersion = SystemProperty.applicationVersion.get();
+			Date uploadDate = new Date(Long
+					.parseLong(applicationVersion
+							.substring(applicationVersion.lastIndexOf(".") + 1))
+					/ (2 << 27) * 1000);
+
+			DateFormat httpDateFormat = new SimpleDateFormat(
+					"EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
+			lastModified = httpDateFormat.format(uploadDate);
+		}
+
+		EmbedServlet.initLibraries(libraries);
+	}
+
+	/**
+	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
+	 */
+	protected void doGet(HttpServletRequest request,
+			HttpServletResponse response) throws ServletException, IOException
+	{
+		try
+		{
+			String qs = request.getQueryString();
+
+			if (qs != null && qs.equals("stats"))
+			{
+				writeStats(response);
+			}
+			else
+			{
+				// Checks or sets last modified date of delivered content.
+				// Date comparison not needed. Only return 304 if
+				// delivered by this servlet instance.
+				String modSince = request.getHeader("If-Modified-Since");
+
+				if (modSince != null && modSince.equals(lastModified))
+				{
+					response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+				}
+				else
+				{
+					writeEmbedResponse(request, response);
+				}
+			}
+		}
+		catch (Exception e)
+		{
+			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+		}
+	}
+
+	public void writeEmbedResponse(HttpServletRequest request,
+			HttpServletResponse response) throws IOException
+	{
+		response.setCharacterEncoding("UTF-8");
+		response.setContentType("application/javascript; charset=UTF-8");
+		response.setHeader("Last-Modified", lastModified);
+
+		OutputStream out = response.getOutputStream();
+
+		// Creates XML for stencils
+		PrintWriter writer = new PrintWriter(out);
+
+		// Writes JavaScript and adds function call with
+		// stylesheet and stencils as arguments 
+		writer.println(createEmbedJavaScript(request));
+		response.setStatus(HttpServletResponse.SC_OK);
+
+		writer.flush();
+		writer.close();
+	}
+
+	public String createEmbedJavaScript(HttpServletRequest request)
+			throws IOException
+	{
+		String sparam = request.getParameter("s");
+		String dev = request.getParameter("dev");
+		StringBuffer result = new StringBuffer("[");
+		StringBuffer js = new StringBuffer("");
+
+		// Processes each stencil only once
+		HashSet<String> done = new HashSet<String>();
+
+		// Processes each lib only once
+		HashSet<String> libsLoaded = new HashSet<String>();
+
+		if (sparam != null)
+		{
+			String[] names = sparam.split(";");
+
+			for (int i = 0; i < names.length; i++)
+			{
+				if (names[i].indexOf("..") < 0 && !done.contains(names[i]))
+				{
+					if (names[i].equals("*"))
+					{
+						js.append(readXmlFile("/js/shapes.min.js", false));
+						result.append(
+								"'" + readXmlFile("/stencils.xml", true) + "'");
+					}
+					else
+					{
+						// Checks if any JS files are associated with the library
+						// name and injects the JS into the page
+						String[] libs = libraries.get(names[i]);
+
+						if (libs != null)
+						{
+							for (int j = 0; j < libs.length; j++)
+							{
+								if (!libsLoaded.contains(libs[j]))
+								{
+									String tmp = stencils.get(libs[j]);
+									libsLoaded.add(libs[j]);
+
+									if (tmp == null)
+									{
+										try
+										{
+											tmp = readXmlFile(libs[j],
+													!libs[j].toLowerCase()
+															.endsWith(".js"));
+
+											// Cache for later use
+											if (tmp != null)
+											{
+												stencils.put(libs[j], tmp);
+											}
+										}
+										catch (NullPointerException e)
+										{
+											// This seems possible according to access log so ignore stencil
+										}
+									}
+
+									if (tmp != null)
+									{
+										// TODO: Add JS to Javascript code inline. This had to be done to quickly
+										// add JS-based dynamic loading to the existing embed setup where everything
+										// dynamic is passed via function call, so an indirection via eval must be
+										// used even though the JS could be parsed directly by adding it to JS.
+										if (libs[j].toLowerCase()
+												.endsWith(".js"))
+										{
+											js.append(tmp);
+										}
+										else
+										{
+											if (result.length() > 1)
+											{
+												result.append(",");
+											}
+
+											result.append("'" + tmp + "'");
+										}
+									}
+								}
+							}
+						}
+						else
+						{
+							String tmp = stencils.get(names[i]);
+
+							if (tmp == null)
+							{
+								try
+								{
+									tmp = readXmlFile(
+											"/stencils/" + names[i] + ".xml",
+											true);
+
+									// Cache for later use
+									if (tmp != null)
+									{
+										stencils.put(names[i], tmp);
+									}
+								}
+								catch (NullPointerException e)
+								{
+									// This seems possible according to access log so ignore stencil
+								}
+							}
+
+							if (tmp != null)
+							{
+								if (result.length() > 1)
+								{
+									result.append(",");
+								}
+
+								result.append("'" + tmp + "'");
+							}
+						}
+					}
+
+					done.add(names[i]);
+				}
+			}
+		}
+
+		result.append("]");
+		
+		// LATER: Detect protocol of request in dev
+		// mode to avoid security errors
+		String proto = "https://";
+
+		// Installs a callback to load the stencils after the viewer was injected
+		return "window.onDrawioViewerLoad = function() {"
+				+ "mxStencilRegistry.parseStencilSets(" + result.toString() + ");"
+				+ js
+				+ "GraphViewer.processElements(); };"
+				+ "var t = document.getElementsByTagName('script');"
+				+ "if (t != null && t.length > 0) {"
+				+ "var script = document.createElement('script');"
+				+ "script.type = 'text/javascript';" + "script.src = '" + proto
+				+ ((dev != null && dev.equals("1")) ? "test" : "www")
+				+ ".draw.io/js/viewer.min.js';"
+				+ "t[0].parentNode.appendChild(script);}";
+	}
+
+	public void writeStats(HttpServletResponse response) throws IOException
+	{
+		PrintWriter writer = new PrintWriter(response.getOutputStream());
+		writer.println("<html>");
+		writer.println("<body>");
+		writer.println("Deployed: " + lastModified);
+		writer.println("</body>");
+		writer.println("</html>");
+		writer.flush();
+	}
+
+	public String readXmlFile(String filename, boolean xmlContent)
+			throws IOException
+	{
+		String result = readFile(filename);
+
+		if (xmlContent)
+		{
+			result = result.replaceAll("'", "\\\\'").replaceAll("\t", "")
+					.replaceAll("\n", "");
+		}
+
+		return result;
+	}
+
+	public String readFile(String filename) throws IOException
+	{
+		InputStream is = getServletContext().getResourceAsStream(filename);
+
+		return Utils.readInputStream(is);
+	}
+
+}

+ 254 - 0
src/com/mxgraph/online/GoogleGadgetServlet.java

@@ -0,0 +1,254 @@
+/**
+ * $Id: GoogleSitesServlet.java,v 1.4 2014/01/27 21:51:26 gaudenz Exp $
+ * Copyright (c) 2011-2012, JGraph Ltd
+ * 
+ * TODO
+ * 
+ * We could split the static part and the stencils into two separate requests
+ * in order for multiple graphs in the pages to not load the static part
+ * multiple times. This is only relevant if the embed arguments are different,
+ * in which case there is a problem with parsin the graph model too soon, ie.
+ * before certain stencils become available.
+ * 
+ * Easier solution is for the user to move the embed script to after the last
+ * graph in the page and merge the stencil arguments.
+ * 
+ * Note: The static part is roundly 105K, the stencils are much smaller in size.
+ * This means if the embed function is widely used, it will make sense to factor
+ * out the static part because only stencils will change between pages.
+ */
+package com.mxgraph.online;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Servlet implementation class OpenServlet
+ */
+public class GoogleGadgetServlet extends HttpServlet
+{
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * @see HttpServlet#HttpServlet()
+	 */
+	public GoogleGadgetServlet()
+	{
+
+	}
+
+	/**
+	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
+	 */
+	protected void doGet(HttpServletRequest request,
+			HttpServletResponse response) throws ServletException, IOException
+	{
+		String diagram = request.getParameter("diagram");
+		String type = request.getParameter("type");
+		String title = request.getParameter("title");
+		String edit = request.getParameter("edit");
+		String embed = request.getParameter("embed");
+		String b = request.getParameter("border");
+		String zoom = request.getParameter("zoom");
+		String pan = request.getParameter("pan");
+		String fit = request.getParameter("fit");
+		String resize = request.getParameter("resize");
+		String height = request.getParameter("height");
+		String math = request.getParameter("math");
+		String x0 = request.getParameter("x0");
+		String y0 = request.getParameter("y0");
+		String s = request.getParameter("s");
+		boolean showEmbed = (embed != null) ? embed.equals("1") : true;
+		
+		if (diagram == null)
+		{
+			diagram = "";
+		}
+		
+		if (type == null)
+		{
+			type = "1";
+		}
+
+		if (title == null)
+		{
+			title = "Draw.io diagram";
+		}
+		
+		if (height == null)
+		{
+			height = "400";
+		}
+		
+		if (edit == null)
+		{
+			edit = "";
+		}
+		
+		if (s == null)
+		{
+			s = "";
+		}
+		
+		if (x0 == null)
+		{
+			x0 = "0";
+		}
+		
+		if (y0 == null)
+		{
+			y0 = "0";
+		}
+
+		if (b == null)
+		{
+			b = "0";
+		}
+
+		if (zoom == null)
+		{
+			zoom = "1";
+		}
+
+		if (resize == null)
+		{
+			resize = "0";
+		}
+
+		if (pan == null)
+		{
+			pan = "1";
+		}
+		
+		if (fit == null)
+		{
+			fit = "1";
+		}
+
+		if (math == null)
+		{
+			math = "0";
+		}
+		
+		response.setCharacterEncoding("UTF-8");
+		response.setContentType("application/xml");
+		response.setStatus(HttpServletResponse.SC_OK);
+		
+		// Could use makeRequest with lower refreshInterval for getting data from
+		// URLs and possibly, using OAUTH, for getting Drive file contents without
+		// requiring to handle auth in the draw.io app again (pass XML in via
+		// postMessage for example with a message to request the XML for solving
+		// the timing issue when loading cross-domain iframe contents).
+		OutputStream out = response.getOutputStream();
+		out.write(("<Module>\n"+
+				"<ModulePrefs title=\"" + title + "\" height=\"" + height + "\"\n"+
+				" screenshot=\"https://www.draw.io/images/gadget-screenshot.png\"\n"+
+				" thumbnail=\"https://www.draw.io/images/gadget-thumb.png\"/>\n"+
+				"<UserPref name=\"diagram\" display_name=\"Diagram ID or URL\" datatype=\"string\"\n"+
+				" default_value=\"" + diagram + "\" required=\"true\">\n"+
+				"</UserPref>\n"+
+				"<UserPref name=\"type\" display_name=\"Location\" datatype=\"enum\"\n"+
+				" default_value=\"" + type + "\">\n"+
+				" <EnumValue value=\"1\" display_value=\"Google Drive\"/>\n"+
+				" <EnumValue value=\"2\" display_value=\"Dropbox\"/>\n"+
+				" <EnumValue value=\"3\" display_value=\"Public\"/>\n"+
+				((showEmbed) ? " <EnumValue value=\"4\" display_value=\"Public (Embed)\"/>\n" : "")+
+				"</UserPref>\n"+
+				((showEmbed) ?
+				"<UserPref name=\"x0\" display_name=\"Left (Embed)\" datatype=\"string\" default_value=\"" + x0 + "\"></UserPref>\n"+
+				"<UserPref name=\"y0\" display_name=\"Top (Embed)\" datatype=\"string\" default_value=\"" + y0 + "\"></UserPref>\n"+
+				"<UserPref name=\"border\" display_name=\"Border (Embed)\" datatype=\"string\" default_value=\"" + b + "\"></UserPref>\n"+
+				"<UserPref name=\"zoom\" display_name=\"Zoom enabled (Embed)\" datatype=\"bool\" default_value=\"" + ((zoom.equals("1")) ? "true" : "false") + "\"></UserPref>\n"+
+				"<UserPref name=\"pan\" display_name=\"Panning enabled (Embed)\" datatype=\"bool\" default_value=\"" + ((pan.equals("1")) ? "true" : "false") + "\"></UserPref>\n"+
+				"<UserPref name=\"fit\" display_name=\"Fit diagram to gadget (Embed)\" datatype=\"bool\" default_value=\"" + ((fit.equals("1")) ? "true" : "false") + "\"></UserPref>\n"+
+				"<UserPref name=\"resize\" display_name=\"Resize container (Embed)\" datatype=\"bool\" default_value=\"" + ((resize.equals("1")) ? "true" : "false") + "\"></UserPref>\n"+
+				"<UserPref name=\"math\" display_name=\"Mathematical typesetting (Embed)\" datatype=\"bool\" default_value=\"" + ((math.equals("1")) ? "true" : "false") + "\"></UserPref>\n"+
+				"<UserPref name=\"edit\" display_name=\"Edit URL (Embed)\" datatype=\"string\" default_value=\"" + edit + "\"></UserPref>\n"+
+				"<UserPref name=\"stencils\" display_name=\"Stencils (Embed)\" datatype=\"string\" default_value=\"" + s + "\"></UserPref>\n"
+				: "")+
+				"<Content type=\"html\">\n"+
+				"<![CDATA[\n"+
+				" <script type=\"text/javascript\">\n"+
+				" var prefs = new _IG_Prefs();\n"+
+				" var type = prefs.getString(\'type\');\n"+
+				" var diagram = prefs.getString(\'diagram\');\n"+
+				" \n"+
+				" if (diagram != null && diagram.length > 0)\n"+
+				" {\n"+
+				" if (type <= 3)\n"+
+				" {\n"+
+				" var file = (type == 3) ? \'&gapi=0&db=0&url=\' + encodeURIComponent(diagram) :\n"+
+				" (((type == 2) ? \'&gapi=0\' : \'&db=0\') +\n"+
+				" \'#\' + ((type == 2) ? \'D\' : \'G\') + diagram);\n"+
+				"\n"+
+				" var iframe = document.createElement(\'iframe\');\n"+
+				" iframe.setAttribute(\'frameborder\', \'0\');\n"+
+				" iframe.style.width = \'100%\';\n"+
+				" iframe.style.height = \'100%\';\n"+
+				" iframe.setAttribute(\'src\', \'https://www.draw.io/?chrome=0\' + file);\n"+
+				" document.body.appendChild(iframe);\n"+
+				" }\n"+
+				" else\n"+
+				" {\n"+
+				" var x0 = prefs.getString(\'x0\');\n"+
+				" var y0 = prefs.getString(\'y0\');\n"+
+				" var b = prefs.getString(\'border\');\n"+
+				" var zoom = (prefs.getBool(\'zoom\') == true) ? \'1\' : \'0\';\n"+
+				" var pan = (prefs.getBool(\'pan\') == true) ? \'1\' : \'0\';\n"+
+				" var fit = (prefs.getBool(\'fit\') == true) ? \'1\' : \'0\';\n"+
+				" var resize = (prefs.getBool(\'resize\') == true) ? \'1\' : \'0\';\n"+
+				" var math = (prefs.getBool(\'math\') == true) ? \'1\' : \'0\';\n"+
+				" var edit = prefs.getString(\'edit\');\n"+
+				" var s = prefs.getString(\'stencils\');\n"+
+				"\n"+
+				" var div = document.createElement(\'div\');\n"+
+				" div.className = \'mxgraph\';\n"+
+				" div.style.position = \'relative\';\n"+
+				" div.style.overflow = \'hidden\';\n"+
+				" div.style.width = \'100%\';\n"+
+				" div.style.height = \'100%\';\n"+
+				" \n"+
+				" var inner = document.createElement(\'div\');\n"+
+				" inner.style.width = \'1px\';\n"+
+				" inner.style.height = \'1px\';\n"+
+				" inner.style.overflow = \'hidden\';\n"+
+				" \n"+
+				" var model = \'<mxGraphModel style=\"default-style2\" x0=\"\' + x0 +\n"+
+				" \'\" y0=\"\' + y0 + \'\" pan=\"\' + pan + \'\" zoom=\"\' + zoom +\n"+
+				" ((edit.length > 0) ? \'\" edit=\"\' + edit : \'\') +\n"+
+				" \'\" resize=\"\' + resize + \'\" fit=\"\' + fit + \'\" border=\"\' + b +\n"+
+				" \'\" math=\"\' + math + \'\" links=\"1\" url=\"\' + diagram + \'\"/>\';\n"+
+				" \n"+
+				" inner.innerHTML = encodeURIComponent(model);\n"+
+				" \n"+
+				" var src = \'https://www.draw.io/embed.js\';\n"+
+				" \n"+
+				" if (s.length > 0)\n"+
+				" {\n"+
+				" src += \'?s=\' + s;\n"+
+				" }\n"+
+				" \n"+
+				" div.appendChild(inner);\n"+
+				" document.body.appendChild(div);\n"+
+				" \n"+
+				" document.write(\'<script src=\"\' + src + \'\"></scr\' + \'ipt>\');\n"+
+				" }\n"+
+				" }\n"+
+				"\t</script>\n"+
+				"]]>\n"+
+				"</Content>\n"+
+				"</Module>\n"+
+				"").getBytes());
+		out.flush();
+		out.close();
+	}
+
+}

+ 100 - 0
src/com/mxgraph/online/IconFinderServlet.java

@@ -0,0 +1,100 @@
+/**
+ * $Id: IconFinderServlet.java,v 1.6 2013/08/22 09:43:18 gaudenz Exp $
+ * Copyright (c) 2011-2012, JGraph Ltd
+ */
+package com.mxgraph.online;
+
+import java.io.IOException;
+import java.net.URL;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Servlet implementation class OpenServlet
+ */
+public class IconFinderServlet extends HttpServlet
+{
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * Path component under war/ to locate iconfinder_key file.
+	 */
+	public static final String API_KEY_FILE_PATH = "/WEB-INF/iconfinder_key";
+
+	/**
+	 * API key for iconfinder.
+	 */
+	public static String API_KEY = null;
+
+	/**
+	 * @see HttpServlet#HttpServlet()
+	 */
+	public IconFinderServlet()
+	{
+		super();
+	}
+
+	/**
+	 * Loads the key.
+	 */
+	protected void updateKey()
+	{
+		if (API_KEY == null)
+		{
+			try
+			{
+				API_KEY = Utils.readInputStream(
+						getServletContext().getResourceAsStream(
+								getAPIKeyFilePath())).replaceAll("\n", "");
+			}
+			catch (IOException e)
+			{
+				throw new RuntimeException("API key file path invalid.");
+			}
+		}
+	}
+
+	public void doGet(HttpServletRequest request, HttpServletResponse response)
+	{
+		doPost(request, response);
+	}
+
+	public void doPost(HttpServletRequest request, HttpServletResponse response)
+	{
+		updateKey();
+
+		try
+		{
+			URL url = new URL("https://www.iconfinder.com/xml/search/?q="
+					+ Utils.encodeURIComponent(request.getParameter("q"),
+							Utils.CHARSET_FOR_URL_ENCODING) + "&p="
+					+ request.getParameter("p") + "&c="
+					+ request.getParameter("c") + "&l="
+					+ request.getParameter("l")
+					+ "&price=nonpremium&min=4&max=130&api_key=" + API_KEY);
+
+			response.addHeader("Access-Control-Allow-Origin", "*");
+			response.addHeader("Access-Control-Allow-Methods",
+					"POST, GET, OPTIONS, PUT, DELETE, HEAD");
+
+			Utils.copy(url.openStream(), response.getOutputStream());
+			response.getOutputStream().flush();
+			response.getOutputStream().close();
+		}
+		catch (Exception e)
+		{
+			System.out.println(e.getMessage());
+			e.printStackTrace();
+		}
+	}
+
+	protected String getAPIKeyFilePath()
+	{
+		return API_KEY_FILE_PATH;
+	}
+}

+ 51 - 0
src/com/mxgraph/online/ImgurRedirectServlet.java

@@ -0,0 +1,51 @@
+/**
+ * $Id: ProxyServlet.java,v 1.4 2013/12/13 13:18:11 david Exp $
+ * Copyright (c) 2011-2012, JGraph Ltd
+ */
+package com.mxgraph.online;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Servlet implementation ProxyServlet
+ */
+@SuppressWarnings("serial")
+public class ImgurRedirectServlet extends HttpServlet
+{
+
+	/**
+	 * @see HttpServlet#HttpServlet()
+	 */
+	public ImgurRedirectServlet()
+	{
+		super();
+	}
+
+	/**
+	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
+	 */
+	protected void doGet(HttpServletRequest request,
+			HttpServletResponse response) throws ServletException, IOException
+	{
+		String uri = request.getRequestURI();
+		int last = uri.lastIndexOf("/");
+				
+		if (last > 0)
+		{
+			String id = uri.substring(last + 1);
+			response.setHeader("Location",
+					"https://www.draw.io/?url=http%3A%2F%2Fi.imgur.com%2F" + id + ".png");
+			response.setStatus(HttpServletResponse.SC_TEMPORARY_REDIRECT);
+		}
+		else
+		{
+			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+		}
+	}
+
+}

+ 192 - 0
src/com/mxgraph/online/ImgurServlet.java

@@ -0,0 +1,192 @@
+/**
+ * $Id: IconFinderServlet.java,v 1.6 2013/08/22 09:43:18 gaudenz Exp $
+ * Copyright (c) 2011-2012, JGraph Ltd
+ */
+package com.mxgraph.online;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.logging.Logger;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Servlet implementation class OpenServlet
+ */
+public class ImgurServlet extends HttpServlet
+{
+	/**
+	 * 
+	 */
+	private static final Logger log = Logger
+			.getLogger(ImgurServlet.class.getName());
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * Path component under war/ to locate imgur client id file.
+	 */
+	public static final String IMGUR_CLIENT_ID_FILE_PATH = "/WEB-INF/imgur_client_id";
+
+	/**
+	 * API key for iconfinder.
+	 */
+	public static String IMGUR_CLIENT_ID = null;
+
+	/**
+	 * Path component under war/ to locate mashape key file.
+	 */
+	public static final String MASHAPE_KEY_FILE_PATH = "/WEB-INF/mashape_key";
+
+	/**
+	 * API key for iconfinder.
+	 */
+	public static String MASHAPE_KEY = null;
+
+	/**
+	 * @see HttpServlet#HttpServlet()
+	 */
+	public ImgurServlet()
+	{
+		super();
+	}
+
+	/**
+	 * Loads the key.
+	 */
+	protected void updateKeys()
+	{
+		if (IMGUR_CLIENT_ID == null)
+		{
+			try
+			{
+				IMGUR_CLIENT_ID = Utils
+						.readInputStream(
+								getServletContext().getResourceAsStream(
+										getImgurClientIdFilePath()))
+						.replaceAll("\n", "");
+			}
+			catch (IOException e)
+			{
+				throw new RuntimeException(
+						"Imgur Client ID file path invalid.");
+			}
+		}
+
+		if (MASHAPE_KEY == null)
+		{
+			try
+			{
+				MASHAPE_KEY = Utils
+						.readInputStream(getServletContext()
+								.getResourceAsStream(getMashapeKeyFilePath()))
+						.replaceAll("\n", "");
+			}
+			catch (IOException e)
+			{
+				throw new RuntimeException("Mashape key file path invalid.");
+			}
+		}
+	}
+
+	public void doGet(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+	{
+		// Redirected for delete requests
+		doPost(request, response);
+	}
+
+	public void doPost(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+	{
+		updateKeys();
+
+		// For some strange reason, calling getParameter kills the incoming request body
+		String query = request.getQueryString();
+
+		try
+		{
+			String id = null;
+			String method = "POST";
+
+			if (query != null)
+			{
+				if (query.startsWith("delete="))
+				{
+					id = query.substring(7);
+					method = "DELETE";
+				}
+				else
+				{
+					id = query;
+				}
+			}
+
+			URL url = new URL((id != null)
+					? "https://imgur-apiv3.p.mashape.com/3/image/" + id
+					: "https://imgur-apiv3.p.mashape.com/3/upload.json");
+
+			HttpURLConnection connection = (HttpURLConnection) url
+					.openConnection();
+			connection.setRequestProperty("Content-Type", "application/json");
+			connection.setRequestProperty("X-Mashape-Key", MASHAPE_KEY);
+			connection.setRequestProperty("Authorization",
+					"Client-ID " + IMGUR_CLIENT_ID);
+
+			// Posts the HTTP body data
+			connection.setDoOutput(true);
+			connection.setRequestMethod(method);
+
+			// Copies incoming to outgoing request
+			OutputStream out = connection.getOutputStream();
+			Utils.copy(request.getInputStream(), out);
+			out.flush();
+			out.close();
+
+			// Copies incoming to outgoing response
+			response.setStatus(connection.getResponseCode());
+			String data = Utils.readInputStream(connection.getInputStream());
+
+			if (query == null || method.equals("DELETE"))
+			{
+				if (query == null)
+				{
+					query = "upload";
+				}
+
+				log.info("imgur: " + request.getRemoteAddr() + " "
+						+ request.getHeader("Referer") + " " + query + " "
+						+ data);
+			}
+
+			out = response.getOutputStream();
+			out.write(data.getBytes());
+			out.flush();
+			out.close();
+		}
+		catch (Exception e)
+		{
+			System.out.println(e.getMessage());
+			e.printStackTrace();
+		}
+	}
+
+	protected String getImgurClientIdFilePath()
+	{
+		return IMGUR_CLIENT_ID_FILE_PATH;
+	}
+
+	protected String getMashapeKeyFilePath()
+	{
+		return MASHAPE_KEY_FILE_PATH;
+	}
+
+}

+ 72 - 0
src/com/mxgraph/online/LicenseInitServlet.java

@@ -0,0 +1,72 @@
+/**
+ * $Id: LicenseInitServlet.java,v 1.4 2013/12/13 13:18:11 david Exp $
+ * Copyright (c) 2011-2012, JGraph Ltd
+ */
+package com.mxgraph.online;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.google.appengine.api.datastore.DatastoreServiceFactory;
+import com.google.appengine.api.datastore.Entity;
+
+@SuppressWarnings("serial")
+public class LicenseInitServlet extends HttpServlet
+{
+
+	/**
+	 * @see HttpServlet#HttpServlet()
+	 */
+	public LicenseInitServlet()
+	{
+		super();
+	}
+
+	/**
+	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
+	 */
+	protected void doGet(HttpServletRequest request,
+			HttpServletResponse response) throws ServletException, IOException
+	{
+		try
+		{
+			initDatastore();
+			response.setStatus(HttpServletResponse.SC_OK);
+			response.setContentType("text/html");
+
+			OutputStream out = response.getOutputStream();
+			out.write("License Datastore initialized".getBytes());
+			out.flush();
+			out.close();
+		}
+		catch (Exception e)
+		{
+			response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
+	 */
+	protected void initDatastore()
+	{
+		Calendar c = new GregorianCalendar();
+		c.add(Calendar.DATE, -1);
+
+		Entity entity = new Entity(LicenseServlet.ENTITY_SC, "example.com");
+		entity.setProperty("type", "test");
+		entity.setProperty("json", "{\"expiry\": \""
+				+ LicenseServlet.isoDateFormat.format(c.getTime()) + "\"}");
+
+		DatastoreServiceFactory.getDatastoreService().put(entity);
+	}
+
+}

+ 138 - 0
src/com/mxgraph/online/LicenseServlet.java

@@ -0,0 +1,138 @@
+/**
+ * $Id: LicenseServlet.java,v 1.4 2013/12/13 13:18:11 david Exp $
+ * Copyright (c) 2011-2012, JGraph Ltd
+ */
+package com.mxgraph.online;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.google.appengine.api.datastore.DatastoreService;
+import com.google.appengine.api.datastore.DatastoreServiceFactory;
+import com.google.appengine.api.datastore.EntityNotFoundException;
+import com.google.appengine.api.datastore.KeyFactory;
+
+/**
+ * Possible values for json property:
+ * 
+ * {"expiry": "YYYY-MM-DD"} (eg. {"expiry": "2016-03-31"}) or {"expiry": "never"}
+ * 
+ * A date within 90 days will show a message that the license will expire.
+ * A date in the past (or same day) will show a message that the license has expired.
+ * A date in the future or an empty JSON object (eg. {}) will remove the footer.
+ * An empty string for json is the same as no entry (ie. footer will not be removed).
+ * 
+ * Possible keys:
+ * 
+ * Any domain name (eg. example.com).
+ */
+@SuppressWarnings("serial")
+public class LicenseServlet extends HttpServlet
+{
+	/**
+	 * 
+	 */
+	public static String ENTITY_SC = "DrawioLicense";
+
+	/**
+	 * 
+	 */
+	public static SimpleDateFormat isoDateFormat = new SimpleDateFormat(
+			"yyyy-MM-dd");
+
+	/**
+	 * @see HttpServlet#HttpServlet()
+	 */
+	public LicenseServlet()
+	{
+		super();
+	}
+
+	/**
+	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
+	 */
+	protected void doPost(HttpServletRequest request,
+			HttpServletResponse response) throws ServletException, IOException
+	{
+		String domain = request.getParameter("domain");
+
+		if (domain != null)
+		{
+			try
+			{
+				response.setCharacterEncoding("UTF-8");
+				response.setContentType("application/json");
+
+				OutputStream out = response.getOutputStream();
+				out.write(getLicense(domain).getBytes());
+				out.flush();
+				out.close();
+
+				response.setStatus(HttpServletResponse.SC_OK);
+			}
+			catch (EntityNotFoundException e)
+			{
+				response.setStatus(HttpServletResponse.SC_NO_CONTENT);
+			}
+			catch (Exception e)
+			{
+				response.setStatus(
+						HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+				e.printStackTrace();
+			}
+		}
+		else
+		{
+			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+		}
+	}
+
+	/**
+	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
+	 */
+	protected String getLicense(String domain) throws EntityNotFoundException
+	{
+		// Returns test licences
+		if (domain.equals("valid-example.test"))
+		{
+			Calendar c = new GregorianCalendar();
+			c.add(Calendar.DATE, 120);
+
+			return "{\"expiry\": \"" + isoDateFormat.format(c.getTime())
+					+ "\"}";
+		}
+		else if (domain.equals("expire-example.test"))
+		{
+			Calendar c = new GregorianCalendar();
+			c.add(Calendar.DATE, 30);
+
+			return "{\"expiry\": \"" + isoDateFormat.format(c.getTime())
+					+ "\"}";
+		}
+		else if (domain.equals("expired-example.test"))
+		{
+			Calendar c = new GregorianCalendar();
+			c.add(Calendar.DATE, -10);
+
+			return "{\"expiry\": \"" + isoDateFormat.format(c.getTime())
+					+ "\"}";
+		}
+
+		// Uses the datastore to retrieve the data for the domain
+		DatastoreService datastore = DatastoreServiceFactory
+				.getDatastoreService();
+
+		// LATER: Check email first then domain
+		return datastore.get(KeyFactory.createKey(ENTITY_SC, domain))
+				.getProperty("json").toString();
+	}
+
+}

+ 366 - 0
src/com/mxgraph/online/OpenServlet.java

@@ -0,0 +1,366 @@
+/**
+ * $Id: OpenServlet.java,v 1.12 2013/10/16 12:31:25 david Exp $
+ * Copyright (c) 2011-2012, JGraph Ltd
+ */
+package com.mxgraph.online;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.net.URLDecoder;
+import java.util.Arrays;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.fileupload.FileItemIterator;
+import org.apache.commons.fileupload.FileItemStream;
+import org.apache.commons.fileupload.servlet.ServletFileUpload;
+import org.apache.commons.fileupload.util.Streams;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import com.mxgraph.io.mxCodec;
+import com.mxgraph.io.mxGraphMlCodec;
+import com.mxgraph.io.mxVsdxCodec;
+import com.mxgraph.io.gliffy.importer.GliffyDiagramConverter;
+import com.mxgraph.model.mxIGraphModel;
+import com.mxgraph.util.mxRectangle;
+import com.mxgraph.util.mxUndoableEdit.mxUndoableChange;
+import com.mxgraph.util.mxXmlUtils;
+import com.mxgraph.view.mxGraph;
+import com.mxgraph.view.mxGraphHeadless;
+
+/**
+ * Servlet implementation class OpenServlet
+ */
+public class OpenServlet extends HttpServlet
+{
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * Global switch to enabled VDX support.
+	 */
+	public static boolean ENABLE_VDX_SUPPORT = true;
+
+	/**
+	 * Global switch to enabled VSDX support.
+	 */
+	public static boolean ENABLE_VSDX_SUPPORT = true;
+
+	/**
+	 * Global switch to enabled Gliffy support.
+	 */
+	public static boolean ENABLE_GLIFFY_SUPPORT = true;
+
+	/**
+	 * Global switch to enabled GraphML support.
+	 */
+	public static boolean ENABLE_GRAPHML_SUPPORT = true;
+
+	/**
+	 * 
+	 */
+	public static final int PNG_CHUNK_ZTXT = 2052348020;
+
+	/**
+	 * 
+	 */
+	public static final int PNG_CHUNK_IEND = 1229278788;
+
+	/**
+	 * 
+	 */
+	protected static String gliffyRegex = "(?s).*\"contentType\":\\s*\"application/gliffy\\+json\".*";
+
+	/**
+	 * 
+	 */
+	protected static String graphMlRegex = "(?s).*<graphml xmlns=\".*";
+
+	/**
+	 * @see HttpServlet#HttpServlet()
+	 */
+	public OpenServlet()
+	{
+		super();
+	}
+
+	/**
+	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
+	 */
+	protected void doPost(HttpServletRequest request,
+			HttpServletResponse response) throws ServletException, IOException
+	{
+		PrintWriter writer = response.getWriter();
+
+		try
+		{
+			if (request.getContentLength() < Constants.MAX_REQUEST_SIZE)
+			{
+				String filename = "";
+				String format = null;
+				String upfile = null;
+				boolean vdx = false;
+				boolean vsdx = false;
+
+				ServletFileUpload upload = new ServletFileUpload();
+				FileItemIterator iterator = upload.getItemIterator(request);
+
+				while (iterator.hasNext())
+				{
+					FileItemStream item = iterator.next();
+					String name = item.getFieldName();
+					InputStream stream = item.openStream();
+
+					if (item.isFormField() && name.equals("format"))
+					{
+						format = Streams.asString(stream);
+					}
+					else if (name.equals("upfile"))
+					{
+						filename = item.getName();
+						vdx = filename.toLowerCase().endsWith(".vdx");
+						vsdx = filename.toLowerCase().endsWith(".vsdx");
+						
+						if (vsdx)
+						{
+							upfile = Streams.asString(stream, "ISO-8859-1");  
+						}
+						else
+						{
+							upfile = Streams.asString(stream,
+									Utils.CHARSET_FOR_URL_ENCODING);
+						}
+					}
+				}
+
+				if (format == null)
+				{
+					format = request.getParameter("format");
+				}
+
+				if (format == null)
+				{
+					format = "html";
+				}
+
+				String xml = null;
+				
+				if (filename.toLowerCase().endsWith(".png"))
+				{
+					xml = extractXmlFromPng(upfile
+							.getBytes(Utils.CHARSET_FOR_URL_ENCODING));
+				}
+				else if (ENABLE_GRAPHML_SUPPORT && upfile.matches(graphMlRegex))
+				{
+					// Creates a graph that contains a model but does not validate
+					// since that is not needed for the model and not allowed on GAE
+					mxGraph graph = new mxGraph()
+					{
+						public mxRectangle graphModelChanged(mxIGraphModel sender,
+								List<mxUndoableChange> changes)
+						{
+							return null;
+						}
+					};
+					mxGraphMlCodec.decode(mxXmlUtils.parseXml(upfile), graph);
+					xml = mxXmlUtils.getXml(new mxCodec().encode(graph.getModel()));
+				}
+				else if (ENABLE_VDX_SUPPORT && (vdx || vsdx))
+				{
+					mxGraph graph = new mxGraphHeadless();
+					graph.setConstrainChildren(false);
+					mxVsdxCodec vdxCodec = new mxVsdxCodec();
+
+					if (vdx)
+					{
+						Document doc = mxXmlUtils.parseXml(upfile);
+						//vdxCodec.decode(doc, graph);
+					}
+					else if (vsdx)
+					{
+						vdxCodec.decodeVsdx(
+								upfile.getBytes("ISO-8859-1"),
+								graph, Utils.CHARSET_FOR_URL_ENCODING);
+					}
+
+					mxCodec codec = new mxCodec();
+					Node node = codec.encode(graph.getModel());
+					// Specifies new stylesheet to be used
+					((Element) node).setAttribute("style", "default-style2");
+					xml = mxXmlUtils.getXml(node);
+
+					// Replaces VDX extension
+					int dot = filename.lastIndexOf('.');
+					filename = filename.substring(0, dot + 1) + "xml";
+				}
+				else if (ENABLE_GLIFFY_SUPPORT && upfile.matches(gliffyRegex))
+				{
+					GliffyDiagramConverter converter = new GliffyDiagramConverter(
+							upfile);
+					xml = converter.getGraphXml();
+				}
+
+				// Fallback to old data parameter
+				if (xml == null)
+				{
+					xml = (upfile == null) ? request.getParameter("data")
+							: upfile;
+				}
+
+				if (!format.equals("xml"))
+				{
+					if (xml == null || xml.length() == 0)
+					{
+						writeScript(writer,
+								"window.parent.showOpenAlert({message:window.parent.mxResources.get('invalidOrMissingFile')});");
+					}
+					else
+					{
+						// Workaround for replacement char and null byte in IE9 request
+						xml = xml.replaceAll("[\\uFFFD\\u0000]*", "");
+						writeScript(writer, "try{window.parent.setCurrentXml(decodeURIComponent('"
+							+ Utils.encodeURIComponent(xml, Utils.CHARSET_FOR_URL_ENCODING)
+							+ "'), decodeURIComponent('" + Utils.encodeURIComponent(filename, Utils.CHARSET_FOR_URL_ENCODING)
+							+ "'));}catch(e){window.parent.showOpenAlert({message:window.parent.mxResources.get('notAUtf8File')});}");
+					}
+				}
+				else
+				{
+					writer.println(xml);
+				}
+			}
+			else
+			{
+				response.setStatus(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE);
+				writeScript(
+						writer,
+						"window.parent.showOpenAlert(window.parent.mxResources.get('drawingTooLarge'));");
+			}
+		}
+		catch (Exception e)
+		{
+			e.printStackTrace();
+			response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+			writeScript(
+					writer,
+					"window.parent.showOpenAlert(window.parent.mxResources.get('invalidOrMissingFile'));");
+		}
+
+		writer.flush();
+		writer.close();
+	}
+
+	/**
+	 * Writes the given string as a script in a HTML page to the given print writer.
+	 */
+	protected void writeScript(PrintWriter writer, String js)
+	{
+		writer.println("<html>");
+		writer.println("<body>");
+		writer.println("<script type=\"text/javascript\">");
+		writer.println(js);
+		writer.println("</script>");
+		writer.println("</body>");
+		writer.println("</html>");
+	}
+
+	// NOTE: Key length must not be longer than 79 bytes (not checked)
+	protected String extractXmlFromPng(byte[] data)
+	{
+		Map<String, String> textChunks = decodeCompressedText(new ByteArrayInputStream(
+				data));
+
+		return (textChunks != null) ? textChunks.get("mxGraphModel") : null;
+	}
+
+	/**
+	 * Decodes the zTXt chunk of the given PNG image stream.
+	 */
+	public static Map<String, String> decodeCompressedText(InputStream stream)
+	{
+		Map<String, String> result = new Hashtable<String, String>();
+
+		if (!stream.markSupported())
+		{
+			stream = new BufferedInputStream(stream);
+		}
+		DataInputStream distream = new DataInputStream(stream);
+
+		try
+		{
+			long magic = distream.readLong();
+
+			if (magic != 0x89504e470d0a1a0aL)
+			{
+				throw new RuntimeException("PNGImageDecoder0");
+			}
+		}
+		catch (Exception e)
+		{
+			e.printStackTrace();
+			throw new RuntimeException("PNGImageDecoder1");
+		}
+
+		try
+		{
+			while (distream.available() > 0)
+			{
+				int length = distream.readInt();
+				int type = distream.readInt();
+				byte[] data = new byte[length];
+				distream.readFully(data);
+				distream.readInt(); // Move past the crc
+
+				if (type == PNG_CHUNK_IEND)
+				{
+					return null;
+				}
+				else if (type == PNG_CHUNK_ZTXT)
+				{
+					int currentIndex = 0;
+					while ((data[currentIndex++]) != 0)
+					{
+					}
+
+					String key = new String(data, 0, currentIndex - 1);
+
+					try
+					{
+						byte[] bytes = Arrays.copyOfRange(data,
+								currentIndex + 1, data.length);
+						String value = URLDecoder.decode(Utils.inflate(bytes),
+								Utils.CHARSET_FOR_URL_ENCODING);
+						result.put(key, value);
+					}
+					catch (Exception e)
+					{
+						e.printStackTrace();
+					}
+
+					// No need to parse the rest of the PNG
+					return result;
+				}
+			}
+		}
+		catch (Exception e)
+		{
+			e.printStackTrace();
+		}
+
+		return null;
+	}
+}

+ 80 - 0
src/com/mxgraph/online/ProxyServlet.java

@@ -0,0 +1,80 @@
+/**
+ * $Id: ProxyServlet.java,v 1.4 2013/12/13 13:18:11 david Exp $
+ * Copyright (c) 2011-2012, JGraph Ltd
+ */
+package com.mxgraph.online;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Servlet implementation ProxyServlet
+ */
+@SuppressWarnings("serial")
+public class ProxyServlet extends HttpServlet
+{
+
+	/**
+	 * @see HttpServlet#HttpServlet()
+	 */
+	public ProxyServlet()
+	{
+		super();
+	}
+
+	/**
+	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
+	 */
+	protected void doGet(HttpServletRequest request,
+			HttpServletResponse response) throws ServletException, IOException
+	{
+		String urlParam = request.getParameter("url");
+
+		if (urlParam != null)
+		{
+			request.setCharacterEncoding("UTF-8");
+			response.setCharacterEncoding("UTF-8");
+
+			OutputStream out = response.getOutputStream();
+
+			try
+			{
+				URL url = new URL(urlParam);
+				URLConnection connection = url.openConnection();
+				
+				// Status code pass-through
+				if (connection instanceof HttpURLConnection)
+				{
+					response.setStatus(((HttpURLConnection) connection).getResponseCode());
+				}
+				
+				if (connection != null)
+				{
+					response.setContentType(connection.getContentType());
+					Utils.copy(connection.getInputStream(), out);
+				}
+
+				out.flush();
+				out.close();
+			}
+			catch (Exception e)
+			{
+				response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+				e.printStackTrace();
+			}
+		}
+		else
+		{
+			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+		}
+	}
+
+}

+ 181 - 0
src/com/mxgraph/online/SaveServlet.java

@@ -0,0 +1,181 @@
+package com.mxgraph.online;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URLDecoder;
+import java.util.logging.Logger;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.mxgraph.util.mxBase64;
+
+/**
+ * Servlet implementation class SaveServlet
+ */
+public class SaveServlet extends HttpServlet
+{
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 
+	 */
+	public static String ALLOW_COMPRESSION = "allowCompression";
+
+	/**
+	 * 
+	 */
+	private static final Logger log = Logger.getLogger(SaveServlet.class
+			.getName());
+
+	/**
+	 * @see HttpServlet#HttpServlet()
+	 */
+	public SaveServlet()
+	{
+		super();
+	}
+
+	/**
+	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
+	 *      response)
+	 */
+	protected void doPost(HttpServletRequest request,
+			HttpServletResponse response) throws ServletException, IOException
+	{
+		handlePost(request, response);
+	}
+
+	public static void handlePost(HttpServletRequest request,
+			HttpServletResponse response) throws ServletException, IOException
+	{
+		if (request.getContentLength() < Constants.MAX_REQUEST_SIZE)
+		{
+			long t0 = System.currentTimeMillis();
+			String mime = request.getParameter("mime");
+			String filename = null;
+			byte[] data = null;
+
+			// Data in data param is base64 encoded and deflated
+			String enc = request.getParameter("data");
+			String xml = null;
+
+			try
+			{
+				if (enc != null && enc.length() > 0)
+				{
+					// NOTE: Simulate is used on client-side so the value is double-encoded
+					xml = Utils.inflate(mxBase64.decode(URLDecoder.decode(enc,
+							Utils.CHARSET_FOR_URL_ENCODING).getBytes()));
+				}
+				else
+				{
+					xml = request.getParameter("xml");
+				}
+
+				// Decoding is optional (no plain text values allowed here so %3C means encoded)
+				if (xml != null && xml.startsWith("%3C"))
+				{
+					xml = URLDecoder
+							.decode(xml, Utils.CHARSET_FOR_URL_ENCODING);
+				}
+
+				String format = request.getParameter("format");
+
+				if (format == null)
+				{
+					format = "xml";
+				}
+
+				if (xml != null)
+				{
+					if (mime == null)
+					{
+						if (format.equals("xml"))
+						{
+							mime = "application/xml";
+						}
+						else if (format.equals("svg"))
+						{
+							mime = "image/svg+xml";
+						}
+						else
+						{
+							mime = "text/plain";
+						}
+					}
+
+					filename = request.getParameter("filename");
+
+					if (filename != null
+							&& filename.length() > 0
+							&& !(format.equals("xml")
+									&& filename.toLowerCase().endsWith(".html") && filename
+									.toLowerCase().endsWith(".svg"))
+							&& !filename.toLowerCase().endsWith("." + format))
+					{
+						filename += "." + format;
+					}
+
+					data = xml.getBytes(Utils.CHARSET_FOR_URL_ENCODING);
+				}
+
+				if (mime != null && data != null)
+				{
+					response.setStatus(HttpServletResponse.SC_OK);
+					
+					if (filename != null)
+					{
+						response.setContentType(mime);
+						response.setHeader("Content-Disposition",
+								"attachment; filename=\"" + filename
+										+ "\"; filename*=UTF-8''" + filename);
+					}
+					else if (mime.equals("image/svg+xml"))
+					{
+						response.setContentType("image/svg+xml");
+					}
+					else
+					{
+						// Required to avoid download of file
+						response.setContentType("text/plain");
+					}
+
+					OutputStream out = response.getOutputStream();
+					out.write(data);
+					out.close();
+				}
+				else
+				{
+					response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+				}
+			}
+			catch (IllegalArgumentException e)
+			{
+				log.warning("Error parsing xml contents : " + xml
+						+ System.getProperty("line.separator")
+						+ "Original stack trace : " + e.getMessage());
+			}
+			long mem = Runtime.getRuntime().totalMemory()
+					- Runtime.getRuntime().freeMemory();
+
+			log.fine("save: ip=" + request.getRemoteAddr() + " ref=\""
+					+ request.getHeader("Referer") + "\" in="
+					+ request.getContentLength() + " enc="
+					+ ((enc != null) ? enc.length() : "[none]") + " xml="
+					+ ((xml != null) ? xml.length() : "[none]") + " dt="
+					+ request.getContentLength() + " mem=" + mem + " dt="
+					+ (System.currentTimeMillis() - t0));
+		}
+		else
+		{
+			response.setStatus(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE);
+		}
+	}
+
+}

+ 117 - 0
src/com/mxgraph/online/Utils.java

@@ -0,0 +1,117 @@
+package com.mxgraph.online;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+
+public class Utils
+{
+
+	/**
+	 * 
+	 */
+	public static String CHARSET_FOR_URL_ENCODING = "ISO-8859-1";
+
+	/**
+	 * 
+	 */
+	protected static final int IO_BUFFER_SIZE = 4 * 1024;
+
+	/**
+	 * 
+	 */
+	public static String inflate(byte[] binary) throws IOException
+	{
+		StringBuffer result = new StringBuffer();
+		InputStream in = new InflaterInputStream(new ByteArrayInputStream(
+				binary), new Inflater(true));
+
+		while (in.available() != 0)
+		{
+			byte[] buffer = new byte[IO_BUFFER_SIZE];
+			int len = in.read(buffer, 0, IO_BUFFER_SIZE);
+
+			if (len <= 0)
+			{
+				break;
+			}
+
+			result.append(new String(buffer, 0, len));
+		}
+
+		in.close();
+
+		return result.toString();
+	}
+
+	public static void copy(InputStream in, OutputStream out)
+			throws IOException
+	{
+		copy(in, out, IO_BUFFER_SIZE);
+	}
+
+	public static void copy(InputStream in, OutputStream out, int bufferSize)
+			throws IOException
+	{
+		byte[] b = new byte[bufferSize];
+		int read;
+		while ((read = in.read(b)) != -1)
+		{
+			out.write(b, 0, read);
+		}
+	}
+
+	public static String readInputStream(InputStream stream) throws IOException
+	{
+		BufferedReader reader = new BufferedReader(
+				new InputStreamReader(stream));
+		StringBuffer result = new StringBuffer();
+		String tmp = reader.readLine();
+
+		while (tmp != null)
+		{
+			result.append(tmp + "\n");
+			tmp = reader.readLine();
+		}
+
+		reader.close();
+
+		return result.toString();
+	}
+
+	/**
+	   * Encodes the passed String as UTF-8 using an algorithm that's compatible
+	   * with JavaScript's <code>encodeURIComponent</code> function. Returns
+	   * <code>null</code> if the String is <code>null</code>.
+	   * 
+	   * @param s The String to be encoded
+	   * @return the encoded String
+	   */
+	public static String encodeURIComponent(String s, String charset)
+	{
+		String result = null;
+
+		try
+		{
+			result = URLEncoder.encode(s, charset).replaceAll("\\+", "%20")
+					.replaceAll("\\%21", "!").replaceAll("\\%28", "(")
+					.replaceAll("\\%29", ")").replaceAll("\\%7E", "~");
+		}
+
+		// This exception should never occur.
+		catch (UnsupportedEncodingException e)
+		{
+			result = s;
+		}
+
+		return result;
+	}
+
+}

+ 151 - 0
src/com/mxgraph/view/mxGraphHeadless.java

@@ -0,0 +1,151 @@
+package com.mxgraph.view;
+
+import java.util.List;
+import java.util.Map;
+
+import com.mxgraph.model.mxGraphModel;
+import com.mxgraph.model.mxIGraphModel;
+import com.mxgraph.util.mxConstants;
+import com.mxgraph.util.mxEventObject;
+import com.mxgraph.util.mxRectangle;
+import com.mxgraph.util.mxUndoableEdit.mxUndoableChange;
+
+public class mxGraphHeadless extends mxGraph
+{
+	/**
+	 * Constructs a new graph with an empty
+	 * {@link com.mxgraph.model.mxGraphModel}.
+	 */
+	public mxGraphHeadless()
+	{
+		this(null, null);
+	}
+
+	/**
+	 * Constructs a new graph for the specified model. If no model is
+	 * specified, then a new, empty {@link com.mxgraph.model.mxGraphModel} is
+	 * used.
+	 * 
+	 * @param model Model that contains the graph data
+	 */
+	public mxGraphHeadless(mxIGraphModel model)
+	{
+		this(model, null);
+	}
+
+	/**
+	 * Constructs a new graph for the specified model. If no model is
+	 * specified, then a new, empty {@link com.mxgraph.model.mxGraphModel} is
+	 * used.
+	 * 
+	 * @param stylesheet The stylesheet to use for the graph.
+	 */
+	public mxGraphHeadless(mxStylesheet stylesheet)
+	{
+		this(null, stylesheet);
+	}
+
+	/**
+	 * Constructs a new graph for the specified model. If no model is
+	 * specified, then a new, empty {@link com.mxgraph.model.mxGraphModel} is
+	 * used.
+	 * 
+	 * @param model Model that contains the graph data
+	 */
+	public mxGraphHeadless(mxIGraphModel model, mxStylesheet stylesheet)
+	{
+		setModel((model != null) ? model : new mxGraphModel());
+	}
+	
+	/**
+	 * Constructs a new selection model to be used in this graph.
+	 */
+	protected mxGraphSelectionModel createSelectionModel()
+	{
+		return null;
+	}
+	
+	/**
+	 * Constructs a new stylesheet to be used in this graph.
+	 */
+	protected mxStylesheet createStylesheet()
+	{
+		return null;
+	}
+	
+	/**
+	 * Returns an array of key, value pairs representing the cell style for the
+	 * given cell. If no string is defined in the model that specifies the
+	 * style, then the default style for the cell is returned or <EMPTY_ARRAY>,
+	 * if not style can be found.
+	 * 
+	 * @param cell Cell whose style should be returned.
+	 * @return Returns the style of the cell.
+	 */
+	public Map<String, Object> getCellStyle(Object cell)
+	{
+		return mxStylesheet.EMPTY_STYLE;
+	}
+	
+	/**
+	 * Called when the graph model changes. Invokes processChange on each
+	 * item of the given array to update the view accordingly.
+	 * 
+	 * Overriden to remove validation of view
+	 */
+	public mxRectangle graphModelChanged(mxIGraphModel sender,
+			List<mxUndoableChange> changes)
+	{
+		return processChanges(changes, true, true);
+	}
+	
+	/**
+	 * Dispatches the given event name with this object as the event source.
+	 * <code>fireEvent(new mxEventObject("eventName", key1, val1, .., keyN, valN))</code>
+	 * 
+	 */
+	public void fireEvent(mxEventObject evt) {}
+	
+	/**
+	 * Returns true if the given cell is a swimlane. This implementation always
+	 * returns false.
+	 * 
+	 * @param cell Cell that should be checked. 
+	 * @return Returns true if the cell is a swimlane.
+	 */
+	public boolean isSwimlane(Object cell)
+	{
+		if (cell != null)
+		{
+			if (model.getParent(cell) != model.getRoot())
+			{
+				mxCellState state = view.getState(cell);
+				Map<String, Object> style = (state != null) ? state.getStyle()
+						: getCellStyle(cell);
+
+				if (style != null && !model.isEdge(cell))
+				{
+					return getString(style, mxConstants.STYLE_SHAPE, "")
+							.equals(mxConstants.SHAPE_SWIMLANE);
+				}
+			}
+		}
+
+		return false;
+	}
+	
+	public String getString(Map<String, Object> dict, String key,
+			String defaultValue)
+	{
+		Object value = dict.get(key);
+
+		if (value == null)
+		{
+			return defaultValue;
+		}
+		else
+		{
+			return value.toString();
+		}
+	}
+}

+ 24 - 0
src/log4j.properties

@@ -0,0 +1,24 @@
+# A default log4j configuration for log4j users.
+#
+# To use this configuration, deploy it into your application's WEB-INF/classes
+# directory.  You are also encouraged to edit it as you like.
+
+# Configure the console as our one appender
+log4j.appender.A1=org.apache.log4j.ConsoleAppender
+log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+log4j.appender.A1.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] - %m%n
+
+# tighten logging on the DataNucleus Categories
+log4j.category.DataNucleus.JDO=WARN, A1
+log4j.category.DataNucleus.Persistence=WARN, A1
+log4j.category.DataNucleus.Cache=WARN, A1
+log4j.category.DataNucleus.MetaData=WARN, A1
+log4j.category.DataNucleus.General=WARN, A1
+log4j.category.DataNucleus.Utility=WARN, A1
+log4j.category.DataNucleus.Transaction=WARN, A1
+log4j.category.DataNucleus.Datastore=WARN, A1
+log4j.category.DataNucleus.ClassLoading=WARN, A1
+log4j.category.DataNucleus.Plugin=WARN, A1
+log4j.category.DataNucleus.ValueGeneration=WARN, A1
+log4j.category.DataNucleus.Enhancer=WARN, A1
+log4j.category.DataNucleus.SchemaTool=WARN, A1

TEMPAT SAMPAH
war/WEB-INF/appengine-generated/local_db.bin


+ 29 - 0
war/WEB-INF/appengine-web.xml

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
+	<application>drawdotio</application>
+	<!-- IMPORTANT! DO NOT CHANGE THIS VALUE IN SOURCE CONTROL! -->
+	<version>test</version>
+	
+	<!-- Configure java.util.logging -->
+	<system-properties>
+		<property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
+	</system-properties>
+
+	<sessions-enabled>false</sessions-enabled>
+	<threadsafe>false</threadsafe>
+
+	<!-- FOR LOCAL TESTING OF EMBED2 WITH DYNAMIC LOADING -->
+	<static-files>
+		<include path="/styles/**" >
+			<http-header name="Access-Control-Allow-Origin" value="*" />
+    	</include>
+    	<include path="/stencils/**" >
+			<http-header name="Access-Control-Allow-Origin" value="*" />
+    	</include>
+    	<include path="/shapes/**" >
+			<http-header name="Access-Control-Allow-Origin" value="*" />
+    	</include>
+    	<include path="/**" />
+	</static-files>
+
+</appengine-web-app>

+ 1 - 0
war/WEB-INF/iconfinder_key

@@ -0,0 +1 @@
+Replace_with_your_own_iconfinder_key

+ 1 - 0
war/WEB-INF/imgur_client_id

@@ -0,0 +1 @@
+Replace_with_your_own_imgur_client_id

TEMPAT SAMPAH
war/WEB-INF/lib/appengine-api-1.0-sdk-1.9.34.jar


TEMPAT SAMPAH
war/WEB-INF/lib/appengine-api-labs.jar


TEMPAT SAMPAH
war/WEB-INF/lib/appengine-endpoints-deps.jar


TEMPAT SAMPAH
war/WEB-INF/lib/appengine-endpoints.jar


TEMPAT SAMPAH
war/WEB-INF/lib/appengine-jsr107cache-1.9.34.jar


+ 0 - 0
war/WEB-INF/lib/commons-codec-1.10.jar


Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini